diff options
-rw-r--r-- | src/java/org/apache/fop/pdf/PDFDocument.java | 1682 |
1 files changed, 313 insertions, 1369 deletions
diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index 7feb2141e..54f2b420b 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -50,30 +50,18 @@ */ package org.apache.fop.pdf; -import org.apache.fop.util.StreamUtilities; - -import org.apache.avalon.framework.logger.AbstractLogEnabled; -import org.apache.fop.fonts.CIDFont; -import org.apache.fop.fonts.CustomFont; -import org.apache.fop.fonts.Font; -import org.apache.fop.fonts.FontDescriptor; -import org.apache.fop.fonts.FontMetrics; -import org.apache.fop.fonts.FontType; -import org.apache.fop.fonts.LazyFont; -import org.apache.fop.fonts.MultiByteFont; -import org.apache.fop.fonts.truetype.FontFileReader; -import org.apache.fop.fonts.truetype.TTFSubSetFile; -import org.apache.fop.fonts.type1.PFBData; -import org.apache.fop.fonts.type1.PFBParser; - // Java import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.util.List; import java.util.Map; import java.util.Iterator; -import java.awt.geom.Rectangle2D; + +// Avalon +import org.apache.avalon.framework.logger.LogEnabled; +import org.apache.avalon.framework.logger.Logger; /* image support modified from work of BoBoGi */ /* font support based on work by Takayuki Takeuchi */ @@ -99,7 +87,7 @@ import java.awt.geom.Rectangle2D; * the object list; enhanced trailer output; cleanups. * */ -public class PDFDocument extends AbstractLogEnabled { +public class PDFDocument implements LogEnabled { private static final Integer LOCATION_PLACEHOLDER = new Integer(0); /** @@ -112,6 +100,8 @@ public class PDFDocument extends AbstractLogEnabled { */ public static final String ENCODING = "ISO-8859-1"; + private Logger logger; + /** * the current character position */ @@ -133,7 +123,7 @@ public class PDFDocument extends AbstractLogEnabled { /** * the objects themselves */ - protected List objects = new java.util.ArrayList(); + protected List objects = new java.util.LinkedList(); /** * character position of xref table @@ -243,6 +233,10 @@ public class PDFDocument extends AbstractLogEnabled { protected List gotos = new java.util.ArrayList(); + private PDFFactory factory; + + private boolean encodingOnTheFly = true; + /** * creates an empty PDF document <p> * @@ -257,17 +251,68 @@ public class PDFDocument extends AbstractLogEnabled { */ public PDFDocument(String prod) { + this.factory = new PDFFactory(this); + /* create the /Root, /Info and /Resources objects */ - this.pages = makePages(); + this.pages = getFactory().makePages(); // Create the Root object - this.root = makeRoot(pages); + this.root = getFactory().makeRoot(pages); // Create the Resources object - this.resources = makeResources(); + this.resources = getFactory().makeResources(); // Make the /Info record - this.info = makeInfo(prod); + this.info = getFactory().makeInfo(prod); + } + + /** + * Returns the factory for PDF objects. + * @return PDFFactory the factory + */ + public PDFFactory getFactory() { + return this.factory; + } + + /** + * Indicates whether stream encoding on-the-fly is enabled. If enabled + * stream can be serialized without the need for a buffer to merely + * calculate the stream length. + * @return boolean true if on-the-fly encoding is enabled + */ + public boolean isEncodingOnTheFly() { + return this.encodingOnTheFly; + } + + /** + * @see org.apache.avalon.framework.logger.LogEnabled#enableLogging(Logger) + */ + public void enableLogging(Logger logger) { + this.logger = logger; + } + + /** + * Helper method to allow sub-classes to aquire logger. + * + * <p>There is no performance penalty as this is a final method + * and will be inlined by the JVM.</p> + * @return the Logger + */ + protected final Logger getLogger() { + return this.logger; + } + + /** + * Converts text to a byte array for writing to a PDF file. + * @param text text to convert/encode + * @return byte[] the resulting byte array + */ + public static byte[] encode(String text) { + try { + return text.getBytes(ENCODING); + } catch (UnsupportedEncodingException uee) { + return text.getBytes(); + } } /** @@ -294,7 +339,7 @@ public class PDFDocument extends AbstractLogEnabled { * @param map the map of filter lists for each stream type */ public void setFilterMap(Map map) { - filterMap = map; + this.filterMap = map; } /** @@ -303,58 +348,15 @@ public class PDFDocument extends AbstractLogEnabled { * @return the map of filters being used */ public Map getFilterMap() { - return filterMap; + return this.filterMap; } /** - * Apply the encryption filter to a PDFStream if encryption is enabled. - * @param stream PDFStream to encrypt - */ - public void applyEncryption(PDFStream stream) { - if (isEncryptionActive()) { - this.encryption.applyFilter(stream); - } - } - - /** - * Enables PDF encryption. - * @param params The encryption parameters for the pdf file - */ - public void setEncryption(PDFEncryptionParams params) { - this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params); - if (encryption != null) { - /**@todo this cast is ugly. PDFObject should be transformed to an interface. */ - addTrailerObject((PDFObject)this.encryption); - } else { - getLogger().warn("PDF encryption is unavailable. PDF will be " - + "generated without encryption."); - } - } - - - /** - * Indicates whether encryption is active for this PDF or not. - * @return boolean True if encryption is active - */ - public boolean isEncryptionActive() { - return this.encryption != null; - } - - /** - * Make a /Catalog (Root) object. This object is written in - * the trailer. - * - * @param pages the pages pdf object that the root points to - * @return the new pdf root object for this document + * Returns the PDFPages object associated with the root object. + * @return the PDFPages object */ - public PDFRoot makeRoot(PDFPages pages) { - - /* - * Make a /Pages object. This object is written in the trailer. - */ - PDFRoot pdfRoot = new PDFRoot(++this.objectcount, pages); - addTrailerObject(pdfRoot); - return pdfRoot; + public PDFPages getPages() { + return this.pages; } /** @@ -367,183 +369,162 @@ public class PDFDocument extends AbstractLogEnabled { } /** - * Make a /Pages object. This object is written in the trailer. + * Get the pdf info object for this document. * - * @return a new PDF Pages object for adding pages to + * @return the PDF Info object for this document */ - public PDFPages makePages() { - PDFPages pdfPages = new PDFPages(++this.objectcount); - addTrailerObject(pdfPages); - return pdfPages; + public PDFInfo getInfo() { + return info; } /** - * Make a /Resources object. This object is written in the trailer. - * - * @return a new PDF resources object + * Registers a PDFObject in this PDF document. The PDF is assigned a new + * object number. + * @param obj PDFObject to add + * @return PDFObject the PDFObject added (its object number set) */ - public PDFResources makeResources() { - PDFResources pdfResources = new PDFResources(++this.objectcount); - addTrailerObject(pdfResources); - return pdfResources; + public PDFObject registerObject(PDFObject obj) { + assignObjectNumber(obj); + addObject(obj); + return obj; } /** - * make an /Info object - * - * @param prod string indicating application producing the PDF - * @return the created /Info object - */ - protected PDFInfo makeInfo(String prod) { - - /* - * create a PDFInfo with the next object number and add to - * list of objects - */ - PDFInfo pdfInfo = new PDFInfo(++this.objectcount); - // set the default producer - pdfInfo.setProducer(prod); - this.objects.add(pdfInfo); - return pdfInfo; + * Assigns the PDFObject a object number and sets the parent of the + * PDFObject to this PDFDocument. + * @param obj PDFObject to assign a number to + */ + public void assignObjectNumber(PDFObject obj) { + if (obj == null) { + throw new NullPointerException("obj must not be null"); + } + if (obj.hasObjectNumber()) { + throw new IllegalStateException("Error registering a PDFObject: " + + "PDFObject already has an object number"); + } + PDFDocument currentParent = obj.getDocument(); + if (currentParent != null && currentParent != this) { + throw new IllegalStateException("Error registering a PDFObject: " + + "PDFObject already has a parent PDFDocument"); + } + + obj.setObjectNumber(++this.objectcount); + + if (currentParent == null) { + obj.setDocument(this); + } } /** - * Get the pdf info object for this document. - * - * @return the PDF Info object for this document + * Adds an PDFObject to this document. The object must have a object number + * assigned. + * @param obj PDFObject to add */ - public PDFInfo getInfo() { - return info; + public void addObject(PDFObject obj) { + if (obj == null) { + throw new NullPointerException("obj must not be null"); + } + if (!obj.hasObjectNumber()) { + throw new IllegalStateException("Error adding a PDFObject: " + + "PDFObject doesn't have an object number"); + } + + //Add object to list + this.objects.add(obj); + + //System.out.println("Registering: "+obj); + + //Add object to special lists where necessary + if (obj instanceof PDFFunction) { + this.functions.add(obj); + } + if (obj instanceof PDFShading) { + final String shadingName = "Sh" + (++this.shadingCount); + ((PDFShading)obj).setName(shadingName); + this.shadings.add(obj); + } + if (obj instanceof PDFPattern) { + final String patternName = "Pa" + (++this.patternCount); + ((PDFPattern)obj).setName(patternName); + this.patterns.add(obj); + } + if (obj instanceof PDFFont) { + final PDFFont font = (PDFFont)obj; + this.fontMap.put(font.getName(), font); + } + if (obj instanceof PDFGState) { + this.gstates.add(obj); + } + if (obj instanceof PDFPage) { + this.pages.notifyKidRegistered((PDFPage)obj); + } + if (obj instanceof PDFLink) { + this.links.add(obj); + } + if (obj instanceof PDFFileSpec) { + this.filespecs.add(obj); + } + if (obj instanceof PDFGoToRemote) { + this.gotoremotes.add(obj); + } } + /** - * Make a Type 0 sampled function - * - * @param theDomain List objects of Double objects. - * This is the domain of the function. - * See page 264 of the PDF 1.3 Spec. - * @param theRange List objects of Double objects. - * This is the Range of the function. - * See page 264 of the PDF 1.3 Spec. - * @param theSize A List object of Integer objects. - * This is the number of samples in each input dimension. - * I can't imagine there being more or less than two input dimensions, - * so maybe this should be an array of length 2. - * - * See page 265 of the PDF 1.3 Spec. - * @param theBitsPerSample An int specifying the number of bits user - * to represent each sample value. - * Limited to 1,2,4,8,12,16,24 or 32. - * See page 265 of the 1.3 PDF Spec. - * @param theOrder The order of interpolation between samples. - * Default is 1 (one). Limited - * to 1 (one) or 3, which means linear or cubic-spline interpolation. - * - * This attribute is optional. - * - * See page 265 in the PDF 1.3 spec. - * @param theEncode List objects of Double objects. - * This is the linear mapping of input values intop the domain - * of the function's sample table. Default is hard to represent in - * ascii, but basically [0 (Size0 1) 0 (Size1 1)...]. - * This attribute is optional. - * - * See page 265 in the PDF 1.3 spec. - * @param theDecode List objects of Double objects. - * This is a linear mapping of sample values into the range. - * The default is just the range. - * - * This attribute is optional. - * Read about it on page 265 of the PDF 1.3 spec. - * @param theFunctionDataStream The sample values that specify - * the function are provided in a stream. - * - * This is optional, but is almost always used. - * - * Page 265 of the PDF 1.3 spec has more. - * @param theFilter This is a vector of String objects which - * are the various filters that have are to be - * applied to the stream to make sense of it. - * Order matters, so watch out. + * Add trailer object. + * Adds an object to the list of trailer objects. * - * This is not documented in the Function section of the PDF 1.3 spec, - * it was deduced from samples that this is sometimes used, even if we may never - * use it in FOP. It is added for completeness sake. - * @param theFunctionType This is the type of function (0,2,3, or 4). - * It should be 0 as this is the constructor for sampled functions. - * @return the PDF function that was created - */ - public PDFFunction makeFunction(int theFunctionType, List theDomain, - List theRange, List theSize, - int theBitsPerSample, int theOrder, - List theEncode, List theDecode, - StringBuffer theFunctionDataStream, - List theFilter) { - // Type 0 function - PDFFunction function = new PDFFunction(++this.objectcount, - theFunctionType, theDomain, - theRange, theSize, - theBitsPerSample, theOrder, - theEncode, theDecode, - theFunctionDataStream, - theFilter); - - PDFFunction oldfunc = findFunction(function); - if (oldfunc == null) { - functions.add(function); - this.objects.add(function); - } else { - this.objectcount--; - function = oldfunc; - } + * @param obj the PDF object to add + */ + public void addTrailerObject(PDFObject obj) { + this.trailerObjects.add(obj); + + if (obj instanceof PDFGoTo) { + this.gotos.add(obj); + } + } - return (function); + /** + * Apply the encryption filter to a PDFStream if encryption is enabled. + * @param stream PDFStream to encrypt + */ + public void applyEncryption(AbstractPDFStream stream) { + if (isEncryptionActive()) { + this.encryption.applyFilter(stream); + } } /** - * make a type Exponential interpolation function - * (for shading usually) - * - * @param theDomain List objects of Double objects. - * This is the domain of the function. - * See page 264 of the PDF 1.3 Spec. - * @param theRange List of Doubles that is the Range of the function. - * See page 264 of the PDF 1.3 Spec. - * @param theCZero This is a vector of Double objects which defines the function result - * when x=0. - * - * This attribute is optional. - * It's described on page 268 of the PDF 1.3 spec. - * @param theCOne This is a vector of Double objects which defines the function result - * when x=1. - * - * This attribute is optional. - * It's described on page 268 of the PDF 1.3 spec. - * @param theInterpolationExponentN This is the inerpolation exponent. - * - * This attribute is required. - * PDF Spec page 268 - * @param theFunctionType The type of the function, which should be 2. - * @return the PDF function that was created - */ - public PDFFunction makeFunction(int theFunctionType, List theDomain, - List theRange, List theCZero, - List theCOne, - double theInterpolationExponentN) { // type 2 - PDFFunction function = new PDFFunction(++this.objectcount, - theFunctionType, theDomain, - theRange, theCZero, theCOne, - theInterpolationExponentN); - PDFFunction oldfunc = findFunction(function); - if (oldfunc == null) { - functions.add(function); - this.objects.add(function); + * Enables PDF encryption. + * @param params The encryption parameters for the pdf file + */ + public void setEncryption(PDFEncryptionParams params) { + this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params); + ((PDFObject)this.encryption).setDocument(this); + if (encryption != null) { + /**@todo this cast is ugly. PDFObject should be transformed to an interface. */ + addTrailerObject((PDFObject)this.encryption); } else { - this.objectcount--; - function = oldfunc; + getLogger().warn("PDF encryption is unavailable. PDF will be " + + "generated without encryption."); } - - return (function); + } + + + /** + * Indicates whether encryption is active for this PDF or not. + * @return boolean True if encryption is active + */ + public boolean isEncryptionActive() { + return this.encryption != null; + } + + /** + * Returns the active Encryption object. + * @return the Encryption object + */ + public PDFEncryption getEncryption() { + return encryption; } private Object findPDFObject(List list, PDFObject compare) { @@ -556,11 +537,23 @@ public class PDFDocument extends AbstractLogEnabled { return null; } - private PDFFunction findFunction(PDFFunction compare) { + /** + * Looks through the registered functions to see if one that is equal to + * a reference object exists + * @param compare reference object + * @return the function if it was found, null otherwise + */ + protected PDFFunction findFunction(PDFFunction compare) { return (PDFFunction)findPDFObject(functions, compare); } - private PDFShading findShading(PDFShading compare) { + /** + * Looks through the registered shadings to see if one that is equal to + * a reference object exists + * @param compare reference object + * @return the shading if it was found, null otherwise + */ + protected PDFShading findShading(PDFShading compare) { return (PDFShading)findPDFObject(shadings, compare); } @@ -569,429 +562,86 @@ public class PDFDocument extends AbstractLogEnabled { * The problem with this is for tiling patterns the pattern * data stream is stored and may use up memory, usually this * would only be a small amount of data. + * @param compare reference object + * @return the shading if it was found, null otherwise */ - private PDFPattern findPattern(PDFPattern compare) { + protected PDFPattern findPattern(PDFPattern compare) { return (PDFPattern)findPDFObject(patterns, compare); } /** - * Make a Type 3 Stitching function - * - * @param theDomain List objects of Double objects. - * This is the domain of the function. - * See page 264 of the PDF 1.3 Spec. - * @param theRange List objects of Double objects. - * This is the Range of the function. - * See page 264 of the PDF 1.3 Spec. - * @param theFunctions An List of the PDFFunction objects - * that the stitching function stitches. - * - * This attributed is required. - * It is described on page 269 of the PDF spec. - * @param theBounds This is a vector of Doubles representing - * the numbers that, in conjunction with Domain - * define the intervals to which each function from - * the 'functions' object applies. It must be in - * order of increasing magnitude, and each must be - * within Domain. - * - * It basically sets how much of the gradient each function handles. - * - * This attributed is required. - * It's described on page 269 of the PDF 1.3 spec. - * @param theEncode List objects of Double objects. - * This is the linear mapping of input values intop the domain - * of the function's sample table. Default is hard to represent in - * ascii, but basically [0 (Size0 1) 0 (Size1 1)...]. - * This attribute is required. - * - * See page 270 in the PDF 1.3 spec. - * @param theFunctionType This is the function type. It should be 3, - * for a stitching function. - * @return the PDF function that was created - */ - public PDFFunction makeFunction(int theFunctionType, List theDomain, - List theRange, List theFunctions, - List theBounds, - List theEncode) { - // Type 3 - - PDFFunction function = new PDFFunction(++this.objectcount, - theFunctionType, theDomain, - theRange, theFunctions, - theBounds, theEncode); - - PDFFunction oldfunc = findFunction(function); - if (oldfunc == null) { - functions.add(function); - this.objects.add(function); - } else { - this.objectcount--; - function = oldfunc; - } - - return (function); - } - - /** - * make a postscript calculator function - * - * @param theNumber the PDF object number - * @param theFunctionType the type of function to make - * @param theDomain the domain values - * @param theRange the range values of the function - * @param theFunctionDataStream a string containing the pdf drawing - * @return the PDF function that was created - */ - public PDFFunction makeFunction(int theNumber, int theFunctionType, - List theDomain, List theRange, - StringBuffer theFunctionDataStream) { - // Type 4 - PDFFunction function = new PDFFunction(++this.objectcount, - theFunctionType, theDomain, - theRange, - theFunctionDataStream); - - PDFFunction oldfunc = findFunction(function); - if (oldfunc == null) { - functions.add(function); - this.objects.add(function); - } else { - this.objectcount--; - function = oldfunc; - } - - return (function); - + * Finds a font. + * @param fontname name of the font + * @return PDFFont the requested font, null if it wasn't found + */ + protected PDFFont findFont(String fontname) { + return (PDFFont)fontMap.get(fontname); } /** - * make a function based shading object - * - * @param res the PDF resource context to add the shading, may be null - * @param theShadingType The type of shading object, which should be 1 for function - * based shading. - * @param theColorSpace The colorspace is 'DeviceRGB' or something similar. - * @param theBackground An array of color components appropriate to the - * colorspace key specifying a single color value. - * This key is used by the f operator buy ignored by the sh operator. - * @param theBBox List of double's representing a rectangle - * in the coordinate space that is current at the - * time of shading is imaged. Temporary clipping - * boundary. - * @param theAntiAlias Whether or not to anti-alias. - * @param theDomain Optional vector of Doubles specifying the domain. - * @param theMatrix List of Doubles specifying the matrix. - * If it's a pattern, then the matrix maps it to pattern space. - * If it's a shading, then it maps it to current user space. - * It's optional, the default is the identity matrix - * @param theFunction The PDF Function that maps an (x,y) location to a color - * @return the PDF shading that was created - */ - public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, - List theBackground, List theBBox, - boolean theAntiAlias, List theDomain, - List theMatrix, - PDFFunction theFunction) { - // make Shading of Type 1 - String theShadingName = new String("Sh" + (++this.shadingCount)); - - PDFShading shading = new PDFShading(++this.objectcount, - theShadingName, theShadingType, - theColorSpace, theBackground, - theBBox, theAntiAlias, theDomain, - theMatrix, theFunction); - - PDFShading oldshad = findShading(shading); - if (oldshad == null) { - shadings.add(shading); - this.objects.add(shading); - } else { - this.objectcount--; - this.shadingCount--; - shading = oldshad; - } - - // add this shading to resources - if (res != null) { - res.getPDFResources().addShading(shading); - } else { - this.resources.addShading(shading); - } - - return (shading); + * Finds a link. + * @param compare reference object to use as search template + * @return the link if found, null otherwise + */ + protected PDFLink findLink(PDFLink compare) { + return (PDFLink)findPDFObject(links, compare); } /** - * Make an axial or radial shading object. - * - * @param res the PDF resource context to add the shading, may be null - * @param theShadingType 2 or 3 for axial or radial shading - * @param theColorSpace "DeviceRGB" or similar. - * @param theBackground theBackground An array of color components appropriate to the - * colorspace key specifying a single color value. - * This key is used by the f operator buy ignored by the sh operator. - * @param theBBox List of double's representing a rectangle - * in the coordinate space that is current at the - * time of shading is imaged. Temporary clipping - * boundary. - * @param theAntiAlias Default is false - * @param theCoords List of four (type 2) or 6 (type 3) Double - * @param theDomain List of Doubles specifying the domain - * @param theFunction the Stitching (PDFfunction type 3) function, - * even if it's stitching a single function - * @param theExtend List of Booleans of whether to extend the - * start and end colors past the start and end points - * The default is [false, false] - * @return the PDF shading that was created - */ - public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, - List theBackground, List theBBox, - boolean theAntiAlias, List theCoords, - List theDomain, PDFFunction theFunction, - List theExtend) { - // make Shading of Type 2 or 3 - String theShadingName = new String("Sh" + (++this.shadingCount)); - - PDFShading shading = new PDFShading(++this.objectcount, - theShadingName, theShadingType, - theColorSpace, theBackground, - theBBox, theAntiAlias, theCoords, - theDomain, theFunction, - theExtend); - - PDFShading oldshad = findShading(shading); - if (oldshad == null) { - shadings.add(shading); - this.objects.add(shading); - } else { - this.objectcount--; - this.shadingCount--; - shading = oldshad; - } - - if (res != null) { - res.getPDFResources().addShading(shading); - } else { - this.resources.addShading(shading); - } - - return (shading); + * Finds a file spec. + * @param compare reference object to use as search template + * @return the file spec if found, null otherwise + */ + protected PDFFileSpec findFileSpec(PDFFileSpec compare) { + return (PDFFileSpec)findPDFObject(filespecs, compare); } /** - * Make a free-form gouraud shaded triangle mesh, coons patch mesh, or tensor patch mesh - * shading object - * - * @param res the PDF resource context to add the shading, may be null - * @param theShadingType 4, 6, or 7 depending on whether it's - * Free-form gouraud-shaded triangle meshes, coons patch meshes, - * or tensor product patch meshes, respectively. - * @param theColorSpace "DeviceRGB" or similar. - * @param theBackground theBackground An array of color components appropriate to the - * colorspace key specifying a single color value. - * This key is used by the f operator buy ignored by the sh operator. - * @param theBBox List of double's representing a rectangle - * in the coordinate space that is current at the - * time of shading is imaged. Temporary clipping - * boundary. - * @param theAntiAlias Default is false - * @param theBitsPerCoordinate 1,2,4,8,12,16,24 or 32. - * @param theBitsPerComponent 1,2,4,8,12, and 16 - * @param theBitsPerFlag 2,4,8. - * @param theDecode List of Doubles see PDF 1.3 spec pages 303 to 312. - * @param theFunction the PDFFunction - * @return the PDF shading that was created - */ - public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, - List theBackground, List theBBox, - boolean theAntiAlias, - int theBitsPerCoordinate, - int theBitsPerComponent, - int theBitsPerFlag, List theDecode, - PDFFunction theFunction) { - // make Shading of type 4,6 or 7 - String theShadingName = new String("Sh" + (++this.shadingCount)); - - PDFShading shading = new PDFShading(++this.objectcount, - theShadingName, theShadingType, - theColorSpace, theBackground, - theBBox, theAntiAlias, - theBitsPerCoordinate, - theBitsPerComponent, - theBitsPerFlag, theDecode, - theFunction); - - PDFShading oldshad = findShading(shading); - if (oldshad == null) { - shadings.add(shading); - this.objects.add(shading); - } else { - this.objectcount--; - this.shadingCount--; - shading = oldshad; - } - - if (res != null) { - res.getPDFResources().addShading(shading); - } else { - this.resources.addShading(shading); - } - - return (shading); + * Finds a goto remote. + * @param compare reference object to use as search template + * @return the goto remote if found, null otherwise + */ + protected PDFGoToRemote findGoToRemote(PDFGoToRemote compare) { + return (PDFGoToRemote)findPDFObject(gotoremotes, compare); } /** - * make a Lattice-Form Gouraud mesh shading object - * - * @param res the PDF resource context to add the shading, may be null - * @param theShadingType 5 for lattice-Form Gouraud shaded-triangle mesh - * without spaces. "Shading1" or "Sh1" are good examples. - * @param theColorSpace "DeviceRGB" or similar. - * @param theBackground theBackground An array of color components appropriate to the - * colorspace key specifying a single color value. - * This key is used by the f operator buy ignored by the sh operator. - * @param theBBox List of double's representing a rectangle - * in the coordinate space that is current at the - * time of shading is imaged. Temporary clipping - * boundary. - * @param theAntiAlias Default is false - * @param theBitsPerCoordinate 1,2,4,8,12,16, 24, or 32 - * @param theBitsPerComponent 1,2,4,8,12,24,32 - * @param theDecode List of Doubles. See page 305 in PDF 1.3 spec. - * @param theVerticesPerRow number of vertices in each "row" of the lattice. - * @param theFunction The PDFFunction that's mapped on to this shape - * @return the PDF shading that was created - */ - public PDFShading makeShading(PDFResourceContext res, int theShadingType, - PDFColorSpace theColorSpace, - List theBackground, List theBBox, - boolean theAntiAlias, - int theBitsPerCoordinate, - int theBitsPerComponent, List theDecode, - int theVerticesPerRow, - PDFFunction theFunction) { - // make shading of Type 5 - String theShadingName = new String("Sh" + (++this.shadingCount)); - - PDFShading shading = new PDFShading(++this.objectcount, - theShadingName, theShadingType, - theColorSpace, theBackground, - theBBox, theAntiAlias, - theBitsPerCoordinate, - theBitsPerComponent, theDecode, - theVerticesPerRow, theFunction); - - PDFShading oldshad = findShading(shading); - if (oldshad == null) { - shadings.add(shading); - this.objects.add(shading); - } else { - this.objectcount--; - this.shadingCount--; - shading = oldshad; - } - - if (res != null) { - res.getPDFResources().addShading(shading); - } else { - this.resources.addShading(shading); - } - - return (shading); + * Finds a goto. + * @param compare reference object to use as search template + * @return the goto if found, null otherwise + */ + protected PDFGoTo findGoTo(PDFGoTo compare) { + return (PDFGoTo)findPDFObject(gotos, compare); } - + /** - * Make a tiling pattern - * - * @param res the PDF resource context to add the shading, may be null - * @param thePatternType the type of pattern, which is 1 for tiling. - * @param theResources the resources associated with this pattern - * @param thePaintType 1 or 2, colored or uncolored. - * @param theTilingType 1, 2, or 3, constant spacing, no distortion, or faster tiling - * @param theBBox List of Doubles: The pattern cell bounding box - * @param theXStep horizontal spacing - * @param theYStep vertical spacing - * @param theMatrix Optional List of Doubles transformation matrix - * @param theXUID Optional vector of Integers that uniquely identify the pattern - * @param thePatternDataStream The stream of pattern data to be tiled. - * @return the PDF pattern that was created - */ - public PDFPattern makePattern(PDFResourceContext res, int thePatternType, // 1 - PDFResources theResources, int thePaintType, int theTilingType, - List theBBox, double theXStep, - double theYStep, List theMatrix, - List theXUID, StringBuffer thePatternDataStream) { - String thePatternName = new String("Pa" + (++this.patternCount)); - // int theNumber, String thePatternName, - // PDFResources theResources - PDFPattern pattern = new PDFPattern(++this.objectcount, - thePatternName, theResources, 1, - thePaintType, theTilingType, - theBBox, theXStep, theYStep, - theMatrix, theXUID, - thePatternDataStream); - - PDFPattern oldpatt = findPattern(pattern); - if (oldpatt == null) { - patterns.add(pattern); - this.objects.add(pattern); - } else { - this.objectcount--; - this.patternCount--; - pattern = oldpatt; - } - - if (res != null) { - res.getPDFResources().addPattern(pattern); - } else { - this.resources.addPattern(pattern); + * Looks for an existing GState to use + * @param wanted requested features + * @param current currently active features + * @return PDFGState the GState if found, null otherwise + */ + protected PDFGState findGState(PDFGState wanted, PDFGState current) { + PDFGState poss; + Iterator iter = gstates.iterator(); + while (iter.hasNext()) { + PDFGState avail = (PDFGState)iter.next(); + poss = new PDFGState(); + poss.addValues(current); + poss.addValues(avail); + if (poss.equals(wanted)) { + return avail; + } } - - return (pattern); + return null; } /** - * Make a smooth shading pattern + * Get the PDF color space object. * - * @param res the PDF resource context to add the shading, may be null - * @param thePatternType the type of the pattern, which is 2, smooth shading - * @param theShading the PDF Shading object that comprises this pattern - * @param theXUID optional:the extended unique Identifier if used. - * @param theExtGState optional: the extended graphics state, if used. - * @param theMatrix Optional:List of Doubles that specify the matrix. - * @return the PDF pattern that was created - */ - public PDFPattern makePattern(PDFResourceContext res, - int thePatternType, PDFShading theShading, - List theXUID, StringBuffer theExtGState, - List theMatrix) { - String thePatternName = new String("Pa" + (++this.patternCount)); - - PDFPattern pattern = new PDFPattern(++this.objectcount, - thePatternName, 2, theShading, - theXUID, theExtGState, theMatrix); - - PDFPattern oldpatt = findPattern(pattern); - if (oldpatt == null) { - patterns.add(pattern); - this.objects.add(pattern); - } else { - this.objectcount--; - this.patternCount--; - pattern = oldpatt; - } - - if (res != null) { - res.getPDFResources().addPattern(pattern); - } else { - this.resources.addPattern(pattern); - } - - return (pattern); + * @return the color space + */ + public PDFColorSpace getPDFColorSpace() { + return this.colorspace; } /** @@ -1000,7 +650,7 @@ public class PDFDocument extends AbstractLogEnabled { * @return the color space */ public int getColorSpace() { - return (this.colorspace.getColorSpace()); + return getPDFColorSpace().getColorSpace(); } /** @@ -1015,132 +665,6 @@ public class PDFDocument extends AbstractLogEnabled { } /** - * Make a gradient - * - * @param res the PDF resource context to add the shading, may be null - * @param radial if true a radial gradient will be created - * @param theColorspace the colorspace of the gradient - * @param theColors the list of colors for the gradient - * @param theBounds the list of bounds associated with the colors - * @param theCoords the coordinates for the gradient - * @return the PDF pattern that was created - */ - public PDFPattern createGradient(PDFResourceContext res, boolean radial, - PDFColorSpace theColorspace, - List theColors, List theBounds, - List theCoords) { - PDFShading myShad; - PDFFunction myfunky; - PDFFunction myfunc; - List theCzero; - List theCone; - PDFPattern myPattern; - //PDFColorSpace theColorSpace; - double interpolation = (double)1.000; - List theFunctions = new java.util.ArrayList(); - - int currentPosition; - int lastPosition = theColors.size() - 1; - - - // if 5 elements, the penultimate element is 3. - // do not go beyond that, because you always need - // to have a next color when creating the function. - - for (currentPosition = 0; currentPosition < lastPosition; - currentPosition++) { // for every consecutive color pair - PDFColor currentColor = - (PDFColor)theColors.get(currentPosition); - PDFColor nextColor = (PDFColor)theColors.get(currentPosition - + 1); - // colorspace must be consistant - if (this.colorspace.getColorSpace() - != currentColor.getColorSpace()) { - currentColor.setColorSpace(this.colorspace.getColorSpace()); - } - - if (this.colorspace.getColorSpace() != nextColor.getColorSpace()) { - nextColor.setColorSpace(this.colorspace.getColorSpace()); - } - - theCzero = currentColor.getVector(); - theCone = nextColor.getVector(); - - myfunc = this.makeFunction(2, null, null, theCzero, theCone, - interpolation); - - theFunctions.add(myfunc); - - } // end of for every consecutive color pair - - myfunky = this.makeFunction(3, null, null, theFunctions, theBounds, - null); - - if (radial) { - if (theCoords.size() == 6) { - myShad = this.makeShading(res, 3, this.colorspace, null, null, - false, theCoords, null, myfunky, - null); - } else { // if the center x, center y, and radius specifiy - // the gradient, then assume the same center x, center y, - // and radius of zero for the other necessary component - List newCoords = new java.util.ArrayList(); - newCoords.add(theCoords.get(0)); - newCoords.add(theCoords.get(1)); - newCoords.add(theCoords.get(2)); - newCoords.add(theCoords.get(0)); - newCoords.add(theCoords.get(1)); - newCoords.add(new Double(0.0)); - - myShad = this.makeShading(res, 3, this.colorspace, null, null, - false, newCoords, null, myfunky, - null); - - } - } else { - myShad = this.makeShading(res, 2, this.colorspace, null, null, false, - theCoords, null, myfunky, null); - - } - - myPattern = this.makePattern(res, 2, myShad, null, null, null); - - return (myPattern); - } - - - /** - * make a /Encoding object - * - * @param encodingName character encoding scheme name - * @return the created /Encoding object - */ - public PDFEncoding makeEncoding(String encodingName) { - - /* - * create a PDFEncoding with the next object number and add to the - * list of objects - */ - PDFEncoding encoding = new PDFEncoding(++this.objectcount, - encodingName); - this.objects.add(encoding); - return encoding; - } - - /** - * Create a PDFICCStream - * @see PDFXObject - * @see org.apache.fop.image.JpegImage - * @see org.apache.fop.pdf.PDFColorSpace - * @return the new PDF ICC stream object - */ - public PDFICCStream makePDFICCStream() { - PDFICCStream iccStream = new PDFICCStream(++this.objectcount); - this.objects.add(iccStream); - return iccStream; - } - - /** * Get the font map for this document. * * @return the map of fonts used in this document @@ -1150,149 +674,6 @@ public class PDFDocument extends AbstractLogEnabled { } /** - * make a Type1 /Font object - * - * @param fontname internal name to use for this font (eg "F1") - * @param basefont name of the base font (eg "Helvetica") - * @param encoding character encoding scheme used by the font - * @param metrics additional information about the font - * @param descriptor additional information about the font - * @return the created /Font object - */ - public PDFFont makeFont(String fontname, String basefont, - String encoding, FontMetrics metrics, - FontDescriptor descriptor) { - if (fontMap.containsKey(fontname)) { - return (PDFFont)fontMap.get(fontname); - } - - /* - * create a PDFFont with the next object number and add to the - * list of objects - */ - if (descriptor == null) { - PDFFont font = new PDFFont(++this.objectcount, fontname, - FontType.TYPE1, basefont, encoding); - this.objects.add(font); - fontMap.put(fontname, font); - return font; - } else { - FontType fonttype = metrics.getFontType(); - - PDFFontDescriptor pdfdesc = makeFontDescriptor(descriptor); - - PDFFontNonBase14 font = null; - if (fonttype == FontType.TYPE0) { - /* - * Temporary commented out - customized CMaps - * isn't needed until /ToUnicode support is added - * PDFCMap cmap = new PDFCMap(++this.objectcount, - * "fop-ucs-H", - * new PDFCIDSystemInfo("Adobe", - * "Identity", - * 0)); - * cmap.addContents(); - * this.objects.add(cmap); - */ - font = - (PDFFontNonBase14)PDFFont.createFont(++this.objectcount, - fontname, fonttype, - basefont, - "Identity-H"); - } else { - - font = - (PDFFontNonBase14)PDFFont.createFont(++this.objectcount, - fontname, fonttype, - basefont, encoding); - } - this.objects.add(font); - - font.setDescriptor(pdfdesc); - - if (fonttype == FontType.TYPE0) { - CIDFont cidMetrics; - if (metrics instanceof LazyFont) { - cidMetrics = (CIDFont)((LazyFont) metrics).getRealFont(); - } else { - cidMetrics = (CIDFont)metrics; - } - PDFCIDSystemInfo sysInfo = - new PDFCIDSystemInfo(cidMetrics.getRegistry(), - cidMetrics.getOrdering(), - cidMetrics.getSupplement()); - PDFCIDFont cidFont = - new PDFCIDFont(++this.objectcount, basefont, - cidMetrics.getCIDType(), - cidMetrics.getDefaultWidth(), - cidMetrics.getWidths(), sysInfo, - (PDFCIDFontDescriptor)pdfdesc); - this.objects.add(cidFont); - - ((PDFFontType0)font).setDescendantFonts(cidFont); - } else { - int firstChar = 0; - int lastChar = 255; - if (metrics instanceof CustomFont) { - CustomFont cf = (CustomFont)metrics; - firstChar = cf.getFirstChar(); - lastChar = cf.getLastChar(); - } - font.setWidthMetrics(firstChar, - lastChar, - makeArray(metrics.getWidths())); - } - - fontMap.put(fontname, font); - - return font; - } - } - - - /** - * make a /FontDescriptor object - * - * @param desc the font descriptor - * @return the new PDF font descriptor - */ - public PDFFontDescriptor makeFontDescriptor(FontDescriptor desc) { - PDFFontDescriptor font = null; - - if (desc.getFontType() == FontType.TYPE0) { - // CID Font - font = new PDFCIDFontDescriptor(++this.objectcount, - desc.getFontName(), - desc.getFontBBox(), - desc.getCapHeight(), desc.getFlags(), - desc.getItalicAngle(), - desc.getStemV(), null); - } else { - // Create normal FontDescriptor - font = new PDFFontDescriptor(++this.objectcount, desc.getFontName(), - desc.getAscender(), - desc.getDescender(), - desc.getCapHeight(), - desc.getFlags(), - new PDFRectangle(desc.getFontBBox()), - desc.getStemV(), - desc.getItalicAngle()); - } - this.objects.add(font); - - // Check if the font is embeddable - if (desc.isEmbeddable()) { - PDFStream stream = makeFontFile(this.objectcount + 1, desc); - if (stream != null) { - this.objectcount++; - font.setFontFile(desc.getFontType(), stream); - this.objects.add(stream); - } - } - return font; - } - - /** * Resolve a URI. * * @param uri the uri to resolve @@ -1310,167 +691,6 @@ public class PDFDocument extends AbstractLogEnabled { } } - /** - * Embeds a font. - * @param obj PDF object number to use - * @param desc FontDescriptor of the font. - * @return PDFStream The embedded font file - */ - public PDFStream makeFontFile(int obj, FontDescriptor desc) { - if (desc.getFontType() == FontType.OTHER) { - throw new IllegalArgumentException("Trying to embed unsupported font type: " - + desc.getFontType()); - } - - Font tempFont; - if (desc instanceof LazyFont) { - tempFont = ((LazyFont)desc).getRealFont(); - } else { - tempFont = (Font)desc; - } - if (!(tempFont instanceof CustomFont)) { - throw new IllegalArgumentException( - "FontDescriptor must be instance of CustomFont, but is a " - + desc.getClass().getName()); - } - CustomFont font = (CustomFont)tempFont; - - InputStream in = null; - try { - // Get file first - if (font.getEmbedFileName() != null) { - try { - in = resolveURI(font.getEmbedFileName()); - } catch (Exception e) { - getLogger().error("Failed to embed fontfile: " - + font.getEmbedFileName() - + "(" + e.getMessage() + ")"); - } - } - - // Get resource - if (in == null && font.getEmbedResourceName() != null) { - try { - in = new java.io.BufferedInputStream( - this.getClass().getResourceAsStream(font.getEmbedResourceName())); - } catch (Exception e) { - getLogger().error("Failed to embed fontresource: " - + font.getEmbedResourceName() - + "(" + e.getMessage() + ")"); - } - } - - if (in == null) { - return null; - } else { - try { - PDFStream embeddedFont; - if (desc.getFontType() == FontType.TYPE0) { - MultiByteFont mbfont = (MultiByteFont)font; - FontFileReader reader = new FontFileReader(in); - - TTFSubSetFile subset = new TTFSubSetFile(); - setupLogger(subset); - - byte[] subsetFont = subset.readFont(reader, - mbfont.getTTCName(), mbfont.getUsedGlyphs()); - // Only TrueType CID fonts are supported now - - embeddedFont = new PDFTTFStream(obj, subsetFont.length); - ((PDFTTFStream)embeddedFont).setData(subsetFont, subsetFont.length); - } else if (desc.getFontType() == FontType.TYPE1) { - PFBParser parser = new PFBParser(); - PFBData pfb = parser.parsePFB(in); - embeddedFont = new PDFT1Stream(obj); - ((PDFT1Stream)embeddedFont).setData(pfb); - } else { - byte[] file = StreamUtilities.toByteArray(in, 128000); - embeddedFont = new PDFTTFStream(obj, file.length); - ((PDFTTFStream)embeddedFont).setData(file, file.length); - } - embeddedFont.addFilter("flate"); - if (isEncryptionActive()) { - this.encryption.applyFilter(embeddedFont); - } else { - embeddedFont.addFilter("ascii-85"); - } - - return embeddedFont; - } finally { - in.close(); - } - } - } catch (IOException ioe) { - //log.error("Failed to embed font [" + obj + "] " - // + fontName + ": " + ioe.getMessage()); - return (PDFStream) null; - } - } - -/* - public PDFStream getFontFile(int i) { - PDFStream embeddedFont = null; - - - return (PDFStream)embeddedFont; - } - - - public PDFStream getFontFile(int i) { - } - -*/ - - - /** - * make an Array object (ex. Widths array for a font) - * - * @param values the int array values - * @return the PDF Array with the int values - */ - public PDFArray makeArray(int[] values) { - PDFArray array = new PDFArray(++this.objectcount, values); - this.objects.add(array); - return array; - } - - /** - * make an ExtGState for extra graphics options - * This tries to find a GState that will setup the correct values - * for the current context. If there is no suitable GState it will - * create a new one. - * - * @param settings the settings required by the caller - * @param current the current GState of the current PDF context - * @return a PDF GState, either an existing GState or a new one - */ - public PDFGState makeGState(Map settings, PDFGState current) { - - // try to locate a gstate that has all the settings - // or will inherit from the current gstate - // compare "DEFAULT + settings" with "current + each gstate" - - PDFGState wanted = new PDFGState(0); - wanted.addValues(PDFGState.DEFAULT); - wanted.addValues(settings); - - PDFGState poss; - for (Iterator iter = gstates.iterator(); iter.hasNext();) { - PDFGState avail = (PDFGState)iter.next(); - poss = new PDFGState(0); - poss.addValues(current); - poss.addValues(avail); - if (poss.equals(wanted)) { - return avail; - } - } - - PDFGState gstate = new PDFGState(++this.objectcount); - gstate.addValues(settings); - this.objects.add(gstate); - gstates.add(gstate); - return gstate; - } /** * Get an image from the image map. @@ -1507,9 +727,9 @@ public class PDFDocument extends AbstractLogEnabled { // setup image img.setup(this); // create a new XObject - xObject = new PDFXObject(++this.objectcount, ++this.xObjectCount, + xObject = new PDFXObject(++this.xObjectCount, img); - this.objects.add(xObject); + registerObject(xObject); this.resources.addXObject(xObject); if (res != null) { res.getPDFResources().addXObject(xObject); @@ -1533,9 +753,9 @@ public class PDFDocument extends AbstractLogEnabled { public PDFFormXObject addFormXObject(PDFResourceContext res, PDFStream cont, PDFResources formres, String key) { PDFFormXObject xObject; - xObject = new PDFFormXObject(++this.objectcount, ++this.xObjectCount, + xObject = new PDFFormXObject(++this.xObjectCount, cont, formres.referencePDF()); - this.objects.add(xObject); + registerObject(xObject); this.resources.addXObject(xObject); if (res != null) { res.getPDFResources().addXObject(xObject); @@ -1544,270 +764,6 @@ public class PDFDocument extends AbstractLogEnabled { } /** - * make a /Page object - * - * @param resources resources object to use - * @param pagewidth width of the page in points - * @param pageheight height of the page in points - * - * @return the created /Page object - */ - public PDFPage makePage(PDFResources resources, - int pagewidth, int pageheight) { - - /* - * create a PDFPage with the next object number, the given - * resources, contents and dimensions - */ - PDFPage page = new PDFPage(this, ++this.objectcount, resources, - pagewidth, pageheight); - - /* add it to the list of objects */ - pages.addPage(page); - return page; - } - - /** - * Add a completed page to the PDF document. - * The page is added to the object list. - * - * @param page the page to add - */ - public void addPage(PDFPage page) { - /* add it to the list of objects */ - this.objects.add(page); - } - - private PDFLink findLink(PDFLink compare) { - return (PDFLink)findPDFObject(links, compare); - } - - private PDFFileSpec findFileSpec(PDFFileSpec compare) { - return (PDFFileSpec)findPDFObject(filespecs, compare); - } - - private PDFGoToRemote findGoToRemote(PDFGoToRemote compare) { - return (PDFGoToRemote)findPDFObject(gotoremotes, compare); - } - - private PDFGoTo findGoTo(PDFGoTo compare) { - return (PDFGoTo)findPDFObject(gotos, compare); - } - - /** - * make a link object - * - * @param rect the clickable rectangle - * @param destination the destination file - * @param linkType the link type - * @param yoffset the yoffset on the page for an internal link - * @return the PDFLink object created - */ - public PDFLink makeLink(Rectangle2D rect, String destination, - int linkType, float yoffset) { - - //PDFLink linkObject; - int index; - - PDFLink link = new PDFLink(++this.objectcount, rect); - - if (linkType == PDFLink.EXTERNAL) { - // check destination - if (destination.startsWith("http://")) { - PDFUri uri = new PDFUri(destination); - link.setAction(uri); - } else if (destination.endsWith(".pdf")) { // FileSpec - PDFGoToRemote remote = getGoToPDFAction(destination, null, -1); - link.setAction(remote); - } else if ((index = destination.indexOf(".pdf#page=")) > 0) { - //String file = destination.substring(0, index + 4); - int page = Integer.parseInt(destination.substring(index + 10)); - PDFGoToRemote remote = getGoToPDFAction(destination, null, page); - link.setAction(remote); - } else if ((index = destination.indexOf(".pdf#dest=")) > 0) { - //String file = destination.substring(0, index + 4); - String dest = destination.substring(index + 10); - PDFGoToRemote remote = getGoToPDFAction(destination, dest, -1); - link.setAction(remote); - } else { // URI - PDFUri uri = new PDFUri(destination); - link.setAction(uri); - } - } else { - // linkType is internal - String goToReference = getGoToReference(destination, yoffset); - PDFInternalLink internalLink = new PDFInternalLink(goToReference); - link.setAction(internalLink); - } - - PDFLink oldlink = findLink(link); - if (oldlink == null) { - links.add(link); - this.objects.add(link); - } else { - this.objectcount--; - link = oldlink; - } - - return link; - } - - /** - * Create and return a goto pdf document action. - * This creates a pdf files spec and pdf goto remote action. - * It also checks available pdf objects so it will not create an - * object if it already exists. - * - * @param file the pdf file name - * @param dest the remote name destination, may be null - * @param page the remote page number, -1 means not specified - * @return the pdf goto remote object - */ - private PDFGoToRemote getGoToPDFAction(String file, String dest, int page) { - PDFFileSpec fileSpec = new PDFFileSpec(++this.objectcount, file); - PDFFileSpec oldspec = findFileSpec(fileSpec); - if (oldspec == null) { - filespecs.add(fileSpec); - this.objects.add(fileSpec); - } else { - this.objectcount--; - fileSpec = oldspec; - } - PDFGoToRemote remote; - - if (dest == null && page == -1) { - remote = new PDFGoToRemote(++this.objectcount, fileSpec); - } else if (dest != null) { - remote = new PDFGoToRemote(++this.objectcount, fileSpec, dest); - } else { - remote = new PDFGoToRemote(++this.objectcount, fileSpec, page); - } - PDFGoToRemote oldremote = findGoToRemote(remote); - if (oldremote == null) { - gotoremotes.add(remote); - this.objects.add(remote); - } else { - this.objectcount--; - remote = oldremote; - } - return remote; - } - - private String getGoToReference(String destination, float yoffset) { - String goToReference = null; - PDFGoTo gt = new PDFGoTo(++this.objectcount, destination); - gt.setYPosition(yoffset); - PDFGoTo oldgt = findGoTo(gt); - if (oldgt == null) { - gotos.add(gt); - addTrailerObject(gt); - } else { - this.objectcount--; - gt = oldgt; - } - - goToReference = gt.referencePDF(); - return goToReference; - } - - /** - * Add trailer object. - * Adds an object to the list of trailer objects. - * - * @param object the PDF object to add - */ - public void addTrailerObject(PDFObject object) { - this.trailerObjects.add(object); - } - - /** - * Make an internal link. - * - * @param rect the hotspot position in absolute coordinates - * @param page the target page reference value - * @param dest the position destination - * @return the new PDF link object - */ - public PDFLink makeLink(Rectangle2D rect, String page, String dest) { - PDFLink link = new PDFLink(++this.objectcount, rect); - this.objects.add(link); - - PDFGoTo gt = new PDFGoTo(++this.objectcount, page); - gt.setDestination(dest); - addTrailerObject(gt); - PDFInternalLink internalLink = new PDFInternalLink(gt.referencePDF()); - link.setAction(internalLink); - - return link; - } - - /** - * Ensure there is room in the locations xref for the number of - * objects that have been created. - */ - private void prepareLocations() { - while (location.size() < objectcount) { - location.add(LOCATION_PLACEHOLDER); - } - } - - /** - * Make a stream object - * - * @param type the type of stream to be created - * @param add if true then the stream will be added immediately - * @return the stream object created - */ - public PDFStream makeStream(String type, boolean add) { - - // create a PDFStream with the next object number - // and add it to the list of objects - PDFStream obj = new PDFStream(++this.objectcount); - obj.addDefaultFilters(filterMap, type); - if (isEncryptionActive()) { - this.encryption.applyFilter(obj); - } - - if (add) { - this.objects.add(obj); - } - return obj; - } - - /** - * add a stream object - * - * @param obj the PDF Stream to add to this document - */ - public void addStream(PDFStream obj) { - this.objects.add(obj); - } - - /** - * make an annotation list object - * - * @return the annotation list object created - */ - public PDFAnnotList makeAnnotList() { - - /* - * create a PDFAnnotList with the next object number and add it - * to the list of objects - */ - PDFAnnotList obj = new PDFAnnotList(++this.objectcount); - return obj; - } - - /** - * Add an annotation list object to the pdf document - * - * @param obj the annotation list to add - */ - public void addAnnotList(PDFAnnotList obj) { - this.objects.add(obj); - } - - /** * Get the root Outlines object. This method does not write * the outline to the PDF document, it simply creates a * reference for later. @@ -1819,35 +775,14 @@ public class PDFDocument extends AbstractLogEnabled { return outlineRoot; } - outlineRoot = new PDFOutline(++this.objectcount, null, null); + outlineRoot = new PDFOutline(null, null); + assignObjectNumber(outlineRoot); addTrailerObject(outlineRoot); root.setRootOutline(outlineRoot); return outlineRoot; } /** - * Make an outline object and add it to the given outline - * - * @param parent parent PDFOutline object which may be null - * @param label the title for the new outline object - * @param destination the reference string for the action to go to - * @param yoffset the yoffset on the destination page - * @return the new PDF outline object - */ - public PDFOutline makeOutline(PDFOutline parent, String label, - String destination, float yoffset) { - String goToRef = getGoToReference(destination, yoffset); - - PDFOutline obj = new PDFOutline(++this.objectcount, label, goToRef); - - if (parent != null) { - parent.addOutline(obj); - } - this.objects.add(obj); - return obj; - } - - /** * get the /Resources object for the document * * @return the /Resources object @@ -1857,25 +792,34 @@ public class PDFDocument extends AbstractLogEnabled { } /** + * Ensure there is room in the locations xref for the number of + * objects that have been created. + */ + private void setLocation(int objidx, int position) { + while (location.size() <= objidx) { + location.add(LOCATION_PLACEHOLDER); + } + location.set(objidx, new Integer(position)); + } + + /** * write the entire document out * * @param stream the OutputStream to output the document to * @throws IOException if there is an exception writing to the output stream */ public void output(OutputStream stream) throws IOException { - - prepareLocations(); - - for (int count = 0; count < this.objects.size(); count++) { - /* retrieve the object with the current number */ - PDFObject object = (PDFObject)this.objects.get(count); - + //Write out objects until the list is empty. This approach (used with a + //LinkedList) allows for output() methods to create and register objects + //on the fly even during serialization. + while (this.objects.size() > 0) { + /* Retrieve first */ + PDFObject object = (PDFObject)this.objects.remove(0); /* * add the position of this object to the list of object * locations */ - location.set(object.getNumber() - 1, - new Integer(this.position)); + setLocation(object.getObjectNumber() - 1, this.position); /* * output the object and increment the character position @@ -1884,7 +828,8 @@ public class PDFDocument extends AbstractLogEnabled { this.position += object.output(stream); } - this.objects.clear(); + //Clear all objects written to the file + //this.objects.clear(); } /** @@ -1924,7 +869,7 @@ public class PDFDocument extends AbstractLogEnabled { output(stream); for (int count = 0; count < trailerObjects.size(); count++) { PDFObject o = (PDFObject) trailerObjects.get(count); - this.location.set(o.getNumber() - 1, + this.location.set(o.getObjectNumber() - 1, new Integer(this.position)); this.position += o.output(stream); } @@ -1941,16 +886,14 @@ public class PDFDocument extends AbstractLogEnabled { /* construct the trailer */ String pdf = "trailer\n" + "<<\n" + "/Size " + (this.objectcount + 1) + "\n" - + "/Root " + this.root.number + " " - + this.root.generation + " R\n" - + "/Info " + this.info.number + " " - + this.info.generation + " R\n" + + "/Root " + this.root.referencePDF() + "\n" + + "/Info " + this.info.referencePDF() + "\n" + encryptEntry + ">>\n" + "startxref\n" + this.xref + "\n" + "%%EOF\n"; /* write the trailer */ - stream.write(pdf.getBytes()); + stream.write(encode(pdf)); } /** @@ -1965,9 +908,10 @@ public class PDFDocument extends AbstractLogEnabled { this.xref = this.position; /* construct initial part of xref */ - StringBuffer pdf = new StringBuffer("xref\n0 " - + (this.objectcount + 1) - + "\n0000000000 65535 f \n"); + StringBuffer pdf = new StringBuffer(128); + pdf.append("xref\n0 " + + (this.objectcount + 1) + + "\n0000000000 65535 f \n"); for (int count = 0; count < this.location.size(); count++) { String x = this.location.get(count).toString(); @@ -1981,7 +925,7 @@ public class PDFDocument extends AbstractLogEnabled { } /* write the xref table and return the character length */ - byte[] pdfBytes = pdf.toString().getBytes(); + byte[] pdfBytes = encode(pdf.toString()); stream.write(pdfBytes); return pdfBytes.length; } |