iText 5 itextpdf.jar Source Code

itextpdf.jar is a component in iText 5 Java library to provide core functionalities. iText Java library allows you to generate and manage PDF documents.

The Source Code files are provided at iText GitHub site.

You can compile it to generate your JAR file, using pom.xml as the build configuration file.

The source code of itextpdf-5.5.14.jar is provided below:

✍: FYIcenter.com

com/itextpdf/text/pdf/PdfDocument.java

/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2020 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program 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 Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.text.pdf;

import com.itextpdf.text.*;
import com.itextpdf.text.List;
import com.itextpdf.text.api.WriterOperation;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.io.TempFileCache;
import com.itextpdf.text.log.LoggerFactory;
import com.itextpdf.text.pdf.collection.PdfCollection;
import com.itextpdf.text.pdf.draw.DrawInterface;
import com.itextpdf.text.pdf.interfaces.IAccessibleElement;
import com.itextpdf.text.pdf.internal.PdfAnnotationsImp;
import com.itextpdf.text.pdf.internal.PdfViewerPreferencesImp;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.*;

/**
 * <CODE>PdfDocument</CODE> is the class that is used by <CODE>PdfWriter</CODE>
 * to translate a <CODE>Document</CODE> into a PDF with different pages.
 * <P>
 * A <CODE>PdfDocument</CODE> always listens to a <CODE>Document</CODE>
 * and adds the Pdf representation of every <CODE>Element</CODE> that is
 * added to the <CODE>Document</CODE>.
 *
 * @see		com.itextpdf.text.Document
 * @see		com.itextpdf.text.DocListener
 * @see		PdfWriter
 * @since	2.0.8 (class was package-private before)
 */

public class PdfDocument extends Document {

    /**
     * <CODE>PdfInfo</CODE> is the PDF InfoDictionary.
     * <P>
     * A document's trailer may contain a reference to an Info dictionary that provides information
     * about the document. This optional dictionary may contain one or more keys, whose values
     * should be strings.<BR>
     * This object is described in the 'Portable Document Format Reference Manual version 1.3'
     * section 6.10 (page 120-121)
     * @since	2.0.8 (PdfDocument was package-private before)
     */

    public static class PdfInfo extends PdfDictionary {

        /**
         * Construct a <CODE>PdfInfo</CODE>-object.
         */

        PdfInfo() {
            super();
            addProducer();
            addCreationDate();
        }

        /**
         * Constructs a <CODE>PdfInfo</CODE>-object.
         *
         * @param		author		name of the author of the document
         * @param		title		title of the document
         * @param		subject		subject of the document
         */

        PdfInfo(final String author, final String title, final String subject) {
            this();
            addTitle(title);
            addSubject(subject);
            addAuthor(author);
        }

        /**
         * Adds the title of the document.
         *
         * @param	title		the title of the document
         */

        void addTitle(final String title) {
            put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE));
        }

        /**
         * Adds the subject to the document.
         *
         * @param	subject		the subject of the document
         */

        void addSubject(final String subject) {
            put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE));
        }

        /**
         * Adds some keywords to the document.
         *
         * @param	keywords		the keywords of the document
         */

        void addKeywords(final String keywords) {
            put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE));
        }

        /**
         * Adds the name of the author to the document.
         *
         * @param	author		the name of the author
         */

        void addAuthor(final String author) {
            put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE));
        }

        /**
         * Adds the name of the creator to the document.
         *
         * @param	creator		the name of the creator
         */

        void addCreator(final String creator) {
            put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE));
        }

        /**
         * Adds the name of the producer to the document.
         */

        void addProducer() {
            put(PdfName.PRODUCER, new PdfString(Version.getInstance().getVersion()));
        }

        /**
         * Adds the date of creation to the document.
         */

        void addCreationDate() {
            PdfString date = new PdfDate();
            put(PdfName.CREATIONDATE, date);
            put(PdfName.MODDATE, date);
        }

        void addkey(final String key, final String value) {
            if (key.equals("Producer") || key.equals("CreationDate"))
                return;
            put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE));
        }
    }

    /**
     * <CODE>PdfCatalog</CODE> is the PDF Catalog-object.
     * <P>
     * The Catalog is a dictionary that is the root node of the document. It contains a reference
     * to the tree of pages contained in the document, a reference to the tree of objects representing
     * the document's outline, a reference to the document's article threads, and the list of named
     * destinations. In addition, the Catalog indicates whether the document's outline or thumbnail
     * page images should be displayed automatically when the document is viewed and whether some location
     * other than the first page should be shown when the document is opened.<BR>
     * In this class however, only the reference to the tree of pages is implemented.<BR>
     * This object is described in the 'Portable Document Format Reference Manual version 1.3'
     * section 6.2 (page 67-71)
     */

    static class PdfCatalog extends PdfDictionary {

        /** The writer writing the PDF for which we are creating this catalog object. */
        PdfWriter writer;

        /**
         * Constructs a <CODE>PdfCatalog</CODE>.
         *
         * @param		pages		an indirect reference to the root of the document's Pages tree.
         * @param writer the writer the catalog applies to
         */

        PdfCatalog(final PdfIndirectReference pages, final PdfWriter writer) {
            super(CATALOG);
            this.writer = writer;
            put(PdfName.PAGES, pages);
        }

        /**
         * Adds the names of the named destinations to the catalog.
         * @param localDestinations the local destinations
         * @param documentLevelJS the javascript used in the document
         * @param documentFileAttachment	the attached files
         * @param writer the writer the catalog applies to
         */
        void addNames(final TreeMap<String, Destination> localDestinations, final HashMap<String, PdfObject> documentLevelJS, final HashMap<String, PdfObject> documentFileAttachment, final PdfWriter writer) {
            if (localDestinations.isEmpty() && documentLevelJS.isEmpty() && documentFileAttachment.isEmpty())
                return;
            try {
                PdfDictionary names = new PdfDictionary();
                if (!localDestinations.isEmpty()) {
                    HashMap<String, PdfObject> destmap = new HashMap<String, PdfObject>();
                    for (Map.Entry<String, Destination> entry : localDestinations.entrySet()) {
                        String name = entry.getKey();
                        Destination dest = entry.getValue();
                        if (dest.destination == null) //no destination
                            continue;
                        destmap.put(name, dest.reference);
                    }
                    if (destmap.size() > 0) {
                        names.put(PdfName.DESTS, writer.addToBody(PdfNameTree.writeTree(destmap, writer)).getIndirectReference());
                    }
                }
                if (!documentLevelJS.isEmpty()) {
                    PdfDictionary tree = PdfNameTree.writeTree(documentLevelJS, writer);
                    names.put(PdfName.JAVASCRIPT, writer.addToBody(tree).getIndirectReference());
                }
                if (!documentFileAttachment.isEmpty()) {
                    names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference());
                }
                if (names.size() > 0)
                    put(PdfName.NAMES, writer.addToBody(names).getIndirectReference());
            }
            catch (IOException e) {
                throw new ExceptionConverter(e);
            }
        }

        /**
         * Adds an open action to the catalog.
         * @param	action	the action that will be triggered upon opening the document
         */
        void setOpenAction(final PdfAction action) {
            put(PdfName.OPENACTION, action);
        }


        /**
         * Sets the document level additional actions.
         * @param actions   dictionary of actions
         */
        void setAdditionalActions(final PdfDictionary actions) {
            try {
                put(PdfName.AA, writer.addToBody(actions).getIndirectReference());
            } catch (Exception e) {
                throw new ExceptionConverter(e);
            }
        }
    }

// CONSTRUCTING A PdfDocument/PdfWriter INSTANCE

    /**
     * Constructs a new PDF document.
     */
    public PdfDocument() {
        super();
        addProducer();
        addCreationDate();
    }

    /** The <CODE>PdfWriter</CODE>. */
    protected PdfWriter writer;

    private HashMap<AccessibleElementId, PdfStructureElement> structElements = new HashMap<AccessibleElementId, PdfStructureElement>();

    //fields for external caching support
    private TempFileCache externalCache;
    private HashMap<AccessibleElementId, TempFileCache.ObjectPosition> externallyStoredStructElements = new HashMap<AccessibleElementId, TempFileCache.ObjectPosition>();
    private HashMap<AccessibleElementId, AccessibleElementId> elementsParents = new HashMap<AccessibleElementId, AccessibleElementId>();
    private boolean isToUseExternalCache = false;


    protected boolean openMCDocument = false;

    protected HashMap<Object, int[]> structParentIndices = new HashMap<Object, int[]>();

    protected HashMap<Object, Integer> markPoints = new HashMap<Object, Integer>();

    /**
     * Adds a <CODE>PdfWriter</CODE> to the <CODE>PdfDocument</CODE>.
     *
     * @param writer the <CODE>PdfWriter</CODE> that writes everything
     *                     what is added to this document to an outputstream.
     * @throws DocumentException on error
     */
    public void addWriter(final PdfWriter writer) throws DocumentException {
        if (this.writer == null) {
            this.writer = writer;
            annotationsImp = new PdfAnnotationsImp(writer);
            return;
        }
        throw new DocumentException(MessageLocalization.getComposedMessage("you.can.only.add.a.writer.to.a.pdfdocument.once"));
    }

// LISTENER METHODS START

