blob: f0456a53f5b74e58ea4e126ab86311ce9c1dd24f [file] [log] [blame]
/*
* 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.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
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.NamedColorSpace;
import org.apache.xmlgraphics.xmp.Metadata;
import org.apache.fop.fonts.CIDFont;
import org.apache.fop.fonts.CodePointMapping;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbeddingMode;
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.OFFontLoader;
import org.apache.fop.fonts.truetype.OTFSubSetFile;
import org.apache.fop.fonts.truetype.TTFSubSetFile;
import org.apache.fop.fonts.type1.PFBData;
import org.apache.fop.fonts.type1.PFBParser;
import org.apache.fop.fonts.type1.Type1SubsetFile;
/**
* 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;
private Map<String, PDFDPart> dparts = new HashMap<String, PDFDPart>();
/**
* 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(document, 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(getDocument());
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(getDocument());
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 Exponential interpolation function
* (for shading usually)
* @param domain List objects of Double objects.
* This is the domain of the function.
* See page 264 of the PDF 1.3 Spec.
* @param range List of Doubles that is the Range of the function.
* See page 264 of the PDF 1.3 Spec.
* @param cZero 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 cOne 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 interpolationExponentN This is the inerpolation exponent.
*
* This attribute is required.
* PDF Spec page 268
*
* @return the PDF function that was created
*/
public PDFFunction makeFunction(List domain, List range, float[] cZero, float[] cOne,
double interpolationExponentN) {
PDFFunction function = new PDFFunction(domain, range, cZero, cOne, interpolationExponentN);
function = registerFunction(function);
return function;
}
/**
* Registers a function against the document
* @param function The function to register
*/
public PDFFunction registerFunction(PDFFunction function) {
PDFFunction oldfunc = getDocument().findFunction(function);
if (oldfunc == null) {
getDocument().registerObject(function);
} else {
function = oldfunc;
}
return function;
}
/* ========================= shadings ================================== */
/**
* Registers a shading object against the document
* @param res The PDF resource context
* @param shading The shading object to be registered
*/
public PDFShading registerShading(PDFResourceContext res, PDFShading shading) {
PDFShading oldshad = getDocument().findShading(shading);
if (oldshad == null) {
getDocument().registerObject(shading);
} else {
shading = oldshad;
}
// add this shading to resources
if (res != null) {
res.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(PDFResourceContext res, int thePatternType,
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.addPattern(pattern);
}
return (pattern);
}
public PDFPattern registerPattern(PDFResourceContext res, PDFPattern pattern) {
PDFPattern oldpatt = getDocument().findPattern(pattern);
if (oldpatt == null) {
getDocument().registerObject(pattern);
} else {
pattern = oldpatt;
}
if (res != null) {
res.addPattern(pattern);
}
return pattern;
}
/* ============= 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;
}
/**
* 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);
for (Object aDestinationList : destinationList) {
PDFDestination dest = (PDFDestination) aDestinationList;
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 an internal link.
*
* @param rect the hotspot position in absolute coordinates
* @param dest the position destination
* @param isNamedDestination set to true if dest param is a named destination
* @return the new PDF link object
*/
public PDFLink makeLink(Rectangle2D rect, String dest, boolean isNamedDestination) {
PDFLink link = new PDFLink(rect);
getDocument().registerObject(link);
PDFAction pdfAction = new PDFGoTo(dest, isNamedDestination);
getDocument().registerObject(pdfAction);
link.setAction(pdfAction);
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;
}
/**
* 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) {
URI uri = getTargetUri(target);
if (uri != null) {
String scheme = uri.getScheme();
String filename = uri.getPath();
if (filename == null) {
filename = uri.getSchemeSpecificPart();
}
if (scheme == null && filename.toLowerCase().endsWith(".pdf")) {
scheme = "file";
}
if (scheme == null) {
return new PDFUri(uri.toASCIIString());
} else if (scheme.equalsIgnoreCase("embedded-file")) {
return getActionForEmbeddedFile(filename, newWindow);
} else if (scheme.equalsIgnoreCase("file")) {
if (filename.startsWith("//")) {
filename = filename.replace("/", "\\");
} else if (filename.matches("^/[A-z]:/.*")) {
filename = filename.substring(1);
}
if (filename.toLowerCase().endsWith(".pdf")) {
int page = -1;
String dest = null;
String fragment = uri.getFragment();
if (fragment != null) {
String fragmentLo = fragment.toLowerCase();
if (fragmentLo.startsWith("page=")) {
page = Integer.parseInt(fragmentLo.substring(5));
} else if (fragmentLo.startsWith("dest=")) {
dest = fragment.substring(5);
}
}
return getGoToPDFAction(filename, dest, page, newWindow);
} else {
if (uri.getQuery() != null || uri.getFragment() != null) {
return new PDFUri(uri.toASCIIString());
} else {
return getLaunchAction(filename, newWindow);
}
}
} else {
return new PDFUri(uri.toASCIIString());
}
}
return new PDFUri(target);
}
private URI getTargetUri(String target) {
URI uri;
try {
uri = new URI(target);
String scheme = uri.getScheme();
String schemeSpecificPart = uri.getSchemeSpecificPart();
String authority = uri.getAuthority();
if (scheme == null && schemeSpecificPart.matches("//.*")) {
uri = getFileUri(target);
} else if ((scheme == null) && schemeSpecificPart.matches("/.*")) {
uri = getFileUri(target);
} else if (scheme != null && scheme.matches("[A-z]")) {
uri = getFileUri(target);
} else if (scheme != null && scheme.equalsIgnoreCase("file") && authority != null) {
uri = getFileUri(target);
}
} catch (URISyntaxException e) {
uri = getFileUri(target);
}
return uri;
}
private URI getFileUri(String target) {
URI uri;
String scheme = null;
String fragment = null;
String filename = target;
int index;
String targetLo = target.toLowerCase();
if (((index = targetLo.indexOf(".pdf#page=")) > 0)
|| ((index = targetLo.indexOf(".pdf#dest=")) > 0)) {
filename = target.substring(0, index + 4);
fragment = target.substring(index + 5);
}
if (targetLo.startsWith("file://")) {
scheme = "file";
filename = filename.substring("file://".length());
} else if (targetLo.startsWith("embedded-file:")) {
scheme = "embedded-file";
filename = filename.substring("embedded-file:".length());
} else if (targetLo.startsWith("file:")) {
scheme = "file";
filename = filename.substring("file:".length());
}
try {
filename = filename.replace("\\", "/");
if (filename.matches("[A-z]:.*")) {
scheme = (scheme == null) ? "file" : scheme;
filename = "/" + filename;
} else if (filename.matches("//.*")) {
scheme = (scheme == null) ? "file" : scheme;
filename = "//" + filename;
} else if (filename.matches("/.*")) {
scheme = (scheme == null) ? "file" : scheme;
}
uri = new URI(scheme, filename, fragment);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
return uri;
}
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
PDFArray files = embeddedFiles.getNames();
PDFFileSpec fileSpec = null;
int i = 0;
while (i < files.length()) {
i++;
PDFReference ref = (PDFReference)files.get(i);
if (ref.getObject() instanceof PDFFileSpec
&& ((PDFFileSpec)ref.getObject()).getUnicodeFilename().equals(filename)) {
fileSpec = (PDFFileSpec)ref.getObject();
break;
}
i++;
}
if (fileSpec == 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(fileSpec.getFilename());
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 specification for
* the document/file to be opened with an external application.
*
* @param file the pdf file name
* @param newWindow boolean indicating whether the target should be
* displayed in a new window
* @return the pdf launch object
*/
private PDFLaunch getLaunchAction(String file, boolean newWindow) {
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, newWindow);
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,
PDFReference 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, new PDFReference(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, new PDFReference(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);
if (descriptor instanceof RefPDFFont) {
font.setObjectNumber(((RefPDFFont)descriptor).getRef().getObjectNumber());
font.setDocument(getDocument());
getDocument().addObject(font);
} else {
getDocument().registerObject(font);
}
if ((fonttype == FontType.TYPE0 || fonttype == FontType.CIDTYPE0)) {
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());
sysInfo.setDocument(document);
assert pdfdesc instanceof PDFCIDFontDescriptor;
PDFCIDFont cidFont = new PDFCIDFont(subsetFontName, cidMetrics.getCIDType(),
cidMetrics.getDefaultWidth(), getFontWidths(cidMetrics), sysInfo,
(PDFCIDFontDescriptor) pdfdesc);
getDocument().registerObject(cidFont);
PDFCMap cmap;
if (cidMetrics instanceof MultiByteFont && ((MultiByteFont) cidMetrics).getCmapStream() != null) {
cmap = new PDFCMap("fop-ucs-H", null);
try {
cmap.setData(IOUtils.toByteArray(((MultiByteFont) cidMetrics).getCmapStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
cmap = new PDFToUnicodeCMap(cidMetrics.getCIDSet().getChars(), "fop-ucs-H",
new PDFCIDSystemInfo("Adobe", "Identity", 0), false);
}
getDocument().registerObject(cmap);
assert font instanceof PDFFontType0;
((PDFFontType0)font).setCMAP(cmap);
((PDFFontType0)font).setDescendantFonts(cidFont);
} else if (fonttype == FontType.TYPE1C
&& (metrics instanceof LazyFont || metrics instanceof MultiByteFont)) {
handleType1CFont(pdfdesc, font, metrics, fontname, basefont, descriptor);
} else {
assert font instanceof PDFFontNonBase14;
PDFFontNonBase14 nonBase14 = (PDFFontNonBase14)font;
nonBase14.setDescriptor(pdfdesc);
SingleByteFont singleByteFont;
if (metrics instanceof LazyFont) {
singleByteFont = (SingleByteFont)((LazyFont)metrics).getRealFont();
} else {
singleByteFont = (SingleByteFont)metrics;
}
int firstChar = 0;
int lastChar = 0;
boolean defaultChars = false;
if (singleByteFont.getEmbeddingMode() == EmbeddingMode.SUBSET) {
Map<Integer, Integer> usedGlyphs = singleByteFont.getUsedGlyphs();
if (fonttype == FontType.TYPE1 && usedGlyphs.size() > 0) {
SortedSet<Integer> keys = new TreeSet<Integer>(usedGlyphs.keySet());
keys.remove(0);
if (keys.size() > 0) {
firstChar = keys.first();
lastChar = keys.last();
int[] newWidths = new int[(lastChar - firstChar) + 1];
for (int i = firstChar; i < lastChar + 1; i++) {
if (usedGlyphs.get(i) != null) {
if (i - singleByteFont.getFirstChar() < metrics.getWidths().length) {
newWidths[i - firstChar] = metrics.getWidths()[i
- singleByteFont.getFirstChar()];
} else {
defaultChars = true;
break;
}
} else {
newWidths[i - firstChar] = 0;
}
}
nonBase14.setWidthMetrics(firstChar,
lastChar,
new PDFArray(null, newWidths));
}
} else {
defaultChars = true;
}
} else {
defaultChars = true;
}
if (defaultChars) {
firstChar = singleByteFont.getFirstChar();
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 if (mapping.getName().equals("FOPPDFEncoding")) {
if (fonttype == FontType.TRUETYPE) {
font.setEncoding(encoding);
} else {
String[] charNameMap = mapping.getCharNameMap();
char[] intmap = mapping.getUnicodeCharMap();
PDFArray differences = new PDFArray();
int len = intmap.length;
if (charNameMap.length < len) {
len = charNameMap.length;
}
int last = 0;
for (int i = 0; i < len; i++) {
if (intmap[i] - 1 != last) {
differences.add(intmap[i]);
}
last = intmap[i];
differences.add(new PDFName(charNameMap[i]));
}
PDFEncoding pdfEncoding = new PDFEncoding(singleByteFont.getEncodingName());
getDocument().registerObject(pdfEncoding);
pdfEncoding.setDifferences(differences);
font.setEncoding(pdfEncoding);
if (mapping.getUnicodeCharMap() != null) {
generateToUnicodeCmap(nonBase14, mapping);
}
}
} 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 handleType1CFont(PDFFontDescriptor pdfdesc, PDFFont font, FontMetrics metrics, String fontname,
String basefont, FontDescriptor descriptor) {
PDFFontNonBase14 nonBase14 = (PDFFontNonBase14)font;
nonBase14.setDescriptor(pdfdesc);
MultiByteFont singleByteFont;
if (metrics instanceof LazyFont) {
singleByteFont = (MultiByteFont)((LazyFont)metrics).getRealFont();
} else {
singleByteFont = (MultiByteFont)metrics;
}
Map<Integer, Integer> usedGlyphs = singleByteFont.getUsedGlyphs();
SortedSet<Integer> keys = new TreeSet<Integer>(usedGlyphs.keySet());
keys.remove(0);
int count = keys.size();
Iterator<String> usedGlyphNames = singleByteFont.getUsedGlyphNames().values().iterator();
count = setupFontMetrics(nonBase14, pdfdesc, usedGlyphNames, 0, count, metrics);
List<PDFFontNonBase14> additionalEncodings = addAdditionalEncodings(metrics, descriptor, fontname, basefont);
for (int j = 0; j < additionalEncodings.size(); j++) {
PDFFontNonBase14 additional = additionalEncodings.get(j);
int start = 256 * (j + 1);
count = setupFontMetrics(additional, pdfdesc, usedGlyphNames, start, count, metrics);
}
}
private int setupFontMetrics(PDFFontNonBase14 font, PDFFontDescriptor pdfdesc,
Iterator<String> usedGlyphNames, int start, int count, FontMetrics metrics) {
font.setDescriptor(pdfdesc);
PDFArray differences = new PDFArray();
int firstChar = 0;
differences.add(firstChar);
int lastChar = Math.min(count, 255);
int[] newWidths = new int[lastChar + 1];
for (int i = 0; i < newWidths.length; i++) {
newWidths[i] = metrics.getWidth(start + i, 1);
differences.add(new PDFName(usedGlyphNames.next()));
count--;
}
font.setWidthMetrics(firstChar,
lastChar,
new PDFArray(null, newWidths));
PDFEncoding pdfEncoding = new PDFEncoding("WinAnsiEncoding");
getDocument().registerTrailerObject(pdfEncoding);
pdfEncoding.setDifferences(differences);
font.setEncoding(pdfEncoding);
return count;
}
private List<PDFFontNonBase14> addAdditionalEncodings(FontMetrics metrics, FontDescriptor descriptor,
String fontname, String basefont) {
List<PDFFontNonBase14> additionalEncodings = new ArrayList<PDFFontNonBase14>();
FontType fonttype = metrics.getFontType();
if (descriptor != null && (fonttype != FontType.TYPE0)) {
CustomFont singleByteFont;
if (metrics instanceof LazyFont) {
singleByteFont = (CustomFont)((LazyFont)metrics).getRealFont();
} else {
singleByteFont = (CustomFont)metrics;
}
//Handle additional encodings (characters outside the primary encoding)
if (singleByteFont.hasAdditionalEncodings()) {
for (int i = additionalEncodings.size(),
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);
getDocument().registerObject(addFont);
getDocument().getResources().addFont(addFont);
additionalEncodings.add(addFont);
}
}
}
return additionalEncodings;
}
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 fontName ...
* @return the PDF Encoding dictionary (or a String with the predefined encoding)
*/
public Object createPDFEncoding(SingleByteEncoding encoding, String fontName) {
return PDFEncoding.createPDFEncoding(encoding, fontName);
}
private PDFWArray getFontWidths(CIDFont cidFont) {
// Create widths for reencoded chars
PDFWArray warray = new PDFWArray();
if (cidFont instanceof MultiByteFont && ((MultiByteFont)cidFont).getWidthsMap() != null) {
Map<Integer, Integer> map = ((MultiByteFont)cidFont).getWidthsMap();
for (Map.Entry<Integer, Integer> cid : map.entrySet()) {
warray.addEntry(cid.getKey(), new int[] {cid.getValue()});
}
// List<Integer> l = new ArrayList<Integer>(map.keySet());
// for (int i=0; i<map.size(); i++) {
// int cid = l.get(i);
// List<Integer> cids = new ArrayList<Integer>();
// cids.add(map.get(cid));
// while (i<map.size()-1 && l.get(i) + 1 == l.get(i + 1)) {
// cids.add(map.get(l.get(i + 1)));
// i++;
// }
// int[] cidsints = new int[cids.size()];
// for (int j=0; j<cids.size(); j++) {
// cidsints[j] = cids.get(j);
// }
// warray.addEntry(cid, cidsints);
// }
} else {
int[] widths = cidFont.getCIDSet().getWidths();
warray.addEntry(0, widths);
}
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 || desc.getFontType() == FontType.CIDTYPE0) {
// 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(fontPrefix + 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, fontPrefix);
if (stream != null) {
descriptor.setFontFile(desc.getFontType(), stream);
getDocument().registerObject(stream);
}
CustomFont font = getCustomFont(desc);
if (font instanceof CIDFont && document.getProfile().pdfAMode.getPart() < 2) {
CIDFont cidFont = (CIDFont)font;
buildCIDSet(descriptor, cidFont);
}
}
return descriptor;
}
private void buildCIDSet(PDFFontDescriptor descriptor, CIDFont cidFont) {
BitSet cidSet = cidFont.getCIDSet().getGlyphIndices();
PDFStream pdfStream = makeStream(null, true);
ByteArrayOutputStream baout = new ByteArrayOutputStream(cidSet.length() / 8 + 1);
int value = 0;
for (int i = 0, c = cidSet.length(); i < c; i++) {
int shift = i % 8;
boolean b = cidSet.get(i);
if (b) {
value |= 1 << 7 - shift;
}
if (shift == 7) {
baout.write(value);
value = 0;
}
}
baout.write(value);
try {
pdfStream.setData(baout.toByteArray());
descriptor.setCIDSet(pdfStream);
} catch (IOException ioe) {
log.error(
"Failed to write CIDSet [" + cidFont + "] "
+ cidFont.getEmbedFontName(), ioe);
} finally {
IOUtils.closeQuietly(baout);
}
}
/**
* Embeds a font.
* @param desc FontDescriptor of the font.
* @return PDFStream The embedded font file
*/
public AbstractPDFStream makeFontFile(FontDescriptor desc, String fontPrefix) {
if (desc.getFontType() == FontType.OTHER) {
throw new IllegalArgumentException("Trying to embed unsupported font type: "
+ desc.getFontType());
}
CustomFont font = getCustomFont(desc);
InputStream in = null;
try {
in = font.getInputStream();
if (in == null) {
return null;
}
AbstractPDFStream embeddedFont = null;
if (desc.getFontType() == FontType.TYPE0) {
MultiByteFont mbfont = (MultiByteFont) font;
FontFileReader reader = new FontFileReader(in);
byte[] fontBytes;
String header = OFFontLoader.readHeader(reader);
boolean isCFF = mbfont.isOTFFile();
if (font.getEmbeddingMode() == EmbeddingMode.FULL) {
fontBytes = reader.getAllBytes();
if (isCFF) {
//Ensure version 1.6 for full OTF CFF embedding
document.setPDFVersion(Version.V1_6);
}
} else {
fontBytes = getFontSubsetBytes(reader, mbfont, header, fontPrefix, desc,
isCFF);
}
embeddedFont = getFontStream(font, fontBytes, isCFF);
} else if (desc.getFontType() == FontType.TYPE1) {
if (font.getEmbeddingMode() != EmbeddingMode.SUBSET) {
embeddedFont = fullyEmbedType1Font(in);
} else {
assert font instanceof SingleByteFont;
SingleByteFont sbfont = (SingleByteFont)font;
Type1SubsetFile pfbFile = new Type1SubsetFile();
byte[] subsetData = pfbFile.createSubset(in, sbfont);
InputStream subsetStream = new ByteArrayInputStream(subsetData);
PFBParser parser = new PFBParser();
PFBData pfb = parser.parsePFB(subsetStream);
embeddedFont = new PDFT1Stream();
((PDFT1Stream) embeddedFont).setData(pfb);
}
} else if (desc.getFontType() == FontType.TYPE1C) {
if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) {
FontFileReader reader = new FontFileReader(in);
String header = OFFontLoader.readHeader(reader);
byte[] fontBytes = getFontSubsetBytes(reader, (MultiByteFont) font, header, fontPrefix, desc, true);
embeddedFont = getFontStream(font, fontBytes, true);
} else {
byte[] file = IOUtils.toByteArray(in);
PDFCFFStream embeddedFont2 = new PDFCFFStream("Type1C");
embeddedFont2.setData(file);
return embeddedFont2;
}
} else if (desc.getFontType() == FontType.CIDTYPE0) {
byte[] file = IOUtils.toByteArray(in);
PDFCFFStream embeddedFont2 = new PDFCFFStream("CIDFontType0C");
embeddedFont2.setData(file);
return embeddedFont2;
} 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;
} catch (IOException ioe) {
log.error("Failed to embed font [" + desc + "] " + desc.getEmbedFontName(), ioe);
return null;
} finally {
if (in != null) {
IOUtils.closeQuietly(in);
}
}
}
private AbstractPDFStream fullyEmbedType1Font(InputStream in) throws IOException {
PFBParser parser = new PFBParser();
PFBData pfb = parser.parsePFB(in);
AbstractPDFStream embeddedFont = new PDFT1Stream();
((PDFT1Stream) embeddedFont).setData(pfb);
return embeddedFont;
}
private byte[] getFontSubsetBytes(FontFileReader reader, MultiByteFont mbfont, String header,
String fontPrefix, FontDescriptor desc, boolean isCFF) throws IOException {
if (isCFF) {
OTFSubSetFile otfFile = new OTFSubSetFile();
otfFile.readFont(reader, fontPrefix + desc.getEmbedFontName(), mbfont);
return otfFile.getFontSubset();
} else {
TTFSubSetFile otfFile = new TTFSubSetFile();
otfFile.readFont(reader, mbfont.getTTCName(), header, mbfont.getUsedGlyphs());
return otfFile.getFontSubset();
}
}
private AbstractPDFStream getFontStream(CustomFont font, byte[] fontBytes, boolean isCFF)
throws IOException {
AbstractPDFStream embeddedFont;
if (isCFF) {
embeddedFont = new PDFCFFStreamType0C(font);
((PDFCFFStreamType0C) embeddedFont).setData(fontBytes, fontBytes.length);
} else {
embeddedFont = new PDFTTFStream(fontBytes.length);
((PDFTTFStream) embeddedFont).setData(fontBytes, fontBytes.length);
}
return embeddedFont;
}
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 = 0d;
final Double one = 1d;
List domain = Arrays.asList(new Double[] {zero, one});
List range = Arrays.asList(new Double[] {zero, one, zero, one, zero, one});
float[] cZero = new float[] {1f, 1f, 1f};
float[] cOne = ncs.getRGBColor().getColorComponents(null);
PDFFunction tintFunction = makeFunction(domain, range, cZero, cOne, 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;
}
public PDFLayer makeLayer(String id) {
PDFLayer layer = new PDFLayer(id);
getDocument().registerObject(layer);
return layer;
}
public PDFSetOCGStateAction makeSetOCGStateAction(String id) {
PDFSetOCGStateAction action = new PDFSetOCGStateAction(id);
getDocument().registerObject(action);
return action;
}
public PDFTransitionAction makeTransitionAction(String id) {
PDFTransitionAction action = new PDFTransitionAction(id);
getDocument().registerObject(action);
return action;
}
public PDFNavigator makeNavigator(String id) {
PDFNavigator navigator = new PDFNavigator(id);
getDocument().registerObject(navigator);
return navigator;
}
public void makeDPart(PDFPage page, String pageMasterName) {
PDFDPartRoot root = getDocument().getRoot().getDPartRoot();
PDFDPart dPart;
if (dparts.containsKey(pageMasterName)) {
dPart = dparts.get(pageMasterName);
} else {
dPart = new PDFDPart(root.dpart);
root.add(dPart);
getDocument().registerTrailerObject(dPart);
dparts.put(pageMasterName, dPart);
}
dPart.addPage(page);
page.put("DPart", dPart);
}
public PDFDPartRoot makeDPartRoot() {
PDFDPartRoot pdfdPartRoot = new PDFDPartRoot(getDocument());
getDocument().registerTrailerObject(pdfdPartRoot);
return pdfdPartRoot;
}
}