/*
* $Id: PdfDocument.java 5789 2013-05-07 10:05:20Z eugenemark $
*
* This file is part of the iText (R) project.
* Copyright (c) 1998-2012 1T3XT BVBA
* Authors: Bruno Lowagie, Paulo Soares, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
* 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA, 02110-1301 USA, or download the license from the following URL:
* http://itextpdf.com/terms-of-use/
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* In accordance with Section 7(b) of the GNU Affero General Public License,
* a covered work must retain the producer line in every PDF that is created
* or manipulated using iText.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the iText software without
* disclosing the source code of your own applications.
* These activities include: offering paid services to customers as an ASP,
* serving PDFs on the fly in a web application, shipping iText with a closed
* source product.
*
* For more information, please contact iText Software Corp. at this
* address: sales@itextpdf.com
*/
package com.lowagie.text.pdf;
import com.lowagie.text.*;
import com.lowagie.text.List;
import com.lowagie.text.api.WriterOperation;
import com.lowagie.text.error_messages.MessageLocalization;
import com.lowagie.text.pdf.collection.PdfCollection;
import com.lowagie.text.pdf.draw.DrawInterface;
import com.lowagie.text.pdf.interfaces.IAccessibleElement;
import com.lowagie.text.pdf.internal.PdfAnnotationsImp;
import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.*;
/**
* 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
* @since 2.0.8 (class was package-private before)
*/
public class PdfDocument extends Document {
/**
* 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)
* @since 2.0.8 (PdfDocument was package-private before)
*/
public static class PdfInfo extends PdfDictionary {
/**
* 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(final String author, final String title, final String subject) {
this();
addTitle(title);
addSubject(subject);
addAuthor(author);
}
/**
* Adds the title of the document.
*
* @param title the title of the document
*/
void addTitle(final String title) {
put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE));
}
/**
* Adds the subject to the document.
*
* @param subject the subject of the document
*/
void addSubject(final String subject) {
put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE));
}
/**
* Adds some keywords to the document.
*
* @param keywords the keywords of the document
*/
void addKeywords(final String keywords) {
put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE));
}
/**
* Adds the name of the author to the document.
*
* @param author the name of the author
*/
void addAuthor(final String author) {
put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE));
}
/**
* Adds the name of the creator to the document.
*
* @param creator the name of the creator
*/
void addCreator(final String creator) {
put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE));
}
/**
* Adds the name of the producer to the document.
*/
void addProducer() {
put(PdfName.PRODUCER, new PdfString(Version.getInstance().getVersion()));
}
/**
* Adds the date of creation to the document.
*/
void addCreationDate() {
PdfString date = new PdfDate();
put(PdfName.CREATIONDATE, date);
put(PdfName.MODDATE, date);
}
void addkey(final String key, final String value) {
if (key.equals("Producer") || key.equals("CreationDate"))
return;
put(new PdfName(key), new PdfString(value, PdfObject.TEXT_UNICODE));
}
}
/**
* 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 whether some location
* other than the first page should be shown when the document is opened.
* You have to open the document before you can begin to add content
* to the body of the document.
*/
@Override
public void open() {
if (!open) {
super.open();
writer.open();
rootOutline = new PdfOutline(writer);
currentOutline = rootOutline;
}
try {
initPage();
if (isTagged(writer)) {
openMCDocument = true;
}
}
catch(DocumentException de) {
throw new ExceptionConverter(de);
}
}
// [L2] DocListener interface
/**
* 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.
*/
@Override
public void close() {
if (close) {
return;
}
try {
if (isTagged(writer)) {
flushFloatingElements();
flushLines();
writer.getDirectContent().closeMCBlock(this);
writer.flushTaggedObjects();
if (isPageEmpty()) {
int pageReferenceCount = writer.pageReferences.size();
if (pageReferenceCount > 0 && writer.currentPageNumber == pageReferenceCount) {
writer.pageReferences.remove(pageReferenceCount - 1);
}
}
}
boolean wasImage = imageWait != null;
newPage();
if (imageWait != null || wasImage) newPage();
if (annotationsImp.hasUnusedAnnotations())
throw new RuntimeException(MessageLocalization.getComposedMessage("not.all.annotations.could.be.added.to.the.document.the.document.doesn.t.have.enough.pages"));
PdfPageEvent pageEvent = writer.getPageEvent();
if (pageEvent != null)
pageEvent.onCloseDocument(writer, this);
super.close();
writer.addLocalDestinations(localDestinations);
calculateOutlineCount();
writeOutlines();
}
catch(Exception e) {
throw ExceptionConverter.convertException(e);
}
writer.close();
}
// [L3] DocListener interface
protected int textEmptySize;
// [C9] Metadata for the page
/**
* Use this method to set the XMP Metadata.
* @param xmpMetadata The xmpMetadata to set.
* @throws IOException
*/
public void setXmpMetadata(final byte[] xmpMetadata) throws IOException {
PdfStream xmp = new PdfStream(xmpMetadata);
xmp.put(PdfName.TYPE, PdfName.METADATA);
xmp.put(PdfName.SUBTYPE, PdfName.XML);
PdfEncryption crypto = writer.getEncryption();
if (crypto != null && !crypto.isMetadataEncrypted()) {
PdfArray ar = new PdfArray();
ar.add(PdfName.CRYPT);
xmp.put(PdfName.FILTER, ar);
}
writer.addPageDictEntry(PdfName.METADATA, writer.addToBody(xmp).getIndirectReference());
}
/**
* Makes a new page and sends it to the
* If the footer/header is set, it is printed.
* @throws DocumentException on error
*/
protected void initPage() throws DocumentException {
// the pagenumber is incremented
pageN++;
// initialization of some page objects
annotationsImp.resetAnnotations();
pageResources = new PageResources();
writer.resetContent();
if (isTagged(writer)) {
graphics = writer.getDirectContentUnder().getDuplicate();
writer.getDirectContent().duplicatedFrom = graphics;
} else {
graphics = new PdfContentByte(writer);
}
markPoint = 0;
setNewPageSizeAndMargins();
imageEnd = -1;
indentation.imageIndentRight = 0;
indentation.imageIndentLeft = 0;
indentation.indentBottom = 0;
indentation.indentTop = 0;
currentHeight = 0;
// backgroundcolors, etc...
thisBoxSize = new HashMap
* Before entering the line position must have been established and the
*
* 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 {
/** The writer writing the PDF for which we are creating this catalog object. */
PdfWriter writer;
/**
* 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(final PdfIndirectReference pages, final PdfWriter writer) {
super(CATALOG);
this.writer = writer;
put(PdfName.PAGES, pages);
}
/**
* Adds the names of the named destinations to the catalog.
* @param localDestinations the local destinations
* @param documentLevelJS the javascript used in the document
* @param documentFileAttachment the attached files
* @param writer the writer the catalog applies to
*/
void addNames(final TreeMapPdfWriter
. */
protected PdfWriter writer;
protected HashMapPdfWriter
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(final PdfWriter writer) throws DocumentException {
if (this.writer == null) {
this.writer = writer;
annotationsImp = new PdfAnnotationsImp(writer);
return;
}
throw new DocumentException(MessageLocalization.getComposedMessage("you.can.only.add.a.writer.to.a.pdfdocument.once"));
}
// LISTENER METHODS START
// [L0] ElementListener interface
/** This is the PdfContentByte object, containing the text. */
protected PdfContentByte text;
/** This is the PdfContentByte object, containing the borders and other Graphics. */
protected PdfContentByte graphics;
/** This represents the leading of the lines. */
protected float leading = 0;
/**
* Getter for the current leading.
* @return the current leading
* @since 2.1.2
*/
public float getLeading() {
return leading;
}
/**
* Setter for the current leading.
* @param leading the current leading
* @since 2.1.6
*/
void setLeading(final float leading) {
this.leading = leading;
}
/** This represents the current alignment of the PDF Elements. */
protected int alignment = Element.ALIGN_LEFT;
/** This is the current height of the document. */
protected float currentHeight = 0;
/**
* Signals that onParagraph is valid (to avoid that a Chapter/Section title is treated as a Paragraph).
* @since 2.1.2
*/
protected boolean isSectionTitle = false;
/**
* Signals that the current leading has to be subtracted from a YMark object when positive.
* @since 2.1.2
*/
protected int leadingCount = 0;
/** The current active PdfAction
when processing an Anchor
. */
protected PdfAction anchorAction = null;
/**
* The current tab settings.
* @return the current
* @since 5.4.0
*/
protected TabSettings tabSettings;
/**
* Getter for the current tab stops.
* @since 5.4.0
*/
public TabSettings getTabSettings() {
return tabSettings;
}
/**
* Setter for the current tab stops.
* @param tabSettings the current tab settings
* @since 5.4.0
*/
public void setTabSettings(TabSettings tabSettings) {
this.tabSettings = tabSettings;
}
/**
* Signals that an 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
*/
@Override
public boolean add(final Element element) throws DocumentException {
if (writer != null && writer.isPaused()) {
return false;
}
try {
if (element.type() != Element.DIV) {
flushFloatingElements();
}
// TODO refactor this uber long switch to State/Strategy or something ...
switch(element.type()) {
// Information (headers)
case Element.HEADER:
info.addkey(((Meta)element).getName(), ((Meta)element).getContent());
break;
case Element.TITLE:
info.addTitle(((Meta)element).getContent());
break;
case Element.SUBJECT:
info.addSubject(((Meta)element).getContent());
break;
case Element.KEYWORDS:
info.addKeywords(((Meta)element).getContent());
break;
case Element.AUTHOR:
info.addAuthor(((Meta)element).getContent());
break;
case Element.CREATOR:
info.addCreator(((Meta)element).getContent());
break;
case Element.LANGUAGE:
setLanguage(((Meta)element).getContent());
break;
case Element.PRODUCER:
// you can not change the name of the producer
info.addProducer();
break;
case Element.CREATIONDATE:
// you can not set the creation date, only reset it
info.addCreationDate();
break;
// content (text)
case Element.CHUNK: {
// if there isn't a current line available, we make one
if (line == null) {
carriageReturn();
}
// we cast the element to a chunk
PdfChunk chunk = new PdfChunk((Chunk) element, anchorAction, tabSettings);
// we try to add the chunk to the line, until we succeed
{
PdfChunk overflow;
while ((overflow = line.add(chunk)) != null) {
carriageReturn();
boolean newlineSplit = chunk.isNewlineSplit();
chunk = overflow;
if (!newlineSplit)
chunk.trimFirstSpace();
}
}
pageEmpty = false;
if (chunk.isAttribute(Chunk.NEWPAGE)) {
newPage();
}
break;
}
case Element.ANCHOR: {
leadingCount++;
Anchor anchor = (Anchor) element;
String url = anchor.getReference();
leading = anchor.getLeading();
if (url != null) {
anchorAction = new PdfAction(url);
}
// we process the element
element.process(this);
anchorAction = null;
leadingCount--;
break;
}
case Element.ANNOTATION: {
if (line == null) {
carriageReturn();
}
Annotation annot = (Annotation) element;
Rectangle rect = new Rectangle(0, 0);
if (line != null)
rect = new Rectangle(annot.llx(indentRight() - line.widthLeft()), annot.ury(indentTop() - currentHeight - 20), annot.urx(indentRight() - line.widthLeft() + 20), annot.lly(indentTop() - currentHeight));
PdfAnnotation an = PdfAnnotationsImp.convertAnnotation(writer, annot, rect);
annotationsImp.addPlainAnnotation(an);
pageEmpty = false;
break;
}
case Element.PHRASE: {
leadingCount++;
TabSettings backupTabSettings = tabSettings;
if (((Phrase) element).getTabSettings() != null)
tabSettings = ((Phrase) element).getTabSettings();
// we cast the element to a phrase and set the leading of the document
leading = ((Phrase) element).getTotalLeading();
// we process the element
element.process(this);
tabSettings = backupTabSettings;
leadingCount--;
break;
}
case Element.PARAGRAPH: {
leadingCount++;
TabSettings backupTabSettings = tabSettings;
if (((Phrase) element).getTabSettings() != null)
tabSettings = ((Phrase) element).getTabSettings();
// we cast the element to a paragraph
Paragraph paragraph = (Paragraph) element;
if (isTagged(writer)) {
flushLines();
text.openMCBlock(paragraph);
}
addSpacing(paragraph.getSpacingBefore(), leading, paragraph.getFont());
// we adjust the parameters of the document
alignment = paragraph.getAlignment();
leading = paragraph.getTotalLeading();
carriageReturn();
// we don't want to make orphans/widows
if (currentHeight + line.height() + leading > indentTop() - indentBottom()) {
newPage();
}
indentation.indentLeft += paragraph.getIndentationLeft();
indentation.indentRight += paragraph.getIndentationRight();
carriageReturn();
PdfPageEvent pageEvent = writer.getPageEvent();
if (pageEvent != null && !isSectionTitle)
pageEvent.onParagraph(writer, this, indentTop() - currentHeight);
// if a paragraph has to be kept together, we wrap it in a table object
if (paragraph.getKeepTogether()) {
carriageReturn();
PdfPTable table = new PdfPTable(1);
table.setKeepTogether(paragraph.getKeepTogether());
table.setWidthPercentage(100f);
PdfPCell cell = new PdfPCell();
cell.addElement(paragraph);
cell.setBorder(Rectangle.NO_BORDER);
cell.setPadding(0);
table.addCell(cell);
indentation.indentLeft -= paragraph.getIndentationLeft();
indentation.indentRight -= paragraph.getIndentationRight();
this.add(table);
indentation.indentLeft += paragraph.getIndentationLeft();
indentation.indentRight += paragraph.getIndentationRight();
}
else {
line.setExtraIndent(paragraph.getFirstLineIndent());
element.process(this);
carriageReturn();
addSpacing(paragraph.getSpacingAfter(), paragraph.getTotalLeading(), paragraph.getFont());
}
if (pageEvent != null && !isSectionTitle)
pageEvent.onParagraphEnd(writer, this, indentTop() - currentHeight);
alignment = Element.ALIGN_LEFT;
indentation.indentLeft -= paragraph.getIndentationLeft();
indentation.indentRight -= paragraph.getIndentationRight();
carriageReturn();
tabSettings = backupTabSettings;
leadingCount--;
if (isTagged(writer)) {
flushLines();
text.closeMCBlock(paragraph);
}
break;
}
case Element.SECTION:
case Element.CHAPTER: {
// Chapters and Sections only differ in their constructor
// so we cast both to a Section
Section section = (Section) element;
PdfPageEvent pageEvent = writer.getPageEvent();
boolean hasTitle = section.isNotAddedYet()
&& section.getTitle() != null;
// if the section is a chapter, we begin a new page
if (section.isTriggerNewPage()) {
newPage();
}
if (hasTitle) {
float fith = indentTop() - currentHeight;
int rotation = pageSize.getRotation();
if (rotation == 90 || rotation == 180)
fith = pageSize.getHeight() - fith;
PdfDestination destination = new PdfDestination(PdfDestination.FITH, fith);
while (currentOutline.level() >= section.getDepth()) {
currentOutline = currentOutline.parent();
}
PdfOutline outline = new PdfOutline(currentOutline, destination, section.getBookmarkTitle(), section.isBookmarkOpen());
currentOutline = outline;
}
// some values are set
carriageReturn();
indentation.sectionIndentLeft += section.getIndentationLeft();
indentation.sectionIndentRight += section.getIndentationRight();
if (section.isNotAddedYet() && pageEvent != null)
if (element.type() == Element.CHAPTER)
pageEvent.onChapter(writer, this, indentTop() - currentHeight, section.getTitle());
else
pageEvent.onSection(writer, this, indentTop() - currentHeight, section.getDepth(), section.getTitle());
// the title of the section (if any has to be printed)
if (hasTitle) {
isSectionTitle = true;
add(section.getTitle());
isSectionTitle = false;
}
indentation.sectionIndentLeft += section.getIndentation();
// we process the section
element.process(this);
flushLines();
// some parameters are set back to normal again
indentation.sectionIndentLeft -= section.getIndentationLeft() + section.getIndentation();
indentation.sectionIndentRight -= section.getIndentationRight();
if (section.isComplete() && pageEvent != null)
if (element.type() == Element.CHAPTER)
pageEvent.onChapterEnd(writer, this, indentTop() - currentHeight);
else
pageEvent.onSectionEnd(writer, this, indentTop() - currentHeight);
break;
}
case Element.LIST: {
// we cast the element to a List
List list = (List) element;
if (isTagged(writer)) {
flushLines();
text.openMCBlock(list);
}
if (list.isAlignindent()) {
list.normalizeIndentation();
}
// we adjust the document
indentation.listIndentLeft += list.getIndentationLeft();
indentation.indentRight += list.getIndentationRight();
// we process the items in the list
element.process(this);
// some parameters are set back to normal again
indentation.listIndentLeft -= list.getIndentationLeft();
indentation.indentRight -= list.getIndentationRight();
carriageReturn();
if (isTagged(writer)) {
flushLines();
text.closeMCBlock(list);
}
break;
}
case Element.LISTITEM: {
leadingCount++;
// we cast the element to a ListItem
ListItem listItem = (ListItem) element;
if (isTagged(writer)) {
flushLines();
text.openMCBlock(listItem);
}
addSpacing(listItem.getSpacingBefore(), leading, listItem.getFont());
// we adjust the document
alignment = listItem.getAlignment();
indentation.listIndentLeft += listItem.getIndentationLeft();
indentation.indentRight += listItem.getIndentationRight();
leading = listItem.getTotalLeading();
carriageReturn();
// we prepare the current line to be able to show us the listsymbol
line.setListItem(listItem);
// we process the item
element.process(this);
addSpacing(listItem.getSpacingAfter(), listItem.getTotalLeading(), listItem.getFont());
// if the last line is justified, it should be aligned to the left
if (line.hasToBeJustified()) {
line.resetAlignment();
}
// some parameters are set back to normal again
carriageReturn();
indentation.listIndentLeft -= listItem.getIndentationLeft();
indentation.indentRight -= listItem.getIndentationRight();
leadingCount--;
if (isTagged(writer)) {
flushLines();
text.closeMCBlock(listItem.getListBody());
text.closeMCBlock(listItem);
}
break;
}
case Element.RECTANGLE: {
Rectangle rectangle = (Rectangle) element;
graphics.rectangle(rectangle);
pageEmpty = false;
break;
}
case Element.PTABLE: {
PdfPTable ptable = (PdfPTable)element;
if (ptable.size() <= ptable.getHeaderRows())
break; //nothing to do
// before every table, we add a new line and flush all lines
ensureNewLine();
flushLines();
addPTable(ptable);
pageEmpty = false;
newLine();
break;
}
case Element.JPEG:
case Element.JPEG2000:
case Element.JBIG2:
case Element.IMGRAW:
case Element.IMGTEMPLATE: {
//carriageReturn(); suggestion by Marc Campforts
add((Image) element);
break;
}
case Element.YMARK: {
DrawInterface zh = (DrawInterface)element;
zh.draw(graphics, indentLeft(), indentBottom(), indentRight(), indentTop(), indentTop() - currentHeight - (leadingCount > 0 ? leading : 0));
pageEmpty = false;
break;
}
case Element.MARKED: {
MarkedObject mo;
if (element instanceof MarkedSection) {
mo = ((MarkedSection)element).getTitle();
if (mo != null) {
mo.process(this);
}
}
mo = (MarkedObject)element;
mo.process(this);
break;
}
case Element.WRITABLE_DIRECT:
if (null != writer) {
((WriterOperation)element).write(writer, this);
}
break;
case Element.DIV:
ensureNewLine();
flushLines();
addDiv((PdfDiv)element);
pageEmpty = false;
//newLine();
break;
default:
return false;
}
lastElementType = element.type();
return true;
}
catch(Exception e) {
throw new DocumentException(e);
}
}
// [L1] DocListener interface
/**
* Opens the document.
* PdfWriter
.
*
* @return a boolean
*/
@Override
public boolean newPage() {
try {
flushFloatingElements();
} catch (DocumentException de) {
// maybe this never happens, but it's better to check.
throw new ExceptionConverter(de);
}
lastElementType = -1;
if (isPageEmpty()) {
setNewPageSizeAndMargins();
return false;
}
if (!open || close) {
throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
}
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
indentation.imageIndentLeft = 0;
indentation.imageIndentRight = 0;
try {
// we flush the arraylist with recently written lines
flushLines();
// we prepare the elements of the page dictionary
// [U1] page size and rotation
int rotation = pageSize.getRotation();
// [C10]
if (writer.isPdfIso()) {
if (thisBoxSize.containsKey("art") && thisBoxSize.containsKey("trim"))
throw new PdfXConformanceException(MessageLocalization.getComposedMessage("only.one.of.artbox.or.trimbox.can.exist.in.the.page"));
if (!thisBoxSize.containsKey("art") && !thisBoxSize.containsKey("trim")) {
if (thisBoxSize.containsKey("crop"))
thisBoxSize.put("trim", thisBoxSize.get("crop"));
else
thisBoxSize.put("trim", new PdfRectangle(pageSize, pageSize.getRotation()));
}
}
// [M1]
pageResources.addDefaultColorDiff(writer.getDefaultColorspace());
if (writer.isRgbTransparencyBlending()) {
PdfDictionary dcs = new PdfDictionary();
dcs.put(PdfName.CS, PdfName.DEVICERGB);
pageResources.addDefaultColorDiff(dcs);
}
PdfDictionary resources = pageResources.getResources();
// we create the page dictionary
PdfPage page = new PdfPage(new PdfRectangle(pageSize, rotation), thisBoxSize, resources, rotation);
if (isTagged(writer)) {
page.put(PdfName.TABS, PdfName.S);
} else {
page.put(PdfName.TABS, writer.getTabs());
}
page.putAll(writer.getPageDictEntries());
writer.resetPageDictEntries();
// we complete the page dictionary
// [U3] page actions: additional actions
if (pageAA != null) {
page.put(PdfName.AA, writer.addToBody(pageAA).getIndirectReference());
pageAA = null;
}
// [C5] and [C8] we add the annotations
if (annotationsImp.hasUnusedAnnotations()) {
PdfArray array = annotationsImp.rotateAnnotations(writer, pageSize);
if (array.size() != 0)
page.put(PdfName.ANNOTS, array);
}
// [F12] we add tag info
if (isTagged(writer))
page.put(PdfName.STRUCTPARENTS, new PdfNumber(writer.getCurrentPageNumber() - 1));
if (text.size() > textEmptySize || isTagged(writer))
text.endText();
else
text = null;
ArrayListtrue
if the page size was set
*/
@Override
public boolean setPageSize(final Rectangle pageSize) {
if (writer != null && writer.isPaused()) {
return false;
}
nextPageSize = new Rectangle(pageSize);
return true;
}
// [L5] DocListener interface
/** margin in x direction starting from the left. Will be valid in the next page */
protected float nextMarginLeft;
/** margin in x direction starting from the right. Will be valid in the next page */
protected float nextMarginRight;
/** margin in y direction starting from the top. Will be valid in the next page */
protected float nextMarginTop;
/** margin in y direction starting from the bottom. Will be valid in the next page */
protected float nextMarginBottom;
/**
* Sets the margins.
*
* @param marginLeft the margin on the left
* @param marginRight the margin on the right
* @param marginTop the margin on the top
* @param marginBottom the margin on the bottom
* @return a boolean
*/
@Override
public boolean setMargins(final float marginLeft, final float marginRight, final float marginTop, final float marginBottom) {
if (writer != null && writer.isPaused()) {
return false;
}
nextMarginLeft = marginLeft;
nextMarginRight = marginRight;
nextMarginTop = marginTop;
nextMarginBottom = marginBottom;
return true;
}
// [L6] DocListener interface
/**
* @see com.lowagie.text.DocListener#setMarginMirroring(boolean)
*/
@Override
public boolean setMarginMirroring(final boolean MarginMirroring) {
if (writer != null && writer.isPaused()) {
return false;
}
return super.setMarginMirroring(MarginMirroring);
}
/**
* @see com.lowagie.text.DocListener#setMarginMirroring(boolean)
* @since 2.1.6
*/
@Override
public boolean setMarginMirroringTopBottom(final boolean MarginMirroringTopBottom) {
if (writer != null && writer.isPaused()) {
return false;
}
return super.setMarginMirroringTopBottom(MarginMirroringTopBottom);
}
// [L7] DocListener interface
/**
* Sets the page number.
*
* @param pageN the new page number
*/
@Override
public void setPageCount(final int pageN) {
if (writer != null && writer.isPaused()) {
return;
}
super.setPageCount(pageN);
}
// [L8] DocListener interface
/**
* Sets the page number to 0.
*/
@Override
public void resetPageCount() {
if (writer != null && writer.isPaused()) {
return;
}
super.resetPageCount();
}
// DOCLISTENER METHODS END
/** Signals that OnOpenDocument should be called. */
protected boolean firstPageEvent = true;
/**
* Initializes a page.
* 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
* @since 5.0.3 returns a float instead of void
*/
float writeLineToContent(final PdfLine line, final PdfContentByte text, final PdfContentByte graphics, final Object currentValues[], final float ratio) throws DocumentException {
PdfFont currentFont = (PdfFont)currentValues[0];
float lastBaseFactor = ((Float)currentValues[1]).floatValue();
PdfChunk chunk;
int numberOfSpaces;
int lineLen;
boolean isJustified;
float hangingCorrection = 0;
float hScale = 1;
float lastHScale = Float.NaN;
float baseWordSpacing = 0;
float baseCharacterSpacing = 0;
float glueWidth = 0;
float lastX = text.getXTLM() + line.getOriginalWidth();
numberOfSpaces = line.numberOfSpaces();
lineLen = line.getLineLengthUtf32();
// does the line need to be justified?
isJustified = line.hasToBeJustified() && (numberOfSpaces != 0 || lineLen > 1);
int separatorCount = line.getSeparatorCount();
if (separatorCount > 0) {
glueWidth = line.widthLeft() / separatorCount;
}
else if (isJustified && separatorCount == 0) {
if (line.isNewlineSplit() && line.widthLeft() >= lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1)) {
if (line.isRTL()) {
text.moveText(line.widthLeft() - lastBaseFactor * (ratio * numberOfSpaces + lineLen - 1), 0);
}
baseWordSpacing = ratio * lastBaseFactor;
baseCharacterSpacing = lastBaseFactor;
}
else {
float width = line.widthLeft();
PdfChunk last = line.getChunk(line.size() - 1);
if (last != null) {
String s = last.toString();
char c;
if (s.length() > 0 && hangingPunctuation.indexOf((c = s.charAt(s.length() - 1))) >= 0) {
float oldWidth = width;
width += last.font().width(c) * 0.4f;
hangingCorrection = width - oldWidth;
}
}
float baseFactor = width / (ratio * numberOfSpaces + lineLen - 1);
baseWordSpacing = ratio * baseFactor;
baseCharacterSpacing = baseFactor;
lastBaseFactor = baseFactor;
}
}
else if (line.alignment == Element.ALIGN_LEFT || line.alignment == Element.ALIGN_UNDEFINED) {
lastX -= line.widthLeft();
}
int lastChunkStroke = line.getLastStrokeChunk();
int chunkStrokeIdx = 0;
float xMarker = text.getXTLM();
float baseXMarker = xMarker;
float yMarker = text.getYTLM();
boolean adjustMatrix = false;
float tabPosition = 0;
// looping over all the chunks in 1 line
for (IteratorPdfInfo
-object.
*
* @return PdfInfo
*/
PdfInfo getInfo() {
return info;
}
/**
* Gets the
PdfCatalog
-object.
*
* @param pages an indirect reference to this document pages
* @return PdfCatalog
*/
PdfCatalog getCatalog(final PdfIndirectReference pages) {
PdfCatalog catalog = new PdfCatalog(pages, writer);
// [C1] outlines
if (rootOutline.getKids().size() > 0) {
catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES);
catalog.put(PdfName.OUTLINES, rootOutline.indirectReference());
}
// [C2] version
writer.getPdfVersion().addToCatalog(catalog);
// [C3] preferences
viewerPreferences.addToCatalog(catalog);
// [C4] pagelabels
if (pageLabels != null) {
catalog.put(PdfName.PAGELABELS, pageLabels.getDictionary(writer));
}
// [C5] named objects
catalog.addNames(localDestinations, getDocumentLevelJS(), documentFileAttachment, writer);
// [C6] actions
if (openActionName != null) {
PdfAction action = getLocalGotoAction(openActionName);
catalog.setOpenAction(action);
}
else if (openActionAction != null)
catalog.setOpenAction(openActionAction);
if (additionalActions != null) {
catalog.setAdditionalActions(additionalActions);
}
// [C7] portable collections
if (collection != null) {
catalog.put(PdfName.COLLECTION, collection);
}
// [C8] AcroForm
if (annotationsImp.hasValidAcroForm()) {
try {
catalog.put(PdfName.ACROFORM, writer.addToBody(annotationsImp.getAcroForm()).getIndirectReference());
}
catch (IOException e) {
throw new ExceptionConverter(e);
}
}
if (language != null) {
catalog.put(PdfName.LANG, language);
}
return catalog;
}
// [C1] outlines
/** This is the root outline of the document. */
protected PdfOutline rootOutline;
/** This is the current PdfOutline
in the hierarchy of outlines. */
protected PdfOutline currentOutline;
/**
* Adds a named outline to the document .
* @param outline the outline to be added
* @param name the name of this local destination
*/
void addOutline(final PdfOutline outline, final String name) {
localDestination(name, outline.getPdfDestination());
}
/**
* Gets the root outline. All the outlines must be created with a parent.
* The first level is created with this outline.
* @return the root outline
*/
public PdfOutline getRootOutline() {
return rootOutline;
}
/**
* Updates the count in the outlines.
*/
void calculateOutlineCount() {
if (rootOutline.getKids().size() == 0)
return;
traverseOutlineCount(rootOutline);
}
/**
* Recursive method to update the count in the outlines.
*/
void traverseOutlineCount(final PdfOutline outline) {
ArrayListPdfAction
* @param llx the lower left x corner of the activation area
* @param lly the lower left y corner of the activation area
* @param urx the upper right x corner of the activation area
* @param ury the upper right y corner of the activation area
*/
void setAction(final PdfAction action, final float llx, final float lly, final float urx, final float ury) {
addAnnotation(new PdfAnnotation(writer, llx, lly, urx, ury, action));
}
/**
* Stores the destinations keyed by name. Value is a Destination
.
*/
protected TreeMapPdfDestination
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(final String name, final PdfDestination destination) {
Destination dest = localDestinations.get(name);
if (dest == null)
dest = new Destination();
if (dest.destination != null)
return false;
dest.destination = destination;
localDestinations.put(name, dest);
if (!destination.hasPage())
destination.addPage(writer.getCurrentPage());
return true;
}
/**
* Stores a list of document level JavaScript actions.
*/
int jsCounter;
protected HashMapImage
to add
* @throws PdfException on error
* @throws DocumentException on error
*/
protected void add(final Image image) throws PdfException, DocumentException {
if (image.hasAbsoluteY()) {
graphics.addImage(image);
pageEmpty = false;
return;
}
// if there isn't enough room for the image on this page, save it for the next page
if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
if (!strictImageSequence && imageWait == null) {
imageWait = image;
return;
}
newPage();
if (currentHeight != 0 && indentTop() - currentHeight - image.getScaledHeight() < indentBottom()) {
imageWait = image;
return;
}
}
pageEmpty = false;
// avoid endless loops
if (image == imageWait)
imageWait = null;
boolean textwrap = (image.getAlignment() & Image.TEXTWRAP) == Image.TEXTWRAP
&& !((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE);
boolean underlying = (image.getAlignment() & Image.UNDERLYING) == Image.UNDERLYING;
float diff = leading / 2;
if (textwrap) {
diff += leading;
}
float lowerleft = indentTop() - currentHeight - image.getScaledHeight() -diff;
float mt[] = image.matrix();
float startPosition = indentLeft() - mt[4];
if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition = indentRight() - image.getScaledWidth() - mt[4];
if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition = indentLeft() + (indentRight() - indentLeft() - image.getScaledWidth()) / 2 - mt[4];
if (image.hasAbsoluteX()) startPosition = image.getAbsoluteX();
if (textwrap) {
if (imageEnd < 0 || imageEnd < currentHeight + image.getScaledHeight() + diff) {
imageEnd = currentHeight + image.getScaledHeight() + diff;
}
if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) {
// indentation suggested by Pelikan Stephan
indentation.imageIndentRight += image.getScaledWidth() + image.getIndentationLeft();
}
else {
// indentation suggested by Pelikan Stephan
indentation.imageIndentLeft += image.getScaledWidth() + image.getIndentationRight();
}
}
else {
if ((image.getAlignment() & Image.RIGHT) == Image.RIGHT) startPosition -= image.getIndentationRight();
else if ((image.getAlignment() & Image.MIDDLE) == Image.MIDDLE) startPosition += image.getIndentationLeft() - image.getIndentationRight();
else startPosition += image.getIndentationLeft();
}
graphics.addImage(image, mt[0], mt[1], mt[2], mt[3], startPosition, lowerleft - mt[5]);
if (!(textwrap || underlying)) {
currentHeight += image.getScaledHeight() + diff;
flushLines();
text.moveText(0, - (image.getScaledHeight() + diff));
newLine();
}
}
// [M4] Adding a PdfPTable
/** Adds a PdfPTable
to the document.
* @param ptable the PdfPTable
to be added to the document.
* @throws DocumentException on error
*/
void addPTable(final PdfPTable ptable) throws DocumentException {
ColumnText ct = new ColumnText(isTagged(writer) ? text : writer.getDirectContent());
// if the table prefers to be on a single page, and it wouldn't
//fit on the current page, start a new page.
if (ptable.getKeepTogether() && !fitsPage(ptable, 0f) && currentHeight > 0) {
newPage();
}
if (currentHeight == 0) {
ct.setAdjustFirstLine(false);
}
ct.addElement(ptable);
boolean he = ptable.isHeadersInEvent();
ptable.setHeadersInEvent(true);
int loop = 0;
while (true) {
ct.setSimpleColumn(indentLeft(), indentBottom(), indentRight(), indentTop() - currentHeight);
int status = ct.go();
if ((status & ColumnText.NO_MORE_TEXT) != 0) {
if (isTagged(writer)) {
text.setTextMatrix(indentLeft(), ct.getYLine());
} else {
text.moveText(0, ct.getYLine() - indentTop() + currentHeight);
}
currentHeight = indentTop() - ct.getYLine();
break;
}
if (indentTop() - currentHeight == ct.getYLine())
++loop;
else
loop = 0;
if (loop == 3) {
throw new DocumentException(MessageLocalization.getComposedMessage("infinite.table.loop"));
}
newPage();
if (isTagged(writer)) {
ct.setCanvas(text);
}
}
ptable.setHeadersInEvent(he);
}
private ArrayListPdfPTable
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(final PdfPTable table, final float margin) {
if (!table.isLockedWidth()) {
float totalWidth = (indentRight() - indentLeft()) * table.getWidthPercentage() / 100;
table.setTotalWidth(totalWidth);
}
// ensuring that a new line has been started.
ensureNewLine();
Float spaceNeeded = table.isSkipFirstHeader() ? table.getTotalHeight() - table.getHeaderHeight() : table.getTotalHeight();
return spaceNeeded + (currentHeight > 0 ? table.spacingBefore() : 0f) <= indentTop() - currentHeight - indentBottom() - margin;
}
private static boolean isTagged(final PdfWriter writer) {
return (writer != null) && writer.isTagged();
}
private PdfLine getLastLine() {
if (lines.size() > 0)
return lines.get(lines.size() - 1);
else
return null;
}
/**
* @since 5.0.1
*/
public class Destination {
public PdfAction action;
public PdfIndirectReference reference;
public PdfDestination destination;
}
}