//	[L0] ElementListener interface

    /** This is the PdfContentByte object, containing the text. */
    protected PdfContentByte text;

    /** This is the PdfContentByte object, containing the borders and other Graphics. */
    protected PdfContentByte graphics;

    /** This represents the leading of the lines. */
    protected float leading = 0;

    /**
     * Getter for the current leading.
     * @return	the current leading
     * @since	2.1.2
     */
    public float getLeading() {
        return leading;
    }

    /**
     * Setter for the current leading.
     * @param	leading the current leading
     * @since	2.1.6
     */
    void setLeading(final float leading) {
        this.leading = leading;
    }

    /** This represents the current alignment of the PDF Elements. */
    protected int alignment = Element.ALIGN_LEFT;

    /** This is the current height of the document. */
    protected float currentHeight = 0;

    /**
     * Signals that onParagraph is valid (to avoid that a Chapter/Section title is treated as a Paragraph).
     * @since 2.1.2
     */
    protected boolean isSectionTitle = false;

    /** The current active <CODE>PdfAction</CODE> when processing an <CODE>Anchor</CODE>. */
    protected PdfAction anchorAction = null;

    /**
     * The current tab settings.
     * @return	the current
     * @since 5.4.0
     */
    protected TabSettings tabSettings;

    /**
     * Signals that the current leading has to be subtracted from a YMark object when positive
     * and save current leading
     * @since 2.1.2
     */
    private Stack<Float> leadingStack = new Stack<Float>();

    private PdfBody body;

    /**
     * Save current @leading
     */
    protected void pushLeading() {
        leadingStack.push(leading);
    }

    /**
     * Restore @leading from leadingStack
     */
    protected void popLeading() {
        leading = leadingStack.pop();
        if (leadingStack.size() > 0)
            leading = leadingStack.peek();
    }

    /**
     * Getter for the current tab stops.
     * @since	5.4.0
     */
    public TabSettings getTabSettings() {
        return tabSettings;
    }

    /**
     * Setter for the current tab stops.
     * @param	tabSettings the current tab settings
     * @since	5.4.0
     */
    public void setTabSettings(TabSettings tabSettings) {
        this.tabSettings = tabSettings;
    }

    /**
     * Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>.
     *
     * @param element the element to add
     * @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
     */
    @Override
    public boolean add(final Element element) throws DocumentException {
        if (writer != null && writer.isPaused()) {
            return false;
        }
        try {
            if (element.type() != Element.DIV) {
                flushFloatingElements();
            }
            // TODO refactor this uber long switch to State/Strategy or something ...
            switch(element.type()) {
                // Information (headers)
                case Element.HEADER:
                    info.addkey(((Meta)element).getName(), ((Meta)element).getContent());
                    break;
                case Element.TITLE:
                    info.addTitle(((Meta)element).getContent());
                    break;
                case Element.SUBJECT:
                    info.addSubject(((Meta)element).getContent());
                    break;
                case Element.KEYWORDS:
                    info.addKeywords(((Meta)element).getContent());
                    break;
                case Element.AUTHOR:
                    info.addAuthor(((Meta)element).getContent());
                    break;
                case Element.CREATOR:
                    info.addCreator(((Meta)element).getContent());
                    break;
                case Element.LANGUAGE:
                    setLanguage(((Meta)element).getContent());
                    break;
                case Element.PRODUCER:
                    // you can not change the name of the producer
                    info.addProducer();
                    break;
                case Element.CREATIONDATE:
                    // you can not set the creation date, only reset it
                    info.addCreationDate();
                    break;
                // content (text)
                case Element.CHUNK: {
                    // if there isn't a current line available, we make one
                    if (line == null) {
                        carriageReturn();
                    }

                    // we cast the element to a chunk
                    PdfChunk chunk = new PdfChunk((Chunk) element, anchorAction, tabSettings);
                    // we try to add the chunk to the line, until we succeed
                    {
                        PdfChunk overflow;
                        while ((overflow = line.add(chunk, leading)) != null) {
                            carriageReturn();
                            boolean newlineSplit = chunk.isNewlineSplit();
                            chunk = overflow;
                            if (!newlineSplit)
                                chunk.trimFirstSpace();
                        }
                    }

                    pageEmpty = false;

                    if (chunk.isAttribute(Chunk.NEWPAGE)) {
                        newPage();
                    }
                    break;
                }
                case Element.ANCHOR: {
                    Anchor anchor = (Anchor) element;
                    String url = anchor.getReference();
                    leading = anchor.getLeading();
                    pushLeading();
                    if (url != null) {
                        anchorAction = new PdfAction(url);
                    }
                    // we process the element
                    element.process(this);
                    anchorAction = null;
                    popLeading();
                    break;
                }
                case Element.ANNOTATION: {
                    if (line == null) {
                        carriageReturn();
                    }
                    Annotation annot = (Annotation) element;
                    Rectangle rect = new Rectangle(0, 0);
                    if (line != null)
                        rect = new Rectangle(annot.llx(indentRight() - line.widthLeft()), annot.ury(indentTop() - currentHeight - 20), annot.urx(indentRight() - line.widthLeft() + 20), annot.lly(indentTop() - currentHeight));
                    PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, rect);
                    annotationsImp.addPlainAnnotation(an);
                    pageEmpty = false;
                    break;
                }
                case Element.PHRASE: {
                    TabSettings backupTabSettings = tabSettings;
                    if (((Phrase) element).getTabSettings() != null)
                        tabSettings = ((Phrase) element).getTabSettings();
                    // we cast the element to a phrase and set the leading of the document
                    leading = ((Phrase) element).getTotalLeading();
                    pushLeading();
                    // we process the element
                    element.process(this);
                    tabSettings = backupTabSettings;
                    popLeading();
                    break;
                }
                case Element.PARAGRAPH: {
                    TabSettings backupTabSettings = tabSettings;
                    if (((Phrase) element).getTabSettings() != null)
                        tabSettings = ((Phrase) element).getTabSettings();
                    // we cast the element to a paragraph
                    Paragraph paragraph = (Paragraph) element;
                    if (isTagged(writer)) {
                        flushLines();
                        text.openMCBlock(paragraph);
                    }
                    addSpacing(paragraph.getSpacingBefore(), leading, paragraph.getFont());

                    // we adjust the parameters of the document
                    alignment = paragraph.getAlignment();
                    leading = paragraph.getTotalLeading();
                    pushLeading();
                    carriageReturn();

                    // we don't want to make orphans/widows
                    if (currentHeight + calculateLineHeight() > indentTop() - indentBottom()) {
                        newPage();
                    }

                    indentation.indentLeft += paragraph.getIndentationLeft();
                    indentation.indentRight += paragraph.getIndentationRight();
                    carriageReturn();

                    PdfPageEvent pageEvent = writer.getPageEvent();
                    if (pageEvent != null && !isSectionTitle)
                        pageEvent.onParagraph(writer, this, indentTop() - currentHeight);

                    // if a paragraph has to be kept together, we wrap it in a table object
                    if (paragraph.getKeepTogether()) {
                        carriageReturn();
                        PdfPTable table = new PdfPTable(1);
                        table.setKeepTogether(paragraph.getKeepTogether());
                        table.setWidthPercentage(100f);
                        PdfPCell cell = new PdfPCell();
                        cell.addElement(paragraph);
                        cell.setBorder(Rectangle.NO_BORDER);
                        cell.setPadding(0);
                        table.addCell(cell);
                        indentation.indentLeft -= paragraph.getIndentationLeft();
                        indentation.indentRight -= paragraph.getIndentationRight();
                        this.add(table);
                        indentation.indentLeft += paragraph.getIndentationLeft();
                        indentation.indentRight += paragraph.getIndentationRight();
                    }
                    else {
                        line.setExtraIndent(paragraph.getFirstLineIndent());
                        float oldHeight = currentHeight;
                        element.process(this);
                        carriageReturn();
                        if (oldHeight != currentHeight || lines.size() > 0) {
                            addSpacing(paragraph.getSpacingAfter(), paragraph.getTotalLeading(), paragraph.getFont(), true);
                        }
                    }

                    if (pageEvent != null && !isSectionTitle)
                        pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight);

                    alignment = Element.ALIGN_LEFT;
                    if ( floatingElements != null && floatingElements.size() != 0 ) {
                        flushFloatingElements();
                    }
                    indentation.indentLeft -= paragraph.getIndentationLeft();
                    indentation.indentRight -= paragraph.getIndentationRight();
                    carriageReturn();
                    tabSettings = backupTabSettings;
                    popLeading();
                    if (isTagged(writer)) {
                        flushLines();
                        text.closeMCBlock(paragraph);
                    }
                    break;
                }
                case Element.SECTION:
                case Element.CHAPTER: {
                    // Chapters and Sections only differ in their constructor
                    // so we cast both to a Section
                    Section section = (Section) element;
                    PdfPageEvent pageEvent = writer.getPageEvent();

                    boolean hasTitle = section.isNotAddedYet()
                            && section.getTitle() != null;

                    // if the section is a chapter, we begin a new page
                    if (section.isTriggerNewPage()) {
                        newPage();
                    }

                    if (hasTitle) {
                        float fith = indentTop() - currentHeight;
                        int rotation = pageSize.getRotation();
                        if (rotation == 90 || rotation == 180)
                            fith = pageSize.getHeight() - fith;
                        PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith);
                        while (currentOutline.level() >= section.getDepth()) {
                            currentOutline = currentOutline.parent();
                        }
                        PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen());
                        currentOutline = outline;
                    }

                    // some values are set
                    carriageReturn();
                    indentation.sectionIndentLeft += section.getIndentationLeft();
                    indentation.sectionIndentRight += section.getIndentationRight();

                    if (section.isNotAddedYet() && pageEvent != null)
                        if (element.type() == Element.CHAPTER)
                            pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.getTitle());
                        else
                            pageEvent.onSection(writer, this, indentTop() - currentHeight, section.getDepth(), section.getTitle());

                    // the title of the section (if any has to be printed)
                    if (hasTitle) {
                        isSectionTitle = true;
                        add(section.getTitle());
                        isSectionTitle = false;
                    }
                    indentation.sectionIndentLeft += section.getIndentation();
                    // we process the section
                    element.process(this);
                    flushLines();
                    // some parameters are set back to normal again
                    indentation.sectionIndentLeft -= section.getIndentationLeft() + section.getIndentation();
                    indentation.sectionIndentRight -= section.getIndentationRight();

                    if (section.isComplete() && pageEvent != null)
                        if (element.type() == Element.CHAPTER)
                            pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight);
                        else
                            pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight);

                    break;
                }
                case Element.LIST: {
                    // we cast the element to a List
                    List list = (List) element;
                    if (isTagged(writer)) {
                        flushLines();
                        text.openMCBlock(list);
                    }
                    if (list.isAlignindent()) {
                        list.normalizeIndentation();
                    }
                    // we adjust the document
                    indentation.listIndentLeft += list.getIndentationLeft();
                    indentation.indentRight += list.getIndentationRight();
                    // we process the items in the list
                    element.process(this);

                    // some parameters are set back to normal again
                    indentation.listIndentLeft -= list.getIndentationLeft();
                    indentation.indentRight -= list.getIndentationRight();
                    carriageReturn();
                    if (isTagged(writer)) {
                        flushLines();
                        text.closeMCBlock(list);
                    }
                    break;
                }
                case Element.LISTITEM: {
                    // we cast the element to a ListItem
                    ListItem listItem = (ListItem) element;
                    if (isTagged(writer)) {
                        flushLines();
                        text.openMCBlock(listItem);
                    }

                    addSpacing(listItem.getSpacingBefore(), leading, listItem.getFont());

                    // we adjust the document
                    alignment = listItem.getAlignment();
                    indentation.listIndentLeft += listItem.getIndentationLeft();
                    indentation.indentRight += listItem.getIndentationRight();
                    leading = listItem.getTotalLeading();
                    pushLeading();
                    carriageReturn();

                    // we prepare the current line to be able to show us the listsymbol
                    line.setListItem(listItem);
                    // we process the item
                    element.process(this);
                    addSpacing(listItem.getSpacingAfter(), listItem.getTotalLeading(), listItem.getFont(), true);

                    // if the last line is justified, it should be aligned to the left
                    if (line.hasToBeJustified()) {
                        line.resetAlignment();
                    }
                    // some parameters are set back to normal again
                    carriageReturn();
                    indentation.listIndentLeft -= listItem.getIndentationLeft();
                    indentation.indentRight -= listItem.getIndentationRight();
                    popLeading();
                    if (isTagged(writer)) {
                        flushLines();
                        text.closeMCBlock(listItem.getListBody());
                        text.closeMCBlock(listItem);
                    }
                    break;
                }
                case Element.RECTANGLE: {
                    Rectangle rectangle = (Rectangle) element;
                    graphics.rectangle(rectangle);
                    pageEmpty = false;
                    break;
                }
                case Element.PTABLE: {
                    PdfPTable ptable = (PdfPTable)element;
                    if (ptable.size() <= ptable.getHeaderRows())
                        break; //nothing to do

                    // before every table, we add a new line and flush all lines
                    ensureNewLine();
                    flushLines();

                    addPTable(ptable);
                    pageEmpty = false;
                    newLine();
                    break;
                }
                case Element.JPEG:
                case Element.JPEG2000:
                case Element.JBIG2:
                case Element.IMGRAW:
                case Element.IMGTEMPLATE: {
                    //carriageReturn(); suggestion by Marc Campforts
                    if (isTagged(writer) && !((Image)element).isImgTemplate()) {
                        flushLines();
                        text.openMCBlock((Image)element);
                    }
                    add((Image) element);
                    if (isTagged(writer) && !((Image)element).isImgTemplate()) {
                        flushLines();
                        text.closeMCBlock((Image)element);
                    }
                    break;
                }
                case Element.YMARK: {
                    DrawInterface zh = (DrawInterface)element;
                    zh.draw(graphics, indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight - (leadingStack.size() > 0 ? leading : 0));
                    pageEmpty = false;
                    break;
                }
                case Element.MARKED: {
                    MarkedObject mo;
                    if (element instanceof MarkedSection) {
                        mo = ((MarkedSection)element).getTitle();
                        if (mo != null) {
                            mo.process(this);
                        }
                    }
                    mo = (MarkedObject)element;
                    mo.process(this);
                    break;
                }
                case Element.WRITABLE_DIRECT:
                    if (null != writer) {
                        ((WriterOperation)element).write(writer, this);
                    }
                    break;
                case Element.DIV:
                    ensureNewLine();
                    flushLines();
                    addDiv((PdfDiv)element);
                    pageEmpty = false;
                    //newLine();
                    break;
                case Element.BODY:
                    body = (PdfBody) element;
                    graphics.rectangle(body);
                default:
                    return false;
            }
            lastElementType = element.type();
            return true;
        }
        catch(Exception e) {
            throw new DocumentException(e);
        }
    }

