1 /*
2  * $Id: PdfDocument.java 3939 2009-05-27 13:09:45Z blowagie $
3  *
4  * Copyright 1999, 2000, 2001, 2002 by Bruno Lowagie.
5  *
6  * The contents of this file are subject to the Mozilla Public License Version 1.1
7  * (the "License"); you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the License.
13  *
14  * The Original Code is 'iText, a free JAVA-PDF library'.
15  *
16  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18  * All Rights Reserved.
19  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21  *
22  * Contributor(s): all the names of the contributors are added in the source code
23  * where applicable.
24  *
25  * Alternatively, the contents of this file may be used under the terms of the
26  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27  * provisions of LGPL are applicable instead of those above.  If you wish to
28  * allow use of your version of this file only under the terms of the LGPL
29  * License and not to allow others to use your version of this file under
30  * the MPL, indicate your decision by deleting the provisions above and
31  * replace them with the notice and other provisions required by the LGPL.
32  * If you do not delete the provisions above, a recipient may use your version
33  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34  *
35  * This library is free software; you can redistribute it and/or modify it
36  * under the terms of the MPL as stated above or under the terms of the GNU
37  * Library General Public License as published by the Free Software Foundation;
38  * either version 2 of the License, or any later version.
39  *
40  * This library is distributed in the hope that it will be useful, but WITHOUT
41  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43  * details.
44  *
45  * If you didn't download this code from the following link, you should check if
46  * you aren't using an obsolete version:
47  * http://www.lowagie.com/iText/
48  */

49
50 package com.lowagie.text.pdf;
51
52 import java.awt.Color;
53 import java.io.IOException;
54 import java.util.ArrayList;
55 import java.util.HashMap;
56 import java.util.HashSet;
57 import java.util.Iterator;
58 import java.util.Map;
59 import java.util.Set;
60 import java.util.TreeMap;
61
62 import com.lowagie.text.Anchor;
63 import com.lowagie.text.Annotation;
64 import com.lowagie.text.BadElementException;
65 import com.lowagie.text.Chunk;
66 import com.lowagie.text.Document;
67 import com.lowagie.text.DocumentException;
68 import com.lowagie.text.Element;
69 import com.lowagie.text.ExceptionConverter;
70 import com.lowagie.text.Font;
71 import com.lowagie.text.HeaderFooter;
72 import com.lowagie.text.Image;
73 import com.lowagie.text.List;
74 import com.lowagie.text.ListItem;
75 import com.lowagie.text.MarkedObject;
76 import com.lowagie.text.MarkedSection;
77 import com.lowagie.text.Meta;
78 import com.lowagie.text.Paragraph;
79 import com.lowagie.text.Phrase;
80 import com.lowagie.text.Rectangle;
81 import com.lowagie.text.Section;
82 import com.lowagie.text.SimpleTable;
83 import com.lowagie.text.Table;
84 import com.lowagie.text.pdf.collection.PdfCollection;
85 import com.lowagie.text.pdf.draw.DrawInterface;
86 import com.lowagie.text.pdf.internal.PdfAnnotationsImp;
87 import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
88 import java.text.DecimalFormat;
89
90 /**
91  * <CODE>PdfDocument</CODE> is the class that is used by <CODE>PdfWriter</CODE>
92  * to translate a <CODE>Document</CODE> into a PDF with different pages.
93  * <P>
94  * A <CODE>PdfDocument</CODE> always listens to a <CODE>Document</CODE>
95  * and adds the Pdf representation of every <CODE>Element</CODE> that is
96  * added to the <CODE>Document</CODE>.
97  *
98  * @see        com.lowagie.text.Document
99  * @see        com.lowagie.text.DocListener
100  * @see        PdfWriter
101  * @since    2.0.8 (class was package-private before)
102  */

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

116
117     public static class PdfInfo extends PdfDictionary {
118
119         /**
120          * Construct a <CODE>PdfInfo</CODE>-object.
121          */

122
123         PdfInfo() {
124             super();
125             addProducer();
126             addCreationDate();
127         }
128
129         /**
130          * Constructs a <CODE>PdfInfo</CODE>-object.
131          *
132          * @param        author        name of the author of the document
133          * @param        title        title of the document
134          * @param        subject        subject of the document
135          */

136
137         PdfInfo(String author, String title, String subject) {
138             this();
139             addTitle(title);
140             addSubject(subject);
141             addAuthor(author);
142         }
143
144         /**
145          * Adds the title of the document.
146          *
147          * @param    title        the title of the document
148          */

149
150         void addTitle(String title) {
151             put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE));
152         }
153
154         /**
155          * Adds the subject to the document.
156          *
157          * @param    subject        the subject of the document
158          */

159
160         void addSubject(String subject) {
161             put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE));
162         }
163
164         /**
165          * Adds some keywords to the document.
166          *
167          * @param    keywords        the keywords of the document
168          */

169
170         void addKeywords(String keywords) {
171             put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE));
172         }
173
174         /**
175          * Adds the name of the author to the document.
176          *
177          * @param    author        the name of the author
178          */

179
180         void addAuthor(String author) {
181             put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE));
182         }
183
184         /**
185          * Adds the name of the creator to the document.
186          *
187          * @param    creator        the name of the creator
188          */

189
190         void addCreator(String creator) {
191             put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE));
192         }
193
194         /**
195          * Adds the name of the producer to the document.
196          */

197
198         void addProducer() {
199             put(PdfName.PRODUCER, new PdfString(getVersion()));
200         }
201
202         /**
203          * Adds the date of creation to the document.
204          */

205
206         void addCreationDate() {
207             PdfString date = new PdfDate();
208             put(PdfName.CREATIONDATE, date);
209             put(PdfName.MODDATE, date);
210         }
211
212         void addkey(String key, String value) {
213             if (key.equals("Producer") || key.equals("CreationDate"))
214                 return;
215             put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE));
216         }
217     }
218
219     /**
220      * <CODE>PdfCatalog</CODE> is the PDF Catalog-object.
221      * <P>
222      * The Catalog is a dictionary that is the root node of the document. It contains a reference
223      * to the tree of pages contained in the document, a reference to the tree of objects representing
224      * the document's outline, a reference to the document's article threads, and the list of named
225      * destinations. In addition, the Catalog indicates whether the document's outline or thumbnail
226      * page images should be displayed automatically when the document is viewed and whether some location
227      * other than the first page should be shown when the document is opened.<BR>
228      * In this class however, only the reference to the tree of pages is implemented.<BR>
229      * This object is described in the 'Portable Document Format Reference Manual version 1.3'
230      * section 6.2 (page 67-71)
231      */

232
233     static class PdfCatalog extends PdfDictionary {
234
235         /** The writer writing the PDF for which we are creating this catalog object. */
236         PdfWriter writer;
237
238         /**
239          * Constructs a <CODE>PdfCatalog</CODE>.
240          *
241          * @param        pages        an indirect reference to the root of the document's Pages tree.
242          * @param writer the writer the catalog applies to
243          */

244
245         PdfCatalog(PdfIndirectReference pages, PdfWriter writer) {
246             super(CATALOG);
247             this.writer = writer;
248             put(PdfName.PAGES, pages);
249         }
250
251         /**
252          * Adds the names of the named destinations to the catalog.
253          * @param localDestinations the local destinations
254          * @param documentLevelJS the javascript used in the document
255          * @param documentFileAttachment    the attached files
256          * @param writer the writer the catalog applies to
257          */

258         void addNames(TreeMap localDestinations, HashMap documentLevelJS, HashMap documentFileAttachment, PdfWriter writer) {
259             if (localDestinations.isEmpty() && documentLevelJS.isEmpty() && documentFileAttachment.isEmpty())
260                 return;
261             try {
262                 PdfDictionary names = new PdfDictionary();
263                 if (!localDestinations.isEmpty()) {
264                     PdfArray ar = new PdfArray();
265                     for (Iterator i = localDestinations.entrySet().iterator(); i.hasNext();) {
266                         Map.Entry entry = (Map.Entry) i.next();
267                         String name = (String) entry.getKey();
268                         Object obj[] = (Object[]) entry.getValue();
269                         if (obj[2] == null//no destination
270                             continue;
271                         PdfIndirectReference ref = (PdfIndirectReference)obj[1];
272                         ar.add(new PdfString(name, null));
273                         ar.add(ref);
274                     }
275                     if (ar.size() > 0) {
276                         PdfDictionary dests = new PdfDictionary();
277                         dests.put(PdfName.NAMES, ar);
278                         names.put(PdfName.DESTS, writer.addToBody(dests).getIndirectReference());
279                     }
280                 }
281                 if (!documentLevelJS.isEmpty()) {
282                     PdfDictionary tree = PdfNameTree.writeTree(documentLevelJS, writer);
283                     names.put(PdfName.JAVASCRIPT, writer.addToBody(tree).getIndirectReference());
284                 }
285                 if (!documentFileAttachment.isEmpty()) {
286                     names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference());
287                 }
288                 if (names.size() > 0)
289                     put(PdfName.NAMES, writer.addToBody(names).getIndirectReference());
290             }
291             catch (IOException e) {
292                 throw new ExceptionConverter(e);
293             }
294         }
295
296         /**
297          * Adds an open action to the catalog.
298          * @param    action    the action that will be triggered upon opening the document
299          */

300         void setOpenAction(PdfAction action) {
301             put(PdfName.OPENACTION, action);
302         }
303
304
305         /**
306          * Sets the document level additional actions.
307          * @param actions   dictionary of actions
308          */

309         void setAdditionalActions(PdfDictionary actions) {
310             try {
311                 put(PdfName.AA, writer.addToBody(actions).getIndirectReference());
312             } catch (Exception e) {
313                 throw new ExceptionConverter(e);
314             }
315         }
316     }
317
318 // CONSTRUCTING A PdfDocument/PdfWriter INSTANCE
319
320     /**
321      * Constructs a new PDF document.
322      */

323     public PdfDocument() {
324         super();
325         addProducer();
326         addCreationDate();
327     }
328
329     /** The <CODE>PdfWriter</CODE>. */
330     protected PdfWriter writer;
331
332     /**
333      * Adds a <CODE>PdfWriter</CODE> to the <CODE>PdfDocument</CODE>.
334      *
335      * @param writer the <CODE>PdfWriter</CODE> that writes everything
336      *                     what is added to this document to an outputstream.
337      * @throws DocumentException on error
338      */

339     public void addWriter(PdfWriter writer) throws DocumentException {
340         if (this.writer == null) {
341             this.writer = writer;
342             annotationsImp = new PdfAnnotationsImp(writer);
343             return;
344         }
345         throw new DocumentException("You can only add a writer to a PdfDocument once.");
346     }
347
348 // LISTENER METHODS START
349
350 //    [L0] ElementListener interface
351
352     /** This is the PdfContentByte object, containing the text. */
353     protected PdfContentByte text;
354
355     /** This is the PdfContentByte object, containing the borders and other Graphics. */
356     protected PdfContentByte graphics;
357
358     /** This represents the leading of the lines. */
359     protected float leading = 0;
360
361     /**
362      * Getter for the current leading.
363      * @return    the current leading
364      * @since    2.1.2
365      */

366     public float getLeading() {
367         return leading;
368     }
369     
370     /**
371      * Setter for the current leading.
372      * @param    leading the current leading
373      * @since    2.1.6
374      */

375     void setLeading(float leading) {
376         this.leading = leading;
377     }
378
379     /** This represents the current alignment of the PDF Elements. */
380     protected int alignment = Element.ALIGN_LEFT;
381
382     /** This is the current height of the document. */
383     protected float currentHeight = 0;
384
385     /**
386      * Signals that onParagraph is valid (to avoid that a Chapter/Section title is treated as a Paragraph).
387      * @since 2.1.2
388      */

389     protected boolean isSectionTitle = false;
390
391     /**
392      * Signals that the current leading has to be subtracted from a YMark object when positive.
393      * @since 2.1.2
394      */

395     protected int leadingCount = 0;
396
397     /** The current active <CODE>PdfAction</CODE> when processing an <CODE>Anchor</CODE>. */
398     protected PdfAction anchorAction = null;
399
400     /**
401      * Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>.
402      *
403      * @param element the element to add
404      * @return <CODE>true</CODE> if the element was added, <CODE>false</CODE> if not.
405      * @throws DocumentException when a document isn't open yet, or has been closed
406      */

