Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* $Id: PdfWriter.java 4095 2009-11-12 14:40:41Z blowagie $
*
* Copyright 1999, 2000, 2001, 2002 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 com.lowagie.text.pdf;
import com.lowagie.text.DocListener;
import com.lowagie.text.DocWriter;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.Image;
import com.lowagie.text.ImgJBIG2;
import com.lowagie.text.ImgWMF;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Table;
import com.lowagie.text.error_messages.MessageLocalization;
import com.lowagie.text.pdf.collection.PdfCollection;
import com.lowagie.text.pdf.events.PdfPageEventForwarder;
import com.lowagie.text.pdf.interfaces.PdfAnnotations;
import com.lowagie.text.pdf.interfaces.PdfDocumentActions;
import com.lowagie.text.pdf.interfaces.PdfEncryptionSettings;
import com.lowagie.text.pdf.interfaces.PdfPageActions;
import com.lowagie.text.pdf.interfaces.PdfRunDirection;
import com.lowagie.text.pdf.interfaces.PdfVersion;
import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
import com.lowagie.text.pdf.interfaces.PdfXConformance;
import com.lowagie.text.pdf.internal.PdfVersionImp;
import com.lowagie.text.pdf.internal.PdfXConformanceImp;
import com.lowagie.text.xml.xmp.XmpWriter;
import java.awt.Color;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* A DocWriter class for PDF.
*
* When this PdfWriter is added
* to a certain PdfDocument, the PDF representation of every Element
* added to this Document will be written to the outputstream.
*/
public class PdfWriter extends DocWriter implements
PdfViewerPreferences,
PdfEncryptionSettings,
PdfVersion,
PdfDocumentActions,
PdfPageActions,
PdfXConformance,
PdfRunDirection,
PdfAnnotations {
/**
* The highest generation number possible.
* @since iText 2.1.6
*/
public static final int GENERATION_MAX = 65535;
// INNER CLASSES
/**
* This class generates the structure of a PDF document.
*
* This class covers the third section of Chapter 5 in the 'Portable Document Format
* Reference Manual version 1.3' (page 55-60). It contains the body of a PDF document
* (section 5.14) and it can also generate a Cross-reference Table (section 5.15).
*
* @see PdfWriter
* @see PdfObject
* @see PdfIndirectObject
*/
public static class PdfBody {
// inner classes
/**
* PdfCrossReference is an entry in the PDF Cross-Reference table.
*/
public static class PdfCrossReference implements Comparable {
/**
* String template for cross-reference entry PDF representation.
*
* @see Formatter
*/
private static final String CROSS_REFERENCE_ENTRY_FORMAT = "%010d %05d %c \n";
// membervariables
private int type;
/** Byte offset in the PDF file. */
private long offset;
private int refnum;
/** generation of the object. */
private int generation;
// constructors
/**
* Constructs a cross-reference element for a PdfIndirectObject.
* @param refnum the reference number
* @param offset byte offset of the object
* @param generation generation number of the object
*/
public PdfCrossReference(int refnum, long offset, int generation) {
type = 0;
this.offset = offset;
this.refnum = refnum;
this.generation = generation;
}
/**
* Constructs a cross-reference element for a PdfIndirectObject.
* @param refnum the reference number
* @param offset byte offset of the object
*/
public PdfCrossReference(int refnum, long offset) {
type = 1;
this.offset = offset;
this.refnum = refnum;
this.generation = 0;
}
public PdfCrossReference(int type, int refnum, long offset, int generation) {
this.type = type;
this.offset = offset;
this.refnum = refnum;
this.generation = generation;
}
int getRefnum() {
return refnum;
}
/**
* Writes PDF representation of cross-reference entry to passed output stream.
*
* @param os Output stream this entry to write to
* @throws IOException If any I/O error occurs
*/
public void toPdf(OutputStream os) throws IOException {
// TODO: are generation number and 'In use' keyword bound that way?
final char inUse = generation == GENERATION_MAX ? 'f' : 'n';
os.write(getISOBytes(String.format(CROSS_REFERENCE_ENTRY_FORMAT, offset, generation, inUse)));
}
/**
* Writes PDF syntax to the OutputStream
* @param midSize the mid size
* @param os the OutputStream
* @throws IOException on error
*/
public void toPdf(int midSize, OutputStream os) throws IOException {
os.write((byte)type);
while (--midSize >= 0)
os.write((byte)((offset >>> (8 * midSize)) & 0xff));
os.write((byte)((generation >>> 8) & 0xff));
os.write((byte)(generation & 0xff));
}
/**
* Compares current {@link PdfCrossReference entry} with passed {@code reference} by PDF object number.
*/
@Override
public int compareTo(final PdfCrossReference reference) {
return Integer.compare(refnum, reference.refnum);
}
/**
* Checks if two entries are equal if their PDF object numbers are equal.
*
* @param obj Another cross-reference entry
* @return If null, not of type {@link PdfCrossReference} or object numbers are not equal,
* returns false; true otherwise
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PdfCrossReference)) {
return false;
}
final PdfCrossReference other = (PdfCrossReference)obj;
return refnum == other.refnum;
}
@Override
public int hashCode() {
return refnum;
}
}
private static final int OBJSINSTREAM = 200;
// membervariables
/** array containing the cross-reference table of the normal objects. */
private TreeSet xrefs;
private int refnum;
/** the current byte position in the body. */
private long position;
private PdfWriter writer;
private ByteBuffer index;
private ByteBuffer streamObjects;
private int currentObjNum;
private int numObj = 0;
// constructors
/**
* Constructs a new PdfBody.
* @param writer
*/
PdfBody(PdfWriter writer) {
xrefs = new TreeSet<>();
xrefs.add(new PdfCrossReference(0, 0, GENERATION_MAX));
position = writer.getOs().getCounter();
refnum = 1;
this.writer = writer;
}
// methods
void setRefnum(int refnum) {
this.refnum = refnum;
}
private PdfWriter.PdfBody.PdfCrossReference addToObjStm(PdfObject obj, int nObj) throws IOException {
if (numObj >= OBJSINSTREAM)
flushObjStm();
if (index == null) {
index = new ByteBuffer();
streamObjects = new ByteBuffer();
currentObjNum = getIndirectReferenceNumber();
numObj = 0;
}
int p = streamObjects.size();
int idx = numObj++;
PdfEncryption enc = writer.crypto;
writer.crypto = null;
obj.toPdf(writer, streamObjects);
writer.crypto = enc;
streamObjects.append(' ');
index.append(nObj).append(' ').append(p).append(' ');
return new PdfWriter.PdfBody.PdfCrossReference(2, nObj, currentObjNum, idx);
}
private void flushObjStm() throws IOException {
if (numObj == 0)
return;
int first = index.size();
index.append(streamObjects);
PdfStream stream = new PdfStream(index.toByteArray());
stream.flateCompress(writer.getCompressionLevel());
stream.put(PdfName.TYPE, PdfName.OBJSTM);
stream.put(PdfName.N, new PdfNumber(numObj));
stream.put(PdfName.FIRST, new PdfNumber(first));
add(stream, currentObjNum);
index = null;
streamObjects = null;
numObj = 0;
}
/**
* Adds a PdfObject to the body.
*
* This methods creates a PdfIndirectObject with a
* certain number, containing the given PdfObject.
* It also adds a PdfCrossReference for this object
* to an ArrayList that will be used to build the
* Cross-reference Table.
*
* @param object a PdfObject
* @return a PdfIndirectObject
* @throws IOException
*/
PdfIndirectObject add(PdfObject object) throws IOException {
return add(object, getIndirectReferenceNumber());
}
PdfIndirectObject add(PdfObject object, boolean inObjStm) throws IOException {
return add(object, getIndirectReferenceNumber(), inObjStm);
}
/**
* Gets a PdfIndirectReference for an object that will be created in the future.
* @return a PdfIndirectReference
*/
PdfIndirectReference getPdfIndirectReference() {
return new PdfIndirectReference(0, getIndirectReferenceNumber());
}
int getIndirectReferenceNumber() {
int n = refnum++;
xrefs.add(new PdfCrossReference(n, 0, GENERATION_MAX));
return n;
}
/**
* Adds a PdfObject to the body given an already existing
* PdfIndirectReference.
*
* This methods creates a PdfIndirectObject with the number given by
* ref, containing the given PdfObject.
* It also adds a PdfCrossReference for this object
* to an ArrayList that will be used to build the
* Cross-reference Table.
*
* @param object a PdfObject
* @param ref a PdfIndirectReference
* @return a PdfIndirectObject
* @throws IOException
*/
PdfIndirectObject add(PdfObject object, PdfIndirectReference ref) throws IOException {
return add(object, ref.getNumber());
}
PdfIndirectObject add(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException {
return add(object, ref.getNumber(), inObjStm);
}
PdfIndirectObject add(PdfObject object, int refNumber) throws IOException {
return add(object, refNumber, true); // to false
}
PdfIndirectObject add(PdfObject object, int refNumber, boolean inObjStm) throws IOException {
if (inObjStm && object.canBeInObjStm() && writer.isFullCompression()) {
PdfCrossReference pxref = addToObjStm(object, refNumber);
PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
if (!xrefs.add(pxref)) {
xrefs.remove(pxref);
xrefs.add(pxref);
}
return indirect;
}
else {
PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
PdfCrossReference pxref = new PdfCrossReference(refNumber, position);
if (!xrefs.add(pxref)) {
xrefs.remove(pxref);
xrefs.add(pxref);
}
indirect.writeTo(writer.getOs());
position = writer.getOs().getCounter();
return indirect;
}
}
/**
* Returns the offset of the Cross-Reference table.
*
* @return an offset
*/
long offset() {
return position;
}
/**
* Returns the total number of objects contained in the CrossReferenceTable of this Body.
*
* @return a number of objects
*/
int size() {
return Math.max((xrefs.last()).getRefnum() + 1, refnum);
}
/**
* Returns the CrossReferenceTable of the Body.
* @param os
* @param root
* @param info
* @param encryption
* @param fileID
* @param prevxref
* @throws IOException
*/
void writeCrossReferenceTable(OutputStream os, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int prevxref) throws IOException {
int refNumber = 0;
// Old-style xref tables limit object offsets to 10 digits
boolean useNewXrefFormat = writer.isFullCompression() || position > 9_999_999_999L;
if (useNewXrefFormat) {
flushObjStm();
refNumber = getIndirectReferenceNumber();
xrefs.add(new PdfCrossReference(refNumber, position));
}
PdfCrossReference entry = xrefs.first();
int first = entry.getRefnum();
int len = 0;
ArrayList sections = new ArrayList<>();
for (PdfCrossReference xref1 : xrefs) {
entry = xref1;
if (first + len == entry.getRefnum())
++len;
else {
sections.add(first);
sections.add(len);
first = entry.getRefnum();
len = 1;
}
}
sections.add(first);
sections.add(len);
PdfTrailer trailer = new PdfTrailer(size(), root, info, encryption, fileID, prevxref);
if (useNewXrefFormat) {
int mid = 8 - (Long.numberOfLeadingZeros(position) >> 3);
ByteBuffer buf = new ByteBuffer();
for (PdfCrossReference xref : xrefs) {
entry = xref;
entry.toPdf(mid, buf);
}
PdfStream xr = new PdfStream(buf.toByteArray());
buf = null;
xr.flateCompress(writer.getCompressionLevel());
xr.putAll(trailer);
xr.put(PdfName.W, new PdfArray(new int[]{1, mid, 2}));
xr.put(PdfName.TYPE, PdfName.XREF);
PdfArray idx = new PdfArray();
for (Integer section : sections) idx.add(new PdfNumber(section));
xr.put(PdfName.INDEX, idx);
PdfEncryption enc = writer.crypto;
writer.crypto = null;
PdfIndirectObject indirect = new PdfIndirectObject(refNumber, xr, writer);
indirect.writeTo(writer.getOs());
writer.crypto = enc;
}
else {
os.write(getISOBytes("xref\n"));
Iterator i = xrefs.iterator();
for (int k = 0; k < sections.size(); k += 2) {
first = sections.get(k);
len = sections.get(k + 1);
os.write(getISOBytes(String.valueOf(first)));
os.write(getISOBytes(" "));
os.write(getISOBytes(String.valueOf(len)));
os.write('\n');
while (len-- > 0) {
entry = i.next();
entry.toPdf(os);
}
}
// make the trailer
trailer.toPdf(writer, os);
}
}
}
/**
* PdfTrailer is the PDF Trailer object.
*
* This object is described in the 'Portable Document Format Reference Manual version 1.3'
* section 5.16 (page 59-60).
*/
static class PdfTrailer extends PdfDictionary {
// constructors
/**
* Constructs a PDF-Trailer.
*
* @param size the number of entries in the PdfCrossReferenceTable
* @param root an indirect reference to the root of the PDF document
* @param info an indirect reference to the info object of the PDF document
* @param encryption
* @param fileID
* @param prevxref
*/
PdfTrailer(int size, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int prevxref) {
put(PdfName.SIZE, new PdfNumber(size));
put(PdfName.ROOT, root);
if (info != null) {
put(PdfName.INFO, info);
}
if (encryption != null)
put(PdfName.ENCRYPT, encryption);
if (fileID != null)
put(PdfName.ID, fileID);
if (prevxref > 0)
put(PdfName.PREV, new PdfNumber(prevxref));
}
/**
* Returns the PDF representation of this PdfObject.
* @param writer
* @param os
* @throws IOException
*/
public void toPdf(PdfWriter writer, OutputStream os) throws IOException {
os.write(getISOBytes("trailer\n"));
super.toPdf(null, os);
os.write('\n');
}
}
// ESSENTIALS
// Construct a PdfWriter instance
/**
* Constructs a PdfWriter.
*/
protected PdfWriter() {
}
/**
* Constructs a PdfWriter.
*
* Remark: a PdfWriter can only be constructed by calling the method
* getInstance(Document document, OutputStream os).
*
* @param document The PdfDocument that has to be written
* @param os The OutputStream the writer has to write to.
*/
protected PdfWriter(PdfDocument document, OutputStream os) {
super(document, os);
pdf = document;
directContent = new PdfContentByte(this);
directContentUnder = new PdfContentByte(this);
}
/**
* Use this method to get an instance of the PdfWriter.
*
* @param document The Document that has to be written
* @param os The OutputStream the writer has to write to.
* @return a new PdfWriter
*
* @throws DocumentException on error
*/
public static PdfWriter getInstance(Document document, OutputStream os)
throws DocumentException {
PdfDocument pdf = new PdfDocument();
pdf.setTextRenderingOptions(document.getTextRenderingOptions());
document.addDocListener(pdf);
PdfWriter writer = new PdfWriter(pdf, os);
pdf.addWriter(writer);
return writer;
}
/**
* Use this method to get an instance of the PdfWriter.
*
* @return a new PdfWriter
* @param document The Document that has to be written
* @param os The OutputStream the writer has to write to.
* @param listener A DocListener to pass to the PdfDocument.
* @throws DocumentException on error
*/
public static PdfWriter getInstance(Document document, OutputStream os, DocListener listener)
throws DocumentException {
PdfDocument pdf = new PdfDocument();
pdf.setTextRenderingOptions(document.getTextRenderingOptions());
pdf.addDocListener(listener);
document.addDocListener(pdf);
PdfWriter writer = new PdfWriter(pdf, os);
pdf.addWriter(writer);
return writer;
}
// the PdfDocument instance
/** the pdfdocument object. */
protected PdfDocument pdf;
/**
* Gets the PdfDocument associated with this writer.
* @return the PdfDocument
*/
PdfDocument getPdfDocument() {
return pdf;
}
/**
* Use this method to get the info dictionary if you want to
* change it directly (add keys and values to the info dictionary).
* @return the info dictionary
*/
public PdfDictionary getInfo() {
return pdf.getInfo();
}
/**
* Use this method to get 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) {
return pdf.getVerticalPosition(ensureNewLine);
}
/**
* Sets the initial leading for the PDF document.
* This has to be done before the document is opened.
* @param leading the initial leading
* @since 2.1.6
* @throws DocumentException if you try setting the leading after the document was opened.
*/
public void setInitialLeading(float leading) throws DocumentException {
if (open)
throw new DocumentException(MessageLocalization.getComposedMessage("you.can.t.set.the.initial.leading.if.the.document.is.already.open"));
pdf.setLeading(leading);
}
// the PdfDirectContentByte instances
/*
* You should see Direct Content as a canvas on which you can draw
* graphics and text. One canvas goes on top of the page (getDirectContent),
* the other goes underneath (getDirectContentUnder).
* You can always the same object throughout your document,
* even if you have moved to a new page. Whatever you add on
* the canvas will be displayed on top or under the current page.
*/
/** The direct content in this document. */
protected PdfContentByte directContent;
/** The direct content under in this document. */
protected PdfContentByte directContentUnder;
/**
* Use this method to get the direct content for this document.
* There is only one direct content, multiple calls to this method
* will allways retrieve the same object.
* @return the direct content
*/
public PdfContentByte getDirectContent() {
if (!open)
throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
return directContent;
}
/**
* Use this method to get the direct content under for this document.
* There is only one direct content, multiple calls to this method
* will always retrieve the same object.
* @return the direct content
*/
public PdfContentByte getDirectContentUnder() {
if (!open)
throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
return directContentUnder;
}
/**
* Resets all the direct contents to empty.
* This happens when a new page is started.
*/
void resetContent() {
directContent.reset();
directContentUnder.reset();
}
// PDF body
/*
* A PDF file has 4 parts: a header, a body, a cross-reference table, and a trailer.
* The body contains all the PDF objects that make up the PDF document.
* Each element gets a reference (a set of numbers) and the byte position of
* every object is stored in the cross-reference table.
* Use these methods only if you know what you're doing.
*/
/** body of the PDF document */
protected PdfBody body;
/**
* Adds the local destinations to the body of the document.
* @param dest the HashMap containing the destinations
* @throws IOException on error
*/
void addLocalDestinations(TreeMap dest) throws IOException {
for (Object o : dest.entrySet()) {
Map.Entry entry = (Map.Entry) o;
String name = (String) entry.getKey();
Object[] obj = (Object[]) entry.getValue();
PdfDestination destination = (PdfDestination) obj[2];
if (obj[1] == null)
obj[1] = getPdfIndirectReference();
if (destination == null)
addToBody(new PdfString("invalid_" + name), (PdfIndirectReference) obj[1]);
else
addToBody(destination, (PdfIndirectReference) obj[1]);
}
}
/**
* Use this method to add a PDF object to the PDF body.
* Use this method only if you know what you're doing!
* @param object the PDF object to add
* @return a PdfIndirectObject
* @throws IOException thrown when an I/O operation fails
*/
public PdfIndirectObject addToBody(PdfObject object) throws IOException {
PdfIndirectObject iobj = body.add(object);
return iobj;
}
/**
* Use this method to add a PDF object to the PDF body.
* Use this method only if you know what you're doing!
* @param object the PDF object to add
* @param inObjStm a boolean
* @return a PdfIndirectObject
* @throws IOException thrown when an I/O operation fails
*/
public PdfIndirectObject addToBody(PdfObject object, boolean inObjStm) throws IOException {
PdfIndirectObject iobj = body.add(object, inObjStm);
return iobj;
}
/**
* Use this method to add a PDF object to the PDF body.
* Use this method only if you know what you're doing!
* @param object the object to add
* @param ref the IndirectReference of that object
* @return a PdfIndirectObject
* @throws IOException thrown when an I/O operation fails
*/
public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref) throws IOException {
PdfIndirectObject iobj = body.add(object, ref);
return iobj;
}
/**
* Use this method to add a PDF object to the PDF body.
* Use this method only if you know what you're doing!
* @param object the object to add
* @param ref the indirect reference
* @param inObjStm a boolean
* @return a PdfIndirectObject
* @throws IOException thrown when an I/O operation fails
*/
public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException {
PdfIndirectObject iobj = body.add(object, ref, inObjStm);
return iobj;
}
/**
* Use this method to add a PDF object to the PDF body.
* Use this method only if you know what you're doing!
* @param object the object to add
* @param refNumber the reference number
* @return a PdfIndirectObject
* @throws IOException thrown when an I/O operation fails
*/
public PdfIndirectObject addToBody(PdfObject object, int refNumber) throws IOException {
PdfIndirectObject iobj = body.add(object, refNumber);
return iobj;
}
/**
* Use this method to add a PDF object to the PDF body.
* Use this method only if you know what you're doing!
* @param object the object to add
* @param refNumber the reference number
* @param inObjStm a boolean
* @return a PdfIndirectObject
* @throws IOException thrown when an I/O operation fails
*/
public PdfIndirectObject addToBody(PdfObject object, int refNumber, boolean inObjStm) throws IOException {
PdfIndirectObject iobj = body.add(object, refNumber, inObjStm);
return iobj;
}
/**
* Use this to get an PdfIndirectReference for an object that
* will be created in the future.
* Use this method only if you know what you're doing!
* @return the PdfIndirectReference
*/
public PdfIndirectReference getPdfIndirectReference() {
return body.getPdfIndirectReference();
}
int getIndirectReferenceNumber() {
return body.getIndirectReferenceNumber();
}
/**
* Returns the outputStreamCounter.
* @return the outputStreamCounter
*/
OutputStreamCounter getOs() {
return os;
}
// PDF Catalog
/*
* The Catalog is also called the root object of the document.
* Whereas the Cross-Reference maps the objects number with the
* byte offset so that the viewer can find the objects, the
* Catalog tells the viewer the numbers of the objects needed
* to render the document.
*/
protected PdfDictionary getCatalog(PdfIndirectReference rootObj)
{
PdfDictionary catalog = pdf.getCatalog(rootObj);
// [F12] tagged PDF
if (tagged) {
try {
getStructureTreeRoot().buildTree();
}
catch (Exception e) {
throw new ExceptionConverter(e);
}
catalog.put(PdfName.STRUCTTREEROOT, structureTreeRoot.getReference());
PdfDictionary mi = new PdfDictionary();
mi.put(PdfName.MARKED, PdfBoolean.PDFTRUE);
if (userProperties)
mi.put(PdfName.USERPROPERTIES, PdfBoolean.PDFTRUE);
catalog.put(PdfName.MARKINFO, mi);
}
// [F13] OCG
if (!documentOCG.isEmpty()) {
fillOCProperties(false);
catalog.put(PdfName.OCPROPERTIES, OCProperties);
}
return catalog;
}
/** Holds value of property extraCatalog this is used for Output Intents. */
protected PdfDictionary extraCatalog;
/**
* Sets extra keys to the catalog.
* @return the catalog to change
*/
public PdfDictionary getExtraCatalog() {
if (extraCatalog == null)
extraCatalog = new PdfDictionary();
return this.extraCatalog;
}
// PdfPages
/*
* The page root keeps the complete page tree of the document.
* There's an entry in the Catalog that refers to the root
* of the page tree, the page tree contains the references
* to pages and other page trees.
*/
/** The root of the page tree. */
protected PdfPages root = new PdfPages(this);
/** The PdfIndirectReference to the pages. */
protected ArrayList pageReferences = new ArrayList<>();
/** The current page number. */
protected int currentPageNumber = 1;
/**
* The value of the Tabs entry in the page dictionary.
* @since 2.1.5
*/
protected PdfName tabs = null;
/**
* Use this method to make sure the page tree has a linear structure
* (every leave is attached directly to the root).
* Use this method to allow page reordering with method reorderPages.
*/
public void setLinearPageMode() {
root.setLinearMode(null);
}
/**
* Use this method to reorder the pages in the document.
* A null argument value only returns the number of pages to process.
* It is advisable to issue a Document.newPage() before using this method.
* @return the total number of pages
* @param order an array with the new page sequence. It must have the
* same size as the number of pages.
* @throws DocumentException if all the pages are not present in the array
*/
public int reorderPages(int[] order) throws DocumentException {
return root.reorderPages(order);
}
/**
* Use this method to get a reference to a page existing or not.
* If the page does not exist yet the reference will be created
* in advance. If on closing the document, a page number greater
* than the total number of pages was requested, an exception
* is thrown.
* @param page the page number. The first page is 1
* @return the reference to the page
*/
public PdfIndirectReference getPageReference(int page) {
--page;
if (page < 0)
throw new IndexOutOfBoundsException(MessageLocalization.getComposedMessage("the.page.number.must.be.gt.eq.1"));
PdfIndirectReference ref;
if (page < pageReferences.size()) {
ref = pageReferences.get(page);
if (ref == null) {
ref = body.getPdfIndirectReference();
pageReferences.set(page, ref);
}
}
else {
int empty = page - pageReferences.size();
for (int k = 0; k < empty; ++k)
pageReferences.add(null);
ref = body.getPdfIndirectReference();
pageReferences.add(ref);
}
return ref;
}
/**
* Gets the pagenumber of this document.
* This number can be different from the real pagenumber,
* if you have (re)set the page number previously.
* @return a page number
*/
public int getPageNumber() {
return pdf.getPageNumber();
}
PdfIndirectReference getCurrentPage() {
return getPageReference(currentPageNumber);
}
public int getCurrentPageNumber() {
return currentPageNumber;
}
/**
* Sets the value for the Tabs entry in the page tree.
* @param tabs Can be PdfName.R, PdfName.C or PdfName.S.
* Since the Adobe Extensions Level 3, it can also be PdfName.A
* or PdfName.W
* @since 2.1.5
*/
public void setTabs(PdfName tabs) {
this.tabs = tabs;
}
/**
* Returns the value to be used for the Tabs entry in the page tree.
* @since 2.1.5
* @return a PdfName of the value of the Tabs entry in the page dictionnary
*/
public PdfName getTabs() {
return tabs;
}
/**
* Adds some PdfContents to this Writer.
*
* The document has to be open before you can begin to add content
* to the body of the document.
*
* @return a PdfIndirectReference
* @param page the PdfPage to add
* @param contents the PdfContents of the page
* @throws PdfException on error
*/
PdfIndirectReference add(PdfPage page, PdfContents contents) throws PdfException {
if (!open) {
throw new PdfException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
}
PdfIndirectObject object;
try {
object = addToBody(contents);
}
catch(IOException ioe) {
throw new ExceptionConverter(ioe);
}
page.add(object.getIndirectReference());
// [U5]
if (group != null) {
page.put(PdfName.GROUP, group);
group = null;
}
else if (rgbTransparencyBlending) {
PdfDictionary pp = new PdfDictionary();
pp.put(PdfName.TYPE, PdfName.GROUP);
pp.put(PdfName.S, PdfName.TRANSPARENCY);
pp.put(PdfName.CS, PdfName.DEVICERGB);
page.put(PdfName.GROUP, pp);
}
root.addPage(page);
currentPageNumber++;
return null;
}
// page events
/*
* Page events are specific for iText, not for PDF.
* Upon specific events (for instance when a page starts
* or ends), the corresponding method in the page event
* implementation that is added to the writer is invoked.
*/
/** The PdfPageEvent for this document. */
private PdfPageEvent pageEvent;
/**
* Sets the PdfPageEvent for this document.
* @param event the PdfPageEvent for this document
*/
public void setPageEvent(PdfPageEvent event) {
if (event == null) this.pageEvent = null;
else if (this.pageEvent == null) this.pageEvent = event;
else if (this.pageEvent instanceof PdfPageEventForwarder) ((PdfPageEventForwarder)this.pageEvent).addPageEvent(event);
else {
PdfPageEventForwarder forward = new PdfPageEventForwarder();
forward.addPageEvent(this.pageEvent);
forward.addPageEvent(event);
this.pageEvent = forward;
}
}
/**
* Gets the PdfPageEvent for this document or null
* if none is set.
* @return the PdfPageEvent for this document or null
* if none is set
*/
public PdfPageEvent getPageEvent() {
return pageEvent;
}
// Open and Close methods + method that create the PDF
/** A number referring to the previous Cross-Reference Table. */
protected int prevxref = 0;
/**
* Signals that the Document has been opened and that
* Elements can be added.
*
* When this method is called, the PDF-document header is
* written to the outputstream.
* @see com.lowagie.text.DocWriter#open()
*/
public void open() {
super.open();
try {
pdf_version.writeHeader(os);
body = new PdfBody(this);
if (pdfxConformance.isPdfX32002()) {
PdfDictionary sec = new PdfDictionary();
sec.put(PdfName.GAMMA, new PdfArray(new float[]{2.2f,2.2f,2.2f}));
sec.put(PdfName.MATRIX, new PdfArray(new float[]{0.4124f,0.2126f,0.0193f,0.3576f,0.7152f,0.1192f,0.1805f,0.0722f,0.9505f}));
sec.put(PdfName.WHITEPOINT, new PdfArray(new float[]{0.9505f,1f,1.089f}));
PdfArray arr = new PdfArray(PdfName.CALRGB);
arr.add(sec);
setDefaultColorspace(PdfName.DEFAULTRGB, addToBody(arr).getIndirectReference());
}
}
catch(IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
/**
* Signals that the Document was closed and that no other
* Elements will be added.
*
* The pages-tree is built and written to the outputstream.
* A Catalog is constructed, as well as an Info-object,
* the reference table is composed and everything is written
* to the outputstream embedded in a Trailer.
* @see com.lowagie.text.DocWriter#close()
*/
@Override
public void close() {
if (open) {
if ((currentPageNumber - 1) != pageReferences.size())
// 2019-04-26: If you get this error, it could be that you are using OpenPDF or
// another library such as flying-saucer's ITextRenderer in a non-threadsafe way.
// ITextRenderer is not thread safe. So if you get this problem here, create a new
// instance, rather than re-using it.
// See: https://github.com/LibrePDF/OpenPDF/issues/164
throw new RuntimeException("The page " + pageReferences.size() +
" was requested but the document has only " + (currentPageNumber - 1) + " pages.");
try {
addSharedObjectsToBody();
// add the root to the body
PdfIndirectReference rootRef = root.writePageTree();
// make the catalog-object and add it to the body
PdfDictionary catalog = getCatalog(rootRef);
// [C9] if there is XMP data to add: add it
if (xmpMetadata != null) {
PdfStream xmp = new PdfStream(xmpMetadata);
xmp.put(PdfName.TYPE, PdfName.METADATA);
xmp.put(PdfName.SUBTYPE, PdfName.XML);
if (crypto != null && !crypto.isMetadataEncrypted()) {
PdfArray ar = new PdfArray();
ar.add(PdfName.CRYPT);
xmp.put(PdfName.FILTER, ar);
}
catalog.put(PdfName.METADATA, body.add(xmp).getIndirectReference());
}
// [C10] make pdfx conformant
if (isPdfX()) {
pdfxConformance.completeInfoDictionary(getInfo());
pdfxConformance.completeExtraCatalog(getExtraCatalog());
}
// [C11] Output Intents
if (extraCatalog != null) {
catalog.mergeDifferent(extraCatalog);
}
writeOutlines(catalog, false);
// add the Catalog to the body
PdfIndirectObject indirectCatalog = addToBody(catalog, false);
// add the info-object to the body
PdfIndirectObject infoObj = addToBody(getInfo(), false);
// [F1] encryption
PdfIndirectReference encryption = null;
PdfObject fileID = null;
body.flushObjStm();
if (crypto != null) {
PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false);
encryption = encryptionObject.getIndirectReference();
fileID = crypto.getFileID();
} else if (getInfo().contains(PdfName.FILEID)) {
fileID = getInfo().get(PdfName.FILEID);
} else {
// the same documentId shall be provided to the first version
byte[] documentId = PdfEncryption.createDocumentId();
fileID = PdfEncryption.createInfoId(documentId, documentId);
}
// write the cross-reference table of the body
body.writeCrossReferenceTable(os, indirectCatalog.getIndirectReference(),
infoObj.getIndirectReference(), encryption, fileID, prevxref);
os.write(getISOBytes("startxref\n"));
os.write(getISOBytes(String.valueOf(body.offset())));
os.write(getISOBytes("\n%%EOF\n"));
super.close();
}
catch(IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
}
protected void addSharedObjectsToBody() throws IOException {
// [F3] add the fonts
for (FontDetails details : documentFonts.values()) {
details.writeFont(this);
}
// [F4] add the form XObjects
for (Object[] objs : formXObjects.values()) {
PdfTemplate template = (PdfTemplate) objs[1];
if (template != null && template.getIndirectReference() instanceof PRIndirectReference)
continue;
if (template != null && template.getType() == PdfTemplate.TYPE_TEMPLATE) {
addToBody(template.getFormXObject(compressionLevel), template.getIndirectReference());
}
}
// [F5] add all the dependencies in the imported pages
for (PdfReaderInstance pdfReaderInstance : importedPages.values()) {
currentPdfReaderInstance = pdfReaderInstance;
currentPdfReaderInstance.writeAllPages();
}
currentPdfReaderInstance = null;
// [F6] add the spotcolors
for (ColorDetails color : documentColors.values()) {
addToBody(color.getSpotColor(this), color.getIndirectReference());
}
// [F7] add the pattern
for (PdfPatternPainter pat : documentPatterns.keySet()) {
addToBody(pat.getPattern(compressionLevel), pat.getIndirectReference());
}
// [F8] add the shading patterns
for (PdfShadingPattern shadingPattern : documentShadingPatterns.keySet()) {
shadingPattern.addToBody();
}
// [F9] add the shadings
for (PdfShading shading : documentShadings.keySet()) {
shading.addToBody();
}
// [F10] add the extgstate
for (Map.Entry entry : documentExtGState.entrySet()) {
PdfDictionary gstate = entry.getKey();
PdfObject[] obj = entry.getValue();
addToBody(gstate, (PdfIndirectReference) obj[1]);
}
// [F11] add the properties
for (Map.Entry