//	[L1] DocListener interface

    /**
     * Opens the document.
     * <P>
     * You have to open the document before you can begin to add content
     * to the body of the document.
     */
    @Override
    public void open() {
        if (!open) {
            super.open();
            writer.open();
            rootOutline = new PdfOutline(writer);
            currentOutline = rootOutline;
        }
        try {
            if (isTagged(writer)) {
                openMCDocument = true;
            }
            initPage();
        }
        catch(DocumentException de) {
            throw new ExceptionConverter(de);
        }
    }

//	[L2] DocListener interface

    /**
     * Closes the document.
     * <B>
     * Once all the content has been written in the body, you have to close
     * the body. After that nothing can be written to the body anymore.
     */
    @Override
    public void close() {
        if (close) {
            return;
        }
        try {
            if (isTagged(writer)) {
                flushFloatingElements();
                flushLines();
                writer.flushAcroFields();
                writer.flushTaggedObjects();
                if (isPageEmpty()) {
                    int pageReferenceCount = writer.pageReferences.size();
                    if (pageReferenceCount > 0 && writer.currentPageNumber == pageReferenceCount) {
                        writer.pageReferences.remove(pageReferenceCount - 1);
                    }
                }
            } else
                writer.flushAcroFields();
            if (imageWait != null) {
                newPage();
            }
            endPage();
            if (isTagged(writer)) {
                writer.getDirectContent().closeMCBlock(this);
            }
            if (annotationsImp.hasUnusedAnnotations())
                throw new RuntimeException(MessageLocalization.getComposedMessage("not.all.annotations.could.be.added.to.the.document.the.document.doesn.t.have.enough.pages"));
            PdfPageEvent pageEvent = writer.getPageEvent();
            if (pageEvent != null)
                pageEvent.onCloseDocument(writer, this);
            super.close();

            writer.addLocalDestinations(localDestinations);
            calculateOutlineCount();
            writeOutlines();
        }
        catch(Exception e) {
            throw ExceptionConverter.convertException(e);
        }

        writer.close();
    }

    //	[L3] DocListener interface
    protected int textEmptySize;

    // [C9] Metadata for the page
    /**
     * Use this method to set the XMP Metadata.
     * @param xmpMetadata The xmpMetadata to set.
     * @throws IOException
     */
    public void setXmpMetadata(final byte[] xmpMetadata) throws IOException {
        PdfStream xmp = new PdfStream(xmpMetadata);
        xmp.put(PdfName.TYPE, PdfName.METADATA);
        xmp.put(PdfName.SUBTYPE, PdfName.XML);
        PdfEncryption crypto = writer.getEncryption();
        if (crypto != null && !crypto.isMetadataEncrypted()) {
            PdfArray ar = new PdfArray();
            ar.add(PdfName.CRYPT);
            xmp.put(PdfName.FILTER, ar);
        }
        writer.addPageDictEntry(PdfName.METADATA, writer.addToBody(xmp).getIndirectReference());
    }

    /**
     * Makes a new page and sends it to the <CODE>PdfWriter</CODE>.
     *
     * @return true if new page is added
     */
    @Override
    public boolean newPage() {
        if (isPageEmpty()) {
            setNewPageSizeAndMargins();
            return false;
        }
        if (!open || close) {
            throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
        }

        //we end current page
        ArrayList<IAccessibleElement> savedMcBlocks = endPage();

        //Added to inform any listeners that we are moving to a new page (added by David Freels)
        super.newPage();

        // the following 2 lines were added by Pelikan Stephan
        indentation.imageIndentLeft = 0;
        indentation.imageIndentRight = 0;

        try {
            if (isTagged(writer)) {
                flushStructureElementsOnNewPage();
                writer.getDirectContentUnder().restoreMCBlocks(savedMcBlocks);
            }

            // we initialize the new page
            initPage();

            if (body != null && body.getBackgroundColor() != null) {
                graphics.rectangle(body);
            }
        }
        catch(DocumentException de) {
            // maybe this never happens, but it's better to check.
            throw new ExceptionConverter(de);
        }
        return true;
    }

    protected ArrayList<IAccessibleElement> endPage() {
        if (isPageEmpty()) {
            return null;
        }

        ArrayList<IAccessibleElement> savedMcBlocks = null;

        try {
            flushFloatingElements();
        } catch (DocumentException de) {
            // maybe this never happens, but it's better to check.
            throw new ExceptionConverter(de);
        }
        lastElementType = -1;

        PdfPageEvent pageEvent = writer.getPageEvent();
        if (pageEvent != null)
            pageEvent.onEndPage(writer, this);

        try {
            // we flush the arraylist with recently written lines
            flushLines();

            // we prepare the elements of the page dictionary

            // [U1] page size and rotation
            int rotation = pageSize.getRotation();

            // [C10]
            if (writer.isPdfIso()) {
                if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim"))
                    throw new PdfXConformanceException(MessageLocalization.getComposedMessage("only.one.of.artbox.or.trimbox.can.exist.in.the.page"));
                if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) {
                    if (thisBoxSize.containsKey("crop"))
                        thisBoxSize.put("trim", thisBoxSize.get("crop"));
                    else
                        thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation()));
                }
            }

            // [M1]
            pageResources.addDefaultColorDiff(writer.getDefaultColorspace());
            if (writer.isRgbTransparencyBlending()) {
                PdfDictionary dcs = new PdfDictionary();
                dcs.put(PdfName.CS, PdfName.DEVICERGB);
                pageResources.addDefaultColorDiff(dcs);
            }
            PdfDictionary resources = pageResources.getResources();

            // we create the page dictionary

            PdfPage page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation);
            if (isTagged(writer)) {
                page.put(PdfName.TABS, PdfName.S);
            } else {
                page.put(PdfName.TABS, writer.getTabs());
            }
            page.putAll(writer.getPageDictEntries());
            writer.resetPageDictEntries();

            // we complete the page dictionary

            // [U3] page actions: additional actions
            if (pageAA != null) {
                page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference());
                pageAA = null;
            }

            // [C5] and [C8] we add the annotations
            if (annotationsImp.hasUnusedAnnotations()) {
                PdfArray array = annotationsImp.rotateAnnotations(writer, pageSize);
                if (array.size() != 0)
                    page.put(PdfName.ANNOTS, array);
            }

            // [F12] we add tag info
            if (isTagged(writer))
                page.put(PdfName.STRUCTPARENTS, new PdfNumber(getStructParentIndex(writer.getCurrentPage())));

            if (text.size() > textEmptySize || isTagged(writer))
                text.endText();
            else
                text = null;

            if (isTagged(writer)) {
                savedMcBlocks = writer.getDirectContent().saveMCBlocks();
            }
            writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, !isTagged(writer) ? text : null, writer.getDirectContent(), pageSize));

            annotationsImp.resetAnnotations();
            writer.resetContent();
        }
        catch(DocumentException de) {
            // maybe this never happens, but it's better to check.
            throw new ExceptionConverter(de);
        }
        catch (IOException ioe) {
            throw new ExceptionConverter(ioe);
        }

        return savedMcBlocks;
    }
//	[L4] DocListener interface

    /**
     * Sets the pagesize.
     *
     * @param pageSize the new pagesize
     * @return <CODE>true</CODE> if the page size was set
     */
    @Override
    public boolean setPageSize(final Rectangle pageSize) {
        if (writer != null && writer.isPaused()) {
            return false;
        }
        nextPageSize = new Rectangle(pageSize);
        return true;
    }

//	[L5] DocListener interface

    /** margin in x direction starting from the left. Will be valid in the next page */
    protected float nextMarginLeft;

    /** margin in x direction starting from the right. Will be valid in the next page */
    protected float nextMarginRight;

    /** margin in y direction starting from the top. Will be valid in the next page */
    protected float nextMarginTop;

    /** margin in y direction starting from the bottom. Will be valid in the next page */
    protected float nextMarginBottom;

    /**
     * Sets the margins.
     *
     * @param	marginLeft		the margin on the left
     * @param	marginRight		the margin on the right
     * @param	marginTop		the margin on the top
     * @param	marginBottom	the margin on the bottom
     * @return	a <CODE>boolean</CODE>
     */
    @Override
    public boolean setMargins(final float marginLeft, final float marginRight, final float marginTop, final float marginBottom) {
        if (writer != null && writer.isPaused()) {
            return false;
        }
        nextMarginLeft = marginLeft;
        nextMarginRight = marginRight;
        nextMarginTop = marginTop;
        nextMarginBottom = marginBottom;
        return true;
    }