407     public boolean add(Element element) throws DocumentException {
408         if (writer != null && writer.isPaused()) {
409             return false;
410         }
411         try {
412             switch(element.type()) {
413                 // Information (headers)
414                 case Element.HEADER:
415                     info.addkey(((Meta)element).getName(), ((Meta)element).getContent());
416                     break;
417                 case Element.TITLE:
418                     info.addTitle(((Meta)element).getContent());
419                     break;
420                 case Element.SUBJECT:
421                     info.addSubject(((Meta)element).getContent());
422                     break;
423                 case Element.KEYWORDS:
424                     info.addKeywords(((Meta)element).getContent());
425                     break;
426                 case Element.AUTHOR:
427                     info.addAuthor(((Meta)element).getContent());
428                     break;
429                 case Element.CREATOR:
430                     info.addCreator(((Meta)element).getContent());
431                     break;
432                 case Element.PRODUCER:
433                     // you can not change the name of the producer
434                     info.addProducer();
435                     break;
436                 case Element.CREATIONDATE:
437                     // you can not set the creation date, only reset it
438                     info.addCreationDate();
439                     break;
440
441                 // content (text)
442                 case Element.CHUNK: {
443                     // if there isn't a current line available, we make one
444                     if (line == null) {
445                         carriageReturn();
446                     }
447
448                     // we cast the element to a chunk
449                     PdfChunk chunk = new PdfChunk((Chunk) element, anchorAction);
450                     // we try to add the chunk to the line, until we succeed
451                     {
452                         PdfChunk overflow;
453                         while ((overflow = line.add(chunk)) != null) {
454                             carriageReturn();
455                             chunk = overflow;
456                             chunk.trimFirstSpace();
457                         }
458                     }
459                     pageEmpty = false;
460                     if (chunk.isAttribute(Chunk.NEWPAGE)) {
461                         newPage();
462                     }
463                     break;
464                 }
465                 case Element.ANCHOR: {
466                     leadingCount++;
467                     Anchor anchor = (Anchor) element;
468                     String url = anchor.getReference();
469                     leading = anchor.getLeading();
470                     if (url != null) {
471                         anchorAction = new PdfAction(url);
472                     }
473                     // we process the element
474                     element.process(this);
475                     anchorAction = null;
476                     leadingCount--;
477                     break;
478                 }
479                 case Element.ANNOTATION: {
480                     if (line == null) {
481                         carriageReturn();
482                     }
483                     Annotation annot = (Annotation) element;
484                     Rectangle rect = new Rectangle(0, 0);
485                     if (line != null)
486                         rect = new Rectangle(annot.llx(indentRight() - line.widthLeft()), annot.lly(indentTop() - currentHeight), annot.urx(indentRight() - line.widthLeft() + 20), annot.ury(indentTop() - currentHeight - 20));
487                     PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, rect);
488                     annotationsImp.addPlainAnnotation(an);
489                     pageEmpty = false;
490                     break;
491                 }
492                 case Element.PHRASE: {
493                     leadingCount++;
494                     // we cast the element to a phrase and set the leading of the document
495                     leading = ((Phrase) element).getLeading();
496                     // we process the element
497                     element.process(this);
498                     leadingCount--;
499                     break;
500                 }
501                 case Element.PARAGRAPH: {
502                     leadingCount++;
503                     // we cast the element to a paragraph
504                     Paragraph paragraph = (Paragraph) element;
505                     addSpacing(paragraph.getSpacingBefore(), leading, paragraph.getFont());
506
507                     // we adjust the parameters of the document
508                     alignment = paragraph.getAlignment();
509                     leading = paragraph.getTotalLeading();
510                     carriageReturn();
511
512                     // we don't want to make orphans/widows
513                     if (currentHeight + line.height() + leading > indentTop() - indentBottom()) {
514                         newPage();
515                     }
516                     indentation.indentLeft += paragraph.getIndentationLeft();
517                     indentation.indentRight += paragraph.getIndentationRight();
518                     carriageReturn();
519
520                     PdfPageEvent pageEvent = writer.getPageEvent();
521                     if (pageEvent != null && !isSectionTitle)
522                         pageEvent.onParagraph(writer, this, indentTop() - currentHeight);
523
524                     // if a paragraph has to be kept together, we wrap it in a table object
525                     if (paragraph.getKeepTogether()) {
526                         carriageReturn();
527                         PdfPTable table = new PdfPTable(1);
528                         table.setWidthPercentage(100f);
529                         PdfPCell cell = new PdfPCell();
530                         cell.addElement(paragraph);
531                         cell.setBorder(Table.NO_BORDER);
532                         cell.setPadding(0);
533                         table.addCell(cell);
534                         indentation.indentLeft -= paragraph.getIndentationLeft();
535                         indentation.indentRight -= paragraph.getIndentationRight();
536                         this.add(table);
537                         indentation.indentLeft += paragraph.getIndentationLeft();
538                         indentation.indentRight += paragraph.getIndentationRight();
539                     }
540                     else {
541                         line.setExtraIndent(paragraph.getFirstLineIndent());
542                         element.process(this);
543                         carriageReturn();
544                         addSpacing(paragraph.getSpacingAfter(), paragraph.getTotalLeading(), paragraph.getFont());
545                     }
546
547                     if (pageEvent != null && !isSectionTitle)
548                         pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight);
549
550                     alignment = Element.ALIGN_LEFT;
551                     indentation.indentLeft -= paragraph.getIndentationLeft();
552                     indentation.indentRight -= paragraph.getIndentationRight();
553                     carriageReturn();
554                     leadingCount--;
555                     break;
556                 }
557                 case Element.SECTION:
558                 case Element.CHAPTER: {
559                     // Chapters and Sections only differ in their constructor
560                     // so we cast both to a Section
561                     Section section = (Section) element;
562                     PdfPageEvent pageEvent = writer.getPageEvent();
563
564                     boolean hasTitle = section.isNotAddedYet()
565                         && section.getTitle() != null;
566
567                     // if the section is a chapter, we begin a new page
568                     if (section.isTriggerNewPage()) {
569                         newPage();
570                     }
571
572                     if (hasTitle) {
573                         float fith = indentTop() - currentHeight;
574                         int rotation = pageSize.getRotation();
575                         if (rotation == 90 || rotation == 180)
576                             fith = pageSize.getHeight() - fith;
577                         PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith);
578                         while (currentOutline.level() >= section.getDepth()) {
579                             currentOutline = currentOutline.parent();
580                         }
581                         PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen());
582                         currentOutline = outline;
583                     }
584
585                     // some values are set
586                     carriageReturn();
587                     indentation.sectionIndentLeft += section.getIndentationLeft();
588                     indentation.sectionIndentRight += section.getIndentationRight();
589
590                     if (section.isNotAddedYet() && pageEvent != null)
591                         if (element.type() == Element.CHAPTER)
592                             pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.getTitle());
593                         else
594                             pageEvent.onSection(writer, this, indentTop() - currentHeight, section.getDepth(), section.getTitle());
595
596                     // the title of the section (if any has to be printed)
597                     if (hasTitle) {
598                         isSectionTitle = true;
599                         add(section.getTitle());
600                         isSectionTitle = false;
601                     }
602                     indentation.sectionIndentLeft += section.getIndentation();
603                     // we process the section
604                     element.process(this);
605                     flushLines();
606                     // some parameters are set back to normal again
607                     indentation.sectionIndentLeft -= (section.getIndentationLeft() + section.getIndentation());
608                     indentation.sectionIndentRight -= section.getIndentationRight();
609
610                     if (section.isComplete() && pageEvent != null)
611                         if (element.type() == Element.CHAPTER)
612                             pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight);
613                         else
614                             pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight);
615
616                     break;
617                 }
618                 case Element.LIST: {
619                     // we cast the element to a List
620                     List list = (List) element;
621                     if (list.isAlignindent()) {
622                         list.normalizeIndentation();
623                     }
624                     // we adjust the document
625                     indentation.listIndentLeft += list.getIndentationLeft();
626                     indentation.indentRight += list.getIndentationRight();
627                     // we process the items in the list
628                     element.process(this);
629                     // some parameters are set back to normal again
630                     indentation.listIndentLeft -= list.getIndentationLeft();
631                     indentation.indentRight -= list.getIndentationRight();
632                     carriageReturn();
633                     break;
634                 }
635                 case Element.LISTITEM: {
636                     leadingCount++;
637                     // we cast the element to a ListItem
638                     ListItem listItem = (ListItem) element;
639
640                     addSpacing(listItem.getSpacingBefore(), leading, listItem.getFont());
641
642                     // we adjust the document
643                     alignment = listItem.getAlignment();
644                     indentation.listIndentLeft += listItem.getIndentationLeft();
645                     indentation.indentRight += listItem.getIndentationRight();
646                     leading = listItem.getTotalLeading();
647                     carriageReturn();
648
649                     // we prepare the current line to be able to show us the listsymbol
650                     line.setListItem(listItem);
651                     // we process the item
652                     element.process(this);
653
654                     addSpacing(listItem.getSpacingAfter(), listItem.getTotalLeading(), listItem.getFont());
655
656                     // if the last line is justified, it should be aligned to the left
657                     if (line.hasToBeJustified()) {
658                         line.resetAlignment();
659                     }
660                     // some parameters are set back to normal again
661                     carriageReturn();
662                     indentation.listIndentLeft -= listItem.getIndentationLeft();
663                     indentation.indentRight -= listItem.getIndentationRight();
664                     leadingCount--;
665                     break;
666                 }
667                 case Element.RECTANGLE: {
668                     Rectangle rectangle = (Rectangle) element;
669                     graphics.rectangle(rectangle);
670                     pageEmpty = false;
671                     break;
672                 }
673                 case Element.PTABLE: {
674                     PdfPTable ptable = (PdfPTable)element;
675                     if (ptable.size() <= ptable.getHeaderRows())
676                         break//nothing to do
677
678                     // before every table, we add a new line and flush all lines
679                     ensureNewLine();
680                     flushLines();
681
682                     addPTable(ptable);
683                     pageEmpty = false;
684                     newLine();
685                     break;
686                 }
687                 case Element.MULTI_COLUMN_TEXT: {
688                     ensureNewLine();
689                     flushLines();
690                     MultiColumnText multiText = (MultiColumnText) element;
691                     float height = multiText.write(writer.getDirectContent(), this, indentTop() - currentHeight);
692                     currentHeight += height;
693                     text.moveText(0, -1f* height);
694                     pageEmpty = false;
695                     break;
696                 }
697                 case Element.TABLE : {
698                     if (element instanceof SimpleTable) {
699                         PdfPTable ptable = ((SimpleTable)element).createPdfPTable();
700                         if (ptable.size() <= ptable.getHeaderRows())
701                             break//nothing to do
702
703                         // before every table, we add a new line and flush all lines
704                         ensureNewLine();
705                         flushLines();
706                         addPTable(ptable);
707                         pageEmpty = false;
708                         break;
709                     } else if (element instanceof Table) {
710                         try {
711                                PdfPTable ptable = ((Table)element).createPdfPTable();
712                                if (ptable.size() <= ptable.getHeaderRows())
713                                    break//nothing to do
714                                // before every table, we add a new line and flush all lines
715                                ensureNewLine();
716                                flushLines();
717                                addPTable(ptable);
718                                pageEmpty = false;
719                                break;
720                         }
721                         catch(BadElementException bee) {
722                             // constructing the PdfTable
723                             // Before the table, add a blank line using offset or default leading
724                             float offset = ((Table)element).getOffset();
725                             if (Float.isNaN(offset))
726                                 offset = leading;
727                             carriageReturn();
728                             lines.add(new PdfLine(indentLeft(), indentRight(), alignment, offset));
729                             currentHeight += offset;
730                             addPdfTable((Table)element);
731                         }
732                     } else {
733                         return false;
734                     }
735                     break;
736                 }
737                 case Element.JPEG:
738                 case Element.JPEG2000:
739                 case Element.JBIG2:
740                 case Element.IMGRAW:
741                 case Element.IMGTEMPLATE: {
742                     //carriageReturn(); suggestion by Marc Campforts
743                     add((Image) element);
744                     break;
745                 }
746                 case Element.YMARK: {
747                     DrawInterface zh = (DrawInterface)element;
748                     zh.draw(graphics, indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight - (leadingCount > 0 ? leading : 0));
749                     pageEmpty = false;
750                     break;
751                 }
752                 case Element.MARKED: {
753                     MarkedObject mo;
754                     if (element instanceof MarkedSection) {
755                         mo = ((MarkedSection)element).getTitle();
756                         if (mo != null) {
757                             mo.process(this);
758                         }
759                     }
760                     mo = (MarkedObject)element;
761                     mo.process(this);
762                     break;
763                 }
764                 default:
765                     return false;
766             }
767             lastElementType = element.type();
768             return true;
769         }
770         catch(Exception e) {
771             throw new DocumentException(e);
772         }
773     }
774
775 //    [L1] DocListener interface
776
777     /**
778      * Opens the document.
779      * <P>
780      * You have to open the document before you can begin to add content
781      * to the body of the document.
782      */

783     public void open() {
784         if (!open) {
785             super.open();
786             writer.open();
787             rootOutline = new PdfOutline(writer);
788             currentOutline = rootOutline;
789         }
790         try {
791             initPage();
792         }
793         catch(DocumentException de) {
794             throw new ExceptionConverter(de);
795         }
796     }
797
798 //    [L2] DocListener interface
799
800     /**
801      * Closes the document.
802      * <B>
803      * Once all the content has been written in the body, you have to close
804      * the body. After that nothing can be written to the body anymore.
805      */

806     public void close() {
807         if (close) {
808             return;
809         }
810         try {
811             boolean wasImage = (imageWait != null);
812             newPage();
813             if (imageWait != null || wasImage) newPage();
814             if (annotationsImp.hasUnusedAnnotations())
815                 throw new RuntimeException("Not all annotations could be added to the document (the document doesn't have enough pages).");
816             PdfPageEvent pageEvent = writer.getPageEvent();
817             if (pageEvent != null)
818                 pageEvent.onCloseDocument(writer, this);
819             super.close();
820
821             writer.addLocalDestinations(localDestinations);
822             calculateOutlineCount();
823             writeOutlines();
824         }
825         catch(Exception e) {
826             throw ExceptionConverter.convertException(e);
827         }
828
829         writer.close();
830     }
831
832 //    [L3] DocListener interface
833     protected int textEmptySize;
834
835     // [C9] Metadata for the page
836     /** XMP Metadata for the page. */
837     protected byte[] xmpMetadata = null;
838     /**
839      * Use this method to set the XMP Metadata.
840      * @param xmpMetadata The xmpMetadata to set.
841      */

