| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (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.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.pdf; |
| |
| // Java |
| import java.awt.Color; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.text.DecimalFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.xml.transform.Source; |
| import javax.xml.transform.stream.StreamSource; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.io.output.ByteArrayOutputStream; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.xmlgraphics.java2d.color.ColorUtil; |
| import org.apache.xmlgraphics.java2d.color.NamedColorSpace; |
| import org.apache.xmlgraphics.xmp.Metadata; |
| |
| import org.apache.fop.fonts.CIDFont; |
| import org.apache.fop.fonts.CIDSubset; |
| import org.apache.fop.fonts.CodePointMapping; |
| import org.apache.fop.fonts.CustomFont; |
| 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.SimpleSingleByteEncoding; |
| import org.apache.fop.fonts.SingleByteEncoding; |
| import org.apache.fop.fonts.SingleByteFont; |
| import org.apache.fop.fonts.Typeface; |
| 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; |
| |
| /** |
| * This class provides method to create and register PDF objects. |
| */ |
| public class PDFFactory { |
| |
| /** Resolution of the User Space coordinate system (72dpi). */ |
| public static final int DEFAULT_PDF_RESOLUTION = 72; |
| |
| private PDFDocument document; |
| |
| private Log log = LogFactory.getLog(PDFFactory.class); |
| |
| private int subsetFontCounter = -1; |
| |
| /** |
| * Creates a new PDFFactory. |
| * @param document the parent PDFDocument needed to register the generated |
| * objects |
| */ |
| public PDFFactory(PDFDocument document) { |
| this.document = document; |
| } |
| |
| /** |
| * Returns the parent PDFDocument associated with this factory. |
| * @return PDFDocument the parent PDFDocument |
| */ |
| public final PDFDocument getDocument() { |
| return this.document; |
| } |
| |
| /* ========================= structure objects ========================= */ |
| |
| /** |
| * 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 |
| */ |
| public PDFRoot makeRoot(PDFPages pages) { |
| //Make a /Pages object. This object is written in the trailer. |
| PDFRoot pdfRoot = new PDFRoot(++this.document.objectcount, pages); |
| pdfRoot.setDocument(getDocument()); |
| getDocument().addTrailerObject(pdfRoot); |
| return pdfRoot; |
| } |
| |
| /** |
| * Make a /Pages object. This object is written in the trailer. |
| * |
| * @return a new PDF Pages object for adding pages to |
| */ |
| public PDFPages makePages() { |
| PDFPages pdfPages = new PDFPages(++(this.document.objectcount)); |
| pdfPages.setDocument(getDocument()); |
| getDocument().addTrailerObject(pdfPages); |
| return pdfPages; |
| } |
| |
| /** |
| * Make a /Resources object. This object is written in the trailer. |
| * |
| * @return a new PDF resources object |
| */ |
| public PDFResources makeResources() { |
| PDFResources pdfResources = new PDFResources(++this.document.objectcount); |
| pdfResources.setDocument(getDocument()); |
| getDocument().addTrailerObject(pdfResources); |
| return pdfResources; |
| } |
| |
| /** |
| * 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(); |
| // set the default producer |
| pdfInfo.setProducer(prod); |
| getDocument().registerObject(pdfInfo); |
| return pdfInfo; |
| } |
| |
| /** |
| * Make a Metadata object. |
| * @param meta the DOM Document containing the XMP metadata. |
| * @param readOnly true if the metadata packet should be marked read-only |
| * @return the newly created Metadata object |
| */ |
| public PDFMetadata makeMetadata(Metadata meta, boolean readOnly) { |
| PDFMetadata pdfMetadata = new PDFMetadata(meta, readOnly); |
| getDocument().registerObject(pdfMetadata); |
| return pdfMetadata; |
| } |
| |
| /** |
| * Make a OutputIntent dictionary. |
| * @return the newly created OutputIntent dictionary |
| */ |
| public PDFOutputIntent makeOutputIntent() { |
| PDFOutputIntent outputIntent = new PDFOutputIntent(); |
| getDocument().registerObject(outputIntent); |
| return outputIntent; |
| } |
| |
| /** |
| * Make a /Page object. The page is assigned an object number immediately |
| * so references can already be made. The page must be added to the |
| * PDFDocument later using addObject(). |
| * |
| * @param resources resources object to use |
| * @param pageIndex index of the page (zero-based) |
| * @param mediaBox the MediaBox area |
| * @param cropBox the CropBox area |
| * @param bleedBox the BleedBox area |
| * @param trimBox the TrimBox area |
| * |
| * @return the created /Page object |
| */ |
| public PDFPage makePage(PDFResources resources, int pageIndex, |
| Rectangle2D mediaBox, Rectangle2D cropBox, |
| Rectangle2D bleedBox, Rectangle2D trimBox) { |
| /* |
| * create a PDFPage with the next object number, the given |
| * resources, contents and dimensions |
| */ |
| PDFPage page = new PDFPage(resources, pageIndex, mediaBox, cropBox, bleedBox, trimBox); |
| |
| getDocument().assignObjectNumber(page); |
| getDocument().getPages().addPage(page); |
| return page; |
| } |
| |
| /** |
| * Make a /Page object. The page is assigned an object number immediately |
| * so references can already be made. The page must be added to the |
| * PDFDocument later using addObject(). |
| * |
| * @param resources resources object to use |
| * @param pageWidth width of the page in points |
| * @param pageHeight height of the page in points |
| * @param pageIndex index of the page (zero-based) |
| * |
| * @return the created /Page object |
| */ |
| public PDFPage makePage(PDFResources resources, |
| int pageWidth, int pageHeight, int pageIndex) { |
| Rectangle2D mediaBox = new Rectangle2D.Double(0, 0, pageWidth, pageHeight); |
| return makePage(resources, pageIndex, mediaBox, mediaBox, mediaBox, mediaBox); |
| } |
| |
| /** |
| * Make a /Page object. The page is assigned an object number immediately |
| * so references can already be made. The page must be added to the |
| * PDFDocument later using addObject(). |
| * |
| * @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) { |
| return makePage(resources, pageWidth, pageHeight, -1); |
| } |
| |
| /* ========================= functions ================================= */ |
| |
| /** |
| * 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. |
| * |
| * 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( // CSOK: ParameterNumber |
| 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(theFunctionType, theDomain, |
| theRange, theSize, |
| theBitsPerSample, theOrder, |
| theEncode, theDecode, |
| theFunctionDataStream, |
| theFilter); |
| |
| PDFFunction oldfunc = getDocument().findFunction(function); |
| if (oldfunc == null) { |
| getDocument().registerObject(function); |
| } else { |
| function = oldfunc; |
| } |
| return (function); |
| } |
| |
| /** |
| * 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(theFunctionType, theDomain, |
| theRange, theCZero, theCOne, |
| theInterpolationExponentN); |
| PDFFunction oldfunc = getDocument().findFunction(function); |
| if (oldfunc == null) { |
| getDocument().registerObject(function); |
| } else { |
| function = oldfunc; |
| } |
| return (function); |
| } |
| |
| /** |
| * 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(theFunctionType, theDomain, |
| theRange, theFunctions, |
| theBounds, theEncode); |
| |
| PDFFunction oldfunc = getDocument().findFunction(function); |
| if (oldfunc == null) { |
| getDocument().registerObject(function); |
| } else { |
| 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(theFunctionType, theDomain, |
| theRange, |
| theFunctionDataStream); |
| |
| PDFFunction oldfunc = getDocument().findFunction(function); |
| if (oldfunc == null) { |
| getDocument().registerObject(function); |
| } else { |
| function = oldfunc; |
| } |
| return (function); |
| |
| } |
| |
| /* ========================= shadings ================================== */ |
| |
| /** |
| * 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( // CSOK: ParameterNumber |
| PDFResourceContext res, int theShadingType, |
| PDFDeviceColorSpace theColorSpace, |
| List theBackground, List theBBox, |
| boolean theAntiAlias, List theDomain, |
| List theMatrix, |
| PDFFunction theFunction) { |
| // make Shading of Type 1 |
| PDFShading shading = new PDFShading(theShadingType, |
| theColorSpace, theBackground, |
| theBBox, theAntiAlias, theDomain, |
| theMatrix, theFunction); |
| |
| PDFShading oldshad = getDocument().findShading(shading); |
| if (oldshad == null) { |
| getDocument().registerObject(shading); |
| } else { |
| shading = oldshad; |
| } |
| |
| // add this shading to resources |
| if (res != null) { |
| res.getPDFResources().addShading(shading); |
| } else { |
| getDocument().getResources().addShading(shading); |
| } |
| |
| return (shading); |
| } |
| |
| /** |
| * 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( // CSOK: ParameterNumber |
| PDFResourceContext res, int theShadingType, |
| PDFDeviceColorSpace theColorSpace, |
| List theBackground, List theBBox, |
| boolean theAntiAlias, List theCoords, |
| List theDomain, PDFFunction theFunction, |
| List theExtend) { |
| // make Shading of Type 2 or 3 |
| PDFShading shading = new PDFShading(theShadingType, |
| theColorSpace, theBackground, |
| theBBox, theAntiAlias, theCoords, |
| theDomain, theFunction, |
| theExtend); |
| |
| PDFShading oldshad = getDocument().findShading(shading); |
| if (oldshad == null) { |
| getDocument().registerObject(shading); |
| } else { |
| shading = oldshad; |
| } |
| |
| if (res != null) { |
| res.getPDFResources().addShading(shading); |
| } else { |
| getDocument().getResources().addShading(shading); |
| } |
| |
| return (shading); |
| } |
| |
| /** |
| * 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( // CSOK: ParameterNumber |
| PDFResourceContext res, int theShadingType, |
| PDFDeviceColorSpace 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 |
| PDFShading shading = new PDFShading(theShadingType, |
| theColorSpace, theBackground, |
| theBBox, theAntiAlias, |
| theBitsPerCoordinate, |
| theBitsPerComponent, |
| theBitsPerFlag, theDecode, |
| theFunction); |
| |
| PDFShading oldshad = getDocument().findShading(shading); |
| if (oldshad == null) { |
| getDocument().registerObject(shading); |
| } else { |
| shading = oldshad; |
| } |
| |
| if (res != null) { |
| res.getPDFResources().addShading(shading); |
| } else { |
| getDocument().getResources().addShading(shading); |
| } |
| |
| return (shading); |
| } |
| |
| /** |
| * 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( // CSOK: ParameterNumber |
| PDFResourceContext res, int theShadingType, |
| PDFDeviceColorSpace theColorSpace, |
| List theBackground, List theBBox, |
| boolean theAntiAlias, |
| int theBitsPerCoordinate, |
| int theBitsPerComponent, List theDecode, |
| int theVerticesPerRow, |
| PDFFunction theFunction) { |
| // make shading of Type 5 |
| PDFShading shading = new PDFShading(theShadingType, |
| theColorSpace, theBackground, |
| theBBox, theAntiAlias, |
| theBitsPerCoordinate, |
| theBitsPerComponent, theDecode, |
| theVerticesPerRow, theFunction); |
| |
| PDFShading oldshad = getDocument().findShading(shading); |
| if (oldshad == null) { |
| getDocument().registerObject(shading); |
| } else { |
| shading = oldshad; |
| } |
| |
| if (res != null) { |
| res.getPDFResources().addShading(shading); |
| } else { |
| getDocument().getResources().addShading(shading); |
| } |
| |
| return (shading); |
| } |
| |
| /* ========================= patterns ================================== */ |
| |
| /** |
| * 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( // CSOK: ParameterNumber |
| PDFResourceContext res, int thePatternType, // 1 |
| PDFResources theResources, int thePaintType, int theTilingType, |
| List theBBox, double theXStep, |
| double theYStep, List theMatrix, |
| List theXUID, StringBuffer thePatternDataStream) { |
| // PDFResources theResources |
| PDFPattern pattern = new PDFPattern(theResources, 1, |
| thePaintType, theTilingType, |
| theBBox, theXStep, theYStep, |
| theMatrix, theXUID, |
| thePatternDataStream); |
| |
| PDFPattern oldpatt = getDocument().findPattern(pattern); |
| if (oldpatt == null) { |
| getDocument().registerObject(pattern); |
| } else { |
| pattern = oldpatt; |
| } |
| |
| if (res != null) { |
| res.getPDFResources().addPattern(pattern); |
| } else { |
| getDocument().getResources().addPattern(pattern); |
| } |
| |
| return (pattern); |
| } |
| |
| /** |
| * Make a smooth shading pattern |
| * |
| * @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) { |
| PDFPattern pattern = new PDFPattern(2, theShading, |
| theXUID, theExtGState, theMatrix); |
| |
| PDFPattern oldpatt = getDocument().findPattern(pattern); |
| if (oldpatt == null) { |
| getDocument().registerObject(pattern); |
| } else { |
| pattern = oldpatt; |
| } |
| |
| if (res != null) { |
| res.getPDFResources().addPattern(pattern); |
| } else { |
| getDocument().getResources().addPattern(pattern); |
| } |
| |
| return (pattern); |
| } |
| |
| /** |
| * 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 |
| * @param theMatrix the coordinate-transformation matrix |
| * @return the PDF pattern that was created |
| */ |
| public PDFPattern makeGradient(PDFResourceContext res, boolean radial, |
| PDFDeviceColorSpace theColorspace, |
| List theColors, List theBounds, |
| List theCoords, List theMatrix) { |
| PDFShading myShad; |
| PDFFunction myfunky; |
| PDFFunction myfunc; |
| List theCzero; |
| List theCone; |
| PDFPattern myPattern; |
| //PDFColorSpace theColorSpace; |
| double interpolation = 1.000; |
| List theFunctions = new 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 |
| Color currentColor = (Color)theColors.get(currentPosition); |
| Color nextColor = (Color)theColors.get(currentPosition + 1); |
| |
| // colorspace must be consistent, so we simply convert to sRGB where necessary |
| if (!currentColor.getColorSpace().isCS_sRGB()) { |
| //Convert to sRGB |
| currentColor = ColorUtil.toSRGBColor(currentColor); |
| theColors.set(currentPosition, currentColor); |
| } |
| if (!nextColor.getColorSpace().isCS_sRGB()) { |
| //Convert to sRGB |
| nextColor = ColorUtil.toSRGBColor(nextColor); |
| theColors.set(currentPosition + 1, nextColor); |
| } |
| |
| theCzero = toColorVector(currentColor); |
| theCone = toColorVector(nextColor); |
| |
| myfunc = makeFunction(2, null, null, theCzero, theCone, |
| interpolation); |
| |
| theFunctions.add(myfunc); |
| |
| } // end of for every consecutive color pair |
| |
| myfunky = makeFunction(3, null, null, theFunctions, theBounds, |
| null); |
| |
| if (radial) { |
| if (theCoords.size() == 6) { |
| myShad = makeShading(res, 3, getDocument().getPDFColorSpace(), |
| 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 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 = makeShading(res, 3, getDocument().getPDFColorSpace(), |
| null, null, |
| false, newCoords, null, myfunky, |
| null); |
| |
| } |
| } else { |
| myShad = makeShading(res, 2, getDocument().getPDFColorSpace(), |
| null, null, |
| false, theCoords, null, myfunky, |
| null); |
| |
| } |
| |
| myPattern = makePattern(res, 2, myShad, null, null, theMatrix); |
| |
| return (myPattern); |
| } |
| |
| private List toColorVector(Color nextColor) { |
| List vector = new java.util.ArrayList(); |
| float[] comps = nextColor.getColorComponents(null); |
| for (int i = 0, c = comps.length; i < c; i++) { |
| vector.add(new Double(comps[i])); |
| } |
| return vector; |
| } |
| |
| /* ============= named destinations and the name dictionary ============ */ |
| |
| /** |
| * Registers and returns newdest if it is unique. Otherwise, returns |
| * the equal destination already present in the document. |
| * |
| * @param newdest a new, as yet unregistered destination |
| * @return newdest if unique, else the already registered instance |
| */ |
| protected PDFDestination getUniqueDestination(PDFDestination newdest) { |
| PDFDestination existing = getDocument().findDestination(newdest); |
| if (existing != null) { |
| return existing; |
| } else { |
| getDocument().addDestination(newdest); |
| return newdest; |
| } |
| } |
| |
| /** |
| * Make a named destination. |
| * |
| * @param idRef ID Reference for this destination (the name of the destination) |
| * @param goToRef Object reference to the GoTo Action |
| * @return the newly created destrination |
| */ |
| public PDFDestination makeDestination(String idRef, Object goToRef) { |
| PDFDestination destination = new PDFDestination(idRef, goToRef); |
| return getUniqueDestination(destination); |
| } |
| |
| /** |
| * Make a names dictionary (the /Names object). |
| * @return the new PDFNames object |
| */ |
| public PDFNames makeNames() { |
| PDFNames names = new PDFNames(); |
| getDocument().assignObjectNumber(names); |
| getDocument().addTrailerObject(names); |
| return names; |
| } |
| |
| /** |
| * Make a names dictionary (the /PageLabels object). |
| * @return the new PDFPageLabels object |
| */ |
| public PDFPageLabels makePageLabels() { |
| PDFPageLabels pageLabels = new PDFPageLabels(); |
| getDocument().assignObjectNumber(pageLabels); |
| getDocument().addTrailerObject(pageLabels); |
| return pageLabels; |
| } |
| |
| /** |
| * Creates and returns a StructTreeRoot object. Used for accessibility. |
| * @param parentTree the value of the ParenTree entry |
| * @return structure Tree Root element |
| */ |
| public PDFStructTreeRoot makeStructTreeRoot(PDFParentTree parentTree) { |
| PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(parentTree); |
| getDocument().assignObjectNumber(structTreeRoot); |
| getDocument().addTrailerObject(structTreeRoot); |
| getDocument().getRoot().setStructTreeRoot(structTreeRoot); |
| return structTreeRoot; |
| } |
| |
| /** |
| * Creates and returns a StructElem object. |
| * |
| * @param structureType the structure type of the new element (value for the |
| * S entry) |
| * @param parent the parent of the new structure element in the structure |
| * hierarchy |
| * @return the newly created element |
| */ |
| public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) { |
| PDFStructElem structElem = new PDFStructElem(parent, structureType); |
| getDocument().assignObjectNumber(structElem); |
| getDocument().addTrailerObject(structElem); |
| return structElem; |
| } |
| |
| /** |
| * Make a the head object of the name dictionary (the /Dests object). |
| * |
| * @param destinationList a list of PDFDestination instances |
| * @return the new PDFDests object |
| */ |
| public PDFDests makeDests(List destinationList) { |
| PDFDests dests; |
| |
| //TODO: Check why the below conditional branch is needed. Condition is always true... |
| final boolean deep = true; |
| //true for a "deep" structure (one node per entry), true for a "flat" structure |
| if (deep) { |
| dests = new PDFDests(); |
| PDFArray kids = new PDFArray(dests); |
| Iterator iter = destinationList.iterator(); |
| while (iter.hasNext()) { |
| PDFDestination dest = (PDFDestination)iter.next(); |
| PDFNameTreeNode node = new PDFNameTreeNode(); |
| getDocument().registerObject(node); |
| node.setLowerLimit(dest.getIDRef()); |
| node.setUpperLimit(dest.getIDRef()); |
| node.setNames(new PDFArray(node)); |
| PDFArray names = node.getNames(); |
| names.add(dest); |
| kids.add(node); |
| } |
| dests.setLowerLimit(((PDFNameTreeNode)kids.get(0)).getLowerLimit()); |
| dests.setUpperLimit(((PDFNameTreeNode)kids.get(kids.length() - 1)).getUpperLimit()); |
| dests.setKids(kids); |
| } else { |
| dests = new PDFDests(destinationList); |
| } |
| getDocument().registerObject(dests); |
| return dests; |
| } |
| |
| /** |
| * Make a name tree node. |
| * |
| * @return the new name tree node |
| */ |
| public PDFNameTreeNode makeNameTreeNode() { |
| PDFNameTreeNode node = new PDFNameTreeNode(); |
| getDocument().registerObject(node); |
| return node; |
| } |
| |
| /* ========================= links ===================================== */ |
| // Some of the "yoffset-only" functions in this part are obsolete and can |
| // possibly be removed or deprecated. Some are still called by PDFGraphics2D |
| // (although that could be changed, they don't need the yOffset param anyway). |
| |
| /** |
| * Create a PDF link to an existing PDFAction object |
| * |
| * @param rect the hotspot position in absolute coordinates |
| * @param pdfAction the PDFAction that this link refers to |
| * @return the new PDFLink object, or null if either rect or pdfAction is null |
| */ |
| public PDFLink makeLink(Rectangle2D rect, PDFAction pdfAction) { |
| if (rect == null || pdfAction == null) { |
| return null; |
| } else { |
| PDFLink link = new PDFLink(rect); |
| link.setAction(pdfAction); |
| getDocument().registerObject(link); |
| return link; |
| // does findLink make sense? I mean, how often will it happen that several |
| // links have the same target *and* the same hot rect? And findLink has to |
| // walk and compare the entire link list everytime you call it... |
| } |
| } |
| |
| /** |
| * 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(rect); |
| getDocument().registerObject(link); |
| |
| PDFGoTo gt = new PDFGoTo(page); |
| gt.setDestination(dest); |
| getDocument().registerObject(gt); |
| PDFInternalLink internalLink = new PDFInternalLink(gt.referencePDF()); |
| link.setAction(internalLink); |
| |
| return link; |
| } |
| |
| /** |
| * Make a {@link PDFLink} 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; |
| PDFLink link = new PDFLink(rect); |
| |
| if (linkType == PDFLink.EXTERNAL) { |
| link.setAction(getExternalAction(destination, false)); |
| } else { |
| // linkType is internal |
| String goToReference = getGoToReference(destination, yoffset); |
| PDFInternalLink internalLink = new PDFInternalLink(goToReference); |
| link.setAction(internalLink); |
| } |
| |
| PDFLink oldlink = getDocument().findLink(link); |
| if (oldlink == null) { |
| getDocument().registerObject(link); |
| } else { |
| link = oldlink; |
| } |
| |
| return link; |
| } |
| |
| private static final String EMBEDDED_FILE = "embedded-file:"; |
| |
| /** |
| * Create/find and return the appropriate external PDFAction according to the target |
| * |
| * @param target The external target. This may be a PDF file name |
| * (optionally with internal page number or destination) or any type of URI. |
| * @param newWindow boolean indicating whether the target should be |
| * displayed in a new window |
| * @return the PDFAction thus created or found |
| */ |
| public PDFAction getExternalAction(String target, boolean newWindow) { |
| int index; |
| String targetLo = target.toLowerCase(); |
| if (target.startsWith(EMBEDDED_FILE)) { |
| // File Attachments (Embedded Files) |
| String filename = target.substring(EMBEDDED_FILE.length()); |
| return getActionForEmbeddedFile(filename, newWindow); |
| } else if (targetLo.startsWith("http://")) { |
| // HTTP URL? |
| return new PDFUri(target); |
| } else if (targetLo.startsWith("file://")) { |
| // Non PDF files. Try to /Launch them. |
| target = target.substring("file://".length()); |
| return getLaunchAction(target); |
| } else if (targetLo.endsWith(".pdf")) { |
| // Bare PDF file name? |
| return getGoToPDFAction(target, null, -1, newWindow); |
| } else if ((index = targetLo.indexOf(".pdf#page=")) > 0) { // CSOK: InnerAssignment |
| // PDF file + page? |
| String filename = target.substring(0, index + 4); |
| int page = Integer.parseInt(target.substring(index + 10)); |
| return getGoToPDFAction(filename, null, page, newWindow); |
| } else if ((index = targetLo.indexOf(".pdf#dest=")) > 0) { // CSOK: InnerAssignment |
| // PDF file + destination? |
| String filename = target.substring(0, index + 4); |
| String dest = target.substring(index + 10); |
| return getGoToPDFAction(filename, dest, -1, newWindow); |
| } else { |
| // None of the above? Default to URI: |
| return new PDFUri(target); |
| } |
| } |
| |
| private PDFAction getActionForEmbeddedFile(String filename, boolean newWindow) { |
| PDFNames names = getDocument().getRoot().getNames(); |
| if (names == null) { |
| throw new IllegalStateException( |
| "No Names dictionary present." |
| + " Cannot create Launch Action for embedded file: " + filename); |
| } |
| PDFNameTreeNode embeddedFiles = names.getEmbeddedFiles(); |
| if (embeddedFiles == null) { |
| throw new IllegalStateException( |
| "No /EmbeddedFiles name tree present." |
| + " Cannot create Launch Action for embedded file: " + filename); |
| } |
| |
| //Find filespec reference for the embedded file |
| filename = PDFText.toPDFString(filename, '_'); |
| PDFArray files = embeddedFiles.getNames(); |
| PDFReference embeddedFileRef = null; |
| int i = 0; |
| while (i < files.length()) { |
| String name = (String)files.get(i); |
| i++; |
| PDFReference ref = (PDFReference)files.get(i); |
| if (name.equals(filename)) { |
| embeddedFileRef = ref; |
| break; |
| } |
| i++; |
| } |
| if (embeddedFileRef == null) { |
| throw new IllegalStateException( |
| "No embedded file with name " + filename + " present."); |
| } |
| |
| //Finally create the action |
| //PDFLaunch action = new PDFLaunch(embeddedFileRef); |
| //This works with Acrobat 8 but not with Acrobat 9 |
| |
| //The following two options didn't seem to have any effect. |
| //PDFGoToEmbedded action = new PDFGoToEmbedded(embeddedFileRef, 0, newWindow); |
| //PDFGoToRemote action = new PDFGoToRemote(embeddedFileRef, 0, newWindow); |
| |
| //This finally seems to work: |
| StringBuffer scriptBuffer = new StringBuffer(); |
| scriptBuffer.append("this.exportDataObject({cName:\""); |
| scriptBuffer.append(filename); |
| scriptBuffer.append("\", nLaunch:2});"); |
| |
| PDFJavaScriptLaunchAction action = new PDFJavaScriptLaunchAction(scriptBuffer.toString()); |
| return action; |
| } |
| |
| /** |
| * Create or find a PDF GoTo with the given page reference string and Y offset, |
| * and return its PDF object reference |
| * |
| * @param pdfPageRef the PDF page reference, e.g. "23 0 R" |
| * @param yoffset the distance from the bottom of the page in points |
| * @return the GoTo's object reference |
| */ |
| public String getGoToReference(String pdfPageRef, float yoffset) { |
| return getPDFGoTo(pdfPageRef, new Point2D.Float(0.0f, yoffset)).referencePDF(); |
| } |
| |
| /** |
| * Finds and returns a PDFGoTo to the given page and position. |
| * Creates the PDFGoTo if not found. |
| * |
| * @param pdfPageRef the PDF page reference |
| * @param position the (X,Y) position in points |
| * |
| * @return the new or existing PDFGoTo object |
| */ |
| public PDFGoTo getPDFGoTo(String pdfPageRef, Point2D position) { |
| getDocument().getProfile().verifyActionAllowed(); |
| PDFGoTo gt = new PDFGoTo(pdfPageRef, position); |
| PDFGoTo oldgt = getDocument().findGoTo(gt); |
| if (oldgt == null) { |
| getDocument().assignObjectNumber(gt); |
| getDocument().addTrailerObject(gt); |
| } else { |
| gt = oldgt; |
| } |
| return gt; |
| } |
| |
| /** |
| * 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 |
| * @param newWindow boolean indicating whether the target should be |
| * displayed in a new window |
| * @return the pdf goto remote object |
| */ |
| private PDFGoToRemote getGoToPDFAction(String file, String dest, int page, boolean newWindow) { |
| getDocument().getProfile().verifyActionAllowed(); |
| PDFFileSpec fileSpec = new PDFFileSpec(file); |
| PDFFileSpec oldspec = getDocument().findFileSpec(fileSpec); |
| if (oldspec == null) { |
| getDocument().registerObject(fileSpec); |
| } else { |
| fileSpec = oldspec; |
| } |
| PDFGoToRemote remote; |
| |
| if (dest == null && page == -1) { |
| remote = new PDFGoToRemote(fileSpec, newWindow); |
| } else if (dest != null) { |
| remote = new PDFGoToRemote(fileSpec, dest, newWindow); |
| } else { |
| remote = new PDFGoToRemote(fileSpec, page, newWindow); |
| } |
| PDFGoToRemote oldremote = getDocument().findGoToRemote(remote); |
| if (oldremote == null) { |
| getDocument().registerObject(remote); |
| } else { |
| remote = oldremote; |
| } |
| return remote; |
| } |
| |
| /** |
| * Creates and returns a launch pdf document action using |
| * <code>file</code> to create a file spcifiaciton for |
| * the document/file to be opened with an external application. |
| * |
| * @param file the pdf file name |
| * @return the pdf launch object |
| */ |
| private PDFLaunch getLaunchAction(String file) { |
| getDocument().getProfile().verifyActionAllowed(); |
| |
| PDFFileSpec fileSpec = new PDFFileSpec(file); |
| PDFFileSpec oldSpec = getDocument().findFileSpec(fileSpec); |
| |
| if (oldSpec == null) { |
| getDocument().registerObject(fileSpec); |
| } else { |
| fileSpec = oldSpec; |
| } |
| PDFLaunch launch = new PDFLaunch(fileSpec); |
| PDFLaunch oldLaunch = getDocument().findLaunch(launch); |
| |
| if (oldLaunch == null) { |
| getDocument().registerObject(launch); |
| } else { |
| launch = oldLaunch; |
| } |
| |
| return launch; |
| } |
| |
| /** |
| * Make an outline object and add it to the given parent |
| * |
| * @param parent the parent PDFOutline object (may be null) |
| * @param label the title for the new outline object |
| * @param actionRef the action reference string to be placed after the /A |
| * @param showSubItems whether to initially display child outline items |
| * @return the new PDF outline object |
| */ |
| public PDFOutline makeOutline(PDFOutline parent, String label, |
| String actionRef, boolean showSubItems) { |
| PDFOutline pdfOutline = new PDFOutline(label, actionRef, showSubItems); |
| if (parent != null) { |
| parent.addOutline(pdfOutline); |
| } |
| getDocument().registerObject(pdfOutline); |
| return pdfOutline; |
| } |
| |
| /** |
| * Make an outline object and add it to the given parent |
| * |
| * @param parent the parent PDFOutline object (may be null) |
| * @param label the title for the new outline object |
| * @param pdfAction the action that this outline item points to - must not be null! |
| * @param showSubItems whether to initially display child outline items |
| * @return the new PDFOutline object, or null if pdfAction is null |
| */ |
| public PDFOutline makeOutline(PDFOutline parent, String label, |
| PDFAction pdfAction, boolean showSubItems) { |
| return pdfAction == null |
| ? null |
| : makeOutline(parent, label, pdfAction.getAction(), showSubItems); |
| } |
| |
| // This one is obsolete now, at least it isn't called from anywhere inside FOP |
| /** |
| * 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 |
| * @param showSubItems whether to initially display child outline items |
| * @return the new PDF outline object |
| */ |
| public PDFOutline makeOutline(PDFOutline parent, String label, |
| String destination, float yoffset, |
| boolean showSubItems) { |
| |
| String goToRef = getGoToReference(destination, yoffset); |
| return makeOutline(parent, label, goToRef, showSubItems); |
| } |
| |
| |
| /* ========================= fonts ===================================== */ |
| |
| /** |
| * make a /Encoding object |
| * |
| * @param encodingName character encoding scheme name |
| * @return the created /Encoding object |
| */ |
| public PDFEncoding makeEncoding(String encodingName) { |
| PDFEncoding encoding = new PDFEncoding(encodingName); |
| |
| getDocument().registerObject(encoding); |
| return encoding; |
| } |
| |
| /** |
| * 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) { |
| PDFFont preRegisteredfont = getDocument().findFont(fontname); |
| if (preRegisteredfont != null) { |
| return preRegisteredfont; |
| } |
| |
| boolean forceToUnicode = true; |
| |
| if (descriptor == null) { |
| //Usually Base 14 fonts |
| PDFFont font = new PDFFont(fontname, FontType.TYPE1, basefont, encoding); |
| getDocument().registerObject(font); |
| if (forceToUnicode && !PDFEncoding.isPredefinedEncoding(encoding)) { |
| SingleByteEncoding mapping; |
| if (encoding != null) { |
| mapping = CodePointMapping.getMapping(encoding); |
| } else { |
| //for Symbol and ZapfDingbats where encoding must be null in PDF |
| Typeface tf = (Typeface)metrics; |
| mapping = CodePointMapping.getMapping(tf.getEncodingName()); |
| } |
| generateToUnicodeCmap(font, mapping); |
| } |
| return font; |
| } else { |
| FontType fonttype = metrics.getFontType(); |
| |
| String fontPrefix = descriptor.isSubsetEmbedded() ? createSubsetFontPrefix() : ""; |
| |
| String subsetFontName = fontPrefix + basefont; |
| |
| PDFFontDescriptor pdfdesc = makeFontDescriptor(descriptor, fontPrefix); |
| |
| PDFFont font = null; |
| |
| font = PDFFont.createFont(fontname, fonttype, subsetFontName, null); |
| getDocument().registerObject(font); |
| |
| if (fonttype == FontType.TYPE0) { |
| font.setEncoding(encoding); |
| 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(subsetFontName, |
| cidMetrics.getCIDType(), |
| cidMetrics.getDefaultWidth(), |
| getSubsetWidths(cidMetrics), sysInfo, |
| (PDFCIDFontDescriptor)pdfdesc); |
| getDocument().registerObject(cidFont); |
| |
| PDFCMap cmap = new PDFToUnicodeCMap( |
| cidMetrics.getCIDSubset().getSubsetChars(), |
| "fop-ucs-H", |
| new PDFCIDSystemInfo("Adobe", |
| "Identity", |
| 0), false); |
| getDocument().registerObject(cmap); |
| ((PDFFontType0)font).setCMAP(cmap); |
| ((PDFFontType0)font).setDescendantFonts(cidFont); |
| } else { |
| PDFFontNonBase14 nonBase14 = (PDFFontNonBase14)font; |
| nonBase14.setDescriptor(pdfdesc); |
| |
| SingleByteFont singleByteFont; |
| if (metrics instanceof LazyFont) { |
| singleByteFont = (SingleByteFont)((LazyFont)metrics).getRealFont(); |
| } else { |
| singleByteFont = (SingleByteFont)metrics; |
| } |
| int firstChar = singleByteFont.getFirstChar(); |
| int lastChar = singleByteFont.getLastChar(); |
| nonBase14.setWidthMetrics(firstChar, |
| lastChar, |
| new PDFArray(null, metrics.getWidths())); |
| |
| //Handle encoding |
| SingleByteEncoding mapping = singleByteFont.getEncoding(); |
| if (singleByteFont.isSymbolicFont()) { |
| //no encoding, use the font's encoding |
| if (forceToUnicode) { |
| generateToUnicodeCmap(nonBase14, mapping); |
| } |
| } else if (PDFEncoding.isPredefinedEncoding(mapping.getName())) { |
| font.setEncoding(mapping.getName()); |
| //No ToUnicode CMap necessary if PDF 1.4, chapter 5.9 (page 368) is to be |
| //believed. |
| } else { |
| Object pdfEncoding = createPDFEncoding(mapping, |
| singleByteFont.getFontName()); |
| if (pdfEncoding instanceof PDFEncoding) { |
| font.setEncoding((PDFEncoding)pdfEncoding); |
| } else { |
| font.setEncoding((String)pdfEncoding); |
| } |
| if (forceToUnicode) { |
| generateToUnicodeCmap(nonBase14, mapping); |
| } |
| } |
| |
| //Handle additional encodings (characters outside the primary encoding) |
| if (singleByteFont.hasAdditionalEncodings()) { |
| for (int i = 0, c = singleByteFont.getAdditionalEncodingCount(); i < c; i++) { |
| SimpleSingleByteEncoding addEncoding |
| = singleByteFont.getAdditionalEncoding(i); |
| String name = fontname + "_" + (i + 1); |
| Object pdfenc = createPDFEncoding(addEncoding, |
| singleByteFont.getFontName()); |
| PDFFontNonBase14 addFont = (PDFFontNonBase14)PDFFont.createFont( |
| name, fonttype, |
| basefont, pdfenc); |
| addFont.setDescriptor(pdfdesc); |
| addFont.setWidthMetrics( |
| addEncoding.getFirstChar(), |
| addEncoding.getLastChar(), |
| new PDFArray(null, singleByteFont.getAdditionalWidths(i))); |
| getDocument().registerObject(addFont); |
| getDocument().getResources().addFont(addFont); |
| if (forceToUnicode) { |
| generateToUnicodeCmap(addFont, addEncoding); |
| } |
| } |
| } |
| } |
| |
| return font; |
| } |
| } |
| |
| private void generateToUnicodeCmap(PDFFont font, SingleByteEncoding encoding) { |
| PDFCMap cmap = new PDFToUnicodeCMap(encoding.getUnicodeCharMap(), |
| "fop-ucs-H", |
| new PDFCIDSystemInfo("Adobe", "Identity", 0), true); |
| getDocument().registerObject(cmap); |
| font.setToUnicode(cmap); |
| } |
| |
| /** |
| * Creates a PDFEncoding instance from a CodePointMapping instance. |
| * @param encoding the code point mapping (encoding) |
| * @param fontNameHint ... |
| * @return the PDF Encoding dictionary (or a String with the predefined encoding) |
| */ |
| public Object createPDFEncoding(SingleByteEncoding encoding, String fontNameHint) { |
| SingleByteEncoding baseEncoding; |
| if (fontNameHint.indexOf("Symbol") >= 0) { |
| baseEncoding = CodePointMapping.getMapping( |
| CodePointMapping.SYMBOL_ENCODING); |
| } else { |
| baseEncoding = CodePointMapping.getMapping( |
| CodePointMapping.STANDARD_ENCODING); |
| } |
| PDFEncoding pdfEncoding = new PDFEncoding(baseEncoding.getName()); |
| PDFEncoding.DifferencesBuilder builder |
| = pdfEncoding.createDifferencesBuilder(); |
| int start = -1; |
| String[] baseNames = baseEncoding.getCharNameMap(); |
| String[] charNameMap = encoding.getCharNameMap(); |
| for (int i = 0, ci = charNameMap.length; i < ci; i++) { |
| String basec = baseNames[i]; |
| String c = charNameMap[i]; |
| if (!basec.equals(c)) { |
| if (start != i) { |
| builder.addDifference(i); |
| start = i; |
| } |
| builder.addName(c); |
| start++; |
| } |
| } |
| if (builder.hasDifferences()) { |
| pdfEncoding.setDifferences(builder.toPDFArray()); |
| return pdfEncoding; |
| } else { |
| return baseEncoding.getName(); |
| } |
| } |
| |
| /** |
| * Creates and returns a width array with the widths of all the characters in the subset. |
| * @param cidFont the font |
| * @return the width array |
| */ |
| public PDFWArray getSubsetWidths(CIDFont cidFont) { |
| // Create widths for reencoded chars |
| PDFWArray warray = new PDFWArray(); |
| int[] widths = cidFont.getWidths(); |
| CIDSubset subset = cidFont.getCIDSubset(); |
| int[] tmpWidth = new int[subset.getSubsetSize()]; |
| |
| for (int i = 0, c = subset.getSubsetSize(); i < c; i++) { |
| int nwx = Math.max(0, subset.getGlyphIndexForSubsetIndex(i)); |
| tmpWidth[i] = widths[nwx]; |
| } |
| warray.addEntry(0, tmpWidth); |
| return warray; |
| } |
| |
| private String createSubsetFontPrefix() { |
| subsetFontCounter++; |
| DecimalFormat counterFormat = new DecimalFormat("00000"); |
| String counterString = counterFormat.format(subsetFontCounter); |
| |
| // Subset prefix as described in chapter 5.5.3 of PDF 1.4 |
| StringBuffer sb = new StringBuffer("E"); |
| |
| for (char c : counterString.toCharArray()) { |
| // translate numbers to uppercase characters |
| sb.append((char) (c + ('A' - '0'))); |
| } |
| sb.append("+"); |
| return sb.toString(); |
| } |
| |
| /** |
| * make a /FontDescriptor object |
| * |
| * @param desc the font descriptor |
| * @param fontPrefix the String with which to prefix the font name |
| * @return the new PDF font descriptor |
| */ |
| private PDFFontDescriptor makeFontDescriptor(FontDescriptor desc, String fontPrefix) { |
| PDFFontDescriptor descriptor = null; |
| |
| if (desc.getFontType() == FontType.TYPE0) { |
| // CID Font |
| descriptor = new PDFCIDFontDescriptor(fontPrefix + desc.getEmbedFontName(), |
| desc.getFontBBox(), |
| desc.getCapHeight(), |
| desc.getFlags(), |
| desc.getItalicAngle(), |
| desc.getStemV(), null); |
| } else { |
| // Create normal FontDescriptor |
| descriptor = new PDFFontDescriptor(desc.getEmbedFontName(), |
| desc.getAscender(), |
| desc.getDescender(), |
| desc.getCapHeight(), |
| desc.getFlags(), |
| new PDFRectangle(desc.getFontBBox()), |
| desc.getItalicAngle(), |
| desc.getStemV()); |
| } |
| getDocument().registerObject(descriptor); |
| |
| // Check if the font is embeddable |
| if (desc.isEmbeddable()) { |
| AbstractPDFStream stream = makeFontFile(desc); |
| if (stream != null) { |
| descriptor.setFontFile(desc.getFontType(), stream); |
| getDocument().registerObject(stream); |
| } |
| CustomFont font = getCustomFont(desc); |
| if (font instanceof CIDFont) { |
| CIDFont cidFont = (CIDFont)font; |
| buildCIDSet(descriptor, cidFont); |
| } |
| } |
| return descriptor; |
| } |
| |
| private void buildCIDSet(PDFFontDescriptor descriptor, CIDFont cidFont) { |
| BitSet cidSubset = cidFont.getCIDSubset().getGlyphIndexBitSet(); |
| PDFStream cidSet = makeStream(null, true); |
| ByteArrayOutputStream baout = new ByteArrayOutputStream(cidSubset.length() / 8 + 1); |
| int value = 0; |
| for (int i = 0, c = cidSubset.length(); i < c; i++) { |
| int shift = i % 8; |
| boolean b = cidSubset.get(i); |
| if (b) { |
| value |= 1 << 7 - shift; |
| } |
| if (shift == 7) { |
| baout.write(value); |
| value = 0; |
| } |
| } |
| baout.write(value); |
| try { |
| cidSet.setData(baout.toByteArray()); |
| descriptor.setCIDSet(cidSet); |
| } catch (IOException ioe) { |
| log.error( |
| "Failed to write CIDSet [" + cidFont + "] " |
| + cidFont.getEmbedFontName(), ioe); |
| } |
| } |
| |
| /** |
| * Embeds a font. |
| * @param desc FontDescriptor of the font. |
| * @return PDFStream The embedded font file |
| */ |
| public AbstractPDFStream makeFontFile(FontDescriptor desc) { |
| if (desc.getFontType() == FontType.OTHER) { |
| throw new IllegalArgumentException("Trying to embed unsupported font type: " |
| + desc.getFontType()); |
| } |
| |
| CustomFont font = getCustomFont(desc); |
| |
| InputStream in = null; |
| try { |
| Source source = font.getEmbedFileSource(); |
| if (source == null && font.getEmbedResourceName() != null) { |
| source = new StreamSource(this.getClass() |
| .getResourceAsStream(font.getEmbedResourceName())); |
| } |
| if (source == null) { |
| return null; |
| } |
| if (source instanceof StreamSource) { |
| in = ((StreamSource) source).getInputStream(); |
| } |
| if (in == null && source.getSystemId() != null) { |
| try { |
| in = new java.net.URL(source.getSystemId()).openStream(); |
| } catch (MalformedURLException e) { |
| //TODO: Why construct a new exception here, when it is not thrown? |
| new FileNotFoundException( |
| "File not found. URL could not be resolved: " |
| + e.getMessage()); |
| } |
| } |
| if (in == null) { |
| return null; |
| } |
| //Make sure the InputStream is decorated with a BufferedInputStream |
| if (!(in instanceof java.io.BufferedInputStream)) { |
| in = new java.io.BufferedInputStream(in); |
| } |
| if (in == null) { |
| return null; |
| } else { |
| try { |
| AbstractPDFStream embeddedFont; |
| if (desc.getFontType() == FontType.TYPE0) { |
| MultiByteFont mbfont = (MultiByteFont)font; |
| FontFileReader reader = new FontFileReader(in); |
| |
| TTFSubSetFile subset = new TTFSubSetFile(); |
| byte[] subsetFont = subset.readFont(reader, |
| mbfont.getTTCName(), mbfont.getUsedGlyphs()); |
| // Only TrueType CID fonts are supported now |
| |
| embeddedFont = new PDFTTFStream(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(); |
| ((PDFT1Stream)embeddedFont).setData(pfb); |
| } else { |
| byte[] file = IOUtils.toByteArray(in); |
| embeddedFont = new PDFTTFStream(file.length); |
| ((PDFTTFStream)embeddedFont).setData(file, file.length); |
| } |
| |
| /* |
| embeddedFont.getFilterList().addFilter("flate"); |
| if (getDocument().isEncryptionActive()) { |
| getDocument().applyEncryption(embeddedFont); |
| } else { |
| embeddedFont.getFilterList().addFilter("ascii-85"); |
| }*/ |
| |
| return embeddedFont; |
| } finally { |
| in.close(); |
| } |
| } |
| } catch (IOException ioe) { |
| log.error( |
| "Failed to embed font [" + desc + "] " |
| + desc.getEmbedFontName(), ioe); |
| return null; |
| } |
| } |
| |
| private CustomFont getCustomFont(FontDescriptor desc) { |
| Typeface tempFont; |
| if (desc instanceof LazyFont) { |
| tempFont = ((LazyFont)desc).getRealFont(); |
| } else { |
| tempFont = (Typeface)desc; |
| } |
| if (!(tempFont instanceof CustomFont)) { |
| throw new IllegalArgumentException( |
| "FontDescriptor must be instance of CustomFont, but is a " |
| + desc.getClass().getName()); |
| } |
| return (CustomFont)tempFont; |
| } |
| |
| |
| /* ========================= streams =================================== */ |
| |
| /** |
| * 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(); |
| obj.setDocument(getDocument()); |
| obj.getFilterList().addDefaultFilters( |
| getDocument().getFilterMap(), |
| type); |
| |
| if (add) { |
| getDocument().registerObject(obj); |
| } |
| //getDocument().applyEncryption(obj); |
| return obj; |
| } |
| |
| /** |
| * Create a PDFICCStream |
| * @see PDFImageXObject |
| * @see org.apache.fop.pdf.PDFDeviceColorSpace |
| * @return the new PDF ICC stream object |
| */ |
| public PDFICCStream makePDFICCStream() { |
| PDFICCStream iccStream = new PDFICCStream(); |
| |
| getDocument().registerObject(iccStream); |
| //getDocument().applyEncryption(iccStream); |
| return iccStream; |
| } |
| |
| /* ========================= misc. objects ============================= */ |
| |
| /** |
| * Makes a new ICCBased color space and registers it in the resource context. |
| * @param res the PDF resource context to add the shading, may be null |
| * @param explicitName the explicit name for the color space, may be null |
| * @param iccStream the ICC stream to associate with this color space |
| * @return the newly instantiated color space |
| */ |
| public PDFICCBasedColorSpace makeICCBasedColorSpace(PDFResourceContext res, |
| String explicitName, PDFICCStream iccStream) { |
| PDFICCBasedColorSpace cs = new PDFICCBasedColorSpace(explicitName, iccStream); |
| |
| getDocument().registerObject(cs); |
| |
| if (res != null) { |
| res.getPDFResources().addColorSpace(cs); |
| } else { |
| getDocument().getResources().addColorSpace(cs); |
| } |
| |
| return cs; |
| } |
| |
| /** |
| * Create a new Separation color space. |
| * @param res the resource context (may be null) |
| * @param ncs the named color space to map to a separation color space |
| * @return the newly created Separation color space |
| */ |
| public PDFSeparationColorSpace makeSeparationColorSpace(PDFResourceContext res, |
| NamedColorSpace ncs) { |
| String colorName = ncs.getColorName(); |
| final Double zero = new Double(0d); |
| final Double one = new Double(1d); |
| List theDomain = Arrays.asList(new Double[] {zero, one}); |
| List theRange = Arrays.asList(new Double[] {zero, one, zero, one, zero, one}); |
| List theCZero = Arrays.asList(new Double[] {one, one, one}); |
| List theCOne = new ArrayList(); |
| float[] comps = ncs.getRGBColor().getColorComponents(null); |
| for (int i = 0, c = comps.length; i < c; i++) { |
| theCOne.add(new Double(comps[i])); |
| } |
| PDFFunction tintFunction = makeFunction(2, theDomain, theRange, |
| theCZero, theCOne, 1.0d); |
| PDFSeparationColorSpace cs = new PDFSeparationColorSpace(colorName, tintFunction); |
| getDocument().registerObject(cs); |
| if (res != null) { |
| res.getPDFResources().addColorSpace(cs); |
| } else { |
| getDocument().getResources().addColorSpace(cs); |
| } |
| |
| return cs; |
| } |
| |
| /** |
| * 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(null, values); |
| getDocument().registerObject(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(); |
| wanted.addValues(PDFGState.DEFAULT); |
| wanted.addValues(settings); |
| |
| |
| PDFGState existing = getDocument().findGState(wanted, current); |
| if (existing != null) { |
| return existing; |
| } |
| |
| PDFGState gstate = new PDFGState(); |
| gstate.addValues(settings); |
| getDocument().registerObject(gstate); |
| return gstate; |
| } |
| |
| /** |
| * Make an annotation list object |
| * |
| * @return the annotation list object created |
| */ |
| public PDFAnnotList makeAnnotList() { |
| PDFAnnotList obj = new PDFAnnotList(); |
| getDocument().assignObjectNumber(obj); |
| return obj; |
| } |
| |
| } |