//	[L6] DocListener interface

    /**
     * @see com.itextpdf.text.DocListener#setMarginMirroring(boolean)
     */
    @Override
    public boolean setMarginMirroring(final boolean MarginMirroring) {
        if (writer != null && writer.isPaused()) {
            return false;
        }
        return super.setMarginMirroring(MarginMirroring);
    }

    /**
     * @see com.itextpdf.text.DocListener#setMarginMirroring(boolean)
     * @since	2.1.6
     */
    @Override
    public boolean setMarginMirroringTopBottom(final boolean MarginMirroringTopBottom) {
        if (writer != null && writer.isPaused()) {
            return false;
        }
        return super.setMarginMirroringTopBottom(MarginMirroringTopBottom);
    }

//	[L7] DocListener interface

    /**
     * Sets the page number.
     *
     * @param	pageN		the new page number
     */
    @Override
    public void setPageCount(final int pageN) {
        if (writer != null && writer.isPaused()) {
            return;
        }
        super.setPageCount(pageN);
    }

//	[L8] DocListener interface

    /**
     * Sets the page number to 0.
     */
    @Override
    public void resetPageCount() {
        if (writer != null && writer.isPaused()) {
            return;
        }
        super.resetPageCount();
    }

// DOCLISTENER METHODS END

    /** Signals that OnOpenDocument should be called. */
    protected boolean firstPageEvent = true;

    /**
     * Initializes a page.
     * <P>
     * If the footer/header is set, it is printed.
     * @throws DocumentException on error
     */
    protected void initPage() throws DocumentException {
        // the pagenumber is incremented
        pageN++;

        // initialization of some page objects
        pageResources = new PageResources();

        if (isTagged(writer)) {
            graphics = writer.getDirectContentUnder().getDuplicate();
            writer.getDirectContent().duplicatedFrom = graphics;
        } else {
            graphics = new PdfContentByte(writer);
        }

        setNewPageSizeAndMargins();
        imageEnd = -1;
        indentation.imageIndentRight = 0;
        indentation.imageIndentLeft = 0;
        indentation.indentBottom = 0;
        indentation.indentTop = 0;
        currentHeight = 0;

        // backgroundcolors, etc...
        thisBoxSize = new HashMap<String, PdfRectangle>(boxSize);
        if (pageSize.getBackgroundColor() != null
                || pageSize.hasBorders()
                || pageSize.getBorderColor() != null) {
            add(pageSize);
        }

        float oldleading = leading;
        int oldAlignment = alignment;
        pageEmpty = true;
        // if there is an image waiting to be drawn, draw it
        try {
            if (imageWait != null) {
                add(imageWait);
                imageWait = null;
            }
        }
        catch(Exception e) {
            throw new ExceptionConverter(e);
        }
        leading = oldleading;
        alignment = oldAlignment;
        carriageReturn();

        PdfPageEvent pageEvent = writer.getPageEvent();
        if (pageEvent != null) {
            if (firstPageEvent) {
                pageEvent.onOpenDocument(writer, this);
            }
            pageEvent.onStartPage(writer, this);
        }
        firstPageEvent = false;
    }

    /** The line that is currently being written. */
    protected PdfLine line = null;
    /** The lines that are written until now. */
    protected ArrayList<PdfLine> lines = new ArrayList<PdfLine>();

    /**
     * Adds the current line to the list of lines and also adds an empty line.
     * @throws DocumentException on error
     */
    protected void newLine() throws DocumentException {
        lastElementType = -1;
        carriageReturn();
        if (lines != null && !lines.isEmpty()) {
            lines.add(line);
            currentHeight += line.height();
        }
        line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
    }

    /**
     * line.height() is usually the same as the leading
     * We should take leading into account if it is not the same as the line.height
     *
     * @return float combined height of the line
     * @since 5.5.1
     */
    protected float calculateLineHeight() {
        float tempHeight = line.height();

        if ( tempHeight != leading) {
            tempHeight += leading;
        }

        return tempHeight;
    }

    /**
     * If the current line is not empty or null, it is added to the arraylist
     * of lines and a new empty line is added.
     */
    protected void carriageReturn() {
        // the arraylist with lines may not be null
        if (lines == null) {
            lines = new ArrayList<PdfLine>();
        }
        // If the current line is not null or empty
        if (line != null && line.size() > 0) {
            // we check if the end of the page is reached (bugfix by Francois Gravel)
            if (currentHeight + calculateLineHeight() > indentTop() - indentBottom()) {
                // if the end of the line is reached, we start a newPage which will flush existing lines
                // then move to next page but before then we need to exclude the current one that does not fit
                // After the new page we add the current line back in
                if ( currentHeight != 0 ) {
                    PdfLine overflowLine = line;
                    line = null;
                    newPage();
                    line = overflowLine;
                    //update left indent because of mirror margins.
                    overflowLine.left = indentLeft();
                }
            }
            currentHeight += line.height();
            lines.add(line);
            pageEmpty = false;
        }
        if (imageEnd > -1 && currentHeight > imageEnd) {
            imageEnd = -1;
            indentation.imageIndentRight = 0;
            indentation.imageIndentLeft = 0;
        }
        // a new current line is constructed
        line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
    }

    /**
     * Gets the current vertical page position.
     * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
     *   for elements that do not terminate the lines they've started because those lines will get
     *   terminated.
     * @return The current vertical page position.
     */
    public float getVerticalPosition(final boolean ensureNewLine) {
        // ensuring that a new line has been started.
        if (ensureNewLine) {
            ensureNewLine();
        }
        return top() -  currentHeight - indentation.indentTop;
    }

    /** Holds the type of the last element, that has been added to the document. */
    protected int lastElementType = -1;

    /**
     * Ensures that a new line has been started.
     */
    protected void ensureNewLine() {
        try {
            if (lastElementType == Element.PHRASE ||
                    lastElementType == Element.CHUNK) {
                newLine();
                flushLines();
            }
        } catch (DocumentException ex) {
            throw new ExceptionConverter(ex);
        }
    }

    /**
     * Writes all the lines to the text-object.
     *
     * @return the displacement that was caused
     * @throws DocumentException on error
     */
    protected float flushLines() throws DocumentException {
        // checks if the ArrayList with the lines is not null
        if (lines == null) {
            return 0;
        }
        // checks if a new Line has to be made.
        if (line != null && line.size() > 0) {
            lines.add(line);
            line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
        }

        // checks if the ArrayList with the lines is empty
        if (lines.isEmpty()) {
            return 0;
        }

        // initialization of some parameters
        Object currentValues[] = new Object[2];
        PdfFont currentFont = null;
        float displacement = 0;
        Float lastBaseFactor = new Float(0);
        currentValues[1] = lastBaseFactor;
        // looping over all the lines
        for (PdfLine l: lines) {
            float moveTextX = l.indentLeft() - indentLeft() + indentation.indentLeft + indentation.listIndentLeft + indentation.sectionIndentLeft;
            text.moveText(moveTextX, -l.height());
            // is the line preceded by a symbol?
            l.flush();

            if (l.listSymbol() != null) {
                ListLabel lbl = null;
                Chunk symbol = l.listSymbol();
                if (isTagged(writer)) {
                    lbl = l.listItem().getListLabel();
                    graphics.openMCBlock(lbl);
                    symbol = new Chunk(symbol);
                    symbol.setRole(null);
                }
                ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(symbol), text.getXTLM() - l.listIndent(), text.getYTLM(), 0);
                if (lbl != null) {
                    graphics.closeMCBlock(lbl);
                }
            }

            currentValues[0] = currentFont;

            if (isTagged(writer) && l.listItem() != null) {
                text.openMCBlock(l.listItem().getListBody());
            }
            writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio());

            currentFont = (PdfFont)currentValues[0];
            displacement += l.height();
            text.moveText(-moveTextX, 0);

        }
        lines = new ArrayList<PdfLine>();
        return displacement;
    }

    /** The characters to be applied the hanging punctuation. */
    static final String hangingPunctuation = ".,;:'";

    /**
     * Writes a text line to the document. It takes care of all the attributes.
     * <P>
     * Before entering the line position must have been established and the
     * <CODE>text</CODE> argument must be in text object scope (<CODE>beginText()</CODE>).
     * @param line the line to be written
     * @param text the <CODE>PdfContentByte</CODE> where the text will be written to
     * @param graphics the <CODE>PdfContentByte</CODE> where the graphics will be written to
     * @param currentValues the current font and extra spacing values
     * @param ratio
     * @throws DocumentException on error
     * @since 5.0.3 returns a float instead of void
     */
    float writeLineToContent(final PdfLine line, final PdfContentByte text, final PdfContentByte graphics, final Object currentValues[], final float ratio)  throws DocumentException {
        PdfFont currentFont = (PdfFont)currentValues[0];
        float lastBaseFactor = ((Float)currentValues[1]).floatValue();
        PdfChunk chunk;
        int numberOfSpaces;
        int lineLen;
        boolean isJustified;
        float hangingCorrection = 0;
        float hScale = 1;
        float lastHScale = Float.NaN;
        float baseWordSpacing = 0;
        float baseCharacterSpacing = 0;
        float glueWidth = 0;
        float lastX = text.getXTLM() + line.getOriginalWidth();
        numberOfSpaces = line.numberOfSpaces();
        lineLen = line.getLineLengthUtf32();
        // does the line need to be justified?
        isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1);
        int separatorCount = line.getSeparatorCount();
        if (separatorCount > 0) {
            glueWidth = line.widthLeft() / separatorCount;
        }
        else if (isJustified && separatorCount == 0) {
            if (line.isNewlineSplit() && line.widthLeft() >= lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1)) {
                if (line.isRTL()) {
                    text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0);
                }
                baseWordSpacing = ratio * lastBaseFactor;
                baseCharacterSpacing = lastBaseFactor;
            }
            else {
                float width = line.widthLeft();
                PdfChunk last = line.getChunk(line.size() - 1);
                if (last != null) {
                    String s = last.toString();
                    char c;
                    if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) {
                        float oldWidth = width;
                        width += last.font().width(c) * 0.4f;
                        hangingCorrection = width - oldWidth;
                    }
                }
                float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1);
                baseWordSpacing = ratio * baseFactor;
                baseCharacterSpacing = baseFactor;
                lastBaseFactor = baseFactor;
            }
        } else if (line.alignment == Element.ALIGN_LEFT || line.alignment == Element.ALIGN_UNDEFINED) {
            lastX -= line.widthLeft();
        }

        int lastChunkStroke = line.getLastStrokeChunk();
        int chunkStrokeIdx = 0;
        float xMarker = text.getXTLM();
        float baseXMarker = xMarker;
        float yMarker = text.getYTLM();
        boolean adjustMatrix = false;
        float tabPosition = 0;
        boolean isMCBlockOpened = false;
        // looping over all the chunks in 1 line
        for (Iterator<PdfChunk> j = line.iterator(); j.hasNext(); ) {
            chunk = j.next();
            if (isTagged(writer) && chunk.accessibleElement != null) {
                text.openMCBlock(chunk.accessibleElement);
                isMCBlockOpened = true;
            }
            BaseColor color = chunk.color();
            float fontSize = chunk.font().size();
            float ascender;
            float descender;
            if (chunk.isImage()) {
                ascender = chunk.height();
                fontSize = chunk.height();
                descender = 0;
            } else {
                ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
                descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
            }
            hScale = 1;

            if (chunkStrokeIdx <= lastChunkStroke) {
                float width;
                if (isJustified) {
                    width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing);
                }
                else {
                    width = chunk.width();
                }
                if (chunk.isStroked()) {
                    PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1);
                    if (chunk.isSeparator()) {
                        width = glueWidth;
                        Object[] sep = (Object[])chunk.getAttribute(Chunk.SEPARATOR);
                        DrawInterface di = (DrawInterface)sep[0];
                        Boolean vertical = (Boolean)sep[1];
                        if (vertical.booleanValue()) {
                            di.draw(graphics, baseXMarker, yMarker + descender, baseXMarker + line.getOriginalWidth(), ascender - descender, yMarker);
                        }
                        else {
                            di.draw(graphics, xMarker, yMarker + descender, xMarker + width, ascender - descender, yMarker);
                        }
                    }
                    if (chunk.isTab()) {
                        if (chunk.isAttribute(Chunk.TABSETTINGS)) {
                            TabStop tabStop = chunk.getTabStop();
                            if (tabStop != null) {
                                tabPosition = tabStop.getPosition() + baseXMarker;
                                if (tabStop.getLeader() != null)
                                    tabStop.getLeader().draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker);
                            } else {
                                tabPosition = xMarker;
                            }
                        } else {
                            //Keep deprecated tab logic for backward compatibility...
                            Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB);
                            DrawInterface di = (DrawInterface)tab[0];
                            tabPosition = ((Float)tab[1]).floatValue() + ((Float)tab[3]).floatValue();
                            if (tabPosition > xMarker) {
                                di.draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker);
                            }
                        }
                        float tmp = xMarker;
                        xMarker = tabPosition;
                        tabPosition = tmp;
                    }
                    if (chunk.isAttribute(Chunk.BACKGROUND)) {
                        Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND);
                        if (bgr[0] != null) {
                            boolean inText = graphics.getInText();
                            if (inText && isTagged(writer)) {
                                graphics.endText();
                            }
                            graphics.saveState();
                            float subtract = lastBaseFactor;
                            if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND)) {
                                subtract = 0;
                            }
                            if (nextChunk == null) {
                                subtract += hangingCorrection;
                            }
                            BaseColor c = (BaseColor) bgr[0];
                            graphics.setColorFill(c);
                            float extra[] = (float[]) bgr[1];
                            graphics.rectangle(xMarker - extra[0],
                                    yMarker + descender - extra[1] + chunk.getTextRise(),
                                    width - subtract + extra[0] + extra[2],
                                    ascender - descender + extra[1] + extra[3]);
                            graphics.fill();
                            graphics.setGrayFill(0);
                            graphics.restoreState();
                            if (inText && isTagged(writer)) {
                                graphics.beginText(true);
                            }
                        }
                    }
                    if (chunk.isAttribute(Chunk.UNDERLINE)) {
                        boolean inText = graphics.getInText();
                        if (inText && isTagged(writer)) {
                            graphics.endText();
                        }
                        float subtract = lastBaseFactor;
                        if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE))
                            subtract = 0;
                        if (nextChunk == null)
                            subtract += hangingCorrection;
                        Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE);
                        BaseColor scolor = null;
                        for (int k = 0; k < unders.length; ++k) {
                            Object obj[] = unders[k];
                            scolor = (BaseColor)obj[0];
                            float ps[] = (float[])obj[1];
                            if (scolor == null)
                                scolor = color;
                            if (scolor != null)
                                graphics.setColorStroke(scolor);
                            graphics.setLineWidth(ps[0] + chunk.font().size() * ps[1]);
                            float shift = ps[2] + chunk.font().size() * ps[3];
                            int cap2 = (int)ps[4];
                            if (cap2 != 0)
                                graphics.setLineCap(cap2);
                            graphics.moveTo(xMarker, yMarker + shift);
                            graphics.lineTo(xMarker + width - subtract, yMarker + shift);
                            graphics.stroke();
                            if (scolor != null)
                                graphics.resetGrayStroke();
                            if (cap2 != 0)
                                graphics.setLineCap(0);
                        }
                        graphics.setLineWidth(1);
                        if (inText && isTagged(writer)) {
                            graphics.beginText(true);
                        }
                    }
                    if (chunk.isAttribute(Chunk.ACTION)) {
                        float subtract = lastBaseFactor;
                        if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION))
                            subtract = 0;
                        if (nextChunk == null)
                            subtract += hangingCorrection;
                        PdfAnnotation annot = null;
                        if (chunk.isImage()) {
                            annot = writer.createAnnotation(xMarker, yMarker + chunk.getImageOffsetY(), xMarker + width - subtract, yMarker + chunk.getImageHeight() + chunk.getImageOffsetY(), (PdfAction) chunk.getAttribute(Chunk.ACTION), null);
                        }
                        else {
                            annot = writer.createAnnotation(xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise(), (PdfAction)chunk.getAttribute(Chunk.ACTION), null);
                        }
                        text.addAnnotation(annot, true);
                        if (isTagged(writer) && chunk.accessibleElement != null) {
                            PdfStructureElement strucElem = getStructElement(chunk.accessibleElement.getId());
                            if (strucElem != null) {
                                int structParent = getStructParentIndex(annot);
                                annot.put(PdfName.STRUCTPARENT, new PdfNumber(structParent));
                                strucElem.setAnnotation(annot, writer.getCurrentPage());
                                writer.getStructureTreeRoot().setAnnotationMark(structParent, strucElem.getReference());
                            }
                        }
                    }
                    if (chunk.isAttribute(Chunk.REMOTEGOTO)) {
                        float subtract = lastBaseFactor;
                        if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO))
                            subtract = 0;
                        if (nextChunk == null)
                            subtract += hangingCorrection;
                        Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO);
                        String filename = (String)obj[0];
                        if (obj[1] instanceof String)
                            remoteGoto(filename, (String)obj[1], xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise());
                        else
                            remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise());
                    }
                    if (chunk.isAttribute(Chunk.LOCALGOTO)) {
                        float subtract = lastBaseFactor;
                        if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO))
                            subtract = 0;
                        if (nextChunk == null)
                            subtract += hangingCorrection;
                        localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize);
                    }
                    if (chunk.isAttribute(Chunk.LOCALDESTINATION)) {
                        /*float subtract = lastBaseFactor;
                        if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION))
                            subtract = 0;
                        if (nextChunk == null)
                            subtract += hangingCorrection;*/
                        localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + fontSize, 0));
                    }
                    if (chunk.isAttribute(Chunk.GENERICTAG)) {
                        float subtract = lastBaseFactor;
                        if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG))
                            subtract = 0;
                        if (nextChunk == null)
                            subtract += hangingCorrection;
                        Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize);
                        PdfPageEvent pev = writer.getPageEvent();
                        if (pev != null)
                            pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG));
                    }
                    if (chunk.isAttribute(Chunk.PDFANNOTATION)) {
                        float subtract = lastBaseFactor;
                        if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION))
                            subtract = 0;
                        if (nextChunk == null)
                            subtract += hangingCorrection;
                        PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION));
                        annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender));
                        text.addAnnotation(annot, true);
                    }
                    float params[] = (float[])chunk.getAttribute(Chunk.SKEW);
                    Float hs = (Float)chunk.getAttribute(Chunk.HSCALE);
                    if (params != null || hs != null) {
                        float b = 0, c = 0;
                        if (params != null) {
                            b = params[0];
                            c = params[1];
                        }
                        if (hs != null)
                            hScale = hs.floatValue();
                        text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker);
                    }
                    if (!isJustified) {
                        if (chunk.isAttribute(Chunk.WORD_SPACING)) {
                            Float ws = (Float) chunk.getAttribute(Chunk.WORD_SPACING);
                            text.setWordSpacing(ws.floatValue());
                        }

                        if (chunk.isAttribute(Chunk.CHAR_SPACING)) {
                            Float cs = (Float) chunk.getAttribute(Chunk.CHAR_SPACING);
                            text.setCharacterSpacing(cs.floatValue());
                        }
                    }
                    if (chunk.isImage()) {
                        Image image = chunk.getImage();
                        width = chunk.getImageWidth();
                        float matrix[] = image.matrix(chunk.getImageScalePercentage());
                        matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX];
                        matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY];
                        boolean wasIntext = false;
                        if ( graphics.getInText() && !(image instanceof ImgTemplate)) {
                            wasIntext = true;
                            graphics.endText();
                        }
                        graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], false, isMCBlockOpened);
                        if ( wasIntext ) {
                            graphics.beginText(true);
                        }
                        text.moveText(xMarker + lastBaseFactor + chunk.getImageWidth() - text.getXTLM(), 0);
                    }
                }

                xMarker += width;
                ++chunkStrokeIdx;
            }

            if (!chunk.isImage() && chunk.font().compareTo(currentFont) != 0) {
                currentFont = chunk.font();
                text.setFontAndSize(currentFont.getFont(), currentFont.size());
            }
            float rise = 0;
            Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE);
            int tr = 0;
            float strokeWidth = 1;
            BaseColor strokeColor = null;
            Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT);
            if (textRender != null) {
                tr = ((Integer)textRender[0]).intValue() & 3;
                if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
                    text.setTextRenderingMode(tr);
                if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) {
                    strokeWidth = ((Float)textRender[1]).floatValue();
                    if (strokeWidth != 1)
                        text.setLineWidth(strokeWidth);
                    strokeColor = (BaseColor)textRender[2];
                    if (strokeColor == null)
                        strokeColor = color;
                    if (strokeColor != null)
                        text.setColorStroke(strokeColor);
                }
            }
            if (fr != null)
                rise = fr.floatValue();
            if (color != null)
                text.setColorFill(color);
            if (rise != 0)
                text.setTextRise(rise);
            if (chunk.isImage()) {
                adjustMatrix = true;
            }
            else if (chunk.isHorizontalSeparator()) {
                PdfTextArray array = new PdfTextArray();
                array.add(-glueWidth * 1000f / chunk.font.size() / hScale);
                text.showText(array);
            }
            else if (chunk.isTab() && tabPosition != xMarker) {
                PdfTextArray array = new PdfTextArray();
                array.add((tabPosition - xMarker) * 1000f / chunk.font.size() / hScale);
                text.showText(array);
            }
            // If it is a CJK chunk or Unicode TTF we will have to simulate the
            // space adjustment.
            else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) {
                if (hScale != lastHScale) {
                    lastHScale = hScale;
                    text.setWordSpacing(baseWordSpacing / hScale);
                    text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing());
                }
                String s = chunk.toString();
                int idx = s.indexOf(' ');
                if (idx < 0)
                    text.showText(s);
                else {
                    float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale;
                    PdfTextArray textArray = new PdfTextArray(s.substring(0, idx));
                    int lastIdx = idx;
                    while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) {
                        textArray.add(spaceCorrection);
                        textArray.add(s.substring(lastIdx, idx));
                        lastIdx = idx;
                    }
                    textArray.add(spaceCorrection);
                    textArray.add(s.substring(lastIdx));
                    text.showText(textArray);
                }
            }
            else {
                if (isJustified && hScale != lastHScale) {
                    lastHScale = hScale;
                    text.setWordSpacing(baseWordSpacing / hScale);
                    text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing());
                }
                text.showText(chunk.toString());
            }

            if (rise != 0)
                text.setTextRise(0);
            if (color != null)
                text.resetRGBColorFill();
            if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
                text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
            if (strokeColor != null)
                text.resetRGBColorStroke();
            if (strokeWidth != 1)
                text.setLineWidth(1);
            if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) {
                adjustMatrix = true;
                text.setTextMatrix(xMarker, yMarker);
            }
            if (!isJustified) {
                if (chunk.isAttribute(Chunk.CHAR_SPACING)) {
                    text.setCharacterSpacing(baseCharacterSpacing);
                }
                if (chunk.isAttribute(Chunk.WORD_SPACING)) {
                    text.setWordSpacing(baseWordSpacing);
                }
            }
            if (isTagged(writer) && chunk.accessibleElement != null) {
                text.closeMCBlock(chunk.accessibleElement);
            }

        }
        if (isJustified) {
            text.setWordSpacing(0);
            text.setCharacterSpacing(0);
            if (line.isNewlineSplit())
                lastBaseFactor = 0;
        }
        if (adjustMatrix)
            text.moveText(baseXMarker - text.getXTLM(), 0);
        currentValues[0] = currentFont;
        currentValues[1] = new Float(lastBaseFactor);
        return lastX;
    }

    protected Indentation indentation = new Indentation();

    /**
     * @since	2.0.8 (PdfDocument was package-private before)
     */
    public static class Indentation {

        /** This represents the current indentation of the PDF Elements on the left side. */
        float indentLeft = 0;

        /** Indentation to the left caused by a section. */
        float sectionIndentLeft = 0;

        /** This represents the current indentation of the PDF Elements on the left side. */
        float listIndentLeft = 0;

        /** This is the indentation caused by an image on the left. */
        float imageIndentLeft = 0;

        /** This represents the current indentation of the PDF Elements on the right side. */
        float indentRight = 0;

        /** Indentation to the right caused by a section. */
        float sectionIndentRight = 0;

        /** This is the indentation caused by an image on the right. */
        float imageIndentRight = 0;

        /** This represents the current indentation of the PDF Elements on the top side. */
        float indentTop = 0;

        /** This represents the current indentation of the PDF Elements on the bottom side. */
        float indentBottom = 0;
    }

    /**
     * Gets the indentation on the left side.
     *
     * @return	a margin
     */

    protected float indentLeft() {
        return left(indentation.indentLeft + indentation.listIndentLeft + indentation.imageIndentLeft + indentation.sectionIndentLeft);
    }

    /**
     * Gets the indentation on the right side.
     *
     * @return	a margin
     */

    protected float indentRight() {
        return right(indentation.indentRight + indentation.sectionIndentRight + indentation.imageIndentRight);
    }

    /**
     * Gets the indentation on the top side.
     *
     * @return	a margin
     */

    protected float indentTop() {
        return top(indentation.indentTop);
    }

    /**
     * Gets the indentation on the bottom side.
     *
     * @return	a margin
     */

    float indentBottom() {
        return bottom(indentation.indentBottom);
    }

    /**
     * Calls addSpacing(float, float, Font, boolean (false)).
     */
    protected void addSpacing(final float extraspace, final float oldleading, Font f) {
        addSpacing(extraspace, oldleading, f, false);
    }

    /**
     * Adds extra spacing.
     */
    // this method should probably be rewritten
    protected void addSpacing(final float extraspace, final float oldleading, Font f, boolean spacingAfter) {
        if (extraspace == 0) {
            return;
        }

        if (pageEmpty) {
            return;
        }

        float height = spacingAfter ? extraspace : calculateLineHeight();

        if ( currentHeight + height > indentTop() - indentBottom() ) {
            newPage();
            return;
        }

        leading = extraspace;
        carriageReturn();
        if (f.isUnderlined() || f.isStrikethru()) {
            f = new Font(f);
            int style = f.getStyle();
            style &= ~Font.UNDERLINE;
            style &= ~Font.STRIKETHRU;
            f.setStyle(style);
        }
        Chunk space  = new Chunk(" ", f);
        if ( spacingAfter && pageEmpty ) {
            space = new Chunk("", f);
        }
        space.process(this);
        carriageReturn();

        leading = oldleading;
    }