842     public void setXmpMetadata(byte[] xmpMetadata) {
843         this.xmpMetadata = xmpMetadata;
844     }
845
846     /**
847      * Makes a new page and sends it to the <CODE>PdfWriter</CODE>.
848      *
849      * @return a <CODE>boolean</CODE>
850      */

851     public boolean newPage() {
852         lastElementType = -1;
853         if (writer == null || (writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || writer.isPaused()))) {
854             setNewPageSizeAndMargins();
855             return false;
856         }
857         if (!open || close) {
858             throw new RuntimeException("The document isn't open.");
859         }
860         PdfPageEvent pageEvent = writer.getPageEvent();
861         if (pageEvent != null)
862             pageEvent.onEndPage(writer, this);
863
864         //Added to inform any listeners that we are moving to a new page (added by David Freels)
865         super.newPage();
866
867         // the following 2 lines were added by Pelikan Stephan
868         indentation.imageIndentLeft = 0;
869         indentation.imageIndentRight = 0;
870
871         try {
872             // we flush the arraylist with recently written lines
873             flushLines();
874
875             // we prepare the elements of the page dictionary
876
877             // [U1] page size and rotation
878             int rotation = pageSize.getRotation();
879
880             // [C10]
881             if (writer.isPdfX()) {
882                 if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim"))
883                     throw new PdfXConformanceException("Only one of ArtBox or TrimBox can exist in the page.");
884                 if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) {
885                     if (thisBoxSize.containsKey("crop"))
886                         thisBoxSize.put("trim", thisBoxSize.get("crop"));
887                     else
888                         thisBoxSize.put("trim"new PdfRectangle(pageSize, pageSize.getRotation()));
889                 }
890             }
891
892             // [M1]
893             pageResources.addDefaultColorDiff(writer.getDefaultColorspace());
894             if (writer.isRgbTransparencyBlending()) {
895                 PdfDictionary dcs = new PdfDictionary();
896                 dcs.put(PdfName.CS, PdfName.DEVICERGB);
897                 pageResources.addDefaultColorDiff(dcs);
898             }
899             PdfDictionary resources = pageResources.getResources();
900
901             // we create the page dictionary
902
903             PdfPage page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation);
904             page.put(PdfName.TABS, writer.getTabs());
905
906             // we complete the page dictionary
907
908             // [C9] if there is XMP data to add: add it
909             if (xmpMetadata != null) {
910                 PdfStream xmp = new PdfStream(xmpMetadata);
911                 xmp.put(PdfName.TYPE, PdfName.METADATA);
912                 xmp.put(PdfName.SUBTYPE, PdfName.XML);
913                 PdfEncryption crypto = writer.getEncryption();
914                 if (crypto != null && !crypto.isMetadataEncrypted()) {
915                     PdfArray ar = new PdfArray();
916                     ar.add(PdfName.CRYPT);
917                     xmp.put(PdfName.FILTER, ar);
918                 }
919                 page.put(PdfName.METADATA, writer.addToBody(xmp).getIndirectReference());
920             }
921
922             // [U3] page actions: transition, duration, additional actions
923             if (this.transition!=null) {
924                 page.put(PdfName.TRANS, this.transition.getTransitionDictionary());
925                 transition = null;
926             }
927             if (this.duration>0) {
928                 page.put(PdfName.DUR,new PdfNumber(this.duration));
929                 duration = 0;
930             }
931             if (pageAA != null) {
932                 page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference());
933                 pageAA = null;
934             }
935
936             // [U4] we add the thumbs
937             if (thumb != null) {
938                 page.put(PdfName.THUMB, thumb);
939                 thumb = null;
940             }
941
942             // [U8] we check if the userunit is defined
943             if (writer.getUserunit() > 0f) {
944                 page.put(PdfName.USERUNIT, new PdfNumber(writer.getUserunit()));
945             }
946
947             // [C5] and [C8] we add the annotations
948             if (annotationsImp.hasUnusedAnnotations()) {
949                 PdfArray array = annotationsImp.rotateAnnotations(writer, pageSize);
950                 if (array.size() != 0)
951                     page.put(PdfName.ANNOTS, array);
952             }
953
954             // [F12] we add tag info
955             if (writer.isTagged())
956                 page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1));
957
958             if (text.size() > textEmptySize)
959                 text.endText();
960             else
961                 text = null;
962             writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, text, writer.getDirectContent(), pageSize));
963             // we initialize the new page
964             initPage();
965         }
966         catch(DocumentException de) {
967             // maybe this never happens, but it's better to check.
968             throw new ExceptionConverter(de);
969         }
970         catch (IOException ioe) {
971             throw new ExceptionConverter(ioe);
972         }
973         return true;
974     }
975
976 //    [L4] DocListener interface
977
978     /**
979      * Sets the pagesize.
980      *
981      * @param pageSize the new pagesize
982      * @return <CODE>true</CODE> if the page size was set
983      */

984     public boolean setPageSize(Rectangle pageSize) {
985         if (writer != null && writer.isPaused()) {
986             return false;
987         }
988         nextPageSize = new Rectangle(pageSize);
989         return true;
990     }
991
992 //    [L5] DocListener interface
993
994     /** margin in x direction starting from the left. Will be valid in the next page */
995     protected float nextMarginLeft;
996
997     /** margin in x direction starting from the right. Will be valid in the next page */
998     protected float nextMarginRight;
999
1000     /** margin in y direction starting from the top. Will be valid in the next page */
1001     protected float nextMarginTop;
1002
1003     /** margin in y direction starting from the bottom. Will be valid in the next page */
1004     protected float nextMarginBottom;
1005
1006     /**
1007      * Sets the margins.
1008      *
1009      * @param    marginLeft        the margin on the left
1010      * @param    marginRight        the margin on the right
1011      * @param    marginTop        the margin on the top
1012      * @param    marginBottom    the margin on the bottom
1013      * @return    a <CODE>boolean</CODE>
1014      */

1015     public boolean setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom) {
1016         if (writer != null && writer.isPaused()) {
1017             return false;
1018         }
1019         nextMarginLeft = marginLeft;
1020         nextMarginRight = marginRight;
1021         nextMarginTop = marginTop;
1022         nextMarginBottom = marginBottom;
1023         return true;
1024     }
1025
1026 //    [L6] DocListener interface
1027
1028     /**
1029      * @see com.lowagie.text.DocListener#setMarginMirroring(boolean)
1030      */

1031     public boolean setMarginMirroring(boolean MarginMirroring) {
1032         if (writer != null && writer.isPaused()) {
1033             return false;
1034         }
1035         return super.setMarginMirroring(MarginMirroring);
1036     }
1037     
1038     /**
1039      * @see com.lowagie.text.DocListener#setMarginMirroring(boolean)
1040      * @since    2.1.6
1041      */

1042     public boolean setMarginMirroringTopBottom(boolean MarginMirroringTopBottom) {
1043         if (writer != null && writer.isPaused()) {
1044             return false;
1045         }
1046         return super.setMarginMirroringTopBottom(MarginMirroringTopBottom);
1047     }
1048
1049 //    [L7] DocListener interface
1050
1051     /**
1052      * Sets the page number.
1053      *
1054      * @param    pageN        the new page number
1055      */

1056     public void setPageCount(int pageN) {
1057         if (writer != null && writer.isPaused()) {
1058             return;
1059         }
1060         super.setPageCount(pageN);
1061     }
1062
1063 //    [L8] DocListener interface
1064
1065     /**
1066      * Sets the page number to 0.
1067      */

1068     public void resetPageCount() {
1069         if (writer != null && writer.isPaused()) {
1070             return;
1071         }
1072         super.resetPageCount();
1073     }
1074
1075 //    [L9] DocListener interface
1076
1077     /**
1078      * Changes the header of this document.
1079      *
1080      * @param header the new header
1081      */

1082     public void setHeader(HeaderFooter header) {
1083         if (writer != null && writer.isPaused()) {
1084             return;
1085         }
1086         super.setHeader(header);
1087     }
1088
1089 //    [L10] DocListener interface
1090
1091     /**
1092      * Resets the header of this document.
1093      */

1094     public void resetHeader() {
1095         if (writer != null && writer.isPaused()) {
1096             return;
1097         }
1098         super.resetHeader();
1099     }
1100
1101 //    [L11] DocListener interface
1102
1103     /**
1104      * Changes the footer of this document.
1105      *
1106      * @param    footer        the new footer
1107      */

1108     public void setFooter(HeaderFooter footer) {
1109         if (writer != null && writer.isPaused()) {
1110             return;
1111         }
1112         super.setFooter(footer);
1113     }
1114
1115 //    [L12] DocListener interface
1116
1117     /**
1118      * Resets the footer of this document.
1119      */

1120     public void resetFooter() {
1121         if (writer != null && writer.isPaused()) {
1122             return;
1123         }
1124         super.resetFooter();
1125     }
1126
1127 // DOCLISTENER METHODS END
1128
1129     /** Signals that OnOpenDocument should be called. */
1130     protected boolean firstPageEvent = true;
1131
1132     /**
1133      * Initializes a page.
1134      * <P>
1135      * If the footer/header is set, it is printed.
1136      * @throws DocumentException on error
1137      */

1138     protected void initPage() throws DocumentException {
1139         // the pagenumber is incremented
1140         pageN++;
1141
1142         // initialization of some page objects
1143         annotationsImp.resetAnnotations();
1144         pageResources = new PageResources();
1145
1146         writer.resetContent();
1147         graphics = new PdfContentByte(writer);
1148         text = new PdfContentByte(writer);
1149         text.reset();
1150         text.beginText();
1151         textEmptySize = text.size();
1152
1153         markPoint = 0;
1154         setNewPageSizeAndMargins();
1155         imageEnd = -1;
1156         indentation.imageIndentRight = 0;
1157         indentation.imageIndentLeft = 0;
1158         indentation.indentBottom = 0;
1159         indentation.indentTop = 0;
1160         currentHeight = 0;
1161
1162         // backgroundcolors, etc...
1163         thisBoxSize = new HashMap(boxSize);
1164         if (pageSize.getBackgroundColor() != null
1165         || pageSize.hasBorders()
1166         || pageSize.getBorderColor() != null) {
1167             add(pageSize);
1168         }
1169
1170         float oldleading = leading;
1171         int oldAlignment = alignment;
1172         // if there is a footer, the footer is added
1173         doFooter();
1174         // we move to the left/top position of the page
1175         text.moveText(left(), top());
1176         doHeader();
1177         pageEmpty = true;
1178         // if there is an image waiting to be drawn, draw it
1179         try {
1180             if (imageWait != null) {
1181                 add(imageWait);
1182                 imageWait = null;
1183             }
1184         }
1185         catch(Exception e) {
1186             throw new ExceptionConverter(e);
1187         }
1188         leading = oldleading;
1189         alignment = oldAlignment;
1190         carriageReturn();
1191
1192         PdfPageEvent pageEvent = writer.getPageEvent();
1193         if (pageEvent != null) {
1194             if (firstPageEvent) {
1195                 pageEvent.onOpenDocument(writer, this);
1196             }
1197             pageEvent.onStartPage(writer, this);
1198         }
1199         firstPageEvent = false;
1200     }
1201
1202     /** The line that is currently being written. */
1203     protected PdfLine line = null;
1204     /** The lines that are written until now. */
1205     protected ArrayList lines = new ArrayList();
1206
1207     /**
1208      * Adds the current line to the list of lines and also adds an empty line.
1209      * @throws DocumentException on error
1210      */

1211     protected void newLine() throws DocumentException {
1212         lastElementType = -1;
1213         carriageReturn();
1214         if (lines != null && !lines.isEmpty()) {
1215             lines.add(line);
1216             currentHeight += line.height();
1217         }
1218         line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1219     }
1220
1221     /**
1222      * If the current line is not empty or null, it is added to the arraylist
1223      * of lines and a new empty line is added.
1224      */

1225     protected void carriageReturn() {
1226         // the arraylist with lines may not be null
1227         if (lines == null) {
1228             lines = new ArrayList();
1229         }
1230         // If the current line is not null
1231         if (line != null) {
1232             // we check if the end of the page is reached (bugfix by Francois Gravel)
1233             if (currentHeight + line.height() + leading < indentTop() - indentBottom()) {
1234                 // if so nonempty lines are added and the height is augmented
1235                 if (line.size() > 0) {
1236                     currentHeight += line.height();
1237                     lines.add(line);
1238                     pageEmpty = false;
1239                 }
1240             }
1241             // if the end of the line is reached, we start a new page
1242             else {
1243                 newPage();
1244             }
1245         }
1246         if (imageEnd > -1 && currentHeight > imageEnd) {
1247             imageEnd = -1;
1248             indentation.imageIndentRight = 0;
1249             indentation.imageIndentLeft = 0;
1250         }
1251         // a new current line is constructed
1252         line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1253     }
1254
1255     /**
1256      * Gets the current vertical page position.
1257      * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
1258      *   for elements that do not terminate the lines they've started because those lines will get
1259      *   terminated.
1260      * @return The current vertical page position.
1261      */

1262     public float getVerticalPosition(boolean ensureNewLine) {
1263         // ensuring that a new line has been started.
1264         if (ensureNewLine) {
1265           ensureNewLine();
1266         }
1267         return top() -  currentHeight - indentation.indentTop;
1268     }
1269
1270     /** Holds the type of the last element, that has been added to the document. */
1271     protected int lastElementType = -1;
1272
1273     /**
1274      * Ensures that a new line has been started.
1275      */

