com.lowagie.text.html.HtmlWriter.java Source code

Java tutorial

Introduction

Here is the source code for com.lowagie.text.html.HtmlWriter.java

Source

/*
 * $Id: HtmlWriter.java 3583 2008-08-12 00:00:09Z xlv $
 *
 * Copyright 1999, 2000, 2001, 2002 by Bruno Lowagie.
 *
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the License.
 *
 * The Original Code is 'iText, a free JAVA-PDF library'.
 *
 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
 * All Rights Reserved.
 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
 *
 * Contributor(s): all the names of the contributors are added in the source code
 * where applicable.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
 * provisions of LGPL are applicable instead of those above.  If you wish to
 * allow use of your version of this file only under the terms of the LGPL
 * License and not to allow others to use your version of this file under
 * the MPL, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the LGPL.
 * If you do not delete the provisions above, a recipient may use your version
 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the MPL as stated above or under the terms of the GNU
 * Library General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU LIBRARY GENERAL PUBLIC LICENSE for more
 * details.
 *
 * If you didn't download this code from the following link, you should check if
 * you aren't using an obsolete version:
 * http://www.lowagie.com/iText/
 */

package com.lowagie.text.html;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.EmptyStackException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.Stack;

import com.lowagie.text.Anchor;
import com.lowagie.text.Annotation;
import com.lowagie.text.BadElementException;
import com.lowagie.text.Cell;
import com.lowagie.text.Chunk;
import com.lowagie.text.DocWriter;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.Font;
import com.lowagie.text.Header;
import com.lowagie.text.HeaderFooter;
import com.lowagie.text.Image;
import com.lowagie.text.List;
import com.lowagie.text.ListItem;
import com.lowagie.text.MarkedObject;
import com.lowagie.text.MarkedSection;
import com.lowagie.text.Meta;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Row;
import com.lowagie.text.Section;
import com.lowagie.text.SimpleTable;
import com.lowagie.text.Table;
import com.lowagie.text.pdf.BaseFont;

/**
 * A <CODE>DocWriter</CODE> class for HTML.
 * <P>
 * An <CODE>HtmlWriter</CODE> can be added as a <CODE>DocListener</CODE>
 * to a certain <CODE>Document</CODE> by getting an instance.
 * Every <CODE>Element</CODE> added to the original <CODE>Document</CODE>
 * will be written to the <CODE>OutputStream</CODE> of this <CODE>HtmlWriter</CODE>.
 * <P>
 * Example:
 * <BLOCKQUOTE><PRE>
 * // creation of the document with a certain size and certain margins
 * Document document = new Document(PageSize.A4, 50, 50, 50, 50);
 * try {
 *    // this will write HTML to the Standard OutputStream
 *    <STRONG>HtmlWriter.getInstance(document, System.out);</STRONG>
 *    // this will write HTML to a file called text.html
 *    <STRONG>HtmlWriter.getInstance(document, new FileOutputStream("text.html"));</STRONG>
 *    // this will write HTML to for instance the OutputStream of a HttpServletResponse-object
 *    <STRONG>HtmlWriter.getInstance(document, response.getOutputStream());</STRONG>
 * }
 * catch(DocumentException de) {
 *    System.err.println(de.getMessage());
 * }
 * // this will close the document and all the OutputStreams listening to it
 * <STRONG>document.close();</CODE>
 * </PRE></BLOCKQUOTE>
 */

public class HtmlWriter extends DocWriter {

    // static membervariables (tags)

    /** This is a possible HTML-tag. */
    public static final byte[] BEGINCOMMENT = getISOBytes("<!-- ");

    /** This is a possible HTML-tag. */
    public static final byte[] ENDCOMMENT = getISOBytes(" -->");

    /** This is a possible HTML-tag. */
    public static final String NBSP = "&nbsp;";

    // membervariables

    /** This is the current font of the HTML. */
    protected Stack currentfont = new Stack();

    /** This is the standard font of the HTML. */
    protected Font standardfont = new Font();

    /** This is a path for images. */
    protected String imagepath = null;

    /** Stores the page number. */
    protected int pageN = 0;

    /** This is the textual part of a header */
    protected HeaderFooter header = null;

    /** This is the textual part of the footer */
    protected HeaderFooter footer = null;