//	Info Dictionary and Catalog

    /** some meta information about the Document. */
    protected PdfInfo info = new PdfInfo();

    /**
     * Gets the <CODE>PdfInfo</CODE>-object.
     *
     * @return	<CODE>PdfInfo</COPE>
     */

    PdfInfo getInfo() {
        return info;
    }

    /**
     * Gets the <CODE>PdfCatalog</CODE>-object.
     *
     * @param pages an indirect reference to this document pages
     * @return <CODE>PdfCatalog</CODE>
     */

    PdfCatalog getCatalog(final PdfIndirectReference pages) {
        PdfCatalog catalog = new PdfCatalog(pages, writer);

        // [C1] outlines
        if (rootOutline.getKids().size() > 0) {
            catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES);
            catalog.put(PdfName.OUTLINES, rootOutline.indirectReference());
        }

        // [C2] version
        writer.getPdfVersion().addToCatalog(catalog);

        // [C3] preferences
        viewerPreferences.addToCatalog(catalog);

        // [C4] pagelabels
        if (pageLabels != null) {
            catalog.put(PdfName.PAGELABELS, pageLabels.getDictionary(writer));
        }

        // [C5] named objects
        catalog.addNames(localDestinations, getDocumentLevelJS(), documentFileAttachment, writer);

        // [C6] actions
        if (openActionName != null) {
            PdfAction action = getLocalGotoAction(openActionName);
            catalog.setOpenAction(action);
        }
        else if (openActionAction != null)
            catalog.setOpenAction(openActionAction);
        if (additionalActions != null)   {
            catalog.setAdditionalActions(additionalActions);
        }

        // [C7] portable collections
        if (collection != null) {
            catalog.put(PdfName.COLLECTION, collection);
        }

        // [C8] AcroForm
        if (annotationsImp.hasValidAcroForm()) {
            try {
                catalog.put(PdfName.ACROFORM, writer.addToBody(annotationsImp.getAcroForm()).getIndirectReference());
            }
            catch (IOException e) {
                throw new ExceptionConverter(e);
            }
        }

        if (language != null) {
            catalog.put(PdfName.LANG, language);
        }

        return catalog;
    }

