001/*
002 * $Id: PdfDocument.java 4879 2011-05-23 23:27:02Z redlab_b $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.io.IOException;
047import java.text.DecimalFormat;
048import java.util.ArrayList;
049import java.util.HashMap;
050import java.util.Iterator;
051import java.util.Map;
052import java.util.TreeMap;
053
054import com.itextpdf.text.Anchor;
055import com.itextpdf.text.Annotation;
056import com.itextpdf.text.BaseColor;
057import com.itextpdf.text.Chunk;
058import com.itextpdf.text.Document;
059import com.itextpdf.text.DocumentException;
060import com.itextpdf.text.Element;
061import com.itextpdf.text.ExceptionConverter;
062import com.itextpdf.text.Font;
063import com.itextpdf.text.Image;
064import com.itextpdf.text.List;
065import com.itextpdf.text.ListItem;
066import com.itextpdf.text.MarkedObject;
067import com.itextpdf.text.MarkedSection;
068import com.itextpdf.text.Meta;
069import com.itextpdf.text.Paragraph;
070import com.itextpdf.text.Phrase;
071import com.itextpdf.text.Rectangle;
072import com.itextpdf.text.Section;
073import com.itextpdf.text.api.WriterOperation;
074import com.itextpdf.text.error_messages.MessageLocalization;
075import com.itextpdf.text.pdf.collection.PdfCollection;
076import com.itextpdf.text.pdf.draw.DrawInterface;
077import com.itextpdf.text.pdf.internal.PdfAnnotationsImp;
078import com.itextpdf.text.pdf.internal.PdfViewerPreferencesImp;
079
080/**
081 * <CODE>PdfDocument</CODE> is the class that is used by <CODE>PdfWriter</CODE>
082 * to translate a <CODE>Document</CODE> into a PDF with different pages.
083 * <P>
084 * A <CODE>PdfDocument</CODE> always listens to a <CODE>Document</CODE>
085 * and adds the Pdf representation of every <CODE>Element</CODE> that is
086 * added to the <CODE>Document</CODE>.
087 *
088 * @see         com.itextpdf.text.Document
089 * @see         com.itextpdf.text.DocListener
090 * @see         PdfWriter
091 * @since       2.0.8 (class was package-private before)
092 */
093
094public class PdfDocument extends Document {
095
096    /**
097     * <CODE>PdfInfo</CODE> is the PDF InfoDictionary.
098     * <P>
099     * A document's trailer may contain a reference to an Info dictionary that provides information
100     * about the document. This optional dictionary may contain one or more keys, whose values
101     * should be strings.<BR>
102     * This object is described in the 'Portable Document Format Reference Manual version 1.3'
103     * section 6.10 (page 120-121)
104     * @since   2.0.8 (PdfDocument was package-private before)
105     */
106
107    public static class PdfInfo extends PdfDictionary {
108
109        /**
110         * Construct a <CODE>PdfInfo</CODE>-object.
111         */
112
113        PdfInfo() {
114            super();
115            addProducer();
116            addCreationDate();
117        }
118
119        /**
120         * Constructs a <CODE>PdfInfo</CODE>-object.
121         *
122         * @param               author          name of the author of the document
123         * @param               title           title of the document
124         * @param               subject         subject of the document
125         */
126
127        PdfInfo(final String author, final String title, final String subject) {
128            this();
129            addTitle(title);
130            addSubject(subject);
131            addAuthor(author);
132        }
133
134        /**
135         * Adds the title of the document.
136         *
137         * @param       title           the title of the document
138         */
139
140        void addTitle(final String title) {
141            put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE));
142        }
143
144        /**
145         * Adds the subject to the document.
146         *
147         * @param       subject         the subject of the document
148         */
149
150        void addSubject(final String subject) {
151            put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE));
152        }
153
154        /**
155         * Adds some keywords to the document.
156         *
157         * @param       keywords                the keywords of the document
158         */
159
160        void addKeywords(final String keywords) {
161            put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE));
162        }
163
164        /**
165         * Adds the name of the author to the document.
166         *
167         * @param       author          the name of the author
168         */
169
170        void addAuthor(final String author) {
171            put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE));
172        }
173
174        /**
175         * Adds the name of the creator to the document.
176         *
177         * @param       creator         the name of the creator
178         */
179
180        void addCreator(final String creator) {
181            put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE));
182        }
183
184        /**
185         * Adds the name of the producer to the document.
186         */
187
188        void addProducer() {
189            put(PdfName.PRODUCER, new PdfString(getVersion()));
190        }
191
192        /**
193         * Adds the date of creation to the document.
194         */
195
196        void addCreationDate() {
197            PdfString date = new PdfDate();
198            put(PdfName.CREATIONDATE, date);
199            put(PdfName.MODDATE, date);
200        }
201
202        void addkey(final String key, final String value) {
203            if (key.equals("Producer") || key.equals("CreationDate"))
204                return;
205            put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE));
206        }
207    }
208
209    /**
210     * <CODE>PdfCatalog</CODE> is the PDF Catalog-object.
211     * <P>
212     * The Catalog is a dictionary that is the root node of the document. It contains a reference
213     * to the tree of pages contained in the document, a reference to the tree of objects representing
214     * the document's outline, a reference to the document's article threads, and the list of named
215     * destinations. In addition, the Catalog indicates whether the document's outline or thumbnail
216     * page images should be displayed automatically when the document is viewed and whether some location
217     * other than the first page should be shown when the document is opened.<BR>
218     * In this class however, only the reference to the tree of pages is implemented.<BR>
219     * This object is described in the 'Portable Document Format Reference Manual version 1.3'
220     * section 6.2 (page 67-71)
221     */
222
223    static class PdfCatalog extends PdfDictionary {
224
225        /** The writer writing the PDF for which we are creating this catalog object. */
226        PdfWriter writer;
227
228        /**
229         * Constructs a <CODE>PdfCatalog</CODE>.
230         *
231         * @param               pages           an indirect reference to the root of the document's Pages tree.
232         * @param writer the writer the catalog applies to
233         */
234
235        PdfCatalog(final PdfIndirectReference pages, final PdfWriter writer) {
236            super(CATALOG);
237            this.writer = writer;
238            put(PdfName.PAGES, pages);
239        }
240
241        /**
242         * Adds the names of the named destinations to the catalog.
243         * @param localDestinations the local destinations
244         * @param documentLevelJS the javascript used in the document
245         * @param documentFileAttachment        the attached files
246         * @param writer the writer the catalog applies to
247         */
248        void addNames(final TreeMap<String, Destination> localDestinations, final HashMap<String, PdfObject> documentLevelJS, final HashMap<String, PdfObject> documentFileAttachment, final PdfWriter writer) {
249            if (localDestinations.isEmpty() && documentLevelJS.isEmpty() && documentFileAttachment.isEmpty())
250                return;
251            try {
252                PdfDictionary names = new PdfDictionary();
253                if (!localDestinations.isEmpty()) {
254                    PdfArray ar = new PdfArray();
255                    for (Map.Entry<String, Destination> entry : localDestinations.entrySet()) {
256                        String name = entry.getKey();
257                        Destination dest = entry.getValue();
258                        if (dest.destination == null) //no destination
259                            continue;
260                        PdfIndirectReference ref = dest.reference;
261                        ar.add(new PdfString(name, null));
262                        ar.add(ref);
263                    }
264                    if (ar.size() > 0) {
265                        PdfDictionary dests = new PdfDictionary();
266                        dests.put(PdfName.NAMES, ar);
267                        names.put(PdfName.DESTS, writer.addToBody(dests).getIndirectReference());
268                    }
269                }
270                if (!documentLevelJS.isEmpty()) {
271                    PdfDictionary tree = PdfNameTree.writeTree(documentLevelJS, writer);
272                    names.put(PdfName.JAVASCRIPT, writer.addToBody(tree).getIndirectReference());
273                }
274                if (!documentFileAttachment.isEmpty()) {
275                    names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference());
276                }
277                if (names.size() > 0)
278                    put(PdfName.NAMES, writer.addToBody(names).getIndirectReference());
279            }
280            catch (IOException e) {
281                throw new ExceptionConverter(e);
282            }
283        }
284
285        /**
286         * Adds an open action to the catalog.
287         * @param       action  the action that will be triggered upon opening the document
288         */
289        void setOpenAction(final PdfAction action) {
290            put(PdfName.OPENACTION, action);
291        }
292
293
294        /**
295         * Sets the document level additional actions.
296         * @param actions   dictionary of actions
297         */
298        void setAdditionalActions(final PdfDictionary actions) {
299            try {
300                put(PdfName.AA, writer.addToBody(actions).getIndirectReference());
301            } catch (Exception e) {
302                throw new ExceptionConverter(e);
303            }
304        }
305    }
306
307// CONSTRUCTING A PdfDocument/PdfWriter INSTANCE
308
309    /**
310     * Constructs a new PDF document.
311     */
312    public PdfDocument() {
313        super();
314        addProducer();
315        addCreationDate();
316    }
317
318    /** The <CODE>PdfWriter</CODE>. */
319    protected PdfWriter writer;
320
321    /**
322     * Adds a <CODE>PdfWriter</CODE> to the <CODE>PdfDocument</CODE>.
323     *
324     * @param writer the <CODE>PdfWriter</CODE> that writes everything
325     *                     what is added to this document to an outputstream.
326     * @throws DocumentException on error
327     */
328    public void addWriter(final PdfWriter writer) throws DocumentException {
329        if (this.writer == null) {
330            this.writer = writer;
331            annotationsImp = new PdfAnnotationsImp(writer);
332            return;
333        }
334        throw new DocumentException(MessageLocalization.getComposedMessage("you.can.only.add.a.writer.to.a.pdfdocument.once"));
335    }
336
337// LISTENER METHODS START
338
339//      [L0] ElementListener interface
340
341    /** This is the PdfContentByte object, containing the text. */
342    protected PdfContentByte text;
343
344    /** This is the PdfContentByte object, containing the borders and other Graphics. */
345    protected PdfContentByte graphics;
346
347    /** This represents the leading of the lines. */
348    protected float leading = 0;
349
350    /**
351     * Getter for the current leading.
352     * @return  the current leading
353     * @since   2.1.2
354     */
355    public float getLeading() {
356        return leading;
357    }
358
359    /**
360     * Setter for the current leading.
361     * @param   leading the current leading
362     * @since   2.1.6
363     */
364    void setLeading(final float leading) {
365        this.leading = leading;
366    }
367
368    /** This represents the current alignment of the PDF Elements. */
369    protected int alignment = Element.ALIGN_LEFT;
370
371    /** This is the current height of the document. */
372    protected float currentHeight = 0;
373
374    /**
375     * Signals that onParagraph is valid (to avoid that a Chapter/Section title is treated as a Paragraph).
376     * @since 2.1.2
377     */
378    protected boolean isSectionTitle = false;
379
380    /**
381     * Signals that the current leading has to be subtracted from a YMark object when positive.
382     * @since 2.1.2
383     */
384    protected int leadingCount = 0;
385
386    /** The current active <CODE>PdfAction</CODE> when processing an <CODE>Anchor</CODE>. */
387    protected PdfAction anchorAction = null;
388
389    /**
390     * Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>.
391     *
392     * @param element the element to add
393     * @return <CODE>true</CODE> if the element was added, <CODE>false</CODE> if not.
394     * @throws DocumentException when a document isn't open yet, or has been closed
395     */
396    @Override
397    public boolean add(final Element element) throws DocumentException {
398        if (writer != null && writer.isPaused()) {
399            return false;
400        }
401        try {
402                // TODO refactor this uber long switch to State/Strategy or something ...
403            switch(element.type()) {
404                // Information (headers)
405                case Element.HEADER:
406                    info.addkey(((Meta)element).getName(), ((Meta)element).getContent());
407                    break;
408                case Element.TITLE:
409                    info.addTitle(((Meta)element).getContent());
410                    break;
411                case Element.SUBJECT:
412                    info.addSubject(((Meta)element).getContent());
413                    break;
414                case Element.KEYWORDS:
415                    info.addKeywords(((Meta)element).getContent());
416                    break;
417                case Element.AUTHOR:
418                    info.addAuthor(((Meta)element).getContent());
419                    break;
420                case Element.CREATOR:
421                    info.addCreator(((Meta)element).getContent());
422                    break;
423                case Element.PRODUCER:
424                    // you can not change the name of the producer
425                    info.addProducer();
426                    break;
427                case Element.CREATIONDATE:
428                    // you can not set the creation date, only reset it
429                    info.addCreationDate();
430                    break;
431
432                // content (text)
433                case Element.CHUNK: {
434                    // if there isn't a current line available, we make one
435                    if (line == null) {
436                        carriageReturn();
437                    }
438
439                    // we cast the element to a chunk
440                    PdfChunk chunk = new PdfChunk((Chunk) element, anchorAction);
441                    // we try to add the chunk to the line, until we succeed
442                    {
443                        PdfChunk overflow;
444                        while ((overflow = line.add(chunk)) != null) {
445                            carriageReturn();
446                            chunk = overflow;
447                            chunk.trimFirstSpace();
448                        }
449                    }
450                    pageEmpty = false;
451                    if (chunk.isAttribute(Chunk.NEWPAGE)) {
452                        newPage();
453                    }
454                    break;
455                }
456                case Element.ANCHOR: {
457                        leadingCount++;
458                    Anchor anchor = (Anchor) element;
459                    String url = anchor.getReference();
460                    leading = anchor.getLeading();
461                    if (url != null) {
462                        anchorAction = new PdfAction(url);
463                    }
464                    // we process the element
465                    element.process(this);
466                    anchorAction = null;
467                    leadingCount--;
468                    break;
469                }
470                case Element.ANNOTATION: {
471                    if (line == null) {
472                        carriageReturn();
473                    }
474                    Annotation annot = (Annotation) element;
475                    Rectangle rect = new Rectangle(0, 0);
476                    if (line != null)
477                        rect = new Rectangle(annot.llx(indentRight() - line.widthLeft()), annot.ury(indentTop() - currentHeight - 20), annot.urx(indentRight() - line.widthLeft() + 20), annot.lly(indentTop() - currentHeight));
478                    PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, rect);
479                    annotationsImp.addPlainAnnotation(an);
480                    pageEmpty = false;
481                    break;
482                }
483                case Element.PHRASE: {
484                        leadingCount++;
485                    // we cast the element to a phrase and set the leading of the document
486                    leading = ((Phrase) element).getLeading();
487                    // we process the element
488                    element.process(this);
489                    leadingCount--;
490                    break;
491                }
492                case Element.PARAGRAPH: {
493                        leadingCount++;
494                    // we cast the element to a paragraph
495                    Paragraph paragraph = (Paragraph) element;
496                    addSpacing(paragraph.getSpacingBefore(), leading, paragraph.getFont());
497
498                    // we adjust the parameters of the document
499                    alignment = paragraph.getAlignment();
500                    leading = paragraph.getTotalLeading();
501                    carriageReturn();
502
503                    // we don't want to make orphans/widows
504                    if (currentHeight + line.height() + leading > indentTop() - indentBottom()) {
505                        newPage();
506                    }
507                    indentation.indentLeft += paragraph.getIndentationLeft();
508                    indentation.indentRight += paragraph.getIndentationRight();
509                    carriageReturn();
510
511                    PdfPageEvent pageEvent = writer.getPageEvent();
512                    if (pageEvent != null && !isSectionTitle)
513                        pageEvent.onParagraph(writer, this, indentTop() - currentHeight);
514
515                    // if a paragraph has to be kept together, we wrap it in a table object
516                    if (paragraph.getKeepTogether()) {
517                        carriageReturn();
518                        PdfPTable table = new PdfPTable(1);
519                        table.setWidthPercentage(100f);
520                        PdfPCell cell = new PdfPCell();
521                        cell.addElement(paragraph);
522                        cell.setBorder(Rectangle.NO_BORDER);
523                        cell.setPadding(0);
524                        table.addCell(cell);
525                        indentation.indentLeft -= paragraph.getIndentationLeft();
526                        indentation.indentRight -= paragraph.getIndentationRight();
527                        this.add(table);
528                        indentation.indentLeft += paragraph.getIndentationLeft();
529                        indentation.indentRight += paragraph.getIndentationRight();
530                    }
531                    else {
532                        line.setExtraIndent(paragraph.getFirstLineIndent());
533                        element.process(this);
534                        carriageReturn();
535                        addSpacing(paragraph.getSpacingAfter(), paragraph.getTotalLeading(), paragraph.getFont());
536                    }
537
538                    if (pageEvent != null && !isSectionTitle)
539                        pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight);
540
541                    alignment = Element.ALIGN_LEFT;
542                    indentation.indentLeft -= paragraph.getIndentationLeft();
543                    indentation.indentRight -= paragraph.getIndentationRight();
544                    carriageReturn();
545                    leadingCount--;
546                    break;
547                }
548                case Element.SECTION:
549                case Element.CHAPTER: {
550                    // Chapters and Sections only differ in their constructor
551                    // so we cast both to a Section
552                    Section section = (Section) element;
553                    PdfPageEvent pageEvent = writer.getPageEvent();
554
555                    boolean hasTitle = section.isNotAddedYet()
556                        && section.getTitle() != null;
557
558                    // if the section is a chapter, we begin a new page
559                    if (section.isTriggerNewPage()) {
560                        newPage();
561                    }
562
563                    if (hasTitle) {
564                        float fith = indentTop() - currentHeight;
565                        int rotation = pageSize.getRotation();
566                        if (rotation == 90 || rotation == 180)
567                                fith = pageSize.getHeight() - fith;
568                        PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith);
569                        while (currentOutline.level() >= section.getDepth()) {
570                                currentOutline = currentOutline.parent();
571                        }
572                        PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen());
573                        currentOutline = outline;
574                    }
575
576                    // some values are set
577                    carriageReturn();
578                    indentation.sectionIndentLeft += section.getIndentationLeft();
579                    indentation.sectionIndentRight += section.getIndentationRight();
580
581                    if (section.isNotAddedYet() && pageEvent != null)
582                        if (element.type() == Element.CHAPTER)
583                            pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.getTitle());
584                        else
585                            pageEvent.onSection(writer, this, indentTop() - currentHeight, section.getDepth(), section.getTitle());
586
587                    // the title of the section (if any has to be printed)
588                    if (hasTitle) {
589                        isSectionTitle = true;
590                        add(section.getTitle());
591                        isSectionTitle = false;
592                    }
593                    indentation.sectionIndentLeft += section.getIndentation();
594                    // we process the section
595                    element.process(this);
596                    flushLines();
597                    // some parameters are set back to normal again
598                    indentation.sectionIndentLeft -= section.getIndentationLeft() + section.getIndentation();
599                    indentation.sectionIndentRight -= section.getIndentationRight();
600
601                    if (section.isComplete() && pageEvent != null)
602                        if (element.type() == Element.CHAPTER)
603                            pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight);
604                        else
605                            pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight);
606
607                    break;
608                }
609                case Element.LIST: {
610                    // we cast the element to a List
611                    List list = (List) element;
612                    if (list.isAlignindent()) {
613                        list.normalizeIndentation();
614                    }
615                    // we adjust the document
616                    indentation.listIndentLeft += list.getIndentationLeft();
617                    indentation.indentRight += list.getIndentationRight();
618                    // we process the items in the list
619                    element.process(this);
620                    // some parameters are set back to normal again
621                    indentation.listIndentLeft -= list.getIndentationLeft();
622                    indentation.indentRight -= list.getIndentationRight();
623                    carriageReturn();
624                    break;
625                }
626                case Element.LISTITEM: {
627                        leadingCount++;
628                    // we cast the element to a ListItem
629                    ListItem listItem = (ListItem) element;
630
631                    addSpacing(listItem.getSpacingBefore(), leading, listItem.getFont());
632
633                    // we adjust the document
634                    alignment = listItem.getAlignment();
635                    indentation.listIndentLeft += listItem.getIndentationLeft();
636                    indentation.indentRight += listItem.getIndentationRight();
637                    leading = listItem.getTotalLeading();
638                    carriageReturn();
639
640                    // we prepare the current line to be able to show us the listsymbol
641                    line.setListItem(listItem);
642                    // we process the item
643                    element.process(this);
644
645                    addSpacing(listItem.getSpacingAfter(), listItem.getTotalLeading(), listItem.getFont());
646
647                    // if the last line is justified, it should be aligned to the left
648                    if (line.hasToBeJustified()) {
649                        line.resetAlignment();
650                    }
651                    // some parameters are set back to normal again
652                    carriageReturn();
653                    indentation.listIndentLeft -= listItem.getIndentationLeft();
654                    indentation.indentRight -= listItem.getIndentationRight();
655                    leadingCount--;
656                    break;
657                }
658                case Element.RECTANGLE: {
659                    Rectangle rectangle = (Rectangle) element;
660                    graphics.rectangle(rectangle);
661                    pageEmpty = false;
662                    break;
663                }
664                case Element.PTABLE: {
665                    PdfPTable ptable = (PdfPTable)element;
666                    if (ptable.size() <= ptable.getHeaderRows())
667                        break; //nothing to do
668
669                    // before every table, we add a new line and flush all lines
670                    ensureNewLine();
671                    flushLines();
672
673                    addPTable(ptable);
674                    pageEmpty = false;
675                    newLine();
676                    break;
677                }
678                case Element.MULTI_COLUMN_TEXT: {
679                    ensureNewLine();
680                    flushLines();
681                    MultiColumnText multiText = (MultiColumnText) element;
682                    float height = multiText.write(writer.getDirectContent(), this, indentTop() - currentHeight);
683                    currentHeight += height;
684                    text.moveText(0, -1f* height);
685                    pageEmpty = false;
686                    break;
687                }
688                case Element.JPEG:
689                case Element.JPEG2000:
690                case Element.JBIG2:
691                case Element.IMGRAW:
692                case Element.IMGTEMPLATE: {
693                    //carriageReturn(); suggestion by Marc Campforts
694                    add((Image) element);
695                    break;
696                }
697                case Element.YMARK: {
698                    DrawInterface zh = (DrawInterface)element;
699                    zh.draw(graphics, indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight - (leadingCount > 0 ? leading : 0));
700                    pageEmpty = false;
701                    break;
702                }
703                case Element.MARKED: {
704                        MarkedObject mo;
705                        if (element instanceof MarkedSection) {
706                                mo = ((MarkedSection)element).getTitle();
707                                if (mo != null) {
708                                        mo.process(this);
709                                }
710                        }
711                        mo = (MarkedObject)element;
712                        mo.process(this);
713                        break;
714                }
715                case Element.WRITABLE_DIRECT:
716                        if (null != writer) {
717                                ((WriterOperation)element).write(writer, this);
718                        }
719                        break;
720                default:
721                    return false;
722            }
723            lastElementType = element.type();
724            return true;
725        }
726        catch(Exception e) {
727            throw new DocumentException(e);
728        }
729    }
730
731//      [L1] DocListener interface
732
733    /**
734     * Opens the document.
735     * <P>
736     * You have to open the document before you can begin to add content
737     * to the body of the document.
738     */
739    @Override
740    public void open() {
741        if (!open) {
742            super.open();
743            writer.open();
744            rootOutline = new PdfOutline(writer);
745            currentOutline = rootOutline;
746        }
747        try {
748            initPage();
749        }
750        catch(DocumentException de) {
751            throw new ExceptionConverter(de);
752        }
753    }
754
755//      [L2] DocListener interface
756
757    /**
758     * Closes the document.
759     * <B>
760     * Once all the content has been written in the body, you have to close
761     * the body. After that nothing can be written to the body anymore.
762     */
763    @Override
764    public void close() {
765        if (close) {
766            return;
767        }
768        try {
769                boolean wasImage = imageWait != null;
770            newPage();
771            if (imageWait != null || wasImage) newPage();
772            if (annotationsImp.hasUnusedAnnotations())
773                throw new RuntimeException(MessageLocalization.getComposedMessage("not.all.annotations.could.be.added.to.the.document.the.document.doesn.t.have.enough.pages"));
774            PdfPageEvent pageEvent = writer.getPageEvent();
775            if (pageEvent != null)
776                pageEvent.onCloseDocument(writer, this);
777            super.close();
778
779            writer.addLocalDestinations(localDestinations);
780            calculateOutlineCount();
781            writeOutlines();
782        }
783        catch(Exception e) {
784            throw ExceptionConverter.convertException(e);
785        }
786
787        writer.close();
788    }
789
790//      [L3] DocListener interface
791    protected int textEmptySize;
792
793    // [C9] Metadata for the page
794        /**
795         * Use this method to set the XMP Metadata.
796         * @param xmpMetadata The xmpMetadata to set.
797         * @throws IOException
798         */
799        public void setXmpMetadata(final byte[] xmpMetadata) throws IOException {
800        PdfStream xmp = new PdfStream(xmpMetadata);
801        xmp.put(PdfName.TYPE, PdfName.METADATA);
802        xmp.put(PdfName.SUBTYPE, PdfName.XML);
803        PdfEncryption crypto = writer.getEncryption();
804        if (crypto != null && !crypto.isMetadataEncrypted()) {
805            PdfArray ar = new PdfArray();
806            ar.add(PdfName.CRYPT);
807            xmp.put(PdfName.FILTER, ar);
808        }
809        writer.addPageDictEntry(PdfName.METADATA, writer.addToBody(xmp).getIndirectReference());
810        }
811
812    /**
813     * Makes a new page and sends it to the <CODE>PdfWriter</CODE>.
814     *
815     * @return a <CODE>boolean</CODE>
816     */
817    @Override
818    public boolean newPage() {
819        lastElementType = -1;
820        if (isPageEmpty()) {
821                setNewPageSizeAndMargins();
822            return false;
823        }
824        if (!open || close) {
825                throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
826        }
827        PdfPageEvent pageEvent = writer.getPageEvent();
828        if (pageEvent != null)
829            pageEvent.onEndPage(writer, this);
830
831        //Added to inform any listeners that we are moving to a new page (added by David Freels)
832        super.newPage();
833
834        // the following 2 lines were added by Pelikan Stephan
835        indentation.imageIndentLeft = 0;
836        indentation.imageIndentRight = 0;
837
838        try {
839            // we flush the arraylist with recently written lines
840                flushLines();
841
842                // we prepare the elements of the page dictionary
843
844                // [U1] page size and rotation
845                int rotation = pageSize.getRotation();
846
847                // [C10]
848                if (writer.isPdfX()) {
849                        if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim"))
850                                throw new PdfXConformanceException(MessageLocalization.getComposedMessage("only.one.of.artbox.or.trimbox.can.exist.in.the.page"));
851                        if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) {
852                                if (thisBoxSize.containsKey("crop"))
853                                        thisBoxSize.put("trim", thisBoxSize.get("crop"));
854                                else
855                                        thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation()));
856                        }
857                }
858
859                // [M1]
860                pageResources.addDefaultColorDiff(writer.getDefaultColorspace());
861            if (writer.isRgbTransparencyBlending()) {
862                PdfDictionary dcs = new PdfDictionary();
863                dcs.put(PdfName.CS, PdfName.DEVICERGB);
864                pageResources.addDefaultColorDiff(dcs);
865            }
866                PdfDictionary resources = pageResources.getResources();
867
868                // we create the page dictionary
869
870                PdfPage page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation);
871                page.put(PdfName.TABS, writer.getTabs());
872                page.putAll(writer.getPageDictEntries());
873                writer.resetPageDictEntries();
874
875            // we complete the page dictionary
876
877                // [U3] page actions: additional actions
878                if (pageAA != null) {
879                        page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference());
880                        pageAA = null;
881                }
882
883                // [C5] and [C8] we add the annotations
884                if (annotationsImp.hasUnusedAnnotations()) {
885                        PdfArray array = annotationsImp.rotateAnnotations(writer, pageSize);
886                        if (array.size() != 0)
887                                page.put(PdfName.ANNOTS, array);
888                }
889
890                // [F12] we add tag info
891                if (writer.isTagged())
892                        page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1));
893
894            if (text.size() > textEmptySize)
895                        text.endText();
896                else
897                        text = null;
898                writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, text, writer.getDirectContent(), pageSize));
899                // we initialize the new page
900                initPage();
901        }
902        catch(DocumentException de) {
903                // maybe this never happens, but it's better to check.
904                throw new ExceptionConverter(de);
905        }
906        catch (IOException ioe) {
907            throw new ExceptionConverter(ioe);
908        }
909        return true;
910    }
911
912//      [L4] DocListener interface
913
914    /**
915     * Sets the pagesize.
916     *
917     * @param pageSize the new pagesize
918     * @return <CODE>true</CODE> if the page size was set
919     */
920    @Override
921    public boolean setPageSize(final Rectangle pageSize) {
922        if (writer != null && writer.isPaused()) {
923            return false;
924        }
925        nextPageSize = new Rectangle(pageSize);
926        return true;
927    }
928
929//      [L5] DocListener interface
930
931    /** margin in x direction starting from the left. Will be valid in the next page */
932    protected float nextMarginLeft;
933
934    /** margin in x direction starting from the right. Will be valid in the next page */
935    protected float nextMarginRight;
936
937    /** margin in y direction starting from the top. Will be valid in the next page */
938    protected float nextMarginTop;
939
940    /** margin in y direction starting from the bottom. Will be valid in the next page */
941    protected float nextMarginBottom;
942
943    /**
944     * Sets the margins.
945     *
946     * @param   marginLeft              the margin on the left
947     * @param   marginRight             the margin on the right
948     * @param   marginTop               the margin on the top
949     * @param   marginBottom    the margin on the bottom
950     * @return  a <CODE>boolean</CODE>
951     */
952    @Override
953    public boolean setMargins(final float marginLeft, final float marginRight, final float marginTop, final float marginBottom) {
954        if (writer != null && writer.isPaused()) {
955            return false;
956        }
957        nextMarginLeft = marginLeft;
958        nextMarginRight = marginRight;
959        nextMarginTop = marginTop;
960        nextMarginBottom = marginBottom;
961        return true;
962    }
963
964//      [L6] DocListener interface
965
966    /**
967     * @see com.itextpdf.text.DocListener#setMarginMirroring(boolean)
968     */
969    @Override
970    public boolean setMarginMirroring(final boolean MarginMirroring) {
971        if (writer != null && writer.isPaused()) {
972            return false;
973        }
974        return super.setMarginMirroring(MarginMirroring);
975    }
976
977    /**
978     * @see com.itextpdf.text.DocListener#setMarginMirroring(boolean)
979     * @since   2.1.6
980     */
981    @Override
982    public boolean setMarginMirroringTopBottom(final boolean MarginMirroringTopBottom) {
983        if (writer != null && writer.isPaused()) {
984            return false;
985        }
986        return super.setMarginMirroringTopBottom(MarginMirroringTopBottom);
987    }
988
989//      [L7] DocListener interface
990
991    /**
992     * Sets the page number.
993     *
994     * @param   pageN           the new page number
995     */
996    @Override
997    public void setPageCount(final int pageN) {
998        if (writer != null && writer.isPaused()) {
999            return;
1000        }
1001        super.setPageCount(pageN);
1002    }
1003
1004//      [L8] DocListener interface
1005
1006    /**
1007     * Sets the page number to 0.
1008     */
1009    @Override
1010    public void resetPageCount() {
1011        if (writer != null && writer.isPaused()) {
1012            return;
1013        }
1014        super.resetPageCount();
1015    }
1016
1017// DOCLISTENER METHODS END
1018
1019    /** Signals that OnOpenDocument should be called. */
1020    protected boolean firstPageEvent = true;
1021
1022    /**
1023     * Initializes a page.
1024     * <P>
1025     * If the footer/header is set, it is printed.
1026     * @throws DocumentException on error
1027     */
1028    protected void initPage() throws DocumentException {
1029        // the pagenumber is incremented
1030        pageN++;
1031
1032        // initialization of some page objects
1033        annotationsImp.resetAnnotations();
1034        pageResources = new PageResources();
1035
1036        writer.resetContent();
1037        graphics = new PdfContentByte(writer);
1038
1039        markPoint = 0;
1040        setNewPageSizeAndMargins();
1041        imageEnd = -1;
1042        indentation.imageIndentRight = 0;
1043        indentation.imageIndentLeft = 0;
1044        indentation.indentBottom = 0;
1045        indentation.indentTop = 0;
1046        currentHeight = 0;
1047
1048        // backgroundcolors, etc...
1049        thisBoxSize = new HashMap<String, PdfRectangle>(boxSize);
1050        if (pageSize.getBackgroundColor() != null
1051        || pageSize.hasBorders()
1052        || pageSize.getBorderColor() != null) {
1053            add(pageSize);
1054        }
1055
1056        float oldleading = leading;
1057        int oldAlignment = alignment;
1058        pageEmpty = true;
1059        // if there is an image waiting to be drawn, draw it
1060        try {
1061            if (imageWait != null) {
1062                add(imageWait);
1063                imageWait = null;
1064            }
1065        }
1066        catch(Exception e) {
1067            throw new ExceptionConverter(e);
1068        }
1069        leading = oldleading;
1070        alignment = oldAlignment;
1071        carriageReturn();
1072
1073        PdfPageEvent pageEvent = writer.getPageEvent();
1074        if (pageEvent != null) {
1075            if (firstPageEvent) {
1076                pageEvent.onOpenDocument(writer, this);
1077            }
1078            pageEvent.onStartPage(writer, this);
1079        }
1080        firstPageEvent = false;
1081    }
1082
1083    /** The line that is currently being written. */
1084    protected PdfLine line = null;
1085    /** The lines that are written until now. */
1086    protected ArrayList<PdfLine> lines = new ArrayList<PdfLine>();
1087
1088    /**
1089     * Adds the current line to the list of lines and also adds an empty line.
1090     * @throws DocumentException on error
1091     */
1092    protected void newLine() throws DocumentException {
1093        lastElementType = -1;
1094        carriageReturn();
1095        if (lines != null && !lines.isEmpty()) {
1096            lines.add(line);
1097            currentHeight += line.height();
1098        }
1099        line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1100    }
1101
1102    /**
1103     * If the current line is not empty or null, it is added to the arraylist
1104     * of lines and a new empty line is added.
1105     */
1106    protected void carriageReturn() {
1107        // the arraylist with lines may not be null
1108        if (lines == null) {
1109            lines = new ArrayList<PdfLine>();
1110        }
1111        // If the current line is not null or empty
1112        if (line != null && line.size() > 0) {
1113            // we check if the end of the page is reached (bugfix by Francois Gravel)
1114            if (currentHeight + line.height() + leading > indentTop() - indentBottom()) {
1115                // if the end of the line is reached, we start a newPage which will flush existing lines
1116                // then move to next page but before then we need to exclude the current one that does not fit
1117                // After the new page we add the current line back in
1118                PdfLine overflowLine = line;
1119                line = null;
1120                newPage();
1121                line = overflowLine;
1122            }
1123            currentHeight += line.height();
1124            lines.add(line);
1125            pageEmpty = false;
1126        }
1127        if (imageEnd > -1 && currentHeight > imageEnd) {
1128            imageEnd = -1;
1129            indentation.imageIndentRight = 0;
1130            indentation.imageIndentLeft = 0;
1131        }
1132        // a new current line is constructed
1133        line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1134    }
1135
1136    /**
1137     * Gets the current vertical page position.
1138     * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
1139     *   for elements that do not terminate the lines they've started because those lines will get
1140     *   terminated.
1141     * @return The current vertical page position.
1142     */
1143    public float getVerticalPosition(final boolean ensureNewLine) {
1144        // ensuring that a new line has been started.
1145        if (ensureNewLine) {
1146          ensureNewLine();
1147        }
1148        return top() -  currentHeight - indentation.indentTop;
1149    }
1150
1151    /** Holds the type of the last element, that has been added to the document. */
1152    protected int lastElementType = -1;
1153
1154    /**
1155     * Ensures that a new line has been started.
1156     */
1157    protected void ensureNewLine() {
1158      try {
1159        if (lastElementType == Element.PHRASE ||
1160            lastElementType == Element.CHUNK) {
1161          newLine();
1162          flushLines();
1163        }
1164      } catch (DocumentException ex) {
1165        throw new ExceptionConverter(ex);
1166        }
1167    }
1168
1169    /**
1170     * Writes all the lines to the text-object.
1171     *
1172     * @return the displacement that was caused
1173     * @throws DocumentException on error
1174     */
1175    protected float flushLines() throws DocumentException {
1176        // checks if the ArrayList with the lines is not null
1177        if (lines == null) {
1178            return 0;
1179        }
1180        // checks if a new Line has to be made.
1181        if (line != null && line.size() > 0) {
1182            lines.add(line);
1183            line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1184        }
1185
1186        // checks if the ArrayList with the lines is empty
1187        if (lines.isEmpty()) {
1188            return 0;
1189        }
1190
1191        // initialization of some parameters
1192        Object currentValues[] = new Object[2];
1193        PdfFont currentFont = null;
1194        float displacement = 0;
1195        Float lastBaseFactor = new Float(0);
1196        currentValues[1] = lastBaseFactor;
1197        // looping over all the lines
1198        for (PdfLine l: lines) {
1199            float moveTextX = l.indentLeft() - indentLeft() + indentation.indentLeft + indentation.listIndentLeft + indentation.sectionIndentLeft;
1200            text.moveText(moveTextX, -l.height());
1201            // is the line preceded by a symbol?
1202            if (l.listSymbol() != null) {
1203                ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(l.listSymbol()), text.getXTLM() - l.listIndent(), text.getYTLM(), 0);
1204            }
1205
1206            currentValues[0] = currentFont;
1207
1208            writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio());
1209
1210            currentFont = (PdfFont)currentValues[0];
1211            displacement += l.height();
1212            text.moveText(-moveTextX, 0);
1213
1214        }
1215        lines = new ArrayList<PdfLine>();
1216        return displacement;
1217    }
1218
1219    /** The characters to be applied the hanging punctuation. */
1220    static final String hangingPunctuation = ".,;:'";
1221
1222    /**
1223     * Writes a text line to the document. It takes care of all the attributes.
1224     * <P>
1225     * Before entering the line position must have been established and the
1226     * <CODE>text</CODE> argument must be in text object scope (<CODE>beginText()</CODE>).
1227     * @param line the line to be written
1228     * @param text the <CODE>PdfContentByte</CODE> where the text will be written to
1229     * @param graphics the <CODE>PdfContentByte</CODE> where the graphics will be written to
1230     * @param currentValues the current font and extra spacing values
1231     * @param ratio
1232     * @throws DocumentException on error
1233     * @since 5.0.3 returns a float instead of void
1234     */
1235    float writeLineToContent(final PdfLine line, final PdfContentByte text, final PdfContentByte graphics, final Object currentValues[], final float ratio)  throws DocumentException {
1236        PdfFont currentFont = (PdfFont)currentValues[0];
1237        float lastBaseFactor = ((Float)currentValues[1]).floatValue();
1238        PdfChunk chunk;
1239        int numberOfSpaces;
1240        int lineLen;
1241        boolean isJustified;
1242        float hangingCorrection = 0;
1243        float hScale = 1;
1244        float lastHScale = Float.NaN;
1245        float baseWordSpacing = 0;
1246        float baseCharacterSpacing = 0;
1247        float glueWidth = 0;
1248        float lastX = text.getXTLM() + line.getOriginalWidth();
1249        numberOfSpaces = line.numberOfSpaces();
1250        lineLen = line.getLineLengthUtf32();
1251        // does the line need to be justified?
1252        isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1);
1253        int separatorCount = line.getSeparatorCount();
1254        if (separatorCount > 0) {
1255                glueWidth = line.widthLeft() / separatorCount;
1256        }
1257        else if (isJustified && separatorCount == 0) {
1258            if (line.isNewlineSplit() && line.widthLeft() >= lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1)) {
1259                if (line.isRTL()) {
1260                    text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0);
1261                }
1262                baseWordSpacing = ratio * lastBaseFactor;
1263                baseCharacterSpacing = lastBaseFactor;
1264            }
1265            else {
1266                float width = line.widthLeft();
1267                PdfChunk last = line.getChunk(line.size() - 1);
1268                if (last != null) {
1269                    String s = last.toString();
1270                    char c;
1271                    if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) {
1272                        float oldWidth = width;
1273                        width += last.font().width(c) * 0.4f;
1274                        hangingCorrection = width - oldWidth;
1275                    }
1276                }
1277                float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1);
1278                baseWordSpacing = ratio * baseFactor;
1279                baseCharacterSpacing = baseFactor;
1280                lastBaseFactor = baseFactor;
1281            }
1282        }
1283        else if (line.alignment == Element.ALIGN_LEFT || line.alignment == Element.ALIGN_UNDEFINED) {
1284                lastX -= line.widthLeft();
1285        }
1286
1287        int lastChunkStroke = line.getLastStrokeChunk();
1288        int chunkStrokeIdx = 0;
1289        float xMarker = text.getXTLM();
1290        float baseXMarker = xMarker;
1291        float yMarker = text.getYTLM();
1292        boolean adjustMatrix = false;
1293        float tabPosition = 0;
1294
1295        // looping over all the chunks in 1 line
1296        for (Iterator<PdfChunk> j = line.iterator(); j.hasNext(); ) {
1297            chunk = j.next();
1298            BaseColor color = chunk.color();
1299            float fontSize = chunk.font().size();
1300            float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1301            float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1302            hScale = 1;
1303
1304            if (chunkStrokeIdx <= lastChunkStroke) {
1305                float width;
1306                if (isJustified) {
1307                    width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing);
1308                }
1309                else {
1310                    width = chunk.width();
1311                }
1312                if (chunk.isStroked()) {
1313                    PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1);
1314                    if (chunk.isSeparator()) {
1315                        width = glueWidth;
1316                        Object[] sep = (Object[])chunk.getAttribute(Chunk.SEPARATOR);
1317                        DrawInterface di = (DrawInterface)sep[0];
1318                        Boolean vertical = (Boolean)sep[1];
1319                        if (vertical.booleanValue()) {
1320                                di.draw(graphics, baseXMarker, yMarker + descender, baseXMarker + line.getOriginalWidth(), ascender - descender, yMarker);
1321                        }
1322                        else {
1323                                di.draw(graphics, xMarker, yMarker + descender, xMarker + width, ascender - descender, yMarker);
1324                        }
1325                    }
1326                    if (chunk.isTab()) {
1327                        Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB);
1328                        DrawInterface di = (DrawInterface)tab[0];
1329                        tabPosition = ((Float)tab[1]).floatValue() + ((Float)tab[3]).floatValue();
1330                        if (tabPosition > xMarker) {
1331                                di.draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker);
1332                        }
1333                        float tmp = xMarker;
1334                        xMarker = tabPosition;
1335                        tabPosition = tmp;
1336                    }
1337                    if (chunk.isAttribute(Chunk.BACKGROUND)) {
1338                        float subtract = lastBaseFactor;
1339                        if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND))
1340                            subtract = 0;
1341                        if (nextChunk == null)
1342                            subtract += hangingCorrection;
1343                        Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND);
1344                        graphics.setColorFill((BaseColor)bgr[0]);
1345                        float extra[] = (float[])bgr[1];
1346                        graphics.rectangle(xMarker - extra[0],
1347                            yMarker + descender - extra[1] + chunk.getTextRise(),
1348                            width - subtract + extra[0] + extra[2],
1349                            ascender - descender + extra[1] + extra[3]);
1350                        graphics.fill();
1351                        graphics.setGrayFill(0);
1352                    }
1353                    if (chunk.isAttribute(Chunk.UNDERLINE)) {
1354                        float subtract = lastBaseFactor;
1355                        if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE))
1356                            subtract = 0;
1357                        if (nextChunk == null)
1358                            subtract += hangingCorrection;
1359                        Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE);
1360                        BaseColor scolor = null;
1361                        for (int k = 0; k < unders.length; ++k) {
1362                            Object obj[] = unders[k];
1363                            scolor = (BaseColor)obj[0];
1364                            float ps[] = (float[])obj[1];
1365                            if (scolor == null)
1366                                scolor = color;
1367                            if (scolor != null)
1368                                graphics.setColorStroke(scolor);
1369                            graphics.setLineWidth(ps[0] + fontSize * ps[1]);
1370                            float shift = ps[2] + fontSize * ps[3];
1371                            int cap2 = (int)ps[4];
1372                            if (cap2 != 0)
1373                                graphics.setLineCap(cap2);
1374                            graphics.moveTo(xMarker, yMarker + shift);
1375                            graphics.lineTo(xMarker + width - subtract, yMarker + shift);
1376                            graphics.stroke();
1377                            if (scolor != null)
1378                                graphics.resetGrayStroke();
1379                            if (cap2 != 0)
1380                                graphics.setLineCap(0);
1381                        }
1382                        graphics.setLineWidth(1);
1383                    }
1384                    if (chunk.isAttribute(Chunk.ACTION)) {
1385                        float subtract = lastBaseFactor;
1386                        if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION))
1387                            subtract = 0;
1388                        if (nextChunk == null)
1389                            subtract += hangingCorrection;
1390                        text.addAnnotation(new PdfAnnotation(writer, xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise(), (PdfAction)chunk.getAttribute(Chunk.ACTION)));
1391                    }
1392                    if (chunk.isAttribute(Chunk.REMOTEGOTO)) {
1393                        float subtract = lastBaseFactor;
1394                        if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO))
1395                            subtract = 0;
1396                        if (nextChunk == null)
1397                            subtract += hangingCorrection;
1398                        Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO);
1399                        String filename = (String)obj[0];
1400                        if (obj[1] instanceof String)
1401                                remoteGoto(filename, (String)obj[1], xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise());
1402                        else
1403                                remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker + descender + chunk.getTextRise(), xMarker + width - subtract, yMarker + ascender + chunk.getTextRise());
1404                    }
1405                    if (chunk.isAttribute(Chunk.LOCALGOTO)) {
1406                        float subtract = lastBaseFactor;
1407                        if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO))
1408                            subtract = 0;
1409                        if (nextChunk == null)
1410                            subtract += hangingCorrection;
1411                        localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize);
1412                    }
1413                    if (chunk.isAttribute(Chunk.LOCALDESTINATION)) {
1414                        float subtract = lastBaseFactor;
1415                        if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION))
1416                            subtract = 0;
1417                        if (nextChunk == null)
1418                            subtract += hangingCorrection;
1419                        localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + fontSize, 0));
1420                    }
1421                    if (chunk.isAttribute(Chunk.GENERICTAG)) {
1422                        float subtract = lastBaseFactor;
1423                        if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG))
1424                            subtract = 0;
1425                        if (nextChunk == null)
1426                            subtract += hangingCorrection;
1427                        Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + fontSize);
1428                        PdfPageEvent pev = writer.getPageEvent();
1429                        if (pev != null)
1430                            pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG));
1431                    }
1432                    if (chunk.isAttribute(Chunk.PDFANNOTATION)) {
1433                        float subtract = lastBaseFactor;
1434                        if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION))
1435                            subtract = 0;
1436                        if (nextChunk == null)
1437                            subtract += hangingCorrection;
1438                        PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION));
1439                        annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender));
1440                        text.addAnnotation(annot);
1441                    }
1442                    float params[] = (float[])chunk.getAttribute(Chunk.SKEW);
1443                    Float hs = (Float)chunk.getAttribute(Chunk.HSCALE);
1444                    if (params != null || hs != null) {
1445                        float b = 0, c = 0;
1446                        if (params != null) {
1447                            b = params[0];
1448                            c = params[1];
1449                        }
1450                        if (hs != null)
1451                            hScale = hs.floatValue();
1452                        text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker);
1453                    }
1454                    if (chunk.isAttribute(Chunk.CHAR_SPACING)) {
1455                        Float cs = (Float) chunk.getAttribute(Chunk.CHAR_SPACING);
1456                                                text.setCharacterSpacing(cs.floatValue());
1457                                        }
1458                    if (chunk.isImage()) {
1459                        Image image = chunk.getImage();
1460                        float matrix[] = image.matrix();
1461                        matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX];
1462                        matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY];
1463                        graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
1464                        text.moveText(xMarker + lastBaseFactor + image.getScaledWidth() - text.getXTLM(), 0);
1465                    }
1466                }
1467                xMarker += width;
1468                ++chunkStrokeIdx;
1469            }
1470
1471            if (chunk.font().compareTo(currentFont) != 0) {
1472                currentFont = chunk.font();
1473                text.setFontAndSize(currentFont.getFont(), currentFont.size());
1474            }
1475            float rise = 0;
1476            Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE);
1477            int tr = 0;
1478            float strokeWidth = 1;
1479            BaseColor strokeColor = null;
1480            Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT);
1481            if (textRender != null) {
1482                tr = ((Integer)textRender[0]).intValue() & 3;
1483                if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
1484                    text.setTextRenderingMode(tr);
1485                if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) {
1486                    strokeWidth = ((Float)textRender[1]).floatValue();
1487                    if (strokeWidth != 1)
1488                        text.setLineWidth(strokeWidth);
1489                    strokeColor = (BaseColor)textRender[2];
1490                    if (strokeColor == null)
1491                        strokeColor = color;
1492                    if (strokeColor != null)
1493                        text.setColorStroke(strokeColor);
1494                }
1495            }
1496            if (fr != null)
1497                rise = fr.floatValue();
1498            if (color != null)
1499                text.setColorFill(color);
1500            if (rise != 0)
1501                text.setTextRise(rise);
1502            if (chunk.isImage()) {
1503                adjustMatrix = true;
1504            }
1505            else if (chunk.isHorizontalSeparator()) {
1506                PdfTextArray array = new PdfTextArray();
1507                array.add(-glueWidth * 1000f / chunk.font.size() / hScale);
1508                text.showText(array);
1509            }
1510            else if (chunk.isTab()) {
1511                PdfTextArray array = new PdfTextArray();
1512                array.add((tabPosition - xMarker) * 1000f / chunk.font.size() / hScale);
1513                text.showText(array);
1514            }
1515            // If it is a CJK chunk or Unicode TTF we will have to simulate the
1516            // space adjustment.
1517            else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) {
1518                if (hScale != lastHScale) {
1519                    lastHScale = hScale;
1520                    text.setWordSpacing(baseWordSpacing / hScale);
1521                    text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing());
1522                }
1523                String s = chunk.toString();
1524                int idx = s.indexOf(' ');
1525                if (idx < 0)
1526                    text.showText(s);
1527                else {
1528                    float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale;
1529                    PdfTextArray textArray = new PdfTextArray(s.substring(0, idx));
1530                    int lastIdx = idx;
1531                    while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) {
1532                        textArray.add(spaceCorrection);
1533                        textArray.add(s.substring(lastIdx, idx));
1534                        lastIdx = idx;
1535                    }
1536                    textArray.add(spaceCorrection);
1537                    textArray.add(s.substring(lastIdx));
1538                    text.showText(textArray);
1539                }
1540            }
1541            else {
1542                if (isJustified && hScale != lastHScale) {
1543                    lastHScale = hScale;
1544                    text.setWordSpacing(baseWordSpacing / hScale);
1545                    text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing());
1546                }
1547                text.showText(chunk.toString());
1548            }
1549
1550            if (rise != 0)
1551                text.setTextRise(0);
1552            if (color != null)
1553                text.resetRGBColorFill();
1554            if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
1555                text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
1556            if (strokeColor != null)
1557                text.resetRGBColorStroke();
1558            if (strokeWidth != 1)
1559                text.setLineWidth(1);
1560            if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) {
1561                adjustMatrix = true;
1562                text.setTextMatrix(xMarker, yMarker);
1563            }
1564            if (chunk.isAttribute(Chunk.CHAR_SPACING)) {
1565                                text.setCharacterSpacing(baseCharacterSpacing);
1566            }
1567        }
1568        if (isJustified) {
1569            text.setWordSpacing(0);
1570            text.setCharacterSpacing(0);
1571            if (line.isNewlineSplit())
1572                lastBaseFactor = 0;
1573        }
1574        if (adjustMatrix)
1575            text.moveText(baseXMarker - text.getXTLM(), 0);
1576        currentValues[0] = currentFont;
1577        currentValues[1] = new Float(lastBaseFactor);
1578        return lastX;
1579    }
1580
1581    protected Indentation indentation = new Indentation();
1582
1583    /**
1584     * @since   2.0.8 (PdfDocument was package-private before)
1585     */
1586    public static class Indentation {
1587
1588        /** This represents the current indentation of the PDF Elements on the left side. */
1589        float indentLeft = 0;
1590
1591        /** Indentation to the left caused by a section. */
1592        float sectionIndentLeft = 0;
1593
1594        /** This represents the current indentation of the PDF Elements on the left side. */
1595        float listIndentLeft = 0;
1596
1597        /** This is the indentation caused by an image on the left. */
1598        float imageIndentLeft = 0;
1599
1600        /** This represents the current indentation of the PDF Elements on the right side. */
1601        float indentRight = 0;
1602
1603        /** Indentation to the right caused by a section. */
1604        float sectionIndentRight = 0;
1605
1606        /** This is the indentation caused by an image on the right. */
1607        float imageIndentRight = 0;
1608
1609        /** This represents the current indentation of the PDF Elements on the top side. */
1610        float indentTop = 0;
1611
1612        /** This represents the current indentation of the PDF Elements on the bottom side. */
1613        float indentBottom = 0;
1614    }
1615
1616    /**
1617     * Gets the indentation on the left side.
1618     *
1619     * @return  a margin
1620     */
1621
1622    protected float indentLeft() {
1623        return left(indentation.indentLeft + indentation.listIndentLeft + indentation.imageIndentLeft + indentation.sectionIndentLeft);
1624    }
1625
1626    /**
1627     * Gets the indentation on the right side.
1628     *
1629     * @return  a margin
1630     */
1631
1632    protected float indentRight() {
1633        return right(indentation.indentRight + indentation.sectionIndentRight + indentation.imageIndentRight);
1634    }
1635
1636    /**
1637     * Gets the indentation on the top side.
1638     *
1639     * @return  a margin
1640     */
1641
1642    protected float indentTop() {
1643        return top(indentation.indentTop);
1644    }
1645
1646    /**
1647     * Gets the indentation on the bottom side.
1648     *
1649     * @return  a margin
1650     */
1651
1652    float indentBottom() {
1653        return bottom(indentation.indentBottom);
1654    }
1655
1656    /**
1657     * Adds extra space.
1658     * This method should probably be rewritten.
1659     */
1660    protected void addSpacing(final float extraspace, final float oldleading, Font f) {
1661        if (extraspace == 0) return;
1662        if (pageEmpty) return;
1663        if (currentHeight + line.height() + leading > indentTop() - indentBottom()) return;
1664        leading = extraspace;
1665        carriageReturn();
1666        if (f.isUnderlined() || f.isStrikethru()) {
1667            f = new Font(f);
1668            int style = f.getStyle();
1669            style &= ~Font.UNDERLINE;
1670            style &= ~Font.STRIKETHRU;
1671            f.setStyle(style);
1672        }
1673        Chunk space = new Chunk(" ", f);
1674        space.process(this);
1675        carriageReturn();
1676        leading = oldleading;
1677    }
1678
1679//      Info Dictionary and Catalog
1680
1681    /** some meta information about the Document. */
1682    protected PdfInfo info = new PdfInfo();
1683
1684    /**
1685     * Gets the <CODE>PdfInfo</CODE>-object.
1686     *
1687     * @return  <CODE>PdfInfo</COPE>
1688     */
1689
1690    PdfInfo getInfo() {
1691        return info;
1692    }
1693
1694    /**
1695     * Gets the <CODE>PdfCatalog</CODE>-object.
1696     *
1697     * @param pages an indirect reference to this document pages
1698     * @return <CODE>PdfCatalog</CODE>
1699     */
1700
1701    PdfCatalog getCatalog(final PdfIndirectReference pages) {
1702        PdfCatalog catalog = new PdfCatalog(pages, writer);
1703
1704        // [C1] outlines
1705        if (rootOutline.getKids().size() > 0) {
1706            catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES);
1707            catalog.put(PdfName.OUTLINES, rootOutline.indirectReference());
1708        }
1709
1710        // [C2] version
1711        writer.getPdfVersion().addToCatalog(catalog);
1712
1713        // [C3] preferences
1714        viewerPreferences.addToCatalog(catalog);
1715
1716        // [C4] pagelabels
1717        if (pageLabels != null) {
1718            catalog.put(PdfName.PAGELABELS, pageLabels.getDictionary(writer));
1719        }
1720
1721        // [C5] named objects
1722        catalog.addNames(localDestinations, getDocumentLevelJS(), documentFileAttachment, writer);
1723
1724        // [C6] actions
1725        if (openActionName != null) {
1726            PdfAction action = getLocalGotoAction(openActionName);
1727            catalog.setOpenAction(action);
1728        }
1729        else if (openActionAction != null)
1730            catalog.setOpenAction(openActionAction);
1731        if (additionalActions != null)   {
1732            catalog.setAdditionalActions(additionalActions);
1733        }
1734
1735        // [C7] portable collections
1736        if (collection != null) {
1737                catalog.put(PdfName.COLLECTION, collection);
1738        }
1739
1740        // [C8] AcroForm
1741        if (annotationsImp.hasValidAcroForm()) {
1742            try {
1743                catalog.put(PdfName.ACROFORM, writer.addToBody(annotationsImp.getAcroForm()).getIndirectReference());
1744            }
1745            catch (IOException e) {
1746                throw new ExceptionConverter(e);
1747            }
1748        }
1749
1750        return catalog;
1751    }
1752
1753//      [C1] outlines
1754
1755    /** This is the root outline of the document. */
1756    protected PdfOutline rootOutline;
1757
1758    /** This is the current <CODE>PdfOutline</CODE> in the hierarchy of outlines. */
1759    protected PdfOutline currentOutline;
1760
1761    /**
1762     * Adds a named outline to the document .
1763     * @param outline the outline to be added
1764     * @param name the name of this local destination
1765     */
1766    void addOutline(final PdfOutline outline, final String name) {
1767        localDestination(name, outline.getPdfDestination());
1768    }
1769
1770    /**
1771     * Gets the root outline. All the outlines must be created with a parent.
1772     * The first level is created with this outline.
1773     * @return the root outline
1774     */
1775    public PdfOutline getRootOutline() {
1776        return rootOutline;
1777    }
1778
1779
1780    /**
1781     * Updates the count in the outlines.
1782     */
1783    void calculateOutlineCount() {
1784        if (rootOutline.getKids().size() == 0)
1785            return;
1786        traverseOutlineCount(rootOutline);
1787    }
1788
1789    /**
1790     * Recursive method to update the count in the outlines.
1791     */
1792    void traverseOutlineCount(final PdfOutline outline) {
1793        ArrayList<PdfOutline> kids = outline.getKids();
1794        PdfOutline parent = outline.parent();
1795        if (kids.isEmpty()) {
1796            if (parent != null) {
1797                parent.setCount(parent.getCount() + 1);
1798            }
1799        }
1800        else {
1801            for (int k = 0; k < kids.size(); ++k) {
1802                traverseOutlineCount(kids.get(k));
1803            }
1804            if (parent != null) {
1805                if (outline.isOpen()) {
1806                    parent.setCount(outline.getCount() + parent.getCount() + 1);
1807                }
1808                else {
1809                    parent.setCount(parent.getCount() + 1);
1810                    outline.setCount(-outline.getCount());
1811                }
1812            }
1813        }
1814    }
1815
1816    /**
1817     * Writes the outline tree to the body of the PDF document.
1818     */
1819    void writeOutlines() throws IOException {
1820        if (rootOutline.getKids().size() == 0)
1821            return;
1822        outlineTree(rootOutline);
1823        writer.addToBody(rootOutline, rootOutline.indirectReference());
1824    }
1825
1826    /**
1827     * Recursive method used to write outlines.
1828     */
1829    void outlineTree(final PdfOutline outline) throws IOException {
1830        outline.setIndirectReference(writer.getPdfIndirectReference());
1831        if (outline.parent() != null)
1832            outline.put(PdfName.PARENT, outline.parent().indirectReference());
1833        ArrayList<PdfOutline> kids = outline.getKids();
1834        int size = kids.size();
1835        for (int k = 0; k < size; ++k)
1836            outlineTree(kids.get(k));
1837        for (int k = 0; k < size; ++k) {
1838            if (k > 0)
1839                kids.get(k).put(PdfName.PREV, kids.get(k - 1).indirectReference());
1840            if (k < size - 1)
1841                kids.get(k).put(PdfName.NEXT, kids.get(k + 1).indirectReference());
1842        }
1843        if (size > 0) {
1844            outline.put(PdfName.FIRST, kids.get(0).indirectReference());
1845            outline.put(PdfName.LAST, kids.get(size - 1).indirectReference());
1846        }
1847        for (int k = 0; k < size; ++k) {
1848            PdfOutline kid = kids.get(k);
1849            writer.addToBody(kid, kid.indirectReference());
1850        }
1851    }
1852
1853//  [C3] PdfViewerPreferences interface
1854
1855        /** Contains the Viewer preferences of this PDF document. */
1856    protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
1857    /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */
1858    void setViewerPreferences(final int preferences) {
1859        this.viewerPreferences.setViewerPreferences(preferences);
1860    }
1861
1862    /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfObject) */
1863    void addViewerPreference(final PdfName key, final PdfObject value) {
1864        this.viewerPreferences.addViewerPreference(key, value);
1865    }
1866
1867//      [C4] Page labels
1868
1869    protected PdfPageLabels pageLabels;
1870    /**
1871     * Sets the page labels
1872     * @param pageLabels the page labels
1873     */
1874    void setPageLabels(final PdfPageLabels pageLabels) {
1875        this.pageLabels = pageLabels;
1876    }
1877
1878//      [C5] named objects: local destinations, javascript, embedded files
1879
1880    /**
1881     * Implements a link to other part of the document. The jump will
1882     * be made to a local destination with the same name, that must exist.
1883     * @param name the name for this link
1884     * @param llx the lower left x corner of the activation area
1885     * @param lly the lower left y corner of the activation area
1886     * @param urx the upper right x corner of the activation area
1887     * @param ury the upper right y corner of the activation area
1888     */
1889    void localGoto(final String name, final float llx, final float lly, final float urx, final float ury) {
1890        PdfAction action = getLocalGotoAction(name);
1891        annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
1892    }
1893
1894    /**
1895     * Implements a link to another document.
1896     * @param filename the filename for the remote document
1897     * @param name the name to jump to
1898     * @param llx the lower left x corner of the activation area
1899     * @param lly the lower left y corner of the activation area
1900     * @param urx the upper right x corner of the activation area
1901     * @param ury the upper right y corner of the activation area
1902     */
1903    void remoteGoto(final String filename, final String name, final float llx, final float lly, final float urx, final float ury) {
1904        annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, name)));
1905    }
1906
1907    /**
1908     * Implements a link to another document.
1909     * @param filename the filename for the remote document
1910     * @param page the page to jump to
1911     * @param llx the lower left x corner of the activation area
1912     * @param lly the lower left y corner of the activation area
1913     * @param urx the upper right x corner of the activation area
1914     * @param ury the upper right y corner of the activation area
1915     */
1916    void remoteGoto(final String filename, final int page, final float llx, final float lly, final float urx, final float ury) {
1917        addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, page)));
1918    }
1919
1920    /** Implements an action in an area.
1921     * @param action the <CODE>PdfAction</CODE>
1922     * @param llx the lower left x corner of the activation area
1923     * @param lly the lower left y corner of the activation area
1924     * @param urx the upper right x corner of the activation area
1925     * @param ury the upper right y corner of the activation area
1926     */
1927    void setAction(final PdfAction action, final float llx, final float lly, final float urx, final float ury) {
1928        addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
1929    }
1930
1931    /**
1932     * Stores the destinations keyed by name. Value is a <Code>Destination</Code>.
1933     */
1934    protected TreeMap<String, Destination> localDestinations = new TreeMap<String, Destination>();
1935
1936    PdfAction getLocalGotoAction(final String name) {
1937        PdfAction action;
1938        Destination dest = localDestinations.get(name);
1939        if (dest == null)
1940            dest = new Destination();
1941        if (dest.action == null) {
1942            if (dest.reference == null) {
1943                dest.reference = writer.getPdfIndirectReference();
1944            }
1945            action = new PdfAction(dest.reference);
1946            dest.action = action;
1947            localDestinations.put(name, dest);
1948        }
1949        else {
1950            action = dest.action;
1951        }
1952        return action;
1953    }
1954
1955    /**
1956     * The local destination to where a local goto with the same
1957     * name will jump to.
1958     * @param name the name of this local destination
1959     * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates
1960     * @return <CODE>true</CODE> if the local destination was added,
1961     * <CODE>false</CODE> if a local destination with the same name
1962     * already existed
1963     */
1964    boolean localDestination(final String name, final PdfDestination destination) {
1965        Destination dest = localDestinations.get(name);
1966        if (dest == null)
1967            dest = new Destination();
1968        if (dest.destination != null)
1969            return false;
1970        dest.destination = destination;
1971        localDestinations.put(name, dest);
1972        if (!destination.hasPage())
1973                destination.addPage(writer.getCurrentPage());
1974        return true;
1975    }
1976
1977    /**
1978     * Stores a list of document level JavaScript actions.
1979     */
1980    int jsCounter;
1981    protected HashMap<String, PdfObject> documentLevelJS = new HashMap<String, PdfObject>();
1982    protected static final DecimalFormat SIXTEEN_DIGITS = new DecimalFormat("0000000000000000");
1983    void addJavaScript(final PdfAction js) {
1984        if (js.get(PdfName.JS) == null)
1985            throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed"));
1986        try {
1987            documentLevelJS.put(SIXTEEN_DIGITS.format(jsCounter++), writer.addToBody(js).getIndirectReference());
1988        }
1989        catch (IOException e) {
1990            throw new ExceptionConverter(e);
1991        }
1992    }
1993    void addJavaScript(final String name, final PdfAction js) {
1994        if (js.get(PdfName.JS) == null)
1995            throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed"));
1996        try {
1997            documentLevelJS.put(name, writer.addToBody(js).getIndirectReference());
1998        }
1999        catch (IOException e) {
2000            throw new ExceptionConverter(e);
2001        }
2002    }
2003
2004    HashMap<String, PdfObject> getDocumentLevelJS() {
2005        return documentLevelJS;
2006    }
2007
2008    protected HashMap<String, PdfObject> documentFileAttachment = new HashMap<String, PdfObject>();
2009
2010    void addFileAttachment(String description, final PdfFileSpecification fs) throws IOException {
2011        if (description == null) {
2012                PdfString desc = (PdfString)fs.get(PdfName.DESC);
2013                if (desc == null) {
2014                        description = "";
2015                }
2016                else {
2017                        description = PdfEncodings.convertToString(desc.getBytes(), null);
2018                }
2019        }
2020        fs.addDescription(description, true);
2021        if (description.length() == 0)
2022            description = "Unnamed";
2023        String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null);
2024        int k = 0;
2025        while (documentFileAttachment.containsKey(fn)) {
2026            ++k;
2027            fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null);
2028        }
2029        documentFileAttachment.put(fn, fs.getReference());
2030    }
2031
2032    HashMap<String, PdfObject> getDocumentFileAttachment() {
2033        return documentFileAttachment;
2034    }
2035
2036//      [C6] document level actions
2037
2038    protected String openActionName;
2039
2040    void setOpenAction(final String name) {
2041        openActionName = name;
2042        openActionAction = null;
2043    }
2044
2045    protected PdfAction openActionAction;
2046    void setOpenAction(final PdfAction action) {
2047        openActionAction = action;
2048        openActionName = null;
2049    }
2050
2051    protected PdfDictionary additionalActions;
2052    void addAdditionalAction(final PdfName actionType, final PdfAction action)  {
2053        if (additionalActions == null)  {
2054            additionalActions = new PdfDictionary();
2055        }
2056        if (action == null)
2057            additionalActions.remove(actionType);
2058        else
2059            additionalActions.put(actionType, action);
2060        if (additionalActions.size() == 0)
2061            additionalActions = null;
2062    }
2063
2064//      [C7] portable collections
2065
2066    protected PdfCollection collection;
2067
2068    /**
2069     * Sets the collection dictionary.
2070     * @param collection a dictionary of type PdfCollection
2071     */
2072        public void setCollection(final PdfCollection collection) {
2073                this.collection = collection;
2074        }
2075
2076//      [C8] AcroForm
2077
2078        PdfAnnotationsImp annotationsImp;
2079
2080    /**
2081     * Gets the AcroForm object.
2082     * @return the PdfAcroform object of the PdfDocument
2083     */
2084    PdfAcroForm getAcroForm() {
2085        return annotationsImp.getAcroForm();
2086    }
2087
2088    void setSigFlags(final int f) {
2089        annotationsImp.setSigFlags(f);
2090    }
2091
2092    void addCalculationOrder(final PdfFormField formField) {
2093        annotationsImp.addCalculationOrder(formField);
2094    }
2095
2096    void addAnnotation(final PdfAnnotation annot) {
2097        pageEmpty = false;
2098        annotationsImp.addAnnotation(annot);
2099    }
2100
2101//      [F12] tagged PDF
2102
2103    protected int markPoint;
2104
2105        int getMarkPoint() {
2106            return markPoint;
2107        }
2108
2109        void incMarkPoint() {
2110            ++markPoint;
2111        }
2112
2113//      [U1] page sizes
2114
2115    /** This is the size of the next page. */
2116    protected Rectangle nextPageSize = null;
2117
2118    /** This is the size of the several boxes of the current Page. */
2119    protected HashMap<String, PdfRectangle> thisBoxSize = new HashMap<String, PdfRectangle>();
2120
2121    /** This is the size of the several boxes that will be used in
2122     * the next page. */
2123    protected HashMap<String, PdfRectangle> boxSize = new HashMap<String, PdfRectangle>();
2124
2125    void setCropBoxSize(final Rectangle crop) {
2126        setBoxSize("crop", crop);
2127    }
2128
2129    void setBoxSize(final String boxName, final Rectangle size) {
2130        if (size == null)
2131            boxSize.remove(boxName);
2132        else
2133            boxSize.put(boxName, new PdfRectangle(size));
2134    }
2135
2136    protected void setNewPageSizeAndMargins() {
2137        pageSize = nextPageSize;
2138        if (marginMirroring && (getPageNumber() & 1) == 0) {
2139            marginRight = nextMarginLeft;
2140            marginLeft = nextMarginRight;
2141        }
2142        else {
2143            marginLeft = nextMarginLeft;
2144            marginRight = nextMarginRight;
2145        }
2146        if (marginMirroringTopBottom && (getPageNumber() & 1) == 0) {
2147                marginTop = nextMarginBottom;
2148                marginBottom = nextMarginTop;
2149        }
2150        else {
2151                marginTop = nextMarginTop;
2152                marginBottom = nextMarginBottom;
2153        }
2154        text = new PdfContentByte(writer);
2155        text.reset();
2156        text.beginText();
2157        textEmptySize = text.size();
2158        // we move to the left/top position of the page
2159        text.moveText(left(), top());
2160    }
2161
2162    /**
2163     * Gives the size of a trim, art, crop or bleed box, or null if not defined.
2164     * @param boxName crop, trim, art or bleed
2165     */
2166    Rectangle getBoxSize(final String boxName) {
2167        PdfRectangle r = thisBoxSize.get(boxName);
2168        if (r != null) return r.getRectangle();
2169        return null;
2170    }
2171
2172//      [U2] empty pages
2173
2174    /** This checks if the page is empty. */
2175    private boolean pageEmpty = true;
2176
2177    void setPageEmpty(final boolean pageEmpty) {
2178        this.pageEmpty = pageEmpty;
2179    }
2180
2181    boolean isPageEmpty() {
2182        return writer == null || writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || writer.isPaused());
2183    }
2184
2185//      [U3] page actions
2186
2187    /**
2188     * Sets the display duration for the page (for presentations)
2189     * @param seconds   the number of seconds to display the page
2190     */
2191    void setDuration(final int seconds) {
2192        if (seconds > 0)
2193                writer.addPageDictEntry(PdfName.DUR, new PdfNumber(seconds));
2194    }
2195
2196    /**
2197     * Sets the transition for the page
2198     * @param transition   the PdfTransition object
2199     */
2200    void setTransition(final PdfTransition transition) {
2201        writer.addPageDictEntry(PdfName.TRANS, transition.getTransitionDictionary());
2202    }
2203
2204    protected PdfDictionary pageAA = null;
2205    void setPageAction(final PdfName actionType, final PdfAction action) {
2206        if (pageAA == null) {
2207            pageAA = new PdfDictionary();
2208        }
2209        pageAA.put(actionType, action);
2210    }
2211
2212//      [U8] thumbnail images
2213
2214    void setThumbnail(final Image image) throws PdfException, DocumentException {
2215        writer.addPageDictEntry(PdfName.THUMB, writer.getImageReference(writer.addDirectImageSimple(image)));
2216    }
2217
2218//      [M0] Page resources contain references to fonts, extgstate, images,...
2219
2220    /** This are the page resources of the current Page. */
2221    protected PageResources pageResources;
2222
2223    PageResources getPageResources() {
2224        return pageResources;
2225    }
2226
2227//      [M3] Images
2228
2229    /** Holds value of property strictImageSequence. */
2230    protected boolean strictImageSequence = false;
2231
2232    /** Getter for property strictImageSequence.
2233     * @return Value of property strictImageSequence.
2234     *
2235     */
2236    boolean isStrictImageSequence() {
2237        return this.strictImageSequence;
2238    }
2239
2240    /** Setter for property strictImageSequence.
2241     * @param strictImageSequence New value of property strictImageSequence.
2242     *
2243     */
2244    void setStrictImageSequence(final boolean strictImageSequence) {
2245        this.strictImageSequence = strictImageSequence;
2246    }
2247
2248    /** This is the position where the image ends. */
2249    protected float imageEnd = -1;
2250
2251        /**
2252         * Method added by Pelikan Stephan
2253         */
2254        public void clearTextWrap() {
2255                float tmpHeight = imageEnd - currentHeight;
2256                if (line != null) {
2257                        tmpHeight += line.height();
2258                }
2259                if (imageEnd > -1 && tmpHeight > 0) {
2260                        carriageReturn();
2261                        currentHeight += tmpHeight;
2262                }
2263        }
2264
2265    /** This is the image that could not be shown on a previous page. */
2266    protected Image imageWait = null;
2267
2268    /**
2269     * Adds an image to the document.
2270     * @param image the <CODE>Image</CODE> to add
2271     * @throws PdfException on error
2272     * @throws DocumentException on error
2273     */
2274
2275    protected void add(final Image image) throws PdfException, DocumentException {
2276
2277        if (image.hasAbsoluteY()) {
2278            graphics.addImage(image);
2279            pageEmpty = false;
2280            return;
2281        }
2282
2283        // if there isn't enough room for the image on this page, save it for the next page
2284        if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
2285            if (!strictImageSequence && imageWait == null) {
2286                imageWait = image;
2287                return;
2288            }
2289            newPage();
2290            if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
2291                imageWait = image;
2292                return;
2293            }
2294        }
2295        pageEmpty = false;
2296        // avoid endless loops
2297        if (image == imageWait)
2298            imageWait = null;
2299        boolean textwrap = (image.getAlignment() & Image.TEXTWRAP) == Image.TEXTWRAP
2300        && !((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE);
2301        boolean underlying = (image.getAlignment() & Image.UNDERLYING) == Image.UNDERLYING;
2302        float diff = leading / 2;
2303        if (textwrap) {
2304            diff += leading;
2305        }
2306        float lowerleft = indentTop() - currentHeight - image.getScaledHeight() -diff;
2307        float mt[] = image.matrix();
2308        float startPosition = indentLeft() - mt[4];
2309        if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.getScaledWidth() - mt[4];
2310        if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + (indentRight() - indentLeft() - image.getScaledWidth()) / 2 - mt[4];
2311        if (image.hasAbsoluteX()) startPosition = image.getAbsoluteX();
2312        if (textwrap) {
2313            if (imageEnd < 0 || imageEnd < currentHeight + image.getScaledHeight() + diff) {
2314                imageEnd = currentHeight + image.getScaledHeight() + diff;
2315            }
2316            if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) {
2317                // indentation suggested by Pelikan Stephan
2318                indentation.imageIndentRight += image.getScaledWidth() + image.getIndentationLeft();
2319            }
2320            else {
2321                // indentation suggested by Pelikan Stephan
2322                indentation.imageIndentLeft += image.getScaledWidth() + image.getIndentationRight();
2323            }
2324        }
2325        else {
2326                if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.getIndentationRight();
2327                else if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.getIndentationLeft() - image.getIndentationRight();
2328                else startPosition += image.getIndentationLeft();
2329        }
2330        graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]);
2331        if (!(textwrap || underlying)) {
2332            currentHeight += image.getScaledHeight() + diff;
2333            flushLines();
2334            text.moveText(0, - (image.getScaledHeight() + diff));
2335            newLine();
2336        }
2337    }
2338
2339//      [M4] Adding a PdfPTable
2340
2341    /** Adds a <CODE>PdfPTable</CODE> to the document.
2342     * @param ptable the <CODE>PdfPTable</CODE> to be added to the document.
2343     * @throws DocumentException on error
2344     */
2345    void addPTable(final PdfPTable ptable) throws DocumentException {
2346        ColumnText ct = new ColumnText(writer.getDirectContent());
2347        // if the table prefers to be on a single page, and it wouldn't
2348        //fit on the current page, start a new page.
2349        if (ptable.getKeepTogether() && !fitsPage(ptable, 0f) && currentHeight > 0)  {
2350                newPage();
2351        }
2352        if (currentHeight == 0) {
2353                ct.setAdjustFirstLine(false);
2354        }
2355        ct.addElement(ptable);
2356        boolean he = ptable.isHeadersInEvent();
2357        ptable.setHeadersInEvent(true);
2358        int loop = 0;
2359        while (true) {
2360            ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight);
2361            int status = ct.go();
2362            if ((status & ColumnText.NO_MORE_TEXT) != 0) {
2363                text.moveText(0, ct.getYLine() - indentTop() + currentHeight);
2364                currentHeight = indentTop() - ct.getYLine();
2365                break;
2366            }
2367            if (indentTop() - currentHeight == ct.getYLine())
2368                ++loop;
2369            else
2370                loop = 0;
2371            if (loop == 3) {
2372                throw new DocumentException(MessageLocalization.getComposedMessage("infinite.table.loop"));
2373            }
2374            newPage();
2375        }
2376        ptable.setHeadersInEvent(he);
2377    }
2378
2379    /**
2380     * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
2381     *
2382     * @param   table   the table that has to be checked
2383     * @param   margin  a certain margin
2384     * @return  <CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise.
2385     */
2386
2387    boolean fitsPage(final PdfPTable table, final float margin) {
2388        if (!table.isLockedWidth()) {
2389                float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100;
2390                table.setTotalWidth(totalWidth);
2391        }
2392        // ensuring that a new line has been started.
2393        ensureNewLine();
2394        return table.getTotalHeight() + (currentHeight > 0 ? table.spacingBefore() : 0f)
2395                <= indentTop() - currentHeight - indentBottom() - margin;
2396    }
2397
2398    /**
2399     * @since 5.0.1
2400     */
2401    public class Destination {
2402        public PdfAction action;
2403        public PdfIndirectReference reference;
2404        public PdfDestination destination;
2405    }
2406}