PdfDocument.java
/*
* $Id: PdfDocument.java 4098 2009-11-16 13:27:45Z blowagie $
*
* 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:
* https://github.com/LibrePDF/OpenPDF
*/
package org.openpdf.text.pdf;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import org.openpdf.text.DocumentException;
import org.openpdf.text.Element;
import org.openpdf.text.ExceptionConverter;
import org.openpdf.text.Meta;
import org.openpdf.text.Rectangle;
import org.openpdf.text.error_messages.MessageLocalization;
import org.openpdf.text.Document;
/**
* <CODE>PdfDocument</CODE> is the class that is used by <CODE>PdfWriter</CODE> to translate a <CODE>Document</CODE>
* into a PDF with different pages.
* <p>
* This pruned version only manages pages (that are filled by the content of the writer), the information dictionary,
* the catalog and the annotations of the pages. The layout of text, tables and images is not supported.
*
* @see org.openpdf.text.Document
* @see PdfWriter
*/
public class PdfDocument extends Document {
/**
* The writer associated to this document.
*/
protected PdfWriter writer;
/**
* The <CODE>PdfContentByte</CODE> object that contains the text, the graphics and the images of the page.
*/
protected PdfContentByte text;
protected PdfContentByte graphics;
private int textEmptySize;
/**
* The info dictionary.
*/
protected PdfInfo info = new PdfInfo();
/**
* Margins and page size of the next page.
*/
private float nextMarginLeft;
private float nextMarginRight;
private float nextMarginTop;
private float nextMarginBottom;
private Rectangle nextPageSize = null;
/**
* The boxes of the pages (crop box...).
*/
protected HashMap<String, PdfRectangle> thisBoxSize = new LinkedHashMap<>();
protected HashMap<String, PdfRectangle> boxSize = new LinkedHashMap<>();
/**
* The resources of the current page.
*/
protected PageResources pageResources;
/**
* The annotations of the current page.
*/
private List<PdfAnnotation> annotations = new ArrayList<>();
/**
* Indicates if the current page is empty.
*/
private boolean pageEmpty = true;
/**
* Constructs a new PDF document.
*/
public PdfDocument() {
super();
addProducer();
addCreationDate();
}
/**
* Adds a <CODE>PdfWriter</CODE> to the <CODE>PdfDocument</CODE>.
*
* @param writer the <CODE>PdfWriter</CODE>
* @throws DocumentException on error
*/
public void addWriter(PdfWriter writer) throws DocumentException {
if (this.writer == null) {
this.writer = writer;
return;
}
throw new DocumentException(
MessageLocalization.getComposedMessage("you.can.only.add.a.writer.to.a.pdfdocument.once"));
}
/**
* Opens the document.
*/
@Override
public void open() {
if (!open) {
super.open();
writer.open();
}
try {
initPage();
} catch (DocumentException de) {
throw new ExceptionConverter(de);
}
}
/**
* Closes the document.
*/
@Override
public void close() {
if (close) {
return;
}
try {
newPage();
if (!annotations.isEmpty()) {
throw new RuntimeException(MessageLocalization.getComposedMessage(
"not.all.annotations.could.be.added.to.the.document.the.document.doesn.t.have.enough.pages"));
}
super.close();
} catch (Exception e) {
throw new ExceptionConverter(e);
}
writer.close();
}
/**
* Signals that an <CODE>Element</CODE> was added to the <CODE>Document</CODE>. Only the meta information is
* supported by this pruned version.
*
* @param element the element to add
* @return <CODE>true</CODE> if the element was added, <CODE>false</CODE> if not.
* @throws DocumentException when a document isn't open yet, or has been closed
*/
@Override
public boolean add(Element element) throws DocumentException {
if (writer != null && writer.isPaused()) {
return false;
}
switch (element.type()) {
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.PRODUCER:
info.addProducer(((Meta) element).getContent());
break;
case Element.CREATIONDATE:
// A PDF date (Document.addCreationDate(PdfDate)) is used as is, anything else
// only resets the creation date to now
if (((Meta) element).getContent().startsWith("D:"))
info.put(PdfName.CREATIONDATE, new PdfDate(((Meta) element).getContent()));
else
info.addCreationDate();
break;
case Element.MODIFICATIONDATE:
info.addModificationDate(new PdfDate(((Meta) element).getContent()));
break;
default:
return false;
}
return true;
}
/**
* Makes a new page and sends it to the <CODE>PdfWriter</CODE>.
*
* @return a <CODE>boolean</CODE>
*/
@Override
public boolean newPage() {
if (isPageEmpty()) {
setNewPageSizeAndMargins();
resetText();
return false;
}
if (!open || close) {
throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
}
try {
// page size and rotation
int rotation = pageSize.getRotation();
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);
page.put(PdfName.TABS, writer.getTabs());
if (writer.getUserunit() > 0f) {
page.put(PdfName.USERUNIT, new PdfNumber(writer.getUserunit()));
}
// we add the annotations
if (!annotations.isEmpty()) {
PdfArray array = writeAnnotations();
if (array.size() != 0) {
page.put(PdfName.ANNOTS, array);
}
}
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();
} catch (DocumentException | IOException de) {
// maybe this never happens, but it's better to check.
throw new ExceptionConverter(de);
}
return true;
}
/**
* Adds the annotations of the page to the body and returns the array of their references.
*/
private PdfArray writeAnnotations() throws IOException {
PdfArray array = new PdfArray();
int rotation = pageSize.getRotation() % 360;
for (PdfAnnotation dic : annotations) {
array.add(dic.getIndirectReference());
PdfRectangle rect = (PdfRectangle) dic.get(PdfName.RECT);
if (rect != null) {
switch (rotation) {
case 90:
dic.put(PdfName.RECT, new PdfRectangle(
pageSize.getTop() - rect.bottom(),
rect.left(),
pageSize.getTop() - rect.top(),
rect.right()));
break;
case 180:
dic.put(PdfName.RECT, new PdfRectangle(
pageSize.getRight() - rect.left(),
pageSize.getTop() - rect.bottom(),
pageSize.getRight() - rect.right(),
pageSize.getTop() - rect.top()));
break;
case 270:
dic.put(PdfName.RECT, new PdfRectangle(
rect.bottom(),
pageSize.getRight() - rect.left(),
rect.top(),
pageSize.getRight() - rect.right()));
break;
default:
break;
}
}
writer.addToBody(dic, dic.getIndirectReference());
}
annotations = new ArrayList<>();
return array;
}
/**
* Sets the pagesize.
*
* @param pageSize the new pagesize
* @return <CODE>true</CODE> if the page size was set
*/
@Override
public boolean setPageSize(Rectangle pageSize) {
if (writer != null && writer.isPaused()) {
return false;
}
nextPageSize = new Rectangle(pageSize);
return true;
}
/**
* 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 <CODE>boolean</CODE>
*/
@Override
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;
}
private void resetText() {
text = new PdfContentByte(writer);
text.reset();
text.beginText();
textEmptySize = text.size();
}
/**
* Initializes a page.
*
* @throws DocumentException on error
*/
protected void initPage() throws DocumentException {
// the pagenumber is incremented
pageN++;
// initialization of some page objects
pageResources = new PageResources();
writer.resetContent();
graphics = new PdfContentByte(writer);
resetText();
setNewPageSizeAndMargins();
thisBoxSize = new LinkedHashMap<>(boxSize);
pageEmpty = true;
}
protected void setNewPageSizeAndMargins() {
pageSize = nextPageSize;
marginLeft = nextMarginLeft;
marginRight = nextMarginRight;
marginTop = nextMarginTop;
marginBottom = nextMarginBottom;
}
/**
* Gets the info dictionary.
*
* @return the info dictionary
*/
protected PdfInfo getInfo() {
return info;
}
/**
* Gets the catalog.
*
* @param pages the reference of the root of the pages tree
* @return the catalog
*/
PdfCatalog getCatalog(PdfIndirectReference pages) {
PdfCatalog catalog = new PdfCatalog(pages, writer);
writer.getPdfVersion().addToCatalog(catalog);
return catalog;
}
/**
* Adds an annotation to the current page.
*
* @param annot the annotation
*/
void addAnnotation(PdfAnnotation annot) {
pageEmpty = false;
annotations.add(annot);
}
boolean isPageEmpty() {
return writer == null || (writer.getDirectContent().size() == 0 && writer.getDirectContentUnder().size() == 0
&& (pageEmpty || writer.isPaused()));
}
PageResources getPageResources() {
return pageResources;
}
/**
* Meta information of the document.
*/
public static class PdfInfo extends PdfDictionary {
PdfInfo() {
super();
}
void addTitle(String title) {
put(PdfName.TITLE, new PdfString(title, PdfObject.TEXT_UNICODE));
}
void addSubject(String subject) {
put(PdfName.SUBJECT, new PdfString(subject, PdfObject.TEXT_UNICODE));
}
void addKeywords(String keywords) {
put(PdfName.KEYWORDS, new PdfString(keywords, PdfObject.TEXT_UNICODE));
}
void addAuthor(String author) {
put(PdfName.AUTHOR, new PdfString(author, PdfObject.TEXT_UNICODE));
}
void addCreator(String creator) {
put(PdfName.CREATOR, new PdfString(creator, PdfObject.TEXT_UNICODE));
}
void addProducer(final String producer) {
put(PdfName.PRODUCER, new PdfString(producer));
}
void addCreationDate() {
PdfString date = new PdfDate();
put(PdfName.CREATIONDATE, date);
}
void addModificationDate(PdfDate date) {
put(PdfName.MODDATE, date);
}
}
/**
* The catalog of the document.
*/
static class PdfCatalog extends PdfDictionary {
PdfWriter writer;
PdfCatalog(PdfIndirectReference pages, PdfWriter writer) {
super(CATALOG);
this.writer = writer;
put(PdfName.PAGES, pages);
}
}
}