//	[C1] outlines

    /** This is the root outline of the document. */
    protected PdfOutline rootOutline;

    /** This is the current <CODE>PdfOutline</CODE> in the hierarchy of outlines. */
    protected PdfOutline currentOutline;

    /**
     * Adds a named outline to the document .
     * @param outline the outline to be added
     * @param name the name of this local destination
     */
    void addOutline(final PdfOutline outline, final String name) {
        localDestination(name, outline.getPdfDestination());
    }

    /**
     * Gets the root outline. All the outlines must be created with a parent.
     * The first level is created with this outline.
     * @return the root outline
     */
    public PdfOutline getRootOutline() {
        return rootOutline;
    }


    /**
     * Updates the count in the outlines.
     */
    void calculateOutlineCount() {
        if (rootOutline.getKids().size() == 0)
            return;
        traverseOutlineCount(rootOutline);
    }

    /**
     * Recursive method to update the count in the outlines.
     */
    void traverseOutlineCount(final PdfOutline outline) {
        ArrayList<PdfOutline> kids = outline.getKids();
        PdfOutline parent = outline.parent();
        if (kids.isEmpty()) {
            if (parent != null) {
                parent.setCount(parent.getCount() + 1);
            }
        }
        else {
            for (int k = 0; k < kids.size(); ++k) {
                traverseOutlineCount(kids.get(k));
            }
            if (parent != null) {
                if (outline.isOpen()) {
                    parent.setCount(outline.getCount() + parent.getCount() + 1);
                }
                else {
                    parent.setCount(parent.getCount() + 1);
                    outline.setCount(-outline.getCount());
                }
            }
        }
    }

    /**
     * Writes the outline tree to the body of the PDF document.
     */
    void writeOutlines() throws IOException {
        if (rootOutline.getKids().size() == 0)
            return;
        outlineTree(rootOutline);
        writer.addToBody(rootOutline, rootOutline.indirectReference());
    }

    /**
     * Recursive method used to write outlines.
     */
    void outlineTree(final PdfOutline outline) throws IOException {
        outline.setIndirectReference(writer.getPdfIndirectReference());
        if (outline.parent() != null)
            outline.put(PdfName.PARENT, outline.parent().indirectReference());
        ArrayList<PdfOutline> kids = outline.getKids();
        int size = kids.size();
        for (int k = 0; k < size; ++k)
            outlineTree(kids.get(k));
        for (int k = 0; k < size; ++k) {
            if (k > 0)
                kids.get(k).put(PdfName.PREV, kids.get(k - 1).indirectReference());
            if (k < size - 1)
                kids.get(k).put(PdfName.NEXT, kids.get(k + 1).indirectReference());
        }
        if (size > 0) {
            outline.put(PdfName.FIRST, kids.get(0).indirectReference());
            outline.put(PdfName.LAST, kids.get(size - 1).indirectReference());
        }
        for (int k = 0; k < size; ++k) {
            PdfOutline kid = kids.get(k);
            writer.addToBody(kid, kid.indirectReference());
        }
    }

//  [C3] PdfViewerPreferences interface

    /** Contains the Viewer preferences of this PDF document. */
    protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
    /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */
    void setViewerPreferences(final int preferences) {
        this.viewerPreferences.setViewerPreferences(preferences);
    }

    /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfObject) */
    void addViewerPreference(final PdfName key, final PdfObject value) {
        this.viewerPreferences.addViewerPreference(key, value);
    }

//	[C4] Page labels

    protected PdfPageLabels pageLabels;
    /**
     * Sets the page labels
     * @param pageLabels the page labels
     */
    void setPageLabels(final PdfPageLabels pageLabels) {
        this.pageLabels = pageLabels;
    }

    public PdfPageLabels getPageLabels() {
        return this.pageLabels;
    }