1276     protected void ensureNewLine() {
1277       try {
1278         if ((lastElementType == Element.PHRASE) ||
1279             (lastElementType == Element.CHUNK)) {
1280           newLine();
1281           flushLines();
1282         }
1283       } catch (DocumentException ex) {
1284         throw new ExceptionConverter(ex);
1285         }
1286     }
1287
1288     /**
1289      * Writes all the lines to the text-object.
1290      *
1291      * @return the displacement that was caused
1292      * @throws DocumentException on error
1293      */

1294     protected float flushLines() throws DocumentException {
1295         // checks if the ArrayList with the lines is not null
1296         if (lines == null) {
1297             return 0;
1298         }
1299         // checks if a new Line has to be made.
1300         if (line != null && line.size() > 0) {
1301             lines.add(line);
1302             line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1303         }
1304
1305         // checks if the ArrayList with the lines is empty
1306         if (lines.isEmpty()) {
1307             return 0;
1308         }
1309
1310         // initialization of some parameters
1311         Object currentValues[] = new Object[2];
1312         PdfFont currentFont = null;
1313         float displacement = 0;
1314         PdfLine l;
1315         Float lastBaseFactor = new Float(0);
1316         currentValues[1] = lastBaseFactor;
1317         // looping over all the lines
1318         for (Iterator i = lines.iterator(); i.hasNext(); ) {
1319
1320             // this is a line in the loop
1321             l = (PdfLine) i.next();
1322
1323             float moveTextX = l.indentLeft() - indentLeft() + indentation.indentLeft + indentation.listIndentLeft + indentation.sectionIndentLeft;
1324             text.moveText(moveTextX, -l.height());
1325             // is the line preceded by a symbol?
1326             if (l.listSymbol() != null) {
1327                 ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(l.listSymbol()), text.getXTLM() - l.listIndent(), text.getYTLM(), 0);
1328             }
1329
1330             currentValues[0] = currentFont;
1331
1332             writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio());
1333
1334             currentFont = (PdfFont)currentValues[0];
1335             displacement += l.height();
1336             text.moveText(-moveTextX, 0);
1337
1338         }
1339         lines = new ArrayList();
1340         return displacement;
1341     }
1342
1343     /** The characters to be applied the hanging punctuation. */
1344     static final String hangingPunctuation = ".,;:'";
1345
1346     /**
1347      * Writes a text line to the document. It takes care of all the attributes.
1348      * <P>
1349      * Before entering the line position must have been established and the
1350      * <CODE>text</CODE> argument must be in text object scope (<CODE>beginText()</CODE>).
1351      * @param line the line to be written
1352      * @param text the <CODE>PdfContentByte</CODE> where the text will be written to
1353      * @param graphics the <CODE>PdfContentByte</CODE> where the graphics will be written to
1354      * @param currentValues the current font and extra spacing values
1355      * @param ratio
1356      * @throws DocumentException on error
1357      */

1358     void writeLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object currentValues[], float ratio)  throws DocumentException {
1359         PdfFont currentFont = (PdfFont)(currentValues[0]);
1360         float lastBaseFactor = ((Float)(currentValues[1])).floatValue();
1361         PdfChunk chunk;
1362         int numberOfSpaces;
1363         int lineLen;
1364         boolean isJustified;
1365         float hangingCorrection = 0;
1366         float hScale = 1;
1367         float lastHScale = Float.NaN;
1368         float baseWordSpacing = 0;
1369         float baseCharacterSpacing = 0;
1370         float glueWidth = 0;
1371
1372         numberOfSpaces = line.numberOfSpaces();
1373         lineLen = line.GetLineLengthUtf32();
1374         // does the line need to be justified?
1375         isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1);
1376         int separatorCount = line.getSeparatorCount();
1377         if (separatorCount > 0) {
1378             glueWidth = line.widthLeft() / separatorCount;
1379         }
1380         else if (isJustified) {
1381             if (line.isNewlineSplit() && line.widthLeft() >= (lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1))) {
1382                 if (line.isRTL()) {
1383                     text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0);
1384                 }
1385                 baseWordSpacing = ratio * lastBaseFactor;
1386                 baseCharacterSpacing = lastBaseFactor;
1387             }
1388             else {
1389                 float width = line.widthLeft();
1390                 PdfChunk last = line.getChunk(line.size() - 1);
1391                 if (last != null) {
1392                     String s = last.toString();
1393                     char c;
1394                     if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) {
1395                         float oldWidth = width;
1396                         width += last.font().width(c) * 0.4f;
1397                         hangingCorrection = width - oldWidth;
1398                     }
1399                 }
1400                 float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1);
1401                 baseWordSpacing = ratio * baseFactor;
1402                 baseCharacterSpacing = baseFactor;
1403                 lastBaseFactor = baseFactor;
1404             }
1405         }
1406
1407         int lastChunkStroke = line.getLastStrokeChunk();
1408         int chunkStrokeIdx = 0;
1409         float xMarker = text.getXTLM();
1410         float baseXMarker = xMarker;
1411         float yMarker = text.getYTLM();
1412         boolean adjustMatrix = false;
1413         float tabPosition = 0;
1414
1415         // looping over all the chunks in 1 line
1416         for (Iterator j = line.iterator(); j.hasNext(); ) {
1417             chunk = (PdfChunk) j.next();
1418             Color color = chunk.color();
1419             hScale = 1;
1420
1421             if (chunkStrokeIdx <= lastChunkStroke) {
1422                 float width;
1423                 if (isJustified) {
1424                     width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing);
1425                 }
1426                 else {
1427                     width = chunk.width();
1428                 }
1429                 if (chunk.isStroked()) {
1430                     PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1);
1431                     if (chunk.isSeparator()) {
1432                         width = glueWidth;
1433                         Object[] sep = (Object[])chunk.getAttribute(Chunk.SEPARATOR);
1434                         DrawInterface di = (DrawInterface)sep[0];
1435                         Boolean vertical = (Boolean)sep[1];
1436                         float fontSize = chunk.font().size();
1437                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1438                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1439                         if (vertical.booleanValue()) {
1440                             di.draw(graphics, baseXMarker, yMarker + descender, baseXMarker + line.getOriginalWidth(), ascender - descender, yMarker);
1441                         }
1442                         else {
1443                             di.draw(graphics, xMarker, yMarker + descender, xMarker + width, ascender - descender, yMarker);
1444                         }
1445                     }
1446                     if (chunk.isTab()) {
1447                         Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB);
1448                         DrawInterface di = (DrawInterface)tab[0];
1449                         tabPosition = ((Float)tab[1]).floatValue() + ((Float)tab[3]).floatValue();
1450                         float fontSize = chunk.font().size();
1451                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1452                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1453                         if (tabPosition > xMarker) {
1454                             di.draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker);
1455                         }
1456                         float tmp = xMarker;
1457                         xMarker = tabPosition;
1458                         tabPosition = tmp;
1459                     }
1460                     if (chunk.isAttribute(Chunk.BACKGROUND)) {
1461                         float subtract = lastBaseFactor;
1462                         if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND))
1463                             subtract = 0;
1464                         if (nextChunk == null)
1465                             subtract += hangingCorrection;
1466                         float fontSize = chunk.font().size();
1467                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1468                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1469                         Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND);
1470                         graphics.setColorFill((Color)bgr[0]);
1471                         float extra[] = (float[])bgr[1];
1472                         graphics.rectangle(xMarker - extra[0],
1473                             yMarker + descender - extra[1] + chunk.getTextRise(),
1474                             width - subtract + extra[0] + extra[2],
1475                             ascender - descender + extra[1] + extra[3]);
1476                         graphics.fill();
1477                         graphics.setGrayFill(0);
1478                     }
1479                     if (chunk.isAttribute(Chunk.UNDERLINE)) {
1480                         float subtract = lastBaseFactor;
1481                         if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE))
1482                             subtract = 0;
1483                         if (nextChunk == null)
1484                             subtract += hangingCorrection;
1485                         Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE);
1486                         Color scolor = null;
1487                         for (int k = 0; k < unders.length; ++k) {
1488                             Object obj[] = unders[k];
1489                             scolor = (Color)obj[0];
1490                             float ps[] = (float[])obj[1];
1491                             if (scolor == null)
1492                                 scolor = color;
1493                             if (scolor != null)
1494                                 graphics.setColorStroke(scolor);
1495                             float fsize = chunk.font().size();
1496                             graphics.setLineWidth(ps[0] + fsize * ps[1]);
1497                             float shift = ps[2] + fsize * ps[3];
1498                             int cap2 = (int)ps[4];
1499                             if (cap2 != 0)
1500                                 graphics.setLineCap(cap2);
1501                             graphics.moveTo(xMarker, yMarker + shift);
1502                             graphics.lineTo(xMarker + width - subtract, yMarker + shift);
1503                             graphics.stroke();
1504                             if (scolor != null)
1505                                 graphics.resetGrayStroke();
1506                             if (cap2 != 0)
1507                                 graphics.setLineCap(0);
1508                         }
1509                         graphics.setLineWidth(1);
1510                     }
1511                     if (chunk.isAttribute(Chunk.ACTION)) {
1512                         float subtract = lastBaseFactor;
1513                         if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION))
1514                             subtract = 0;
1515                         if (nextChunk == null)
1516                             subtract += hangingCorrection;
1517                         text.addAnnotation(new PdfAnnotation(writer, xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size(), (PdfAction)chunk.getAttribute(Chunk.ACTION)));
1518                     }
1519                     if (chunk.isAttribute(Chunk.REMOTEGOTO)) {
1520                         float subtract = lastBaseFactor;
1521                         if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO))
1522                             subtract = 0;
1523                         if (nextChunk == null)
1524                             subtract += hangingCorrection;
1525                         Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO);
1526                         String filename = (String)obj[0];
1527                         if (obj[1] instanceof String)
1528                             remoteGoto(filename, (String)obj[1], xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1529                         else
1530                             remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1531                     }
1532                     if (chunk.isAttribute(Chunk.LOCALGOTO)) {
1533                         float subtract = lastBaseFactor;
1534                         if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO))
1535                             subtract = 0;
1536                         if (nextChunk == null)
1537                             subtract += hangingCorrection;
1538                         localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1539                     }
1540                     if (chunk.isAttribute(Chunk.LOCALDESTINATION)) {
1541                         float subtract = lastBaseFactor;
1542                         if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION))
1543                             subtract = 0;
1544                         if (nextChunk == null)
1545                             subtract += hangingCorrection;
1546                         localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + chunk.font().size(), 0));
1547                     }
1548                     if (chunk.isAttribute(Chunk.GENERICTAG)) {
1549                         float subtract = lastBaseFactor;
1550                         if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG))
1551                             subtract = 0;
1552                         if (nextChunk == null)
1553                             subtract += hangingCorrection;
1554                         Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1555                         PdfPageEvent pev = writer.getPageEvent();
1556                         if (pev != null)
1557                             pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG));
1558                     }
1559                     if (chunk.isAttribute(Chunk.PDFANNOTATION)) {
1560                         float subtract = lastBaseFactor;
1561                         if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION))
1562                             subtract = 0;
1563                         if (nextChunk == null)
1564                             subtract += hangingCorrection;
1565                         float fontSize = chunk.font().size();
1566                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1567                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1568                         PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION));
1569                         annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender));
1570                         text.addAnnotation(annot);
1571                     }
1572                     float params[] = (float[])chunk.getAttribute(Chunk.SKEW);
1573                     Float hs = (Float)chunk.getAttribute(Chunk.HSCALE);
1574                     if (params != null || hs != null) {
1575                         float b = 0, c = 0;
1576                         if (params != null) {
1577                             b = params[0];
1578                             c = params[1];
1579                         }
1580                         if (hs != null)
1581                             hScale = hs.floatValue();
1582                         text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker);
1583                     }
1584                     if (chunk.isImage()) {
1585                         Image image = chunk.getImage();
1586                         float matrix[] = image.matrix();
1587                         matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX];
1588                         matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY];
1589                         graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
1590                         text.moveText(xMarker + lastBaseFactor + image.getScaledWidth() - text.getXTLM(), 0);
1591                     }
1592                 }
1593                 xMarker += width;
1594                 ++chunkStrokeIdx;
1595             }
1596
1597             if (chunk.font().compareTo(currentFont) != 0) {
1598                 currentFont = chunk.font();
1599                 text.setFontAndSize(currentFont.getFont(), currentFont.size());
1600             }
1601             float rise = 0;
1602             Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE);
1603             int tr = 0;
1604             float strokeWidth = 1;
1605             Color strokeColor = null;
1606             Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT);
1607             if (textRender != null) {
1608                 tr = ((Integer)textRender[0]).intValue() & 3;
1609                 if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
1610                     text.setTextRenderingMode(tr);
1611                 if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) {
1612                     strokeWidth = ((Float)textRender[1]).floatValue();
1613                     if (strokeWidth != 1)
1614                         text.setLineWidth(strokeWidth);
1615                     strokeColor = (Color)textRender[2];
1616                     if (strokeColor == null)
1617                         strokeColor = color;
1618                     if (strokeColor != null)
1619                         text.setColorStroke(strokeColor);
1620                 }
1621             }
1622             if (fr != null)
1623                 rise = fr.floatValue();
1624             if (color != null)
1625                 text.setColorFill(color);
1626             if (rise != 0)
1627                 text.setTextRise(rise);
1628             if (chunk.isImage()) {
1629                 adjustMatrix = true;
1630             }
1631             else if (chunk.isHorizontalSeparator()) {
1632                 PdfTextArray array = new PdfTextArray();
1633                 array.add(-glueWidth * 1000f / chunk.font.size() / hScale);
1634                 text.showText(array);
1635             }
1636             else if (chunk.isTab()) {
1637                 PdfTextArray array = new PdfTextArray();
1638                 array.add((tabPosition - xMarker) * 1000f / chunk.font.size() / hScale);
1639                 text.showText(array);
1640             }
1641             // If it is a CJK chunk or Unicode TTF we will have to simulate the
1642             // space adjustment.
1643             else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) {
1644                 if (hScale != lastHScale) {
1645                     lastHScale = hScale;
1646                     text.setWordSpacing(baseWordSpacing / hScale);
1647                     text.setCharacterSpacing(baseCharacterSpacing / hScale);
1648                 }
1649                 String s = chunk.toString();
1650                 int idx = s.indexOf(' ');
1651                 if (idx < 0)
1652                     text.showText(s);
1653                 else {
1654                     float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale;
1655                     PdfTextArray textArray = new PdfTextArray(s.substring(0, idx));
1656                     int lastIdx = idx;
1657                     while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) {
1658                         textArray.add(spaceCorrection);
1659                         textArray.add(s.substring(lastIdx, idx));
1660                         lastIdx = idx;
1661                     }
1662                     textArray.add(spaceCorrection);
1663                     textArray.add(s.substring(lastIdx));
1664                     text.showText(textArray);
1665                 }
1666             }
1667             else {
1668                 if (isJustified && hScale != lastHScale) {
1669                     lastHScale = hScale;
1670                     text.setWordSpacing(baseWordSpacing / hScale);
1671                     text.setCharacterSpacing(baseCharacterSpacing / hScale);
1672                 }
1673                 text.showText(chunk.toString());
1674             }
1675
1676             if (rise != 0)
1677                 text.setTextRise(0);
1678             if (color != null)
1679                 text.resetRGBColorFill();
1680             if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
1681                 text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
1682             if (strokeColor != null)
1683                 text.resetRGBColorStroke();
1684             if (strokeWidth != 1)
1685                 text.setLineWidth(1);
1686             if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) {
1687                 adjustMatrix = true;
1688                 text.setTextMatrix(xMarker, yMarker);
1689             }
1690         }
1691         if (isJustified) {
1692             text.setWordSpacing(0);
1693             text.setCharacterSpacing(0);
1694             if (line.isNewlineSplit())
1695                 lastBaseFactor = 0;
1696         }
1697         if (adjustMatrix)
1698             text.moveText(baseXMarker - text.getXTLM(), 0);
1699         currentValues[0] = currentFont;
1700         currentValues[1] = new Float(lastBaseFactor);
1701     }
1702
1703     protected Indentation indentation = new Indentation();
1704
1705     /**
1706      * @since    2.0.8 (PdfDocument was package-private before)
1707      */

