/*
* $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.
* 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.
*
*
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;
}
}