From 6025b6016517c6d898d8957d1d7e03ba71431912 Mon Sep 17 00:00:00 2001 From: tknall Date: Fri, 1 Dec 2006 12:20:24 +0000 Subject: Initial import of release 2.2. git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@4 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c --- src/main/java/com/lowagie/text/pdf/PdfWriter.java | 2752 +++++++++++++++++++++ 1 file changed, 2752 insertions(+) create mode 100644 src/main/java/com/lowagie/text/pdf/PdfWriter.java (limited to 'src/main/java/com/lowagie/text/pdf/PdfWriter.java') diff --git a/src/main/java/com/lowagie/text/pdf/PdfWriter.java b/src/main/java/com/lowagie/text/pdf/PdfWriter.java new file mode 100644 index 0000000..ed93b71 --- /dev/null +++ b/src/main/java/com/lowagie/text/pdf/PdfWriter.java @@ -0,0 +1,2752 @@ +/* + * $Id: PdfWriter.java,v 1.114 2006/06/04 22:23:33 psoares33 Exp $ + * $Name: $ + * + * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie + * + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is 'iText, a free JAVA-PDF library'. + * + * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by + * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie. + * All Rights Reserved. + * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer + * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved. + * + * Contributor(s): all the names of the contributors are added in the source code + * where applicable. + * + * Alternatively, the contents of this file may be used under the terms of the + * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the + * provisions of LGPL are applicable instead of those above. If you wish to + * allow use of your version of this file only under the terms of the LGPL + * License and not to allow others to use your version of this file under + * the MPL, indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by the LGPL. + * If you do not delete the provisions above, a recipient may use your version + * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MPL as stated above or under the terms of the GNU + * Library General Public License as published by the Free Software Foundation; + * either version 2 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more + * details. + * + * If you didn't download this code from the following link, you should check if + * you aren't using an obsolete version: + * http://www.lowagie.com/iText/ + */ + +package com.lowagie.text.pdf; + +import java.awt.Color; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.HashSet; + +import com.lowagie.text.DocListener; +import com.lowagie.text.DocWriter; +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.ExceptionConverter; +import com.lowagie.text.Image; +import com.lowagie.text.ImgWMF; +import com.lowagie.text.Rectangle; +import com.lowagie.text.Table; +import com.lowagie.text.ImgPostscript; +import com.lowagie.text.pdf.events.PdfPageEventForwarder; + +/** + * A DocWriter class for PDF. + *

+ * When this PdfWriter is added + * to a certain PdfDocument, the PDF representation of every Element + * added to this Document will be written to the outputstream.

+ */ + +public class PdfWriter extends DocWriter { + + // inner classes + + /** + * This class generates the structure of a PDF document. + *

+ * This class covers the third section of Chapter 5 in the 'Portable Document Format + * Reference Manual version 1.3' (page 55-60). It contains the body of a PDF document + * (section 5.14) and it can also generate a Cross-reference Table (section 5.15). + * + * @see PdfWriter + * @see PdfObject + * @see PdfIndirectObject + */ + + public static class PdfBody { + + // inner classes + + /** + * PdfCrossReference is an entry in the PDF Cross-Reference table. + */ + + static class PdfCrossReference implements Comparable { + + // membervariables + private int type; + + /** Byte offset in the PDF file. */ + private int offset; + + private int refnum; + /** generation of the object. */ + private int generation; + + // constructors + /** + * Constructs a cross-reference element for a PdfIndirectObject. + * @param refnum + * @param offset byte offset of the object + * @param generation generationnumber of the object + */ + + PdfCrossReference(int refnum, int offset, 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 + */ + + PdfCrossReference(int refnum, int offset) { + type = 1; + this.offset = offset; + this.refnum = refnum; + this.generation = 0; + } + + PdfCrossReference(int type, int refnum, int offset, int generation) { + this.type = type; + this.offset = offset; + this.refnum = refnum; + this.generation = generation; + } + + int getRefnum() { + return refnum; + } + + /** + * Returns the PDF representation of this PdfObject. + * @param os + * @throws IOException + */ + + public void toPdf(OutputStream os) throws IOException { + // This code makes it more difficult to port the lib to JDK1.1.x: + // 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); + // so it was changed into this: + String s = "0000000000" + offset; + StringBuffer off = new StringBuffer(s.substring(s.length() - 10)); + s = "00000" + generation; + String gen = s.substring(s.length() - 5); + if (generation == 65535) { + os.write(getISOBytes(off.append(' ').append(gen).append(" f \n").toString())); + } + else + os.write(getISOBytes(off.append(' ').append(gen).append(" n \n").toString())); + } + + /** + * Writes PDF syntax to the OutputStream + * @param midSize + * @param os + * @throws IOException + */ + public void toPdf(int midSize, OutputStream os) throws IOException { + os.write((byte)type); + while (--midSize >= 0) + os.write((byte)((offset >>> (8 * midSize)) & 0xff)); + os.write((byte)((generation >>> 8) & 0xff)); + os.write((byte)(generation & 0xff)); + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Object o) { + PdfCrossReference other = (PdfCrossReference)o; + return (refnum < other.refnum ? -1 : (refnum==other.refnum ? 0 : 1)); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (obj instanceof PdfCrossReference) { + PdfCrossReference other = (PdfCrossReference)obj; + return (refnum == other.refnum); + } + else + return false; + } + + } + + // membervariables + + /** array containing the cross-reference table of the normal objects. */ + private TreeSet xrefs; + private int refnum; + /** the current byteposition in the body. */ + private int position; + private PdfWriter writer; + // constructors + + /** + * Constructs a new PdfBody. + * @param writer + */ + PdfBody(PdfWriter writer) { + xrefs = new TreeSet(); + xrefs.add(new PdfCrossReference(0, 0, 65535)); + position = writer.getOs().getCounter(); + refnum = 1; + this.writer = writer; + } + + void setRefnum(int refnum) { + this.refnum = refnum; + } + + // methods + + private static final int OBJSINSTREAM = 200; + + private ByteBuffer index; + private ByteBuffer streamObjects; + private int currentObjNum; + private int numObj = 0; + + private PdfWriter.PdfBody.PdfCrossReference addToObjStm(PdfObject obj, int nObj) throws IOException { + if (numObj >= OBJSINSTREAM) + flushObjStm(); + if (index == null) { + index = new ByteBuffer(); + streamObjects = new ByteBuffer(); + currentObjNum = getIndirectReferenceNumber(); + numObj = 0; + } + int p = streamObjects.size(); + int idx = numObj++; + PdfEncryption enc = writer.crypto; + writer.crypto = null; + obj.toPdf(writer, streamObjects); + writer.crypto = enc; + streamObjects.append(' '); + index.append(nObj).append(' ').append(p).append(' '); + return new PdfWriter.PdfBody.PdfCrossReference(2, nObj, currentObjNum, idx); + } + + private void flushObjStm() throws IOException { + if (numObj == 0) + return; + int first = index.size(); + index.append(streamObjects); + PdfStream stream = new PdfStream(index.toByteArray()); + stream.flateCompress(); + stream.put(PdfName.TYPE, PdfName.OBJSTM); + stream.put(PdfName.N, new PdfNumber(numObj)); + stream.put(PdfName.FIRST, new PdfNumber(first)); + add(stream, currentObjNum); + index = null; + streamObjects = null; + numObj = 0; + } + + /** + * Adds a PdfObject to the body. + *

+ * This methods creates a PdfIndirectObject with a + * certain number, containing the given PdfObject. + * It also adds a PdfCrossReference for this object + * to an ArrayList that will be used to build the + * Cross-reference Table. + * + * @param object a PdfObject + * @return a PdfIndirectObject + * @throws IOException + */ + + PdfIndirectObject add(PdfObject object) throws IOException { + return add(object, getIndirectReferenceNumber()); + } + + PdfIndirectObject add(PdfObject object, boolean inObjStm) throws IOException { + return add(object, getIndirectReferenceNumber(), inObjStm); + } + + /** + * Gets a PdfIndirectReference for an object that will be created in the future. + * @return a PdfIndirectReference + */ + + PdfIndirectReference getPdfIndirectReference() { + return new PdfIndirectReference(0, getIndirectReferenceNumber()); + } + + int getIndirectReferenceNumber() { + int n = refnum++; + xrefs.add(new PdfCrossReference(n, 0, 65536)); + return n; + } + + /** + * Adds a PdfObject to the body given an already existing + * PdfIndirectReference. + *

+ * This methods creates a PdfIndirectObject with the number given by + * ref, containing the given PdfObject. + * It also adds a PdfCrossReference for this object + * to an ArrayList that will be used to build the + * Cross-reference Table. + * + * @param object a PdfObject + * @param ref a PdfIndirectReference + * @return a PdfIndirectObject + * @throws IOException + */ + + PdfIndirectObject add(PdfObject object, PdfIndirectReference ref) throws IOException { + return add(object, ref.getNumber()); + } + + PdfIndirectObject add(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException { + return add(object, ref.getNumber(), inObjStm); + } + + PdfIndirectObject add(PdfObject object, int refNumber) throws IOException { + return add(object, refNumber, true); // to false + } + + PdfIndirectObject add(PdfObject object, int refNumber, boolean inObjStm) throws IOException { + if (inObjStm && object.canBeInObjStm() && writer.isFullCompression()) { + PdfCrossReference pxref = addToObjStm(object, refNumber); + PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer); + if (!xrefs.add(pxref)) { + xrefs.remove(pxref); + xrefs.add(pxref); + } + return indirect; + } + else { + PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer); + PdfCrossReference pxref = new PdfCrossReference(refNumber, position); + if (!xrefs.add(pxref)) { + xrefs.remove(pxref); + xrefs.add(pxref); + } + indirect.writeTo(writer.getOs()); + position = writer.getOs().getCounter(); + return indirect; + } + } + + /** + * Adds a PdfResources object to the body. + * + * @param object the PdfResources + * @return a PdfIndirectObject + */ + +// PdfIndirectObject add(PdfResources object) { +// return add(object); +// } + + /** + * Adds a PdfPages object to the body. + * + * @param object the root of the document + * @return a PdfIndirectObject + */ + +// PdfIndirectObject add(PdfPages object) throws IOException { +// PdfIndirectObject indirect = new PdfIndirectObject(PdfWriter.ROOT, object, writer); +// rootOffset = position; +// indirect.writeTo(writer.getOs()); +// position = writer.getOs().getCounter(); +// return indirect; +// } + + /** + * Returns the offset of the Cross-Reference table. + * + * @return an offset + */ + + int offset() { + return position; + } + + /** + * Returns the total number of objects contained in the CrossReferenceTable of this Body. + * + * @return a number of objects + */ + + int size() { + return Math.max(((PdfCrossReference)xrefs.last()).getRefnum() + 1, refnum); + } + + /** + * Returns the CrossReferenceTable of the Body. + * @param os + * @param root + * @param info + * @param encryption + * @param fileID + * @param prevxref + * @throws IOException + */ + + void writeCrossReferenceTable(OutputStream os, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int prevxref) throws IOException { + int refNumber = 0; + if (writer.isFullCompression()) { + flushObjStm(); + refNumber = getIndirectReferenceNumber(); + xrefs.add(new PdfCrossReference(refNumber, position)); + } + PdfCrossReference entry = (PdfCrossReference)xrefs.first(); + int first = entry.getRefnum(); + int len = 0; + ArrayList sections = new ArrayList(); + for (Iterator i = xrefs.iterator(); i.hasNext(); ) { + entry = (PdfCrossReference)i.next(); + if (first + len == entry.getRefnum()) + ++len; + else { + sections.add(new Integer(first)); + sections.add(new Integer(len)); + first = entry.getRefnum(); + len = 1; + } + } + sections.add(new Integer(first)); + sections.add(new Integer(len)); + if (writer.isFullCompression()) { + int mid = 4; + int mask = 0xff000000; + for (; mid > 1; --mid) { + if ((mask & position) != 0) + break; + mask >>>= 8; + } + ByteBuffer buf = new ByteBuffer(); + + for (Iterator i = xrefs.iterator(); i.hasNext(); ) { + entry = (PdfCrossReference) i.next(); + entry.toPdf(mid, buf); + } + PdfStream xr = new PdfStream(buf.toByteArray()); + buf = null; + xr.flateCompress(); + 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(((Integer)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 i = xrefs.iterator(); + for (int k = 0; k < sections.size(); k += 2) { + first = ((Integer)sections.get(k)).intValue(); + len = ((Integer)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 = (PdfCrossReference) i.next(); + entry.toPdf(os); + } + } + } + } + } + + /** + * PdfTrailer is the PDF Trailer object. + *

+ * This object is described in the 'Portable Document Format Reference Manual version 1.3' + * section 5.16 (page 59-60). + */ + + static class PdfTrailer extends PdfDictionary { + + // membervariables + + int offset; + + // constructors + + /** + * Constructs a PDF-Trailer. + * + * @param size the number of entries in the PdfCrossReferenceTable + * @param offset offset of the PdfCrossReferenceTable + * @param root an indirect reference to the root of the PDF document + * @param info an indirect reference to the info object of the PDF document + * @param encryption + * @param fileID + * @param prevxref + */ + + PdfTrailer(int size, int offset, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int 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 PdfObject. + * @param writer + * @param os + * @throws IOException + */ + public void toPdf(PdfWriter writer, OutputStream os) throws IOException { + os.write(getISOBytes("trailer\n")); + super.toPdf(null, os); + os.write(getISOBytes("\nstartxref\n")); + os.write(getISOBytes(String.valueOf(offset))); + os.write(getISOBytes("\n%%EOF\n")); + } + } + // static membervariables + + /** 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 = 1 << 22; + /** A viewer preference */ + public static final int PageLayoutTwoPageRight = 1 << 23; + + /** A viewer preference */ + public static final int PageModeUseNone = 16; + /** A viewer preference */ + public static final int PageModeUseOutlines = 32; + /** A viewer preference */ + public static final int PageModeUseThumbs = 64; + /** A viewer preference */ + public static final int PageModeFullScreen = 128; + /** A viewer preference */ + public static final int PageModeUseOC = 1 << 20; + /** A viewer preference */ + public static final int PageModeUseAttachments = 1 << 24; + + /** A viewer preference */ + public static final int HideToolbar = 256; + /** A viewer preference */ + public static final int HideMenubar = 512; + /** A viewer preference */ + public static final int HideWindowUI = 1024; + /** A viewer preference */ + public static final int FitWindow = 2048; + /** A viewer preference */ + public static final int CenterWindow = 4096; + + /** A viewer preference */ + public static final int NonFullScreenPageModeUseNone = 8192; + /** A viewer preference */ + public static final int NonFullScreenPageModeUseOutlines = 16384; + /** A viewer preference */ + public static final int NonFullScreenPageModeUseThumbs = 32768; + /** A viewer preference */ + public static final int NonFullScreenPageModeUseOC = 1 << 19; + + /** A viewer preference */ + public static final int DirectionL2R = 1 << 16; + /** A viewer preference */ + public static final int DirectionR2L = 1 << 17; + /** A viewer preference */ + public static final int DisplayDocTitle = 1 << 18; + /** A viewer preference */ + public static final int PrintScalingNone = 1 << 21; + /** The mask to decide if a ViewerPreferences dictionary is needed */ + static final int ViewerPreferencesMask = 0xffff00; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowPrinting = 4 + 2048; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowModifyContents = 8; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowCopy = 16; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowModifyAnnotations = 32; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowFillIn = 256; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowScreenReaders = 512; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowAssembly = 1024; + /** The operation permitted when the document is opened with the user password */ + public static final int AllowDegradedPrinting = 4; + /** Type of encryption */ + public static final boolean STRENGTH40BITS = false; + /** Type of encryption */ + public static final boolean STRENGTH128BITS = true; + /** 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; + /** action value */ + public static final PdfName PAGE_OPEN = PdfName.O; + /** action value */ + public static final PdfName PAGE_CLOSE = PdfName.C; + + /** signature value */ + public static final int SIGNATURE_EXISTS = 1; + /** signature value */ + public static final int SIGNATURE_APPEND_ONLY = 2; + + /** possible PDF version */ + public static final char VERSION_1_2 = '2'; + /** possible PDF version */ + public static final char VERSION_1_3 = '3'; + /** possible PDF version */ + public static final char VERSION_1_4 = '4'; + /** possible PDF version */ + public static final char VERSION_1_5 = '5'; + /** possible PDF version */ + public static final char VERSION_1_6 = '6'; + + private static final int VPOINT = 7; + /** this is the header of a PDF document */ + protected byte[] HEADER = getISOBytes("%PDF-1.4\n%\u00e2\u00e3\u00cf\u00d3\n"); + + protected int prevxref = 0; + + protected PdfPages root = new PdfPages(this); + + /** 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 HashMap images = new HashMap(); + + /** The form XObjects in this document. The key is the xref and the value + is Object[]{PdfName, template}.*/ + protected HashMap formXObjects = new HashMap(); + + /** The name counter for the form XObjects name. */ + protected int formXObjectsCounter = 1; + + /** The font number counter for the fonts in the document. */ + protected int fontNumber = 1; + + /** The color number counter for the colors in the document. */ + protected int colorNumber = 1; + + /** The patten number counter for the colors in the document. */ + protected int patternNumber = 1; + + /** The direct content in this document. */ + protected PdfContentByte directContent; + + /** The direct content under in this document. */ + protected PdfContentByte directContentUnder; + + /** The fonts of this document */ + protected HashMap documentFonts = new HashMap(); + + /** The colors of this document */ + protected HashMap documentColors = new HashMap(); + + /** The patterns of this document */ + protected HashMap documentPatterns = new HashMap(); + + protected HashMap documentShadings = new HashMap(); + + protected HashMap documentShadingPatterns = new HashMap(); + + protected ColorDetails patternColorspaceRGB; + protected ColorDetails patternColorspaceGRAY; + protected ColorDetails patternColorspaceCMYK; + protected HashMap documentSpotPatterns = new HashMap(); + + protected HashMap documentExtGState = new HashMap(); + + protected HashMap documentProperties = new HashMap(); + protected HashSet documentOCG = new HashSet(); + protected ArrayList documentOCGorder = new ArrayList(); + protected PdfOCProperties OCProperties; + protected PdfArray OCGRadioGroup = new PdfArray(); + + protected PdfDictionary defaultColorspace = new PdfDictionary(); + protected float userunit = 0f; + + /** PDF/X value */ + public static final int PDFXNONE = 0; + /** PDF/X value */ + public static final int PDFX1A2001 = 1; + /** PDF/X value */ + public static final int PDFX32002 = 2; + + private int pdfxConformance = PDFXNONE; + + static final int PDFXKEY_COLOR = 1; + static final int PDFXKEY_CMYK = 2; + static final int PDFXKEY_RGB = 3; + static final int PDFXKEY_FONT = 4; + static final int PDFXKEY_IMAGE = 5; + static final int PDFXKEY_GSTATE = 6; + static final int PDFXKEY_LAYER = 7; + + // membervariables + + /** body of the PDF document */ + protected PdfBody body; + + /** the pdfdocument object. */ + protected PdfDocument pdf; + + /** The PdfPageEvent for this document. */ + private PdfPageEvent pageEvent; + + protected PdfEncryption crypto; + + protected HashMap importedPages = new HashMap(); + + protected PdfReaderInstance currentPdfReaderInstance; + + /** The PdfIndirectReference to the pages. */ + protected ArrayList pageReferences = new ArrayList(); + + protected int currentPageNumber = 1; + + protected PdfDictionary group; + + /** 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; + + /** 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; + /** + * The ratio between the extra word spacing and the extra character spacing. + * Extra word spacing will grow ratio times more than extra character spacing. + */ + private float spaceCharRatio = SPACE_CHAR_RATIO_DEFAULT; + + /** Holds value of property extraCatalog. */ + private PdfDictionary extraCatalog; + + /** XMP Metadata for the document. */ + protected byte[] xmpMetadata = null; + /** + * Holds value of property fullCompression. + */ + protected boolean fullCompression = false; + + protected boolean tagged = false; + + protected PdfStructureTreeRoot structureTreeRoot; + + // constructor + + protected PdfWriter() { + } + + /** + * Constructs a PdfWriter. + *

+ * Remark: a PdfWriter can only be constructed by calling the method + * getInstance(Document document, OutputStream os). + * + * @param document The PdfDocument that has to be written + * @param os The OutputStream the writer has to write to. + */ + + protected PdfWriter(PdfDocument document, OutputStream os) { + super(document, os); + pdf = document; + directContent = new PdfContentByte(this); + directContentUnder = new PdfContentByte(this); + } + + // get an instance of the PdfWriter + + /** + * Gets an instance of the PdfWriter. + * + * @param document The Document that has to be written + * @param os The OutputStream the writer has to write to. + * @return a new PdfWriter + * + * @throws DocumentException on error + */ + + public static PdfWriter getInstance(Document document, OutputStream os) + throws DocumentException { + PdfDocument pdf = new PdfDocument(); + document.addDocListener(pdf); + PdfWriter writer = new PdfWriter(pdf, os); + pdf.addWriter(writer); + return writer; + } + + /** Gets an instance of the PdfWriter. + * + * @return a new PdfWriter + * @param document The Document that has to be written + * @param os The OutputStream the writer has to write to. + * @param listener A DocListener to pass to the PdfDocument. + * @throws DocumentException on error + */ + + public static PdfWriter getInstance(Document document, OutputStream os, DocListener listener) + throws DocumentException { + PdfDocument pdf = new PdfDocument(); + pdf.addDocListener(listener); + document.addDocListener(pdf); + PdfWriter writer = new PdfWriter(pdf, os); + pdf.addWriter(writer); + return writer; + } + + // methods to write objects to the outputstream + + /** + * Adds some PdfContents to this Writer. + *

+ * The document has to be open before you can begin to add content + * to the body of the document. + * + * @return a PdfIndirectReference + * @param page the PdfPage to add + * @param contents the PdfContents of the page + * @throws PdfException on error + */ + + PdfIndirectReference add(PdfPage page, PdfContents contents) throws PdfException { + if (!open) { + throw new PdfException("The document isn't open."); + } + PdfIndirectObject object; + try { + object = addToBody(contents); + } + catch(IOException ioe) { + throw new ExceptionConverter(ioe); + } + page.add(object.getIndirectReference()); + if (group != null) { + page.put(PdfName.GROUP, group); + group = null; + } + root.addPage(page); + currentPageNumber++; + return null; + } + + /** + * Adds an image to the document but not to the page resources. It is used with + * templates and Document.add(Image). + * @param image the Image to add + * @return the name of the image added + * @throws PdfException on error + * @throws DocumentException on error + */ + public PdfName addDirectImageSimple(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 Document.add(Image). + * @param image the Image to add + * @param fixedRef the reference to used. It may be null, + * a PdfIndirectReference or a PRIndirectReference. + * @return the name of the image added + * @throws PdfException on error + * @throws DocumentException on error + */ + public PdfName addDirectImageSimple(Image image, PdfIndirectReference fixedRef) throws PdfException, DocumentException { + PdfName name; + // if the images is already added, just retrieve the name + if (images.containsKey(image.getMySerialId())) { + name = (PdfName) 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.templateData() == null) { + if(image instanceof ImgWMF){ + try { + ImgWMF wmf = (ImgWMF)image; + wmf.readWMF(getDirectContent().createTemplate(0, 0)); + } + catch (Exception e) { + throw new DocumentException(e); + } + }else{ + try { + ((ImgPostscript)image).readPostscript(getDirectContent().createTemplate(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 = (PdfName)images.get(maskImage.getMySerialId()); + maskRef = getImageReference(mname); + } + PdfImage i = new PdfImage(image, "img" + images.size(), maskRef); + if (image.hasICCProfile()) { + PdfICCBased icc = new PdfICCBased(image.getICCProfile()); + PdfIndirectReference iccRef = add(icc); + PdfArray iccArray = new PdfArray(); + iccArray.add(PdfName.ICCBASED); + iccArray.add(iccRef); + PdfObject colorspace = i.get(PdfName.COLORSPACE); + if (colorspace != null && colorspace.isArray()) { + ArrayList ar = ((PdfArray)colorspace).getArrayList(); + if (ar.size() > 1 && PdfName.INDEXED.equals(ar.get(0))) + ar.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 PdfImage to the outputstream. + * + * @param pdfImage the image to be added + * @return a PdfIndirectReference to the encapsulated image + * @throws PdfException when a document isn't open yet, or has been closed + */ + + PdfIndirectReference add(PdfImage pdfImage, PdfIndirectReference fixedRef) throws PdfException { + if (! imageDictionary.contains(pdfImage.name())) { + checkPDFXConformance(this, PDFXKEY_IMAGE, pdfImage); + if (fixedRef != null && 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()); + } + + protected PdfIndirectReference add(PdfICCBased icc) throws PdfException { + PdfIndirectObject object; + try { + object = addToBody(icc); + } + catch(IOException ioe) { + throw new ExceptionConverter(ioe); + } + return object.getIndirectReference(); + } + + /** + * return the PdfIndirectReference to the image with a given name. + * + * @param name the name of the image + * @return a PdfIndirectReference + */ + + PdfIndirectReference getImageReference(PdfName name) { + return (PdfIndirectReference) imageDictionary.get(name); + } + + // methods to open and close the writer + + /** + * Signals that the Document has been opened and that + * Elements can be added. + *

+ * When this method is called, the PDF-document header is + * written to the outputstream. + */ + + public void open() { + super.open(); + try { + os.write(HEADER); + body = new PdfBody(this); + if (pdfxConformance == PDFX32002) { + 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); + } + } + + private static void getOCGOrder(PdfArray order, PdfLayer layer) { + if (!layer.isOnPanel()) + return; + if (layer.getTitle() == null) + order.add(layer.getRef()); + ArrayList 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, (PdfLayer)children.get(k)); + } + if (kids.size() > 0) + order.add(kids); + } + + private void addASEvent(PdfName event, PdfName category) { + PdfArray arr = new PdfArray(); + for (Iterator it = documentOCG.iterator(); it.hasNext();) { + PdfLayer layer = (PdfLayer)it.next(); + PdfDictionary usage = (PdfDictionary)layer.get(PdfName.USAGE); + if (usage != null && usage.get(category) != null) + arr.add(layer.getRef()); + } + if (arr.size() == 0) + return; + PdfDictionary d = (PdfDictionary)OCProperties.get(PdfName.D); + PdfArray arras = (PdfArray)d.get(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); + } + + private void fillOCProperties(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 (Iterator it = documentOCG.iterator(); it.hasNext();) { + PdfLayer layer = (PdfLayer)it.next(); + gr.add(layer.getRef()); + } + OCProperties.put(PdfName.OCGS, gr); + } + if (OCProperties.get(PdfName.D) != null) + return; + ArrayList docOrder = new ArrayList(documentOCGorder); + for (Iterator it = docOrder.iterator(); it.hasNext();) { + PdfLayer layer = (PdfLayer)it.next(); + if (layer.getParent() != null) + it.remove(); + } + PdfArray order = new PdfArray(); + for (Iterator it = docOrder.iterator(); it.hasNext();) { + PdfLayer layer = (PdfLayer)it.next(); + getOCGOrder(order, layer); + } + PdfDictionary d = new PdfDictionary(); + OCProperties.put(PdfName.D, d); + d.put(PdfName.ORDER, order); + PdfArray gr = new PdfArray(); + for (Iterator it = documentOCG.iterator(); it.hasNext();) { + PdfLayer layer = (PdfLayer)it.next(); + 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); + 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); + } + + protected PdfDictionary getCatalog(PdfIndirectReference rootObj) + { + PdfDictionary catalog = ((PdfDocument)document).getCatalog(rootObj); + if (tagged) { + try { + getStructureTreeRoot().buildTree(); + } + catch (Exception e) { + throw new ExceptionConverter(e); + } + catalog.put(PdfName.STRUCTTREEROOT, structureTreeRoot.getReference()); + PdfDictionary mi = new PdfDictionary(); + mi.put(PdfName.MARKED, PdfBoolean.PDFTRUE); + catalog.put(PdfName.MARKINFO, mi); + } + if (documentOCG.size() == 0) + return catalog; + fillOCProperties(false); + catalog.put(PdfName.OCPROPERTIES, OCProperties); + return catalog; + } + + protected void addSharedObjectsToBody() throws IOException { + // add the fonts + for (Iterator it = documentFonts.values().iterator(); it.hasNext();) { + FontDetails details = (FontDetails)it.next(); + details.writeFont(this); + } + // add the form XObjects + for (Iterator it = formXObjects.values().iterator(); it.hasNext();) { + Object objs[] = (Object[])it.next(); + PdfTemplate template = (PdfTemplate)objs[1]; + if (template != null && template.getIndirectReference() instanceof PRIndirectReference) + continue; + if (template != null && template.getType() == PdfTemplate.TYPE_TEMPLATE) { + addToBody(template.getFormXObject(), template.getIndirectReference()); + } + } + // add all the dependencies in the imported pages + for (Iterator it = importedPages.values().iterator(); it.hasNext();) { + currentPdfReaderInstance = (PdfReaderInstance)it.next(); + currentPdfReaderInstance.writeAllPages(); + } + currentPdfReaderInstance = null; + // add the color + for (Iterator it = documentColors.values().iterator(); it.hasNext();) { + ColorDetails color = (ColorDetails)it.next(); + addToBody(color.getSpotColor(this), color.getIndirectReference()); + } + // add the pattern + for (Iterator it = documentPatterns.keySet().iterator(); it.hasNext();) { + PdfPatternPainter pat = (PdfPatternPainter)it.next(); + addToBody(pat.getPattern(), pat.getIndirectReference()); + } + // add the shading patterns + for (Iterator it = documentShadingPatterns.keySet().iterator(); it.hasNext();) { + PdfShadingPattern shadingPattern = (PdfShadingPattern)it.next(); + shadingPattern.addToBody(); + } + // add the shadings + for (Iterator it = documentShadings.keySet().iterator(); it.hasNext();) { + PdfShading shading = (PdfShading)it.next(); + shading.addToBody(); + } + // add the extgstate + for (Iterator it = documentExtGState.keySet().iterator(); it.hasNext();) { + PdfDictionary gstate = (PdfDictionary)it.next(); + PdfObject obj[] = (PdfObject[])documentExtGState.get(gstate); + addToBody(gstate, (PdfIndirectReference)obj[1]); + } + // add the properties + for (Iterator it = documentProperties.keySet().iterator(); it.hasNext();) { + Object prop = it.next(); + PdfObject[] obj = (PdfObject[])documentProperties.get(prop); + 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]); + } + } + for (Iterator it = documentOCG.iterator(); it.hasNext();) { + PdfOCG layer = (PdfOCG)it.next(); + addToBody(layer.getPdfObject(), layer.getRef()); + } + } + + /** + * Signals that the Document was closed and that no other + * Elements will be added. + *

+ * The pages-tree is built and written to the outputstream. + * A Catalog is constructed, as well as an Info-object, + * the referencetable is composed and everything is written + * to the outputstream embedded in a Trailer. + */ + + public synchronized 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(); + // 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 there is XMP data to add: add it + if (xmpMetadata != null) { + PdfStream xmp = new PdfStream(xmpMetadata); + xmp.put(PdfName.TYPE, PdfName.METADATA); + xmp.put(PdfName.SUBTYPE, PdfName.XML); + catalog.put(PdfName.METADATA, body.add(xmp).getIndirectReference()); + } + // make pdfx conformant + PdfDictionary info = getInfo(); + if (pdfxConformance != PDFXNONE) { + if (info.get(PdfName.GTS_PDFXVERSION) == null) { + if (pdfxConformance == PDFX1A2001) { + 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 (pdfxConformance == PDFX32002) + 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")); + } + getExtraCatalog(); + 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)); + } + } + if (extraCatalog != null) { + catalog.mergeDifferent(extraCatalog); + } + PdfIndirectObject indirectCatalog = addToBody(catalog, false); + // add the info-object to the body + PdfIndirectObject infoObj = addToBody(info, false); + PdfIndirectReference encryption = null; + PdfObject fileID = null; + body.flushObjStm(); + if (crypto != null) { + PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false); + encryption = encryptionObject.getIndirectReference(); + fileID = crypto.getFileID(); + } + else + fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId()); + + // write the cross-reference table of the body + body.writeCrossReferenceTable(os, indirectCatalog.getIndirectReference(), + infoObj.getIndirectReference(), encryption, fileID, prevxref); + + // make the trailer + if (fullCompression) { + 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); + } + super.close(); + } + catch(IOException ioe) { + throw new ExceptionConverter(ioe); + } + } + } + + // methods + + /** + * Sometimes it is necessary to know where the just added Table ends. + * + * For instance to avoid to add another table in a page that is ending up, because + * the new table will be probably splitted just after the header (it is an + * unpleasant effect, isn't it?). + * + * Added on September 8th, 2001 + * by Francesco De Milato + * francesco.demilato@tiscalinet.it + * @param table the Table + * @return the bottom height of the just added table + */ + + public float getTableBottom(Table table) { + return pdf.bottom(table) - pdf.indentBottom(); + } + + /** + * Gets a pre-rendered table. + * (Contributed by dperezcar@fcc.es) + * @param table Contains the table definition. Its contents are deleted, after being pre-rendered. + * @return a PdfTable + */ + + public PdfTable getPdfTable(Table table) { + return pdf.getPdfTable(table, true); + } + + /** + * Row additions to the original {@link Table} used to build the {@link PdfTable} are processed and pre-rendered, + * and then the contents are deleted. + * If the pre-rendered table doesn't fit, then it is fully rendered and its data discarded. + * There shouldn't be any column change in the underlying {@link Table} object. + * (Contributed by dperezcar@fcc.es) + * + * @param table The pre-rendered table obtained from {@link #getPdfTable(Table)} + * @return true if the table is rendered and emptied. + * @throws DocumentException + * @see #getPdfTable(Table) + */ + + public boolean breakTableIfDoesntFit(PdfTable table) throws DocumentException { + return pdf.breakTableIfDoesntFit(table); + } + + /** + * Checks if a Table fits the current page of the PdfDocument. + * + * @param table the table that has to be checked + * @param margin a certain margin + * @return true if the Table fits the page, false otherwise. + */ + + public boolean fitsPage(Table table, float margin) { + return pdf.bottom(table) > pdf.indentBottom() + margin; + } + + /** + * Checks if a Table fits the current page of the PdfDocument. + * + * @param table the table that has to be checked + * @return true if the Table fits the page, false otherwise. + */ + + public boolean fitsPage(Table table) { + return fitsPage(table, 0); + } + + /** + * Checks if a PdfPTable fits the current page of the PdfDocument. + * + * @param table the table that has to be checked + * @param margin a certain margin + * @return true if the PdfPTable fits the page, false otherwise. + */ + public boolean fitsPage(PdfPTable table, float margin) { + return pdf.fitsPage(table, margin); + } + + /** + * Checks if a PdfPTable fits the current page of the PdfDocument. + * + * @param table the table that has to be checked + * @return true if the PdfPTable fits the page, false otherwise. + */ + public boolean fitsPage(PdfPTable table) { + return pdf.fitsPage(table, 0); + } + + /** + * Gets the current vertical page position. + * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects + * for elements that do not terminate the lines they've started because those lines will get + * terminated. + * @return The current vertical page position. + */ + public float getVerticalPosition(boolean ensureNewLine) { + return pdf.getVerticalPosition(ensureNewLine); + } + + /** + * Checks if writing is paused. + * + * @return true if writing temporarely has to be paused, false otherwise. + */ + + boolean isPaused() { + return pause; + } + + /** + * Gets the direct content for this document. There is only one direct content, + * multiple calls to this method will allways retrieve the same. + * @return the direct content + */ + + public PdfContentByte getDirectContent() { + if (!open) + throw new RuntimeException("The document is not open."); + return directContent; + } + + /** + * Gets the direct content under for this document. There is only one direct content, + * multiple calls to this method will allways retrieve the same. + * @return the direct content + */ + + public PdfContentByte getDirectContentUnder() { + if (!open) + throw new RuntimeException("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(); + } + + /** Gets the AcroForm object. + * @return the PdfAcroForm + */ + + public PdfAcroForm getAcroForm() { + return pdf.getAcroForm(); + } + + /** Gets the root outline. + * @return the root outline + */ + + public PdfOutline getRootOutline() { + return directContent.getRootOutline(); + } + + /** + * Returns the outputStreamCounter. + * @return the outputStreamCounter + */ + public OutputStreamCounter getOs() { + return os; + } + + /** + * Adds a BaseFont to the document but not to the page resources. + * It is used for templates. + * @param bf the BaseFont to add + * @return an Object[] where position 0 is a PdfName + * and position 1 is an PdfIndirectReference + */ + + FontDetails addSimple(BaseFont bf) { + if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) { + return new FontDetails(new PdfName("F" + (fontNumber++)), ((DocumentFont)bf).getIndirectReference(), bf); + } + FontDetails ret = (FontDetails)documentFonts.get(bf); + if (ret == null) { + checkPDFXConformance(this, PDFXKEY_FONT, bf); + ret = new FontDetails(new PdfName("F" + (fontNumber++)), body.getPdfIndirectReference(), bf); + documentFonts.put(bf, ret); + } + return ret; + } + + void eliminateFontSubset(PdfDictionary fonts) { + for (Iterator it = documentFonts.values().iterator(); it.hasNext();) { + FontDetails ft = (FontDetails)it.next(); + if (fonts.get(ft.getFontName()) != null) + ft.setSubset(false); + } + } + + PdfName getColorspaceName() { + return new PdfName("CS" + (colorNumber++)); + } + + /** + * Adds a SpotColor to the document but not to the page resources. + * @param spc the SpotColor to add + * @return an Object[] where position 0 is a PdfName + * and position 1 is an PdfIndirectReference + */ + + ColorDetails addSimple(PdfSpotColor spc) { + ColorDetails ret = (ColorDetails)documentColors.get(spc); + if (ret == null) { + ret = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), spc); + documentColors.put(spc, ret); + } + return ret; + } + + ColorDetails addSimplePatternColorspace(Color color) { + int type = ExtendedColor.getType(color); + if (type == ExtendedColor.TYPE_PATTERN || type == ExtendedColor.TYPE_SHADING) + throw new RuntimeException("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 = (ColorDetails)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("Invalid color type in PdfWriter.addSimplePatternColorspace()."); + } + } + catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + } + + void addSimpleShadingPattern(PdfShadingPattern shading) { + if (!documentShadingPatterns.containsKey(shading)) { + shading.setName(patternNumber); + ++patternNumber; + documentShadingPatterns.put(shading, null); + addSimpleShading(shading.getShading()); + } + } + + void addSimpleShading(PdfShading shading) { + if (!documentShadings.containsKey(shading)) { + documentShadings.put(shading, null); + shading.setName(documentShadings.size()); + } + } + + PdfObject[] addSimpleExtGState(PdfDictionary gstate) { + if (!documentExtGState.containsKey(gstate)) { + checkPDFXConformance(this, PDFXKEY_GSTATE, gstate); + documentExtGState.put(gstate, new PdfObject[]{new PdfName("GS" + (documentExtGState.size() + 1)), getPdfIndirectReference()}); + } + return (PdfObject[])documentExtGState.get(gstate); + } + + void registerLayer(PdfOCG layer) { + checkPDFXConformance(this, PDFXKEY_LAYER, null); + 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("Only PdfLayer is accepted."); + } + + PdfObject[] addSimpleProperty(Object prop, PdfIndirectReference refi) { + if (!documentProperties.containsKey(prop)) { + if (prop instanceof PdfOCG) + checkPDFXConformance(this, PDFXKEY_LAYER, null); + documentProperties.put(prop, new PdfObject[]{new PdfName("Pr" + (documentProperties.size() + 1)), refi}); + } + return (PdfObject[])documentProperties.get(prop); + } + + boolean propertyExists(Object prop) { + return documentProperties.containsKey(prop); + } + /** + * Gets the PdfDocument associated with this writer. + * @return the PdfDocument + */ + + PdfDocument getPdfDocument() { + return pdf; + } + + /** + * Gets a PdfIndirectReference for an object that + * will be created in the future. + * @return the PdfIndirectReference + */ + + public PdfIndirectReference getPdfIndirectReference() { + return body.getPdfIndirectReference(); + } + + int getIndirectReferenceNumber() { + return body.getIndirectReferenceNumber(); + } + + PdfName addSimplePattern(PdfPatternPainter painter) { + PdfName name = (PdfName)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; + } + + /** + * 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 PdfName for this template + */ + + PdfName addDirectTemplateSimple(PdfTemplate template, PdfName forcedName) { + PdfIndirectReference ref = template.getIndirectReference(); + Object obj[] = (Object[])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) + template = null; + formXObjects.put(ref, new Object[]{name, template}); + } + else + name = (PdfName)obj[0]; + } + catch (Exception e) { + throw new ExceptionConverter(e); + } + return name; + } + + /** + * Sets the PdfPageEvent for this document. + * @param event the PdfPageEvent for this document + */ + + public void setPageEvent(PdfPageEvent event) { + if (event == null) this.pageEvent = null; + else if (this.pageEvent == null) this.pageEvent = event; + else if (this.pageEvent instanceof PdfPageEventForwarder) ((PdfPageEventForwarder)this.pageEvent).addPageEvent(event); + else { + PdfPageEventForwarder forward = new PdfPageEventForwarder(); + forward.addPageEvent(this.pageEvent); + forward.addPageEvent(event); + this.pageEvent = forward; + } + } + + /** + * Gets the PdfPageEvent for this document or null + * if none is set. + * @return the PdfPageEvent for this document or null + * if none is set + */ + + public PdfPageEvent getPageEvent() { + return pageEvent; + } + + /** + * Adds the local destinations to the body of the document. + * @param dest the HashMap containing the destinations + * @throws IOException on error + */ + + void addLocalDestinations(TreeMap dest) throws IOException { + for (Iterator i = dest.keySet().iterator(); i.hasNext();) { + String name = (String)i.next(); + Object obj[] = (Object[])dest.get(name); + PdfDestination destination = (PdfDestination)obj[2]; + if (destination == null) + throw new RuntimeException("The name '" + name + "' has no local destination."); + if (obj[1] == null) + obj[1] = getPdfIndirectReference(); + addToBody(destination, (PdfIndirectReference)obj[1]); + } + } + + /** + * Gets the current pagenumber of this document. + * + * @return a page number + */ + + public int getPageNumber() { + return pdf.getPageNumber(); + } + + /** + * Sets the viewer preferences by ORing some constants. + *

+ *

+ * @param preferences the viewer preferences + */ + + public void setViewerPreferences(int preferences) { + pdf.setViewerPreferences(preferences); + } + + /** 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 true for 128 bit key length, false for 40 bit key length + * @throws DocumentException if the document is already open + */ + public void setEncryption(byte userPassword[], byte ownerPassword[], int permissions, boolean strength128Bits) throws DocumentException { + if (pdf.isOpen()) + throw new DocumentException("Encryption can only be added before opening the document."); + crypto = new PdfEncryption(); + crypto.setupAllKeys(userPassword, ownerPassword, permissions, strength128Bits); + } + + /** + * 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 true for 128 bit key length, false 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 + */ + public void setEncryption(boolean strength, String userPassword, String ownerPassword, int permissions) throws DocumentException { + setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, strength); + } + + /** + * Adds an object to the PDF body. + * @param object + * @return a PdfIndirectObject + * @throws IOException + */ + public PdfIndirectObject addToBody(PdfObject object) throws IOException { + PdfIndirectObject iobj = body.add(object); + return iobj; + } + + /** + * Adds an object to the PDF body. + * @param object + * @param inObjStm + * @return a PdfIndirectObject + * @throws IOException + */ + public PdfIndirectObject addToBody(PdfObject object, boolean inObjStm) throws IOException { + PdfIndirectObject iobj = body.add(object, inObjStm); + return iobj; + } + + /** + * Adds an object to the PDF body. + * @param object + * @param ref + * @return a PdfIndirectObject + * @throws IOException + */ + public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref) throws IOException { + PdfIndirectObject iobj = body.add(object, ref); + return iobj; + } + + /** + * Adds an object to the PDF body. + * @param object + * @param ref + * @param inObjStm + * @return a PdfIndirectObject + * @throws IOException + */ + public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException { + PdfIndirectObject iobj = body.add(object, ref, inObjStm); + return iobj; + } + + /** + * Adds an object to the PDF body. + * @param object + * @param refNumber + * @return a PdfIndirectObject + * @throws IOException + */ + public PdfIndirectObject addToBody(PdfObject object, int refNumber) throws IOException { + PdfIndirectObject iobj = body.add(object, refNumber); + return iobj; + } + + /** + * Adds an object to the PDF body. + * @param object + * @param refNumber + * @param inObjStm + * @return a PdfIndirectObject + * @throws IOException + */ + public PdfIndirectObject addToBody(PdfObject object, int refNumber, boolean inObjStm) throws IOException { + PdfIndirectObject iobj = body.add(object, refNumber, inObjStm); + return iobj; + } + + /** When the document opens it will jump to the destination with + * this name. + * @param name the name of the destination to jump to + */ + public void setOpenAction(String name) { + pdf.setOpenAction(name); + } + + /** Additional-actions defining the actions to be taken in + * response to various trigger events affecting the document + * as a whole. The actions types allowed are: DOCUMENT_CLOSE, + * WILL_SAVE, DID_SAVE, WILL_PRINT + * and DID_PRINT. + * + * @param actionType the action type + * @param action the action to execute in response to the trigger + * @throws PdfException on invalid action type + */ + public void setAdditionalAction(PdfName actionType, PdfAction action) throws PdfException { + if (!(actionType.equals(DOCUMENT_CLOSE) || + actionType.equals(WILL_SAVE) || + actionType.equals(DID_SAVE) || + actionType.equals(WILL_PRINT) || + actionType.equals(DID_PRINT))) { + throw new PdfException("Invalid additional action type: " + actionType.toString()); + } + pdf.addAdditionalAction(actionType, action); + } + + /** When the document opens this action will be + * invoked. + * @param action the action to be invoked + */ + public void setOpenAction(PdfAction action) { + pdf.setOpenAction(action); + } + + /** Sets the page labels + * @param pageLabels the page labels + */ + public void setPageLabels(PdfPageLabels pageLabels) { + pdf.setPageLabels(pageLabels); + } + + PdfEncryption getEncryption() { + return crypto; + } + + RandomAccessFileOrArray getReaderFile(PdfReader reader) { + return currentPdfReaderInstance.getReaderFile(); + } + + protected int getNewObjectNumber(PdfReader reader, int number, int generation) { + return currentPdfReaderInstance.getNewObjectNumber(number, generation); + } + + /** Gets 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(PdfReader reader, int pageNumber) { + PdfReaderInstance inst = (PdfReaderInstance)importedPages.get(reader); + if (inst == null) { + inst = reader.getPdfReaderInstance(this); + importedPages.put(reader, inst); + } + return inst.getImportedPage(pageNumber); + } + + /** Adds a JavaScript action at the document level. When the document + * opens all this JavaScript runs. + * @param js The JavaScrip action + */ + public void addJavaScript(PdfAction js) { + pdf.addJavaScript(js); + } + + /** Adds 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(String code, boolean unicode) { + addJavaScript(PdfAction.javaScript(code, this, unicode)); + } + + /** Adds a JavaScript action at the document level. When the document + * opens all this JavaScript runs. + * @param code the JavaScript code + */ + public void addJavaScript(String code) { + addJavaScript(code, false); + } + + /** Adds a file attachment at the document level. + * @param description the file description + * @param fileStore an array with the file. If it's null + * the file will be read from the disk + * @param file the path to the file. It will only be used if + * fileStore is not null + * @param fileDisplay the actual file name stored in the pdf + * @throws IOException on error + */ + public void addFileAttachment(String description, byte fileStore[], String file, String fileDisplay) throws IOException { + addFileAttachment(description, PdfFileSpecification.fileEmbedded(this, file, fileDisplay, fileStore)); + } + + /** Adds a file attachment at the document level. + * @param description the file description + * @param fs the file specification + */ + public void addFileAttachment(String description, PdfFileSpecification fs) throws IOException { + pdf.addFileAttachment(description, fs); + } + + /** Sets 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(Rectangle crop) { + pdf.setCropBoxSize(crop); + } + + /** Gets 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("The page numbers start at 1."); + PdfIndirectReference ref; + if (page < pageReferences.size()) { + ref = (PdfIndirectReference)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; + } + + PdfIndirectReference getCurrentPage() { + return getPageReference(currentPageNumber); + } + + int getCurrentPageNumber() { + return currentPageNumber; + } + + /** Adds the PdfAnnotation to the calculation order + * array. + * @param annot the PdfAnnotation to be added + */ + public void addCalculationOrder(PdfFormField annot) { + pdf.addCalculationOrder(annot); + } + + /** Set the signature flags. + * @param f the flags. This flags are ORed with current ones + */ + public void setSigFlags(int f) { + pdf.setSigFlags(f); + } + + /** Adds a PdfAnnotation or a PdfFormField + * to the document. Only the top parent of a PdfFormField + * needs to be added. + * @param annot the PdfAnnotation or the PdfFormField to add + */ + public void addAnnotation(PdfAnnotation annot) { + pdf.addAnnotation(annot); + } + + void addAnnotation(PdfAnnotation annot, int page) { + addAnnotation(annot); + } + + /** Sets the PDF version. Must be used right before the document + * is opened. Valid options are VERSION_1_2, VERSION_1_3, + * VERSION_1_4, VERSION_1_5 and VERSION_1_6. VERSION_1_4 is the default. + * @param version the version number + */ + public void setPdfVersion(char version) { + if (HEADER.length > VPOINT) + HEADER[VPOINT] = (byte)version; + } + + /** Reorder the pages in the document. A null argument value + * only returns the number of pages to process. It is + * advisable to issue a Document.newPage() + * before using this method. + * @return the total number of pages + * @param order an array with the new page sequence. It must have the + * same size as the number of pages. + * @throws DocumentException if all the pages are not present in the array + */ + public int reorderPages(int order[]) throws DocumentException { + return root.reorderPages(order); + } + + /** Gets the space/character extra spacing ratio for + * fully justified text. + * @return the space/character extra spacing ratio + */ + public float getSpaceCharRatio() { + return spaceCharRatio; + } + + /** Sets the ratio between the extra word spacing and the extra character spacing + * when the text is fully justified. + * Extra word spacing will grow spaceCharRatio times more than extra character spacing. + * If the ratio is PdfWriter.NO_SPACE_CHAR_RATIO 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(float spaceCharRatio) { + if (spaceCharRatio < 0.001f) + this.spaceCharRatio = 0.001f; + else + this.spaceCharRatio = spaceCharRatio; + } + + /** Sets the run direction. This is only used as a placeholder + * as it does not affect anything. + * @param runDirection the run direction + */ + public void setRunDirection(int runDirection) { + if (runDirection < RUN_DIRECTION_NO_BIDI || runDirection > RUN_DIRECTION_RTL) + throw new RuntimeException("Invalid run direction: " + runDirection); + this.runDirection = runDirection; + } + + /** Gets the run direction. + * @return the run direction + */ + public int getRunDirection() { + return runDirection; + } + + /** + * Sets the display duration for the page (for presentations) + * @param seconds the number of seconds to display the page + */ + public void setDuration(int seconds) { + pdf.setDuration(seconds); + } + + /** + * Sets the transition for the page + * @param transition the Transition object + */ + public void setTransition(PdfTransition transition) { + pdf.setTransition(transition); + } + + /** Writes the reader to the document and frees 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 PdfReader to free + * @throws IOException on error + */ + public void freeReader(PdfReader reader) throws IOException { + currentPdfReaderInstance = (PdfReaderInstance)importedPages.get(reader); + if (currentPdfReaderInstance == null) + return; + currentPdfReaderInstance.writeAllPages(); + currentPdfReaderInstance = null; + importedPages.remove(reader); + } + + /** Sets the open and close page additional action. + * @param actionType the action type. It can be PdfWriter.PAGE_OPEN + * or PdfWriter.PAGE_CLOSE + * @param action the action to perform + * @throws PdfException if the action type is invalid + */ + public void setPageAction(PdfName actionType, PdfAction action) throws PdfException { + if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE)) + throw new PdfException("Invalid page additional action type: " + actionType.toString()); + pdf.setPageAction(actionType, action); + } + + /** Gets the current document size. This size only includes + * the data already writen to the output stream, it does not + * include templates or fonts. It is usefull if used with + * freeReader() when concatenating many documents + * and an idea of the current size is needed. + * @return the approximate size without fonts or templates + */ + public int getCurrentDocumentSize() { + return body.offset() + body.size() * 20 + 0x48; + } + + /** Getter for property strictImageSequence. + * @return value of property strictImageSequence + * + */ + public boolean isStrictImageSequence() { + return pdf.isStrictImageSequence(); + } + + /** Sets the image sequence to follow the text in strict order. + * @param strictImageSequence new value of property strictImageSequence + * + */ + public void setStrictImageSequence(boolean strictImageSequence) { + pdf.setStrictImageSequence(strictImageSequence); + } + + /** + * If you use setPageEmpty(false), invoking newPage() after a blank page will add a newPage. + * @param pageEmpty the state + */ + public void setPageEmpty(boolean pageEmpty) { + pdf.setPageEmpty(pageEmpty); + } + + /** Gets the info dictionary for changing. + * @return the info dictionary + */ + public PdfDictionary getInfo() { + return ((PdfDocument)document).getInfo(); + } + + /** + * Sets extra keys to the catalog. + * @return the catalog to change + */ + public PdfDictionary getExtraCatalog() { + if (extraCatalog == null) + extraCatalog = new PdfDictionary(); + return this.extraCatalog; + } + + /** + * Sets the document in a suitable way to do page reordering. + */ + public void setLinearPageMode() { + root.setLinearMode(null); + } + + /** Getter for property group. + * @return Value of property group. + * + */ + public PdfDictionary getGroup() { + return this.group; + } + + /** Setter for property group. + * @param group New value of property group. + * + */ + public void setGroup(PdfDictionary group) { + this.group = group; + } + + /** + * Sets the PDFX conformance level. Allowed values are PDFX1A2001 and PDFX32002. It + * must be called before opening the document. + * @param pdfxConformance the conformance level + */ + public void setPDFXConformance(int pdfxConformance) { + if (this.pdfxConformance == pdfxConformance) + return; + if (pdf.isOpen()) + throw new PdfXConformanceException("PDFX conformance can only be set before opening the document."); + if (crypto != null) + throw new PdfXConformanceException("A PDFX conforming document cannot be encrypted."); + if (pdfxConformance != PDFXNONE) + setPdfVersion(VERSION_1_3); + this.pdfxConformance = pdfxConformance; + } + + /** + * Gets the PDFX conformance level. + * @return the PDFX conformance level + */ + public int getPDFXConformance() { + return pdfxConformance; + } + + static void checkPDFXConformance(PdfWriter writer, int key, Object obj1) { + if (writer == null || writer.pdfxConformance == PDFXNONE) + return; + int conf = writer.pdfxConformance; + switch (key) { + case PDFXKEY_COLOR: + switch (conf) { + case PDFX1A2001: + if (obj1 instanceof ExtendedColor) { + ExtendedColor ec = (ExtendedColor)obj1; + switch (ec.getType()) { + case ExtendedColor.TYPE_CMYK: + case ExtendedColor.TYPE_GRAY: + return; + case ExtendedColor.TYPE_RGB: + throw new PdfXConformanceException("Colorspace RGB is not allowed."); + case ExtendedColor.TYPE_SEPARATION: + SpotColor sc = (SpotColor)ec; + checkPDFXConformance(writer, PDFXKEY_COLOR, sc.getPdfSpotColor().getAlternativeCS()); + break; + case ExtendedColor.TYPE_SHADING: + ShadingColor xc = (ShadingColor)ec; + checkPDFXConformance(writer, PDFXKEY_COLOR, xc.getPdfShadingPattern().getShading().getColorSpace()); + break; + case ExtendedColor.TYPE_PATTERN: + PatternColor pc = (PatternColor)ec; + checkPDFXConformance(writer, PDFXKEY_COLOR, pc.getPainter().getDefaultColor()); + break; + } + } + else if (obj1 instanceof Color) + throw new PdfXConformanceException("Colorspace RGB is not allowed."); + break; + } + break; + case PDFXKEY_CMYK: + break; + case PDFXKEY_RGB: + if (conf == PDFX1A2001) + throw new PdfXConformanceException("Colorspace RGB is not allowed."); + break; + case PDFXKEY_FONT: + if (!((BaseFont)obj1).isEmbedded()) + throw new PdfXConformanceException("All the fonts must be embedded."); + break; + case PDFXKEY_IMAGE: + PdfImage image = (PdfImage)obj1; + if (image.get(PdfName.SMASK) != null) + throw new PdfXConformanceException("The /SMask key is not allowed in images."); + switch (conf) { + case PDFX1A2001: + PdfObject cs = image.get(PdfName.COLORSPACE); + if (cs == null) + return; + if (cs.isName()) { + if (PdfName.DEVICERGB.equals(cs)) + throw new PdfXConformanceException("Colorspace RGB is not allowed."); + } + else if (cs.isArray()) { + if (PdfName.CALRGB.equals(((PdfArray)cs).getArrayList().get(0))) + throw new PdfXConformanceException("Colorspace CalRGB is not allowed."); + } + break; + } + break; + case PDFXKEY_GSTATE: + PdfDictionary gs = (PdfDictionary)obj1; + PdfObject obj = gs.get(PdfName.BM); + if (obj != null && !PdfGState.BM_NORMAL.equals(obj) && !PdfGState.BM_COMPATIBLE.equals(obj)) + throw new PdfXConformanceException("Blend mode " + obj.toString() + " not allowed."); + obj = gs.get(PdfName.CA); + double v = 0.0; + if (obj != null && (v = ((PdfNumber)obj).doubleValue()) != 1.0) + throw new PdfXConformanceException("Transparency is not allowed: /CA = " + v); + obj = gs.get(PdfName.ca); + v = 0.0; + if (obj != null && (v = ((PdfNumber)obj).doubleValue()) != 1.0) + throw new PdfXConformanceException("Transparency is not allowed: /ca = " + v); + break; + case PDFXKEY_LAYER: + throw new PdfXConformanceException("Layers are not allowed."); + } + } + + /** + * 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 destOutputProfile a value + * @throws IOException on error + */ + public void setOutputIntents(String outputConditionIdentifier, String outputCondition, String registryName, String info, byte destOutputProfile[]) throws IOException { + 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(registryName, PdfObject.TEXT_UNICODE)); + if (destOutputProfile != null) { + PdfStream stream = new PdfStream(destOutputProfile); + stream.flateCompress(); + out.put(PdfName.DESTOUTPUTPROFILE, addToBody(stream).getIndirectReference()); + } + out.put(PdfName.S, PdfName.GTS_PDFX); + extraCatalog.put(PdfName.OUTPUTINTENTS, new PdfArray(out)); + } + + private static String getNameString(PdfDictionary dic, PdfName key) { + PdfObject obj = PdfReader.getPdfObject(dic.get(key)); + if (obj == null || !obj.isString()) + return null; + return ((PdfString)obj).toUnicodeString(); + } + + /** + * Copies the output intent dictionary from other document to this one. + * @param reader the other document + * @param checkExistence true to just check for the existence of a valid output intent + * dictionary, false to insert the dictionary if it exists + * @throws IOException on error + * @return true if the output intent dictionary exists, false + * otherwise + */ + public boolean setOutputIntents(PdfReader reader, boolean checkExistence) throws IOException { + PdfDictionary catalog = reader.getCatalog(); + PdfArray outs = (PdfArray)PdfReader.getPdfObject(catalog.get(PdfName.OUTPUTINTENTS)); + if (outs == null) + return false; + ArrayList arr = outs.getArrayList(); + if (arr.size() == 0) + return false; + PdfDictionary out = (PdfDictionary)PdfReader.getPdfObject((PdfObject)arr.get(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; + } + + /** + * Sets the page box sizes. Allowed names are: "crop", "trim", "art" and "bleed". + * @param boxName the box size + * @param size the size + */ + public void setBoxSize(String boxName, Rectangle size) { + pdf.setBoxSize(boxName, size); + } + + /** + * Gives 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(String boxName) { + return pdf.getBoxSize(boxName); + } + + /** + * Gives the size of the media box. + * @return a Rectangle + */ + public Rectangle getPageSize() { + return pdf.getPageSize(); + } + /** + * Gets the default colorspaces. + * @return the default colorspaces + */ + public PdfDictionary getDefaultColorspace() { + return defaultColorspace; + } + + /** + * 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. + *

+ * 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 PdfName.DEFAULTGRAY, PdfName.DEFAULTRGB + * or PdfName.DEFAULTCMYK + * @param cs the colorspace. A null or PdfNull removes any colorspace with the same name + */ + public void setDefaultColorspace(PdfName key, PdfObject cs) { + if (cs == null || cs.isNull()) + defaultColorspace.remove(key); + defaultColorspace.put(key, cs); + } + + /** + * Gets the 1.5 compression status. + * @return true if the 1.5 compression is on + */ + public boolean isFullCompression() { + return this.fullCompression; + } + + /** + * Sets the document's compression to the new 1.5 mode with object streams and xref + * streams. It can be set at any time but once set it can't be unset. + *

+ * If set before opening the document it will also set the pdf version to 1.5. + */ + public void setFullCompression() { + this.fullCompression = true; + setPdfVersion(VERSION_1_5); + } + + /** + * Gets the Optional Content Properties Dictionary. 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; + } + + /** + * Sets 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(ArrayList group) { + PdfArray ar = new PdfArray(); + for (int k = 0; k < group.size(); ++k) { + PdfLayer layer = (PdfLayer)group.get(k); + if (layer.getTitle() == null) + ar.add(layer.getRef()); + } + if (ar.size() == 0) + return; + OCGRadioGroup.add(ar); + } + + /** + * Sets the the thumbnail image for the current page. + * @param image the image + * @throws PdfException on error + * @throws DocumentException or error + */ + public void setThumbnail(Image image) throws PdfException, DocumentException { + pdf.setThumbnail(image); + } + + /** + * 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. + * Remark that this userunit only works starting with PDF1.6! + * @return Returns the userunit. + */ + public float getUserunit() { + return userunit; + } + /** + * 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. + * Remark that this userunit only works starting with PDF1.6! + * @param userunit The userunit to set. + * @throws DocumentException + */ + public void setUserunit(float userunit) throws DocumentException { + if (userunit < 1f || userunit > 75000f) throw new DocumentException("UserUnit should be a value between 1 and 75000."); + this.userunit = userunit; + setPdfVersion(VERSION_1_6); + } + + /** + * Sets XMP Metadata. + * @param xmpMetadata The xmpMetadata to set. + */ + public void setXmpMetadata(byte[] xmpMetadata) { + this.xmpMetadata = xmpMetadata; + } + + /** + * Creates XMP Metadata based on the metadata in the PdfDocument. + */ + public void createXmpMetadata() { + setXmpMetadata(pdf.createXmpMetadata()); + } + + /** + * Releases the memory used by a template by writing it 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(PdfTemplate tp) throws IOException { + PdfIndirectReference ref = tp.getIndirectReference(); + Object[] objs = (Object[])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(), template.getIndirectReference()); + objs[1] = null; + } + } + + /** + * Mark this document for tagging. It must be called before open. + */ + public void setTagged() { + if (open) + throw new IllegalArgumentException("Tagging must be set before opening the document."); + tagged = true; + } + + /** + * Check if the document is marked for tagging. + * @return true if the document is marked for tagging + */ + public boolean isTagged() { + return tagged; + } + + /** + * Gets the structure tree root. If the document is not marked for tagging it will return null. + * @return the structure tree root + */ + public PdfStructureTreeRoot getStructureTreeRoot() { + if (tagged && structureTreeRoot == null) + structureTreeRoot = new PdfStructureTreeRoot(this); + return structureTreeRoot; + } +} \ No newline at end of file -- cgit v1.2.3