1708     public static class Indentation {
1709
1710         /** This represents the current indentation of the PDF Elements on the left side. */
1711         float indentLeft = 0;
1712
1713         /** Indentation to the left caused by a section. */
1714         float sectionIndentLeft = 0;
1715
1716         /** This represents the current indentation of the PDF Elements on the left side. */
1717         float listIndentLeft = 0;
1718
1719         /** This is the indentation caused by an image on the left. */
1720         float imageIndentLeft = 0;
1721
1722         /** This represents the current indentation of the PDF Elements on the right side. */
1723         float indentRight = 0;
1724
1725         /** Indentation to the right caused by a section. */
1726         float sectionIndentRight = 0;
1727
1728         /** This is the indentation caused by an image on the right. */
1729         float imageIndentRight = 0;
1730
1731         /** This represents the current indentation of the PDF Elements on the top side. */
1732         float indentTop = 0;
1733
1734         /** This represents the current indentation of the PDF Elements on the bottom side. */
1735         float indentBottom = 0;
1736     }
1737
1738     /**
1739      * Gets the indentation on the left side.
1740      *
1741      * @return    a margin
1742      */

1743
1744     protected float indentLeft() {
1745         return left(indentation.indentLeft + indentation.listIndentLeft + indentation.imageIndentLeft + indentation.sectionIndentLeft);
1746     }
1747
1748     /**
1749      * Gets the indentation on the right side.
1750      *
1751      * @return    a margin
1752      */

1753
1754     protected float indentRight() {
1755         return right(indentation.indentRight + indentation.sectionIndentRight + indentation.imageIndentRight);
1756     }
1757
1758     /**
1759      * Gets the indentation on the top side.
1760      *
1761      * @return    a margin
1762      */

1763
1764     protected float indentTop() {
1765         return top(indentation.indentTop);
1766     }
1767
1768     /**
1769      * Gets the indentation on the bottom side.
1770      *
1771      * @return    a margin
1772      */

1773
1774     float indentBottom() {
1775         return bottom(indentation.indentBottom);
1776     }
1777
1778     /**
1779      * Adds extra space.
1780      * This method should probably be rewritten.
1781      */

1782     protected void addSpacing(float extraspace, float oldleading, Font f) {
1783         if (extraspace == 0) return;
1784         if (pageEmpty) return;
1785         if (currentHeight + line.height() + leading > indentTop() - indentBottom()) return;
1786         leading = extraspace;
1787         carriageReturn();
1788         if (f.isUnderlined() || f.isStrikethru()) {
1789             f = new Font(f);
1790             int style = f.getStyle();
1791             style &= ~Font.UNDERLINE;
1792             style &= ~Font.STRIKETHRU;
1793             f.setStyle(style);
1794         }
1795         Chunk space = new Chunk(" ", f);
1796         space.process(this);
1797         carriageReturn();
1798         leading = oldleading;
1799     }
1800
1801 //    Info Dictionary and Catalog
1802
1803     /** some meta information about the Document. */
1804     protected PdfInfo info = new PdfInfo();
1805
1806     /**
1807      * Gets the <CODE>PdfInfo</CODE>-object.
1808      *
1809      * @return    <CODE>PdfInfo</COPE>
1810      */

1811
1812     PdfInfo getInfo() {
1813         return info;
1814     }
1815
1816     /**
1817      * Gets the <CODE>PdfCatalog</CODE>-object.
1818      *
1819      * @param pages an indirect reference to this document pages
1820      * @return <CODE>PdfCatalog</CODE>
1821      */

1822
1823     PdfCatalog getCatalog(PdfIndirectReference pages) {
1824         PdfCatalog catalog = new PdfCatalog(pages, writer);
1825
1826         // [C1] outlines
1827         if (rootOutline.getKids().size() > 0) {
1828             catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES);
1829             catalog.put(PdfName.OUTLINES, rootOutline.indirectReference());
1830         }
1831
1832         // [C2] version
1833         writer.getPdfVersion().addToCatalog(catalog);
1834
1835         // [C3] preferences
1836         viewerPreferences.addToCatalog(catalog);
1837
1838         // [C4] pagelabels
1839         if (pageLabels != null) {
1840             catalog.put(PdfName.PAGELABELS, pageLabels.getDictionary(writer));
1841         }
1842
1843         // [C5] named objects
1844         catalog.addNames(localDestinations, getDocumentLevelJS(), documentFileAttachment, writer);
1845
1846         // [C6] actions
1847         if (openActionName != null) {
1848             PdfAction action = getLocalGotoAction(openActionName);
1849             catalog.setOpenAction(action);
1850         }
1851         else if (openActionAction != null)
1852             catalog.setOpenAction(openActionAction);
1853         if (additionalActions != null)   {
1854             catalog.setAdditionalActions(additionalActions);
1855         }
1856
1857         // [C7] portable collections
1858         if (collection != null) {
1859             catalog.put(PdfName.COLLECTION, collection);
1860         }
1861
1862         // [C8] AcroForm
1863         if (annotationsImp.hasValidAcroForm()) {
1864             try {
1865                 catalog.put(PdfName.ACROFORM, writer.addToBody(annotationsImp.getAcroForm()).getIndirectReference());
1866             }
1867             catch (IOException e) {
1868                 throw new ExceptionConverter(e);
1869             }
1870         }
1871
1872         return catalog;
1873     }
1874
1875 //    [C1] outlines
1876
1877     /** This is the root outline of the document. */
1878     protected PdfOutline rootOutline;
1879
1880     /** This is the current <CODE>PdfOutline</CODE> in the hierarchy of outlines. */
1881     protected PdfOutline currentOutline;
1882
1883     /**
1884      * Adds a named outline to the document .
1885      * @param outline the outline to be added
1886      * @param name the name of this local destination
1887      */

1888     void addOutline(PdfOutline outline, String name) {
1889         localDestination(name, outline.getPdfDestination());
1890     }
1891
1892     /**
1893      * Gets the root outline. All the outlines must be created with a parent.
1894      * The first level is created with this outline.
1895      * @return the root outline
1896      */

1897     public PdfOutline getRootOutline() {
1898         return rootOutline;
1899     }
1900
1901
1902     /**
1903      * Updates the count in the outlines.
1904      */

1905     void calculateOutlineCount() {
1906         if (rootOutline.getKids().size() == 0)
1907             return;
1908         traverseOutlineCount(rootOutline);
1909     }
1910
1911     /**
1912      * Recursive method to update the count in the outlines.
1913      */

1914     void traverseOutlineCount(PdfOutline outline) {
1915         ArrayList kids = outline.getKids();
1916         PdfOutline parent = outline.parent();
1917         if (kids.isEmpty()) {
1918             if (parent != null) {
1919                 parent.setCount(parent.getCount() + 1);
1920             }
1921         }
1922         else {
1923             for (int k = 0; k < kids.size(); ++k) {
1924                 traverseOutlineCount((PdfOutline)kids.get(k));
1925             }
1926             if (parent != null) {
1927                 if (outline.isOpen()) {
1928                     parent.setCount(outline.getCount() + parent.getCount() + 1);
1929                 }
1930                 else {
1931                     parent.setCount(parent.getCount() + 1);
1932                     outline.setCount(-outline.getCount());
1933                 }
1934             }
1935         }
1936     }
1937
1938     /**
1939      * Writes the outline tree to the body of the PDF document.
1940      */

1941     void writeOutlines() throws IOException {
1942         if (rootOutline.getKids().size() == 0)
1943             return;
1944         outlineTree(rootOutline);
1945         writer.addToBody(rootOutline, rootOutline.indirectReference());
1946     }
1947
1948     /**
1949      * Recursive method used to write outlines.
1950      */

1951     void outlineTree(PdfOutline outline) throws IOException {
1952         outline.setIndirectReference(writer.getPdfIndirectReference());
1953         if (outline.parent() != null)
1954             outline.put(PdfName.PARENT, outline.parent().indirectReference());
1955         ArrayList kids = outline.getKids();
1956         int size = kids.size();
1957         for (int k = 0; k < size; ++k)
1958             outlineTree((PdfOutline)kids.get(k));
1959         for (int k = 0; k < size; ++k) {
1960             if (k > 0)
1961                 ((PdfOutline)kids.get(k)).put(PdfName.PREV, ((PdfOutline)kids.get(k - 1)).indirectReference());
1962             if (k < size - 1)
1963                 ((PdfOutline)kids.get(k)).put(PdfName.NEXT, ((PdfOutline)kids.get(k + 1)).indirectReference());
1964         }
1965         if (size > 0) {
1966             outline.put(PdfName.FIRST, ((PdfOutline)kids.get(0)).indirectReference());
1967             outline.put(PdfName.LAST, ((PdfOutline)kids.get(size - 1)).indirectReference());
1968         }
1969         for (int k = 0; k < size; ++k) {
1970             PdfOutline kid = (PdfOutline)kids.get(k);
1971             writer.addToBody(kid, kid.indirectReference());
1972         }
1973     }
1974
1975 //  [C3] PdfViewerPreferences interface
1976
1977     /** Contains the Viewer preferences of this PDF document. */
1978     protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
1979     /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */
1980     void setViewerPreferences(int preferences) {
1981         this.viewerPreferences.setViewerPreferences(preferences);
1982     }
1983
1984     /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfObject) */
1985     void addViewerPreference(PdfName key, PdfObject value) {
1986         this.viewerPreferences.addViewerPreference(key, value);
1987     }
1988
1989 //    [C4] Page labels
1990
1991     protected PdfPageLabels pageLabels;
1992     /**
1993      * Sets the page labels
1994      * @param pageLabels the page labels
1995      */

1996     void setPageLabels(PdfPageLabels pageLabels) {
1997         this.pageLabels = pageLabels;
1998     }
1999
2000 //    [C5] named objects: local destinations, javascript, embedded files
2001
2002     /**
2003      * Implements a link to other part of the document. The jump will
2004      * be made to a local destination with the same name, that must exist.
2005      * @param name the name for this link
2006      * @param llx the lower left x corner of the activation area
2007      * @param lly the lower left y corner of the activation area
2008      * @param urx the upper right x corner of the activation area
2009      * @param ury the upper right y corner of the activation area
2010      */

2011     void localGoto(String name, float llx, float lly, float urx, float ury) {
2012         PdfAction action = getLocalGotoAction(name);
2013         annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
2014     }
2015
2016     /**
2017      * Implements a link to another document.
2018      * @param filename the filename for the remote document
2019      * @param name the name to jump to
2020      * @param llx the lower left x corner of the activation area
2021      * @param lly the lower left y corner of the activation area
2022      * @param urx the upper right x corner of the activation area
2023      * @param ury the upper right y corner of the activation area
2024      */

