Java tutorial
/* * * This file is part of the iText (R) project. Copyright (c) 1998-2019 iText Group NV * 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 * ITEXT GROUP. ITEXT GROUP 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.itextpdf.text.pdf; import com.itextpdf.text.*; import com.itextpdf.text.error_messages.MessageLocalization; import com.itextpdf.text.io.TempFileCache; import com.itextpdf.text.log.Counter; import com.itextpdf.text.log.CounterFactory; import com.itextpdf.text.pdf.collection.PdfCollection; import com.itextpdf.text.pdf.events.PdfPageEventForwarder; import com.itextpdf.text.pdf.interfaces.*; import com.itextpdf.text.pdf.internal.PdfIsoKeys; import com.itextpdf.text.pdf.internal.PdfVersionImp; import com.itextpdf.text.pdf.internal.PdfXConformanceImp; import com.itextpdf.text.xml.xmp.PdfProperties; import com.itextpdf.text.xml.xmp.XmpWriter; import com.itextpdf.xmp.XMPConst; import com.itextpdf.xmp.XMPException; import com.itextpdf.xmp.options.PropertyOptions; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.security.cert.Certificate; import java.util.*; import java.util.List; /** * A <CODE>DocWriter</CODE> class for PDF. * <P> * When this <CODE>PdfWriter</CODE> is added * to a certain <CODE>PdfDocument</CODE>, the PDF representation of every Element * added to this Document will be written to the outputstream.</P> */ public class PdfWriter extends DocWriter implements PdfViewerPreferences, PdfEncryptionSettings, PdfVersion, PdfDocumentActions, PdfPageActions, 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. * <P> * 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 /** * <CODE>PdfCrossReference</CODE> is an entry in the PDF Cross-Reference table. */ static public class PdfCrossReference implements Comparable<PdfCrossReference> { // membervariables private final int type; /** Byte offset in the PDF file. */ private final long offset; private final int refnum; /** generation of the object. */ private final int generation; // constructors /** * Constructs a cross-reference element for a PdfIndirectObject. * @param refnum * @param offset byte offset of the object * @param generation generation number of the object */ public PdfCrossReference(final int refnum, final long offset, final int generation) { type = 0; this.offset = offset; this.refnum = refnum; this.generation = generation; } /** * Constructs a cross-reference element for a PdfIndirectObject. * @param refnum * @param offset byte offset of the object */ public PdfCrossReference(final int refnum, final long offset) { type = 1; this.offset = offset; this.refnum = refnum; this.generation = 0; } public PdfCrossReference(final int type, final int refnum, final long offset, final int generation) { this.type = type; this.offset = offset; this.refnum = refnum; this.generation = generation; } public int getRefnum() { return refnum; } /** * Returns the PDF representation of this <CODE>PdfObject</CODE>. * @param os * @throws IOException */ public void toPdf(final OutputStream os) throws IOException { StringBuffer off = new StringBuffer("0000000000").append(offset); off.delete(0, off.length() - 10); StringBuffer gen = new StringBuffer("00000").append(generation); gen.delete(0, gen.length() - 5); off.append(' ').append(gen).append(generation == GENERATION_MAX ? " f \n" : " n \n"); os.write(getISOBytes(off.toString())); } /** * Writes PDF syntax to the OutputStream * @param midSize * @param os * @throws IOException */ public void toPdf(int midSize, final 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)); } /** * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(final PdfCrossReference other) { return refnum < other.refnum ? -1 : refnum == other.refnum ? 0 : 1; } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (obj instanceof PdfCrossReference) { PdfCrossReference other = (PdfCrossReference) obj; return refnum == other.refnum; } else return false; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return refnum; } } private static final int OBJSINSTREAM = 200; // membervariables /** array containing the cross-reference table of the normal objects. */ protected final TreeSet<PdfCrossReference> xrefs; protected int refnum; /** the current byte position in the body. */ protected long position; protected final PdfWriter writer; protected ByteBuffer index; protected ByteBuffer streamObjects; protected int currentObjNum; protected int numObj = 0; // constructors /** * Constructs a new <CODE>PdfBody</CODE>. * @param writer */ protected PdfBody(final PdfWriter writer) { xrefs = new TreeSet<PdfCrossReference>(); xrefs.add(new PdfCrossReference(0, 0, GENERATION_MAX)); position = writer.getOs().getCounter(); refnum = 1; this.writer = writer; } // methods void setRefnum(final int refnum) { this.refnum = refnum; } protected PdfWriter.PdfBody.PdfCrossReference addToObjStm(final PdfObject obj, final 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); } public 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 <CODE>PdfObject</CODE> to the body. * <P> * This methods creates a <CODE>PdfIndirectObject</CODE> with a * certain number, containing the given <CODE>PdfObject</CODE>. * It also adds a <CODE>PdfCrossReference</CODE> for this object * to an <CODE>ArrayList</CODE> that will be used to build the * Cross-reference Table. * * @param object a <CODE>PdfObject</CODE> * @return a <CODE>PdfIndirectObject</CODE> * @throws IOException */ PdfIndirectObject add(final PdfObject object) throws IOException { return add(object, getIndirectReferenceNumber()); } PdfIndirectObject add(final PdfObject object, final boolean inObjStm) throws IOException { return add(object, getIndirectReferenceNumber(), 0, inObjStm); } /** * Gets a PdfIndirectReference for an object that will be created in the future. * @return a PdfIndirectReference */ public PdfIndirectReference getPdfIndirectReference() { return new PdfIndirectReference(0, getIndirectReferenceNumber()); } protected int getIndirectReferenceNumber() { int n = refnum++; xrefs.add(new PdfCrossReference(n, 0, GENERATION_MAX)); return n; } /** * Adds a <CODE>PdfObject</CODE> to the body given an already existing * PdfIndirectReference. * <P> * This methods creates a <CODE>PdfIndirectObject</CODE> with the number given by * <CODE>ref</CODE>, containing the given <CODE>PdfObject</CODE>. * It also adds a <CODE>PdfCrossReference</CODE> for this object * to an <CODE>ArrayList</CODE> that will be used to build the * Cross-reference Table. * * @param object a <CODE>PdfObject</CODE> * @param ref a <CODE>PdfIndirectReference</CODE> * @return a <CODE>PdfIndirectObject</CODE> * @throws IOException */ PdfIndirectObject add(final PdfObject object, final PdfIndirectReference ref) throws IOException { return add(object, ref, true); } PdfIndirectObject add(final PdfObject object, final PdfIndirectReference ref, final boolean inObjStm) throws IOException { return add(object, ref.getNumber(), ref.getGeneration(), inObjStm); } PdfIndirectObject add(final PdfObject object, final int refNumber) throws IOException { return add(object, refNumber, 0, true); // to false } protected PdfIndirectObject add(final PdfObject object, final int refNumber, final int generation, final 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; if (writer.isFullCompression()) { indirect = new PdfIndirectObject(refNumber, object, writer); write(indirect, refNumber); } else { indirect = new PdfIndirectObject(refNumber, generation, object, writer); write(indirect, refNumber, generation); } return indirect; } } protected void write(final PdfIndirectObject indirect, final int refNumber) throws IOException { PdfCrossReference pxref = new PdfCrossReference(refNumber, position); if (!xrefs.add(pxref)) { xrefs.remove(pxref); xrefs.add(pxref); } indirect.writeTo(writer.getOs()); position = writer.getOs().getCounter(); } protected void write(final PdfIndirectObject indirect, final int refNumber, final int generation) throws IOException { PdfCrossReference pxref = new PdfCrossReference(refNumber, position, generation); if (!xrefs.add(pxref)) { xrefs.remove(pxref); xrefs.add(pxref); } indirect.writeTo(writer.getOs()); position = writer.getOs().getCounter(); } /** * Returns the offset of the Cross-Reference table. * * @return an offset */ public long offset() { return position; } /** * Returns the total number of objects contained in the CrossReferenceTable of this <CODE>Body</CODE>. * * @return a number of objects */ public int size() { return Math.max(xrefs.last().getRefnum() + 1, refnum); } /** * Returns the CrossReferenceTable of the <CODE>Body</CODE>. * @param os * @param root * @param info * @param encryption * @param fileID * @param prevxref * @throws IOException */ public void writeCrossReferenceTable(final OutputStream os, final PdfIndirectReference root, final PdfIndirectReference info, final PdfIndirectReference encryption, final PdfObject fileID, final long prevxref) throws IOException { int refNumber = 0; if (writer.isFullCompression()) { flushObjStm(); refNumber = getIndirectReferenceNumber(); xrefs.add(new PdfCrossReference(refNumber, position)); } PdfCrossReference entry = xrefs.first(); int first = entry.getRefnum(); int len = 0; ArrayList<Integer> sections = new ArrayList<Integer>(); for (PdfCrossReference pdfCrossReference : xrefs) { entry = pdfCrossReference; if (first + len == entry.getRefnum()) ++len; else { sections.add(Integer.valueOf(first)); sections.add(Integer.valueOf(len)); first = entry.getRefnum(); len = 1; } } sections.add(Integer.valueOf(first)); sections.add(Integer.valueOf(len)); if (writer.isFullCompression()) { int mid = 5; long mask = 0xff00000000L; for (; mid > 1; --mid) { if ((mask & position) != 0) break; mask >>>= 8; } ByteBuffer buf = new ByteBuffer(); for (Object element : xrefs) { entry = (PdfCrossReference) element; entry.toPdf(mid, buf); } PdfStream xr = new PdfStream(buf.toByteArray()); buf = null; xr.flateCompress(writer.getCompressionLevel()); xr.put(PdfName.SIZE, new PdfNumber(size())); xr.put(PdfName.ROOT, root); if (info != null) { xr.put(PdfName.INFO, info); } if (encryption != null) xr.put(PdfName.ENCRYPT, encryption); if (fileID != null) xr.put(PdfName.ID, fileID); xr.put(PdfName.W, new PdfArray(new int[] { 1, mid, 2 })); xr.put(PdfName.TYPE, PdfName.XREF); PdfArray idx = new PdfArray(); for (int k = 0; k < sections.size(); ++k) idx.add(new PdfNumber(sections.get(k).intValue())); xr.put(PdfName.INDEX, idx); if (prevxref > 0) xr.put(PdfName.PREV, new PdfNumber(prevxref)); 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<PdfCrossReference> i = xrefs.iterator(); for (int k = 0; k < sections.size(); k += 2) { first = sections.get(k).intValue(); len = sections.get(k + 1).intValue(); 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); } } } } } /** * <CODE>PdfTrailer</CODE> is the PDF Trailer object. * <P> * This object is described in the 'Portable Document Format Reference Manual version 1.3' * section 5.16 (page 59-60). */ static public class PdfTrailer extends PdfDictionary { // membervariables long offset; // constructors /** * Constructs a PDF-Trailer. * * @param size the number of entries in the <CODE>PdfCrossReferenceTable</CODE> * @param offset offset of the <CODE>PdfCrossReferenceTable</CODE> * @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 */ public PdfTrailer(final int size, final long offset, final PdfIndirectReference root, final PdfIndirectReference info, final PdfIndirectReference encryption, final PdfObject fileID, final long prevxref) { this.offset = offset; 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 <CODE>PdfObject</CODE>. * @param writer * @param os * @throws IOException */ @Override public void toPdf(final PdfWriter writer, final OutputStream os) throws IOException { PdfWriter.checkPdfIsoConformance(writer, PdfIsoKeys.PDFISOKEY_TRAILER, this); os.write(getISOBytes("trailer\n")); super.toPdf(null, os); os.write('\n'); writeKeyInfo(os); os.write(getISOBytes("startxref\n")); os.write(getISOBytes(String.valueOf(offset))); os.write(getISOBytes("\n%%EOF\n")); } } // ESSENTIALS protected static Counter COUNTER = CounterFactory.getCounter(PdfWriter.class); protected Counter getCounter() { return COUNTER; } // Construct a PdfWriter instance /** * Constructs a <CODE>PdfWriter</CODE>. */ protected PdfWriter() { } /** * Constructs a <CODE>PdfWriter</CODE>. * <P> * Remark: a PdfWriter can only be constructed by calling the method * <CODE>getInstance(Document document, OutputStream os)</CODE>. * * @param document The <CODE>PdfDocument</CODE> that has to be written * @param os The <CODE>OutputStream</CODE> the writer has to write to. */ protected PdfWriter(final PdfDocument document, final OutputStream os) { super(document, os); pdf = document; directContentUnder = new PdfContentByte(this); directContent = directContentUnder.getDuplicate(); } /** * Use this method to get an instance of the <CODE>PdfWriter</CODE>. * * @param document The <CODE>Document</CODE> that has to be written * @param os The <CODE>OutputStream</CODE> the writer has to write to. * @return a new <CODE>PdfWriter</CODE> * * @throws DocumentException on error */ public static PdfWriter getInstance(final Document document, final OutputStream os) throws DocumentException { PdfDocument pdf = new PdfDocument(); document.addDocListener(pdf); PdfWriter writer = new PdfWriter(pdf, os); pdf.addWriter(writer); return writer; } /** * Use this method to get an instance of the <CODE>PdfWriter</CODE>. * * @return a new <CODE>PdfWriter</CODE> * @param document The <CODE>Document</CODE> that has to be written * @param os The <CODE>OutputStream</CODE> the writer has to write to. * @param listener A <CODE>DocListener</CODE> to pass to the PdfDocument. * @throws DocumentException on error */ public static PdfWriter getInstance(final Document document, final OutputStream os, final DocListener listener) throws DocumentException { PdfDocument pdf = new PdfDocument(); 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 <CODE>PdfDocument</CODE> associated with this writer. * @return the <CODE>PdfDocument</CODE> */ 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(final 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(final 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; protected ICC_Profile colorProfile; public ICC_Profile getColorProfile() { return colorProfile; } /** * Adds the local destinations to the body of the document. * @param desto the <CODE>HashMap</CODE> containing the destinations * @throws IOException on error */ void addLocalDestinations(final TreeMap<String, PdfDocument.Destination> desto) throws IOException { for (Map.Entry<String, PdfDocument.Destination> entry : desto.entrySet()) { String name = entry.getKey(); PdfDocument.Destination dest = entry.getValue(); PdfDestination destination = dest.destination; if (dest.reference == null) dest.reference = getPdfIndirectReference(); if (destination == null) addToBody(new PdfString("invalid_" + name), dest.reference); else addToBody(destination, dest.reference); } } /** * 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 * @return a PdfIndirectObject * @throws IOException */ public PdfIndirectObject addToBody(final PdfObject object) throws IOException { PdfIndirectObject iobj = body.add(object); cacheObject(iobj); 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 * @param inObjStm * @return a PdfIndirectObject * @throws IOException */ public PdfIndirectObject addToBody(final PdfObject object, final boolean inObjStm) throws IOException { PdfIndirectObject iobj = body.add(object, inObjStm); cacheObject(iobj); 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 * @param ref * @return a PdfIndirectObject * @throws IOException */ public PdfIndirectObject addToBody(final PdfObject object, final PdfIndirectReference ref) throws IOException { PdfIndirectObject iobj = body.add(object, ref); cacheObject(iobj); 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 * @param ref * @param inObjStm * @return a PdfIndirectObject * @throws IOException */ public PdfIndirectObject addToBody(final PdfObject object, final PdfIndirectReference ref, final boolean inObjStm) throws IOException { PdfIndirectObject iobj = body.add(object, ref, inObjStm); cacheObject(iobj); 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 * @param refNumber * @return a PdfIndirectObject * @throws IOException */ public PdfIndirectObject addToBody(final PdfObject object, final int refNumber) throws IOException { PdfIndirectObject iobj = body.add(object, refNumber); cacheObject(iobj); 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 * @param refNumber * @param inObjStm * @return a PdfIndirectObject * @throws IOException */ public PdfIndirectObject addToBody(final PdfObject object, final int refNumber, final boolean inObjStm) throws IOException { PdfIndirectObject iobj = body.add(object, refNumber, 0, inObjStm); cacheObject(iobj); return iobj; } /** * Use this method for caching objects. * @param iobj @see PdfIndirectObject */ protected void cacheObject(PdfIndirectObject iobj) { } /** * Use this to get an <CODE>PdfIndirectReference</CODE> for an object that * will be created in the future. * Use this method only if you know what you're doing! * @return the <CODE>PdfIndirectReference</CODE> */ public PdfIndirectReference getPdfIndirectReference() { return body.getPdfIndirectReference(); } protected int getIndirectReferenceNumber() { return body.getIndirectReferenceNumber(); } /** * Returns the outputStreamCounter. * @return the outputStreamCounter */ public 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(final PdfIndirectReference rootObj) { PdfDictionary catalog = pdf.getCatalog(rootObj); // [F12] tagged PDF buildStructTreeRootForTagged(catalog); // [F13] OCG if (!documentOCG.isEmpty()) { fillOCProperties(false); catalog.put(PdfName.OCPROPERTIES, OCProperties); } return catalog; } protected void buildStructTreeRootForTagged(PdfDictionary catalog) { if (tagged) { try { getStructureTreeRoot().buildTree(); for (AccessibleElementId elementId : pdf.getStructElements()) { PdfStructureElement element = pdf.getStructElement(elementId, false); addToBody(element, element.getReference()); } } 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); } } /** 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<PdfIndirectReference> pageReferences = new ArrayList<PdfIndirectReference>(); /** 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; /** * Additional page dictionary entries. * @since 5.1.0 */ protected PdfDictionary pageDictEntries = new PdfDictionary(); /** * Adds an additional entry for the page dictionary. * @param key the key * @param object the PdfObject for the given key * @since 5.1.0 */ public void addPageDictEntry(final PdfName key, final PdfObject object) { pageDictEntries.put(key, object); } /** * Gets the additional pageDictEntries. * @return the page dictionary entries * @since 5.1.0 */ public PdfDictionary getPageDictEntries() { return pageDictEntries; } /** * Resets the additional pageDictEntries. * @since 5.1.0 */ public void resetPageDictEntries() { pageDictEntries = new PdfDictionary(); } /** * 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 <CODE>null</CODE> argument value only returns the number of pages to process. * It is advisable to issue a <CODE>Document.newPage()</CODE> 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(final 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 Viewport for the next page. * @param vp an array consisting of Viewport dictionaries. * @since 5.1.0 */ public void setPageViewport(final PdfArray vp) { addPageDictEntry(PdfName.VP, vp); } /** * 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(final PdfName tabs) { this.tabs = tabs; } /** * Returns the value to be used for the Tabs entry in the page tree. * @return the Tabs PdfName * @since 2.1.5 */ public PdfName getTabs() { return tabs; } /** * Adds some <CODE>PdfContents</CODE> to this Writer. * <P> * The document has to be open before you can begin to add content * to the body of the document. * * @return a <CODE>PdfIndirectReference</CODE> * @param page the <CODE>PdfPage</CODE> to add * @param contents the <CODE>PdfContents</CODE> of the page * @throws PdfException on error */ PdfIndirectReference add(final PdfPage page, final 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 <CODE>PdfPageEvent</CODE> for this document. */ private PdfPageEvent pageEvent; /** * Sets the <CODE>PdfPageEvent</CODE> for this document. * @param event the <CODE>PdfPageEvent</CODE> for this document */ public void setPageEvent(final 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 <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE> * if none is set. * @return the <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE> * 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 long prevxref = 0; /** The original file ID (if present). */ protected byte[] originalFileID = null; /** * Signals that the <CODE>Document</CODE> has been opened and that * <CODE>Elements</CODE> can be added. * <P> * When this method is called, the PDF-document header is * written to the outputstream. * @see com.itextpdf.text.DocWriter#open() */ @Override public void open() { super.open(); try { pdf_version.writeHeader(os); body = new PdfBody(this); if (isPdfX() && ((PdfXConformanceImp) pdfIsoConformance).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 <CODE>Document</CODE> was closed and that no other * <CODE>Elements</CODE> will be added. * <P> * 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.itextpdf.text.DocWriter#close() */ @Override public void close() { if (open) { if (currentPageNumber - 1 != pageReferences.size()) throw new RuntimeException("The page " + pageReferences.size() + " was requested but the document has only " + (currentPageNumber - 1) + " pages."); pdf.close(); try { addSharedObjectsToBody(); for (PdfOCG layer : documentOCG) { addToBody(layer.getPdfObject(), layer.getRef()); } // add the root to the body PdfIndirectReference rootRef = root.writePageTree(); // make the catalog-object and add it to the body PdfDictionary catalog = getCatalog(rootRef); if (!documentOCG.isEmpty()) PdfWriter.checkPdfIsoConformance(this, PdfIsoKeys.PDFISOKEY_LAYER, OCProperties); // [C9] if there is XMP data to add: add it if (xmpMetadata == null && xmpWriter != null) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); xmpWriter.serialize(baos); xmpWriter.close(); xmpMetadata = baos.toByteArray(); } catch (IOException exc) { xmpWriter = null; } catch (XMPException exc) { xmpWriter = null; } } 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()); } getInfo().put(PdfName.PRODUCER, new PdfString(Version.getInstance().getVersion())); // [C10] make pdfx conformant if (isPdfX()) { completeInfoDictionary(getInfo()); 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(); boolean isModified = (originalFileID != null); if (crypto != null) { PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false); encryption = encryptionObject.getIndirectReference(); fileID = crypto.getFileID(isModified); } else { fileID = PdfEncryption.createInfoId( isModified ? originalFileID : PdfEncryption.createDocumentId(), isModified); } // write the cross-reference table of the body body.writeCrossReferenceTable(os, indirectCatalog.getIndirectReference(), infoObj.getIndirectReference(), encryption, fileID, prevxref); // make the trailer // [F2] full compression if (fullCompression) { writeKeyInfo(os); os.write(getISOBytes("startxref\n")); os.write(getISOBytes(String.valueOf(body.offset()))); os.write(getISOBytes("\n%%EOF\n")); } else { PdfTrailer trailer = new PdfTrailer(body.size(), body.offset(), indirectCatalog.getIndirectReference(), infoObj.getIndirectReference(), encryption, fileID, prevxref); trailer.toPdf(this, os); } } catch (IOException ioe) { throw new ExceptionConverter(ioe); } finally { super.close(); } } getCounter().written(os.getCounter()); } protected void addXFormsToBody() throws IOException { 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()); } } } protected void addSharedObjectsToBody() throws IOException { // [F3] add the fonts for (FontDetails details : documentFonts.values()) { details.writeFont(this); } // [F4] add the form XObjects addXFormsToBody(); // [F5] add all the dependencies in the imported pages for (PdfReaderInstance element : readerInstances.values()) { currentPdfReaderInstance = element; currentPdfReaderInstance.writeAllPages(); } currentPdfReaderInstance = null; // [F6] add the spotcolors for (ColorDetails color : documentColors.values()) { addToBody(color.getPdfObject(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) { shadingPattern.addToBody(); } // [F9] add the shadings for (PdfShading shading : documentShadings) { shading.addToBody(); } // [F10] add the extgstate for (Map.Entry<PdfDictionary, PdfObject[]> entry : documentExtGState.entrySet()) { PdfDictionary gstate = entry.getKey(); PdfObject obj[] = entry.getValue(); addToBody(gstate, (PdfIndirectReference) obj[1]); } // [F11] add the properties for (Map.Entry<Object, PdfObject[]> entry : documentProperties.entrySet()) { Object prop = entry.getKey(); PdfObject[] obj = entry.getValue(); if (prop instanceof PdfLayerMembership) { PdfLayerMembership layer = (PdfLayerMembership) prop; addToBody(layer.getPdfObject(), layer.getRef()); } else if (prop instanceof PdfDictionary && !(prop instanceof PdfLayer)) { addToBody((PdfDictionary) prop, (PdfIndirectReference) obj[1]); } } } // Root data for the PDF document (used when composing the Catalog) // [C1] Outlines (bookmarks) /** * Use this method to get the root outline * and construct bookmarks. * @return the root outline */ public PdfOutline getRootOutline() { return directContent.getRootOutline(); } protected List<HashMap<String, Object>> newBookmarks; /** * Sets the bookmarks. The list structure is defined in * {@link SimpleBookmark}. * @param outlines the bookmarks or <CODE>null</CODE> to remove any */ public void setOutlines(final List<HashMap<String, Object>> outlines) { newBookmarks = outlines; } protected void writeOutlines(final PdfDictionary catalog, final boolean namedAsNames) throws IOException { if (newBookmarks == null || newBookmarks.isEmpty()) return; PdfDictionary top = new PdfDictionary(); PdfIndirectReference topRef = getPdfIndirectReference(); Object kids[] = SimpleBookmark.iterateOutlines(this, topRef, newBookmarks, namedAsNames); top.put(PdfName.FIRST, (PdfIndirectReference) kids[0]); top.put(PdfName.LAST, (PdfIndirectReference) kids[1]); top.put(PdfName.COUNT, new PdfNumber(((Integer) kids[2]).intValue())); addToBody(top, topRef); catalog.put(PdfName.OUTLINES, topRef); } // [C2] PdfVersion interface /** possible PDF version (header) */ public static final char VERSION_1_2 = '2'; /** possible PDF version (header) */ public static final char VERSION_1_3 = '3'; /** possible PDF version (header) */ public static final char VERSION_1_4 = '4'; /** possible PDF version (header) */ public static final char VERSION_1_5 = '5'; /** possible PDF version (header) */ public static final char VERSION_1_6 = '6'; /** possible PDF version (header) */ public static final char VERSION_1_7 = '7'; /** possible PDF version (catalog) */ public static final PdfName PDF_VERSION_1_2 = new PdfName("1.2"); /** possible PDF version (catalog) */ public static final PdfName PDF_VERSION_1_3 = new PdfName("1.3"); /** possible PDF version (catalog) */ public static final PdfName PDF_VERSION_1_4 = new PdfName("1.4"); /** possible PDF version (catalog) */ public static final PdfName PDF_VERSION_1_5 = new PdfName("1.5"); /** possible PDF version (catalog) */ public static final PdfName PDF_VERSION_1_6 = new PdfName("1.6"); /** possible PDF version (catalog) */ public static final PdfName PDF_VERSION_1_7 = new PdfName("1.7"); /** Stores the version information for the header and the catalog. */ protected PdfVersionImp pdf_version = new PdfVersionImp(); /** @see com.itextpdf.text.pdf.interfaces.PdfVersion#setPdfVersion(char) */ public void setPdfVersion(final char version) { pdf_version.setPdfVersion(version); } /** @see com.itextpdf.text.pdf.interfaces.PdfVersion#setAtLeastPdfVersion(char) */ public void setAtLeastPdfVersion(final char version) { pdf_version.setAtLeastPdfVersion(version); } /** @see com.itextpdf.text.pdf.interfaces.PdfVersion#setPdfVersion(com.itextpdf.text.pdf.PdfName) */ public void setPdfVersion(final PdfName version) { pdf_version.setPdfVersion(version); } /** * @see com.itextpdf.text.pdf.interfaces.PdfVersion#addDeveloperExtension(com.itextpdf.text.pdf.PdfDeveloperExtension) * @since 2.1.6 */ public void addDeveloperExtension(final PdfDeveloperExtension de) { pdf_version.addDeveloperExtension(de); } /** * Returns the version information. * @return the PdfVersion */ PdfVersionImp getPdfVersion() { return pdf_version; } // [C3] PdfViewerPreferences interface // page layout (section 13.1.1 of "iText in Action") /** A viewer preference */ public static final int PageLayoutSinglePage = 1; /** A viewer preference */ public static final int PageLayoutOneColumn = 2; /** A viewer preference */ public static final int PageLayoutTwoColumnLeft = 4; /** A viewer preference */ public static final int PageLayoutTwoColumnRight = 8; /** A viewer preference */ public static final int PageLayoutTwoPageLeft = 16; /** A viewer preference */ public static final int PageLayoutTwoPageRight = 32; // page mode (section 13.1.2 of "iText in Action") /** A viewer preference */ public static final int PageModeUseNone = 64; /** A viewer preference */ public static final int PageModeUseOutlines = 128; /** A viewer preference */ public static final int PageModeUseThumbs = 256; /** A viewer preference */ public static final int PageModeFullScreen = 512; /** A viewer preference */ public static final int PageModeUseOC = 1024; /** A viewer preference */ public static final int PageModeUseAttachments = 2048; // values for setting viewer preferences in iText versions older than 2.x /** A viewer preference */ public static final int HideToolbar = 1 << 12; /** A viewer preference */ public static final int HideMenubar = 1 << 13; /** A viewer preference */ public static final int HideWindowUI = 1 << 14; /** A viewer preference */ public static final int FitWindow = 1 << 15; /** A viewer preference */ public static final int CenterWindow = 1 << 16; /** A viewer preference */ public static final int DisplayDocTitle = 1 << 17; /** A viewer preference */ public static final int NonFullScreenPageModeUseNone = 1 << 18; /** A viewer preference */ public static final int NonFullScreenPageModeUseOutlines = 1 << 19; /** A viewer preference */ public static final int NonFullScreenPageModeUseThumbs = 1 << 20; /** A viewer preference */ public static final int NonFullScreenPageModeUseOC = 1 << 21; /** A viewer preference */ public static final int DirectionL2R = 1 << 22; /** A viewer preference */ public static final int DirectionR2L = 1 << 23; /** A viewer preference */ public static final int PrintScalingNone = 1 << 24; /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */ public void setViewerPreferences(final int preferences) { pdf.setViewerPreferences(preferences); } /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfObject) */ public void addViewerPreference(final PdfName key, final PdfObject value) { pdf.addViewerPreference(key, value); } // [C4] Page labels /** * Use this method to add page labels * @param pageLabels the page labels */ public void setPageLabels(final PdfPageLabels pageLabels) { pdf.setPageLabels(pageLabels); } // [C5] named objects: named destinations, javascript, embedded files /** * Adds named destinations in bulk. * Valid keys and values of the map can be found in the map * that is created by SimpleNamedDestination. * @param map a map with strings as keys for the names, * and structured strings as values for the destinations * @param page_offset number of pages that has to be added to * the page numbers in the destinations (useful if you * use this method in combination with PdfCopy). * @since iText 5.0 */ public void addNamedDestinations(final Map<String, String> map, final int page_offset) { int page; String dest; PdfDestination destination; for (Map.Entry<String, String> entry : map.entrySet()) { dest = entry.getValue(); page = Integer.parseInt(dest.substring(0, dest.indexOf(" "))); destination = new PdfDestination(dest.substring(dest.indexOf(" ") + 1)); addNamedDestination(entry.getKey(), page + page_offset, destination); } } /** * Adds one named destination. * @param name the name for the destination * @param page the page number where you want to jump to * @param dest an explicit destination * @since iText 5.0 */ public void addNamedDestination(final String name, final int page, final PdfDestination dest) { PdfDestination d = new PdfDestination(dest); d.addPage(getPageReference(page)); pdf.localDestination(name, d); } /** * Use this method to add a JavaScript action at the document level. * When the document opens, all this JavaScript runs. * @param js The JavaScript action */ public void addJavaScript(final PdfAction js) { pdf.addJavaScript(js); } /** * Use this method to add a JavaScript action at the document level. * When the document opens, all this JavaScript runs. * @param code the JavaScript code * @param unicode select JavaScript unicode. Note that the internal * Acrobat JavaScript engine does not support unicode, * so this may or may not work for you */ public void addJavaScript(final String code, final boolean unicode) { addJavaScript(PdfAction.javaScript(code, this, unicode)); } /** * Use this method to adds a JavaScript action at the document level. * When the document opens, all this JavaScript runs. * @param code the JavaScript code */ public void addJavaScript(final String code) { addJavaScript(code, false); } /** * Use this method to add a JavaScript action at the document level. * When the document opens, all this JavaScript runs. * @param name The name of the JS Action in the name tree * @param js The JavaScript action */ public void addJavaScript(final String name, final PdfAction js) { pdf.addJavaScript(name, js); } /** * Use this method to add a JavaScript action at the document level. * When the document opens, all this JavaScript runs. * @param name The name of the JS Action in the name tree * @param code the JavaScript code * @param unicode select JavaScript unicode. Note that the internal * Acrobat JavaScript engine does not support unicode, * so this may or may not work for you */ public void addJavaScript(final String name, final String code, final boolean unicode) { addJavaScript(name, PdfAction.javaScript(code, this, unicode)); } /** * Use this method to adds a JavaScript action at the document level. * When the document opens, all this JavaScript runs. * @param name The name of the JS Action in the name tree * @param code the JavaScript code */ public void addJavaScript(final String name, final String code) { addJavaScript(name, code, false); } /** * Use this method to add a file attachment at the document level. * @param description the file description * @param fileStore an array with the file. If it's <CODE>null</CODE> * the file will be read from the disk * @param file the path to the file. It will only be used if * <CODE>fileStore</CODE> is not <CODE>null</CODE> * @param fileDisplay the actual file name stored in the pdf * @throws IOException on error */ public void addFileAttachment(final String description, final byte fileStore[], final String file, final String fileDisplay) throws IOException { addFileAttachment(description, PdfFileSpecification.fileEmbedded(this, file, fileDisplay, fileStore)); } /** * Use this method to add a file attachment at the document level. * @param description the file description * @param fs the file specification * @throws IOException if the file attachment could not be added to the document */ public void addFileAttachment(final String description, final PdfFileSpecification fs) throws IOException { pdf.addFileAttachment(description, fs); } /** * Use this method to add a file attachment at the document level. * @param fs the file specification * @throws IOException if the file attachment could not be added to the document */ public void addFileAttachment(final PdfFileSpecification fs) throws IOException { addFileAttachment(null, fs); } // [C6] Actions (open and additional) /** action value */ public static final PdfName DOCUMENT_CLOSE = PdfName.WC; /** action value */ public static final PdfName WILL_SAVE = PdfName.WS; /** action value */ public static final PdfName DID_SAVE = PdfName.DS; /** action value */ public static final PdfName WILL_PRINT = PdfName.WP; /** action value */ public static final PdfName DID_PRINT = PdfName.DP; /** @see com.itextpdf.text.pdf.interfaces.PdfDocumentActions#setOpenAction(java.lang.String) */ public void setOpenAction(final String name) { pdf.setOpenAction(name); } /** @see com.itextpdf.text.pdf.interfaces.PdfDocumentActions#setOpenAction(com.itextpdf.text.pdf.PdfAction) */ public void setOpenAction(final PdfAction action) { pdf.setOpenAction(action); } /** @see com.itextpdf.text.pdf.interfaces.PdfDocumentActions#setAdditionalAction(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfAction) */ public void setAdditionalAction(final PdfName actionType, final PdfAction action) throws DocumentException { if (!(actionType.equals(DOCUMENT_CLOSE) || actionType.equals(WILL_SAVE) || actionType.equals(DID_SAVE) || actionType.equals(WILL_PRINT) || actionType.equals(DID_PRINT))) { throw new DocumentException(MessageLocalization.getComposedMessage("invalid.additional.action.type.1", actionType.toString())); } pdf.addAdditionalAction(actionType, action); } // [C7] portable collections /** * Use this method to add the Collection dictionary. * @param collection a dictionary of type PdfCollection */ public void setCollection(final PdfCollection collection) { setAtLeastPdfVersion(VERSION_1_7); pdf.setCollection(collection); } // [C8] AcroForm /** signature value */ public static final int SIGNATURE_EXISTS = 1; /** signature value */ public static final int SIGNATURE_APPEND_ONLY = 2; /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#getAcroForm() */ public PdfAcroForm getAcroForm() { return pdf.getAcroForm(); } /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#addAnnotation(com.itextpdf.text.pdf.PdfAnnotation) */ public void addAnnotation(final PdfAnnotation annot) { pdf.addAnnotation(annot); } void addAnnotation(final PdfAnnotation annot, final int page) { addAnnotation(annot); } /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#addCalculationOrder(com.itextpdf.text.pdf.PdfFormField) */ public void addCalculationOrder(final PdfFormField annot) { pdf.addCalculationOrder(annot); } /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#setSigFlags(int) */ public void setSigFlags(final int f) { pdf.setSigFlags(f); } public void setLanguage(final String language) { pdf.setLanguage(language); } // [C9] Metadata /** XMP Metadata for the document. */ protected byte[] xmpMetadata = null; /** * Use this method to set the XMP Metadata. * @param xmpMetadata The xmpMetadata to set. */ public void setXmpMetadata(final byte[] xmpMetadata) { this.xmpMetadata = xmpMetadata; } /** * Use this method to set the XMP Metadata for each page. * @param xmpMetadata The xmpMetadata to set. * @throws IOException */ public void setPageXmpMetadata(final byte[] xmpMetadata) throws IOException { pdf.setXmpMetadata(xmpMetadata); } protected XmpWriter xmpWriter = null; public XmpWriter getXmpWriter() { return xmpWriter; } /** * Use this method to creates XMP Metadata based * on the metadata in the PdfDocument. * @since 5.4.4 just creates XmpWriter instance which will be serialized in close. */ public void createXmpMetadata() { try { xmpWriter = createXmpWriter(null, pdf.getInfo()); if (isTagged()) { try { xmpWriter.getXmpMeta().setPropertyInteger(XMPConst.NS_PDFUA_ID, PdfProperties.PART, 1, new PropertyOptions(PropertyOptions.SEPARATE_NODE)); } catch (XMPException e) { throw new ExceptionConverter(e); } } xmpMetadata = null; } catch (IOException ioe) { ioe.printStackTrace(); } } // [C10] PDFX Conformance /** A PDF/X level. */ public static final int PDFXNONE = 0; /** A PDF/X level. */ public static final int PDFX1A2001 = 1; /** A PDF/X level. */ public static final int PDFX32002 = 2; /** Stores the PDF ISO conformance. */ protected PdfIsoConformance pdfIsoConformance = initPdfIsoConformance(); protected PdfIsoConformance initPdfIsoConformance() { return new PdfXConformanceImp(this); } /** @see com.itextpdf.text.pdf.interfaces.PdfXConformance#setPDFXConformance(int) */ public void setPDFXConformance(final int pdfx) { if (!(pdfIsoConformance instanceof PdfXConformanceImp)) return; if (((PdfXConformance) pdfIsoConformance).getPDFXConformance() == pdfx) return; if (pdf.isOpen()) throw new PdfXConformanceException(MessageLocalization .getComposedMessage("pdfx.conformance.can.only.be.set.before.opening.the.document")); if (crypto != null) throw new PdfXConformanceException( MessageLocalization.getComposedMessage("a.pdfx.conforming.document.cannot.be.encrypted")); if (pdfx != PDFXNONE) setPdfVersion(VERSION_1_3); ((PdfXConformance) pdfIsoConformance).setPDFXConformance(pdfx); } /** @see com.itextpdf.text.pdf.interfaces.PdfXConformance#getPDFXConformance() */ public int getPDFXConformance() { if (pdfIsoConformance instanceof PdfXConformanceImp) return ((PdfXConformance) pdfIsoConformance).getPDFXConformance(); else return PDFXNONE; } /** @see com.itextpdf.text.pdf.interfaces.PdfXConformance#isPdfX() */ public boolean isPdfX() { if (pdfIsoConformance instanceof PdfXConformanceImp) return ((PdfXConformance) pdfIsoConformance).isPdfX(); else return false; } /** * Checks if any PDF ISO conformance is necessary. * @return <code>true</code> if the PDF has to be in conformance with any of the PDF ISO specifications */ public boolean isPdfIso() { return pdfIsoConformance.isPdfIso(); } // [C11] Output intents /** * Sets the values of the output intent dictionary. Null values are allowed to * suppress any key. * * @param outputConditionIdentifier a value * @param outputCondition a value * @param registryName a value * @param info a value * @param colorProfile a value * @since 2.1.5 * @throws IOException on error */ public void setOutputIntents(final String outputConditionIdentifier, final String outputCondition, final String registryName, final String info, final ICC_Profile colorProfile) throws IOException { PdfWriter.checkPdfIsoConformance(this, PdfIsoKeys.PDFISOKEY_OUTPUTINTENT, colorProfile); getExtraCatalog(); PdfDictionary out = new PdfDictionary(PdfName.OUTPUTINTENT); if (outputCondition != null) out.put(PdfName.OUTPUTCONDITION, new PdfString(outputCondition, PdfObject.TEXT_UNICODE)); if (outputConditionIdentifier != null) out.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString(outputConditionIdentifier, PdfObject.TEXT_UNICODE)); if (registryName != null) out.put(PdfName.REGISTRYNAME, new PdfString(registryName, PdfObject.TEXT_UNICODE)); if (info != null) out.put(PdfName.INFO, new PdfString(info, PdfObject.TEXT_UNICODE)); if (colorProfile != null) { PdfStream stream = new PdfICCBased(colorProfile, compressionLevel); out.put(PdfName.DESTOUTPUTPROFILE, addToBody(stream).getIndirectReference()); } out.put(PdfName.S, PdfName.GTS_PDFX); extraCatalog.put(PdfName.OUTPUTINTENTS, new PdfArray(out)); this.colorProfile = colorProfile; } /** * Sets the values of the output intent dictionary. Null values are allowed to * suppress any key. * * Prefer the <CODE>ICC_Profile</CODE>-based version of this method. * @param outputConditionIdentifier a value * @param outputCondition a value, "PDFA/A" to force GTS_PDFA1, otherwise cued by pdfxConformance. * @param registryName a value * @param info a value * @param destOutputProfile a value * @since 1.x * * @throws IOException */ public void setOutputIntents(final String outputConditionIdentifier, final String outputCondition, final String registryName, final String info, final byte destOutputProfile[]) throws IOException { ICC_Profile colorProfile = destOutputProfile == null ? null : ICC_Profile.getInstance(destOutputProfile); setOutputIntents(outputConditionIdentifier, outputCondition, registryName, info, colorProfile); } /** * Use this method to copy the output intent dictionary * from another document to this one. * @param reader the other document * @param checkExistence <CODE>true</CODE> to just check for the existence of a valid output intent * dictionary, <CODE>false</CODE> to insert the dictionary if it exists * @throws IOException on error * @return <CODE>true</CODE> if the output intent dictionary exists, <CODE>false</CODE> * otherwise */ public boolean setOutputIntents(final PdfReader reader, final boolean checkExistence) throws IOException { PdfDictionary catalog = reader.getCatalog(); PdfArray outs = catalog.getAsArray(PdfName.OUTPUTINTENTS); if (outs == null) return false; if (outs.isEmpty()) return false; PdfDictionary out = outs.getAsDict(0); PdfObject obj = PdfReader.getPdfObject(out.get(PdfName.S)); if (obj == null || !PdfName.GTS_PDFX.equals(obj)) return false; if (checkExistence) return true; PRStream stream = (PRStream) PdfReader.getPdfObject(out.get(PdfName.DESTOUTPUTPROFILE)); byte destProfile[] = null; if (stream != null) { destProfile = PdfReader.getStreamBytes(stream); } setOutputIntents(getNameString(out, PdfName.OUTPUTCONDITIONIDENTIFIER), getNameString(out, PdfName.OUTPUTCONDITION), getNameString(out, PdfName.REGISTRYNAME), getNameString(out, PdfName.INFO), destProfile); return true; } protected static String getNameString(final PdfDictionary dic, final PdfName key) { PdfObject obj = PdfReader.getPdfObject(dic.get(key)); if (obj == null || !obj.isString()) return null; return ((PdfString) obj).toUnicodeString(); } // PDF Objects that have an impact on the PDF body // [F1] PdfEncryptionSettings interface // types of encryption /** Type of encryption */ public static final int STANDARD_ENCRYPTION_40 = 0; /** Type of encryption */ public static final int STANDARD_ENCRYPTION_128 = 1; /** Type of encryption */ public static final int ENCRYPTION_AES_128 = 2; /** Type of encryption */ public static final int ENCRYPTION_AES_256 = 3; /** Mask to separate the encryption type from the encryption mode. */ static final int ENCRYPTION_MASK = 7; /** Add this to the mode to keep the metadata in clear text */ public static final int DO_NOT_ENCRYPT_METADATA = 8; /** * Add this to the mode to keep encrypt only the embedded files. * @since 2.1.3 */ public static final int EMBEDDED_FILES_ONLY = 24; // permissions /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_PRINTING = 4 + 2048; /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_MODIFY_CONTENTS = 8; /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_COPY = 16; /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_MODIFY_ANNOTATIONS = 32; /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_FILL_IN = 256; /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_SCREENREADERS = 512; /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_ASSEMBLY = 1024; /** The operation permitted when the document is opened with the user password * * @since 2.0.7 */ public static final int ALLOW_DEGRADED_PRINTING = 4; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_PRINTING} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowPrinting = ALLOW_PRINTING; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_MODIFY_CONTENTS} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowModifyContents = ALLOW_MODIFY_CONTENTS; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_COPY} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowCopy = ALLOW_COPY; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_MODIFY_ANNOTATIONS} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowModifyAnnotations = ALLOW_MODIFY_ANNOTATIONS; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_FILL_IN} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowFillIn = ALLOW_FILL_IN; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_SCREENREADERS} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowScreenReaders = ALLOW_SCREENREADERS; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_ASSEMBLY} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowAssembly = ALLOW_ASSEMBLY; /** @deprecated As of iText 2.0.7, use {@link #ALLOW_DEGRADED_PRINTING} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final int AllowDegradedPrinting = ALLOW_DEGRADED_PRINTING; // Strength of the encryption (kept for historical reasons) /** @deprecated As of iText 2.0.7, use {@link #STANDARD_ENCRYPTION_40} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final boolean STRENGTH40BITS = false; /** @deprecated As of iText 2.0.7, use {@link #STANDARD_ENCRYPTION_128} instead. Scheduled for removal at or after 2.2.0 */ @Deprecated public static final boolean STRENGTH128BITS = true; /** Contains the business logic for cryptography. */ protected PdfEncryption crypto; PdfEncryption getEncryption() { return crypto; } /** * Sets the encryption options for this document. The userPassword and the * ownerPassword can be null or have zero length. In this case the ownerPassword * is replaced by a random string. The open permissions for the document can be * AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, * AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. * The permissions can be combined by ORing them. * @param userPassword the user password. Can be null or empty * @param ownerPassword the owner password. Can be null or empty * @param permissions the user permissions * @param encryptionType can be any of the following: PdfWriter#STANDARD_ENCRYPTION_40, PdfWriter#STANDARD_ENCRYPTION_128, PdfWriter#ENCRYPTION_AES_128 or PdfWriter#ENCRYPTION_AES_256 * @throws DocumentException if the document is already open */ public void setEncryption(final byte userPassword[], final byte ownerPassword[], final int permissions, final int encryptionType) throws DocumentException { if (pdf.isOpen()) throw new DocumentException(MessageLocalization .getComposedMessage("encryption.can.only.be.added.before.opening.the.document")); crypto = new PdfEncryption(); crypto.setCryptoMode(encryptionType, 0); crypto.setupAllKeys(userPassword, ownerPassword, permissions); } /** * Sets the encryption options for this document. The userPassword and the * ownerPassword can be null or have zero length. In this case the ownerPassword * is replaced by a random string. The open permissions for the document can be * AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, * AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. * The permissions can be combined by ORing them. * @param certs Certificates to encrypt the document * @param permissions the user permissions * @param encryptionType can be any of the following: PdfWriter#STANDARD_ENCRYPTION_40, PdfWriter#STANDARD_ENCRYPTION_128, PdfWriter#ENCRYPTION_AES_128 or PdfWriter#ENCRYPTION_AES_256 * @throws DocumentException if the document is already open */ public void setEncryption(final Certificate[] certs, final int[] permissions, final int encryptionType) throws DocumentException { if (pdf.isOpen()) throw new DocumentException(MessageLocalization .getComposedMessage("encryption.can.only.be.added.before.opening.the.document")); crypto = new PdfEncryption(); if (certs != null) { for (int i = 0; i < certs.length; i++) { crypto.addRecipient(certs[i], permissions[i]); } } crypto.setCryptoMode(encryptionType, 0); crypto.getEncryptionDictionary(); } /** * Sets the encryption options for this document. The userPassword and the * ownerPassword can be null or have zero length. In this case the ownerPassword * is replaced by a random string. The open permissions for the document can be * AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, * AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. * The permissions can be combined by ORing them. * @param userPassword the user password. Can be null or empty * @param ownerPassword the owner password. Can be null or empty * @param permissions the user permissions * @param strength128Bits <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length * @throws DocumentException if the document is already open * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], int, int)}. Scheduled for removal at or after 2.2.0 */ @Deprecated public void setEncryption(final byte userPassword[], final byte ownerPassword[], final int permissions, final boolean strength128Bits) throws DocumentException { setEncryption(userPassword, ownerPassword, permissions, strength128Bits ? STANDARD_ENCRYPTION_128 : STANDARD_ENCRYPTION_40); } /** * Sets the encryption options for this document. The userPassword and the * ownerPassword can be null or have zero length. In this case the ownerPassword * is replaced by a random string. The open permissions for the document can be * AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, * AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. * The permissions can be combined by ORing them. * @param strength <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length * @param userPassword the user password. Can be null or empty * @param ownerPassword the owner password. Can be null or empty * @param permissions the user permissions * @throws DocumentException if the document is already open * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], int, int)}. Scheduled for removal at or after 2.2.0 */ @Deprecated public void setEncryption(final boolean strength, final String userPassword, final String ownerPassword, final int permissions) throws DocumentException { setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, strength ? STANDARD_ENCRYPTION_128 : STANDARD_ENCRYPTION_40); } /** * Sets the encryption options for this document. The userPassword and the * ownerPassword can be null or have zero length. In this case the ownerPassword * is replaced by a random string. The open permissions for the document can be * AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, * AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. * The permissions can be combined by ORing them. * @param encryptionType the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128 or ENCRYPTION_AES128. * Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext * @param userPassword the user password. Can be null or empty * @param ownerPassword the owner password. Can be null or empty * @param permissions the user permissions * @throws DocumentException if the document is already open * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], int, int)}. Scheduled for removal at or after 2.2.0 */ @Deprecated public void setEncryption(final int encryptionType, final String userPassword, final String ownerPassword, final int permissions) throws DocumentException { setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, encryptionType); } // [F2] compression /** Holds value of property fullCompression. */ protected boolean fullCompression = false; /** * Use this method to find out if 1.5 compression is on. * @return the 1.5 compression status */ public boolean isFullCompression() { return this.fullCompression; } /** * Use this method to set the document's compression to the * PDF 1.5 mode with object streams and xref streams. * It can be set at any time but once set it can't be unset. */ public void setFullCompression() throws DocumentException { if (open) throw new DocumentException(MessageLocalization .getComposedMessage("you.can.t.set.the.full.compression.if.the.document.is.already.open")); this.fullCompression = true; setAtLeastPdfVersion(VERSION_1_5); } /** * The compression level of the content streams. * @since 2.1.3 */ protected int compressionLevel = PdfStream.DEFAULT_COMPRESSION; /** * Returns the compression level used for streams written by this writer. * @return the compression level (0 = best speed, 9 = best compression, -1 is default) * @since 2.1.3 */ public int getCompressionLevel() { return compressionLevel; } /** * Sets the compression level to be used for streams written by this writer. * @param compressionLevel a value between 0 (best speed) and 9 (best compression) * @since 2.1.3 */ public void setCompressionLevel(final int compressionLevel) { if (compressionLevel < PdfStream.NO_COMPRESSION || compressionLevel > PdfStream.BEST_COMPRESSION) this.compressionLevel = PdfStream.DEFAULT_COMPRESSION; else this.compressionLevel = compressionLevel; } // [F3] adding fonts /** The fonts of this document */ protected LinkedHashMap<BaseFont, FontDetails> documentFonts = new LinkedHashMap<BaseFont, FontDetails>(); /** The font number counter for the fonts in the document. */ protected int fontNumber = 1; /** * Adds a <CODE>BaseFont</CODE> to the document but not to the page resources. * It is used for templates. * @param bf the <CODE>BaseFont</CODE> to add * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE> * and position 1 is an <CODE>PdfIndirectReference</CODE> */ FontDetails addSimple(final BaseFont bf) { FontDetails ret = documentFonts.get(bf); if (ret == null) { PdfWriter.checkPdfIsoConformance(this, PdfIsoKeys.PDFISOKEY_FONT, bf); if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) { ret = new FontDetails(new PdfName("F" + fontNumber++), ((DocumentFont) bf).getIndirectReference(), bf); } else { ret = new FontDetails(new PdfName("F" + fontNumber++), body.getPdfIndirectReference(), bf); } documentFonts.put(bf, ret); } return ret; } void eliminateFontSubset(final PdfDictionary fonts) { for (Object element : documentFonts.values()) { FontDetails ft = (FontDetails) element; if (fonts.get(ft.getFontName()) != null) ft.setSubset(false); } } // [F4] adding (and releasing) form XObjects /** The form XObjects in this document. The key is the xref and the value is Object[]{PdfName, template}.*/ protected HashMap<PdfIndirectReference, Object[]> formXObjects = new HashMap<PdfIndirectReference, Object[]>(); /** The name counter for the form XObjects name. */ protected int formXObjectsCounter = 1; /** * Adds a template to the document but not to the page resources. * @param template the template to add * @param forcedName the template name, rather than a generated one. Can be null * @return the <CODE>PdfName</CODE> for this template */ PdfName addDirectTemplateSimple(PdfTemplate template, final PdfName forcedName) { PdfIndirectReference ref = template.getIndirectReference(); Object obj[] = formXObjects.get(ref); PdfName name = null; try { if (obj == null) { if (forcedName == null) { name = new PdfName("Xf" + formXObjectsCounter); ++formXObjectsCounter; } else name = forcedName; if (template.getType() == PdfTemplate.TYPE_IMPORTED) { // If we got here from PdfCopy we'll have to fill importedPages PdfImportedPage ip = (PdfImportedPage) template; PdfReader r = ip.getPdfReaderInstance().getReader(); if (!readerInstances.containsKey(r)) { readerInstances.put(r, ip.getPdfReaderInstance()); } template = null; } formXObjects.put(ref, new Object[] { name, template }); } else name = (PdfName) obj[0]; } catch (Exception e) { throw new ExceptionConverter(e); } return name; } /** * Use this method to releases the memory used by a template. * This method writes the template to the output. * The template can still be added to any content * but changes to the template itself won't have any effect. * @param tp the template to release * @throws IOException on error */ public void releaseTemplate(final PdfTemplate tp) throws IOException { PdfIndirectReference ref = tp.getIndirectReference(); Object[] objs = formXObjects.get(ref); if (objs == null || objs[1] == null) return; PdfTemplate template = (PdfTemplate) objs[1]; if (template.getIndirectReference() instanceof PRIndirectReference) return; if (template.getType() == PdfTemplate.TYPE_TEMPLATE) { addToBody(template.getFormXObject(compressionLevel), template.getIndirectReference()); objs[1] = null; } } // [F5] adding pages imported form other PDF documents /** * Instances of PdfReader/PdfReaderInstance that are used to import pages. * @since 5.0.3 */ protected HashMap<PdfReader, PdfReaderInstance> readerInstances = new HashMap<PdfReader, PdfReaderInstance>(); /** * Use this method to get a page from other PDF document. * The page can be used as any other PdfTemplate. * Note that calling this method more than once with the same parameters * will retrieve the same object. * @param reader the PDF document where the page is * @param pageNumber the page number. The first page is 1 * @return the template representing the imported page */ public PdfImportedPage getImportedPage(final PdfReader reader, final int pageNumber) { return getPdfReaderInstance(reader).getImportedPage(pageNumber); } /** * Returns the PdfReaderInstance associated with the specified reader. * Multiple calls with the same reader object will return the same * PdfReaderInstance. * @param reader the PDF reader that you want an instance for * @return the instance for the provided reader * @since 5.0.3 */ protected PdfReaderInstance getPdfReaderInstance(final PdfReader reader) { PdfReaderInstance inst = readerInstances.get(reader); if (inst == null) { inst = reader.getPdfReaderInstance(this); readerInstances.put(reader, inst); } return inst; } /** * Use this method to writes the reader to the document * and free the memory used by it. * The main use is when concatenating multiple documents * to keep the memory usage restricted to the current * appending document. * @param reader the <CODE>PdfReader</CODE> to free * @throws IOException on error */ public void freeReader(final PdfReader reader) throws IOException { currentPdfReaderInstance = readerInstances.get(reader); if (currentPdfReaderInstance == null) return; currentPdfReaderInstance.writeAllPages(); currentPdfReaderInstance = null; readerInstances.remove(reader); } /** * Use this method to gets the current document size. * This size only includes the data already written * to the output stream, it does not include templates or fonts. * It is useful if used with <CODE>freeReader()</CODE> * when concatenating many documents and an idea of * the current size is needed. * @return the approximate size without fonts or templates */ public long getCurrentDocumentSize() { return body.offset() + body.size() * 20 + 0x48; } protected PdfReaderInstance currentPdfReaderInstance; protected int getNewObjectNumber(final PdfReader reader, final int number, final int generation) { if (currentPdfReaderInstance == null || currentPdfReaderInstance.getReader() != reader) { currentPdfReaderInstance = getPdfReaderInstance(reader); } return currentPdfReaderInstance.getNewObjectNumber(number, generation); } RandomAccessFileOrArray getReaderFile(final PdfReader reader) { return currentPdfReaderInstance.getReaderFile(); } // [F6] spot colors /** The colors of this document */ protected HashMap<ICachedColorSpace, ColorDetails> documentColors = new HashMap<ICachedColorSpace, ColorDetails>(); /** The color number counter for the colors in the document. */ protected int colorNumber = 1; PdfName getColorspaceName() { return new PdfName("CS" + colorNumber++); } /** * Adds a <CODE>SpotColor</CODE> to the document but not to the page resources. * @param spc the <CODE>SpotColor</CODE> to add * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE> * and position 1 is an <CODE>PdfIndirectReference</CODE> */ ColorDetails addSimple(final ICachedColorSpace spc) { ColorDetails ret = documentColors.get(spc); if (ret == null) { ret = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), spc); if (spc instanceof IPdfSpecialColorSpace) { ((IPdfSpecialColorSpace) spc).getColorantDetails(this); } documentColors.put(spc, ret); } return ret; } // [F7] document patterns /** The patterns of this document */ protected HashMap<PdfPatternPainter, PdfName> documentPatterns = new HashMap<PdfPatternPainter, PdfName>(); /** The pattern number counter for the colors in the document. */ protected int patternNumber = 1; PdfName addSimplePattern(final PdfPatternPainter painter) { PdfName name = documentPatterns.get(painter); try { if (name == null) { name = new PdfName("P" + patternNumber); ++patternNumber; documentPatterns.put(painter, name); } } catch (Exception e) { throw new ExceptionConverter(e); } return name; } // [F8] shading patterns protected HashSet<PdfShadingPattern> documentShadingPatterns = new HashSet<PdfShadingPattern>(); void addSimpleShadingPattern(final PdfShadingPattern shading) { if (!documentShadingPatterns.contains(shading)) { shading.setName(patternNumber); ++patternNumber; documentShadingPatterns.add(shading); addSimpleShading(shading.getShading()); } } // [F9] document shadings protected HashSet<PdfShading> documentShadings = new HashSet<PdfShading>(); void addSimpleShading(final PdfShading shading) { if (!documentShadings.contains(shading)) { documentShadings.add(shading); shading.setName(documentShadings.size()); } } // [F10] extended graphics state (for instance for transparency) protected HashMap<PdfDictionary, PdfObject[]> documentExtGState = new HashMap<PdfDictionary, PdfObject[]>(); PdfObject[] addSimpleExtGState(final PdfDictionary gstate) { if (!documentExtGState.containsKey(gstate)) { documentExtGState.put(gstate, new PdfObject[] { new PdfName("GS" + (documentExtGState.size() + 1)), getPdfIndirectReference() }); } return documentExtGState.get(gstate); } // [F11] adding properties (OCG, marked content) protected HashMap<Object, PdfObject[]> documentProperties = new HashMap<Object, PdfObject[]>(); PdfObject[] addSimpleProperty(final Object prop, final PdfIndirectReference refi) { if (!documentProperties.containsKey(prop)) { if (prop instanceof PdfOCG) PdfWriter.checkPdfIsoConformance(this, PdfIsoKeys.PDFISOKEY_LAYER, prop); documentProperties.put(prop, new PdfObject[] { new PdfName("Pr" + (documentProperties.size() + 1)), refi }); } return documentProperties.get(prop); } boolean propertyExists(final Object prop) { return documentProperties.containsKey(prop); } // [F12] tagged PDF static public final int markAll = 0x00; static public final int markInlineElementsOnly = 0x01; protected boolean tagged = false; protected int taggingMode = markInlineElementsOnly; protected PdfStructureTreeRoot structureTreeRoot; /** * Mark this document for tagging. It must be called before open. */ public void setTagged() { setTagged(markInlineElementsOnly); } public void setTagged(int taggingMode) { if (open) throw new IllegalArgumentException( MessageLocalization.getComposedMessage("tagging.must.be.set.before.opening.the.document")); tagged = true; this.taggingMode = taggingMode; } public boolean needToBeMarkedInContent(IAccessibleElement element) { if ((taggingMode & markInlineElementsOnly) != 0) { if (element.isInline() || PdfName.ARTIFACT.equals(element.getRole())) { return true; } return false; } return true; } public void checkElementRole(IAccessibleElement element, IAccessibleElement parent) { if (parent != null && (parent.getRole() == null || PdfName.ARTIFACT.equals(parent.getRole()))) element.setRole(null); else if ((taggingMode & markInlineElementsOnly) != 0) { if (element.isInline() && element.getRole() == null && (parent == null || !parent.isInline())) throw new IllegalArgumentException( MessageLocalization.getComposedMessage("inline.elements.with.role.null.are.not.allowed")); } } /** * Check if the document is marked for tagging. * @return <CODE>true</CODE> if the document is marked for tagging */ public boolean isTagged() { return tagged; } /** * Fix structure of tagged document: remove unused objects, remove unused items from class map, * fix xref table due to removed objects. */ protected void flushTaggedObjects() throws IOException { } protected void flushAcroFields() throws IOException, BadPdfFormatException { } /** * Gets the structure tree root. If the document is not marked for tagging it will return <CODE>null</CODE>. * @return the structure tree root */ public PdfStructureTreeRoot getStructureTreeRoot() { if (tagged && structureTreeRoot == null) structureTreeRoot = new PdfStructureTreeRoot(this); return structureTreeRoot; } // [F13] Optional Content Groups /** A hashSet containing all the PdfLayer objects. */ protected LinkedHashSet<PdfOCG> documentOCG = new LinkedHashSet<PdfOCG>(); /** An array list used to define the order of an OCG tree. */ protected ArrayList<PdfOCG> documentOCGorder = new ArrayList<PdfOCG>(); /** The OCProperties in a catalog dictionary. */ protected PdfOCProperties OCProperties; /** The RBGroups array in an OCG dictionary */ protected PdfArray OCGRadioGroup = new PdfArray(); /** * The locked array in an OCG dictionary * @since 2.1.2 */ protected PdfArray OCGLocked = new PdfArray(); /** * Use this method to get the <B>Optional Content Properties Dictionary</B>. * Each call fills the dictionary with the current layer state. * It's advisable to only call this method right before close * and do any modifications at that time. * @return the Optional Content Properties Dictionary */ public PdfOCProperties getOCProperties() { fillOCProperties(true); return OCProperties; } /** * Use this method to set a collection of optional content groups * whose states are intended to follow a "radio button" paradigm. * That is, the state of at most one optional content group * in the array should be ON at a time: if one group is turned * ON, all others must be turned OFF. * @param group the radio group */ public void addOCGRadioGroup(final ArrayList<PdfLayer> group) { PdfArray ar = new PdfArray(); for (int k = 0; k < group.size(); ++k) { PdfLayer layer = group.get(k); if (layer.getTitle() == null) ar.add(layer.getRef()); } if (ar.size() == 0) return; OCGRadioGroup.add(ar); } /** * Use this method to lock an optional content group. * The state of a locked group cannot be changed through the user interface * of a viewer application. Producers can use this entry to prevent the visibility * of content that depends on these groups from being changed by users. * @param layer the layer that needs to be added to the array of locked OCGs * @since 2.1.2 */ public void lockLayer(final PdfLayer layer) { OCGLocked.add(layer.getRef()); } private static void getOCGOrder(final PdfArray order, final PdfLayer layer) { if (!layer.isOnPanel()) return; if (layer.getTitle() == null) order.add(layer.getRef()); ArrayList<PdfLayer> children = layer.getChildren(); if (children == null) return; PdfArray kids = new PdfArray(); if (layer.getTitle() != null) kids.add(new PdfString(layer.getTitle(), PdfObject.TEXT_UNICODE)); for (int k = 0; k < children.size(); ++k) { getOCGOrder(kids, children.get(k)); } if (kids.size() > 0) order.add(kids); } private void addASEvent(final PdfName event, final PdfName category) { PdfArray arr = new PdfArray(); for (Object element : documentOCG) { PdfLayer layer = (PdfLayer) element; PdfDictionary usage = layer.getAsDict(PdfName.USAGE); if (usage != null && usage.get(category) != null) arr.add(layer.getRef()); } if (arr.size() == 0) return; PdfDictionary d = OCProperties.getAsDict(PdfName.D); PdfArray arras = d.getAsArray(PdfName.AS); if (arras == null) { arras = new PdfArray(); d.put(PdfName.AS, arras); } PdfDictionary as = new PdfDictionary(); as.put(PdfName.EVENT, event); as.put(PdfName.CATEGORY, new PdfArray(category)); as.put(PdfName.OCGS, arr); arras.add(as); } /** * @param erase true to erase the {@link PdfName#OCGS} and {@link PdfName#D} from the OCProperties first. * @since 2.1.2 */ protected void fillOCProperties(final boolean erase) { if (OCProperties == null) OCProperties = new PdfOCProperties(); if (erase) { OCProperties.remove(PdfName.OCGS); OCProperties.remove(PdfName.D); } if (OCProperties.get(PdfName.OCGS) == null) { PdfArray gr = new PdfArray(); for (Object element : documentOCG) { PdfLayer layer = (PdfLayer) element; gr.add(layer.getRef()); } OCProperties.put(PdfName.OCGS, gr); } if (OCProperties.get(PdfName.D) != null) return; ArrayList<PdfOCG> docOrder = new ArrayList<PdfOCG>(documentOCGorder); for (Iterator<PdfOCG> it = docOrder.iterator(); it.hasNext();) { PdfLayer layer = (PdfLayer) it.next(); if (layer.getParent() != null) it.remove(); } PdfArray order = new PdfArray(); for (Object element : docOrder) { PdfLayer layer = (PdfLayer) element; getOCGOrder(order, layer); } PdfDictionary d = new PdfDictionary(); OCProperties.put(PdfName.D, d); d.put(PdfName.ORDER, order); if (docOrder.size() > 0 && (docOrder.get(0) instanceof PdfLayer)) { PdfLayer l = (PdfLayer) docOrder.get(0); PdfString name = l.getAsString(PdfName.NAME); if (name != null) { d.put(PdfName.NAME, name); } } PdfArray gr = new PdfArray(); for (Object element : documentOCG) { PdfLayer layer = (PdfLayer) element; if (!layer.isOn()) gr.add(layer.getRef()); } if (gr.size() > 0) d.put(PdfName.OFF, gr); if (OCGRadioGroup.size() > 0) d.put(PdfName.RBGROUPS, OCGRadioGroup); if (OCGLocked.size() > 0) d.put(PdfName.LOCKED, OCGLocked); addASEvent(PdfName.VIEW, PdfName.ZOOM); addASEvent(PdfName.VIEW, PdfName.VIEW); addASEvent(PdfName.PRINT, PdfName.PRINT); addASEvent(PdfName.EXPORT, PdfName.EXPORT); d.put(PdfName.LISTMODE, PdfName.VISIBLEPAGES); } void registerLayer(final PdfOCG layer) { PdfWriter.checkPdfIsoConformance(this, PdfIsoKeys.PDFISOKEY_LAYER, layer); if (layer instanceof PdfLayer) { PdfLayer la = (PdfLayer) layer; if (la.getTitle() == null) { if (!documentOCG.contains(layer)) { documentOCG.add(layer); documentOCGorder.add(layer); } } else { documentOCGorder.add(layer); } } else throw new IllegalArgumentException(MessageLocalization.getComposedMessage("only.pdflayer.is.accepted")); } // User methods to change aspects of the page // [U1] page size /** * Use this method to get the size of the media box. * @return a Rectangle */ public Rectangle getPageSize() { return pdf.getPageSize(); } /** * Use this method to set the crop box. * The crop box should not be rotated even if the page is rotated. * This change only takes effect in the next page. * @param crop the crop box */ public void setCropBoxSize(final Rectangle crop) { pdf.setCropBoxSize(crop); } /** * Use this method to set the page box sizes. * Allowed names are: "crop", "trim", "art" and "bleed". * @param boxName the box size * @param size the size */ public void setBoxSize(final String boxName, final Rectangle size) { pdf.setBoxSize(boxName, size); } /** * Use this method to get the size of a trim, art, crop or bleed box, * or null if not defined. * @param boxName crop, trim, art or bleed */ public Rectangle getBoxSize(final String boxName) { return pdf.getBoxSize(boxName); } /** * Returns the intersection between the crop, trim art or bleed box and the parameter intersectingRectangle. * This method returns null when * - there is no intersection * - any of the above boxes are not defined * - the parameter intersectingRectangle is null * * @param boxName crop, trim, art, bleed * @param intersectingRectangle the rectangle that intersects the rectangle associated to the boxName * @return the intersection of the two rectangles */ public Rectangle getBoxSize(final String boxName, final Rectangle intersectingRectangle) { Rectangle pdfRectangle = pdf.getBoxSize(boxName); if (pdfRectangle == null || intersectingRectangle == null) { // no intersection return null; } com.itextpdf.awt.geom.Rectangle boxRect = new com.itextpdf.awt.geom.Rectangle(pdfRectangle); com.itextpdf.awt.geom.Rectangle intRect = new com.itextpdf.awt.geom.Rectangle(intersectingRectangle); com.itextpdf.awt.geom.Rectangle outRect = boxRect.intersection(intRect); if (outRect.isEmpty()) { // no intersection return null; } Rectangle output = new Rectangle((float) outRect.getX(), (float) outRect.getY(), (float) (outRect.getX() + outRect.getWidth()), (float) (outRect.getY() + outRect.getHeight())); output.normalize(); return output; } // [U2] take care of empty pages /** * Use this method to make sure a page is added, * even if it's empty. If you use setPageEmpty(false), * invoking newPage() after a blank page will add a newPage. * setPageEmpty(true) won't have any effect. * @param pageEmpty the state */ public void setPageEmpty(final boolean pageEmpty) { if (pageEmpty) return; pdf.setPageEmpty(pageEmpty); } /** * Checks if a newPage() will actually generate a new page. * @return true if a new page will be generated, false otherwise * @since 5.0.0 */ public boolean isPageEmpty() { return pdf.isPageEmpty(); } // [U3] page actions (open and close) /** action value */ public static final PdfName PAGE_OPEN = PdfName.O; /** action value */ public static final PdfName PAGE_CLOSE = PdfName.C; /** @see com.itextpdf.text.pdf.interfaces.PdfPageActions#setPageAction(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfAction) */ public void setPageAction(final PdfName actionType, final PdfAction action) throws DocumentException { if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE)) throw new DocumentException(MessageLocalization .getComposedMessage("invalid.page.additional.action.type.1", actionType.toString())); pdf.setPageAction(actionType, action); } /** @see com.itextpdf.text.pdf.interfaces.PdfPageActions#setDuration(int) */ public void setDuration(final int seconds) { pdf.setDuration(seconds); } /** @see com.itextpdf.text.pdf.interfaces.PdfPageActions#setTransition(com.itextpdf.text.pdf.PdfTransition) */ public void setTransition(final PdfTransition transition) { pdf.setTransition(transition); } // [U4] Thumbnail image /** * Use this method to set the thumbnail image for the current page. * @param image the image * @throws PdfException on error * @throws DocumentException or error */ public void setThumbnail(final Image image) throws PdfException, DocumentException { pdf.setThumbnail(image); } // [U5] Transparency groups /** * A group attributes dictionary specifying the attributes * of the page's page group for use in the transparent * imaging model */ protected PdfDictionary group; /** * Use this method to get the group dictionary. * @return Value of property group. */ public PdfDictionary getGroup() { return this.group; } /** * Use this method to set the group dictionary. * @param group New value of property group. */ public void setGroup(final PdfDictionary group) { this.group = group; } // [U6] space char ratio /** The default space-char ratio. */ public static final float SPACE_CHAR_RATIO_DEFAULT = 2.5f; /** Disable the inter-character spacing. */ public static final float NO_SPACE_CHAR_RATIO = 10000000f; /** * The ratio between the extra word spacing and the extra character spacing. * Extra word spacing will grow <CODE>ratio</CODE> times more than extra character spacing. */ private float spaceCharRatio = SPACE_CHAR_RATIO_DEFAULT; /** * Use this method to gets the space/character extra spacing ratio * for fully justified text. * @return the space/character extra spacing ratio */ public float getSpaceCharRatio() { return spaceCharRatio; } /** * Use this method to set the ratio between the extra word spacing and * the extra character spacing when the text is fully justified. * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more * than extra character spacing. If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> * then the extra character spacing will be zero. * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing */ public void setSpaceCharRatio(final float spaceCharRatio) { if (spaceCharRatio < 0.001f) this.spaceCharRatio = 0.001f; else this.spaceCharRatio = spaceCharRatio; } // [U7] run direction (doesn't actually do anything) /** Use the default run direction. */ public static final int RUN_DIRECTION_DEFAULT = 0; /** Do not use bidirectional reordering. */ public static final int RUN_DIRECTION_NO_BIDI = 1; /** Use bidirectional reordering with left-to-right * preferential run direction. */ public static final int RUN_DIRECTION_LTR = 2; /** Use bidirectional reordering with right-to-left * preferential run direction. */ public static final int RUN_DIRECTION_RTL = 3; protected int runDirection = RUN_DIRECTION_NO_BIDI; /** * Use this method to set the run direction. * This is only used as a placeholder as it does not affect anything. * @param runDirection the run direction */ public void setRunDirection(final int runDirection) { if (runDirection < RUN_DIRECTION_NO_BIDI || runDirection > RUN_DIRECTION_RTL) throw new RuntimeException( MessageLocalization.getComposedMessage("invalid.run.direction.1", runDirection)); this.runDirection = runDirection; } /** * Use this method to set the run direction. * @return the run direction */ public int getRunDirection() { return runDirection; } // [U8] user units /** * Use this method to set the user unit. * A UserUnit is a value that defines the default user space unit. * The minimum UserUnit is 1 (1 unit = 1/72 inch). * The maximum UserUnit is 75,000. * Note that this userunit only works starting with PDF1.6! * @param userunit The userunit to set. * @throws DocumentException on error */ public void setUserunit(final float userunit) throws DocumentException { if (userunit < 1f || userunit > 75000f) throw new DocumentException( MessageLocalization.getComposedMessage("userunit.should.be.a.value.between.1.and.75000")); addPageDictEntry(PdfName.USERUNIT, new PdfNumber(userunit)); setAtLeastPdfVersion(VERSION_1_6); } // Miscellaneous topics // [M1] Color settings protected PdfDictionary defaultColorspace = new PdfDictionary(); /** * Use this method to get the default colorspaces. * @return the default colorspaces */ public PdfDictionary getDefaultColorspace() { return defaultColorspace; } /** * Use this method to sets the default colorspace that will be applied * to all the document. The colorspace is only applied if another colorspace * with the same name is not present in the content. * <p> * The colorspace is applied immediately when creating templates and * at the page end for the main document content. * @param key the name of the colorspace. It can be <CODE>PdfName.DEFAULTGRAY</CODE>, <CODE>PdfName.DEFAULTRGB</CODE> * or <CODE>PdfName.DEFAULTCMYK</CODE> * @param cs the colorspace. A <CODE>null</CODE> or <CODE>PdfNull</CODE> removes any colorspace with the same name */ public void setDefaultColorspace(final PdfName key, final PdfObject cs) { if (cs == null || cs.isNull()) defaultColorspace.remove(key); defaultColorspace.put(key, cs); } // [M2] spot patterns protected HashMap<ColorDetails, ColorDetails> documentSpotPatterns = new HashMap<ColorDetails, ColorDetails>(); protected ColorDetails patternColorspaceRGB; protected ColorDetails patternColorspaceGRAY; protected ColorDetails patternColorspaceCMYK; ColorDetails addSimplePatternColorspace(final BaseColor color) { int type = ExtendedColor.getType(color); if (type == ExtendedColor.TYPE_PATTERN || type == ExtendedColor.TYPE_SHADING) throw new RuntimeException(MessageLocalization.getComposedMessage( "an.uncolored.tile.pattern.can.not.have.another.pattern.or.shading.as.color")); try { switch (type) { case ExtendedColor.TYPE_RGB: if (patternColorspaceRGB == null) { patternColorspaceRGB = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null); PdfArray array = new PdfArray(PdfName.PATTERN); array.add(PdfName.DEVICERGB); addToBody(array, patternColorspaceRGB.getIndirectReference()); } return patternColorspaceRGB; case ExtendedColor.TYPE_CMYK: if (patternColorspaceCMYK == null) { patternColorspaceCMYK = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null); PdfArray array = new PdfArray(PdfName.PATTERN); array.add(PdfName.DEVICECMYK); addToBody(array, patternColorspaceCMYK.getIndirectReference()); } return patternColorspaceCMYK; case ExtendedColor.TYPE_GRAY: if (patternColorspaceGRAY == null) { patternColorspaceGRAY = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null); PdfArray array = new PdfArray(PdfName.PATTERN); array.add(PdfName.DEVICEGRAY); addToBody(array, patternColorspaceGRAY.getIndirectReference()); } return patternColorspaceGRAY; case ExtendedColor.TYPE_SEPARATION: { ColorDetails details = addSimple(((SpotColor) color).getPdfSpotColor()); ColorDetails patternDetails = documentSpotPatterns.get(details); if (patternDetails == null) { patternDetails = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null); PdfArray array = new PdfArray(PdfName.PATTERN); array.add(details.getIndirectReference()); addToBody(array, patternDetails.getIndirectReference()); documentSpotPatterns.put(details, patternDetails); } return patternDetails; } default: throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.color.type")); } } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } // [M3] Images /** * Use this method to get the strictImageSequence status. * @return value of property strictImageSequence */ public boolean isStrictImageSequence() { return pdf.isStrictImageSequence(); } /** * Use this method to set the image sequence, so that it follows * the text in strict order (or not). * @param strictImageSequence new value of property strictImageSequence * */ public void setStrictImageSequence(final boolean strictImageSequence) { pdf.setStrictImageSequence(strictImageSequence); } /** * Use this method to clear text wrapping around images (if applicable). * @throws DocumentException */ public void clearTextWrap() throws DocumentException { pdf.clearTextWrap(); } /** Dictionary, containing all the images of the PDF document */ protected PdfDictionary imageDictionary = new PdfDictionary(); /** This is the list with all the images in the document. */ private final HashMap<Long, PdfName> images = new HashMap<Long, PdfName>(); /** * Use this method to adds an image to the document * but not to the page resources. It is used with * templates and <CODE>Document.add(Image)</CODE>. * Use this method only if you know what you're doing! * @param image the <CODE>Image</CODE> to add * @return the name of the image added * @throws PdfException on error * @throws DocumentException on error */ public PdfName addDirectImageSimple(final Image image) throws PdfException, DocumentException { return addDirectImageSimple(image, null); } /** * Adds an image to the document but not to the page resources. * It is used with templates and <CODE>Document.add(Image)</CODE>. * Use this method only if you know what you're doing! * @param image the <CODE>Image</CODE> to add * @param fixedRef the reference to used. It may be <CODE>null</CODE>, * a <CODE>PdfIndirectReference</CODE> or a <CODE>PRIndirectReference</CODE>. * @return the name of the image added * @throws PdfException on error * @throws DocumentException on error */ public PdfName addDirectImageSimple(final Image image, final PdfIndirectReference fixedRef) throws PdfException, DocumentException { PdfName name; // if the images is already added, just retrieve the name if (images.containsKey(image.getMySerialId())) { name = images.get(image.getMySerialId()); } // if it's a new image, add it to the document else { if (image.isImgTemplate()) { name = new PdfName("img" + images.size()); if (image instanceof ImgWMF) { try { ImgWMF wmf = (ImgWMF) image; wmf.readWMF(PdfTemplate.createTemplate(this, 0, 0)); } catch (Exception e) { throw new DocumentException(e); } } } else { PdfIndirectReference dref = image.getDirectReference(); if (dref != null) { PdfName rname = new PdfName("img" + images.size()); images.put(image.getMySerialId(), rname); imageDictionary.put(rname, dref); return rname; } Image maskImage = image.getImageMask(); PdfIndirectReference maskRef = null; if (maskImage != null) { PdfName mname = images.get(maskImage.getMySerialId()); maskRef = getImageReference(mname); } PdfImage i = new PdfImage(image, "img" + images.size(), maskRef); if (image instanceof ImgJBIG2) { byte[] globals = ((ImgJBIG2) image).getGlobalBytes(); if (globals != null) { PdfDictionary decodeparms = new PdfDictionary(); decodeparms.put(PdfName.JBIG2GLOBALS, getReferenceJBIG2Globals(globals)); i.put(PdfName.DECODEPARMS, decodeparms); } } if (image.hasICCProfile()) { PdfICCBased icc = new PdfICCBased(image.getICCProfile(), image.getCompressionLevel()); PdfIndirectReference iccRef = add(icc); PdfArray iccArray = new PdfArray(); iccArray.add(PdfName.ICCBASED); iccArray.add(iccRef); PdfArray colorspace = i.getAsArray(PdfName.COLORSPACE); if (colorspace != null) { if (colorspace.size() > 1 && PdfName.INDEXED.equals(colorspace.getPdfObject(0))) colorspace.set(1, iccArray); else i.put(PdfName.COLORSPACE, iccArray); } else i.put(PdfName.COLORSPACE, iccArray); } add(i, fixedRef); name = i.name(); } images.put(image.getMySerialId(), name); } return name; } /** * Writes a <CODE>PdfImage</CODE> to the outputstream. * * @param pdfImage the image to be added * @param fixedRef the IndirectReference, may be null then a new indirect reference is returned * @return a <CODE>PdfIndirectReference</CODE> to the encapsulated image * @throws PdfException when a document isn't open yet, or has been closed */ PdfIndirectReference add(final PdfImage pdfImage, PdfIndirectReference fixedRef) throws PdfException { if (!imageDictionary.contains(pdfImage.name())) { PdfWriter.checkPdfIsoConformance(this, PdfIsoKeys.PDFISOKEY_IMAGE, pdfImage); if (fixedRef instanceof PRIndirectReference) { PRIndirectReference r2 = (PRIndirectReference) fixedRef; fixedRef = new PdfIndirectReference(0, getNewObjectNumber(r2.getReader(), r2.getNumber(), r2.getGeneration())); } try { if (fixedRef == null) fixedRef = addToBody(pdfImage).getIndirectReference(); else addToBody(pdfImage, fixedRef); } catch (IOException ioe) { throw new ExceptionConverter(ioe); } imageDictionary.put(pdfImage.name(), fixedRef); return fixedRef; } return (PdfIndirectReference) imageDictionary.get(pdfImage.name()); } /** * return the <CODE>PdfIndirectReference</CODE> to the image with a given name. * * @param name the name of the image * @return a <CODE>PdfIndirectReference</CODE> */ PdfIndirectReference getImageReference(final PdfName name) { return (PdfIndirectReference) imageDictionary.get(name); } protected PdfIndirectReference add(final PdfICCBased icc) { PdfIndirectObject object; try { object = addToBody(icc); } catch (IOException ioe) { throw new ExceptionConverter(ioe); } return object.getIndirectReference(); } /** * A HashSet with Stream objects containing JBIG2 Globals * @since 2.1.5 */ protected HashMap<PdfStream, PdfIndirectReference> JBIG2Globals = new HashMap<PdfStream, PdfIndirectReference>(); /** * Gets an indirect reference to a JBIG2 Globals stream. * Adds the stream if it hasn't already been added to the writer. * @param content a byte array that may already been added to the writer inside a stream object. * @return the PdfIndirectReference of the stream * @since 2.1.5 */ protected PdfIndirectReference getReferenceJBIG2Globals(final byte[] content) { if (content == null) return null; for (PdfStream stream : JBIG2Globals.keySet()) { if (Arrays.equals(content, stream.getBytes())) { return JBIG2Globals.get(stream); } } PdfStream stream = new PdfStream(content); PdfIndirectObject ref; try { ref = addToBody(stream); } catch (IOException e) { return null; } JBIG2Globals.put(stream, ref.getIndirectReference()); return ref.getIndirectReference(); } // [F12] tagged PDF /** * A flag indicating the presence of structure elements that contain user properties attributes. */ private boolean userProperties; /** * Gets the flag indicating the presence of structure elements that contain user properties attributes. * @return the user properties flag */ public boolean isUserProperties() { return this.userProperties; } /** * Sets the flag indicating the presence of structure elements that contain user properties attributes. * @param userProperties the user properties flag */ public void setUserProperties(final boolean userProperties) { this.userProperties = userProperties; } /** * Holds value of property RGBTranparency. */ private boolean rgbTransparencyBlending; /** * Gets the transparency blending colorspace. * @return <code>true</code> if the transparency blending colorspace is RGB, <code>false</code> * if it is the default blending colorspace * @since 2.1.0 */ public boolean isRgbTransparencyBlending() { return this.rgbTransparencyBlending; } /** * Sets the transparency blending colorspace to RGB. The default blending colorspace is * CMYK and will result in faded colors in the screen and in printing. Calling this method * will return the RGB colors to what is expected. The RGB blending will be applied to all subsequent pages * until other value is set. * Note that this is a generic solution that may not work in all cases. * @param rgbTransparencyBlending <code>true</code> to set the transparency blending colorspace to RGB, <code>false</code> * to use the default blending colorspace * @since 2.1.0 */ public void setRgbTransparencyBlending(final boolean rgbTransparencyBlending) { this.rgbTransparencyBlending = rgbTransparencyBlending; } protected static void writeKeyInfo(OutputStream os) throws IOException { Version version = Version.getInstance(); String k = version.getKey(); if (k == null) { k = "iText"; } os.write(getISOBytes(String.format("%%%s-%s\n", k, version.getRelease()))); } protected TtfUnicodeWriter ttfUnicodeWriter = null; protected TtfUnicodeWriter getTtfUnicodeWriter() { if (ttfUnicodeWriter == null) ttfUnicodeWriter = new TtfUnicodeWriter(this); return ttfUnicodeWriter; } protected XmpWriter createXmpWriter(ByteArrayOutputStream baos, PdfDictionary info) throws IOException { return new XmpWriter(baos, info); } protected XmpWriter createXmpWriter(ByteArrayOutputStream baos, HashMap<String, String> info) throws IOException { return new XmpWriter(baos, info); } /** * A wrapper around PdfAnnotation constructor. * It is recommended to use this wrapper instead of direct constructor as this is a convenient way to override PdfAnnotation construction when needed. * * @param rect * @param subtype * @return */ public PdfAnnotation createAnnotation(Rectangle rect, PdfName subtype) { PdfAnnotation a = new PdfAnnotation(this, rect); if (subtype != null) a.put(PdfName.SUBTYPE, subtype); return a; } /** * A wrapper around PdfAnnotation constructor. * It is recommended to use this wrapper instead of direct constructor as this is a convenient way to override PdfAnnotation construction when needed. * * @param llx * @param lly * @param urx * @param ury * @param title * @param content * @param subtype * @return */ public PdfAnnotation createAnnotation(float llx, float lly, float urx, float ury, PdfString title, PdfString content, PdfName subtype) { PdfAnnotation a = new PdfAnnotation(this, llx, lly, urx, ury, title, content); if (subtype != null) a.put(PdfName.SUBTYPE, subtype); return a; } /** * A wrapper around PdfAnnotation constructor. * It is recommended to use this wrapper instead of direct constructor as this is a convenient way to override PdfAnnotation construction when needed. * * @param llx * @param lly * @param urx * @param ury * @param action * @param subtype * @return */ public PdfAnnotation createAnnotation(float llx, float lly, float urx, float ury, PdfAction action, PdfName subtype) { PdfAnnotation a = new PdfAnnotation(this, llx, lly, urx, ury, action); if (subtype != null) a.put(PdfName.SUBTYPE, subtype); return a; } public static void checkPdfIsoConformance(PdfWriter writer, int key, Object obj1) { if (writer != null) writer.checkPdfIsoConformance(key, obj1); } public void checkPdfIsoConformance(int key, Object obj1) { pdfIsoConformance.checkPdfIsoConformance(key, obj1); } private void completeInfoDictionary(PdfDictionary info) { if (isPdfX()) { if (info.get(PdfName.GTS_PDFXVERSION) == null) { if (((PdfXConformanceImp) pdfIsoConformance).isPdfX1A2001()) { info.put(PdfName.GTS_PDFXVERSION, new PdfString("PDF/X-1:2001")); info.put(new PdfName("GTS_PDFXConformance"), new PdfString("PDF/X-1a:2001")); } else if (((PdfXConformanceImp) pdfIsoConformance).isPdfX32002()) info.put(PdfName.GTS_PDFXVERSION, new PdfString("PDF/X-3:2002")); } if (info.get(PdfName.TITLE) == null) { info.put(PdfName.TITLE, new PdfString("Pdf document")); } if (info.get(PdfName.CREATOR) == null) { info.put(PdfName.CREATOR, new PdfString("Unknown")); } if (info.get(PdfName.TRAPPED) == null) { info.put(PdfName.TRAPPED, new PdfName("False")); } } } private void completeExtraCatalog(PdfDictionary extraCatalog) { if (isPdfX()) { if (extraCatalog.get(PdfName.OUTPUTINTENTS) == null) { PdfDictionary out = new PdfDictionary(PdfName.OUTPUTINTENT); out.put(PdfName.OUTPUTCONDITION, new PdfString("SWOP CGATS TR 001-1995")); out.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString("CGATS TR 001")); out.put(PdfName.REGISTRYNAME, new PdfString("http://www.color.org")); out.put(PdfName.INFO, new PdfString("")); out.put(PdfName.S, PdfName.GTS_PDFX); extraCatalog.put(PdfName.OUTPUTINTENTS, new PdfArray(out)); } } } static private final List<PdfName> standardStructElems_1_4 = Arrays.asList(PdfName.DOCUMENT, PdfName.PART, PdfName.ART, PdfName.SECT, PdfName.DIV, PdfName.BLOCKQUOTE, PdfName.CAPTION, PdfName.TOC, PdfName.TOCI, PdfName.INDEX, PdfName.NONSTRUCT, PdfName.PRIVATE, PdfName.P, PdfName.H, PdfName.H1, PdfName.H2, PdfName.H3, PdfName.H4, PdfName.H5, PdfName.H6, PdfName.L, PdfName.LBL, PdfName.LI, PdfName.LBODY, PdfName.TABLE, PdfName.TR, PdfName.TH, PdfName.TD, PdfName.SPAN, PdfName.QUOTE, PdfName.NOTE, PdfName.REFERENCE, PdfName.BIBENTRY, PdfName.CODE, PdfName.LINK, PdfName.FIGURE, PdfName.FORMULA, PdfName.FORM); static private final List<PdfName> standardStructElems_1_7 = Arrays.asList(PdfName.DOCUMENT, PdfName.PART, PdfName.ART, PdfName.SECT, PdfName.DIV, PdfName.BLOCKQUOTE, PdfName.CAPTION, PdfName.TOC, PdfName.TOCI, PdfName.INDEX, PdfName.NONSTRUCT, PdfName.PRIVATE, PdfName.P, PdfName.H, PdfName.H1, PdfName.H2, PdfName.H3, PdfName.H4, PdfName.H5, PdfName.H6, PdfName.L, PdfName.LBL, PdfName.LI, PdfName.LBODY, PdfName.TABLE, PdfName.TR, PdfName.TH, PdfName.TD, PdfName.THEAD, PdfName.TBODY, PdfName.TFOOT, PdfName.SPAN, PdfName.QUOTE, PdfName.NOTE, PdfName.REFERENCE, PdfName.BIBENTRY, PdfName.CODE, PdfName.LINK, PdfName.ANNOT, PdfName.RUBY, PdfName.RB, PdfName.RT, PdfName.RP, PdfName.WARICHU, PdfName.WT, PdfName.WP, PdfName.FIGURE, PdfName.FORMULA, PdfName.FORM); /** * Gets the list of the standard structure element names (roles). * @return */ public List<PdfName> getStandardStructElems() { if (pdf_version.getVersion() < VERSION_1_7) { return standardStructElems_1_4; } else { return standardStructElems_1_7; } } public void useExternalCacheForTagStructure(TempFileCache fileCache) { pdf.useExternalCache(fileCache); } }