//	[C5] named objects: local destinations, javascript, embedded files

    /**
     * Implements a link to other part of the document. The jump will
     * be made to a local destination with the same name, that must exist.
     * @param name the name for this link
     * @param llx the lower left x corner of the activation area
     * @param lly the lower left y corner of the activation area
     * @param urx the upper right x corner of the activation area
     * @param ury the upper right y corner of the activation area
     */
    void localGoto(final String name, final float llx, final float lly, final float urx, final float ury) {
        PdfAction action = getLocalGotoAction(name);
        annotationsImp.addPlainAnnotation(writer.createAnnotation(llx, lly, urx, ury, action, null));
    }

    /**
     * Implements a link to another document.
     * @param filename the filename for the remote document
     * @param name the name to jump to
     * @param llx the lower left x corner of the activation area
     * @param lly the lower left y corner of the activation area
     * @param urx the upper right x corner of the activation area
     * @param ury the upper right y corner of the activation area
     */
    void remoteGoto(final String filename, final String name, final float llx, final float lly, final float urx, final float ury) {
        annotationsImp.addPlainAnnotation(writer.createAnnotation(llx, lly, urx, ury, new PdfAction(filename, name), null));
    }

    /**
     * Implements a link to another document.
     * @param filename the filename for the remote document
     * @param page the page to jump to
     * @param llx the lower left x corner of the activation area
     * @param lly the lower left y corner of the activation area
     * @param urx the upper right x corner of the activation area
     * @param ury the upper right y corner of the activation area
     */
    void remoteGoto(final String filename, final int page, final float llx, final float lly, final float urx, final float ury) {
        addAnnotation(writer.createAnnotation(llx, lly, urx, ury, new PdfAction(filename, page), null));
    }

    /** Implements an action in an area.
     * @param action the <CODE>PdfAction</CODE>
     * @param llx the lower left x corner of the activation area
     * @param lly the lower left y corner of the activation area
     * @param urx the upper right x corner of the activation area
     * @param ury the upper right y corner of the activation area
     */
    void setAction(final PdfAction action, final float llx, final float lly, final float urx, final float ury) {
        addAnnotation(writer.createAnnotation(llx, lly, urx, ury, action, null));
    }

    /**
     * Stores the destinations keyed by name. Value is a <Code>Destination</Code>.
     */
    protected TreeMap<String, Destination> localDestinations = new TreeMap<String, Destination>();

    PdfAction getLocalGotoAction(final String name) {
        PdfAction action;
        Destination dest = localDestinations.get(name);
        if (dest == null)
            dest = new Destination();
        if (dest.action == null) {
            if (dest.reference == null) {
                dest.reference = writer.getPdfIndirectReference();
            }
            action = new PdfAction(dest.reference);
            dest.action = action;
            localDestinations.put(name, dest);
        }
        else {
            action = dest.action;
        }
        return action;
    }

    /**
     * The local destination to where a local goto with the same
     * name will jump to.
     * @param name the name of this local destination
     * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates
     * @return <CODE>true</CODE> if the local destination was added,
     * <CODE>false</CODE> if a local destination with the same name
     * already existed
     */
    boolean localDestination(final String name, final PdfDestination destination) {
        Destination dest = localDestinations.get(name);
        if (dest == null)
            dest = new Destination();
        if (dest.destination != null)
            return false;
        dest.destination = destination;
        localDestinations.put(name, dest);
        if (!destination.hasPage())
            destination.addPage(writer.getCurrentPage());
        return true;
    }

    /**
     * Stores a list of document level JavaScript actions.
     */
    int jsCounter;
    protected HashMap<String, PdfObject> documentLevelJS = new HashMap<String, PdfObject>();
    protected static final DecimalFormat SIXTEEN_DIGITS = new DecimalFormat("0000000000000000");
    void addJavaScript(final PdfAction js) {
        if (js.get(PdfName.JS) == null)
            throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed"));
        try {
            documentLevelJS.put(SIXTEEN_DIGITS.format(jsCounter++), writer.addToBody(js).getIndirectReference());
        }
        catch (IOException e) {
            throw new ExceptionConverter(e);
        }
    }
    void addJavaScript(final String name, final PdfAction js) {
        if (js.get(PdfName.JS) == null)
            throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed"));
        try {
            documentLevelJS.put(name, writer.addToBody(js).getIndirectReference());
        }
        catch (IOException e) {
            throw new ExceptionConverter(e);
        }
    }

    HashMap<String, PdfObject> getDocumentLevelJS() {
        return documentLevelJS;
    }

    protected HashMap<String, PdfObject> documentFileAttachment = new HashMap<String, PdfObject>();

    void addFileAttachment(String description, final PdfFileSpecification fs) throws IOException {
        if (description == null) {
            PdfString desc = (PdfString)fs.get(PdfName.DESC);
            if (desc == null) {
                description = "";
            }
            else {
                description = PdfEncodings.convertToString(desc.getBytes(), null);
            }
        }
        fs.addDescription(description, true);
        if (description.length() == 0)
            description = "Unnamed";
        String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null);
        int k = 0;
        while (documentFileAttachment.containsKey(fn)) {
            ++k;
            fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null);
        }
        documentFileAttachment.put(fn, fs.getReference());
    }

    HashMap<String, PdfObject> getDocumentFileAttachment() {
        return documentFileAttachment;
    }

//	[C6] document level actions

    protected String openActionName;

    void setOpenAction(final String name) {
        openActionName = name;
        openActionAction = null;
    }

    protected PdfAction openActionAction;
    void setOpenAction(final PdfAction action) {
        openActionAction = action;
        openActionName = null;
    }

    protected PdfDictionary additionalActions;
    void addAdditionalAction(final PdfName actionType, final PdfAction action)  {
        if (additionalActions == null)  {
            additionalActions = new PdfDictionary();
        }
        if (action == null)
            additionalActions.remove(actionType);
        else
            additionalActions.put(actionType, action);
        if (additionalActions.size() == 0)
            additionalActions = null;
    }

//	[C7] portable collections

    protected PdfCollection collection;

    /**
     * Sets the collection dictionary.
     * @param collection a dictionary of type PdfCollection
     */
    public void setCollection(final PdfCollection collection) {
        this.collection = collection;
    }

//	[C8] AcroForm

    PdfAnnotationsImp annotationsImp;

    /**
     * Gets the AcroForm object.
     * @return the PdfAcroform object of the PdfDocument
     */
    PdfAcroForm getAcroForm() {
        return annotationsImp.getAcroForm();
    }

    void setSigFlags(final int f) {
        annotationsImp.setSigFlags(f);
    }

    void addCalculationOrder(final PdfFormField formField) {
        annotationsImp.addCalculationOrder(formField);
    }

    void addAnnotation(final PdfAnnotation annot) {
        pageEmpty = false;
        annotationsImp.addAnnotation(annot);
    }

    protected PdfString language;
    void setLanguage(final String language) {
        this.language = new PdfString(language);
    }

//	[F12] tagged PDF
//	[U1] page sizes

    /** This is the size of the next page. */
    protected Rectangle nextPageSize = null;

    /** This is the size of the several boxes of the current Page. */
    protected HashMap<String, PdfRectangle> thisBoxSize = new HashMap<String, PdfRectangle>();

    /** This is the size of the several boxes that will be used in
     * the next page. */
    protected HashMap<String, PdfRectangle> boxSize = new HashMap<String, PdfRectangle>();

    void setCropBoxSize(final Rectangle crop) {
        setBoxSize("crop", crop);
    }

    void setBoxSize(final String boxName, final Rectangle size) {
        if (size == null)
            boxSize.remove(boxName);
        else
            boxSize.put(boxName, new PdfRectangle(size));
    }

    protected void setNewPageSizeAndMargins() {
        pageSize = nextPageSize;
        if (marginMirroring && (getPageNumber() & 1) == 0) {
            marginRight = nextMarginLeft;
            marginLeft = nextMarginRight;
        }
        else {
            marginLeft = nextMarginLeft;
            marginRight = nextMarginRight;
        }
        if (marginMirroringTopBottom && (getPageNumber() & 1) == 0) {
            marginTop = nextMarginBottom;
            marginBottom = nextMarginTop;
        }
        else {
            marginTop = nextMarginTop;
            marginBottom = nextMarginBottom;
        }
        if (!isTagged(writer)) {
            text = new PdfContentByte(writer);
            text.reset();
        } else {
            text = graphics;
        }
        text.beginText();
        // we move to the left/top position of the page
        text.moveText(left(), top());
        if (isTagged(writer))
            textEmptySize = text.size();
    }

    /**
     * Gives the size of a trim, art, crop or bleed box, or null if not defined.
     * @param boxName crop, trim, art or bleed
     */
    Rectangle getBoxSize(final String boxName) {
        PdfRectangle r = thisBoxSize.get(boxName);
        if (r != null)  {
            return r.getRectangle();
        }
        return null;
    }

//	[U2] empty pages

    /** This checks if the page is empty. */
    private boolean pageEmpty = true;

    void setPageEmpty(final boolean pageEmpty) {
        this.pageEmpty = pageEmpty;
    }

    boolean isPageEmpty() {
        if (isTagged(writer)) {
            return writer == null || writer.getDirectContent().size(false) == 0 && writer.getDirectContentUnder().size(false) == 0 && text.size(false) - textEmptySize == 0 && (pageEmpty || writer.isPaused());
        } else {
            return writer == null || writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || writer.isPaused());
        }
    }

//	[U3] page actions

    /**
     * Sets the display duration for the page (for presentations)
     * @param seconds   the number of seconds to display the page
     */
    void setDuration(final int seconds) {
        if (seconds > 0)
            writer.addPageDictEntry(PdfName.DUR, new PdfNumber(seconds));
    }

    /**
     * Sets the transition for the page
     * @param transition   the PdfTransition object
     */
    void setTransition(final PdfTransition transition) {
        writer.addPageDictEntry(PdfName.TRANS, transition.getTransitionDictionary());
    }

    protected PdfDictionary pageAA = null;
    void setPageAction(final PdfName actionType, final PdfAction action) {
        if (pageAA == null) {
            pageAA = new PdfDictionary();
        }
        pageAA.put(actionType, action);
    }

//	[U8] thumbnail images

    void setThumbnail(final Image image) throws PdfException, DocumentException {
        writer.addPageDictEntry(PdfName.THUMB, writer.getImageReference(writer.addDirectImageSimple(image)));
    }

//	[M0] Page resources contain references to fonts, extgstate, images,...

    /** This are the page resources of the current Page. */
    protected PageResources pageResources;

    PageResources getPageResources() {
        return pageResources;
    }