2025     void remoteGoto(String filename, String name, float llx, float lly, float urx, float ury) {
2026         annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, name)));
2027     }
2028
2029     /**
2030      * Implements a link to another document.
2031      * @param filename the filename for the remote document
2032      * @param page the page to jump to
2033      * @param llx the lower left x corner of the activation area
2034      * @param lly the lower left y corner of the activation area
2035      * @param urx the upper right x corner of the activation area
2036      * @param ury the upper right y corner of the activation area
2037      */

2038     void remoteGoto(String filename, int page, float llx, float lly, float urx, float ury) {
2039         addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, page)));
2040     }
2041
2042     /** Implements an action in an area.
2043      * @param action the <CODE>PdfAction</CODE>
2044      * @param llx the lower left x corner of the activation area
2045      * @param lly the lower left y corner of the activation area
2046      * @param urx the upper right x corner of the activation area
2047      * @param ury the upper right y corner of the activation area
2048      */

2049     void setAction(PdfAction action, float llx, float lly, float urx, float ury) {
2050         addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
2051     }
2052
2053     /**
2054      * Stores the destinations keyed by name. Value is
2055      * <CODE>Object[]{PdfAction,PdfIndirectReference,PdfDestintion}</CODE>.
2056      */

2057     protected TreeMap localDestinations = new TreeMap();
2058
2059     PdfAction getLocalGotoAction(String name) {
2060         PdfAction action;
2061         Object obj[] = (Object[])localDestinations.get(name);
2062         if (obj == null)
2063             obj = new Object[3];
2064         if (obj[0] == null) {
2065             if (obj[1] == null) {
2066                 obj[1] = writer.getPdfIndirectReference();
2067             }
2068             action = new PdfAction((PdfIndirectReference)obj[1]);
2069             obj[0] = action;
2070             localDestinations.put(name, obj);
2071         }
2072         else {
2073             action = (PdfAction)obj[0];
2074         }
2075         return action;
2076     }
2077
2078     /**
2079      * The local destination to where a local goto with the same
2080      * name will jump to.
2081      * @param name the name of this local destination
2082      * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates
2083      * @return <CODE>true</CODE> if the local destination was added,
2084      * <CODE>false</CODE> if a local destination with the same name
2085      * already existed
2086      */

2087     boolean localDestination(String name, PdfDestination destination) {
2088         Object obj[] = (Object[])localDestinations.get(name);
2089         if (obj == null)
2090             obj = new Object[3];
2091         if (obj[2] != null)
2092             return false;
2093         obj[2] = destination;
2094         localDestinations.put(name, obj);
2095         destination.addPage(writer.getCurrentPage());
2096         return true;
2097     }
2098
2099     /**
2100      * Stores a list of document level JavaScript actions.
2101      */

2102     int jsCounter;
2103     protected HashMap documentLevelJS = new HashMap();
2104     protected static final DecimalFormat SIXTEEN_DIGITS = new DecimalFormat("0000000000000000");
2105     void addJavaScript(PdfAction js) {
2106         if (js.get(PdfName.JS) == null)
2107             throw new RuntimeException("Only JavaScript actions are allowed.");
2108         try {
2109             documentLevelJS.put(SIXTEEN_DIGITS.format(jsCounter++), writer.addToBody(js).getIndirectReference());
2110         }
2111         catch (IOException e) {
2112             throw new ExceptionConverter(e);
2113         }
2114     }
2115     void addJavaScript(String name, PdfAction js) {
2116         if (js.get(PdfName.JS) == null)
2117             throw new RuntimeException("Only JavaScript actions are allowed.");
2118         try {
2119             documentLevelJS.put(name, writer.addToBody(js).getIndirectReference());
2120         }
2121         catch (IOException e) {
2122             throw new ExceptionConverter(e);
2123         }
2124     }
2125
2126     HashMap getDocumentLevelJS() {
2127         return documentLevelJS;
2128     }
2129
2130     protected HashMap documentFileAttachment = new HashMap();
2131
2132     void addFileAttachment(String description, PdfFileSpecification fs) throws IOException {
2133         if (description == null) {
2134             PdfString desc = (PdfString)fs.get(PdfName.DESC);
2135             if (desc == null) {
2136                 description = "";
2137             }
2138             else {
2139                 description = PdfEncodings.convertToString(desc.getBytes(), null);
2140             }
2141         }
2142         fs.addDescription(description, true);
2143         if (description.length() == 0)
2144             description = "Unnamed";
2145         String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null);
2146         int k = 0;
2147         while (documentFileAttachment.containsKey(fn)) {
2148             ++k;
2149             fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null);
2150         }
2151         documentFileAttachment.put(fn, fs.getReference());
2152     }
2153
2154     HashMap getDocumentFileAttachment() {
2155         return documentFileAttachment;
2156     }
2157
2158 //    [C6] document level actions
2159
2160     protected String openActionName;
2161
2162     void setOpenAction(String name) {
2163         openActionName = name;
2164         openActionAction = null;
2165     }
2166
2167     protected PdfAction openActionAction;
2168     void setOpenAction(PdfAction action) {
2169         openActionAction = action;
2170         openActionName = null;
2171     }
2172
2173     protected PdfDictionary additionalActions;
2174     void addAdditionalAction(PdfName actionType, PdfAction action)  {
2175         if (additionalActions == null)  {
2176             additionalActions = new PdfDictionary();
2177         }
2178         if (action == null)
2179             additionalActions.remove(actionType);
2180         else
2181             additionalActions.put(actionType, action);
2182         if (additionalActions.size() == 0)
2183             additionalActions = null;
2184     }
2185
2186 //    [C7] portable collections
2187
2188     protected PdfCollection collection;
2189
2190     /**
2191      * Sets the collection dictionary.
2192      * @param collection a dictionary of type PdfCollection
2193      */

2194     public void setCollection(PdfCollection collection) {
2195         this.collection = collection;
2196     }
2197
2198 //    [C8] AcroForm
2199
2200     PdfAnnotationsImp annotationsImp;
2201
2202     /**
2203      * Gets the AcroForm object.
2204      * @return the PdfAcroform object of the PdfDocument
2205      */

2206     PdfAcroForm getAcroForm() {
2207         return annotationsImp.getAcroForm();
2208     }
2209
2210     void setSigFlags(int f) {
2211         annotationsImp.setSigFlags(f);
2212     }
2213
2214     void addCalculationOrder(PdfFormField formField) {
2215         annotationsImp.addCalculationOrder(formField);
2216     }
2217
2218     void addAnnotation(PdfAnnotation annot) {
2219         pageEmpty = false;
2220         annotationsImp.addAnnotation(annot);
2221     }
2222
2223 //    [F12] tagged PDF
2224
2225     protected int markPoint;
2226
2227     int getMarkPoint() {
2228         return markPoint;
2229     }
2230
2231     void incMarkPoint() {
2232         ++markPoint;
2233     }
2234
2235 //    [U1] page sizes
2236
2237     /** This is the size of the next page. */
2238     protected Rectangle nextPageSize = null;
2239
2240     /** This is the size of the several boxes of the current Page. */
2241     protected HashMap thisBoxSize = new HashMap();
2242
2243     /** This is the size of the several boxes that will be used in
2244      * the next page. */

2245     protected HashMap boxSize = new HashMap();
2246
2247     void setCropBoxSize(Rectangle crop) {
2248         setBoxSize("crop", crop);
2249     }
2250
2251     void setBoxSize(String boxName, Rectangle size) {
2252         if (size == null)
2253             boxSize.remove(boxName);
2254         else
2255             boxSize.put(boxName, new PdfRectangle(size));
2256     }
2257
2258     protected void setNewPageSizeAndMargins() {
2259         pageSize = nextPageSize;
2260         if (marginMirroring && (getPageNumber() & 1) == 0) {
2261             marginRight = nextMarginLeft;
2262             marginLeft = nextMarginRight;
2263         }
2264         else {
2265             marginLeft = nextMarginLeft;
2266             marginRight = nextMarginRight;
2267         }
2268         if (marginMirroringTopBottom && (getPageNumber() & 1) == 0) {
2269             marginTop = nextMarginBottom;
2270             marginBottom = nextMarginTop;
2271         }
2272         else {
2273             marginTop = nextMarginTop;
2274             marginBottom = nextMarginBottom;
2275         }
2276     }
2277
2278     /**
2279      * Gives the size of a trim, art, crop or bleed box, or null if not defined.
2280      * @param boxName crop, trim, art or bleed
2281      */

2282     Rectangle getBoxSize(String boxName) {
2283         PdfRectangle r = (PdfRectangle)thisBoxSize.get(boxName);
2284         if (r != nullreturn r.getRectangle();
2285         return null;
2286     }
2287
2288 //    [U2] empty pages
2289
2290     /** This checks if the page is empty. */
2291     protected boolean pageEmpty = true;
2292
2293     void setPageEmpty(boolean pageEmpty) {
2294         this.pageEmpty = pageEmpty;
2295     }
2296
2297 //    [U3] page actions
2298
2299     /** The duration of the page */
2300     protected int duration=-1; // negative values will indicate no duration
2301
2302     /** The page transition */
2303     protected PdfTransition transition=null;
2304
2305     /**
2306      * Sets the display duration for the page (for presentations)
2307      * @param seconds   the number of seconds to display the page
2308      */

2309     void setDuration(int seconds) {
2310         if (seconds > 0)
2311             this.duration=seconds;
2312         else
2313             this.duration=-1;
2314     }
2315
2316     /**
2317      * Sets the transition for the page
2318      * @param transition   the PdfTransition object
2319      */

2320     void setTransition(PdfTransition transition) {
2321         this.transition=transition;
2322     }
2323
2324     protected PdfDictionary pageAA = null;
2325     void setPageAction(PdfName actionType, PdfAction action) {
2326         if (pageAA == null) {
2327             pageAA = new PdfDictionary();
2328         }
2329         pageAA.put(actionType, action);
2330     }
2331
2332 //    [U8] thumbnail images
2333
2334     protected PdfIndirectReference thumb;
2335     void setThumbnail(Image image) throws PdfException, DocumentException {
2336         thumb = writer.getImageReference(writer.addDirectImageSimple(image));
2337     }
2338
2339 //    [M0] Page resources contain references to fonts, extgstate, images,...
2340
2341     /** This are the page resources of the current Page. */
2342     protected PageResources pageResources;
2343
2344     PageResources getPageResources() {
2345         return pageResources;
2346     }
2347
2348 //    [M3] Images
2349
2350     /** Holds value of property strictImageSequence. */
2351     protected boolean strictImageSequence = false;
2352
2353     /** Getter for property strictImageSequence.
2354      * @return Value of property strictImageSequence.
2355      *
2356      */

2357     boolean isStrictImageSequence() {
2358         return this.strictImageSequence;
2359     }
2360
2361     /** Setter for property strictImageSequence.
2362      * @param strictImageSequence New value of property strictImageSequence.
2363      *
2364      */

2365     void setStrictImageSequence(boolean strictImageSequence) {
2366         this.strictImageSequence = strictImageSequence;
2367     }
2368
2369     /** This is the position where the image ends. */
2370     protected float imageEnd = -1;
2371
2372     /**
2373      * Method added by Pelikan Stephan
2374      */

2375     public void clearTextWrap() {
2376         float tmpHeight = imageEnd - currentHeight;
2377         if (line != null) {
2378             tmpHeight += line.height();
2379         }
2380         if ((imageEnd > -1) && (tmpHeight > 0)) {
2381             carriageReturn();
2382             currentHeight += tmpHeight;
2383         }
2384     }
2385
2386     /** This is the image that could not be shown on a previous page. */
2387     protected Image imageWait = null;
2388
2389     /**
2390      * Adds an image to the document.
2391      * @param image the <CODE>Image</CODE> to add
2392      * @throws PdfException on error
2393      * @throws DocumentException on error
2394      */

2395
2396     protected void add(Image image) throws PdfException, DocumentException {
2397
2398         if (image.hasAbsoluteY()) {
2399             graphics.addImage(image);
2400             pageEmpty = false;
2401             return;
2402         }
2403
2404         // if there isn't enough room for the image on this page, save it for the next page
2405         if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
2406             if (!strictImageSequence && imageWait == null) {
2407                 imageWait = image;
2408                 return;
2409             }
2410             newPage();
2411             if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
2412                 imageWait = image;
2413                 return;
2414             }
2415         }
2416         pageEmpty = false;
2417         // avoid endless loops
2418         if (image == imageWait)
2419             imageWait = null;
2420         boolean textwrap = (image.getAlignment() & Image.TEXTWRAP) == Image.TEXTWRAP
2421         && !((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE);
2422         boolean underlying = (image.getAlignment() & Image.UNDERLYING) == Image.UNDERLYING;
2423         float diff = leading / 2;
2424         if (textwrap) {
2425             diff += leading;
2426         }
2427         float lowerleft = indentTop() - currentHeight - image.getScaledHeight() -diff;
2428         float mt[] = image.matrix();
2429         float startPosition = indentLeft() - mt[4];
2430         if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.getScaledWidth() - mt[4];
2431         if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + ((indentRight() - indentLeft() - image.getScaledWidth()) / 2) - mt[4];
2432         if (image.hasAbsoluteX()) startPosition = image.getAbsoluteX();
2433         if (textwrap) {
2434             if (imageEnd < 0 || imageEnd < currentHeight + image.getScaledHeight() + diff) {
2435                 imageEnd = currentHeight + image.getScaledHeight() + diff;
2436             }
2437             if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) {
2438                 // indentation suggested by Pelikan Stephan
2439                 indentation.imageIndentRight += image.getScaledWidth() + image.getIndentationLeft();
2440             }
2441             else {
2442                 // indentation suggested by Pelikan Stephan
2443                 indentation.imageIndentLeft += image.getScaledWidth() + image.getIndentationRight();
2444             }
2445         }
2446         else {
2447             if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.getIndentationRight();
2448             else if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.getIndentationLeft() - image.getIndentationRight();
2449             else startPosition += image.getIndentationLeft();
2450         }
2451         graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]);
2452         if (!(textwrap || underlying)) {
2453             currentHeight += image.getScaledHeight() + diff;
2454             flushLines();
2455             text.moveText(0, - (image.getScaledHeight() + diff));
2456             newLine();
2457         }
2458     }
2459
2460 //    [M4] Adding a PdfPTable
2461
2462     /** Adds a <CODE>PdfPTable</CODE> to the document.
2463      * @param ptable the <CODE>PdfPTable</CODE> to be added to the document.
2464      * @throws DocumentException on error
2465      */