    /** Store the markup properties of a MarkedObject. */
    protected Properties markup = new Properties();

    // constructor

    /**
     * Constructs a <CODE>HtmlWriter</CODE>.
     *
     * @param doc     The <CODE>Document</CODE> that has to be written as HTML
     * @param os      The <CODE>OutputStream</CODE> the writer has to write to.
     */

    protected HtmlWriter(Document doc, OutputStream os) {
        super(doc, os);

        document.addDocListener(this);
        this.pageN = document.getPageNumber();
        try {
            os.write(LT);
            os.write(getISOBytes(HtmlTags.HTML));
            os.write(GT);
            os.write(NEWLINE);
            os.write(TAB);
            os.write(LT);
            os.write(getISOBytes(HtmlTags.HEAD));
            os.write(GT);
        } catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    // get an instance of the HtmlWriter

    /**
     * Gets an instance of the <CODE>HtmlWriter</CODE>.
     *
     * @param document  The <CODE>Document</CODE> that has to be written
     * @param os  The <CODE>OutputStream</CODE> the writer has to write to.
     * @return  a new <CODE>HtmlWriter</CODE>
     */

    public static HtmlWriter getInstance(Document document, OutputStream os) {
        return new HtmlWriter(document, os);
    }

    // implementation of the DocListener methods

    /**
     * Signals that an new page has to be started.
     *
     * @return  <CODE>true</CODE> if this action succeeded, <CODE>false</CODE> if not.
     */

    public boolean newPage() {
        try {
            writeStart(HtmlTags.DIV);
            write(" ");
            write(HtmlTags.STYLE);
            write("=\"");
            writeCssProperty(Markup.CSS_KEY_PAGE_BREAK_BEFORE, Markup.CSS_VALUE_ALWAYS);
            write("\" /");
            os.write(GT);
        } catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
        return true;
    }

    /**
     * Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>.
     * 
     * @param element a high level object that has to be translated to HTML
     * @return  <CODE>true</CODE> if the element was added, <CODE>false</CODE> if not.
     * @throws  DocumentException when a document isn't open yet, or has been closed
     */

    public boolean add(Element element) throws DocumentException {
        if (pause) {
            return false;
        }
        if (open && !element.isContent()) {
            throw new DocumentException("The document is open; you can only add Elements with content.");
        }
        try {
            switch (element.type()) {
            case Element.HEADER:
                try {
                    Header h = (Header) element;
                    if (HtmlTags.STYLESHEET.equals(h.getName())) {
                        writeLink(h);
                    } else if (HtmlTags.JAVASCRIPT.equals(h.getName())) {
                        writeJavaScript(h);
                    } else {
                        writeHeader(h);
                    }
                } catch (ClassCastException cce) {
                }
                return true;
            case Element.SUBJECT:
            case Element.KEYWORDS:
            case Element.AUTHOR:
                Meta meta = (Meta) element;
                writeHeader(meta);
                return true;
            case Element.TITLE:
                addTabs(2);
                writeStart(HtmlTags.TITLE);
                os.write(GT);
                addTabs(3);
                write(HtmlEncoder.encode(((Meta) element).getContent()));
                addTabs(2);
                writeEnd(HtmlTags.TITLE);
                return true;
            case Element.CREATOR:
                writeComment("Creator: " + HtmlEncoder.encode(((Meta) element).getContent()));
                return true;
            case Element.PRODUCER:
                writeComment("Producer: " + HtmlEncoder.encode(((Meta) element).getContent()));
                return true;
            case Element.CREATIONDATE:
                writeComment("Creationdate: " + HtmlEncoder.encode(((Meta) element).getContent()));
                return true;
            case Element.MARKED:
                if (element instanceof MarkedSection) {
                    MarkedSection ms = (MarkedSection) element;
                    addTabs(1);
                    writeStart(HtmlTags.DIV);
                    writeMarkupAttributes(ms.getMarkupAttributes());
                    os.write(GT);
                    MarkedObject mo = ((MarkedSection) element).getTitle();
                    if (mo != null) {
                        markup = mo.getMarkupAttributes();
                        mo.process(this);
                    }
                    ms.process(this);
                    writeEnd(HtmlTags.DIV);
                    return true;
                } else {
                    MarkedObject mo = (MarkedObject) element;
                    markup = mo.getMarkupAttributes();
                    return mo.process(this);
                }
            default:
                write(element, 2);
                return true;
            }
        } catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    /**
     * Signals that the <CODE>Document</CODE> has been opened and that
     * <CODE>Elements</CODE> can be added.
     * <P>
     * The <CODE>HEAD</CODE>-section of the HTML-document is written.
     */

    public void open() {
        super.open();
        try {
            writeComment(Document.getVersion());
            writeComment("CreationDate: " + new Date().toString());
            addTabs(1);
            writeEnd(HtmlTags.HEAD);
            addTabs(1);
            writeStart(HtmlTags.BODY);
            if (document.leftMargin() > 0) {
                write(HtmlTags.LEFTMARGIN, String.valueOf(document.leftMargin()));
            }
            if (document.rightMargin() > 0) {
                write(HtmlTags.RIGHTMARGIN, String.valueOf(document.rightMargin()));
            }
            if (document.topMargin() > 0) {
                write(HtmlTags.TOPMARGIN, String.valueOf(document.topMargin()));
            }
            if (document.bottomMargin() > 0) {
                write(HtmlTags.BOTTOMMARGIN, String.valueOf(document.bottomMargin()));
            }
            if (pageSize.getBackgroundColor() != null) {
                write(HtmlTags.BACKGROUNDCOLOR, HtmlEncoder.encode(pageSize.getBackgroundColor()));
            }
            if (document.getJavaScript_onLoad() != null) {
                write(HtmlTags.JAVASCRIPT_ONLOAD, HtmlEncoder.encode(document.getJavaScript_onLoad()));
            }
            if (document.getJavaScript_onUnLoad() != null) {
                write(HtmlTags.JAVASCRIPT_ONUNLOAD, HtmlEncoder.encode(document.getJavaScript_onUnLoad()));
            }
            if (document.getHtmlStyleClass() != null) {
                write(Markup.HTML_ATTR_CSS_CLASS, document.getHtmlStyleClass());
            }
            os.write(GT);
            initHeader(); // line added by David Freels
        } catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    /**
     * Signals that the <CODE>Document</CODE> was closed and that no other
     * <CODE>Elements</CODE> will be added.
     */

    public void close() {
        try {
            initFooter(); // line added by David Freels
            addTabs(1);
            writeEnd(HtmlTags.BODY);
            os.write(NEWLINE);
            writeEnd(HtmlTags.HTML);
            super.close();
        } catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    // some protected methods

    /**
     * Adds the header to the top of the </CODE>Document</CODE>
     */

    protected void initHeader() {
        if (header != null) {
            try {
                add(header.paragraph());
            } catch (Exception e) {
                throw new ExceptionConverter(e);
            }
        }
    }

    /**
     *  Adds the header to the top of the </CODE>Document</CODE>
     */

    protected void initFooter() {
        if (footer != null) {
            try {
                // Set the page number. HTML has no notion of a page, so it should always
                // add up to 1
                footer.setPageNumber(pageN + 1);
                add(footer.paragraph());
            } catch (Exception e) {
                throw new ExceptionConverter(e);
            }
        }
    }

    /**
     * Writes a Metatag in the header.
     *
     * @param   meta   the element that has to be written
     * @throws  IOException
     */

    protected void writeHeader(Meta meta) throws IOException {
        addTabs(2);
        writeStart(HtmlTags.META);
        switch (meta.type()) {
        case Element.HEADER:
            write(HtmlTags.NAME, ((Header) meta).getName());
            break;
        case Element.SUBJECT:
            write(HtmlTags.NAME, HtmlTags.SUBJECT);
            break;
        case Element.KEYWORDS:
            write(HtmlTags.NAME, HtmlTags.KEYWORDS);
            break;
        case Element.AUTHOR:
            write(HtmlTags.NAME, HtmlTags.AUTHOR);
            break;
        }
        write(HtmlTags.CONTENT, HtmlEncoder.encode(meta.getContent()));
        writeEnd();
    }

    /**
     * Writes a link in the header.
     *
     * @param   header   the element that has to be written
     * @throws  IOException
     */

    protected void writeLink(Header header) throws IOException {
        addTabs(2);
        writeStart(HtmlTags.LINK);
        write(HtmlTags.REL, header.getName());
        write(HtmlTags.TYPE, HtmlTags.TEXT_CSS);
        write(HtmlTags.REFERENCE, header.getContent());
        writeEnd();
    }

    /**
     * Writes a JavaScript section or, if the markup attribute HtmlTags.URL is set, a JavaScript reference in the header.
     *
     * @param   header   the element that has to be written
     * @throws  IOException
     */

    protected void writeJavaScript(Header header) throws IOException {
        addTabs(2);
        writeStart(HtmlTags.SCRIPT);
        write(HtmlTags.LANGUAGE, HtmlTags.JAVASCRIPT);
        if (markup.size() > 0) {
            /* JavaScript reference example:
             *
             * <script language="JavaScript" src="/myPath/MyFunctions.js"/>
             */
            writeMarkupAttributes(markup);
            os.write(GT);
            writeEnd(HtmlTags.SCRIPT);
        } else {
            /* JavaScript coding convention:
             *
             * <script language="JavaScript" type="text/javascript">
             * <!--
             * // ... JavaScript methods ...
             * //-->
             * </script>
             */
            write(HtmlTags.TYPE, Markup.HTML_VALUE_JAVASCRIPT);
            os.write(GT);
            addTabs(2);
            write(new String(BEGINCOMMENT) + "\n");
            write(header.getContent());
            addTabs(2);
            write("//" + new String(ENDCOMMENT));
            addTabs(2);
            writeEnd(HtmlTags.SCRIPT);
        }
    }

    /**
     * Writes some comment.
     * <P>
     * This method writes some comment.
     *
     * @param comment   the comment that has to be written
     * @throws  IOException
     */

    protected void writeComment(String comment) throws IOException {
        addTabs(2);
        os.write(BEGINCOMMENT);
        write(comment);
        os.write(ENDCOMMENT);
    }

    // public methods

    /**
     * Changes the standardfont.
     *
     * @param standardfont  The font
     */

    public void setStandardFont(Font standardfont) {
        this.standardfont = standardfont;
    }

    /**
     * Checks if a given font is the same as the font that was last used.
     *
     * @param   font    the font of an object
     * @return  true if the font differs
     */

    public boolean isOtherFont(Font font) {
        try {
            Font cFont = (Font) currentfont.peek();
            if (cFont.compareTo(font) == 0)
                return false;
            return true;
        } catch (EmptyStackException ese) {
            if (standardfont.compareTo(font) == 0)
                return false;
            return true;
        }
    }

    /**
     * Sets the basepath for images.
     * <P>
     * This is especially useful if you add images using a file,
     * rather than an URL. In PDF there is no problem, since
     * the images are added inline, but in HTML it is sometimes
     * necessary to use a relative path or a special path to some
     * images directory.
     *
     * @param imagepath the new imagepath
     */

    public void setImagepath(String imagepath) {
        this.imagepath = imagepath;
    }

    /**
     * Resets the imagepath.
     */

    public void resetImagepath() {
        imagepath = null;
    }

    /**
     * Changes the header of this document.
     *
     * @param header    the new header
     */

    public void setHeader(HeaderFooter header) {
        this.header = header;
    }

    /**
     * Changes the footer of this document.
     *
     * @param footer    the new footer
     */

    public void setFooter(HeaderFooter footer) {
        this.footer = footer;
    }

    /**
     * Signals that a <CODE>String</CODE> was added to the <CODE>Document</CODE>.
     * 
     * @param string a String to add to the HTML
     * @return  <CODE>true</CODE> if the string was added, <CODE>false</CODE> if not.
     */

    public boolean add(String string) {
        if (pause) {
            return false;
        }
        try {
            write(string);
            return true;
        } catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }
    }

    /**
     * Writes the HTML representation of an element.
     *
     * @param   element     the element
     * @param   indent      the indentation
     * @throws IOException
     */

    protected void write(Element element, int indent) throws IOException {
        Properties styleAttributes = null;
        switch (element.type()) {
        case Element.MARKED: {
            try {
                add(element);
            } catch (DocumentException e) {
                e.printStackTrace();
            }
            return;
        }
        case Element.CHUNK: {
            Chunk chunk = (Chunk) element;
            // if the chunk contains an image, return the image representation
            Image image = chunk.getImage();
            if (image != null) {
                write(image, indent);
                return;
            }

            if (chunk.isEmpty())
                return;
            HashMap attributes = chunk.getAttributes();
            if (attributes != null && attributes.get(Chunk.NEWPAGE) != null) {
                return;
            }
            boolean tag = isOtherFont(chunk.getFont()) || markup.size() > 0;
            if (tag) {
                // start span tag
                addTabs(indent);
                writeStart(HtmlTags.SPAN);
                if (isOtherFont(chunk.getFont())) {
                    write(chunk.getFont(), null);
                }
                writeMarkupAttributes(markup);
                os.write(GT);
            }
            if (attributes != null && attributes.get(Chunk.SUBSUPSCRIPT) != null) {
                // start sup or sub tag
                if (((Float) attributes.get(Chunk.SUBSUPSCRIPT)).floatValue() > 0) {
                    writeStart(HtmlTags.SUP);
                } else {
                    writeStart(HtmlTags.SUB);
                }
                os.write(GT);
            }
            // contents
            write(HtmlEncoder.encode(chunk.getContent()));
            if (attributes != null && attributes.get(Chunk.SUBSUPSCRIPT) != null) {
                // end sup or sub tag
                os.write(LT);
                os.write(FORWARD);
                if (((Float) attributes.get(Chunk.SUBSUPSCRIPT)).floatValue() > 0) {
                    write(HtmlTags.SUP);
                } else {
                    write(HtmlTags.SUB);
                }
                os.write(GT);
            }
            if (tag) {
                // end tag
                writeEnd(Markup.HTML_TAG_SPAN);
            }
            return;
        }
        case Element.PHRASE: {
            Phrase phrase = (Phrase) element;
            styleAttributes = new Properties();
            if (phrase.hasLeading())
                styleAttributes.setProperty(Markup.CSS_KEY_LINEHEIGHT, phrase.getLeading() + "pt");

            // start tag
            addTabs(indent);
            writeStart(Markup.HTML_TAG_SPAN);
            writeMarkupAttributes(markup);
            write(phrase.getFont(), styleAttributes);
            os.write(GT);
            currentfont.push(phrase.getFont());
            // contents
            for (Iterator i = phrase.iterator(); i.hasNext();) {
                write((Element) i.next(), indent + 1);
            }
            // end tag
            addTabs(indent);
            writeEnd(Markup.HTML_TAG_SPAN);
            currentfont.pop();
            return;
        }
        case Element.ANCHOR: {
            Anchor anchor = (Anchor) element;
            styleAttributes = new Properties();
            if (anchor.hasLeading())
                styleAttributes.setProperty(Markup.CSS_KEY_LINEHEIGHT, anchor.getLeading() + "pt");

            // start tag
            addTabs(indent);
            writeStart(HtmlTags.ANCHOR);
            if (anchor.getName() != null) {
                write(HtmlTags.NAME, anchor.getName());
            }
            if (anchor.getReference() != null) {
                write(HtmlTags.REFERENCE, anchor.getReference());
            }
            writeMarkupAttributes(markup);
            write(anchor.getFont(), styleAttributes);
            os.write(GT);
            currentfont.push(anchor.getFont());
            // contents
            for (Iterator i = anchor.iterator(); i.hasNext();) {
                write((Element) i.next(), indent + 1);
            }
            // end tag
            addTabs(indent);
            writeEnd(HtmlTags.ANCHOR);
            currentfont.pop();
            return;
        }
        case Element.PARAGRAPH: {
            Paragraph paragraph = (Paragraph) element;
            styleAttributes = new Properties();
            if (paragraph.hasLeading())
                styleAttributes.setProperty(Markup.CSS_KEY_LINEHEIGHT, paragraph.getTotalLeading() + "pt");
            // start tag
            addTabs(indent);
            writeStart(HtmlTags.DIV);
            writeMarkupAttributes(markup);
            String alignment = HtmlEncoder.getAlignment(paragraph.getAlignment());
            if (!"".equals(alignment)) {
                write(HtmlTags.ALIGN, alignment);
            }
            write(paragraph.getFont(), styleAttributes);
            os.write(GT);
            currentfont.push(paragraph.getFont());
            // contents
            for (Iterator i = paragraph.iterator(); i.hasNext();) {
                write((Element) i.next(), indent + 1);
            }
            // end tag
            addTabs(indent);
            writeEnd(HtmlTags.DIV);
            currentfont.pop();
            return;
        }
        case Element.SECTION:
        case Element.CHAPTER: {
            // part of the start tag + contents
            writeSection((Section) element, indent);
            return;
        }
        case Element.LIST: {
            List list = (List) element;
            // start tag
            addTabs(indent);
            if (list.isNumbered()) {
                writeStart(HtmlTags.ORDEREDLIST);
            } else {
                writeStart(HtmlTags.UNORDEREDLIST);
            }
            writeMarkupAttributes(markup);
            os.write(GT);
            // contents
            for (Iterator i = list.getItems().iterator(); i.hasNext();) {
                write((Element) i.next(), indent + 1);
            }
            // end tag
            addTabs(indent);
            if (list.isNumbered()) {
                writeEnd(HtmlTags.ORDEREDLIST);
            } else {
                writeEnd(HtmlTags.UNORDEREDLIST);
            }
            return;
        }
        case Element.LISTITEM: {
            ListItem listItem = (ListItem) element;
            styleAttributes = new Properties();
            if (listItem.hasLeading())
                styleAttributes.setProperty(Markup.CSS_KEY_LINEHEIGHT, listItem.getTotalLeading() + "pt");

            // start tag
            addTabs(indent);
            writeStart(HtmlTags.LISTITEM);
            writeMarkupAttributes(markup);
            write(listItem.getFont(), styleAttributes);
            os.write(GT);
            currentfont.push(listItem.getFont());
            // contents
            for (Iterator i = listItem.iterator(); i.hasNext();) {
                write((Element) i.next(), indent + 1);
            }
            // end tag
            addTabs(indent);
            writeEnd(HtmlTags.LISTITEM);
            currentfont.pop();
            return;
        }
        case Element.CELL: {
            Cell cell = (Cell) element;

            // start tag
            addTabs(indent);
            if (cell.isHeader()) {
                writeStart(HtmlTags.HEADERCELL);
            } else {
                writeStart(HtmlTags.CELL);
            }
            writeMarkupAttributes(markup);
            if (cell.getBorderWidth() != Rectangle.UNDEFINED) {
                write(HtmlTags.BORDERWIDTH, String.valueOf(cell.getBorderWidth()));
            }
            if (cell.getBorderColor() != null) {
                write(HtmlTags.BORDERCOLOR, HtmlEncoder.encode(cell.getBorderColor()));
            }
            if (cell.getBackgroundColor() != null) {
                write(HtmlTags.BACKGROUNDCOLOR, HtmlEncoder.encode(cell.getBackgroundColor()));
            }
            String alignment = HtmlEncoder.getAlignment(cell.getHorizontalAlignment());
            if (!"".equals(alignment)) {
                write(HtmlTags.HORIZONTALALIGN, alignment);
            }
            alignment = HtmlEncoder.getAlignment(cell.getVerticalAlignment());
            if (!"".equals(alignment)) {
                write(HtmlTags.VERTICALALIGN, alignment);
            }
            if (cell.getWidthAsString() != null) {
                write(HtmlTags.WIDTH, cell.getWidthAsString());
            }
            if (cell.getColspan() != 1) {
                write(HtmlTags.COLSPAN, String.valueOf(cell.getColspan()));
            }
            if (cell.getRowspan() != 1) {
                write(HtmlTags.ROWSPAN, String.valueOf(cell.getRowspan()));
            }
            if (cell.getMaxLines() == 1) {
                write(HtmlTags.STYLE, "white-space: nowrap;");
            }
            os.write(GT);
            // contents
            if (cell.isEmpty()) {
                write(NBSP);
            } else {
                for (Iterator i = cell.getElements(); i.hasNext();) {
                    write((Element) i.next(), indent + 1);
                }
            }
            // end tag
            addTabs(indent);
            if (cell.isHeader()) {
                writeEnd(HtmlTags.HEADERCELL);
            } else {
                writeEnd(HtmlTags.CELL);
            }
            return;
        }
        case Element.ROW: {
            Row row = (Row) element;

            // start tag
            addTabs(indent);
            writeStart(HtmlTags.ROW);
            writeMarkupAttributes(markup);
            os.write(GT);
            // contents
            Element cell;
            for (int i = 0; i < row.getColumns(); i++) {
                if ((cell = (Element) row.getCell(i)) != null) {
                    write(cell, indent + 1);
                }
            }
            // end tag
            addTabs(indent);
            writeEnd(HtmlTags.ROW);
            return;
        }
        case Element.TABLE: {
            Table table;
            try {
                table = (Table) element;
            } catch (ClassCastException cce) {
                try {
                    table = ((SimpleTable) element).createTable();
                } catch (BadElementException e) {
                    throw new ExceptionConverter(e);
                }
            }
            table.complete();
            // start tag
            addTabs(indent);
            writeStart(HtmlTags.TABLE);
            writeMarkupAttributes(markup);
            os.write(SPACE);
            write(HtmlTags.WIDTH);
            os.write(EQUALS);
            os.write(QUOTE);
            write(String.valueOf(table.getWidth()));
            if (!table.isLocked()) {
                write("%");
            }
            os.write(QUOTE);
            String alignment = HtmlEncoder.getAlignment(table.getAlignment());
            if (!"".equals(alignment)) {
                write(HtmlTags.ALIGN, alignment);
            }
            write(HtmlTags.CELLPADDING, String.valueOf(table.getPadding()));
            write(HtmlTags.CELLSPACING, String.valueOf(table.getSpacing()));
            if (table.getBorderWidth() != Rectangle.UNDEFINED) {
                write(HtmlTags.BORDERWIDTH, String.valueOf(table.getBorderWidth()));
            }
            if (table.getBorderColor() != null) {
                write(HtmlTags.BORDERCOLOR, HtmlEncoder.encode(table.getBorderColor()));
            }
            if (table.getBackgroundColor() != null) {
                write(HtmlTags.BACKGROUNDCOLOR, HtmlEncoder.encode(table.getBackgroundColor()));
            }
            os.write(GT);
            // contents
            Row row;
            for (Iterator iterator = table.iterator(); iterator.hasNext();) {
                row = (Row) iterator.next();
                write(row, indent + 1);
            }
            // end tag
            addTabs(indent);
            writeEnd(HtmlTags.TABLE);
            return;
        }
        case Element.ANNOTATION: {
            Annotation annotation = (Annotation) element;
            writeComment(annotation.title() + ": " + annotation.content());
            return;
        }
        case Element.IMGRAW:
        case Element.JPEG:
        case Element.JPEG2000:
        case Element.IMGTEMPLATE: {
            Image image = (Image) element;
            if (image.getUrl() == null) {
                return;
            }

            // start tag
            addTabs(indent);
            writeStart(HtmlTags.IMAGE);
            String path = image.getUrl().toString();
            if (imagepath != null) {
                if (path.indexOf('/') > 0) {
                    path = imagepath + path.substring(path.lastIndexOf('/') + 1);
                } else {
                    path = imagepath + path;
                }
            }
            write(HtmlTags.URL, path);
            if ((image.getAlignment() & Image.RIGHT) > 0) {
                write(HtmlTags.ALIGN, HtmlTags.ALIGN_RIGHT);
            } else if ((image.getAlignment() & Image.MIDDLE) > 0) {
                write(HtmlTags.ALIGN, HtmlTags.ALIGN_MIDDLE);
            } else {
                write(HtmlTags.ALIGN, HtmlTags.ALIGN_LEFT);
            }
            if (image.getAlt() != null) {
                write(HtmlTags.ALT, image.getAlt());
            }
            write(HtmlTags.PLAINWIDTH, String.valueOf(image.getScaledWidth()));
            write(HtmlTags.PLAINHEIGHT, String.valueOf(image.getScaledHeight()));
            writeMarkupAttributes(markup);
            writeEnd();
            return;
        }

        default:
            return;
        }
    }

    /**
     * Writes the HTML representation of a section.
     *
     * @param   section     the section to write
     * @param   indent      the indentation
     * @throws IOException
     */

    protected void writeSection(Section section, int indent) throws IOException {
        if (section.getTitle() != null) {
            int depth = section.getDepth() - 1;
            if (depth > 5) {
                depth = 5;
            }
            Properties styleAttributes = new Properties();
            if (section.getTitle().hasLeading())
                styleAttributes.setProperty(Markup.CSS_KEY_LINEHEIGHT, section.getTitle().getTotalLeading() + "pt");
            // start tag
            addTabs(indent);
            writeStart(HtmlTags.H[depth]);
            write(section.getTitle().getFont(), styleAttributes);
            String alignment = HtmlEncoder.getAlignment(section.getTitle().getAlignment());
            if (!"".equals(alignment)) {
                write(HtmlTags.ALIGN, alignment);
            }
            writeMarkupAttributes(markup);
            os.write(GT);
            currentfont.push(section.getTitle().getFont());
            // contents
            for (Iterator i = section.getTitle().iterator(); i.hasNext();) {
                write((Element) i.next(), indent + 1);
            }
            // end tag
            addTabs(indent);
            writeEnd(HtmlTags.H[depth]);
            currentfont.pop();
        }
        for (Iterator i = section.iterator(); i.hasNext();) {
            write((Element) i.next(), indent);
        }
    }

    /**
     * Writes the representation of a <CODE>Font</CODE>.
     *
     * @param font              a <CODE>Font</CODE>
     * @param styleAttributes   the style of the font
     * @throws IOException
     */

    protected void write(Font font, Properties styleAttributes) throws IOException {
        if (font == null || !isOtherFont(font) /* || styleAttributes == null*/)
            return;
        write(" ");
        write(HtmlTags.STYLE);
        write("=\"");
        if (styleAttributes != null) {
            String key;
            for (Enumeration e = styleAttributes.propertyNames(); e.hasMoreElements();) {
                key = (String) e.nextElement();
                writeCssProperty(key, styleAttributes.getProperty(key));
            }
        }
        if (isOtherFont(font)) {
            writeCssProperty(Markup.CSS_KEY_FONTFAMILY, font.getFamilyname());

            if (font.getSize() != Font.UNDEFINED) {
                writeCssProperty(Markup.CSS_KEY_FONTSIZE, font.getSize() + "pt");
            }
            if (font.getColor() != null) {
                writeCssProperty(Markup.CSS_KEY_COLOR, HtmlEncoder.encode(font.getColor()));
            }

            int fontstyle = font.getStyle();
            BaseFont bf = font.getBaseFont();
            if (bf != null) {
                String ps = bf.getPostscriptFontName().toLowerCase();
                if (ps.indexOf("bold") >= 0) {
                    if (fontstyle == Font.UNDEFINED)
                        fontstyle = 0;
                    fontstyle |= Font.BOLD;
                }
                if (ps.indexOf("italic") >= 0 || ps.indexOf("oblique") >= 0) {
                    if (fontstyle == Font.UNDEFINED)
                        fontstyle = 0;
                    fontstyle |= Font.ITALIC;
                }
            }
            if (fontstyle != Font.UNDEFINED && fontstyle != Font.NORMAL) {
                switch (fontstyle & Font.BOLDITALIC) {
                case Font.BOLD:
                    writeCssProperty(Markup.CSS_KEY_FONTWEIGHT, Markup.CSS_VALUE_BOLD);
                    break;
                case Font.ITALIC:
                    writeCssProperty(Markup.CSS_KEY_FONTSTYLE, Markup.CSS_VALUE_ITALIC);
                    break;
                case Font.BOLDITALIC:
                    writeCssProperty(Markup.CSS_KEY_FONTWEIGHT, Markup.CSS_VALUE_BOLD);
                    writeCssProperty(Markup.CSS_KEY_FONTSTYLE, Markup.CSS_VALUE_ITALIC);
                    break;
                }

                // CSS only supports one decoration tag so if both are specified
                // only one of the two will display
                if ((fontstyle & Font.UNDERLINE) > 0) {
                    writeCssProperty(Markup.CSS_KEY_TEXTDECORATION, Markup.CSS_VALUE_UNDERLINE);
                }
                if ((fontstyle & Font.STRIKETHRU) > 0) {
                    writeCssProperty(Markup.CSS_KEY_TEXTDECORATION, Markup.CSS_VALUE_LINETHROUGH);
                }
            }
        }
        write("\"");
    }

    /**
     * Writes out a CSS property.
     * @param prop a CSS property
     * @param value the value of the CSS property
     * @throws IOException
     */
    protected void writeCssProperty(String prop, String value) throws IOException {
        write(new StringBuffer(prop).append(": ").append(value).append("; ").toString());
    }
}