//	[M3] Images

    /** Holds value of property strictImageSequence. */
    protected boolean strictImageSequence = false;

    /** Getter for property strictImageSequence.
     * @return Value of property strictImageSequence.
     *
     */
    boolean isStrictImageSequence() {
        return this.strictImageSequence;
    }

    /** Setter for property strictImageSequence.
     * @param strictImageSequence New value of property strictImageSequence.
     *
     */
    void setStrictImageSequence(final boolean strictImageSequence) {
        this.strictImageSequence = strictImageSequence;
    }

    /** This is the position where the image ends. */
    protected float imageEnd = -1;

    /**
     * Method added by Pelikan Stephan
     */
    public void clearTextWrap() {
        float tmpHeight = imageEnd - currentHeight;
        if (line != null) {
            tmpHeight += line.height();
        }
        if (imageEnd > -1 && tmpHeight > 0) {
            carriageReturn();
            currentHeight += tmpHeight;
        }
    }

    public int getStructParentIndex(Object obj) {
        int[] i = structParentIndices.get(obj);
        if (i == null) {
            i = new int[]{structParentIndices.size(), 0};
            structParentIndices.put(obj, i);
        }
        return i[0];
    }

    public int getNextMarkPoint(Object obj) {
        int[] i = structParentIndices.get(obj);
        if (i == null) {
            i = new int[]{structParentIndices.size(), 0};
            structParentIndices.put(obj, i);
        }
        int markPoint = i[1];
        i[1]++;
        return markPoint;
    }

    public int[] getStructParentIndexAndNextMarkPoint(Object obj) {
        int[] i = structParentIndices.get(obj);
        if (i == null) {
            i = new int[]{structParentIndices.size(), 0};
            structParentIndices.put(obj, i);
        }
        int markPoint = i[1];
        i[1]++;
        return new int[] {i[0], markPoint};
    }

    /** This is the image that could not be shown on a previous page. */
    protected Image imageWait = null;

    /**
     * Adds an image to the document.
     * @param image the <CODE>Image</CODE> to add
     * @throws PdfException on error
     * @throws DocumentException on error
     */

    protected void add(final Image image) throws PdfException, DocumentException {
        if (image.hasAbsoluteY()) {
            graphics.addImage(image);
            pageEmpty = false;
            return;
        }

        // if there isn't enough room for the image on this page, save it for the next page
        if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
            if (!strictImageSequence && imageWait == null) {
                imageWait = image;
                return;
            }
            newPage();
            if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
                imageWait = image;
                return;
            }
        }
        pageEmpty = false;
        // avoid endless loops
        if (image == imageWait)
            imageWait = null;
        boolean textwrap = (image.getAlignment() & Image.TEXTWRAP) == Image.TEXTWRAP
                && !((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE);
        boolean underlying = (image.getAlignment() & Image.UNDERLYING) == Image.UNDERLYING;
        float diff = leading / 2;
        if (textwrap) {
            diff += leading;
        }
        float lowerleft = indentTop() - currentHeight - image.getScaledHeight() -diff;
        float mt[] = image.matrix();
        float startPosition = indentLeft() - mt[4];
        if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.getScaledWidth() - mt[4];
        if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + (indentRight() - indentLeft() - image.getScaledWidth()) / 2 - mt[4];
        if (image.hasAbsoluteX()) startPosition = image.getAbsoluteX();
        if (textwrap) {
            if (imageEnd < 0 || imageEnd < currentHeight + image.getScaledHeight() + diff) {
                imageEnd = currentHeight + image.getScaledHeight() + diff;
            }
            if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) {
                // indentation suggested by Pelikan Stephan
                indentation.imageIndentRight += image.getScaledWidth() + image.getIndentationLeft();
            }
            else {
                // indentation suggested by Pelikan Stephan
                indentation.imageIndentLeft += image.getScaledWidth() + image.getIndentationRight();
            }
        }
        else {
            if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.getIndentationRight();
            else if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.getIndentationLeft() - image.getIndentationRight();
            else startPosition += image.getIndentationLeft();
        }
        graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]);
        if (!(textwrap || underlying)) {
            currentHeight += image.getScaledHeight() + diff;
            flushLines();
            text.moveText(0, - (image.getScaledHeight() + diff));
            newLine();
        }
    }

//	[M4] Adding a PdfPTable

    /** Adds a <CODE>PdfPTable</CODE> to the document.
     * @param ptable the <CODE>PdfPTable</CODE> to be added to the document.
     * @throws DocumentException on error
     */
    void addPTable(final PdfPTable ptable) throws DocumentException {
        ColumnText ct = new ColumnText(isTagged(writer) ? text : writer.getDirectContent());
        ct.setRunDirection(ptable.getRunDirection());
        // if the table prefers to be on a single page, and it wouldn't
        //fit on the current page, start a new page.
        if (ptable.getKeepTogether() && !fitsPage(ptable, 0f) && currentHeight > 0)  {
            newPage();
            if (isTagged(writer)) {
                ct.setCanvas(text);
            }
        }
        if (currentHeight == 0) {
            ct.setAdjustFirstLine(false);
        }
        ct.addElement(ptable);
        boolean he = ptable.isHeadersInEvent();
        ptable.setHeadersInEvent(true);
        int loop = 0;
        while (true) {
            ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight);
            int status = ct.go();
            if ((status & ColumnText.NO_MORE_TEXT) != 0) {
                if (isTagged(writer)) {
                    text.setTextMatrix(indentLeft(), ct.getYLine());
                } else {
                    text.moveText(0, ct.getYLine() - indentTop() + currentHeight);
                }
                currentHeight = indentTop() - ct.getYLine();
                break;
            }
            if (indentTop() - currentHeight == ct.getYLine())
                ++loop;
            else
                loop = 0;
            if (loop == 3) {
                throw new DocumentException(MessageLocalization.getComposedMessage("infinite.table.loop"));
            }
            currentHeight = indentTop() - ct.getYLine();
            newPage();
            ptable.setSkipFirstHeader(false);
            if (isTagged(writer)) {
                ct.setCanvas(text);
            }
        }
        ptable.setHeadersInEvent(he);
    }

    private ArrayList<Element> floatingElements = new ArrayList<Element>();

    private void addDiv(final PdfDiv div) throws DocumentException {
        if (floatingElements == null) {
            floatingElements = new ArrayList<Element>();
        }
        floatingElements.add(div);
    }

    private void flushFloatingElements() throws DocumentException {
        if (floatingElements != null && !floatingElements.isEmpty()) {
            ArrayList<Element> cachedFloatingElements = floatingElements;
            floatingElements = null;
            FloatLayout fl = new FloatLayout(cachedFloatingElements, false);
            int loop = 0;
            while (true) {
                float left = indentLeft();
                fl.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight);
                try {
                    int status = fl.layout(isTagged(writer) ? text : writer.getDirectContent(), false);
                    if ((status & ColumnText.NO_MORE_TEXT) != 0) {
                        if (isTagged(writer)) {
                            text.setTextMatrix(indentLeft(), fl.getYLine());
                        } else {
                            text.moveText(0, fl.getYLine() - indentTop() + currentHeight);
                        }
                        currentHeight = indentTop() - fl.getYLine();
                        break;
                    }
                } catch (Exception exc) {
                    return;
                }
                if (indentTop() - currentHeight == fl.getYLine() || isPageEmpty())
                    ++loop;
                else {
                    loop = 0;
                }
                if (loop == 2) {
                    return;
                }
                newPage();
            }
        }
    }

    /**
     * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
     *
     * @param	table	the table that has to be checked
     * @param	margin	a certain margin
     * @return	<CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise.
     */

    boolean fitsPage(final PdfPTable table, final float margin) {
        if (!table.isLockedWidth()) {
            float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100;
            table.setTotalWidth(totalWidth);
        }
        // ensuring that a new line has been started.
        ensureNewLine();
        Float spaceNeeded = table.isSkipFirstHeader() ? table.getTotalHeight() - table.getHeaderHeight() : table.getTotalHeight();
        return spaceNeeded + (currentHeight > 0 ? table.spacingBefore() : 0f) <= indentTop() - currentHeight - indentBottom() - margin;
    }

    private static boolean isTagged(final PdfWriter writer) {
        return (writer != null) && writer.isTagged();
    }

    private PdfLine getLastLine() {
        if (lines.size() > 0)
            return lines.get(lines.size() - 1);
        else
            return null;
    }

    /**
     * @since 5.0.1
     */
    public class Destination {
        public PdfAction action;
        public PdfIndirectReference reference;
        public PdfDestination destination;
    }

    protected void useExternalCache(TempFileCache externalCache) {
        isToUseExternalCache = true;
        this.externalCache = externalCache;
    }

    protected void saveStructElement(AccessibleElementId id, PdfStructureElement element) {
        structElements.put(id, element);
    }

    protected PdfStructureElement getStructElement(AccessibleElementId id) {
        return getStructElement(id, true);
    }

    protected PdfStructureElement getStructElement(AccessibleElementId id, boolean toSaveFetchedElement) {
        PdfStructureElement element = structElements.get(id);
        if (isToUseExternalCache && element == null) {
            TempFileCache.ObjectPosition pos = externallyStoredStructElements.get(id);
            if (pos != null) {
                try {
                    element = (PdfStructureElement) externalCache.get(pos);
                    element.setStructureTreeRoot(writer.getStructureTreeRoot());
                    element.setStructureElementParent(getStructElement(elementsParents.get(element.getElementId()), toSaveFetchedElement));

                    if (toSaveFetchedElement) {
                        externallyStoredStructElements.remove(id);
                        structElements.put(id, element);
                    }
                } catch (IOException e) {
                    throw new ExceptionConverter(e);
                } catch (ClassNotFoundException e) {
                    throw new ExceptionConverter(e);
                }
            }
        }
        return element;
    }

    protected void flushStructureElementsOnNewPage() {
        if (!isToUseExternalCache)
            return;

        Iterator<Map.Entry<AccessibleElementId, PdfStructureElement>> iterator = structElements.entrySet().iterator();
        Map.Entry<AccessibleElementId, PdfStructureElement> entry;
        while (iterator.hasNext()) {
            entry = iterator.next();
            if (entry.getValue().getStructureType().equals(PdfName.DOCUMENT))
                continue;

            try {
                PdfStructureElement el = entry.getValue();
                PdfDictionary parentDict = el.getParent();
                PdfStructureElement parent = null;
                if (parentDict instanceof PdfStructureElement) {
                    parent = (PdfStructureElement) parentDict;
                }
                if (parent != null) {
                    elementsParents.put(entry.getKey(), parent.getElementId());
                }

                TempFileCache.ObjectPosition pos = externalCache.put(el);
                externallyStoredStructElements.put(entry.getKey(), pos);
                iterator.remove();
            } catch (IOException e) {
                throw new ExceptionConverter(e);
            }
        }
    }

    public Set<AccessibleElementId> getStructElements() {
        Set<AccessibleElementId> elements = new HashSet<AccessibleElementId>();
        elements.addAll(externallyStoredStructElements.keySet());
        elements.addAll(structElements.keySet());
        return elements;
    }

}

com/itextpdf/text/pdf/PdfDocument.java

 

⇒ iText-2.1.6.jar - iText, a JAVA-PDF library

⇐ iText layout.jar Source Code

⇑ Download and Install iText Java Library

⇑⇑ iText for PDF Generation

2021-07-03, 47644👍, 0💬