2466     void addPTable(PdfPTable ptable) throws DocumentException {
2467         ColumnText ct = new ColumnText(writer.getDirectContent());
2468         // if the table prefers to be on a single page, and it wouldn't
2469         //fit on the current page, start a new page.
2470         if (ptable.getKeepTogether() && !fitsPage(ptable, 0f) && currentHeight > 0)  {
2471             newPage();
2472         }
2473         // add dummy paragraph if we aren't at the top of a page, so that
2474         // spacingBefore will be taken into account by ColumnText
2475         if (currentHeight > 0) {
2476             Paragraph p = new Paragraph();
2477             p.setLeading(0);
2478             ct.addElement(p);
2479         }
2480         ct.addElement(ptable);
2481         boolean he = ptable.isHeadersInEvent();
2482         ptable.setHeadersInEvent(true);
2483         int loop = 0;
2484         while (true) {
2485             ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight);
2486             int status = ct.go();
2487             if ((status & ColumnText.NO_MORE_TEXT) != 0) {
2488                 text.moveText(0, ct.getYLine() - indentTop() + currentHeight);
2489                 currentHeight = indentTop() - ct.getYLine();
2490                 break;
2491             }
2492             if (indentTop() - currentHeight == ct.getYLine())
2493                 ++loop;
2494             else
2495                 loop = 0;
2496             if (loop == 3) {
2497                 add(new Paragraph("ERROR: Infinite table loop"));
2498                 break;
2499             }
2500             newPage();
2501         }
2502         ptable.setHeadersInEvent(he);
2503     }
2504
2505     /**
2506      * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
2507      *
2508      * @param    table    the table that has to be checked
2509      * @param    margin    a certain margin
2510      * @return    <CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise.
2511      */

2512
2513     boolean fitsPage(PdfPTable table, float margin) {
2514         if (!table.isLockedWidth()) {
2515             float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100;
2516             table.setTotalWidth(totalWidth);
2517         }
2518         // ensuring that a new line has been started.
2519         ensureNewLine();
2520         return table.getTotalHeight() + ((currentHeight > 0) ? table.spacingBefore() : 0f)
2521             <= indentTop() - currentHeight - indentBottom() - margin;
2522     }
2523
2524 //    [M4'] Adding a Table
2525
2526     /**
2527      * This is a helper class for adding a Table to a document.
2528      * @since    2.0.8 (PdfDocument was package-private before)
2529      */

2530     protected static class RenderingContext {
2531         float pagetop = -1;
2532         float oldHeight = -1;
2533
2534         PdfContentByte cellGraphics = null;
2535
2536         float lostTableBottom;
2537
2538         float maxCellBottom;
2539         float maxCellHeight;
2540
2541         Map rowspanMap;
2542         Map pageMap = new HashMap();
2543         /**
2544          * A PdfPTable
2545          */

2546         public PdfTable table;
2547
2548         /**
2549          * Consumes the rowspan
2550          * @param c
2551          * @return a rowspan.
2552          */

2553         public int consumeRowspan(PdfCell c) {
2554             if (c.rowspan() == 1) {
2555                 return 1;
2556             }
2557
2558             Integer i = (Integer) rowspanMap.get(c);
2559             if (i == null) {
2560                 i = new Integer(c.rowspan());
2561             }
2562
2563             i = new Integer(i.intValue() - 1);
2564             rowspanMap.put(c, i);
2565
2566             if (i.intValue() < 1) {
2567                 return 1;
2568             }
2569             return i.intValue();
2570         }
2571
2572         /**
2573          * Looks at the current rowspan.
2574          * @param c
2575          * @return the current rowspan
2576          */

2577         public int currentRowspan(PdfCell c) {
2578             Integer i = (Integer) rowspanMap.get(c);
2579             if (i == null) {
2580                 return c.rowspan();
2581             } else {
2582                 return i.intValue();
2583             }
2584         }
2585
2586         public int cellRendered(PdfCell cell, int pageNumber) {
2587             Integer i = (Integer) pageMap.get(cell);
2588             if (i == null) {
2589                 i = new Integer(1);
2590             } else {
2591                 i = new Integer(i.intValue() + 1);
2592             }
2593             pageMap.put(cell, i);
2594
2595             Integer pageInteger = new Integer(pageNumber);
2596             Set set = (Set) pageMap.get(pageInteger);
2597
2598             if (set == null) {
2599                 set = new HashSet();
2600                 pageMap.put(pageInteger, set);
2601             }
2602
2603             set.add(cell);
2604
2605             return i.intValue();
2606         }
2607
2608         public int numCellRendered(PdfCell cell) {
2609             Integer i = (Integer) pageMap.get(cell);
2610             if (i == null) {
2611                 i = new Integer(0);
2612             }
2613             return i.intValue();
2614         }
2615
2616         public boolean isCellRenderedOnPage(PdfCell cell, int pageNumber) {
2617             Integer pageInteger = new Integer(pageNumber);
2618             Set set = (Set) pageMap.get(pageInteger);
2619
2620             if (set != null) {
2621                 return set.contains(cell);
2622             }
2623
2624             return false;
2625         }
2626     };
2627
2628     /**
2629      * Adds a new table to the document.
2630      * @param t                Table to add.  Rendered rows will be deleted after processing.
2631      * @throws DocumentException
2632      * @since    iText 2.0.8
2633      */

