1 /*
2  * $Id$
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 import com.lowagie.text.error_messages.MessageLocalization;
62 
63 import com.lowagie.text.Anchor;
64 import com.lowagie.text.Annotation;
65 import com.lowagie.text.BadElementException;
66 import com.lowagie.text.Chunk;
67 import com.lowagie.text.Document;
68 import com.lowagie.text.DocumentException;
69 import com.lowagie.text.Element;
70 import com.lowagie.text.ExceptionConverter;
71 import com.lowagie.text.Font;
72 import com.lowagie.text.HeaderFooter;
73 import com.lowagie.text.Image;
74 import com.lowagie.text.List;
75 import com.lowagie.text.ListItem;
76 import com.lowagie.text.MarkedObject;
77 import com.lowagie.text.MarkedSection;
78 import com.lowagie.text.Meta;
79 import com.lowagie.text.Paragraph;
80 import com.lowagie.text.Phrase;
81 import com.lowagie.text.Rectangle;
82 import com.lowagie.text.Section;
83 import com.lowagie.text.SimpleTable;
84 import com.lowagie.text.Table;
85 import com.lowagie.text.pdf.collection.PdfCollection;
86 import com.lowagie.text.pdf.draw.DrawInterface;
87 import com.lowagie.text.pdf.internal.PdfAnnotationsImp;
88 import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
89 import java.text.DecimalFormat;
90 
91 /**
92  * <CODE>PdfDocument</CODE> is the class that is used by <CODE>PdfWriter</CODE>
93  * to translate a <CODE>Document</CODE> into a PDF with different pages.
94  * <P>
95  * A <CODE>PdfDocument</CODE> always listens to a <CODE>Document</CODE>
96  * and adds the Pdf representation of every <CODE>Element</CODE> that is
97  * added to the <CODE>Document</CODE>.
98  *
99  * @see		com.lowagie.text.Document
100  * @see		com.lowagie.text.DocListener
101  * @see		PdfWriter
102  * @since	2.0.8 (class was package-private before)
103  */
104 
105 public class PdfDocument extends Document {
106 
107     /**
108      * <CODE>PdfInfo</CODE> is the PDF InfoDictionary.
109      * <P>
110      * A document's trailer may contain a reference to an Info dictionary that provides information
111      * about the document. This optional dictionary may contain one or more keys, whose values
112      * should be strings.<BR>
113      * This object is described in the 'Portable Document Format Reference Manual version 1.3'
114      * section 6.10 (page 120-121)
115      * @since	2.0.8 (PdfDocument was package-private before)
116      */
117 
118     public static class PdfInfo extends PdfDictionary {
119 
120         /**
121          * Construct a <CODE>PdfInfo</CODE>-object.
122          */
123 
PdfInfo()124         PdfInfo() {
125             super();
126             addProducer();
127             addCreationDate();
128         }
129 
130         /**
131          * Constructs a <CODE>PdfInfo</CODE>-object.
132          *
133          * @param		author		name of the author of the document
134          * @param		title		title of the document
135          * @param		subject		subject of the document
136          */
137 
PdfInfo(String author, String title, String subject)138         PdfInfo(String author, String title, String subject) {
139             this();
140             addTitle(title);
141             addSubject(subject);
142             addAuthor(author);
143         }
144 
145         /**
146          * Adds the title of the document.
147          *
148          * @param	title		the title of the document
149          */
150 
addTitle(String title)151         void addTitle(String title) {
152             put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE));
153         }
154 
155         /**
156          * Adds the subject to the document.
157          *
158          * @param	subject		the subject of the document
159          */
160 
addSubject(String subject)161         void addSubject(String subject) {
162             put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE));
163         }
164 
165         /**
166          * Adds some keywords to the document.
167          *
168          * @param	keywords		the keywords of the document
169          */
170 
addKeywords(String keywords)171         void addKeywords(String keywords) {
172             put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE));
173         }
174 
175         /**
176          * Adds the name of the author to the document.
177          *
178          * @param	author		the name of the author
179          */
180 
addAuthor(String author)181         void addAuthor(String author) {
182             put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE));
183         }
184 
185         /**
186          * Adds the name of the creator to the document.
187          *
188          * @param	creator		the name of the creator
189          */
190 
addCreator(String creator)191         void addCreator(String creator) {
192             put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE));
193         }
194 
195         /**
196          * Adds the name of the producer to the document.
197          */
198 
addProducer()199         void addProducer() {
200             put(PdfName.PRODUCER, new PdfString(getVersion()));
201         }
202 
203         /**
204          * Adds the date of creation to the document.
205          */
206 
addCreationDate()207         void addCreationDate() {
208             PdfString date = new PdfDate();
209             put(PdfName.CREATIONDATE, date);
210             put(PdfName.MODDATE, date);
211         }
212 
addkey(String key, String value)213         void addkey(String key, String value) {
214             if (key.equals("Producer") || key.equals("CreationDate"))
215                 return;
216             put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE));
217         }
218     }
219 
220     /**
221      * <CODE>PdfCatalog</CODE> is the PDF Catalog-object.
222      * <P>
223      * The Catalog is a dictionary that is the root node of the document. It contains a reference
224      * to the tree of pages contained in the document, a reference to the tree of objects representing
225      * the document's outline, a reference to the document's article threads, and the list of named
226      * destinations. In addition, the Catalog indicates whether the document's outline or thumbnail
227      * page images should be displayed automatically when the document is viewed and whether some location
228      * other than the first page should be shown when the document is opened.<BR>
229      * In this class however, only the reference to the tree of pages is implemented.<BR>
230      * This object is described in the 'Portable Document Format Reference Manual version 1.3'
231      * section 6.2 (page 67-71)
232      */
233 
234     static class PdfCatalog extends PdfDictionary {
235 
236     	/** The writer writing the PDF for which we are creating this catalog object. */
237         PdfWriter writer;
238 
239         /**
240          * Constructs a <CODE>PdfCatalog</CODE>.
241          *
242          * @param		pages		an indirect reference to the root of the document's Pages tree.
243          * @param writer the writer the catalog applies to
244          */
245 
PdfCatalog(PdfIndirectReference pages, PdfWriter writer)246         PdfCatalog(PdfIndirectReference pages, PdfWriter writer) {
247             super(CATALOG);
248             this.writer = writer;
249             put(PdfName.PAGES, pages);
250         }
251 
252         /**
253          * Adds the names of the named destinations to the catalog.
254          * @param localDestinations the local destinations
255          * @param documentLevelJS the javascript used in the document
256          * @param documentFileAttachment	the attached files
257          * @param writer the writer the catalog applies to
258          */
addNames(TreeMap localDestinations, HashMap documentLevelJS, HashMap documentFileAttachment, PdfWriter writer)259         void addNames(TreeMap localDestinations, HashMap documentLevelJS, HashMap documentFileAttachment, PdfWriter writer) {
260             if (localDestinations.isEmpty() && documentLevelJS.isEmpty() && documentFileAttachment.isEmpty())
261                 return;
262             try {
263                 PdfDictionary names = new PdfDictionary();
264                 if (!localDestinations.isEmpty()) {
265                     PdfArray ar = new PdfArray();
266                     for (Iterator i = localDestinations.entrySet().iterator(); i.hasNext();) {
267                         Map.Entry entry = (Map.Entry) i.next();
268                         String name = (String) entry.getKey();
269                         Object obj[] = (Object[]) entry.getValue();
270                         if (obj[2] == null) //no destination
271                             continue;
272                         PdfIndirectReference ref = (PdfIndirectReference)obj[1];
273                         ar.add(new PdfString(name, null));
274                         ar.add(ref);
275                     }
276                     if (ar.size() > 0) {
277                         PdfDictionary dests = new PdfDictionary();
278                         dests.put(PdfName.NAMES, ar);
279                         names.put(PdfName.DESTS, writer.addToBody(dests).getIndirectReference());
280                     }
281                 }
282                 if (!documentLevelJS.isEmpty()) {
283                     PdfDictionary tree = PdfNameTree.writeTree(documentLevelJS, writer);
284                     names.put(PdfName.JAVASCRIPT, writer.addToBody(tree).getIndirectReference());
285                 }
286                 if (!documentFileAttachment.isEmpty()) {
287                     names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference());
288                 }
289                 if (names.size() > 0)
290                     put(PdfName.NAMES, writer.addToBody(names).getIndirectReference());
291             }
292             catch (IOException e) {
293                 throw new ExceptionConverter(e);
294             }
295         }
296 
297         /**
298          * Adds an open action to the catalog.
299          * @param	action	the action that will be triggered upon opening the document
300          */
setOpenAction(PdfAction action)301         void setOpenAction(PdfAction action) {
302             put(PdfName.OPENACTION, action);
303         }
304 
305 
306         /**
307          * Sets the document level additional actions.
308          * @param actions   dictionary of actions
309          */
setAdditionalActions(PdfDictionary actions)310         void setAdditionalActions(PdfDictionary actions) {
311             try {
312                 put(PdfName.AA, writer.addToBody(actions).getIndirectReference());
313             } catch (Exception e) {
314                 throw new ExceptionConverter(e);
315             }
316         }
317     }
318 
319 // CONSTRUCTING A PdfDocument/PdfWriter INSTANCE
320 
321     /**
322      * Constructs a new PDF document.
323      */
PdfDocument()324     public PdfDocument() {
325         super();
326         addProducer();
327         addCreationDate();
328     }
329 
330     /** The <CODE>PdfWriter</CODE>. */
331     protected PdfWriter writer;
332 
333     /**
334      * Adds a <CODE>PdfWriter</CODE> to the <CODE>PdfDocument</CODE>.
335      *
336      * @param writer the <CODE>PdfWriter</CODE> that writes everything
337      *                     what is added to this document to an outputstream.
338      * @throws DocumentException on error
339      */
addWriter(PdfWriter writer)340     public void addWriter(PdfWriter writer) throws DocumentException {
341         if (this.writer == null) {
342             this.writer = writer;
343             annotationsImp = new PdfAnnotationsImp(writer);
344             return;
345         }
346         throw new DocumentException(MessageLocalization.getComposedMessage("you.can.only.add.a.writer.to.a.pdfdocument.once"));
347     }
348 
349 // LISTENER METHODS START
350 
351 //	[L0] ElementListener interface
352 
353     /** This is the PdfContentByte object, containing the text. */
354     protected PdfContentByte text;
355 
356     /** This is the PdfContentByte object, containing the borders and other Graphics. */
357     protected PdfContentByte graphics;
358 
359     /** This represents the leading of the lines. */
360     protected float leading = 0;
361 
362     /**
363      * Getter for the current leading.
364      * @return	the current leading
365      * @since	2.1.2
366      */
getLeading()367     public float getLeading() {
368     	return leading;
369     }
370 
371     /**
372      * Setter for the current leading.
373      * @param	leading the current leading
374      * @since	2.1.6
375      */
setLeading(float leading)376     void setLeading(float leading) {
377     	this.leading = leading;
378     }
379 
380     /** This represents the current alignment of the PDF Elements. */
381     protected int alignment = Element.ALIGN_LEFT;
382 
383     /** This is the current height of the document. */
384     protected float currentHeight = 0;
385 
386     /**
387      * Signals that onParagraph is valid (to avoid that a Chapter/Section title is treated as a Paragraph).
388      * @since 2.1.2
389      */
390     protected boolean isSectionTitle = false;
391 
392     /**
393      * Signals that the current leading has to be subtracted from a YMark object when positive.
394      * @since 2.1.2
395      */
396     protected int leadingCount = 0;
397 
398     /** The current active <CODE>PdfAction</CODE> when processing an <CODE>Anchor</CODE>. */
399     protected PdfAction anchorAction = null;
400 
401     /**
402      * Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>.
403      *
404      * @param element the element to add
405      * @return <CODE>true</CODE> if the element was added, <CODE>false</CODE> if not.
406      * @throws DocumentException when a document isn't open yet, or has been closed
407      */
add(Element element)408     public boolean add(Element element) throws DocumentException {
409         if (writer != null && writer.isPaused()) {
410             return false;
411         }
412         try {
413             switch(element.type()) {
414                 // Information (headers)
415                 case Element.HEADER:
416                     info.addkey(((Meta)element).getName(), ((Meta)element).getContent());
417                     break;
418                 case Element.TITLE:
419                     info.addTitle(((Meta)element).getContent());
420                     break;
421                 case Element.SUBJECT:
422                     info.addSubject(((Meta)element).getContent());
423                     break;
424                 case Element.KEYWORDS:
425                     info.addKeywords(((Meta)element).getContent());
426                     break;
427                 case Element.AUTHOR:
428                     info.addAuthor(((Meta)element).getContent());
429                     break;
430                 case Element.CREATOR:
431                     info.addCreator(((Meta)element).getContent());
432                     break;
433                 case Element.PRODUCER:
434                     // you can not change the name of the producer
435                     info.addProducer();
436                     break;
437                 case Element.CREATIONDATE:
438                     // you can not set the creation date, only reset it
439                     info.addCreationDate();
440                     break;
441 
442                 // content (text)
443                 case Element.CHUNK: {
444                     // if there isn't a current line available, we make one
445                     if (line == null) {
446                         carriageReturn();
447                     }
448 
449                     // we cast the element to a chunk
450                     PdfChunk chunk = new PdfChunk((Chunk) element, anchorAction);
451                     // we try to add the chunk to the line, until we succeed
452                     {
453                         PdfChunk overflow;
454                         while ((overflow = line.add(chunk)) != null) {
455                             carriageReturn();
456                             chunk = overflow;
457                             chunk.trimFirstSpace();
458                         }
459                     }
460                     pageEmpty = false;
461                     if (chunk.isAttribute(Chunk.NEWPAGE)) {
462                         newPage();
463                     }
464                     break;
465                 }
466                 case Element.ANCHOR: {
467                 	leadingCount++;
468                     Anchor anchor = (Anchor) element;
469                     String url = anchor.getReference();
470                     leading = anchor.getLeading();
471                     if (url != null) {
472                         anchorAction = new PdfAction(url);
473                     }
474                     // we process the element
475                     element.process(this);
476                     anchorAction = null;
477                     leadingCount--;
478                     break;
479                 }
480                 case Element.ANNOTATION: {
481                     if (line == null) {
482                         carriageReturn();
483                     }
484                     Annotation annot = (Annotation) element;
485                     Rectangle rect = new Rectangle(0, 0);
486                     if (line != null)
487                     	rect = new Rectangle(annot.llx(indentRight() - line.widthLeft()), annot.ury(indentTop() - currentHeight - 20), annot.urx(indentRight() - line.widthLeft() + 20), annot.lly(indentTop() - currentHeight));
488                     PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, rect);
489                     annotationsImp.addPlainAnnotation(an);
490                     pageEmpty = false;
491                     break;
492                 }
493                 case Element.PHRASE: {
494                 	leadingCount++;
495                     // we cast the element to a phrase and set the leading of the document
496                     leading = ((Phrase) element).getLeading();
497                     // we process the element
498                     element.process(this);
499                     leadingCount--;
500                     break;
501                 }
502                 case Element.PARAGRAPH: {
503                 	leadingCount++;
504                     // we cast the element to a paragraph
505                     Paragraph paragraph = (Paragraph) element;
506                     addSpacing(paragraph.getSpacingBefore(), leading, paragraph.getFont());
507 
508                     // we adjust the parameters of the document
509                     alignment = paragraph.getAlignment();
510                     leading = paragraph.getTotalLeading();
511                     carriageReturn();
512 
513                     // we don't want to make orphans/widows
514                     if (currentHeight + line.height() + leading > indentTop() - indentBottom()) {
515                         newPage();
516                     }
517                     indentation.indentLeft += paragraph.getIndentationLeft();
518                     indentation.indentRight += paragraph.getIndentationRight();
519                     carriageReturn();
520 
521                     PdfPageEvent pageEvent = writer.getPageEvent();
522                     if (pageEvent != null && !isSectionTitle)
523                         pageEvent.onParagraph(writer, this, indentTop() - currentHeight);
524 
525                     // if a paragraph has to be kept together, we wrap it in a table object
526                     if (paragraph.getKeepTogether()) {
527                     	carriageReturn();
528                         PdfPTable table = new PdfPTable(1);
529                         table.setWidthPercentage(100f);
530                         PdfPCell cell = new PdfPCell();
531                         cell.addElement(paragraph);
532                         cell.setBorder(Table.NO_BORDER);
533                         cell.setPadding(0);
534                         table.addCell(cell);
535                         indentation.indentLeft -= paragraph.getIndentationLeft();
536                         indentation.indentRight -= paragraph.getIndentationRight();
537                         this.add(table);
538                         indentation.indentLeft += paragraph.getIndentationLeft();
539                         indentation.indentRight += paragraph.getIndentationRight();
540                     }
541                     else {
542                     	line.setExtraIndent(paragraph.getFirstLineIndent());
543                     	element.process(this);
544                         carriageReturn();
545                         addSpacing(paragraph.getSpacingAfter(), paragraph.getTotalLeading(), paragraph.getFont());
546                     }
547 
548                     if (pageEvent != null && !isSectionTitle)
549                         pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight);
550 
551                     alignment = Element.ALIGN_LEFT;
552                     indentation.indentLeft -= paragraph.getIndentationLeft();
553                     indentation.indentRight -= paragraph.getIndentationRight();
554                     carriageReturn();
555                     leadingCount--;
556                     break;
557                 }
558                 case Element.SECTION:
559                 case Element.CHAPTER: {
560                     // Chapters and Sections only differ in their constructor
561                     // so we cast both to a Section
562                     Section section = (Section) element;
563                     PdfPageEvent pageEvent = writer.getPageEvent();
564 
565                     boolean hasTitle = section.isNotAddedYet()
566                     	&& section.getTitle() != null;
567 
568                     // if the section is a chapter, we begin a new page
569                     if (section.isTriggerNewPage()) {
570                         newPage();
571                     }
572 
573                     if (hasTitle) {
574                     	float fith = indentTop() - currentHeight;
575                     	int rotation = pageSize.getRotation();
576                     	if (rotation == 90 || rotation == 180)
577                     		fith = pageSize.getHeight() - fith;
578                     	PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith);
579                     	while (currentOutline.level() >= section.getDepth()) {
580                     		currentOutline = currentOutline.parent();
581                     	}
582                     	PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen());
583                     	currentOutline = outline;
584                     }
585 
586                     // some values are set
587                     carriageReturn();
588                     indentation.sectionIndentLeft += section.getIndentationLeft();
589                     indentation.sectionIndentRight += section.getIndentationRight();
590 
591                     if (section.isNotAddedYet() && pageEvent != null)
592                         if (element.type() == Element.CHAPTER)
593                             pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.getTitle());
594                         else
595                             pageEvent.onSection(writer, this, indentTop() - currentHeight, section.getDepth(), section.getTitle());
596 
597                     // the title of the section (if any has to be printed)
598                     if (hasTitle) {
599                         isSectionTitle = true;
600                         add(section.getTitle());
601                         isSectionTitle = false;
602                     }
603                     indentation.sectionIndentLeft += section.getIndentation();
604                     // we process the section
605                     element.process(this);
606                     flushLines();
607                     // some parameters are set back to normal again
608                     indentation.sectionIndentLeft -= (section.getIndentationLeft() + section.getIndentation());
609                     indentation.sectionIndentRight -= section.getIndentationRight();
610 
611                     if (section.isComplete() && pageEvent != null)
612                         if (element.type() == Element.CHAPTER)
613                             pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight);
614                         else
615                             pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight);
616 
617                     break;
618                 }
619                 case Element.LIST: {
620                     // we cast the element to a List
621                     List list = (List) element;
622                     if (list.isAlignindent()) {
623                     	list.normalizeIndentation();
624                     }
625                     // we adjust the document
626                     indentation.listIndentLeft += list.getIndentationLeft();
627                     indentation.indentRight += list.getIndentationRight();
628                     // we process the items in the list
629                     element.process(this);
630                     // some parameters are set back to normal again
631                     indentation.listIndentLeft -= list.getIndentationLeft();
632                     indentation.indentRight -= list.getIndentationRight();
633                     carriageReturn();
634                     break;
635                 }
636                 case Element.LISTITEM: {
637                 	leadingCount++;
638                     // we cast the element to a ListItem
639                     ListItem listItem = (ListItem) element;
640 
641                     addSpacing(listItem.getSpacingBefore(), leading, listItem.getFont());
642 
643                     // we adjust the document
644                     alignment = listItem.getAlignment();
645                     indentation.listIndentLeft += listItem.getIndentationLeft();
646                     indentation.indentRight += listItem.getIndentationRight();
647                     leading = listItem.getTotalLeading();
648                     carriageReturn();
649 
650                     // we prepare the current line to be able to show us the listsymbol
651                     line.setListItem(listItem);
652                     // we process the item
653                     element.process(this);
654 
655                     addSpacing(listItem.getSpacingAfter(), listItem.getTotalLeading(), listItem.getFont());
656 
657                     // if the last line is justified, it should be aligned to the left
658                     if (line.hasToBeJustified()) {
659                     	line.resetAlignment();
660                     }
661                     // some parameters are set back to normal again
662                     carriageReturn();
663                     indentation.listIndentLeft -= listItem.getIndentationLeft();
664                     indentation.indentRight -= listItem.getIndentationRight();
665                     leadingCount--;
666                     break;
667                 }
668                 case Element.RECTANGLE: {
669                     Rectangle rectangle = (Rectangle) element;
670                     graphics.rectangle(rectangle);
671                     pageEmpty = false;
672                     break;
673                 }
674                 case Element.PTABLE: {
675                     PdfPTable ptable = (PdfPTable)element;
676                     if (ptable.size() <= ptable.getHeaderRows())
677                         break; //nothing to do
678 
679                     // before every table, we add a new line and flush all lines
680                     ensureNewLine();
681                     flushLines();
682 
683                     addPTable(ptable);
684                     pageEmpty = false;
685                     newLine();
686                     break;
687                 }
688                 case Element.MULTI_COLUMN_TEXT: {
689                     ensureNewLine();
690                     flushLines();
691                     MultiColumnText multiText = (MultiColumnText) element;
692                     float height = multiText.write(writer.getDirectContent(), this, indentTop() - currentHeight);
693                     currentHeight += height;
694                     text.moveText(0, -1f* height);
695                     pageEmpty = false;
696                     break;
697                 }
698                 case Element.TABLE : {
699                     if (element instanceof SimpleTable) {
700                     	PdfPTable ptable = ((SimpleTable)element).createPdfPTable();
701                     	if (ptable.size() <= ptable.getHeaderRows())
702                     		break; //nothing to do
703 
704                     	// before every table, we add a new line and flush all lines
705                     	ensureNewLine();
706                     	flushLines();
707                     	addPTable(ptable);
708                     	pageEmpty = false;
709                     	break;
710                     } else if (element instanceof Table) {
711                     	try {
712                        		PdfPTable ptable = ((Table)element).createPdfPTable();
713                        		if (ptable.size() <= ptable.getHeaderRows())
714                        			break; //nothing to do
715                        		// before every table, we add a new line and flush all lines
716                        		ensureNewLine();
717                        		flushLines();
718                        		addPTable(ptable);
719                        		pageEmpty = false;
720                        		break;
721                     	}
722                     	catch(BadElementException bee) {
723                     		// constructing the PdfTable
724                     		// Before the table, add a blank line using offset or default leading
725                     		float offset = ((Table)element).getOffset();
726                     		if (Float.isNaN(offset))
727                     			offset = leading;
728                     		carriageReturn();
729                     		lines.add(new PdfLine(indentLeft(), indentRight(), alignment, offset));
730                     		currentHeight += offset;
731                     		addPdfTable((Table)element);
732                     	}
733 					} else {
734 						return false;
735 					}
736                     break;
737                 }
738                 case Element.JPEG:
739                 case Element.JPEG2000:
740                 case Element.JBIG2:
741                 case Element.IMGRAW:
742                 case Element.IMGTEMPLATE: {
743                     //carriageReturn(); suggestion by Marc Campforts
744                     add((Image) element);
745                     break;
746                 }
747                 case Element.YMARK: {
748                     DrawInterface zh = (DrawInterface)element;
749                     zh.draw(graphics, indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight - (leadingCount > 0 ? leading : 0));
750                     pageEmpty = false;
751                     break;
752                 }
753                 case Element.MARKED: {
754                 	MarkedObject mo;
755                 	if (element instanceof MarkedSection) {
756                 		mo = ((MarkedSection)element).getTitle();
757                 		if (mo != null) {
758                 			mo.process(this);
759                 		}
760                 	}
761                 	mo = (MarkedObject)element;
762                 	mo.process(this);
763                 	break;
764                 }
765                 default:
766                     return false;
767             }
768             lastElementType = element.type();
769             return true;
770         }
771         catch(Exception e) {
772             throw new DocumentException(e);
773         }
774     }
775 
776 //	[L1] DocListener interface
777 
778     /**
779      * Opens the document.
780      * <P>
781      * You have to open the document before you can begin to add content
782      * to the body of the document.
783      */
open()784     public void open() {
785         if (!open) {
786             super.open();
787             writer.open();
788             rootOutline = new PdfOutline(writer);
789             currentOutline = rootOutline;
790         }
791         try {
792             initPage();
793         }
794         catch(DocumentException de) {
795             throw new ExceptionConverter(de);
796         }
797     }
798 
799 //	[L2] DocListener interface
800 
801     /**
802      * Closes the document.
803      * <B>
804      * Once all the content has been written in the body, you have to close
805      * the body. After that nothing can be written to the body anymore.
806      */
close()807     public void close() {
808         if (close) {
809             return;
810         }
811         try {
812         	boolean wasImage = (imageWait != null);
813             newPage();
814             if (imageWait != null || wasImage) newPage();
815             if (annotationsImp.hasUnusedAnnotations())
816                 throw new RuntimeException(MessageLocalization.getComposedMessage("not.all.annotations.could.be.added.to.the.document.the.document.doesn.t.have.enough.pages"));
817             PdfPageEvent pageEvent = writer.getPageEvent();
818             if (pageEvent != null)
819                 pageEvent.onCloseDocument(writer, this);
820             super.close();
821 
822             writer.addLocalDestinations(localDestinations);
823             calculateOutlineCount();
824             writeOutlines();
825         }
826         catch(Exception e) {
827             throw ExceptionConverter.convertException(e);
828         }
829 
830         writer.close();
831     }
832 
833 //	[L3] DocListener interface
834     protected int textEmptySize;
835 
836     // [C9] Metadata for the page
837     /** XMP Metadata for the page. */
838     protected byte[] xmpMetadata = null;
839 	/**
840 	 * Use this method to set the XMP Metadata.
841 	 * @param xmpMetadata The xmpMetadata to set.
842 	 */
setXmpMetadata(byte[] xmpMetadata)843 	public void setXmpMetadata(byte[] xmpMetadata) {
844 		this.xmpMetadata = xmpMetadata;
845 	}
846 
847     /**
848      * Makes a new page and sends it to the <CODE>PdfWriter</CODE>.
849      *
850      * @return a <CODE>boolean</CODE>
851      */
newPage()852     public boolean newPage() {
853         lastElementType = -1;
854         if (isPageEmpty()) {
855         	setNewPageSizeAndMargins();
856             return false;
857         }
858     	if (!open || close) {
859     		throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
860     	}
861         PdfPageEvent pageEvent = writer.getPageEvent();
862         if (pageEvent != null)
863             pageEvent.onEndPage(writer, this);
864 
865         //Added to inform any listeners that we are moving to a new page (added by David Freels)
866         super.newPage();
867 
868         // the following 2 lines were added by Pelikan Stephan
869         indentation.imageIndentLeft = 0;
870         indentation.imageIndentRight = 0;
871 
872         try {
873             // we flush the arraylist with recently written lines
874         	flushLines();
875 
876         	// we prepare the elements of the page dictionary
877 
878         	// [U1] page size and rotation
879         	int rotation = pageSize.getRotation();
880 
881         	// [C10]
882         	if (writer.isPdfX()) {
883         		if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim"))
884         			throw new PdfXConformanceException(MessageLocalization.getComposedMessage("only.one.of.artbox.or.trimbox.can.exist.in.the.page"));
885         		if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) {
886         			if (thisBoxSize.containsKey("crop"))
887         				thisBoxSize.put("trim", thisBoxSize.get("crop"));
888         			else
889         				thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation()));
890         		}
891         	}
892 
893         	// [M1]
894         	pageResources.addDefaultColorDiff(writer.getDefaultColorspace());
895             if (writer.isRgbTransparencyBlending()) {
896                 PdfDictionary dcs = new PdfDictionary();
897                 dcs.put(PdfName.CS, PdfName.DEVICERGB);
898                 pageResources.addDefaultColorDiff(dcs);
899             }
900         	PdfDictionary resources = pageResources.getResources();
901 
902         	// we create the page dictionary
903 
904         	PdfPage page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation);
905         	page.put(PdfName.TABS, writer.getTabs());
906 
907             // we complete the page dictionary
908 
909             // [C9] if there is XMP data to add: add it
910             if (xmpMetadata != null) {
911             	PdfStream xmp = new PdfStream(xmpMetadata);
912             	xmp.put(PdfName.TYPE, PdfName.METADATA);
913             	xmp.put(PdfName.SUBTYPE, PdfName.XML);
914             	PdfEncryption crypto = writer.getEncryption();
915                 if (crypto != null && !crypto.isMetadataEncrypted()) {
916                     PdfArray ar = new PdfArray();
917                     ar.add(PdfName.CRYPT);
918                     xmp.put(PdfName.FILTER, ar);
919                 }
920             	page.put(PdfName.METADATA, writer.addToBody(xmp).getIndirectReference());
921             }
922 
923         	// [U3] page actions: transition, duration, additional actions
924         	if (this.transition!=null) {
925         		page.put(PdfName.TRANS, this.transition.getTransitionDictionary());
926         		transition = null;
927         	}
928         	if (this.duration>0) {
929         		page.put(PdfName.DUR,new PdfNumber(this.duration));
930         		duration = 0;
931         	}
932         	if (pageAA != null) {
933         		page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference());
934         		pageAA = null;
935         	}
936 
937         	// [U4] we add the thumbs
938         	if (thumb != null) {
939         		page.put(PdfName.THUMB, thumb);
940         		thumb = null;
941         	}
942 
943         	// [U8] we check if the userunit is defined
944         	if (writer.getUserunit() > 0f) {
945         		page.put(PdfName.USERUNIT, new PdfNumber(writer.getUserunit()));
946         	}
947 
948         	// [C5] and [C8] we add the annotations
949         	if (annotationsImp.hasUnusedAnnotations()) {
950         		PdfArray array = annotationsImp.rotateAnnotations(writer, pageSize);
951         		if (array.size() != 0)
952         			page.put(PdfName.ANNOTS, array);
953         	}
954 
955         	// [F12] we add tag info
956         	if (writer.isTagged())
957         		page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1));
958 
959             if (text.size() > textEmptySize)
960         		text.endText();
961         	else
962         		text = null;
963         	writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, text, writer.getDirectContent(), pageSize));
964         	// we initialize the new page
965         	initPage();
966         }
967         catch(DocumentException de) {
968         	// maybe this never happens, but it's better to check.
969         	throw new ExceptionConverter(de);
970         }
971         catch (IOException ioe) {
972             throw new ExceptionConverter(ioe);
973         }
974         return true;
975     }
976 
977 //	[L4] DocListener interface
978 
979     /**
980      * Sets the pagesize.
981      *
982      * @param pageSize the new pagesize
983      * @return <CODE>true</CODE> if the page size was set
984      */
setPageSize(Rectangle pageSize)985     public boolean setPageSize(Rectangle pageSize) {
986         if (writer != null && writer.isPaused()) {
987             return false;
988         }
989         nextPageSize = new Rectangle(pageSize);
990         return true;
991     }
992 
993 //	[L5] DocListener interface
994 
995     /** margin in x direction starting from the left. Will be valid in the next page */
996     protected float nextMarginLeft;
997 
998     /** margin in x direction starting from the right. Will be valid in the next page */
999     protected float nextMarginRight;
1000 
1001     /** margin in y direction starting from the top. Will be valid in the next page */
1002     protected float nextMarginTop;
1003 
1004     /** margin in y direction starting from the bottom. Will be valid in the next page */
1005     protected float nextMarginBottom;
1006 
1007     /**
1008      * Sets the margins.
1009      *
1010      * @param	marginLeft		the margin on the left
1011      * @param	marginRight		the margin on the right
1012      * @param	marginTop		the margin on the top
1013      * @param	marginBottom	the margin on the bottom
1014      * @return	a <CODE>boolean</CODE>
1015      */
setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom)1016     public boolean setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom) {
1017         if (writer != null && writer.isPaused()) {
1018             return false;
1019         }
1020         nextMarginLeft = marginLeft;
1021         nextMarginRight = marginRight;
1022         nextMarginTop = marginTop;
1023         nextMarginBottom = marginBottom;
1024         return true;
1025     }
1026 
1027 //	[L6] DocListener interface
1028 
1029     /**
1030      * @see com.lowagie.text.DocListener#setMarginMirroring(boolean)
1031      */
setMarginMirroring(boolean MarginMirroring)1032     public boolean setMarginMirroring(boolean MarginMirroring) {
1033         if (writer != null && writer.isPaused()) {
1034             return false;
1035         }
1036         return super.setMarginMirroring(MarginMirroring);
1037     }
1038 
1039     /**
1040      * @see com.lowagie.text.DocListener#setMarginMirroring(boolean)
1041      * @since	2.1.6
1042      */
setMarginMirroringTopBottom(boolean MarginMirroringTopBottom)1043     public boolean setMarginMirroringTopBottom(boolean MarginMirroringTopBottom) {
1044         if (writer != null && writer.isPaused()) {
1045             return false;
1046         }
1047         return super.setMarginMirroringTopBottom(MarginMirroringTopBottom);
1048     }
1049 
1050 //	[L7] DocListener interface
1051 
1052     /**
1053      * Sets the page number.
1054      *
1055      * @param	pageN		the new page number
1056      */
setPageCount(int pageN)1057     public void setPageCount(int pageN) {
1058         if (writer != null && writer.isPaused()) {
1059             return;
1060         }
1061         super.setPageCount(pageN);
1062     }
1063 
1064 //	[L8] DocListener interface
1065 
1066     /**
1067      * Sets the page number to 0.
1068      */
resetPageCount()1069     public void resetPageCount() {
1070         if (writer != null && writer.isPaused()) {
1071             return;
1072         }
1073         super.resetPageCount();
1074     }
1075 
1076 //	[L9] DocListener interface
1077 
1078     /**
1079      * Changes the header of this document.
1080      *
1081      * @param header the new header
1082      */
setHeader(HeaderFooter header)1083     public void setHeader(HeaderFooter header) {
1084         if (writer != null && writer.isPaused()) {
1085             return;
1086         }
1087         super.setHeader(header);
1088     }
1089 
1090 //	[L10] DocListener interface
1091 
1092     /**
1093      * Resets the header of this document.
1094      */
resetHeader()1095     public void resetHeader() {
1096         if (writer != null && writer.isPaused()) {
1097             return;
1098         }
1099         super.resetHeader();
1100     }
1101 
1102 //	[L11] DocListener interface
1103 
1104     /**
1105      * Changes the footer of this document.
1106      *
1107      * @param	footer		the new footer
1108      */
setFooter(HeaderFooter footer)1109     public void setFooter(HeaderFooter footer) {
1110         if (writer != null && writer.isPaused()) {
1111             return;
1112         }
1113         super.setFooter(footer);
1114     }
1115 
1116 //	[L12] DocListener interface
1117 
1118     /**
1119      * Resets the footer of this document.
1120      */
resetFooter()1121     public void resetFooter() {
1122         if (writer != null && writer.isPaused()) {
1123             return;
1124         }
1125         super.resetFooter();
1126     }
1127 
1128 // DOCLISTENER METHODS END
1129 
1130     /** Signals that OnOpenDocument should be called. */
1131     protected boolean firstPageEvent = true;
1132 
1133     /**
1134      * Initializes a page.
1135      * <P>
1136      * If the footer/header is set, it is printed.
1137      * @throws DocumentException on error
1138      */
initPage()1139     protected void initPage() throws DocumentException {
1140         // the pagenumber is incremented
1141         pageN++;
1142 
1143         // initialization of some page objects
1144         annotationsImp.resetAnnotations();
1145         pageResources = new PageResources();
1146 
1147         writer.resetContent();
1148         graphics = new PdfContentByte(writer);
1149         text = new PdfContentByte(writer);
1150         text.reset();
1151         text.beginText();
1152         textEmptySize = text.size();
1153 
1154     	markPoint = 0;
1155         setNewPageSizeAndMargins();
1156         imageEnd = -1;
1157         indentation.imageIndentRight = 0;
1158         indentation.imageIndentLeft = 0;
1159         indentation.indentBottom = 0;
1160         indentation.indentTop = 0;
1161         currentHeight = 0;
1162 
1163         // backgroundcolors, etc...
1164         thisBoxSize = new HashMap(boxSize);
1165         if (pageSize.getBackgroundColor() != null
1166         || pageSize.hasBorders()
1167         || pageSize.getBorderColor() != null) {
1168             add(pageSize);
1169         }
1170 
1171         float oldleading = leading;
1172         int oldAlignment = alignment;
1173         // if there is a footer, the footer is added
1174         doFooter();
1175         // we move to the left/top position of the page
1176         text.moveText(left(), top());
1177         doHeader();
1178         pageEmpty = true;
1179         // if there is an image waiting to be drawn, draw it
1180         try {
1181             if (imageWait != null) {
1182                 add(imageWait);
1183                 imageWait = null;
1184             }
1185         }
1186         catch(Exception e) {
1187             throw new ExceptionConverter(e);
1188         }
1189         leading = oldleading;
1190         alignment = oldAlignment;
1191         carriageReturn();
1192 
1193         PdfPageEvent pageEvent = writer.getPageEvent();
1194         if (pageEvent != null) {
1195             if (firstPageEvent) {
1196                 pageEvent.onOpenDocument(writer, this);
1197             }
1198             pageEvent.onStartPage(writer, this);
1199         }
1200         firstPageEvent = false;
1201     }
1202 
1203     /** The line that is currently being written. */
1204     protected PdfLine line = null;
1205     /** The lines that are written until now. */
1206     protected ArrayList lines = new ArrayList();
1207 
1208     /**
1209      * Adds the current line to the list of lines and also adds an empty line.
1210      * @throws DocumentException on error
1211      */
newLine()1212     protected void newLine() throws DocumentException {
1213         lastElementType = -1;
1214         carriageReturn();
1215         if (lines != null && !lines.isEmpty()) {
1216             lines.add(line);
1217             currentHeight += line.height();
1218         }
1219         line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1220     }
1221 
1222     /**
1223      * If the current line is not empty or null, it is added to the arraylist
1224      * of lines and a new empty line is added.
1225      */
carriageReturn()1226     protected void carriageReturn() {
1227         // the arraylist with lines may not be null
1228         if (lines == null) {
1229             lines = new ArrayList();
1230         }
1231         // If the current line is not null
1232         if (line != null) {
1233             // we check if the end of the page is reached (bugfix by Francois Gravel)
1234             if (currentHeight + line.height() + leading < indentTop() - indentBottom()) {
1235                 // if so nonempty lines are added and the height is augmented
1236                 if (line.size() > 0) {
1237                     currentHeight += line.height();
1238                     lines.add(line);
1239                     pageEmpty = false;
1240                 }
1241             }
1242             // if the end of the line is reached, we start a new page
1243             else {
1244                 newPage();
1245             }
1246         }
1247         if (imageEnd > -1 && currentHeight > imageEnd) {
1248             imageEnd = -1;
1249             indentation.imageIndentRight = 0;
1250             indentation.imageIndentLeft = 0;
1251         }
1252         // a new current line is constructed
1253         line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1254     }
1255 
1256     /**
1257      * Gets the current vertical page position.
1258      * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
1259      *   for elements that do not terminate the lines they've started because those lines will get
1260      *   terminated.
1261      * @return The current vertical page position.
1262      */
getVerticalPosition(boolean ensureNewLine)1263     public float getVerticalPosition(boolean ensureNewLine) {
1264         // ensuring that a new line has been started.
1265         if (ensureNewLine) {
1266           ensureNewLine();
1267         }
1268         return top() -  currentHeight - indentation.indentTop;
1269     }
1270 
1271     /** Holds the type of the last element, that has been added to the document. */
1272     protected int lastElementType = -1;
1273 
1274     /**
1275      * Ensures that a new line has been started.
1276      */
ensureNewLine()1277     protected void ensureNewLine() {
1278       try {
1279         if ((lastElementType == Element.PHRASE) ||
1280             (lastElementType == Element.CHUNK)) {
1281           newLine();
1282           flushLines();
1283         }
1284       } catch (DocumentException ex) {
1285         throw new ExceptionConverter(ex);
1286         }
1287     }
1288 
1289     /**
1290      * Writes all the lines to the text-object.
1291      *
1292      * @return the displacement that was caused
1293      * @throws DocumentException on error
1294      */
flushLines()1295     protected float flushLines() throws DocumentException {
1296         // checks if the ArrayList with the lines is not null
1297         if (lines == null) {
1298             return 0;
1299         }
1300         // checks if a new Line has to be made.
1301         if (line != null && line.size() > 0) {
1302             lines.add(line);
1303             line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
1304         }
1305 
1306         // checks if the ArrayList with the lines is empty
1307         if (lines.isEmpty()) {
1308             return 0;
1309         }
1310 
1311         // initialization of some parameters
1312         Object currentValues[] = new Object[2];
1313         PdfFont currentFont = null;
1314         float displacement = 0;
1315         PdfLine l;
1316         Float lastBaseFactor = new Float(0);
1317         currentValues[1] = lastBaseFactor;
1318         // looping over all the lines
1319         for (Iterator i = lines.iterator(); i.hasNext(); ) {
1320 
1321             // this is a line in the loop
1322             l = (PdfLine) i.next();
1323 
1324             float moveTextX = l.indentLeft() - indentLeft() + indentation.indentLeft + indentation.listIndentLeft + indentation.sectionIndentLeft;
1325             text.moveText(moveTextX, -l.height());
1326             // is the line preceded by a symbol?
1327             if (l.listSymbol() != null) {
1328                 ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(l.listSymbol()), text.getXTLM() - l.listIndent(), text.getYTLM(), 0);
1329             }
1330 
1331             currentValues[0] = currentFont;
1332 
1333             writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio());
1334 
1335             currentFont = (PdfFont)currentValues[0];
1336             displacement += l.height();
1337             text.moveText(-moveTextX, 0);
1338 
1339         }
1340         lines = new ArrayList();
1341         return displacement;
1342     }
1343 
1344     /** The characters to be applied the hanging punctuation. */
1345     static final String hangingPunctuation = ".,;:'";
1346 
1347     /**
1348      * Writes a text line to the document. It takes care of all the attributes.
1349      * <P>
1350      * Before entering the line position must have been established and the
1351      * <CODE>text</CODE> argument must be in text object scope (<CODE>beginText()</CODE>).
1352      * @param line the line to be written
1353      * @param text the <CODE>PdfContentByte</CODE> where the text will be written to
1354      * @param graphics the <CODE>PdfContentByte</CODE> where the graphics will be written to
1355      * @param currentValues the current font and extra spacing values
1356      * @param ratio
1357      * @throws DocumentException on error
1358      */
writeLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object currentValues[], float ratio)1359     void writeLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object currentValues[], float ratio)  throws DocumentException {
1360         PdfFont currentFont = (PdfFont)(currentValues[0]);
1361         float lastBaseFactor = ((Float)(currentValues[1])).floatValue();
1362         PdfChunk chunk;
1363         int numberOfSpaces;
1364         int lineLen;
1365         boolean isJustified;
1366         float hangingCorrection = 0;
1367         float hScale = 1;
1368         float lastHScale = Float.NaN;
1369         float baseWordSpacing = 0;
1370         float baseCharacterSpacing = 0;
1371         float glueWidth = 0;
1372 
1373         numberOfSpaces = line.numberOfSpaces();
1374         lineLen = line.GetLineLengthUtf32();
1375         // does the line need to be justified?
1376         isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1);
1377         int separatorCount = line.getSeparatorCount();
1378         if (separatorCount > 0) {
1379         	glueWidth = line.widthLeft() / separatorCount;
1380         }
1381         else if (isJustified) {
1382             if (line.isNewlineSplit() && line.widthLeft() >= (lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1))) {
1383                 if (line.isRTL()) {
1384                     text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0);
1385                 }
1386                 baseWordSpacing = ratio * lastBaseFactor;
1387                 baseCharacterSpacing = lastBaseFactor;
1388             }
1389             else {
1390                 float width = line.widthLeft();
1391                 PdfChunk last = line.getChunk(line.size() - 1);
1392                 if (last != null) {
1393                     String s = last.toString();
1394                     char c;
1395                     if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) {
1396                         float oldWidth = width;
1397                         width += last.font().width(c) * 0.4f;
1398                         hangingCorrection = width - oldWidth;
1399                     }
1400                 }
1401                 float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1);
1402                 baseWordSpacing = ratio * baseFactor;
1403                 baseCharacterSpacing = baseFactor;
1404                 lastBaseFactor = baseFactor;
1405             }
1406         }
1407 
1408         int lastChunkStroke = line.getLastStrokeChunk();
1409         int chunkStrokeIdx = 0;
1410         float xMarker = text.getXTLM();
1411         float baseXMarker = xMarker;
1412         float yMarker = text.getYTLM();
1413         boolean adjustMatrix = false;
1414         float tabPosition = 0;
1415 
1416         // looping over all the chunks in 1 line
1417         for (Iterator j = line.iterator(); j.hasNext(); ) {
1418             chunk = (PdfChunk) j.next();
1419             Color color = chunk.color();
1420             hScale = 1;
1421 
1422             if (chunkStrokeIdx <= lastChunkStroke) {
1423                 float width;
1424                 if (isJustified) {
1425                     width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing);
1426                 }
1427                 else {
1428                     width = chunk.width();
1429                 }
1430                 if (chunk.isStroked()) {
1431                     PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1);
1432                     if (chunk.isSeparator()) {
1433                     	width = glueWidth;
1434                     	Object[] sep = (Object[])chunk.getAttribute(Chunk.SEPARATOR);
1435                         DrawInterface di = (DrawInterface)sep[0];
1436                         Boolean vertical = (Boolean)sep[1];
1437                         float fontSize = chunk.font().size();
1438                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1439                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1440                         if (vertical.booleanValue()) {
1441                         	di.draw(graphics, baseXMarker, yMarker + descender, baseXMarker + line.getOriginalWidth(), ascender - descender, yMarker);
1442                         }
1443                         else {
1444                         	di.draw(graphics, xMarker, yMarker + descender, xMarker + width, ascender - descender, yMarker);
1445                         }
1446                     }
1447                     if (chunk.isTab()) {
1448                     	Object[] tab = (Object[])chunk.getAttribute(Chunk.TAB);
1449                         DrawInterface di = (DrawInterface)tab[0];
1450                         tabPosition = ((Float)tab[1]).floatValue() + ((Float)tab[3]).floatValue();
1451                         float fontSize = chunk.font().size();
1452                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1453                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1454                         if (tabPosition > xMarker) {
1455                         	di.draw(graphics, xMarker, yMarker + descender, tabPosition, ascender - descender, yMarker);
1456                         }
1457                         float tmp = xMarker;
1458                     	xMarker = tabPosition;
1459                     	tabPosition = tmp;
1460                     }
1461                     if (chunk.isAttribute(Chunk.BACKGROUND)) {
1462                         float subtract = lastBaseFactor;
1463                         if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND))
1464                             subtract = 0;
1465                         if (nextChunk == null)
1466                             subtract += hangingCorrection;
1467                         float fontSize = chunk.font().size();
1468                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1469                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1470                         Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND);
1471                         graphics.setColorFill((Color)bgr[0]);
1472                         float extra[] = (float[])bgr[1];
1473                         graphics.rectangle(xMarker - extra[0],
1474                             yMarker + descender - extra[1] + chunk.getTextRise(),
1475                             width - subtract + extra[0] + extra[2],
1476                             ascender - descender + extra[1] + extra[3]);
1477                         graphics.fill();
1478                         graphics.setGrayFill(0);
1479                     }
1480                     if (chunk.isAttribute(Chunk.UNDERLINE)) {
1481                         float subtract = lastBaseFactor;
1482                         if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE))
1483                             subtract = 0;
1484                         if (nextChunk == null)
1485                             subtract += hangingCorrection;
1486                         Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE);
1487                         Color scolor = null;
1488                         for (int k = 0; k < unders.length; ++k) {
1489                             Object obj[] = unders[k];
1490                             scolor = (Color)obj[0];
1491                             float ps[] = (float[])obj[1];
1492                             if (scolor == null)
1493                                 scolor = color;
1494                             if (scolor != null)
1495                                 graphics.setColorStroke(scolor);
1496                             float fsize = chunk.font().size();
1497                             graphics.setLineWidth(ps[0] + fsize * ps[1]);
1498                             float shift = ps[2] + fsize * ps[3];
1499                             int cap2 = (int)ps[4];
1500                             if (cap2 != 0)
1501                                 graphics.setLineCap(cap2);
1502                             graphics.moveTo(xMarker, yMarker + shift);
1503                             graphics.lineTo(xMarker + width - subtract, yMarker + shift);
1504                             graphics.stroke();
1505                             if (scolor != null)
1506                                 graphics.resetGrayStroke();
1507                             if (cap2 != 0)
1508                                 graphics.setLineCap(0);
1509                         }
1510                         graphics.setLineWidth(1);
1511                     }
1512                     if (chunk.isAttribute(Chunk.ACTION)) {
1513                         float subtract = lastBaseFactor;
1514                         if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION))
1515                             subtract = 0;
1516                         if (nextChunk == null)
1517                             subtract += hangingCorrection;
1518                         text.addAnnotation(new PdfAnnotation(writer, xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size(), (PdfAction)chunk.getAttribute(Chunk.ACTION)));
1519                     }
1520                     if (chunk.isAttribute(Chunk.REMOTEGOTO)) {
1521                         float subtract = lastBaseFactor;
1522                         if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO))
1523                             subtract = 0;
1524                         if (nextChunk == null)
1525                             subtract += hangingCorrection;
1526                         Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO);
1527                         String filename = (String)obj[0];
1528                         if (obj[1] instanceof String)
1529                             remoteGoto(filename, (String)obj[1], xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1530                         else
1531                             remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1532                     }
1533                     if (chunk.isAttribute(Chunk.LOCALGOTO)) {
1534                         float subtract = lastBaseFactor;
1535                         if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO))
1536                             subtract = 0;
1537                         if (nextChunk == null)
1538                             subtract += hangingCorrection;
1539                         localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1540                     }
1541                     if (chunk.isAttribute(Chunk.LOCALDESTINATION)) {
1542                         float subtract = lastBaseFactor;
1543                         if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION))
1544                             subtract = 0;
1545                         if (nextChunk == null)
1546                             subtract += hangingCorrection;
1547                         localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + chunk.font().size(), 0));
1548                     }
1549                     if (chunk.isAttribute(Chunk.GENERICTAG)) {
1550                         float subtract = lastBaseFactor;
1551                         if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG))
1552                             subtract = 0;
1553                         if (nextChunk == null)
1554                             subtract += hangingCorrection;
1555                         Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
1556                         PdfPageEvent pev = writer.getPageEvent();
1557                         if (pev != null)
1558                             pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG));
1559                     }
1560                     if (chunk.isAttribute(Chunk.PDFANNOTATION)) {
1561                         float subtract = lastBaseFactor;
1562                         if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION))
1563                             subtract = 0;
1564                         if (nextChunk == null)
1565                             subtract += hangingCorrection;
1566                         float fontSize = chunk.font().size();
1567                         float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
1568                         float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
1569                         PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION));
1570                         annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender));
1571                         text.addAnnotation(annot);
1572                     }
1573                     float params[] = (float[])chunk.getAttribute(Chunk.SKEW);
1574                     Float hs = (Float)chunk.getAttribute(Chunk.HSCALE);
1575                     if (params != null || hs != null) {
1576                         float b = 0, c = 0;
1577                         if (params != null) {
1578                             b = params[0];
1579                             c = params[1];
1580                         }
1581                         if (hs != null)
1582                             hScale = hs.floatValue();
1583                         text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker);
1584                     }
1585                     if (chunk.isAttribute(Chunk.CHAR_SPACING)) {
1586                     	Float cs = (Float) chunk.getAttribute(Chunk.CHAR_SPACING);
1587 						text.setCharacterSpacing(cs.floatValue());
1588 					}
1589                     if (chunk.isImage()) {
1590                         Image image = chunk.getImage();
1591                         float matrix[] = image.matrix();
1592                         matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX];
1593                         matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY];
1594                         graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
1595                         text.moveText(xMarker + lastBaseFactor + image.getScaledWidth() - text.getXTLM(), 0);
1596                     }
1597                 }
1598                 xMarker += width;
1599                 ++chunkStrokeIdx;
1600             }
1601 
1602             if (chunk.font().compareTo(currentFont) != 0) {
1603                 currentFont = chunk.font();
1604                 text.setFontAndSize(currentFont.getFont(), currentFont.size());
1605             }
1606             float rise = 0;
1607             Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE);
1608             int tr = 0;
1609             float strokeWidth = 1;
1610             Color strokeColor = null;
1611             Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT);
1612             if (textRender != null) {
1613                 tr = ((Integer)textRender[0]).intValue() & 3;
1614                 if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
1615                     text.setTextRenderingMode(tr);
1616                 if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) {
1617                     strokeWidth = ((Float)textRender[1]).floatValue();
1618                     if (strokeWidth != 1)
1619                         text.setLineWidth(strokeWidth);
1620                     strokeColor = (Color)textRender[2];
1621                     if (strokeColor == null)
1622                         strokeColor = color;
1623                     if (strokeColor != null)
1624                         text.setColorStroke(strokeColor);
1625                 }
1626             }
1627             if (fr != null)
1628                 rise = fr.floatValue();
1629             if (color != null)
1630                 text.setColorFill(color);
1631             if (rise != 0)
1632                 text.setTextRise(rise);
1633             if (chunk.isImage()) {
1634                 adjustMatrix = true;
1635             }
1636             else if (chunk.isHorizontalSeparator()) {
1637             	PdfTextArray array = new PdfTextArray();
1638             	array.add(-glueWidth * 1000f / chunk.font.size() / hScale);
1639             	text.showText(array);
1640             }
1641             else if (chunk.isTab()) {
1642             	PdfTextArray array = new PdfTextArray();
1643             	array.add((tabPosition - xMarker) * 1000f / chunk.font.size() / hScale);
1644             	text.showText(array);
1645             }
1646             // If it is a CJK chunk or Unicode TTF we will have to simulate the
1647             // space adjustment.
1648             else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) {
1649                 if (hScale != lastHScale) {
1650                     lastHScale = hScale;
1651                     text.setWordSpacing(baseWordSpacing / hScale);
1652                     text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing());
1653                 }
1654                 String s = chunk.toString();
1655                 int idx = s.indexOf(' ');
1656                 if (idx < 0)
1657                     text.showText(s);
1658                 else {
1659                     float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale;
1660                     PdfTextArray textArray = new PdfTextArray(s.substring(0, idx));
1661                     int lastIdx = idx;
1662                     while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) {
1663                         textArray.add(spaceCorrection);
1664                         textArray.add(s.substring(lastIdx, idx));
1665                         lastIdx = idx;
1666                     }
1667                     textArray.add(spaceCorrection);
1668                     textArray.add(s.substring(lastIdx));
1669                     text.showText(textArray);
1670                 }
1671             }
1672             else {
1673                 if (isJustified && hScale != lastHScale) {
1674                     lastHScale = hScale;
1675                     text.setWordSpacing(baseWordSpacing / hScale);
1676                     text.setCharacterSpacing(baseCharacterSpacing / hScale + text.getCharacterSpacing());
1677                 }
1678                 text.showText(chunk.toString());
1679             }
1680 
1681             if (rise != 0)
1682                 text.setTextRise(0);
1683             if (color != null)
1684                 text.resetRGBColorFill();
1685             if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
1686                 text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
1687             if (strokeColor != null)
1688                 text.resetRGBColorStroke();
1689             if (strokeWidth != 1)
1690                 text.setLineWidth(1);
1691             if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) {
1692                 adjustMatrix = true;
1693                 text.setTextMatrix(xMarker, yMarker);
1694             }
1695             if (chunk.isAttribute(Chunk.CHAR_SPACING)) {
1696 				text.setCharacterSpacing(baseCharacterSpacing);
1697             }
1698         }
1699         if (isJustified) {
1700             text.setWordSpacing(0);
1701             text.setCharacterSpacing(0);
1702             if (line.isNewlineSplit())
1703                 lastBaseFactor = 0;
1704         }
1705         if (adjustMatrix)
1706             text.moveText(baseXMarker - text.getXTLM(), 0);
1707         currentValues[0] = currentFont;
1708         currentValues[1] = new Float(lastBaseFactor);
1709     }
1710 
1711     protected Indentation indentation = new Indentation();
1712 
1713     /**
1714      * @since	2.0.8 (PdfDocument was package-private before)
1715      */
1716     public static class Indentation {
1717 
1718         /** This represents the current indentation of the PDF Elements on the left side. */
1719         float indentLeft = 0;
1720 
1721         /** Indentation to the left caused by a section. */
1722         float sectionIndentLeft = 0;
1723 
1724         /** This represents the current indentation of the PDF Elements on the left side. */
1725         float listIndentLeft = 0;
1726 
1727         /** This is the indentation caused by an image on the left. */
1728         float imageIndentLeft = 0;
1729 
1730         /** This represents the current indentation of the PDF Elements on the right side. */
1731         float indentRight = 0;
1732 
1733         /** Indentation to the right caused by a section. */
1734         float sectionIndentRight = 0;
1735 
1736         /** This is the indentation caused by an image on the right. */
1737         float imageIndentRight = 0;
1738 
1739         /** This represents the current indentation of the PDF Elements on the top side. */
1740         float indentTop = 0;
1741 
1742         /** This represents the current indentation of the PDF Elements on the bottom side. */
1743         float indentBottom = 0;
1744     }
1745 
1746     /**
1747      * Gets the indentation on the left side.
1748      *
1749      * @return	a margin
1750      */
1751 
indentLeft()1752     protected float indentLeft() {
1753         return left(indentation.indentLeft + indentation.listIndentLeft + indentation.imageIndentLeft + indentation.sectionIndentLeft);
1754     }
1755 
1756     /**
1757      * Gets the indentation on the right side.
1758      *
1759      * @return	a margin
1760      */
1761 
indentRight()1762     protected float indentRight() {
1763         return right(indentation.indentRight + indentation.sectionIndentRight + indentation.imageIndentRight);
1764     }
1765 
1766     /**
1767      * Gets the indentation on the top side.
1768      *
1769      * @return	a margin
1770      */
1771 
indentTop()1772     protected float indentTop() {
1773         return top(indentation.indentTop);
1774     }
1775 
1776     /**
1777      * Gets the indentation on the bottom side.
1778      *
1779      * @return	a margin
1780      */
1781 
indentBottom()1782     float indentBottom() {
1783         return bottom(indentation.indentBottom);
1784     }
1785 
1786     /**
1787      * Adds extra space.
1788      * This method should probably be rewritten.
1789      */
addSpacing(float extraspace, float oldleading, Font f)1790     protected void addSpacing(float extraspace, float oldleading, Font f) {
1791     	if (extraspace == 0) return;
1792     	if (pageEmpty) return;
1793     	if (currentHeight + line.height() + leading > indentTop() - indentBottom()) return;
1794         leading = extraspace;
1795         carriageReturn();
1796         if (f.isUnderlined() || f.isStrikethru()) {
1797             f = new Font(f);
1798             int style = f.getStyle();
1799             style &= ~Font.UNDERLINE;
1800             style &= ~Font.STRIKETHRU;
1801             f.setStyle(style);
1802         }
1803         Chunk space = new Chunk(" ", f);
1804         space.process(this);
1805         carriageReturn();
1806         leading = oldleading;
1807     }
1808 
1809 //	Info Dictionary and Catalog
1810 
1811     /** some meta information about the Document. */
1812     protected PdfInfo info = new PdfInfo();
1813 
1814     /**
1815      * Gets the <CODE>PdfInfo</CODE>-object.
1816      *
1817      * @return	<CODE>PdfInfo</COPE>
1818      */
1819 
getInfo()1820     PdfInfo getInfo() {
1821         return info;
1822     }
1823 
1824     /**
1825      * Gets the <CODE>PdfCatalog</CODE>-object.
1826      *
1827      * @param pages an indirect reference to this document pages
1828      * @return <CODE>PdfCatalog</CODE>
1829      */
1830 
getCatalog(PdfIndirectReference pages)1831     PdfCatalog getCatalog(PdfIndirectReference pages) {
1832         PdfCatalog catalog = new PdfCatalog(pages, writer);
1833 
1834         // [C1] outlines
1835         if (rootOutline.getKids().size() > 0) {
1836             catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES);
1837             catalog.put(PdfName.OUTLINES, rootOutline.indirectReference());
1838         }
1839 
1840         // [C2] version
1841         writer.getPdfVersion().addToCatalog(catalog);
1842 
1843         // [C3] preferences
1844         viewerPreferences.addToCatalog(catalog);
1845 
1846         // [C4] pagelabels
1847         if (pageLabels != null) {
1848             catalog.put(PdfName.PAGELABELS, pageLabels.getDictionary(writer));
1849         }
1850 
1851         // [C5] named objects
1852         catalog.addNames(localDestinations, getDocumentLevelJS(), documentFileAttachment, writer);
1853 
1854         // [C6] actions
1855         if (openActionName != null) {
1856             PdfAction action = getLocalGotoAction(openActionName);
1857             catalog.setOpenAction(action);
1858         }
1859         else if (openActionAction != null)
1860             catalog.setOpenAction(openActionAction);
1861         if (additionalActions != null)   {
1862             catalog.setAdditionalActions(additionalActions);
1863         }
1864 
1865         // [C7] portable collections
1866         if (collection != null) {
1867         	catalog.put(PdfName.COLLECTION, collection);
1868         }
1869 
1870         // [C8] AcroForm
1871         if (annotationsImp.hasValidAcroForm()) {
1872             try {
1873                 catalog.put(PdfName.ACROFORM, writer.addToBody(annotationsImp.getAcroForm()).getIndirectReference());
1874             }
1875             catch (IOException e) {
1876                 throw new ExceptionConverter(e);
1877             }
1878         }
1879 
1880         return catalog;
1881     }
1882 
1883 //	[C1] outlines
1884 
1885     /** This is the root outline of the document. */
1886     protected PdfOutline rootOutline;
1887 
1888     /** This is the current <CODE>PdfOutline</CODE> in the hierarchy of outlines. */
1889     protected PdfOutline currentOutline;
1890 
1891     /**
1892      * Adds a named outline to the document .
1893      * @param outline the outline to be added
1894      * @param name the name of this local destination
1895      */
addOutline(PdfOutline outline, String name)1896     void addOutline(PdfOutline outline, String name) {
1897         localDestination(name, outline.getPdfDestination());
1898     }
1899 
1900     /**
1901      * Gets the root outline. All the outlines must be created with a parent.
1902      * The first level is created with this outline.
1903      * @return the root outline
1904      */
getRootOutline()1905     public PdfOutline getRootOutline() {
1906         return rootOutline;
1907     }
1908 
1909 
1910     /**
1911      * Updates the count in the outlines.
1912      */
calculateOutlineCount()1913     void calculateOutlineCount() {
1914         if (rootOutline.getKids().size() == 0)
1915             return;
1916         traverseOutlineCount(rootOutline);
1917     }
1918 
1919     /**
1920      * Recursive method to update the count in the outlines.
1921      */
traverseOutlineCount(PdfOutline outline)1922     void traverseOutlineCount(PdfOutline outline) {
1923         ArrayList kids = outline.getKids();
1924         PdfOutline parent = outline.parent();
1925         if (kids.isEmpty()) {
1926             if (parent != null) {
1927                 parent.setCount(parent.getCount() + 1);
1928             }
1929         }
1930         else {
1931             for (int k = 0; k < kids.size(); ++k) {
1932                 traverseOutlineCount((PdfOutline)kids.get(k));
1933             }
1934             if (parent != null) {
1935                 if (outline.isOpen()) {
1936                     parent.setCount(outline.getCount() + parent.getCount() + 1);
1937                 }
1938                 else {
1939                     parent.setCount(parent.getCount() + 1);
1940                     outline.setCount(-outline.getCount());
1941                 }
1942             }
1943         }
1944     }
1945 
1946     /**
1947      * Writes the outline tree to the body of the PDF document.
1948      */
writeOutlines()1949     void writeOutlines() throws IOException {
1950         if (rootOutline.getKids().size() == 0)
1951             return;
1952         outlineTree(rootOutline);
1953         writer.addToBody(rootOutline, rootOutline.indirectReference());
1954     }
1955 
1956     /**
1957      * Recursive method used to write outlines.
1958      */
outlineTree(PdfOutline outline)1959     void outlineTree(PdfOutline outline) throws IOException {
1960         outline.setIndirectReference(writer.getPdfIndirectReference());
1961         if (outline.parent() != null)
1962             outline.put(PdfName.PARENT, outline.parent().indirectReference());
1963         ArrayList kids = outline.getKids();
1964         int size = kids.size();
1965         for (int k = 0; k < size; ++k)
1966             outlineTree((PdfOutline)kids.get(k));
1967         for (int k = 0; k < size; ++k) {
1968             if (k > 0)
1969                 ((PdfOutline)kids.get(k)).put(PdfName.PREV, ((PdfOutline)kids.get(k - 1)).indirectReference());
1970             if (k < size - 1)
1971                 ((PdfOutline)kids.get(k)).put(PdfName.NEXT, ((PdfOutline)kids.get(k + 1)).indirectReference());
1972         }
1973         if (size > 0) {
1974             outline.put(PdfName.FIRST, ((PdfOutline)kids.get(0)).indirectReference());
1975             outline.put(PdfName.LAST, ((PdfOutline)kids.get(size - 1)).indirectReference());
1976         }
1977         for (int k = 0; k < size; ++k) {
1978             PdfOutline kid = (PdfOutline)kids.get(k);
1979             writer.addToBody(kid, kid.indirectReference());
1980         }
1981     }
1982 
1983 //  [C3] PdfViewerPreferences interface
1984 
1985 	/** Contains the Viewer preferences of this PDF document. */
1986     protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
1987     /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */
setViewerPreferences(int preferences)1988     void setViewerPreferences(int preferences) {
1989         this.viewerPreferences.setViewerPreferences(preferences);
1990     }
1991 
1992     /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfObject) */
addViewerPreference(PdfName key, PdfObject value)1993     void addViewerPreference(PdfName key, PdfObject value) {
1994     	this.viewerPreferences.addViewerPreference(key, value);
1995     }
1996 
1997 //	[C4] Page labels
1998 
1999     protected PdfPageLabels pageLabels;
2000     /**
2001      * Sets the page labels
2002      * @param pageLabels the page labels
2003      */
setPageLabels(PdfPageLabels pageLabels)2004     void setPageLabels(PdfPageLabels pageLabels) {
2005         this.pageLabels = pageLabels;
2006     }
2007 
2008 //	[C5] named objects: local destinations, javascript, embedded files
2009 
2010     /**
2011      * Implements a link to other part of the document. The jump will
2012      * be made to a local destination with the same name, that must exist.
2013      * @param name the name for this link
2014      * @param llx the lower left x corner of the activation area
2015      * @param lly the lower left y corner of the activation area
2016      * @param urx the upper right x corner of the activation area
2017      * @param ury the upper right y corner of the activation area
2018      */
localGoto(String name, float llx, float lly, float urx, float ury)2019     void localGoto(String name, float llx, float lly, float urx, float ury) {
2020         PdfAction action = getLocalGotoAction(name);
2021         annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
2022     }
2023 
2024     /**
2025      * Implements a link to another document.
2026      * @param filename the filename for the remote document
2027      * @param name the name to jump to
2028      * @param llx the lower left x corner of the activation area
2029      * @param lly the lower left y corner of the activation area
2030      * @param urx the upper right x corner of the activation area
2031      * @param ury the upper right y corner of the activation area
2032      */
remoteGoto(String filename, String name, float llx, float lly, float urx, float ury)2033     void remoteGoto(String filename, String name, float llx, float lly, float urx, float ury) {
2034         annotationsImp.addPlainAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, name)));
2035     }
2036 
2037     /**
2038      * Implements a link to another document.
2039      * @param filename the filename for the remote document
2040      * @param page the page to jump to
2041      * @param llx the lower left x corner of the activation area
2042      * @param lly the lower left y corner of the activation area
2043      * @param urx the upper right x corner of the activation area
2044      * @param ury the upper right y corner of the activation area
2045      */
remoteGoto(String filename, int page, float llx, float lly, float urx, float ury)2046     void remoteGoto(String filename, int page, float llx, float lly, float urx, float ury) {
2047         addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, page)));
2048     }
2049 
2050     /** Implements an action in an area.
2051      * @param action the <CODE>PdfAction</CODE>
2052      * @param llx the lower left x corner of the activation area
2053      * @param lly the lower left y corner of the activation area
2054      * @param urx the upper right x corner of the activation area
2055      * @param ury the upper right y corner of the activation area
2056      */
setAction(PdfAction action, float llx, float lly, float urx, float ury)2057     void setAction(PdfAction action, float llx, float lly, float urx, float ury) {
2058         addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
2059     }
2060 
2061     /**
2062      * Stores the destinations keyed by name. Value is
2063      * <CODE>Object[]{PdfAction,PdfIndirectReference,PdfDestintion}</CODE>.
2064      */
2065     protected TreeMap localDestinations = new TreeMap();
2066 
getLocalGotoAction(String name)2067     PdfAction getLocalGotoAction(String name) {
2068         PdfAction action;
2069         Object obj[] = (Object[])localDestinations.get(name);
2070         if (obj == null)
2071             obj = new Object[3];
2072         if (obj[0] == null) {
2073             if (obj[1] == null) {
2074                 obj[1] = writer.getPdfIndirectReference();
2075             }
2076             action = new PdfAction((PdfIndirectReference)obj[1]);
2077             obj[0] = action;
2078             localDestinations.put(name, obj);
2079         }
2080         else {
2081             action = (PdfAction)obj[0];
2082         }
2083         return action;
2084     }
2085 
2086     /**
2087      * The local destination to where a local goto with the same
2088      * name will jump to.
2089      * @param name the name of this local destination
2090      * @param destination the <CODE>PdfDestination</CODE> with the jump coordinates
2091      * @return <CODE>true</CODE> if the local destination was added,
2092      * <CODE>false</CODE> if a local destination with the same name
2093      * already existed
2094      */
localDestination(String name, PdfDestination destination)2095     boolean localDestination(String name, PdfDestination destination) {
2096         Object obj[] = (Object[])localDestinations.get(name);
2097         if (obj == null)
2098             obj = new Object[3];
2099         if (obj[2] != null)
2100             return false;
2101         obj[2] = destination;
2102         localDestinations.put(name, obj);
2103         if (!destination.hasPage())
2104         	destination.addPage(writer.getCurrentPage());
2105         return true;
2106     }
2107 
2108     /**
2109      * Stores a list of document level JavaScript actions.
2110      */
2111     int jsCounter;
2112     protected HashMap documentLevelJS = new HashMap();
2113     protected static final DecimalFormat SIXTEEN_DIGITS = new DecimalFormat("0000000000000000");
addJavaScript(PdfAction js)2114     void addJavaScript(PdfAction js) {
2115         if (js.get(PdfName.JS) == null)
2116             throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed"));
2117         try {
2118             documentLevelJS.put(SIXTEEN_DIGITS.format(jsCounter++), writer.addToBody(js).getIndirectReference());
2119         }
2120         catch (IOException e) {
2121             throw new ExceptionConverter(e);
2122         }
2123     }
addJavaScript(String name, PdfAction js)2124     void addJavaScript(String name, PdfAction js) {
2125         if (js.get(PdfName.JS) == null)
2126             throw new RuntimeException(MessageLocalization.getComposedMessage("only.javascript.actions.are.allowed"));
2127         try {
2128             documentLevelJS.put(name, writer.addToBody(js).getIndirectReference());
2129         }
2130         catch (IOException e) {
2131             throw new ExceptionConverter(e);
2132         }
2133     }
2134 
getDocumentLevelJS()2135     HashMap getDocumentLevelJS() {
2136     	return documentLevelJS;
2137     }
2138 
2139     protected HashMap documentFileAttachment = new HashMap();
2140 
addFileAttachment(String description, PdfFileSpecification fs)2141     void addFileAttachment(String description, PdfFileSpecification fs) throws IOException {
2142         if (description == null) {
2143         	PdfString desc = (PdfString)fs.get(PdfName.DESC);
2144         	if (desc == null) {
2145         		description = "";
2146         	}
2147         	else {
2148         		description = PdfEncodings.convertToString(desc.getBytes(), null);
2149         	}
2150         }
2151         fs.addDescription(description, true);
2152         if (description.length() == 0)
2153             description = "Unnamed";
2154         String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null);
2155         int k = 0;
2156         while (documentFileAttachment.containsKey(fn)) {
2157             ++k;
2158             fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null);
2159         }
2160         documentFileAttachment.put(fn, fs.getReference());
2161     }
2162 
getDocumentFileAttachment()2163     HashMap getDocumentFileAttachment() {
2164         return documentFileAttachment;
2165     }
2166 
2167 //	[C6] document level actions
2168 
2169     protected String openActionName;
2170 
setOpenAction(String name)2171     void setOpenAction(String name) {
2172         openActionName = name;
2173         openActionAction = null;
2174     }
2175 
2176     protected PdfAction openActionAction;
setOpenAction(PdfAction action)2177     void setOpenAction(PdfAction action) {
2178         openActionAction = action;
2179         openActionName = null;
2180     }
2181 
2182     protected PdfDictionary additionalActions;
addAdditionalAction(PdfName actionType, PdfAction action)2183     void addAdditionalAction(PdfName actionType, PdfAction action)  {
2184         if (additionalActions == null)  {
2185             additionalActions = new PdfDictionary();
2186         }
2187         if (action == null)
2188             additionalActions.remove(actionType);
2189         else
2190             additionalActions.put(actionType, action);
2191         if (additionalActions.size() == 0)
2192             additionalActions = null;
2193     }
2194 
2195 //	[C7] portable collections
2196 
2197     protected PdfCollection collection;
2198 
2199     /**
2200      * Sets the collection dictionary.
2201      * @param collection a dictionary of type PdfCollection
2202      */
setCollection(PdfCollection collection)2203 	public void setCollection(PdfCollection collection) {
2204 		this.collection = collection;
2205 	}
2206 
2207 //	[C8] AcroForm
2208 
2209 	PdfAnnotationsImp annotationsImp;
2210 
2211     /**
2212      * Gets the AcroForm object.
2213      * @return the PdfAcroform object of the PdfDocument
2214      */
getAcroForm()2215     PdfAcroForm getAcroForm() {
2216         return annotationsImp.getAcroForm();
2217     }
2218 
setSigFlags(int f)2219     void setSigFlags(int f) {
2220         annotationsImp.setSigFlags(f);
2221     }
2222 
addCalculationOrder(PdfFormField formField)2223     void addCalculationOrder(PdfFormField formField) {
2224         annotationsImp.addCalculationOrder(formField);
2225     }
2226 
addAnnotation(PdfAnnotation annot)2227     void addAnnotation(PdfAnnotation annot) {
2228         pageEmpty = false;
2229         annotationsImp.addAnnotation(annot);
2230     }
2231 
2232 //	[F12] tagged PDF
2233 
2234     protected int markPoint;
2235 
getMarkPoint()2236 	int getMarkPoint() {
2237 	    return markPoint;
2238 	}
2239 
incMarkPoint()2240 	void incMarkPoint() {
2241 	    ++markPoint;
2242 	}
2243 
2244 //	[U1] page sizes
2245 
2246     /** This is the size of the next page. */
2247     protected Rectangle nextPageSize = null;
2248 
2249     /** This is the size of the several boxes of the current Page. */
2250     protected HashMap thisBoxSize = new HashMap();
2251 
2252     /** This is the size of the several boxes that will be used in
2253      * the next page. */
2254     protected HashMap boxSize = new HashMap();
2255 
setCropBoxSize(Rectangle crop)2256     void setCropBoxSize(Rectangle crop) {
2257         setBoxSize("crop", crop);
2258     }
2259 
setBoxSize(String boxName, Rectangle size)2260     void setBoxSize(String boxName, Rectangle size) {
2261         if (size == null)
2262             boxSize.remove(boxName);
2263         else
2264             boxSize.put(boxName, new PdfRectangle(size));
2265     }
2266 
setNewPageSizeAndMargins()2267     protected void setNewPageSizeAndMargins() {
2268         pageSize = nextPageSize;
2269     	if (marginMirroring && (getPageNumber() & 1) == 0) {
2270             marginRight = nextMarginLeft;
2271             marginLeft = nextMarginRight;
2272         }
2273         else {
2274             marginLeft = nextMarginLeft;
2275             marginRight = nextMarginRight;
2276         }
2277     	if (marginMirroringTopBottom && (getPageNumber() & 1) == 0) {
2278     		marginTop = nextMarginBottom;
2279     		marginBottom = nextMarginTop;
2280     	}
2281     	else {
2282     		marginTop = nextMarginTop;
2283     		marginBottom = nextMarginBottom;
2284     	}
2285     }
2286 
2287     /**
2288      * Gives the size of a trim, art, crop or bleed box, or null if not defined.
2289      * @param boxName crop, trim, art or bleed
2290      */
getBoxSize(String boxName)2291     Rectangle getBoxSize(String boxName) {
2292     	PdfRectangle r = (PdfRectangle)thisBoxSize.get(boxName);
2293     	if (r != null) return r.getRectangle();
2294     	return null;
2295     }
2296 
2297 //	[U2] empty pages
2298 
2299     /** This checks if the page is empty. */
2300     private boolean pageEmpty = true;
2301 
setPageEmpty(boolean pageEmpty)2302     void setPageEmpty(boolean pageEmpty) {
2303         this.pageEmpty = pageEmpty;
2304     }
2305 
isPageEmpty()2306     boolean isPageEmpty() {
2307         return writer == null || (writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || writer.isPaused()));
2308     }
2309 
2310 //	[U3] page actions
2311 
2312     /** The duration of the page */
2313     protected int duration=-1; // negative values will indicate no duration
2314 
2315     /** The page transition */
2316     protected PdfTransition transition=null;
2317 
2318     /**
2319      * Sets the display duration for the page (for presentations)
2320      * @param seconds   the number of seconds to display the page
2321      */
setDuration(int seconds)2322     void setDuration(int seconds) {
2323         if (seconds > 0)
2324             this.duration=seconds;
2325         else
2326             this.duration=-1;
2327     }
2328 
2329     /**
2330      * Sets the transition for the page
2331      * @param transition   the PdfTransition object
2332      */
setTransition(PdfTransition transition)2333     void setTransition(PdfTransition transition) {
2334         this.transition=transition;
2335     }
2336 
2337     protected PdfDictionary pageAA = null;
setPageAction(PdfName actionType, PdfAction action)2338     void setPageAction(PdfName actionType, PdfAction action) {
2339         if (pageAA == null) {
2340             pageAA = new PdfDictionary();
2341         }
2342         pageAA.put(actionType, action);
2343     }
2344 
2345 //	[U8] thumbnail images
2346 
2347     protected PdfIndirectReference thumb;
setThumbnail(Image image)2348     void setThumbnail(Image image) throws PdfException, DocumentException {
2349         thumb = writer.getImageReference(writer.addDirectImageSimple(image));
2350     }
2351 
2352 //	[M0] Page resources contain references to fonts, extgstate, images,...
2353 
2354     /** This are the page resources of the current Page. */
2355     protected PageResources pageResources;
2356 
getPageResources()2357     PageResources getPageResources() {
2358         return pageResources;
2359     }
2360 
2361 //	[M3] Images
2362 
2363     /** Holds value of property strictImageSequence. */
2364     protected boolean strictImageSequence = false;
2365 
2366     /** Getter for property strictImageSequence.
2367      * @return Value of property strictImageSequence.
2368      *
2369      */
isStrictImageSequence()2370     boolean isStrictImageSequence() {
2371         return this.strictImageSequence;
2372     }
2373 
2374     /** Setter for property strictImageSequence.
2375      * @param strictImageSequence New value of property strictImageSequence.
2376      *
2377      */
setStrictImageSequence(boolean strictImageSequence)2378     void setStrictImageSequence(boolean strictImageSequence) {
2379         this.strictImageSequence = strictImageSequence;
2380     }
2381 
2382     /** This is the position where the image ends. */
2383     protected float imageEnd = -1;
2384 
2385 	/**
2386 	 * Method added by Pelikan Stephan
2387 	 */
clearTextWrap()2388 	public void clearTextWrap() {
2389 		float tmpHeight = imageEnd - currentHeight;
2390 		if (line != null) {
2391 			tmpHeight += line.height();
2392 		}
2393 		if ((imageEnd > -1) && (tmpHeight > 0)) {
2394 			carriageReturn();
2395 			currentHeight += tmpHeight;
2396 		}
2397 	}
2398 
2399     /** This is the image that could not be shown on a previous page. */
2400     protected Image imageWait = null;
2401 
2402     /**
2403      * Adds an image to the document.
2404      * @param image the <CODE>Image</CODE> to add
2405      * @throws PdfException on error
2406      * @throws DocumentException on error
2407      */
2408 
add(Image image)2409     protected void add(Image image) throws PdfException, DocumentException {
2410 
2411         if (image.hasAbsoluteY()) {
2412             graphics.addImage(image);
2413             pageEmpty = false;
2414             return;
2415         }
2416 
2417         // if there isn't enough room for the image on this page, save it for the next page
2418         if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
2419             if (!strictImageSequence && imageWait == null) {
2420                 imageWait = image;
2421                 return;
2422             }
2423             newPage();
2424             if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
2425                 imageWait = image;
2426                 return;
2427             }
2428         }
2429         pageEmpty = false;
2430         // avoid endless loops
2431         if (image == imageWait)
2432             imageWait = null;
2433         boolean textwrap = (image.getAlignment() & Image.TEXTWRAP) == Image.TEXTWRAP
2434         && !((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE);
2435         boolean underlying = (image.getAlignment() & Image.UNDERLYING) == Image.UNDERLYING;
2436         float diff = leading / 2;
2437         if (textwrap) {
2438             diff += leading;
2439         }
2440         float lowerleft = indentTop() - currentHeight - image.getScaledHeight() -diff;
2441         float mt[] = image.matrix();
2442         float startPosition = indentLeft() - mt[4];
2443         if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.getScaledWidth() - mt[4];
2444         if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + ((indentRight() - indentLeft() - image.getScaledWidth()) / 2) - mt[4];
2445         if (image.hasAbsoluteX()) startPosition = image.getAbsoluteX();
2446         if (textwrap) {
2447             if (imageEnd < 0 || imageEnd < currentHeight + image.getScaledHeight() + diff) {
2448                 imageEnd = currentHeight + image.getScaledHeight() + diff;
2449             }
2450             if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) {
2451             	// indentation suggested by Pelikan Stephan
2452             	indentation.imageIndentRight += image.getScaledWidth() + image.getIndentationLeft();
2453             }
2454             else {
2455             	// indentation suggested by Pelikan Stephan
2456             	indentation.imageIndentLeft += image.getScaledWidth() + image.getIndentationRight();
2457             }
2458         }
2459         else {
2460         	if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.getIndentationRight();
2461         	else if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.getIndentationLeft() - image.getIndentationRight();
2462         	else startPosition += image.getIndentationLeft();
2463         }
2464         graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]);
2465         if (!(textwrap || underlying)) {
2466             currentHeight += image.getScaledHeight() + diff;
2467             flushLines();
2468             text.moveText(0, - (image.getScaledHeight() + diff));
2469             newLine();
2470         }
2471     }
2472 
2473 //	[M4] Adding a PdfPTable
2474 
2475     /** Adds a <CODE>PdfPTable</CODE> to the document.
2476      * @param ptable the <CODE>PdfPTable</CODE> to be added to the document.
2477      * @throws DocumentException on error
2478      */
addPTable(PdfPTable ptable)2479     void addPTable(PdfPTable ptable) throws DocumentException {
2480         ColumnText ct = new ColumnText(writer.getDirectContent());
2481         // if the table prefers to be on a single page, and it wouldn't
2482         //fit on the current page, start a new page.
2483         if (ptable.getKeepTogether() && !fitsPage(ptable, 0f) && currentHeight > 0)  {
2484         	newPage();
2485         }
2486         // add dummy paragraph if we aren't at the top of a page, so that
2487         // spacingBefore will be taken into account by ColumnText
2488         if (currentHeight > 0) {
2489 	        Paragraph p = new Paragraph();
2490 	        p.setLeading(0);
2491 	        ct.addElement(p);
2492         }
2493         ct.addElement(ptable);
2494         boolean he = ptable.isHeadersInEvent();
2495         ptable.setHeadersInEvent(true);
2496         int loop = 0;
2497         while (true) {
2498             ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight);
2499             int status = ct.go();
2500             if ((status & ColumnText.NO_MORE_TEXT) != 0) {
2501                 text.moveText(0, ct.getYLine() - indentTop() + currentHeight);
2502                 currentHeight = indentTop() - ct.getYLine();
2503                 break;
2504             }
2505             if (indentTop() - currentHeight == ct.getYLine())
2506                 ++loop;
2507             else
2508                 loop = 0;
2509             if (loop == 3) {
2510                 add(new Paragraph("ERROR: Infinite table loop"));
2511                 break;
2512             }
2513             newPage();
2514         }
2515         ptable.setHeadersInEvent(he);
2516     }
2517 
2518     /**
2519      * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
2520      *
2521      * @param	table	the table that has to be checked
2522      * @param	margin	a certain margin
2523      * @return	<CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise.
2524      */
2525 
fitsPage(PdfPTable table, float margin)2526     boolean fitsPage(PdfPTable table, float margin) {
2527     	if (!table.isLockedWidth()) {
2528     		float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100;
2529     		table.setTotalWidth(totalWidth);
2530     	}
2531         // ensuring that a new line has been started.
2532         ensureNewLine();
2533         return table.getTotalHeight() + ((currentHeight > 0) ? table.spacingBefore() : 0f)
2534         	<= indentTop() - currentHeight - indentBottom() - margin;
2535     }
2536 
2537 //	[M4'] Adding a Table
2538 
2539 	/**
2540 	 * This is a helper class for adding a Table to a document.
2541 	 * @since	2.0.8 (PdfDocument was package-private before)
2542 	 */
2543     protected static class RenderingContext {
2544         float pagetop = -1;
2545         float oldHeight = -1;
2546 
2547         PdfContentByte cellGraphics = null;
2548 
2549         float lostTableBottom;
2550 
2551         float maxCellBottom;
2552         float maxCellHeight;
2553 
2554         Map rowspanMap;
2555         Map pageMap = new HashMap();
2556         /**
2557          * A PdfPTable
2558          */
2559         public PdfTable table;
2560 
2561         /**
2562          * Consumes the rowspan
2563          * @param c
2564          * @return a rowspan.
2565          */
consumeRowspan(PdfCell c)2566         public int consumeRowspan(PdfCell c) {
2567             if (c.rowspan() == 1) {
2568                 return 1;
2569             }
2570 
2571             Integer i = (Integer) rowspanMap.get(c);
2572             if (i == null) {
2573                 i = new Integer(c.rowspan());
2574             }
2575 
2576             i = new Integer(i.intValue() - 1);
2577             rowspanMap.put(c, i);
2578 
2579             if (i.intValue() < 1) {
2580                 return 1;
2581             }
2582             return i.intValue();
2583         }
2584 
2585         /**
2586          * Looks at the current rowspan.
2587          * @param c
2588          * @return the current rowspan
2589          */
currentRowspan(PdfCell c)2590         public int currentRowspan(PdfCell c) {
2591             Integer i = (Integer) rowspanMap.get(c);
2592             if (i == null) {
2593                 return c.rowspan();
2594             } else {
2595                 return i.intValue();
2596             }
2597         }
2598 
cellRendered(PdfCell cell, int pageNumber)2599         public int cellRendered(PdfCell cell, int pageNumber) {
2600             Integer i = (Integer) pageMap.get(cell);
2601             if (i == null) {
2602                 i = new Integer(1);
2603             } else {
2604                 i = new Integer(i.intValue() + 1);
2605             }
2606             pageMap.put(cell, i);
2607 
2608             Integer pageInteger = new Integer(pageNumber);
2609             Set set = (Set) pageMap.get(pageInteger);
2610 
2611             if (set == null) {
2612                 set = new HashSet();
2613                 pageMap.put(pageInteger, set);
2614             }
2615 
2616             set.add(cell);
2617 
2618             return i.intValue();
2619         }
2620 
numCellRendered(PdfCell cell)2621         public int numCellRendered(PdfCell cell) {
2622             Integer i = (Integer) pageMap.get(cell);
2623             if (i == null) {
2624                 i = new Integer(0);
2625             }
2626             return i.intValue();
2627         }
2628 
isCellRenderedOnPage(PdfCell cell, int pageNumber)2629         public boolean isCellRenderedOnPage(PdfCell cell, int pageNumber) {
2630             Integer pageInteger = new Integer(pageNumber);
2631             Set set = (Set) pageMap.get(pageInteger);
2632 
2633             if (set != null) {
2634                 return set.contains(cell);
2635             }
2636 
2637             return false;
2638         }
2639     };
2640 
2641 	/**
2642 	 * Adds a new table to the document.
2643 	 * @param t				Table to add.  Rendered rows will be deleted after processing.
2644 	 * @throws DocumentException
2645 	 * @since	iText 2.0.8
2646 	 */
addPdfTable(Table t)2647     private void addPdfTable(Table t) throws DocumentException {
2648         // before every table, we flush all lines
2649         flushLines();
2650 
2651     	PdfTable table = new PdfTable(t, indentLeft(), indentRight(), indentTop() - currentHeight);
2652         RenderingContext ctx = new RenderingContext();
2653         ctx.pagetop = indentTop();
2654         ctx.oldHeight = currentHeight;
2655         ctx.cellGraphics = new PdfContentByte(writer);
2656         ctx.rowspanMap = new HashMap();
2657         ctx.table = table;
2658 
2659 		// initialization of parameters
2660 		PdfCell cell;
2661 
2662 		// drawing the table
2663 		ArrayList headercells = table.getHeaderCells();
2664         ArrayList cells = table.getCells();
2665         ArrayList rows = extractRows(cells, ctx);
2666         boolean isContinue = false;
2667 		while (!cells.isEmpty()) {
2668 			// initialization of some extra parameters;
2669 			ctx.lostTableBottom = 0;
2670 
2671 			// loop over the cells
2672 			boolean cellsShown = false;
2673 
2674             // draw the cells (line by line)
2675             Iterator iterator = rows.iterator();
2676 
2677             boolean atLeastOneFits = false;
2678             while (iterator.hasNext()) {
2679                 ArrayList row = (ArrayList) iterator.next();
2680                 analyzeRow(rows, ctx);
2681                 renderCells(ctx, row, table.hasToFitPageCells() & atLeastOneFits);
2682 
2683                 if (!mayBeRemoved(row)) {
2684                     break;
2685                 }
2686                 consumeRowspan(row, ctx);
2687                 iterator.remove();
2688                 atLeastOneFits = true;
2689             }
2690 
2691 //          compose cells array list for subsequent code
2692             cells.clear();
2693             Set opt = new HashSet();
2694             iterator = rows.iterator();
2695             while (iterator.hasNext()) {
2696                 ArrayList row = (ArrayList) iterator.next();
2697 
2698                 Iterator cellIterator = row.iterator();
2699                 while (cellIterator.hasNext()) {
2700                     cell = (PdfCell) cellIterator.next();
2701 
2702                     if (!opt.contains(cell)) {
2703                         cells.add(cell);
2704                         opt.add(cell);
2705                     }
2706                 }
2707             }
2708 
2709 			// we paint the graphics of the table after looping through all the cells
2710 			Rectangle tablerec = new Rectangle(table);
2711 			tablerec.setBorder(table.getBorder());
2712 			tablerec.setBorderWidth(table.getBorderWidth());
2713 			tablerec.setBorderColor(table.getBorderColor());
2714 			tablerec.setBackgroundColor(table.getBackgroundColor());
2715 			PdfContentByte under = writer.getDirectContentUnder();
2716 			under.rectangle(tablerec.rectangle(top(), indentBottom()));
2717 			under.add(ctx.cellGraphics);
2718 			// bugfix by Gerald Fehringer: now again add the border for the table
2719 			// since it might have been covered by cell backgrounds
2720 			tablerec.setBackgroundColor(null);
2721 			tablerec = tablerec.rectangle(top(), indentBottom());
2722 			tablerec.setBorder(table.getBorder());
2723 			under.rectangle(tablerec);
2724 			// end bugfix
2725 
2726             ctx.cellGraphics = new PdfContentByte(null);
2727 			// if the table continues on the next page
2728 
2729 			if (!rows.isEmpty()) {
2730 				isContinue = true;
2731 				graphics.setLineWidth(table.getBorderWidth());
2732 				if (cellsShown && (table.getBorder() & Rectangle.BOTTOM) == Rectangle.BOTTOM) {
2733 					// Draw the bottom line
2734 
2735 					// the color is set to the color of the element
2736 					Color tColor = table.getBorderColor();
2737 					if (tColor != null) {
2738 						graphics.setColorStroke(tColor);
2739 					}
2740 					graphics.moveTo(table.getLeft(), Math.max(table.getBottom(), indentBottom()));
2741 					graphics.lineTo(table.getRight(), Math.max(table.getBottom(), indentBottom()));
2742 					graphics.stroke();
2743 					if (tColor != null) {
2744 						graphics.resetRGBColorStroke();
2745 					}
2746 				}
2747 
2748 				// old page
2749 				pageEmpty = false;
2750                 float difference = ctx.lostTableBottom;
2751 
2752 				// new page
2753 				newPage();
2754 
2755 				// G.F.: if something added in page event i.e. currentHeight > 0
2756 				float heightCorrection = 0;
2757 				boolean somethingAdded = false;
2758 				if (currentHeight > 0) {
2759 					heightCorrection = 6;
2760 					currentHeight += heightCorrection;
2761 					somethingAdded = true;
2762 					newLine();
2763 					flushLines();
2764 					indentation.indentTop = currentHeight - leading;
2765 					currentHeight = 0;
2766 				}
2767 				else {
2768                     flushLines();
2769 				}
2770 
2771 				// this part repeats the table headers (if any)
2772 				int size = headercells.size();
2773 				if (size > 0) {
2774 					// this is the top of the headersection
2775 					cell = (PdfCell) headercells.get(0);
2776 					float oldTop = cell.getTop(0);
2777 					// loop over all the cells of the table header
2778 					for (int i = 0; i < size; i++) {
2779 						cell = (PdfCell) headercells.get(i);
2780 						// calculation of the new cellpositions
2781 						cell.setTop(indentTop() - oldTop + cell.getTop(0));
2782 						cell.setBottom(indentTop() - oldTop + cell.getBottom(0));
2783 						ctx.pagetop = cell.getBottom();
2784 						// we paint the borders of the cell
2785 						ctx.cellGraphics.rectangle(cell.rectangle(indentTop(), indentBottom()));
2786 						// we write the text of the cell
2787 						ArrayList images = cell.getImages(indentTop(), indentBottom());
2788 						for (Iterator im = images.iterator(); im.hasNext();) {
2789 							cellsShown = true;
2790 							Image image = (Image) im.next();
2791 							graphics.addImage(image);
2792 						}
2793 						lines = cell.getLines(indentTop(), indentBottom());
2794 						float cellTop = cell.getTop(indentTop());
2795 						text.moveText(0, cellTop-heightCorrection);
2796 						float cellDisplacement = flushLines() - cellTop+heightCorrection;
2797 						text.moveText(0, cellDisplacement);
2798 					}
2799 
2800 					currentHeight = indentTop() - ctx.pagetop + table.cellspacing();
2801 					text.moveText(0, ctx.pagetop - indentTop() - currentHeight);
2802 				}
2803 				else {
2804 					if (somethingAdded) {
2805 						ctx.pagetop = indentTop();
2806 						text.moveText(0, -table.cellspacing());
2807 					}
2808 				}
2809 				ctx.oldHeight = currentHeight - heightCorrection;
2810 
2811 				// calculating the new positions of the table and the cells
2812 				size = Math.min(cells.size(), table.columns());
2813 				int i = 0;
2814 				while (i < size) {
2815 					cell = (PdfCell) cells.get(i);
2816 					if (cell.getTop(-table.cellspacing()) > ctx.lostTableBottom) {
2817 						float newBottom = ctx.pagetop - difference + cell.getBottom();
2818 						float neededHeight = cell.remainingHeight();
2819 						if (newBottom > ctx.pagetop - neededHeight) {
2820 							difference += newBottom - (ctx.pagetop - neededHeight);
2821 						}
2822 					}
2823 					i++;
2824 				}
2825 				size = cells.size();
2826 				table.setTop(indentTop());
2827 				table.setBottom(ctx.pagetop - difference + table.getBottom(table.cellspacing()));
2828 				for (i = 0; i < size; i++) {
2829 					cell = (PdfCell) cells.get(i);
2830 					float newBottom = ctx.pagetop - difference + cell.getBottom();
2831 					float newTop = ctx.pagetop - difference + cell.getTop(-table.cellspacing());
2832 					if (newTop > indentTop() - currentHeight) {
2833 						newTop = indentTop() - currentHeight;
2834 					}
2835 
2836 					cell.setTop(newTop );
2837 					cell.setBottom(newBottom );
2838 				}
2839 			}
2840 		}
2841 
2842         float tableHeight = table.getTop() - table.getBottom();
2843         // bugfix by Adauto Martins when have more than two tables and more than one page
2844         // If continuation of table in other page (bug report #1460051)
2845         if (isContinue) {
2846         	currentHeight = tableHeight;
2847         	text.moveText(0, -(tableHeight - (ctx.oldHeight * 2)));
2848         } else {
2849         	currentHeight = ctx.oldHeight + tableHeight;
2850         	text.moveText(0, -tableHeight);
2851         }
2852         // end bugfix
2853         pageEmpty = false;
2854     }
2855 
analyzeRow(ArrayList rows, RenderingContext ctx)2856     protected void analyzeRow(ArrayList rows, RenderingContext ctx) {
2857         ctx.maxCellBottom = indentBottom();
2858 
2859         // determine whether row(index) is in a rowspan
2860         int rowIndex = 0;
2861 
2862         ArrayList row = (ArrayList) rows.get(rowIndex);
2863         int maxRowspan = 1;
2864         Iterator iterator = row.iterator();
2865         while (iterator.hasNext()) {
2866             PdfCell cell = (PdfCell) iterator.next();
2867             maxRowspan = Math.max(ctx.currentRowspan(cell), maxRowspan);
2868         }
2869         rowIndex += maxRowspan;
2870 
2871         boolean useTop = true;
2872         if (rowIndex == rows.size()) {
2873             rowIndex = rows.size() - 1;
2874             useTop = false;
2875         }
2876 
2877         if (rowIndex < 0 || rowIndex >= rows.size()) return;
2878 
2879         row = (ArrayList) rows.get(rowIndex);
2880         iterator = row.iterator();
2881         while (iterator.hasNext()) {
2882             PdfCell cell = (PdfCell) iterator.next();
2883             Rectangle cellRect = cell.rectangle(ctx.pagetop, indentBottom());
2884             if (useTop) {
2885                 ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.getTop());
2886             } else {
2887                 if (ctx.currentRowspan(cell) == 1) {
2888                     ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.getBottom());
2889                 }
2890             }
2891         }
2892     }
2893 
mayBeRemoved(ArrayList row)2894     protected boolean mayBeRemoved(ArrayList row) {
2895         Iterator iterator = row.iterator();
2896         boolean mayBeRemoved = true;
2897         while (iterator.hasNext()) {
2898             PdfCell cell = (PdfCell) iterator.next();
2899 
2900             mayBeRemoved &= cell.mayBeRemoved();
2901         }
2902         return mayBeRemoved;
2903     }
2904 
consumeRowspan(ArrayList row, RenderingContext ctx)2905     protected void consumeRowspan(ArrayList row, RenderingContext ctx) {
2906         Iterator iterator = row.iterator();
2907         while (iterator.hasNext()) {
2908             PdfCell c = (PdfCell) iterator.next();
2909             ctx.consumeRowspan(c);
2910         }
2911     }
2912 
extractRows(ArrayList cells, RenderingContext ctx)2913     protected ArrayList extractRows(ArrayList cells, RenderingContext ctx) {
2914         PdfCell cell;
2915         PdfCell previousCell = null;
2916         ArrayList rows = new ArrayList();
2917         java.util.List rowCells = new ArrayList();
2918 
2919         Iterator iterator = cells.iterator();
2920         while (iterator.hasNext()) {
2921             cell = (PdfCell) iterator.next();
2922 
2923             boolean isAdded = false;
2924 
2925             boolean isEndOfRow = !iterator.hasNext();
2926             boolean isCurrentCellPartOfRow = !iterator.hasNext();
2927 
2928             if (previousCell != null) {
2929                 if (cell.getLeft() <= previousCell.getLeft()) {
2930                     isEndOfRow = true;
2931                     isCurrentCellPartOfRow = false;
2932                 }
2933             }
2934 
2935             if (isCurrentCellPartOfRow) {
2936                 rowCells.add(cell);
2937                 isAdded = true;
2938             }
2939 
2940             if (isEndOfRow) {
2941                 if (!rowCells.isEmpty()) {
2942                     // add to rowlist
2943                     rows.add(rowCells);
2944                 }
2945 
2946                 // start a new list for next line
2947                 rowCells = new ArrayList();
2948             }
2949 
2950             if (!isAdded) {
2951                 rowCells.add(cell);
2952             }
2953 
2954             previousCell = cell;
2955         }
2956 
2957         if (!rowCells.isEmpty()) {
2958             rows.add(rowCells);
2959         }
2960 
2961         // fill row information with rowspan cells to get complete "scan lines"
2962         for (int i = rows.size() - 1; i >= 0; i--) {
2963             ArrayList row = (ArrayList) rows.get(i);
2964             // iterator through row
2965             for (int j = 0; j < row.size(); j++) {
2966                 PdfCell c = (PdfCell) row.get(j);
2967                 int rowspan = c.rowspan();
2968                 // fill in missing rowspan cells to complete "scan line"
2969                 for (int k = 1; k < rowspan && rows.size() < i+k; k++) {
2970                     ArrayList spannedRow = ((ArrayList) rows.get(i + k));
2971                     if (spannedRow.size() > j)
2972                     	spannedRow.add(j, c);
2973                 }
2974             }
2975         }
2976 
2977         return rows;
2978     }
2979 
renderCells(RenderingContext ctx, java.util.List cells, boolean hasToFit)2980     protected void renderCells(RenderingContext ctx, java.util.List cells, boolean hasToFit) throws DocumentException {
2981         PdfCell cell;
2982         Iterator iterator;
2983         if (hasToFit) {
2984             iterator = cells.iterator();
2985             while (iterator.hasNext()) {
2986             	cell = (PdfCell) iterator.next();
2987             	if (!cell.isHeader()) {
2988             		if (cell.getBottom() < indentBottom()) return;
2989             	}
2990             }
2991         }
2992         iterator = cells.iterator();
2993 
2994         while (iterator.hasNext()) {
2995             cell = (PdfCell) iterator.next();
2996             if (!ctx.isCellRenderedOnPage(cell, getPageNumber())) {
2997 
2998                 float correction = 0;
2999                 if (ctx.numCellRendered(cell) >= 1) {
3000                     correction = 1.0f;
3001                 }
3002 
3003                 lines = cell.getLines(ctx.pagetop, indentBottom() - correction);
3004 
3005                 // if there is still text to render we render it
3006                 if (lines != null && !lines.isEmpty()) {
3007                     // we write the text
3008                     float cellTop = cell.getTop(ctx.pagetop - ctx.oldHeight);
3009                     text.moveText(0, cellTop);
3010                     float cellDisplacement = flushLines() - cellTop;
3011 
3012                     text.moveText(0, cellDisplacement);
3013                     if (ctx.oldHeight + cellDisplacement > currentHeight) {
3014                         currentHeight = ctx.oldHeight + cellDisplacement;
3015                     }
3016 
3017                     ctx.cellRendered(cell, getPageNumber());
3018                 }
3019                 float indentBottom = Math.max(cell.getBottom(), indentBottom());
3020                 Rectangle tableRect = ctx.table.rectangle(ctx.pagetop, indentBottom());
3021                 indentBottom = Math.max(tableRect.getBottom(), indentBottom);
3022 
3023                 // we paint the borders of the cells
3024                 Rectangle cellRect = cell.rectangle(tableRect.getTop(), indentBottom);
3025  				//cellRect.setBottom(cellRect.bottom());
3026                 if (cellRect.getHeight() > 0) {
3027                     ctx.lostTableBottom = indentBottom;
3028                     ctx.cellGraphics.rectangle(cellRect);
3029                 }
3030 
3031                 // and additional graphics
3032                 ArrayList images = cell.getImages(ctx.pagetop, indentBottom());
3033                 for (Iterator i = images.iterator(); i.hasNext();) {
3034                     Image image = (Image) i.next();
3035                     graphics.addImage(image);
3036                 }
3037 
3038             }
3039         }
3040     }
3041 
3042     /**
3043      * Returns the bottomvalue of a <CODE>Table</CODE> if it were added to this document.
3044      *
3045      * @param	table	the table that may or may not be added to this document
3046      * @return	a bottom value
3047      */
bottom(Table table)3048     float bottom(Table table) {
3049         // constructing a PdfTable
3050         PdfTable tmp = new PdfTable(table, indentLeft(), indentRight(), indentTop() - currentHeight);
3051         return tmp.getBottom();
3052     }
3053 
3054 //	[M5] header/footer
doFooter()3055     protected void doFooter() throws DocumentException {
3056     	if (footer == null) return;
3057 		// Begin added by Edgar Leonardo Prieto Perilla
3058     	// Avoid footer indentation
3059     	float tmpIndentLeft = indentation.indentLeft;
3060     	float tmpIndentRight = indentation.indentRight;
3061     	// Begin added: Bonf (Marc Schneider) 2003-07-29
3062         float tmpListIndentLeft = indentation.listIndentLeft;
3063         float tmpImageIndentLeft = indentation.imageIndentLeft;
3064         float tmpImageIndentRight = indentation.imageIndentRight;
3065         // End added: Bonf (Marc Schneider) 2003-07-29
3066 
3067         indentation.indentLeft = indentation.indentRight = 0;
3068         // Begin added: Bonf (Marc Schneider) 2003-07-29
3069         indentation.listIndentLeft = 0;
3070         indentation.imageIndentLeft = 0;
3071         indentation.imageIndentRight = 0;
3072         // End added: Bonf (Marc Schneider) 2003-07-29
3073         // End Added by Edgar Leonardo Prieto Perilla
3074         footer.setPageNumber(pageN);
3075         leading = footer.paragraph().getTotalLeading();
3076         add(footer.paragraph());
3077         // adding the footer limits the height
3078         indentation.indentBottom = currentHeight;
3079         text.moveText(left(), indentBottom());
3080         flushLines();
3081         text.moveText(-left(), -bottom());
3082         footer.setTop(bottom(currentHeight));
3083         footer.setBottom(bottom() - (0.75f * leading));
3084         footer.setLeft(left());
3085         footer.setRight(right());
3086         graphics.rectangle(footer);
3087         indentation.indentBottom = currentHeight + leading * 2;
3088         currentHeight = 0;
3089         // Begin added by Edgar Leonardo Prieto Perilla
3090         indentation.indentLeft = tmpIndentLeft;
3091         indentation.indentRight = tmpIndentRight;
3092         // Begin added: Bonf (Marc Schneider) 2003-07-29
3093         indentation.listIndentLeft = tmpListIndentLeft;
3094         indentation.imageIndentLeft = tmpImageIndentLeft;
3095         indentation.imageIndentRight = tmpImageIndentRight;
3096         // End added: Bonf (Marc Schneider) 2003-07-29
3097         // End added by Edgar Leonardo Prieto Perilla
3098     }
3099 
doHeader()3100     protected void doHeader() throws DocumentException {
3101         // if there is a header, the header = added
3102         if (header == null) return;
3103 		// Begin added by Edgar Leonardo Prieto Perilla
3104 		// Avoid header indentation
3105 		float tmpIndentLeft = indentation.indentLeft;
3106 		float tmpIndentRight = indentation.indentRight;
3107         // Begin added: Bonf (Marc Schneider) 2003-07-29
3108         float tmpListIndentLeft = indentation.listIndentLeft;
3109         float tmpImageIndentLeft = indentation.imageIndentLeft;
3110         float tmpImageIndentRight = indentation.imageIndentRight;
3111         // End added: Bonf (Marc Schneider) 2003-07-29
3112         indentation.indentLeft = indentation.indentRight = 0;
3113         //  Added: Bonf
3114         indentation.listIndentLeft = 0;
3115         indentation.imageIndentLeft = 0;
3116         indentation.imageIndentRight = 0;
3117         // End added: Bonf
3118         // Begin added by Edgar Leonardo Prieto Perilla
3119 		header.setPageNumber(pageN);
3120         leading = header.paragraph().getTotalLeading();
3121         text.moveText(0, leading);
3122         add(header.paragraph());
3123         newLine();
3124         indentation.indentTop = currentHeight - leading;
3125         header.setTop(top() + leading);
3126         header.setBottom(indentTop() + leading * 2 / 3);
3127         header.setLeft(left());
3128         header.setRight(right());
3129         graphics.rectangle(header);
3130         flushLines();
3131         currentHeight = 0;
3132         // Begin added by Edgar Leonardo Prieto Perilla
3133         // Restore indentation
3134 		indentation.indentLeft = tmpIndentLeft;
3135 		indentation.indentRight = tmpIndentRight;
3136         // Begin added: Bonf (Marc Schneider) 2003-07-29
3137         indentation.listIndentLeft = tmpListIndentLeft;
3138         indentation.imageIndentLeft = tmpImageIndentLeft;
3139         indentation.imageIndentRight = tmpImageIndentRight;
3140         // End added: Bonf (Marc Schneider) 2003-07-29
3141 		// End Added by Edgar Leonardo Prieto Perilla
3142     }
3143 }
3144