/*
* $Name: $
* $Id: PdfDocument.java,v 1.229 2006/06/04 22:23:34 psoares33 Exp $
*
* Copyright 1999, 2000, 2001, 2002 by Bruno Lowagie.
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* http://www.lowagie.com/iText/
*/
package com.lowagie.text.pdf;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.lowagie.text.Anchor;
import com.lowagie.text.Annotation;
import com.lowagie.text.BadElementException;
import com.lowagie.text.Cell;
import com.lowagie.text.Chunk;
import com.lowagie.text.DocListener;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.Graphic;
import com.lowagie.text.HeaderFooter;
import com.lowagie.text.Image;
import com.lowagie.text.List;
import com.lowagie.text.ListItem;
import com.lowagie.text.Meta;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Section;
import com.lowagie.text.SimpleTable;
import com.lowagie.text.StringCompare;
import com.lowagie.text.Table;
import com.lowagie.text.Watermark;
import com.lowagie.text.xml.xmp.XmpWriter;
/**
* PdfDocument
is the class that is used by PdfWriter
* to translate a Document
into a PDF with different pages.
*
* A PdfDocument
always listens to a Document
* and adds the Pdf representation of every Element
that is
* added to the Document
.
*
* @see com.lowagie.text.Document
* @see com.lowagie.text.DocListener
* @see PdfWriter
*/
class PdfDocument extends Document implements DocListener {
/**
* PdfInfo
is the PDF InfoDictionary.
*
* A document's trailer may contain a reference to an Info dictionary that provides information
* about the document. This optional dictionary may contain one or more keys, whose values
* should be strings.
* This object is described in the 'Portable Document Format Reference Manual version 1.3'
* section 6.10 (page 120-121)
*/
public static class PdfInfo extends PdfDictionary {
// constructors
/**
* Construct a PdfInfo
-object.
*/
PdfInfo() {
super();
addProducer();
addCreationDate();
}
/**
* Constructs a PdfInfo
-object.
*
* @param author name of the author of the document
* @param title title of the document
* @param subject subject of the document
*/
PdfInfo(String author, String title, String subject) {
this();
addTitle(title);
addSubject(subject);
addAuthor(author);
}
/**
* Adds the title of the document.
*
* @param title the title of the document
*/
void addTitle(String title) {
put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE));
}
/**
* Adds the subject to the document.
*
* @param subject the subject of the document
*/
void addSubject(String subject) {
put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE));
}
/**
* Adds some keywords to the document.
*
* @param keywords the keywords of the document
*/
void addKeywords(String keywords) {
put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE));
}
/**
* Adds the name of the author to the document.
*
* @param author the name of the author
*/
void addAuthor(String author) {
put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE));
}
/**
* Adds the name of the creator to the document.
*
* @param creator the name of the creator
*/
void addCreator(String creator) {
put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE));
}
/**
* Adds the name of the producer to the document.
*/
void addProducer() {
// This line may only be changed by Bruno Lowagie or Paulo Soares
put(PdfName.PRODUCER, new PdfString(getVersion()));
// Do not edit the line above!
}
/**
* Adds the date of creation to the document.
*/
void addCreationDate() {
PdfString date = new PdfDate();
put(PdfName.CREATIONDATE, date);
put(PdfName.MODDATE, date);
}
void addkey(String key, String value) {
if (key.equals("Producer") || key.equals("CreationDate"))
return;
put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE));
}
}
/**
* PdfCatalog
is the PDF Catalog-object.
*
* The Catalog is a dictionary that is the root node of the document. It contains a reference
* to the tree of pages contained in the document, a reference to the tree of objects representing
* the document's outline, a reference to the document's article threads, and the list of named
* destinations. In addition, the Catalog indicates whether the document's outline or thumbnail
* page images should be displayed automatically when the document is viewed and wether some location
* other than the first page should be shown when the document is opened.
* In this class however, only the reference to the tree of pages is implemented.
* This object is described in the 'Portable Document Format Reference Manual version 1.3'
* section 6.2 (page 67-71)
*/
static class PdfCatalog extends PdfDictionary {
PdfWriter writer;
// constructors
/**
* Constructs a PdfCatalog
.
*
* @param pages an indirect reference to the root of the document's Pages tree.
* @param writer the writer the catalog applies to
*/
PdfCatalog(PdfIndirectReference pages, PdfWriter writer) {
super(CATALOG);
this.writer = writer;
put(PdfName.PAGES, pages);
}
/**
* Constructs a PdfCatalog
.
*
* @param pages an indirect reference to the root of the document's Pages tree.
* @param outlines an indirect reference to the outline tree.
* @param writer the writer the catalog applies to
*/
PdfCatalog(PdfIndirectReference pages, PdfIndirectReference outlines, PdfWriter writer) {
super(CATALOG);
this.writer = writer;
put(PdfName.PAGES, pages);
put(PdfName.PAGEMODE, PdfName.USEOUTLINES);
put(PdfName.OUTLINES, outlines);
}
/**
* Adds the names of the named destinations to the catalog.
* @param localDestinations the local destinations
* @param documentJavaScript the javascript used in the document
* @param writer the writer the catalog applies to
*/
void addNames(TreeMap localDestinations, ArrayList documentJavaScript, HashMap documentFileAttachment, PdfWriter writer) {
if (localDestinations.size() == 0 && documentJavaScript.size() == 0 && documentFileAttachment.size() == 0)
return;
try {
PdfDictionary names = new PdfDictionary();
if (localDestinations.size() > 0) {
PdfArray ar = new PdfArray();
for (Iterator i = localDestinations.keySet().iterator(); i.hasNext();) {
String name = (String)i.next();
Object obj[] = (Object[])localDestinations.get(name);
PdfIndirectReference ref = (PdfIndirectReference)obj[1];
ar.add(new PdfString(name));
ar.add(ref);
}
PdfDictionary dests = new PdfDictionary();
dests.put(PdfName.NAMES, ar);
names.put(PdfName.DESTS, writer.addToBody(dests).getIndirectReference());
}
if (documentJavaScript.size() > 0) {
String s[] = new String[documentJavaScript.size()];
for (int k = 0; k < s.length; ++k)
s[k] = Integer.toHexString(k);
Arrays.sort(s, new StringCompare());
PdfArray ar = new PdfArray();
for (int k = 0; k < s.length; ++k) {
ar.add(new PdfString(s[k]));
ar.add((PdfIndirectReference)documentJavaScript.get(k));
}
PdfDictionary js = new PdfDictionary();
js.put(PdfName.NAMES, ar);
names.put(PdfName.JAVASCRIPT, writer.addToBody(js).getIndirectReference());
}
if (documentFileAttachment.size() > 0) {
names.put(PdfName.EMBEDDEDFILES, writer.addToBody(PdfNameTree.writeTree(documentFileAttachment, writer)).getIndirectReference());
}
put(PdfName.NAMES, writer.addToBody(names).getIndirectReference());
}
catch (IOException e) {
throw new ExceptionConverter(e);
}
}
/** Sets the viewer preferences as the sum of several constants.
* @param preferences the viewer preferences
* @see PdfWriter#setViewerPreferences
*/
void setViewerPreferences(int preferences) {
PdfReader.setViewerPreferences(preferences, this);
}
void setOpenAction(PdfAction action) {
put(PdfName.OPENACTION, action);
}
/** Sets the document level additional actions.
* @param actions dictionary of actions
*/
void setAdditionalActions(PdfDictionary actions) {
try {
put(PdfName.AA, writer.addToBody(actions).getIndirectReference());
} catch (Exception e) {
new ExceptionConverter(e);
}
}
void setPageLabels(PdfPageLabels pageLabels) {
put(PdfName.PAGELABELS, pageLabels.getDictionary());
}
void setAcroForm(PdfObject fields) {
put(PdfName.ACROFORM, fields);
}
}
// membervariables
private PdfIndirectReference thumb;
/** The characters to be applied the hanging ponctuation. */
static final String hangingPunctuation = ".,;:'";
/** The PdfWriter
. */
private PdfWriter writer;
/** some meta information about the Document. */
private PdfInfo info = new PdfInfo();
/** Signals that OnOpenDocument should be called. */
private boolean firstPageEvent = true;
/** Signals that onParagraph is valid. */
private boolean isParagraph = true;
// Horizontal line
/** The line that is currently being written. */
private PdfLine line = null;
/** This represents the current indentation of the PDF Elements on the left side. */
private float indentLeft = 0;
/** This represents the current indentation of the PDF Elements on the right side. */
private float indentRight = 0;
/** This represents the current indentation of the PDF Elements on the left side. */
private float listIndentLeft = 0;
/** This represents the current alignment of the PDF Elements. */
private int alignment = Element.ALIGN_LEFT;
// Vertical lines
/** This is the PdfContentByte object, containing the text. */
private PdfContentByte text;
/** This is the PdfContentByte object, containing the borders and other Graphics. */
private PdfContentByte graphics;
/** The lines that are written until now. */
private ArrayList lines = new ArrayList();
/** This represents the leading of the lines. */
private float leading = 0;
/** This is the current height of the document. */
private float currentHeight = 0;
/** This represents the current indentation of the PDF Elements on the top side. */
private float indentTop = 0;
/** This represents the current indentation of the PDF Elements on the bottom side. */
private float indentBottom = 0;
/** This checks if the page is empty. */
private boolean pageEmpty = true;
private int textEmptySize;
// resources
/** This is the size of the next page. */
protected Rectangle nextPageSize = null;
/** This is the size of the several boxes of the current Page. */
protected HashMap thisBoxSize = new HashMap();
/** This is the size of the several boxes that will be used in
* the next page. */
protected HashMap boxSize = new HashMap();
/** This are the page resources of the current Page. */
protected PageResources pageResources;
// images
/** This is the image that could not be shown on a previous page. */
private Image imageWait = null;
/** This is the position where the image ends. */
private float imageEnd = -1;
/** This is the indentation caused by an image on the left. */
private float imageIndentLeft = 0;
/** This is the indentation caused by an image on the right. */
private float imageIndentRight = 0;
// annotations and outlines
/** This is the array containing the references to the annotations. */
private ArrayList annotations;
/** This is an array containg references to some delayed annotations. */
private ArrayList delayedAnnotations = new ArrayList();
/** This is the AcroForm object. */
PdfAcroForm acroForm;
/** This is the root outline of the document. */
private PdfOutline rootOutline;
/** This is the current PdfOutline
in the hierarchy of outlines. */
private PdfOutline currentOutline;
/** The current active PdfAction
when processing an Anchor
. */
private PdfAction currentAction = null;
/**
* Stores the destinations keyed by name. Value is
* Object[]{PdfAction,PdfIndirectReference,PdfDestintion}
.
*/
private TreeMap localDestinations = new TreeMap(new StringCompare());
private ArrayList documentJavaScript = new ArrayList();
private HashMap documentFileAttachment = new HashMap();
/** these are the viewerpreferences of the document */
private int viewerPreferences = 0;
private String openActionName;
private PdfAction openActionAction;
private PdfDictionary additionalActions;
private PdfPageLabels pageLabels;
//add by Jin-Hsia Yang
private boolean isNewpage = false;
private float paraIndent = 0;
//end add by Jin-Hsia Yang
/** margin in x direction starting from the left. Will be valid in the next page */
protected float nextMarginLeft;
/** margin in x direction starting from the right. Will be valid in the next page */
protected float nextMarginRight;
/** margin in y direction starting from the top. Will be valid in the next page */
protected float nextMarginTop;
/** margin in y direction starting from the bottom. Will be valid in the next page */
protected float nextMarginBottom;
/** The duration of the page */
protected int duration=-1; // negative values will indicate no duration
/** The page transition */
protected PdfTransition transition=null;
protected PdfDictionary pageAA = null;
/** Holds value of property strictImageSequence. */
private boolean strictImageSequence = false;
/** Holds the type of the last element, that has been added to the document. */
private int lastElementType = -1;
private boolean isNewPagePending;
protected int markPoint;
// constructors
/**
* Constructs a new PDF document.
* @throws DocumentException on error
*/
public PdfDocument() throws DocumentException {
super();
addProducer();
addCreationDate();
}
// listener methods
/**
* Adds a PdfWriter
to the PdfDocument
.
*
* @param writer the PdfWriter
that writes everything
* what is added to this document to an outputstream.
* @throws DocumentException on error
*/
public void addWriter(PdfWriter writer) throws DocumentException {
if (this.writer == null) {
this.writer = writer;
acroForm = new PdfAcroForm(writer);
return;
}
throw new DocumentException("You can only add a writer to a PdfDocument once.");
}
/**
* Sets the pagesize.
*
* @param pageSize the new pagesize
* @return true
if the page size was set
*/
public boolean setPageSize(Rectangle pageSize) {
if (writer != null && writer.isPaused()) {
return false;
}
nextPageSize = new Rectangle(pageSize);
return true;
}
/**
* Changes the header of this document.
*
* @param header the new header
*/
public void setHeader(HeaderFooter header) {
if (writer != null && writer.isPaused()) {
return;
}
super.setHeader(header);
}
/**
* Resets the header of this document.
*/
public void resetHeader() {
if (writer != null && writer.isPaused()) {
return;
}
super.resetHeader();
}
/**
* Changes the footer of this document.
*
* @param footer the new footer
*/
public void setFooter(HeaderFooter footer) {
if (writer != null && writer.isPaused()) {
return;
}
super.setFooter(footer);
}
/**
* Resets the footer of this document.
*/
public void resetFooter() {
if (writer != null && writer.isPaused()) {
return;
}
super.resetFooter();
}
/**
* Sets the page number to 0.
*/
public void resetPageCount() {
if (writer != null && writer.isPaused()) {
return;
}
super.resetPageCount();
}
/**
* Sets the page number.
*
* @param pageN the new page number
*/
public void setPageCount(int pageN) {
if (writer != null && writer.isPaused()) {
return;
}
super.setPageCount(pageN);
}
/**
* Sets the Watermark
.
*
* @param watermark the watermark to add
* @return true
if the element was added, false
if not.
*/
public boolean add(Watermark watermark) {
if (writer != null && writer.isPaused()) {
return false;
}
this.watermark = watermark;
return true;
}
/**
* Removes the Watermark
.
*/
public void removeWatermark() {
if (writer != null && writer.isPaused()) {
return;
}
this.watermark = null;
}
/**
* Sets the margins.
*
* @param marginLeft the margin on the left
* @param marginRight the margin on the right
* @param marginTop the margin on the top
* @param marginBottom the margin on the bottom
* @return a boolean
*/
public boolean setMargins(float marginLeft, float marginRight, float marginTop, float marginBottom) {
if (writer != null && writer.isPaused()) {
return false;
}
nextMarginLeft = marginLeft;
nextMarginRight = marginRight;
nextMarginTop = marginTop;
nextMarginBottom = marginBottom;
return true;
}
protected PdfArray rotateAnnotations() {
PdfArray array = new PdfArray();
int rotation = pageSize.getRotation() % 360;
int currentPage = writer.getCurrentPageNumber();
for (int k = 0; k < annotations.size(); ++k) {
PdfAnnotation dic = (PdfAnnotation)annotations.get(k);
int page = dic.getPlaceInPage();
if (page > currentPage) {
delayedAnnotations.add(dic);
continue;
}
if (dic.isForm()) {
if (!dic.isUsed()) {
HashMap templates = dic.getTemplates();
if (templates != null)
acroForm.addFieldTemplates(templates);
}
PdfFormField field = (PdfFormField)dic;
if (field.getParent() == null)
acroForm.addDocumentField(field.getIndirectReference());
}
if (dic.isAnnotation()) {
array.add(dic.getIndirectReference());
if (!dic.isUsed()) {
PdfRectangle rect = (PdfRectangle)dic.get(PdfName.RECT);
if (rect != null) {
switch (rotation) {
case 90:
dic.put(PdfName.RECT, new PdfRectangle(
pageSize.top() - rect.bottom(),
rect.left(),
pageSize.top() - rect.top(),
rect.right()));
break;
case 180:
dic.put(PdfName.RECT, new PdfRectangle(
pageSize.right() - rect.left(),
pageSize.top() - rect.bottom(),
pageSize.right() - rect.right(),
pageSize.top() - rect.top()));
break;
case 270:
dic.put(PdfName.RECT, new PdfRectangle(
rect.bottom(),
pageSize.right() - rect.left(),
rect.top(),
pageSize.right() - rect.right()));
break;
}
}
}
}
if (!dic.isUsed()) {
dic.setUsed();
try {
writer.addToBody(dic, dic.getIndirectReference());
}
catch (IOException e) {
throw new ExceptionConverter(e);
}
}
}
return array;
}
/**
* Makes a new page and sends it to the PdfWriter
.
*
* @return a boolean
* @throws DocumentException on error
*/
public boolean newPage() throws DocumentException {
lastElementType = -1;
//add by Jin-Hsia Yang
isNewpage = true;
//end add by Jin-Hsia Yang
if (writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0 && (pageEmpty || (writer != null && writer.isPaused()))) {
return false;
}
PdfPageEvent pageEvent = writer.getPageEvent();
if (pageEvent != null)
pageEvent.onEndPage(writer, this);
//Added to inform any listeners that we are moving to a new page (added by David Freels)
super.newPage();
// the following 2 lines were added by Pelikan Stephan
imageIndentLeft = 0;
imageIndentRight = 0;
// we flush the arraylist with recently written lines
flushLines();
// we assemble the resources of this pages
pageResources.addDefaultColorDiff(writer.getDefaultColorspace());
PdfDictionary resources = pageResources.getResources();
// we make a new page and add it to the document
if (writer.getPDFXConformance() != PdfWriter.PDFXNONE) {
if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim"))
throw new PdfXConformanceException("Only one of ArtBox or TrimBox can exist in the page.");
if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) {
if (thisBoxSize.containsKey("crop"))
thisBoxSize.put("trim", thisBoxSize.get("crop"));
else
thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation()));
}
}
PdfPage page;
int rotation = pageSize.getRotation();
page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation);
// we add tag info
if (writer.isTagged())
page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1));
// we add the transitions
if (this.transition!=null) {
page.put(PdfName.TRANS, this.transition.getTransitionDictionary());
transition = null;
}
if (this.duration>0) {
page.put(PdfName.DUR,new PdfNumber(this.duration));
duration = 0;
}
// we add the page object additional actions
if (pageAA != null) {
try {
page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference());
}
catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
pageAA = null;
}
// we check if the userunit is defined
if (writer.getUserunit() > 0f) {
page.put(PdfName.USERUNIT, new PdfNumber(writer.getUserunit()));
}
// we add the annotations
if (annotations.size() > 0) {
PdfArray array = rotateAnnotations();
if (array.size() != 0)
page.put(PdfName.ANNOTS, array);
}
// we add the thumbs
if (thumb != null) {
page.put(PdfName.THUMB, thumb);
thumb = null;
}
if (!open || close) {
throw new PdfException("The document isn't open.");
}
if (text.size() > textEmptySize)
text.endText();
else
text = null;
writer.add(page, new PdfContents(writer.getDirectContentUnder(), graphics, text, writer.getDirectContent(), pageSize));
// we initialize the new page
initPage();
//add by Jin-Hsia Yang
isNewpage = false;
//end add by Jin-Hsia Yang
return true;
}
// methods to open and close a document
/**
* Opens the document.
*
* You have to open the document before you can begin to add content
* to the body of the document.
*/
public void open() {
if (!open) {
super.open();
writer.open();
rootOutline = new PdfOutline(writer);
currentOutline = rootOutline;
}
try {
initPage();
}
catch(DocumentException de) {
throw new ExceptionConverter(de);
}
}
void outlineTree(PdfOutline outline) throws IOException {
outline.setIndirectReference(writer.getPdfIndirectReference());
if (outline.parent() != null)
outline.put(PdfName.PARENT, outline.parent().indirectReference());
ArrayList kids = outline.getKids();
int size = kids.size();
for (int k = 0; k < size; ++k)
outlineTree((PdfOutline)kids.get(k));
for (int k = 0; k < size; ++k) {
if (k > 0)
((PdfOutline)kids.get(k)).put(PdfName.PREV, ((PdfOutline)kids.get(k - 1)).indirectReference());
if (k < size - 1)
((PdfOutline)kids.get(k)).put(PdfName.NEXT, ((PdfOutline)kids.get(k + 1)).indirectReference());
}
if (size > 0) {
outline.put(PdfName.FIRST, ((PdfOutline)kids.get(0)).indirectReference());
outline.put(PdfName.LAST, ((PdfOutline)kids.get(size - 1)).indirectReference());
}
for (int k = 0; k < size; ++k) {
PdfOutline kid = (PdfOutline)kids.get(k);
writer.addToBody(kid, kid.indirectReference());
}
}
void writeOutlines() throws IOException {
if (rootOutline.getKids().size() == 0)
return;
outlineTree(rootOutline);
writer.addToBody(rootOutline, rootOutline.indirectReference());
}
void traverseOutlineCount(PdfOutline outline) {
ArrayList kids = outline.getKids();
PdfOutline parent = outline.parent();
if (kids.size() == 0) {
if (parent != null) {
parent.setCount(parent.getCount() + 1);
}
}
else {
for (int k = 0; k < kids.size(); ++k) {
traverseOutlineCount((PdfOutline)kids.get(k));
}
if (parent != null) {
if (outline.isOpen()) {
parent.setCount(outline.getCount() + parent.getCount() + 1);
}
else {
parent.setCount(parent.getCount() + 1);
outline.setCount(-outline.getCount());
}
}
}
}
void calculateOutlineCount() {
if (rootOutline.getKids().size() == 0)
return;
traverseOutlineCount(rootOutline);
}
/**
* Closes the document.
*
* Once all the content has been written in the body, you have to close
* the body. After that nothing can be written to the body anymore.
*/
public void close() {
if (close) {
return;
}
try {
boolean wasImage = (imageWait != null);
newPage();
if (imageWait != null || wasImage) newPage();
if (annotations.size() > 0)
throw new RuntimeException(annotations.size() + " annotations had invalid placement pages.");
PdfPageEvent pageEvent = writer.getPageEvent();
if (pageEvent != null)
pageEvent.onCloseDocument(writer, this);
super.close();
writer.addLocalDestinations(localDestinations);
calculateOutlineCount();
writeOutlines();
}
catch(Exception e) {
throw new ExceptionConverter(e);
}
writer.close();
}
PageResources getPageResources() {
return pageResources;
}
/** Adds a PdfPTable
to the document.
* @param ptable the PdfPTable
to be added to the document.
* @throws DocumentException on error
*/
void addPTable(PdfPTable ptable) throws DocumentException {
ColumnText ct = new ColumnText(writer.getDirectContent());
if (currentHeight > 0) {
Paragraph p = new Paragraph();
p.setLeading(0);
ct.addElement(p);
// if the table prefers to be on a single page, and it wouldn't
//fit on the current page, start a new page.
if (ptable.getKeepTogether() && !fitsPage(ptable, 0f))
newPage();
}
ct.addElement(ptable);
boolean he = ptable.isHeadersInEvent();
ptable.setHeadersInEvent(true);
int loop = 0;
while (true) {
ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight);
int status = ct.go();
if ((status & ColumnText.NO_MORE_TEXT) != 0) {
text.moveText(0, ct.getYLine() - indentTop() + currentHeight);
currentHeight = indentTop() - ct.getYLine();
break;
}
if (indentTop() - currentHeight == ct.getYLine())
++loop;
else
loop = 0;
if (loop == 3) {
add(new Paragraph("ERROR: Infinite table loop"));
break;
}
newPage();
}
ptable.setHeadersInEvent(he);
}
/**
* Gets a PdfTable object
* (contributed by dperezcar@fcc.es)
* @param table a high level table object
* @param supportRowAdditions
* @return returns a PdfTable object
* @see PdfWriter#getPdfTable(Table)
*/
PdfTable getPdfTable(Table table, boolean supportRowAdditions) {
return new PdfTable(table, indentLeft(), indentRight(), indentTop() - currentHeight, supportRowAdditions);
}
/**
* @see PdfWriter#breakTableIfDoesntFit(PdfTable)
* (contributed by dperezcar@fcc.es)
* @param table Table to add
* @return true if the table will be broken
* @throws DocumentException
*/
boolean breakTableIfDoesntFit(PdfTable table) throws DocumentException {
table.updateRowAdditions();
// Do we have any full page available?
if (!table.hasToFitPageTable() && table.bottom() <= indentBottom) {
// Then output that page
add(table, true);
return true;
}
return false;
}
private static class RenderingContext {
int countPageBreaks = 0;
float pagetop = -1;
float oldHeight = -1;
PdfContentByte cellGraphics = null;
float lostTableBottom;
float maxCellBottom;
float maxCellHeight;
Map rowspanMap;
Map pageMap = new HashMap();
/**
* A PdfPTable
*/
public PdfTable table;
/**
* Consumes the rowspan
* @param c
* @return a rowspan.
*/
public int consumeRowspan(PdfCell c) {
if (c.rowspan() == 1) {
return 1;
}
Integer i = (Integer) rowspanMap.get(c);
if (i == null) {
i = new Integer(c.rowspan());
}
i = new Integer(i.intValue() - 1);
rowspanMap.put(c, i);
if (i.intValue() < 1) {
return 1;
}
return i.intValue();
}
/**
* Looks at the current rowspan.
* @param c
* @return the current rowspan
*/
public int currentRowspan(PdfCell c) {
Integer i = (Integer) rowspanMap.get(c);
if (i == null) {
return c.rowspan();
} else {
return i.intValue();
}
}
public int cellRendered(PdfCell cell, int pageNumber) {
Integer i = (Integer) pageMap.get(cell);
if (i == null) {
i = new Integer(1);
} else {
i = new Integer(i.intValue() + 1);
}
pageMap.put(cell, i);
Integer pageInteger = new Integer(pageNumber);
Set set = (Set) pageMap.get(pageInteger);
if (set == null) {
set = new HashSet();
pageMap.put(pageInteger, set);
}
set.add(cell);
return i.intValue();
}
public int numCellRendered(PdfCell cell) {
Integer i = (Integer) pageMap.get(cell);
if (i == null) {
i = new Integer(0);
}
return i.intValue();
}
public boolean isCellRenderedOnPage(PdfCell cell, int pageNumber) {
Integer pageInteger = new Integer(pageNumber);
Set set = (Set) pageMap.get(pageInteger);
if (set != null) {
return set.contains(cell);
}
return false;
}
};
private void analyzeRow(ArrayList rows, RenderingContext ctx) {
ctx.maxCellBottom = indentBottom();
// determine whether row(index) is in a rowspan
int rowIndex = 0;
ArrayList row = (ArrayList) rows.get(rowIndex);
int maxRowspan = 1;
Iterator iterator = row.iterator();
while (iterator.hasNext()) {
PdfCell cell = (PdfCell) iterator.next();
maxRowspan = Math.max(ctx.currentRowspan(cell), maxRowspan);
}
rowIndex += maxRowspan;
boolean useTop = true;
if (rowIndex == rows.size()) {
rowIndex = rows.size() - 1;
useTop = false;
}
if (rowIndex < 0 || rowIndex >= rows.size()) return;
row = (ArrayList) rows.get(rowIndex);
iterator = row.iterator();
while (iterator.hasNext()) {
PdfCell cell = (PdfCell) iterator.next();
Rectangle cellRect = cell.rectangle(ctx.pagetop, indentBottom());
if (useTop) {
ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.top());
} else {
if (ctx.currentRowspan(cell) == 1) {
ctx.maxCellBottom = Math.max(ctx.maxCellBottom, cellRect.bottom());
}
}
}
}
/**
* Adds a new table to
* @param table Table to add. Rendered rows will be deleted after processing.
* @param onlyFirstPage Render only the first full page
* @throws DocumentException
*/
private void add(PdfTable table, boolean onlyFirstPage) throws DocumentException {
// before every table, we flush all lines
flushLines();
RenderingContext ctx = new RenderingContext();
ctx.pagetop = indentTop();
ctx.oldHeight = currentHeight;
ctx.cellGraphics = new PdfContentByte(writer);
ctx.rowspanMap = new HashMap();
ctx.table = table;
// initialisation of parameters
PdfCell cell;
boolean tableHasToFit =
table.hasToFitPageTable() ? (table.bottom() < indentBottom() && table.height() < (top() - bottom())) : false;
if (pageEmpty)
tableHasToFit = false;
boolean cellsHaveToFit = table.hasToFitPageCells();
// drawing the table
ArrayList dataCells = table.getCells();
ArrayList headercells = table.getHeaderCells();
// Check if we have removed header cells in a previous call
if (headercells.size() > 0 && (dataCells.size() == 0 || dataCells.get(0) != headercells.get(0))) {
ArrayList allCells = new ArrayList(dataCells.size()+headercells.size());
allCells.addAll(headercells);
allCells.addAll(dataCells);
dataCells = allCells;
}
ArrayList cells = dataCells;
ArrayList rows = extractRows(cells, ctx);
boolean isContinue = false;
while (!cells.isEmpty()) {
// initialisation of some extra parameters;
ctx.lostTableBottom = 0;
// loop over the cells
boolean cellsShown = false;
int currentGroupNumber = 0;
boolean headerChecked = false;
float headerHeight = 0;
// draw the cells (line by line)
Iterator iterator = rows.iterator();
boolean atLeastOneFits = false;
while (iterator.hasNext()) {
ArrayList row = (ArrayList) iterator.next();
analyzeRow(rows, ctx);
renderCells(ctx, row, table.hasToFitPageCells() & atLeastOneFits);
if (!mayBeRemoved(row)) {
break;
}
consumeRowspan(row, ctx);
iterator.remove();
atLeastOneFits = true;
}
// compose cells array list for subsequent code
cells.clear();
Set opt = new HashSet();
iterator = rows.iterator();
while (iterator.hasNext()) {
ArrayList row = (ArrayList) iterator.next();
Iterator cellIterator = row.iterator();
while (cellIterator.hasNext()) {
cell = (PdfCell) cellIterator.next();
if (!opt.contains(cell)) {
cells.add(cell);
opt.add(cell);
}
}
}
tableHasToFit = false;
// we paint the graphics of the table after looping through all the cells
Rectangle tablerec = new Rectangle(table);
tablerec.setBorder(table.border());
tablerec.setBorderWidth(table.borderWidth());
tablerec.setBorderColor(table.borderColor());
tablerec.setBackgroundColor(table.backgroundColor());
PdfContentByte under = writer.getDirectContentUnder();
under.rectangle(tablerec.rectangle(top(), indentBottom()));
under.add(ctx.cellGraphics);
// bugfix by Gerald Fehringer: now again add the border for the table
// since it might have been covered by cell backgrounds
tablerec.setBackgroundColor(null);
tablerec = tablerec.rectangle(top(), indentBottom());
tablerec.setBorder(table.border());
under.rectangle(tablerec);
// end bugfix
ctx.cellGraphics = new PdfContentByte(null);
// if the table continues on the next page
if (!rows.isEmpty()) {
isContinue = true;
graphics.setLineWidth(table.borderWidth());
if (cellsShown && (table.border() & Rectangle.BOTTOM) == Rectangle.BOTTOM) {
// Draw the bottom line
// the color is set to the color of the element
Color tColor = table.borderColor();
if (tColor != null) {
graphics.setColorStroke(tColor);
}
graphics.moveTo(table.left(), Math.max(table.bottom(), indentBottom()));
graphics.lineTo(table.right(), Math.max(table.bottom(), indentBottom()));
graphics.stroke();
if (tColor != null) {
graphics.resetRGBColorStroke();
}
}
// old page
pageEmpty = false;
float difference = ctx.lostTableBottom;
// new page
newPage();
ctx.countPageBreaks++;
// G.F.: if something added in page event i.e. currentHeight > 0
float heightCorrection = 0;
boolean somethingAdded = false;
if (currentHeight > 0) {
heightCorrection = 6;
currentHeight += heightCorrection;
somethingAdded = true;
newLine();
flushLines();
indentTop = currentHeight - leading;
currentHeight = 0;
}
else {
flushLines();
}
// this part repeats the table headers (if any)
int size = headercells.size();
if (size > 0) {
// this is the top of the headersection
cell = (PdfCell) headercells.get(0);
float oldTop = cell.top(0);
// loop over all the cells of the table header
for (int i = 0; i < size; i++) {
cell = (PdfCell) headercells.get(i);
// calculation of the new cellpositions
cell.setTop(indentTop() - oldTop + cell.top(0));
cell.setBottom(indentTop() - oldTop + cell.bottom(0));
ctx.pagetop = cell.bottom();
// we paint the borders of the cell
ctx.cellGraphics.rectangle(cell.rectangle(indentTop(), indentBottom()));
// we write the text of the cell
ArrayList images = cell.getImages(indentTop(), indentBottom());
for (Iterator im = images.iterator(); im.hasNext();) {
cellsShown = true;
Image image = (Image) im.next();
graphics.addImage(image);
}
lines = cell.getLines(indentTop(), indentBottom());
float cellTop = cell.top(indentTop());
text.moveText(0, cellTop-heightCorrection);
float cellDisplacement = flushLines() - cellTop+heightCorrection;
text.moveText(0, cellDisplacement);
}
currentHeight = indentTop() - ctx.pagetop + table.cellspacing();
text.moveText(0, ctx.pagetop - indentTop() - currentHeight);
}
else {
if (somethingAdded) {
ctx.pagetop = indentTop();
text.moveText(0, -table.cellspacing());
}
}
ctx.oldHeight = currentHeight - heightCorrection;
// calculating the new positions of the table and the cells
size = Math.min(cells.size(), table.columns());
int i = 0;
while (i < size) {
cell = (PdfCell) cells.get(i);
if (cell.top(-table.cellspacing()) > ctx.lostTableBottom) {
float newBottom = ctx.pagetop - difference + cell.bottom();
float neededHeight = cell.remainingHeight();
if (newBottom > ctx.pagetop - neededHeight) {
difference += newBottom - (ctx.pagetop - neededHeight);
}
}
i++;
}
size = cells.size();
table.setTop(indentTop());
table.setBottom(ctx.pagetop - difference + table.bottom(table.cellspacing()));
for (i = 0; i < size; i++) {
cell = (PdfCell) cells.get(i);
float newBottom = ctx.pagetop - difference + cell.bottom();
float newTop = ctx.pagetop - difference + cell.top(-table.cellspacing());
if (newTop > indentTop() - currentHeight) {
newTop = indentTop() - currentHeight;
}
cell.setTop(newTop );
cell.setBottom(newBottom );
}
if (onlyFirstPage) {
break;
}
}
}
float tableHeight = table.top() - table.bottom();
// bugfix by Adauto Martins when have more than two tables and more than one page
// If continuation of table in other page (bug report #1460051)
if (isContinue) {
currentHeight = tableHeight;
text.moveText(0, -(tableHeight - (ctx.oldHeight * 2)));
} else {
currentHeight = ctx.oldHeight + tableHeight;
text.moveText(0, -tableHeight);
}
// end bugfix
pageEmpty = false;
if (ctx.countPageBreaks > 0) {
// in case of tables covering more that one page have to have
// a newPage followed to reset some internal state. Otherwise
// subsequent tables are rendered incorrectly.
isNewPagePending = true;
}
}
private boolean mayBeRemoved(ArrayList row) {
Iterator iterator = row.iterator();
boolean mayBeRemoved = true;
while (iterator.hasNext()) {
PdfCell cell = (PdfCell) iterator.next();
mayBeRemoved &= cell.mayBeRemoved();
}
return mayBeRemoved;
}
private void consumeRowspan(ArrayList row, RenderingContext ctx) {
Iterator iterator = row.iterator();
while (iterator.hasNext()) {
PdfCell c = (PdfCell) iterator.next();
ctx.consumeRowspan(c);
}
}
private ArrayList extractRows(ArrayList cells, RenderingContext ctx) {
PdfCell cell;
PdfCell previousCell = null;
ArrayList rows = new ArrayList();
java.util.List rowCells = new ArrayList();
Iterator iterator = cells.iterator();
while (iterator.hasNext()) {
cell = (PdfCell) iterator.next();
boolean isAdded = false;
boolean isEndOfRow = !iterator.hasNext();
boolean isCurrentCellPartOfRow = !iterator.hasNext();
if (previousCell != null) {
if (cell.left() <= previousCell.left()) {
isEndOfRow = true;
isCurrentCellPartOfRow = false;
}
}
if (isCurrentCellPartOfRow) {
rowCells.add(cell);
isAdded = true;
}
if (isEndOfRow) {
if (!rowCells.isEmpty()) {
// add to rowlist
rows.add(rowCells);
}
// start a new list for next line
rowCells = new ArrayList();
}
if (!isAdded) {
rowCells.add(cell);
}
previousCell = cell;
}
if (!rowCells.isEmpty()) {
rows.add(rowCells);
}
// fill row information with rowspan cells to get complete "scan lines"
for (int i = rows.size() - 1; i >= 0; i--) {
ArrayList row = (ArrayList) rows.get(i);
// iterator through row
for (int j = 0; j < row.size(); j++) {
PdfCell c = (PdfCell) row.get(j);
int rowspan = c.rowspan();
// fill in missing rowspan cells to complete "scan line"
for (int k = 1; k < rowspan; k++) {
ArrayList spannedRow = ((ArrayList) rows.get(i + k));
if (spannedRow.size() > j)
spannedRow.add(j, c);
}
}
}
return rows;
}
private void renderCells(RenderingContext ctx, java.util.List cells, boolean hasToFit) throws DocumentException {
PdfCell cell;
Iterator iterator;
if (hasToFit) {
iterator = cells.iterator();
while (iterator.hasNext()) {
cell = (PdfCell) iterator.next();
if (!cell.isHeader()) {
if (cell.bottom() < indentBottom()) return;
}
}
}
iterator = cells.iterator();
while (iterator.hasNext()) {
cell = (PdfCell) iterator.next();
if (!ctx.isCellRenderedOnPage(cell, getPageNumber())) {
float correction = 0;
if (ctx.numCellRendered(cell) >= 1) {
correction = 1.0f;
}
lines = cell.getLines(ctx.pagetop, indentBottom() - correction);
// if there is still text to render we render it
if (lines != null && lines.size() > 0) {
// we write the text
float cellTop = cell.top(ctx.pagetop - ctx.oldHeight);
text.moveText(0, cellTop);
float cellDisplacement = flushLines() - cellTop;
text.moveText(0, cellDisplacement);
if (ctx.oldHeight + cellDisplacement > currentHeight) {
currentHeight = ctx.oldHeight + cellDisplacement;
}
ctx.cellRendered(cell, getPageNumber());
}
float indentBottom = Math.max(cell.bottom(), indentBottom());
Rectangle tableRect = ctx.table.rectangle(ctx.pagetop, indentBottom());
indentBottom = Math.max(tableRect.bottom(), indentBottom);
// we paint the borders of the cells
Rectangle cellRect = cell.rectangle(tableRect.top(), indentBottom);
//cellRect.setBottom(cellRect.bottom());
if (cellRect.height() > 0) {
ctx.lostTableBottom = indentBottom;
ctx.cellGraphics.rectangle(cellRect);
}
// and additional graphics
ArrayList images = cell.getImages(ctx.pagetop, indentBottom());
for (Iterator i = images.iterator(); i.hasNext();) {
Image image = (Image) i.next();
graphics.addImage(image);
}
}
}
}
/**
* Signals that an Element
was added to the Document
.
*
* @param element the element to add
* @return true
if the element was added, false
if not.
* @throws DocumentException when a document isn't open yet, or has been closed
*/
public boolean add(Element element) throws DocumentException {
if (writer != null && writer.isPaused()) {
return false;
}
try {
// resolves problem described in add(PdfTable)
if (isNewPagePending) {
isNewPagePending = false;
newPage();
}
switch(element.type()) {
// Information (headers)
case Element.HEADER:
info.addkey(((Meta)element).name(), ((Meta)element).content());
break;
case Element.TITLE:
info.addTitle(((Meta)element).content());
break;
case Element.SUBJECT:
info.addSubject(((Meta)element).content());
break;
case Element.KEYWORDS:
info.addKeywords(((Meta)element).content());
break;
case Element.AUTHOR:
info.addAuthor(((Meta)element).content());
break;
case Element.CREATOR:
info.addCreator(((Meta)element).content());
break;
case Element.PRODUCER:
// you can not change the name of the producer
info.addProducer();
break;
case Element.CREATIONDATE:
// you can not set the creation date, only reset it
info.addCreationDate();
break;
// content (text)
case Element.CHUNK: {
// if there isn't a current line available, we make one
if (line == null) {
carriageReturn();
}
// we cast the element to a chunk
PdfChunk chunk = new PdfChunk((Chunk) element, currentAction);
// we try to add the chunk to the line, until we succeed
{
PdfChunk overflow;
while ((overflow = line.add(chunk)) != null) {
carriageReturn();
chunk = overflow;
}
}
pageEmpty = false;
if (chunk.isAttribute(Chunk.NEWPAGE)) {
newPage();
}
break;
}
case Element.ANCHOR: {
Anchor anchor = (Anchor) element;
String url = anchor.reference();
leading = anchor.leading();
if (url != null) {
currentAction = new PdfAction(url);
}
// we process the element
element.process(this);
currentAction = null;
break;
}
case Element.ANNOTATION: {
if (line == null) {
carriageReturn();
}
Annotation annot = (Annotation) element;
PdfAnnotation an = convertAnnotation(writer, annot);
annotations.add(an);
pageEmpty = false;
break;
}
case Element.PHRASE: {
// we cast the element to a phrase and set the leading of the document
leading = ((Phrase) element).leading();
// we process the element
element.process(this);
break;
}
case Element.PARAGRAPH: {
// we cast the element to a paragraph
Paragraph paragraph = (Paragraph) element;
float spacingBefore = paragraph.spacingBefore();
if (spacingBefore != 0) {
leading = spacingBefore;
carriageReturn();
if (!pageEmpty) {
/*
* Don't add spacing before a paragraph if it's the first
* on the page
*/
Chunk space = new Chunk(" ");
space.process(this);
carriageReturn();
}
}
// we adjust the parameters of the document
alignment = paragraph.alignment();
leading = paragraph.leading();
carriageReturn();
// we don't want to make orphans/widows
if (currentHeight + line.height() + leading > indentTop() - indentBottom()) {
newPage();
}
// Begin added: Bonf (Marc Schneider) 2003-07-29
//carriageReturn();
// End added: Bonf (Marc Schneider) 2003-07-29
indentLeft += paragraph.indentationLeft();
indentRight += paragraph.indentationRight();
// Begin removed: Bonf (Marc Schneider) 2003-07-29
carriageReturn();
// End removed: Bonf (Marc Schneider) 2003-07-29
//add by Jin-Hsia Yang
paraIndent += paragraph.indentationLeft();
//end add by Jin-Hsia Yang
PdfPageEvent pageEvent = writer.getPageEvent();
if (pageEvent != null && isParagraph)
pageEvent.onParagraph(writer, this, indentTop() - currentHeight);
// if a paragraph has to be kept together, we wrap it in a table object
if (paragraph.getKeepTogether()) {
Table table = new Table(1, 1);
table.setOffset(0f);
table.setBorder(Table.NO_BORDER);
table.setWidth(100f);
table.setTableFitsPage(true);
Cell cell = new Cell(paragraph);
cell.setBorder(Table.NO_BORDER);
//patch by Matt Benson 11/01/2002 - 14:32:00
cell.setHorizontalAlignment(paragraph.alignment());
//end patch by Matt Benson
table.addCell(cell);
this.add(table);
break;
}
else
// we process the paragraph
element.process(this);
//add by Jin-Hsia Yang and blowagie
paraIndent -= paragraph.indentationLeft();
//end add by Jin-Hsia Yang and blowagie
// Begin removed: Bonf (Marc Schneider) 2003-07-29
// carriageReturn();
// End removed: Bonf (Marc Schneider) 2003-07-29
float spacingAfter = paragraph.spacingAfter();
if (spacingAfter != 0) {
leading = spacingAfter;
carriageReturn();
if (currentHeight + line.height() + leading < indentTop() - indentBottom()) {
/*
* Only add spacing after a paragraph if the extra
* spacing fits on the page.
*/
Chunk space = new Chunk(" ");
space.process(this);
carriageReturn();
}
leading = paragraph.leading(); // restore original leading
}
if (pageEvent != null && isParagraph)
pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight);
alignment = Element.ALIGN_LEFT;
indentLeft -= paragraph.indentationLeft();
indentRight -= paragraph.indentationRight();
// Begin added: Bonf (Marc Schneider) 2003-07-29
carriageReturn();
// End added: Bonf (Marc Schneider) 2003-07-29
//add by Jin-Hsia Yang
//end add by Jin-Hsia Yang
break;
}
case Element.SECTION:
case Element.CHAPTER: {
// Chapters and Sections only differ in their constructor
// so we cast both to a Section
Section section = (Section) element;
boolean hasTitle = section.title() != null;
// if the section is a chapter, we begin a new page
if (section.isChapter()) {
newPage();
}
// otherwise, we begin a new line
else {
newLine();
}
if (hasTitle) {
float fith = indentTop() - currentHeight;
int rotation = pageSize.getRotation();
if (rotation == 90 || rotation == 180)
fith = pageSize.height() - fith;
PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith);
while (currentOutline.level() >= section.depth()) {
currentOutline = currentOutline.parent();
}
PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen());
currentOutline = outline;
}
// some values are set
carriageReturn();
indentLeft += section.indentationLeft();
indentRight += section.indentationRight();
PdfPageEvent pageEvent = writer.getPageEvent();
if (pageEvent != null)
if (element.type() == Element.CHAPTER)
pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.title());
else
pageEvent.onSection(writer, this, indentTop() - currentHeight, section.depth(), section.title());
// the title of the section (if any has to be printed)
if (hasTitle) {
isParagraph = false;
add(section.title());
isParagraph = true;
}
indentLeft += section.indentation();
// we process the section
element.process(this);
// some parameters are set back to normal again
indentLeft -= section.indentationLeft() + section.indentation();
indentRight -= section.indentationRight();
if (pageEvent != null)
if (element.type() == Element.CHAPTER)
pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight);
else
pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight);
break;
}
case Element.LIST: {
// we cast the element to a List
List list = (List) element;
// we adjust the document
listIndentLeft += list.indentationLeft();
indentRight += list.indentationRight();
// we process the items in the list
element.process(this);
// some parameters are set back to normal again
listIndentLeft -= list.indentationLeft();
indentRight -= list.indentationRight();
break;
}
case Element.LISTITEM: {
// we cast the element to a ListItem
ListItem listItem = (ListItem) element;
float spacingBefore = listItem.spacingBefore();
if (spacingBefore != 0) {
leading = spacingBefore;
carriageReturn();
if (!pageEmpty) {
/*
* Don't add spacing before a paragraph if it's the first
* on the page
*/
Chunk space = new Chunk(" ");
space.process(this);
carriageReturn();
}
}
// we adjust the document
alignment = listItem.alignment();
listIndentLeft += listItem.indentationLeft();
indentRight += listItem.indentationRight();
leading = listItem.leading();
carriageReturn();
// we prepare the current line to be able to show us the listsymbol
line.setListItem(listItem);
// we process the item
element.process(this);
float spacingAfter = listItem.spacingAfter();
if (spacingAfter != 0) {
leading = spacingAfter;
carriageReturn();
if (currentHeight + line.height() + leading < indentTop() - indentBottom()) {
/*
* Only add spacing after a paragraph if the extra
* spacing fits on the page.
*/
Chunk space = new Chunk(" ");
space.process(this);
carriageReturn();
}
leading = listItem.leading(); // restore original leading
}
// if the last line is justified, it should be aligned to the left
// if (line.hasToBeJustified()) {
// line.resetAlignment();
// }
// some parameters are set back to normal again
carriageReturn();
listIndentLeft -= listItem.indentationLeft();
indentRight -= listItem.indentationRight();
break;
}
case Element.RECTANGLE: {
Rectangle rectangle = (Rectangle) element;
graphics.rectangle(rectangle);
pageEmpty = false;
break;
}
case Element.PTABLE: {
PdfPTable ptable = (PdfPTable)element;
if (ptable.size() <= ptable.getHeaderRows())
break; //nothing to do
// before every table, we add a new line and flush all lines
ensureNewLine();
flushLines();
addPTable(ptable);
pageEmpty = false;
break;
}
case Element.MULTI_COLUMN_TEXT: {
ensureNewLine();
flushLines();
MultiColumnText multiText = (MultiColumnText) element;
float height = multiText.write(writer.getDirectContent(), this, indentTop() - currentHeight);
currentHeight += height;
text.moveText(0, -1f* height);
pageEmpty = false;
break;
}
case Element.TABLE : {
/**
* This is a list of people who worked on the Table functionality.
* To see who did what, please check the CVS repository:
*
* Leslie Baski
* Matt Benson
* Francesco De Milato
* David Freels
* Bruno Lowagie
* Veerendra Namineni
* Geert Poels
* Tom Ring
* Paulo Soares
* Gerald Fehringer
* Steve Appling
* Karsten Klein
*/
PdfTable table;
if (element instanceof PdfTable) {
// Already pre-rendered
table = (PdfTable)element;
table.updateRowAdditions();
} else if (element instanceof SimpleTable) {
PdfPTable ptable = ((SimpleTable)element).createPdfPTable();
if (ptable.size() <= ptable.getHeaderRows())
break; //nothing to do
// before every table, we add a new line and flush all lines
ensureNewLine();
flushLines();
addPTable(ptable);
pageEmpty = false;
break;
} else if (element instanceof Table) {
try {
PdfPTable ptable = ((Table)element).createPdfPTable();
if (ptable.size() <= ptable.getHeaderRows())
break; //nothing to do
// before every table, we add a new line and flush all lines
ensureNewLine();
flushLines();
addPTable(ptable);
pageEmpty = false;
break;
}
catch(BadElementException bee) {
// constructing the PdfTable
// Before the table, add a blank line using offset or default leading
float offset = ((Table)element).getOffset();
if (Float.isNaN(offset))
offset = leading;
carriageReturn();
lines.add(new PdfLine(indentLeft(), indentRight(), alignment, offset));
currentHeight += offset;
table = getPdfTable((Table)element, false);
}
} else {
return false;
}
add(table, false);
break;
}
case Element.JPEG:
case Element.IMGRAW:
case Element.IMGTEMPLATE: {
//carriageReturn(); suggestion by Marc Campforts
add((Image) element);
break;
}
case Element.GRAPHIC: {
Graphic graphic = (Graphic) element;
graphic.processAttributes(indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight);
graphics.add(graphic);
pageEmpty = false;
break;
}
default:
return false;
}
lastElementType = element.type();
return true;
}
catch(Exception e) {
throw new DocumentException(e);
}
}
// methods to add Content
/**
* Adds an image to the document.
* @param image the Image
to add
* @throws PdfException on error
* @throws DocumentException on error
*/
private void add(Image image) throws PdfException, DocumentException {
if (image.hasAbsolutePosition()) {
graphics.addImage(image);
pageEmpty = false;
return;
}
// if there isn't enough room for the image on this page, save it for the next page
if (currentHeight != 0 && indentTop() - currentHeight - image.scaledHeight() < indentBottom()) {
if (!strictImageSequence && imageWait == null) {
imageWait = image;
return;
}
newPage();
if (currentHeight != 0 && indentTop() - currentHeight - image.scaledHeight() < indentBottom()) {
imageWait = image;
return;
}
}
pageEmpty = false;
// avoid endless loops
if (image == imageWait)
imageWait = null;
boolean textwrap = (image.alignment() & Image.TEXTWRAP) == Image.TEXTWRAP
&& !((image.alignment() & Image.MIDDLE) == Image.MIDDLE);
boolean underlying = (image.alignment() & Image.UNDERLYING) == Image.UNDERLYING;
float diff = leading / 2;
if (textwrap) {
diff += leading;
}
float lowerleft = indentTop() - currentHeight - image.scaledHeight() -diff;
float mt[] = image.matrix();
float startPosition = indentLeft() - mt[4];
if ((image.alignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.scaledWidth() - mt[4];
if ((image.alignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + ((indentRight() - indentLeft() - image.scaledWidth()) / 2) - mt[4];
if (image.hasAbsoluteX()) startPosition = image.absoluteX();
if (textwrap) {
if (imageEnd < 0 || imageEnd < currentHeight + image.scaledHeight() + diff) {
imageEnd = currentHeight + image.scaledHeight() + diff;
}
if ((image.alignment() & Image.RIGHT) == Image.RIGHT) {
// indentation suggested by Pelikan Stephan
imageIndentRight += image.scaledWidth() + image.indentationLeft();
}
else {
// indentation suggested by Pelikan Stephan
imageIndentLeft += image.scaledWidth() + image.indentationRight();
}
}
else {
if ((image.alignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.indentationRight();
else if ((image.alignment() & Image.LEFT) == Image.LEFT) startPosition += image.indentationLeft();
else if ((image.alignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.indentationLeft() - image.indentationRight();
}
graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]);
if (!(textwrap || underlying)) {
currentHeight += image.scaledHeight() + diff;
flushLines();
text.moveText(0, - (image.scaledHeight() + diff));
newLine();
}
}
/**
* Initializes a page.
*
* If the footer/header is set, it is printed.
* @throws DocumentException on error
*/
private void initPage() throws DocumentException {
// initialisation of some page objects
markPoint = 0;
annotations = delayedAnnotations;
delayedAnnotations = new ArrayList();
pageResources = new PageResources();
writer.resetContent();
// the pagenumber is incremented
pageN++;
// graphics and text are initialized
float oldleading = leading;
int oldAlignment = alignment;
if (marginMirroring && (getPageNumber() & 1) == 0) {
marginRight = nextMarginLeft;
marginLeft = nextMarginRight;
}
else {
marginLeft = nextMarginLeft;
marginRight = nextMarginRight;
}
marginTop = nextMarginTop;
marginBottom = nextMarginBottom;
imageEnd = -1;
imageIndentRight = 0;
imageIndentLeft = 0;
graphics = new PdfContentByte(writer);
text = new PdfContentByte(writer);
text.beginText();
text.moveText(left(), top());
textEmptySize = text.size();
text.reset();
text.beginText();
leading = 16;
indentBottom = 0;
indentTop = 0;
currentHeight = 0;
// backgroundcolors, etc...
pageSize = nextPageSize;
thisBoxSize = new HashMap(boxSize);
if (pageSize.backgroundColor() != null
|| pageSize.hasBorders()
|| pageSize.borderColor() != null) {
add(pageSize);
}
// if there is a watermark, the watermark is added
if (watermark != null) {
float mt[] = watermark.matrix();
graphics.addImage(watermark, mt[0], mt[1], mt[2], mt[3], watermark.offsetX() - mt[4], watermark.offsetY() - mt[5]);
}
// if there is a footer, the footer is added
if (footer != null) {
/*
Added by Edgar Leonardo Prieto Perilla
*/
// Avoid footer identation
float tmpIndentLeft = indentLeft;
float tmpIndentRight = indentRight;
// Begin added: Bonf (Marc Schneider) 2003-07-29
float tmpListIndentLeft = listIndentLeft;
float tmpImageIndentLeft = imageIndentLeft;
float tmpImageIndentRight = imageIndentRight;
// End added: Bonf (Marc Schneider) 2003-07-29
indentLeft = indentRight = 0;
// Begin added: Bonf (Marc Schneider) 2003-07-29
listIndentLeft = 0;
imageIndentLeft = 0;
imageIndentRight = 0;
// End added: Bonf (Marc Schneider) 2003-07-29
/*
End Added by Edgar Leonardo Prieto Perilla
*/
footer.setPageNumber(pageN);
leading = footer.paragraph().leading();
add(footer.paragraph());
// adding the footer limits the height
indentBottom = currentHeight;
text.moveText(left(), indentBottom());
flushLines();
text.moveText(-left(), -bottom());
footer.setTop(bottom(currentHeight));
footer.setBottom(bottom() - (0.75f * leading));
footer.setLeft(left());
footer.setRight(right());
graphics.rectangle(footer);
indentBottom = currentHeight + leading * 2;
currentHeight = 0;
/*
Added by Edgar Leonardo Prieto Perilla
*/
indentLeft = tmpIndentLeft;
indentRight = tmpIndentRight;
// Begin added: Bonf (Marc Schneider) 2003-07-29
listIndentLeft = tmpListIndentLeft;
imageIndentLeft = tmpImageIndentLeft;
imageIndentRight = tmpImageIndentRight;
// End added: Bonf (Marc Schneider) 2003-07-29
/*
End Added by Edgar Leonardo Prieto Perilla
*/
}
// we move to the left/top position of the page
text.moveText(left(), top());
// if there is a header, the header = added
if (header != null) {
/*
Added by Edgar Leonardo Prieto Perilla
*/
// Avoid header identation
float tmpIndentLeft = indentLeft;
float tmpIndentRight = indentRight;
// Begin added: Bonf (Marc Schneider) 2003-07-29
float tmpListIndentLeft = listIndentLeft;
float tmpImageIndentLeft = imageIndentLeft;
float tmpImageIndentRight = imageIndentRight;
// End added: Bonf (Marc Schneider) 2003-07-29
indentLeft = indentRight = 0;
// Added: Bonf
listIndentLeft = 0;
imageIndentLeft = 0;
imageIndentRight = 0;
// End added: Bonf
/*
End Added by Edgar Leonardo Prieto Perilla
*/
header.setPageNumber(pageN);
leading = header.paragraph().leading();
text.moveText(0, leading);
add(header.paragraph());
newLine();
indentTop = currentHeight - leading;
header.setTop(top() + leading);
header.setBottom(indentTop() + leading * 2 / 3);
header.setLeft(left());
header.setRight(right());
graphics.rectangle(header);
flushLines();
currentHeight = 0;
/*
Added by Edgar Leonardo Prieto Perilla
*/
// Restore identation
indentLeft = tmpIndentLeft;
indentRight = tmpIndentRight;
// Begin added: Bonf (Marc Schneider) 2003-07-29
listIndentLeft = tmpListIndentLeft;
imageIndentLeft = tmpImageIndentLeft;
imageIndentRight = tmpImageIndentRight;
// End added: Bonf (Marc Schneider) 2003-07-29
/*
End Added by Edgar Leonardo Prieto Perilla
*/
}
pageEmpty = true;
// if there is an image waiting to be drawn, draw it
try {
if (imageWait != null) {
add(imageWait);
imageWait = null;
}
}
catch(Exception e) {
throw new ExceptionConverter(e);
}
leading = oldleading;
alignment = oldAlignment;
carriageReturn();
PdfPageEvent pageEvent = writer.getPageEvent();
if (pageEvent != null) {
if (firstPageEvent) {
pageEvent.onOpenDocument(writer, this);
}
pageEvent.onStartPage(writer, this);
}
firstPageEvent = false;
}
/**
* If the current line is not empty or null, it is added to the arraylist
* of lines and a new empty line is added.
* @throws DocumentException on error
*/
private void carriageReturn() throws DocumentException {
// the arraylist with lines may not be null
if (lines == null) {
lines = new ArrayList();
}
// If the current line is not null
if (line != null) {
// we check if the end of the page is reached (bugfix by Francois Gravel)
if (currentHeight + line.height() + leading < indentTop() - indentBottom()) {
// if so nonempty lines are added and the heigt is augmented
if (line.size() > 0) {
currentHeight += line.height();
lines.add(line);
pageEmpty = false;
}
}
// if the end of the line is reached, we start a new page
else {
newPage();
}
}
if (imageEnd > -1 && currentHeight > imageEnd) {
imageEnd = -1;
imageIndentRight = 0;
imageIndentLeft = 0;
}
// a new current line is constructed
line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
}
/**
* Adds the current line to the list of lines and also adds an empty line.
* @throws DocumentException on error
*/
private void newLine() throws DocumentException {
lastElementType = -1;
carriageReturn();
if (lines != null && lines.size() > 0) {
lines.add(line);
currentHeight += line.height();
}
line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
}
/**
* Writes all the lines to the text-object.
*
* @return the displacement that was caused
* @throws DocumentException on error
*/
private float flushLines() throws DocumentException {
// checks if the ArrayList with the lines is not null
if (lines == null) {
return 0;
}
//add by Jin-Hsia Yang
boolean newline=false;
//end add by Jin-Hsia Yang
// checks if a new Line has to be made.
if (line != null && line.size() > 0) {
lines.add(line);
line = new PdfLine(indentLeft(), indentRight(), alignment, leading);
//add by Jin-Hsia Yang
newline=true;
//end add by Jin-Hsia Yang
}
// checks if the ArrayList with the lines is empty
if (lines.size() == 0) {
return 0;
}
// initialisation of some parameters
Object currentValues[] = new Object[2];
PdfFont currentFont = null;
float displacement = 0;
PdfLine l;
Float lastBaseFactor = new Float(0);
currentValues[1] = lastBaseFactor;
// looping over all the lines
for (Iterator i = lines.iterator(); i.hasNext(); ) {
// this is a line in the loop
l = (PdfLine) i.next();
if(isNewpage && newline) { // fix Ken@PDI
newline=false;
text.moveText(l.indentLeft() - indentLeft() + listIndentLeft + paraIndent,-l.height());
}
else {
text.moveText(l.indentLeft() - indentLeft() + listIndentLeft, -l.height());
}
// is the line preceeded by a symbol?
if (l.listSymbol() != null) {
ColumnText.showTextAligned(graphics, Element.ALIGN_LEFT, new Phrase(l.listSymbol()), text.getXTLM() - l.listIndent(), text.getYTLM(), 0);
}
currentValues[0] = currentFont;
writeLineToContent(l, text, graphics, currentValues, writer.getSpaceCharRatio());
currentFont = (PdfFont)currentValues[0];
displacement += l.height();
if (indentLeft() - listIndentLeft != l.indentLeft()) {
text.moveText(indentLeft() - l.indentLeft() - listIndentLeft, 0);
}
}
lines = new ArrayList();
return displacement;
}
// methods to retrieve information
/**
* Gets the
* Before entering the line position must have been established and the
* PdfInfo
-object.
*
* @return PdfInfo
*/
PdfInfo getInfo() {
return info;
}
/**
* Gets the
PdfCatalog
-object.
*
* @param pages an indirect reference to this document pages
* @return PdfCatalog
*/
PdfCatalog getCatalog(PdfIndirectReference pages) {
PdfCatalog catalog;
if (rootOutline.getKids().size() > 0) {
catalog = new PdfCatalog(pages, rootOutline.indirectReference(), writer);
}
else
catalog = new PdfCatalog(pages, writer);
if (openActionName != null) {
PdfAction action = getLocalGotoAction(openActionName);
catalog.setOpenAction(action);
}
else if (openActionAction != null)
catalog.setOpenAction(openActionAction);
if (additionalActions != null) {
catalog.setAdditionalActions(additionalActions);
}
if (pageLabels != null)
catalog.setPageLabels(pageLabels);
catalog.addNames(localDestinations, documentJavaScript, documentFileAttachment, writer);
catalog.setViewerPreferences(viewerPreferences);
if (acroForm.isValid()) {
try {
catalog.setAcroForm(writer.addToBody(acroForm).getIndirectReference());
}
catch (IOException e) {
throw new ExceptionConverter(e);
}
}
return catalog;
}
// methods concerning the layout
/**
* Returns the bottomvalue of a Table
if it were added to this document.
*
* @param table the table that may or may not be added to this document
* @return a bottom value
*/
float bottom(Table table) {
// where will the table begin?
float h = (currentHeight > 0) ? indentTop() - currentHeight - 2f * leading : indentTop();
// constructing a PdfTable
PdfTable tmp = getPdfTable(table, false);
return tmp.bottom();
}
/**
* Checks if a PdfPTable
fits the current page of the PdfDocument
.
*
* @param table the table that has to be checked
* @param margin a certain margin
* @return true
if the PdfPTable
fits the page, false
otherwise.
*/
boolean fitsPage(PdfPTable table, float margin) {
if (!table.isLockedWidth()) {
float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100;
table.setTotalWidth(totalWidth);
}
// ensuring that a new line has been started.
ensureNewLine();
return table.getTotalHeight() <= indentTop() - currentHeight - indentBottom() - margin;
}
/**
* Gets the current vertical page position.
* @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
* for elements that do not terminate the lines they've started because those lines will get
* terminated.
* @return The current vertical page position.
*/
public float getVerticalPosition(boolean ensureNewLine) {
// ensuring that a new line has been started.
if (ensureNewLine) {
ensureNewLine();
}
return top() - currentHeight - indentTop;
}
/**
* Ensures that a new line has been started.
*/
private void ensureNewLine() {
try {
if ((lastElementType == Element.PHRASE) ||
(lastElementType == Element.CHUNK)) {
newLine();
flushLines();
}
} catch (DocumentException ex) {
throw new ExceptionConverter(ex);
}
}
/**
* Gets the indentation on the left side.
*
* @return a margin
*/
private float indentLeft() {
return left(indentLeft + listIndentLeft + imageIndentLeft);
}
/**
* Gets the indentation on the right side.
*
* @return a margin
*/
private float indentRight() {
return right(indentRight + imageIndentRight);
}
/**
* Gets the indentation on the top side.
*
* @return a margin
*/
private float indentTop() {
return top(indentTop);
}
/**
* Gets the indentation on the bottom side.
*
* @return a margin
*/
float indentBottom() {
return bottom(indentBottom);
}
/**
* Adds a named outline to the document .
* @param outline the outline to be added
* @param name the name of this local destination
*/
void addOutline(PdfOutline outline, String name) {
localDestination(name, outline.getPdfDestination());
}
/**
* Gets the AcroForm object.
* @return the PdfAcroform object of the PdfDocument
*/
public PdfAcroForm getAcroForm() {
return acroForm;
}
/**
* Gets the root outline. All the outlines must be created with a parent.
* The first level is created with this outline.
* @return the root outline
*/
public PdfOutline getRootOutline() {
return rootOutline;
}
/**
* Writes a text line to the document. It takes care of all the attributes.
* text
argument must be in text object scope (beginText()
).
* @param line the line to be written
* @param text the PdfContentByte
where the text will be written to
* @param graphics the PdfContentByte
where the graphics will be written to
* @param currentValues the current font and extra spacing values
* @param ratio
* @throws DocumentException on error
*/
void writeLineToContent(PdfLine line, PdfContentByte text, PdfContentByte graphics, Object currentValues[], float ratio) throws DocumentException {
PdfFont currentFont = (PdfFont)(currentValues[0]);
float lastBaseFactor = ((Float)(currentValues[1])).floatValue();
PdfChunk chunk;
int numberOfSpaces;
int lineLen;
boolean isJustified;
float hangingCorrection = 0;
float hScale = 1;
float lastHScale = Float.NaN;
float baseWordSpacing = 0;
float baseCharacterSpacing = 0;
numberOfSpaces = line.numberOfSpaces();
lineLen = line.toString().length();
// does the line need to be justified?
isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1);
if (isJustified) {
if (line.isNewlineSplit() && line.widthLeft() >= (lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1))) {
if (line.isRTL()) {
text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0);
}
baseWordSpacing = ratio * lastBaseFactor;
baseCharacterSpacing = lastBaseFactor;
}
else {
float width = line.widthLeft();
PdfChunk last = line.getChunk(line.size() - 1);
if (last != null) {
String s = last.toString();
char c;
if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) {
float oldWidth = width;
width += last.font().width(c) * 0.4f;
hangingCorrection = width - oldWidth;
}
}
float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1);
baseWordSpacing = ratio * baseFactor;
baseCharacterSpacing = baseFactor;
lastBaseFactor = baseFactor;
}
}
int lastChunkStroke = line.getLastStrokeChunk();
int chunkStrokeIdx = 0;
float xMarker = text.getXTLM();
float baseXMarker = xMarker;
float yMarker = text.getYTLM();
boolean adjustMatrix = false;
// looping over all the chunks in 1 line
for (Iterator j = line.iterator(); j.hasNext(); ) {
chunk = (PdfChunk) j.next();
Color color = chunk.color();
hScale = 1;
if (chunkStrokeIdx <= lastChunkStroke) {
float width;
if (isJustified) {
width = chunk.getWidthCorrected(baseCharacterSpacing, baseWordSpacing);
}
else
width = chunk.width();
if (chunk.isStroked()) {
PdfChunk nextChunk = line.getChunk(chunkStrokeIdx + 1);
if (chunk.isAttribute(Chunk.BACKGROUND)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.BACKGROUND))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
float fontSize = chunk.font().size();
float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
Object bgr[] = (Object[])chunk.getAttribute(Chunk.BACKGROUND);
graphics.setColorFill((Color)bgr[0]);
float extra[] = (float[])bgr[1];
graphics.rectangle(xMarker - extra[0],
yMarker + descender - extra[1] + chunk.getTextRise(),
width - subtract + extra[0] + extra[2],
ascender - descender + extra[1] + extra[3]);
graphics.fill();
graphics.setGrayFill(0);
}
if (chunk.isAttribute(Chunk.UNDERLINE)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.UNDERLINE))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
Object unders[][] = (Object[][])chunk.getAttribute(Chunk.UNDERLINE);
Color scolor = null;
for (int k = 0; k < unders.length; ++k) {
Object obj[] = unders[k];
scolor = (Color)obj[0];
float ps[] = (float[])obj[1];
if (scolor == null)
scolor = color;
if (scolor != null)
graphics.setColorStroke(scolor);
float fsize = chunk.font().size();
graphics.setLineWidth(ps[0] + fsize * ps[1]);
float shift = ps[2] + fsize * ps[3];
int cap2 = (int)ps[4];
if (cap2 != 0)
graphics.setLineCap(cap2);
graphics.moveTo(xMarker, yMarker + shift);
graphics.lineTo(xMarker + width - subtract, yMarker + shift);
graphics.stroke();
if (scolor != null)
graphics.resetGrayStroke();
if (cap2 != 0)
graphics.setLineCap(0);
}
graphics.setLineWidth(1);
}
if (chunk.isAttribute(Chunk.ACTION)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.ACTION))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
text.addAnnotation(new PdfAnnotation(writer, xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size(), (PdfAction)chunk.getAttribute(Chunk.ACTION)));
}
if (chunk.isAttribute(Chunk.REMOTEGOTO)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.REMOTEGOTO))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
Object obj[] = (Object[])chunk.getAttribute(Chunk.REMOTEGOTO);
String filename = (String)obj[0];
if (obj[1] instanceof String)
remoteGoto(filename, (String)obj[1], xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
else
remoteGoto(filename, ((Integer)obj[1]).intValue(), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
}
if (chunk.isAttribute(Chunk.LOCALGOTO)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALGOTO))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
localGoto((String)chunk.getAttribute(Chunk.LOCALGOTO), xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
}
if (chunk.isAttribute(Chunk.LOCALDESTINATION)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.LOCALDESTINATION))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
localDestination((String)chunk.getAttribute(Chunk.LOCALDESTINATION), new PdfDestination(PdfDestination.XYZ, xMarker, yMarker + chunk.font().size(), 0));
}
if (chunk.isAttribute(Chunk.GENERICTAG)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.GENERICTAG))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
Rectangle rect = new Rectangle(xMarker, yMarker, xMarker + width - subtract, yMarker + chunk.font().size());
PdfPageEvent pev = writer.getPageEvent();
if (pev != null)
pev.onGenericTag(writer, this, rect, (String)chunk.getAttribute(Chunk.GENERICTAG));
}
if (chunk.isAttribute(Chunk.PDFANNOTATION)) {
float subtract = lastBaseFactor;
if (nextChunk != null && nextChunk.isAttribute(Chunk.PDFANNOTATION))
subtract = 0;
if (nextChunk == null)
subtract += hangingCorrection;
float fontSize = chunk.font().size();
float ascender = chunk.font().getFont().getFontDescriptor(BaseFont.ASCENT, fontSize);
float descender = chunk.font().getFont().getFontDescriptor(BaseFont.DESCENT, fontSize);
PdfAnnotation annot = PdfFormField.shallowDuplicate((PdfAnnotation)chunk.getAttribute(Chunk.PDFANNOTATION));
annot.put(PdfName.RECT, new PdfRectangle(xMarker, yMarker + descender, xMarker + width - subtract, yMarker + ascender));
text.addAnnotation(annot);
}
float params[] = (float[])chunk.getAttribute(Chunk.SKEW);
Float hs = (Float)chunk.getAttribute(Chunk.HSCALE);
if (params != null || hs != null) {
float b = 0, c = 0;
if (params != null) {
b = params[0];
c = params[1];
}
if (hs != null)
hScale = hs.floatValue();
text.setTextMatrix(hScale, b, c, 1, xMarker, yMarker);
}
if (chunk.isImage()) {
Image image = chunk.getImage();
float matrix[] = image.matrix();
matrix[Image.CX] = xMarker + chunk.getImageOffsetX() - matrix[Image.CX];
matrix[Image.CY] = yMarker + chunk.getImageOffsetY() - matrix[Image.CY];
graphics.addImage(image, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
text.moveText(xMarker + lastBaseFactor + image.scaledWidth() - text.getXTLM(), 0);
}
}
xMarker += width;
++chunkStrokeIdx;
}
if (chunk.font().compareTo(currentFont) != 0) {
currentFont = chunk.font();
text.setFontAndSize(currentFont.getFont(), currentFont.size());
}
float rise = 0;
Object textRender[] = (Object[])chunk.getAttribute(Chunk.TEXTRENDERMODE);
int tr = 0;
float strokeWidth = 1;
Color strokeColor = null;
Float fr = (Float)chunk.getAttribute(Chunk.SUBSUPSCRIPT);
if (textRender != null) {
tr = ((Integer)textRender[0]).intValue() & 3;
if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
text.setTextRenderingMode(tr);
if (tr == PdfContentByte.TEXT_RENDER_MODE_STROKE || tr == PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE) {
strokeWidth = ((Float)textRender[1]).floatValue();
if (strokeWidth != 1)
text.setLineWidth(strokeWidth);
strokeColor = (Color)textRender[2];
if (strokeColor == null)
strokeColor = color;
if (strokeColor != null)
text.setColorStroke(strokeColor);
}
}
if (fr != null)
rise = fr.floatValue();
if (color != null)
text.setColorFill(color);
if (rise != 0)
text.setTextRise(rise);
if (chunk.isImage()) {
adjustMatrix = true;
}
// If it is a CJK chunk or Unicode TTF we will have to simulate the
// space adjustment.
else if (isJustified && numberOfSpaces > 0 && chunk.isSpecialEncoding()) {
if (hScale != lastHScale) {
lastHScale = hScale;
text.setWordSpacing(baseWordSpacing / hScale);
text.setCharacterSpacing(baseCharacterSpacing / hScale);
}
String s = chunk.toString();
int idx = s.indexOf(' ');
if (idx < 0)
text.showText(chunk.toString());
else {
float spaceCorrection = - baseWordSpacing * 1000f / chunk.font.size() / hScale;
PdfTextArray textArray = new PdfTextArray(s.substring(0, idx));
int lastIdx = idx;
while ((idx = s.indexOf(' ', lastIdx + 1)) >= 0) {
textArray.add(spaceCorrection);
textArray.add(s.substring(lastIdx, idx));
lastIdx = idx;
}
textArray.add(spaceCorrection);
textArray.add(s.substring(lastIdx));
text.showText(textArray);
}
}
else {
if (isJustified && hScale != lastHScale) {
lastHScale = hScale;
text.setWordSpacing(baseWordSpacing / hScale);
text.setCharacterSpacing(baseCharacterSpacing / hScale);
}
text.showText(chunk.toString());
}
if (rise != 0)
text.setTextRise(0);
if (color != null)
text.resetRGBColorFill();
if (tr != PdfContentByte.TEXT_RENDER_MODE_FILL)
text.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
if (strokeColor != null)
text.resetRGBColorStroke();
if (strokeWidth != 1)
text.setLineWidth(1);
if (chunk.isAttribute(Chunk.SKEW) || chunk.isAttribute(Chunk.HSCALE)) {
adjustMatrix = true;
text.setTextMatrix(xMarker, yMarker);
}
}
if (isJustified) {
text.setWordSpacing(0);
text.setCharacterSpacing(0);
if (line.isNewlineSplit())
lastBaseFactor = 0;
}
if (adjustMatrix)
text.moveText(baseXMarker - text.getXTLM(), 0);
currentValues[0] = currentFont;
currentValues[1] = new Float(lastBaseFactor);
}
/**
* Implements a link to other part of the document. The jump will
* be made to a local destination with the same name, that must exist.
* @param name the name for this link
* @param llx the lower left x corner of the activation area
* @param lly the lower left y corner of the activation area
* @param urx the upper right x corner of the activation area
* @param ury the upper right y corner of the activation area
*/
void localGoto(String name, float llx, float lly, float urx, float ury) {
PdfAction action = getLocalGotoAction(name);
annotations.add(new PdfAnnotation(writer, llx, lly, urx, ury, action));
}
PdfAction getLocalGotoAction(String name) {
PdfAction action;
Object obj[] = (Object[])localDestinations.get(name);
if (obj == null)
obj = new Object[3];
if (obj[0] == null) {
if (obj[1] == null) {
obj[1] = writer.getPdfIndirectReference();
}
action = new PdfAction((PdfIndirectReference)obj[1]);
obj[0] = action;
localDestinations.put(name, obj);
}
else {
action = (PdfAction)obj[0];
}
return action;
}
/**
* The local destination to where a local goto with the same
* name will jump to.
* @param name the name of this local destination
* @param destination the PdfDestination
with the jump coordinates
* @return true
if the local destination was added,
* false
if a local destination with the same name
* already existed
*/
boolean localDestination(String name, PdfDestination destination) {
Object obj[] = (Object[])localDestinations.get(name);
if (obj == null)
obj = new Object[3];
if (obj[2] != null)
return false;
obj[2] = destination;
localDestinations.put(name, obj);
destination.addPage(writer.getCurrentPage());
return true;
}
/**
* Implements a link to another document.
* @param filename the filename for the remote document
* @param name the name to jump to
* @param llx the lower left x corner of the activation area
* @param lly the lower left y corner of the activation area
* @param urx the upper right x corner of the activation area
* @param ury the upper right y corner of the activation area
*/
void remoteGoto(String filename, String name, float llx, float lly, float urx, float ury) {
annotations.add(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, name)));
}
/**
* Implements a link to another document.
* @param filename the filename for the remote document
* @param page the page to jump to
* @param llx the lower left x corner of the activation area
* @param lly the lower left y corner of the activation area
* @param urx the upper right x corner of the activation area
* @param ury the upper right y corner of the activation area
*/
void remoteGoto(String filename, int page, float llx, float lly, float urx, float ury) {
writer.addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, new PdfAction(filename, page)));
}
/** Sets the viewer preferences as the sum of several constants.
* @param preferences the viewer preferences
* @see PdfWriter#setViewerPreferences
*/
public void setViewerPreferences(int preferences) {
viewerPreferences |= preferences;
}
/** Implements an action in an area.
* @param action the PdfAction
* @param llx the lower left x corner of the activation area
* @param lly the lower left y corner of the activation area
* @param urx the upper right x corner of the activation area
* @param ury the upper right y corner of the activation area
*/
void setAction(PdfAction action, float llx, float lly, float urx, float ury) {
writer.addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
}
void setOpenAction(String name) {
openActionName = name;
openActionAction = null;
}
void setOpenAction(PdfAction action) {
openActionAction = action;
openActionName = null;
}
void addAdditionalAction(PdfName actionType, PdfAction action) {
if (additionalActions == null) {
additionalActions = new PdfDictionary();
}
if (action == null)
additionalActions.remove(actionType);
else
additionalActions.put(actionType, action);
if (additionalActions.size() == 0)
additionalActions = null;
}
void setPageLabels(PdfPageLabels pageLabels) {
this.pageLabels = pageLabels;
}
void addJavaScript(PdfAction js) {
if (js.get(PdfName.JS) == null)
throw new RuntimeException("Only JavaScript actions are allowed.");
try {
documentJavaScript.add(writer.addToBody(js).getIndirectReference());
}
catch (IOException e) {
throw new ExceptionConverter(e);
}
}
void setCropBoxSize(Rectangle crop) {
setBoxSize("crop", crop);
}
void setBoxSize(String boxName, Rectangle size) {
if (size == null)
boxSize.remove(boxName);
else
boxSize.put(boxName, new PdfRectangle(size));
}
void addCalculationOrder(PdfFormField formField) {
acroForm.addCalculationOrder(formField);
}
/**
* Gives the size of a trim, art, crop or bleed box, or null if not defined.
* @param boxName crop, trim, art or bleed
*/
Rectangle getBoxSize(String boxName) {
PdfRectangle r = (PdfRectangle)thisBoxSize.get(boxName);
if (r != null) return r.getRectangle();
return null;
}
void setSigFlags(int f) {
acroForm.setSigFlags(f);
}
void addFormFieldRaw(PdfFormField field) {
annotations.add(field);
ArrayList kids = field.getKids();
if (kids != null) {
for (int k = 0; k < kids.size(); ++k)
addFormFieldRaw((PdfFormField)kids.get(k));
}
}
void addAnnotation(PdfAnnotation annot) {
pageEmpty = false;
if (annot.isForm()) {
PdfFormField field = (PdfFormField)annot;
if (field.getParent() == null)
addFormFieldRaw(field);
}
else
annotations.add(annot);
}
/**
* Sets the display duration for the page (for presentations)
* @param seconds the number of seconds to display the page
*/
void setDuration(int seconds) {
if (seconds > 0)
this.duration=seconds;
else
this.duration=-1;
}
/**
* Sets the transition for the page
* @param transition the PdfTransition object
*/
void setTransition(PdfTransition transition) {
this.transition=transition;
}
void setPageAction(PdfName actionType, PdfAction action) {
if (pageAA == null) {
pageAA = new PdfDictionary();
}
pageAA.put(actionType, action);
}
/** Getter for property strictImageSequence.
* @return Value of property strictImageSequence.
*
*/
boolean isStrictImageSequence() {
return this.strictImageSequence;
}
/** Setter for property strictImageSequence.
* @param strictImageSequence New value of property strictImageSequence.
*
*/
void setStrictImageSequence(boolean strictImageSequence) {
this.strictImageSequence = strictImageSequence;
}
void setPageEmpty(boolean pageEmpty) {
this.pageEmpty = pageEmpty;
}
/**
* Method added by Pelikan Stephan
* @see com.lowagie.text.DocListener#clearTextWrap()
*/
public void clearTextWrap() throws DocumentException {
super.clearTextWrap();
float tmpHeight = imageEnd - currentHeight;
if (line != null) {
tmpHeight += line.height();
}
if ((imageEnd > -1) && (tmpHeight > 0)) {
carriageReturn();
currentHeight += tmpHeight;
}
}
ArrayList getDocumentJavaScript() {
return documentJavaScript;
}
/**
* @see com.lowagie.text.DocListener#setMarginMirroring(boolean)
*/
public boolean setMarginMirroring(boolean MarginMirroring) {
if (writer != null && writer.isPaused()) {
return false;
}
return super.setMarginMirroring(MarginMirroring);
}
void setThumbnail(Image image) throws PdfException, DocumentException {
thumb = writer.getImageReference(writer.addDirectImageSimple(image));
}
void addFileAttachment(String description, PdfFileSpecification fs) throws IOException {
if (description == null)
description = "";
fs.put(PdfName.DESC, new PdfString(description, PdfObject.TEXT_UNICODE));
if (description.length() == 0)
description = "Unnamed";
String fn = PdfEncodings.convertToString(new PdfString(description, PdfObject.TEXT_UNICODE).getBytes(), null);
int k = 0;
while (documentFileAttachment.containsKey(fn)) {
++k;
fn = PdfEncodings.convertToString(new PdfString(description + " " + k, PdfObject.TEXT_UNICODE).getBytes(), null);
}
documentFileAttachment.put(fn, fs.getReference());
}
HashMap getDocumentFileAttachment() {
return documentFileAttachment;
}
static PdfAnnotation convertAnnotation(PdfWriter writer, Annotation annot) throws IOException {
switch(annot.annotationType()) {
case Annotation.URL_NET:
return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((URL) annot.attributes().get(Annotation.URL)));
case Annotation.URL_AS_STRING:
return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.FILE)));
case Annotation.FILE_DEST:
return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.FILE), (String) annot.attributes().get(Annotation.DESTINATION)));
case Annotation.SCREEN:
boolean sparams[] = (boolean[])annot.attributes().get(Annotation.PARAMETERS);
String fname = (String) annot.attributes().get(Annotation.FILE);
String mimetype = (String) annot.attributes().get(Annotation.MIMETYPE);
PdfFileSpecification fs;
if (sparams[0])
fs = PdfFileSpecification.fileEmbedded(writer, fname, fname, null);
else
fs = PdfFileSpecification.fileExtern(writer, fname);
PdfAnnotation ann = PdfAnnotation.createScreen(writer, new Rectangle(annot.llx(), annot.lly(), annot.urx(), annot.ury()),
fname, fs, mimetype, sparams[1]);
return ann;
case Annotation.FILE_PAGE:
return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.FILE), ((Integer) annot.attributes().get(Annotation.PAGE)).intValue()));
case Annotation.NAMED_DEST:
return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction(((Integer) annot.attributes().get(Annotation.NAMED)).intValue()));
case Annotation.LAUNCH:
return new PdfAnnotation(writer, annot.llx(), annot.lly(), annot.urx(), annot.ury(), new PdfAction((String) annot.attributes().get(Annotation.APPLICATION),(String) annot.attributes().get(Annotation.PARAMETERS),(String) annot.attributes().get(Annotation.OPERATION),(String) annot.attributes().get(Annotation.DEFAULTDIR)));
default:
PdfDocument doc = writer.getPdfDocument();
if (doc.line == null)
return null;
PdfAnnotation an = new PdfAnnotation(writer, annot.llx(doc.indentRight() - doc.line.widthLeft()), annot.lly(doc.indentTop() - doc.currentHeight), annot.urx(doc.indentRight() - doc.line.widthLeft() + 20), annot.ury(doc.indentTop() - doc.currentHeight - 20), new PdfString(annot.title()), new PdfString(annot.content()));
return an;
}
}
/**
* @return an XmpMetadata byte array
*/
public byte[] createXmpMetadata() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
XmpWriter xmp = new XmpWriter(baos, getInfo());
xmp.close();
}
catch(IOException ioe) {
ioe.printStackTrace();
}
return baos.toByteArray();
}
int getMarkPoint() {
return markPoint;
}
void incMarkPoint() {
++markPoint;
}
}