2634     private void addPdfTable(Table t) throws DocumentException {
2635         // before every table, we flush all lines
2636         flushLines();
2637
2638         PdfTable table = new PdfTable(t, indentLeft(), indentRight(), indentTop() - currentHeight);
2639         RenderingContext ctx = new RenderingContext();
2640         ctx.pagetop = indentTop();
2641         ctx.oldHeight = currentHeight;
2642         ctx.cellGraphics = new PdfContentByte(writer);
2643         ctx.rowspanMap = new HashMap();
2644         ctx.table = table;
2645
2646         // initialization of parameters
2647         PdfCell cell;
2648
2649         // drawing the table
2650         ArrayList headercells = table.getHeaderCells();
2651         ArrayList cells = table.getCells();
2652         ArrayList rows = extractRows(cells, ctx);
2653         boolean isContinue = false;
2654         while (!cells.isEmpty()) {
2655             // initialization of some extra parameters;
2656             ctx.lostTableBottom = 0;
2657
2658             // loop over the cells
2659             boolean cellsShown = false;
2660
2661             // draw the cells (line by line)
2662             Iterator iterator = rows.iterator();
2663
2664             boolean atLeastOneFits = false;
2665             while (iterator.hasNext()) {
2666                 ArrayList row = (ArrayList) iterator.next();
2667                 analyzeRow(rows, ctx);
2668                 renderCells(ctx, row, table.hasToFitPageCells() & atLeastOneFits);
2669
2670                 if (!mayBeRemoved(row)) {
2671                     break;
2672                 }
2673                 consumeRowspan(row, ctx);
2674                 iterator.remove();
2675                 atLeastOneFits = true;
2676             }
2677
2678 //          compose cells array list for subsequent code
2679             cells.clear();
2680             Set opt = new HashSet();
2681             iterator = rows.iterator();
2682             while (iterator.hasNext()) {
2683                 ArrayList row = (ArrayList) iterator.next();
2684
2685                 Iterator cellIterator = row.iterator();
2686                 while (cellIterator.hasNext()) {
2687                     cell = (PdfCell) cellIterator.next();
2688
2689                     if (!opt.contains(cell)) {
2690                         cells.add(cell);
2691                         opt.add(cell);
2692                     }
2693                 }
2694             }
2695
2696             // we paint the graphics of the table after looping through all the cells
2697             Rectangle tablerec = new Rectangle(table);
2698             tablerec.setBorder(table.getBorder());
2699             tablerec.setBorderWidth(table.getBorderWidth());
2700             tablerec.setBorderColor(table.getBorderColor());
2701             tablerec.setBackgroundColor(table.getBackgroundColor());
2702             PdfContentByte under = writer.getDirectContentUnder();
2703             under.rectangle(tablerec.rectangle(top(), indentBottom()));
2704             under.add(ctx.cellGraphics);
2705             // bugfix by Gerald Fehringer: now again add the border for the table
2706             // since it might have been covered by cell backgrounds
2707             tablerec.setBackgroundColor(null);
2708             tablerec = tablerec.rectangle(top(), indentBottom());
2709             tablerec.setBorder(table.getBorder());
2710             under.rectangle(tablerec);
2711             // end bugfix
2712
2713             ctx.cellGraphics = new PdfContentByte(null);
2714             // if the table continues on the next page
2715
2716             if (!rows.isEmpty()) {
2717                 isContinue = true;
2718                 graphics.setLineWidth(table.getBorderWidth());
2719                 if (cellsShown && (table.getBorder() & Rectangle.BOTTOM) == Rectangle.BOTTOM) {
2720                     // Draw the bottom line
2721
2722                     // the color is set to the color of the element
2723                     Color tColor = table.getBorderColor();
2724                     if (tColor != null) {
2725                         graphics.setColorStroke(tColor);
2726                     }
2727                     graphics.moveTo(table.getLeft(), Math.max(table.getBottom(), indentBottom()));
2728                     graphics.lineTo(table.getRight(), Math.max(table.getBottom(), indentBottom()));
2729                     graphics.stroke();
2730                     if (tColor != null) {
2731                         graphics.resetRGBColorStroke();
2732                     }
2733                 }
2734
2735                 // old page
2736                 pageEmpty = false;
2737                 float difference = ctx.lostTableBottom;
2738
2739                 // new page
2740                 newPage();
2741
2742                 // G.F.: if something added in page event i.e. currentHeight > 0
2743                 float heightCorrection = 0;
2744                 boolean somethingAdded = false;
2745                 if (currentHeight > 0) {
2746                     heightCorrection = 6;
2747                     currentHeight += heightCorrection;
2748                     somethingAdded = true;
2749                     newLine();
2750                     flushLines();
2751                     indentation.indentTop = currentHeight - leading;
2752                     currentHeight = 0;
2753                 }
2754                 else {
2755                     flushLines();
2756                 }
2757
2758                 // this part repeats the table headers (if any)
2759                 int size = headercells.size();
2760                 if (size > 0) {
2761                     // this is the top of the headersection
2762                     cell = (PdfCell) headercells.get(0);
2763                     float oldTop = cell.getTop(0);
2764                     // loop over all the cells of the table header
2765                     for (int i = 0; i < size; i++) {
2766                         cell = (PdfCell) headercells.get(i);
2767                         // calculation of the new cellpositions
2768                         cell.setTop(indentTop() - oldTop + cell.getTop(0));
2769                         cell.setBottom(indentTop() - oldTop + cell.getBottom(0));
2770                         ctx.pagetop = cell.getBottom();
2771                         // we paint the borders of the cell
2772                         ctx.cellGraphics.rectangle(cell.rectangle(indentTop(), indentBottom()));
2773                         // we write the text of the cell
2774                         ArrayList images = cell.getImages(indentTop(), indentBottom());
2775                         for (Iterator im = images.iterator(); im.hasNext();) {
2776                             cellsShown = true;
2777                             Image image = (Image) im.next();
2778                             graphics.addImage(image);
2779                         }
2780                         lines = cell.getLines(indentTop(), indentBottom());
2781                         float cellTop = cell.getTop(indentTop());
2782                         text.moveText(0, cellTop-heightCorrection);
2783                         float cellDisplacement = flushLines() - cellTop+heightCorrection;
2784                         text.moveText(0, cellDisplacement);
2785                     }
2786
2787                     currentHeight = indentTop() - ctx.pagetop + table.cellspacing();
2788                     text.moveText(0, ctx.pagetop - indentTop() - currentHeight);
2789                 }
2790                 else {
2791                     if (somethingAdded) {
2792                         ctx.pagetop = indentTop();
2793                         text.moveText(0, -table.cellspacing());
2794                     }
2795                 }
2796                 ctx.oldHeight = currentHeight - heightCorrection;
2797
2798                 // calculating the new positions of the table and the cells
2799                 size = Math.min(cells.size(), table.columns());
2800                 int i = 0;
2801                 while (i < size) {
2802                     cell = (PdfCell) cells.get(i);
2803                     if (cell.getTop(-table.cellspacing()) > ctx.lostTableBottom) {
2804                         float newBottom = ctx.pagetop - difference + cell.getBottom();
2805                         float neededHeight = cell.remainingHeight();
2806                         if (newBottom > ctx.pagetop - neededHeight) {
2807                             difference += newBottom - (ctx.pagetop - neededHeight);
2808                         }
2809                     }
2810                     i++;
2811                 }
2812                 size = cells.size();
2813                 table.setTop(indentTop());
2814                 table.setBottom(ctx.pagetop - difference + table.getBottom(table.cellspacing()));
2815                 for (i = 0; i < size; i++) {
2816                     cell = (PdfCell) cells.get(i);
2817                     float newBottom = ctx.pagetop - difference + cell.getBottom();
2818                     float newTop = ctx.pagetop - difference + cell.getTop(-table.cellspacing());
2819                     if (newTop > indentTop() - currentHeight) {
2820                         newTop = indentTop() - currentHeight;
2821                     }
2822
2823                     cell.setTop(newTop );
2824                     cell.setBottom(newBottom );
2825                 }
2826             }
2827         }
2828
2829         float tableHeight = table.getTop() - table.getBottom();
2830         // bugfix by Adauto Martins when have more than two tables and more than one page
2831         // If continuation of table in other page (bug report #1460051)
2832         if (isContinue) {
2833             currentHeight = tableHeight;
2834             text.moveText(0, -(tableHeight - (ctx.oldHeight * 2)));
2835         } else {
2836             currentHeight = ctx.oldHeight + tableHeight;
2837             text.moveText(0, -tableHeight);
2838         }
2839         // end bugfix
2840         pageEmpty = false;
2841     }
2842
2843     protected void analyzeRow(ArrayList rows, RenderingContext ctx) {
2844         ctx.maxCellBottom = indentBottom();
2845
2846         // determine whether row(index) is in a rowspan
2847         int rowIndex = 0;
2848
2849         ArrayList row = (ArrayList) rows.get(rowIndex);
2850         int maxRowspan = 1;
2851         Iterator iterator = row.iterator();
2852         while (iterator.hasNext()) {
2853             PdfCell cell = (PdfCell) iterator.next();
2854             maxRowspan = Math.max(ctx.currentRowspan(cell), maxRowspan);
2855         }
2856         rowIndex += maxRowspan;
2857
2858         boolean useTop = true;
2859         if (rowIndex == rows.size()) {
2860             rowIndex = rows.size() - 1;
2861             useTop = false;
2862         }
2863
2864         if (rowIndex < 0 || rowIndex >= rows.size()) return;
2865
2866         row = (ArrayList) rows.get(rowIndex);
2867         iterator = row.iterator();
2868         while (iterator.hasNext()) {
2869             PdfCell cell = (PdfCell) iterator.next();
2870             Rectangle cellRect = cell.rectangle(ctx.pagetop, indentBottom());
2871             if (useTop) {
2872                 ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.getTop());
2873             } else {
2874                 if (ctx.currentRowspan(cell) == 1) {
2875                     ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.getBottom());
2876                 }
2877             }
2878         }
2879     }
2880
2881     protected boolean mayBeRemoved(ArrayList row) {
2882         Iterator iterator = row.iterator();
2883         boolean mayBeRemoved = true;
2884         while (iterator.hasNext()) {
2885             PdfCell cell = (PdfCell) iterator.next();
2886
2887             mayBeRemoved &= cell.mayBeRemoved();
2888         }
2889         return mayBeRemoved;
2890     }
2891
2892     protected void consumeRowspan(ArrayList row, RenderingContext ctx) {
2893         Iterator iterator = row.iterator();
2894         while (iterator.hasNext()) {
2895             PdfCell c = (PdfCell) iterator.next();
2896             ctx.consumeRowspan(c);
2897         }
2898     }
2899
2900     protected ArrayList extractRows(ArrayList cells, RenderingContext ctx) {
2901         PdfCell cell;
2902         PdfCell previousCell = null;
2903         ArrayList rows = new ArrayList();
2904         java.util.List rowCells = new ArrayList();
2905
2906         Iterator iterator = cells.iterator();
2907         while (iterator.hasNext()) {
2908             cell = (PdfCell) iterator.next();
2909
2910             boolean isAdded = false;
2911
2912             boolean isEndOfRow = !iterator.hasNext();
2913             boolean isCurrentCellPartOfRow = !iterator.hasNext();
2914
2915             if (previousCell != null) {
2916                 if (cell.getLeft() <= previousCell.getLeft()) {
2917                     isEndOfRow = true;
2918                     isCurrentCellPartOfRow = false;
2919                 }
2920             }
2921
2922             if (isCurrentCellPartOfRow) {
2923                 rowCells.add(cell);
2924                 isAdded = true;
2925             }
2926
2927             if (isEndOfRow) {
2928                 if (!rowCells.isEmpty()) {
2929                     // add to rowlist
2930                     rows.add(rowCells);
2931                 }
2932
2933                 // start a new list for next line
2934                 rowCells = new ArrayList();
2935             }
2936
2937             if (!isAdded) {
2938                 rowCells.add(cell);
2939             }
2940
2941             previousCell = cell;
2942         }
2943
2944         if (!rowCells.isEmpty()) {
2945             rows.add(rowCells);
2946         }
2947
2948         // fill row information with rowspan cells to get complete "scan lines"
2949         for (int i = rows.size() - 1; i >= 0; i--) {
2950             ArrayList row = (ArrayList) rows.get(i);
2951             // iterator through row
2952             for (int j = 0; j < row.size(); j++) {
2953                 PdfCell c = (PdfCell) row.get(j);
2954                 int rowspan = c.rowspan();
2955                 // fill in missing rowspan cells to complete "scan line"
2956                 for (int k = 1; k < rowspan && rows.size() < i+k; k++) {
2957                     ArrayList spannedRow = ((ArrayList) rows.get(i + k));
2958                     if (spannedRow.size() > j)
2959                         spannedRow.add(j, c);
2960                 }
2961             }
2962         }
2963
2964         return rows;
2965     }
2966
2967     protected void renderCells(RenderingContext ctx, java.util.List cells, boolean hasToFit) throws DocumentException {
2968         PdfCell cell;
2969         Iterator iterator;
2970         if (hasToFit) {
2971             iterator = cells.iterator();
2972             while (iterator.hasNext()) {
2973                 cell = (PdfCell) iterator.next();
2974                 if (!cell.isHeader()) {
2975                     if (cell.getBottom() < indentBottom()) return;
2976                 }
2977             }
2978         }
2979         iterator = cells.iterator();
2980
2981         while (iterator.hasNext()) {
2982             cell = (PdfCell) iterator.next();
2983             if (!ctx.isCellRenderedOnPage(cell, getPageNumber())) {
2984
2985                 float correction = 0;
2986                 if (ctx.numCellRendered(cell) >= 1) {
2987                     correction = 1.0f;
2988                 }
2989
2990                 lines = cell.getLines(ctx.pagetop, indentBottom() - correction);
2991
2992                 // if there is still text to render we render it
2993                 if (lines != null && !lines.isEmpty()) {
2994                     // we write the text
2995                     float cellTop = cell.getTop(ctx.pagetop - ctx.oldHeight);
2996                     text.moveText(0, cellTop);
2997                     float cellDisplacement = flushLines() - cellTop;
2998
2999                     text.moveText(0, cellDisplacement);
3000                     if (ctx.oldHeight + cellDisplacement > currentHeight) {
3001                         currentHeight = ctx.oldHeight + cellDisplacement;
3002                     }
3003
3004                     ctx.cellRendered(cell, getPageNumber());
3005                 }
3006                 float indentBottom = Math.max(cell.getBottom(), indentBottom());
3007                 Rectangle tableRect = ctx.table.rectangle(ctx.pagetop, indentBottom());
3008                 indentBottom = Math.max(tableRect.getBottom(), indentBottom);
3009
3010                 // we paint the borders of the cells
3011                 Rectangle cellRect = cell.rectangle(tableRect.getTop(), indentBottom);
3012                  //cellRect.setBottom(cellRect.bottom());
3013                 if (cellRect.getHeight() > 0) {
3014                     ctx.lostTableBottom = indentBottom;
3015                     ctx.cellGraphics.rectangle(cellRect);
3016                 }
3017
3018                 // and additional graphics
3019                 ArrayList images = cell.getImages(ctx.pagetop, indentBottom());
3020                 for (Iterator i = images.iterator(); i.hasNext();) {
3021                     Image image = (Image) i.next();
3022                     graphics.addImage(image);
3023                 }
3024
3025             }
3026         }
3027     }
3028
3029     /**
3030      * Returns the bottomvalue of a <CODE>Table</CODE> if it were added to this document.
3031      *
3032      * @param    table    the table that may or may not be added to this document
3033      * @return    a bottom value
3034      */

3035     float bottom(Table table) {
3036         // constructing a PdfTable
3037         PdfTable tmp = new PdfTable(table, indentLeft(), indentRight(), indentTop() - currentHeight);
3038         return tmp.getBottom();
3039     }
3040
3041 //    [M5] header/footer
3042     protected void doFooter() throws DocumentException {
3043         if (footer == nullreturn;
3044         // Begin added by Edgar Leonardo Prieto Perilla
3045         // Avoid footer indentation
3046         float tmpIndentLeft = indentation.indentLeft;
3047         float tmpIndentRight = indentation.indentRight;
3048         // Begin added: Bonf (Marc Schneider) 2003-07-29
3049         float tmpListIndentLeft = indentation.listIndentLeft;
3050         float tmpImageIndentLeft = indentation.imageIndentLeft;
3051         float tmpImageIndentRight = indentation.imageIndentRight;
3052         // End added: Bonf (Marc Schneider) 2003-07-29
3053
3054         indentation.indentLeft = indentation.indentRight = 0;
3055         // Begin added: Bonf (Marc Schneider) 2003-07-29
3056         indentation.listIndentLeft = 0;
3057         indentation.imageIndentLeft = 0;
3058         indentation.imageIndentRight = 0;
3059         // End added: Bonf (Marc Schneider) 2003-07-29
3060         // End Added by Edgar Leonardo Prieto Perilla
3061         footer.setPageNumber(pageN);
3062         leading = footer.paragraph().getTotalLeading();
3063         add(footer.paragraph());
3064         // adding the footer limits the height
3065         indentation.indentBottom = currentHeight;
3066         text.moveText(left(), indentBottom());
3067         flushLines();
3068         text.moveText(-left(), -bottom());
3069         footer.setTop(bottom(currentHeight));
3070         footer.setBottom(bottom() - (0.75f * leading));
3071         footer.setLeft(left());
3072         footer.setRight(right());
3073         graphics.rectangle(footer);
3074         indentation.indentBottom = currentHeight + leading * 2;
3075         currentHeight = 0;
3076         // Begin added by Edgar Leonardo Prieto Perilla
3077         indentation.indentLeft = tmpIndentLeft;
3078         indentation.indentRight = tmpIndentRight;
3079         // Begin added: Bonf (Marc Schneider) 2003-07-29
3080         indentation.listIndentLeft = tmpListIndentLeft;
3081         indentation.imageIndentLeft = tmpImageIndentLeft;
3082         indentation.imageIndentRight = tmpImageIndentRight;
3083         // End added: Bonf (Marc Schneider) 2003-07-29
3084         // End added by Edgar Leonardo Prieto Perilla
3085     }
3086
3087     protected void doHeader() throws DocumentException {
3088         // if there is a header, the header = added
3089         if (header == nullreturn;
3090         // Begin added by Edgar Leonardo Prieto Perilla
3091         // Avoid header indentation
3092         float tmpIndentLeft = indentation.indentLeft;
3093         float tmpIndentRight = indentation.indentRight;
3094         // Begin added: Bonf (Marc Schneider) 2003-07-29
3095         float tmpListIndentLeft = indentation.listIndentLeft;
3096         float tmpImageIndentLeft = indentation.imageIndentLeft;
3097         float tmpImageIndentRight = indentation.imageIndentRight;
3098         // End added: Bonf (Marc Schneider) 2003-07-29
3099         indentation.indentLeft = indentation.indentRight = 0;
3100         //  Added: Bonf
3101         indentation.listIndentLeft = 0;
3102         indentation.imageIndentLeft = 0;
3103         indentation.imageIndentRight = 0;
3104         // End added: Bonf
3105         // Begin added by Edgar Leonardo Prieto Perilla
3106         header.setPageNumber(pageN);
3107         leading = header.paragraph().getTotalLeading();
3108         text.moveText(0, leading);
3109         add(header.paragraph());
3110         newLine();
3111         indentation.indentTop = currentHeight - leading;
3112         header.setTop(top() + leading);
3113         header.setBottom(indentTop() + leading * 2 / 3);
3114         header.setLeft(left());
3115         header.setRight(right());
3116         graphics.rectangle(header);
3117         flushLines();
3118         currentHeight = 0;
3119         // Begin added by Edgar Leonardo Prieto Perilla
3120         // Restore indentation
3121         indentation.indentLeft = tmpIndentLeft;
3122         indentation.indentRight = tmpIndentRight;
3123         // Begin added: Bonf (Marc Schneider) 2003-07-29
3124         indentation.listIndentLeft = tmpListIndentLeft;
3125         indentation.imageIndentLeft = tmpImageIndentLeft;
3126         indentation.imageIndentRight = tmpImageIndentRight;
3127         // End added: Bonf (Marc Schneider) 2003-07-29
3128         // End Added by Edgar Leonardo Prieto Perilla
3129     }
3130 }