diff options
author | Jeremias Maerki <jeremias@apache.org> | 2003-03-11 13:05:43 +0000 |
---|---|---|
committer | Jeremias Maerki <jeremias@apache.org> | 2003-03-11 13:05:43 +0000 |
commit | 1e5d512c216d329effa693b91ef64652945def6a (patch) | |
tree | 5bd3521ee8121eade7bf1909ceaf29cfc0263fd1 /src/java/org/apache/fop/render/pdf | |
parent | 73c824d39411bf11ad0c2f4e1c57cd9c484665f9 (diff) | |
download | xmlgraphics-fop-1e5d512c216d329effa693b91ef64652945def6a.tar.gz xmlgraphics-fop-1e5d512c216d329effa693b91ef64652945def6a.zip |
Moved sources from src/org/** to src/java/org/**
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@196061 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render/pdf')
-rw-r--r-- | src/java/org/apache/fop/render/pdf/CTMHelper.java | 145 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/EmbedFontInfo.java | 112 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/FontReader.java | 321 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/FontSetup.java | 270 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/FontTriplet.java | 96 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/FopPDFImage.java | 307 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/PDFRenderer.java | 1361 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/PDFXMLHandler.java | 338 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/pdf/package.html | 6 |
9 files changed, 2956 insertions, 0 deletions
diff --git a/src/java/org/apache/fop/render/pdf/CTMHelper.java b/src/java/org/apache/fop/render/pdf/CTMHelper.java new file mode 100644 index 000000000..4a3103a01 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/CTMHelper.java @@ -0,0 +1,145 @@ +/* + * $Id: CTMHelper.java,v 1.2 2003/03/07 09:46:32 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +import org.apache.fop.area.CTM; + +/** + * CTMHelper converts FOP transformation matrixis to those + * suitable for use by the PDFRender. The e and f elements + * of the matrix will be divided by 1000 as FOP uses millipoints + * as it's default user space and PDF uses points. + * + * @see org.apache.fop.area.CTM + * + * @author <a href="kevin@rocketred.com>Kevin O'Neill</a> + */ +public final class CTMHelper { + /** + * <p>Converts the sourceMatrix to a string for use in the PDFRender cm operations.</p> + * <p>For example: + * <pre> + * org.apache.fop.area.CTM ctm = + * new org.apache.fop.area.CTM(1.0, 0.0, 0.0, 1.0, 1000.0, 1000.0); + * String pdfMatrix = org.apache.fop.render.pdf.CTMHelper.toPDFString(ctm); + * </pre> + * will return the string "<code>1.0 0.0 0.0 1.0 1.0 1.0</code>". + * + * @param sourceMatrix - The matrix to convert. + * + * @return a space seperated string containing the matrix elements. + */ + public static String toPDFString(CTM sourceMatrix) { + if (null == sourceMatrix) { + throw new NullPointerException("sourceMatrix must not be null"); + } + + final double matrix[] = toPDFArray(sourceMatrix); + + return matrix[0] + " " + matrix[1] + " " + + matrix[2] + " " + matrix[3] + " " + + matrix[4] + " " + matrix[5]; + } + + /** + * <p>Creates a new CTM based in the sourceMatrix.</p> + * <p>For example: + * <pre> + * org.apache.fop.area.CTM inCTM = + * new org.apache.fop.area.CTM(1.0, 0.0, 0.0, 1.0, 1000.0, 1000.0); + * org.apache.fop.area.CTM outCTM = + * org.apache.fop.render.pdf.CTMHelper.toPDFCTM(ctm); + * </pre> + * will return a new CTM where a == 1.0, b == 0.0, c == 0.0, d == 1.0, e == 1.0 and f == 1.0. + * + * @param sourceMatrix - The matrix to convert. + * + * @return a new converted matrix. + */ + public static CTM toPDFCTM(CTM sourceMatrix) { + if (null == sourceMatrix) { + throw new NullPointerException("sourceMatrix must not be null"); + } + + final double matrix[] = toPDFArray(sourceMatrix); + + return new CTM(matrix[0], matrix[1], matrix[2], matrix[3], + matrix[4], matrix[5]); + } + + /** + * <p>Creates an array of six doubles from the source CTM.</p> + * <p>For example: + * <pre> + * org.apache.fop.area.CTM inCTM = + * new org.apache.fop.area.CTM(1.0, 0.0, 0.0, 1.0, 1000.0, 1000.0); + * double matrix[] = org.apache.fop.render.pdf.CTMHelper.toPDFArray(ctm); + * </pre> + * will return a new array where matrix[0] == 1.0, matrix[1] == 0.0, + * matrix[2] == 0.0, matrix[3] == 1.0, + * matrix[4] == 1.0 and matrix[5] == 1.0. + * + * @param sourceMatrix - The matrix to convert. + * @return an array of doubles containing the converted matrix. + */ + public static double[] toPDFArray(CTM sourceMatrix) { + if (null == sourceMatrix) { + throw new NullPointerException("sourceMatrix must not be null"); + } + + final double matrix[] = sourceMatrix.toArray(); + + return new double[]{matrix[0], matrix[1], matrix[2], matrix[3], + matrix[4] / 1000.0, matrix[5] / 1000.0}; + } + +} + diff --git a/src/java/org/apache/fop/render/pdf/EmbedFontInfo.java b/src/java/org/apache/fop/render/pdf/EmbedFontInfo.java new file mode 100644 index 000000000..a78dbd8e0 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/EmbedFontInfo.java @@ -0,0 +1,112 @@ +/* + * $Id: EmbedFontInfo.java,v 1.4 2003/03/07 09:46:32 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +import java.util.List; + +/** + * FontInfo contains meta information on fonts (where is the metrics file etc.) + */ +public class EmbedFontInfo { + + private String metricsFile, embedFile; + private boolean kerning; + private List fontTriplets; + + /** + * Main constructor + * @param metricsFile Path to the xml file containing font metrics + * @param kerning True if kerning should be enabled + * @param fontTriplets List of font triplets to associate with this font + * @param embedFile Path to the embeddable font file (may be null) + */ + public EmbedFontInfo(String metricsFile, boolean kerning, + List fontTriplets, String embedFile) { + this.metricsFile = metricsFile; + this.embedFile = embedFile; + this.kerning = kerning; + this.fontTriplets = fontTriplets; + } + + /** + * Returns the path to the metrics file + * @return the metrics file path + */ + public String getMetricsFile() { + return metricsFile; + } + + /** + * Returns the path to the embeddable font file + * @return the font file path + */ + public String getEmbedFile() { + return embedFile; + } + + /** + * Determines if kerning is enabled + * @return True if enabled + */ + public boolean getKerning() { + return kerning; + } + + /** + * Returns the list of font triplets associated with this font. + * @return List of font triplets + */ + public List getFontTriplets() { + return fontTriplets; + } + +} + diff --git a/src/java/org/apache/fop/render/pdf/FontReader.java b/src/java/org/apache/fop/render/pdf/FontReader.java new file mode 100644 index 000000000..375c4e56a --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/FontReader.java @@ -0,0 +1,321 @@ +/* + * $Id: FontReader.java,v 1.8 2003/03/07 09:46:32 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +//Java +import java.util.List; +import java.util.Map; +import java.io.IOException; + +//SAX +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import org.xml.sax.Locator; +import org.xml.sax.Attributes; +import org.xml.sax.helpers.DefaultHandler; + +//FOP +import org.apache.fop.apps.FOPException; +import org.apache.fop.fonts.BFEntry; +import org.apache.fop.fonts.CIDFontType; +import org.apache.fop.fonts.CustomFont; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontType; +import org.apache.fop.fonts.MultiByteFont; +import org.apache.fop.fonts.SingleByteFont; + +/** + * Class for reading a metric.xml file and creating a font object. + * Typical usage: + * <pre> + * FontReader reader = new FontReader(<path til metrics.xml>); + * reader.setFontEmbedPath(<path to a .ttf or .pfb file or null to diable embedding>); + * reader.useKerning(true); + * Font f = reader.getFont(); + * </pre> + */ +public class FontReader extends DefaultHandler { + + private Locator locator = null; + private boolean isCID = false; + private CustomFont returnFont = null; + private MultiByteFont multiFont = null; + private SingleByteFont singleFont = null; + private StringBuffer text = new StringBuffer(); + + private List cidWidths = null; + private int cidWidthIndex = 0; + + private Map currentKerning = null; + + private List bfranges = null; + + private void createFont(String path) throws FOPException { + XMLReader parser = null; + + try { + parser = javax.xml.parsers.SAXParserFactory.newInstance().newSAXParser().getXMLReader(); + } catch (Exception e) { + throw new FOPException(e); + } + if (parser == null) { + throw new FOPException("Unable to create SAX parser"); + } + + try { + parser.setFeature("http://xml.org/sax/features/namespace-prefixes", + false); + } catch (SAXException e) { + throw new FOPException("You need a SAX parser which supports SAX version 2", + e); + } + + parser.setContentHandler(this); + + try { + parser.parse(path); + } catch (SAXException e) { + throw new FOPException(e); + } catch (IOException e) { + throw new FOPException(e); + } + + } + + /** + * Sets the path to embed a font. A null value disables font embedding. + * @param path URI for the embeddable file + */ + public void setFontEmbedPath(String path) { + returnFont.setEmbedFileName(path); + } + + /** + * Enable/disable use of kerning for the font + * @param enabled true to enable kerning, false to disable + */ + public void setKerningEnabled(boolean enabled) { + returnFont.setKerningEnabled(enabled); + } + + + /** + * Get the generated font object + * @return the font + */ + public Font getFont() { + return returnFont; + } + + /** + * Construct a FontReader object from a path to a metric.xml file + * and read metric data + * @param path URI to the font metric file + * @throws FOPException if loading the font fails + */ + public FontReader(String path) throws FOPException { + createFont(path); + } + + /** + * @see org.xml.sax.ContentHandler#startDocument() + */ + public void startDocument() { + } + + /** + * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator) + */ + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + /** + * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) + */ + public void startElement(String uri, String localName, String qName, + Attributes attributes) { + if (localName.equals("font-metrics")) { + if ("TYPE0".equals(attributes.getValue("type"))) { + multiFont = new MultiByteFont(); + returnFont = multiFont; + isCID = true; + } else if ("TRUETYPE".equals(attributes.getValue("type"))) { + singleFont = new SingleByteFont(); + singleFont.setFontType(FontType.TRUETYPE); + returnFont = singleFont; + isCID = false; + } else { + singleFont = new SingleByteFont(); + singleFont.setFontType(FontType.TYPE1); + returnFont = singleFont; + isCID = false; + } + } else if ("embed".equals(localName)) { + returnFont.setEmbedFileName(attributes.getValue("file")); + returnFont.setEmbedResourceName(attributes.getValue("class")); + } else if ("cid-widths".equals(localName)) { + cidWidthIndex = getInt(attributes.getValue("start-index")); + cidWidths = new java.util.ArrayList(); + } else if ("kerning".equals(localName)) { + currentKerning = new java.util.HashMap(); + returnFont.putKerningEntry(new Integer(attributes.getValue("kpx1")), + currentKerning); + } else if ("bfranges".equals(localName)) { + bfranges = new java.util.ArrayList(); + } else if ("bf".equals(localName)) { + BFEntry entry = new BFEntry(getInt(attributes.getValue("us")), + getInt(attributes.getValue("ue")), + getInt(attributes.getValue("gi"))); + bfranges.add(entry); + } else if ("wx".equals(localName)) { + cidWidths.add(new Integer(attributes.getValue("w"))); + } else if ("widths".equals(localName)) { + //singleFont.width = new int[256]; + } else if ("char".equals(localName)) { + try { + singleFont.setWidth(Integer.parseInt(attributes.getValue("idx")), + Integer.parseInt(attributes.getValue("wdt"))); + } catch (NumberFormatException ne) { + System.out.println("Malformed width in metric file: " + + ne.getMessage()); + } + } else if ("pair".equals(localName)) { + currentKerning.put(new Integer(attributes.getValue("kpx2")), + new Integer(attributes.getValue("kern"))); + } + } + + private int getInt(String str) { + int ret = 0; + try { + ret = Integer.parseInt(str); + } catch (Exception e) { + /**@todo log this exception */ + } + return ret; + } + + /** + * @see org.xml.sax.ContentHandler#endElement(String, String, String) + */ + public void endElement(String uri, String localName, String qName) { + if ("font-name".equals(localName)) { + returnFont.setFontName(text.toString()); + } else if ("ttc-name".equals(localName) && isCID) { + multiFont.setTTCName(text.toString()); + } else if ("cap-height".equals(localName)) { + returnFont.setCapHeight(getInt(text.toString())); + } else if ("x-height".equals(localName)) { + returnFont.setXHeight(getInt(text.toString())); + } else if ("ascender".equals(localName)) { + returnFont.setAscender(getInt(text.toString())); + } else if ("descender".equals(localName)) { + returnFont.setDescender(getInt(text.toString())); + } else if ("left".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[0] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("bottom".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[1] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("right".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[2] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("top".equals(localName)) { + int[] bbox = returnFont.getFontBBox(); + bbox[3] = getInt(text.toString()); + returnFont.setFontBBox(bbox); + } else if ("first-char".equals(localName)) { + returnFont.setFirstChar(getInt(text.toString())); + } else if ("last-char".equals(localName)) { + returnFont.setLastChar(getInt(text.toString())); + } else if ("flags".equals(localName)) { + returnFont.setFlags(getInt(text.toString())); + } else if ("stemv".equals(localName)) { + returnFont.setStemV(getInt(text.toString())); + } else if ("italic-angle".equals(localName)) { + returnFont.setItalicAngle(getInt(text.toString())); + } else if ("missing-width".equals(localName)) { + returnFont.setMissingWidth(getInt(text.toString())); + } else if ("cid-type".equals(localName)) { + multiFont.setCIDType(CIDFontType.byName(text.toString())); + } else if ("default-width".equals(localName)) { + multiFont.setDefaultWidth(getInt(text.toString())); + } else if ("cid-widths".equals(localName)) { + int[] wds = new int[cidWidths.size()]; + int j = 0; + for (int count = 0; count < cidWidths.size(); count++) { + Integer i = (Integer)cidWidths.get(count); + wds[j++] = i.intValue(); + } + + multiFont.addCIDWidthEntry(cidWidthIndex, wds); + multiFont.setWidthArray(wds); + + } else if ("bfranges".equals(localName)) { + multiFont.setBFEntries((BFEntry[])bfranges.toArray(new BFEntry[0])); + } + text.setLength(0); //Reset text buffer (see characters()) + } + + /** + * @see org.xml.sax.ContentHandler#characters(char[], int, int) + */ + public void characters(char[] ch, int start, int length) { + text.append(ch, start, length); + } + +} + + diff --git a/src/java/org/apache/fop/render/pdf/FontSetup.java b/src/java/org/apache/fop/render/pdf/FontSetup.java new file mode 100644 index 000000000..684fa3652 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/FontSetup.java @@ -0,0 +1,270 @@ +/* + * $Id: FontSetup.java,v 1.22 2003/03/07 09:46:32 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +// FOP +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontDescriptor; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.layout.FontInfo; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFResources; +// FOP (base 14 fonts) +import org.apache.fop.fonts.base14.Helvetica; +import org.apache.fop.fonts.base14.HelveticaBold; +import org.apache.fop.fonts.base14.HelveticaOblique; +import org.apache.fop.fonts.base14.HelveticaBoldOblique; +import org.apache.fop.fonts.base14.TimesRoman; +import org.apache.fop.fonts.base14.TimesBold; +import org.apache.fop.fonts.base14.TimesItalic; +import org.apache.fop.fonts.base14.TimesBoldItalic; +import org.apache.fop.fonts.base14.Courier; +import org.apache.fop.fonts.base14.CourierBold; +import org.apache.fop.fonts.base14.CourierOblique; +import org.apache.fop.fonts.base14.CourierBoldOblique; +import org.apache.fop.fonts.base14.Symbol; +import org.apache.fop.fonts.base14.ZapfDingbats; + +// Java +import java.util.Map; +import java.util.Iterator; +import java.util.List; + +/** + * sets up the PDF fonts. + * + * Assigns the font (with metrics) to internal names like "F1" and + * assigns family-style-weight triplets to the fonts + */ +public class FontSetup { + + /** + * Sets up the font info object. + * + * Adds metrics for basic fonts and useful family-style-weight + * triplets for lookup. + * + * @param fontInfo the font info object to set up + * @param embedList ??? + */ + public static void setup(FontInfo fontInfo, List embedList) { + + fontInfo.addMetrics("F1", new Helvetica()); + fontInfo.addMetrics("F2", new HelveticaOblique()); + fontInfo.addMetrics("F3", new HelveticaBold()); + fontInfo.addMetrics("F4", new HelveticaBoldOblique()); + fontInfo.addMetrics("F5", new TimesRoman()); + fontInfo.addMetrics("F6", new TimesItalic()); + fontInfo.addMetrics("F7", new TimesBold()); + fontInfo.addMetrics("F8", new TimesBoldItalic()); + fontInfo.addMetrics("F9", new Courier()); + fontInfo.addMetrics("F10", new CourierOblique()); + fontInfo.addMetrics("F11", new CourierBold()); + fontInfo.addMetrics("F12", new CourierBoldOblique()); + fontInfo.addMetrics("F13", new Symbol()); + fontInfo.addMetrics("F14", new ZapfDingbats()); + + // Custom type 1 fonts step 1/2 + // fontInfo.addMetrics("F15", new OMEP()); + // fontInfo.addMetrics("F16", new GaramondLightCondensed()); + // fontInfo.addMetrics("F17", new BauerBodoniBoldItalic()); + + /* any is treated as serif */ + fontInfo.addFontProperties("F5", "any", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "any", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "any", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F7", "any", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "any", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "any", "oblique", FontInfo.BOLD); + + fontInfo.addFontProperties("F1", "sans-serif", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F2", "sans-serif", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F3", "sans-serif", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F4", "sans-serif", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F5", "serif", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "serif", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "serif", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F7", "serif", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "serif", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "serif", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F9", "monospace", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F10", "monospace", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F11", "monospace", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F12", "monospace", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F12", "monospace", "italic", FontInfo.BOLD); + + fontInfo.addFontProperties("F1", "Helvetica", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F2", "Helvetica", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F3", "Helvetica", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F4", "Helvetica", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F5", "Times", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "Times", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "Times", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F7", "Times", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "Times", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "Times", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F9", "Courier", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F10", "Courier", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F11", "Courier", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F12", "Courier", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F12", "Courier", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F13", "Symbol", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F14", "ZapfDingbats", "normal", FontInfo.NORMAL); + + // Custom type 1 fonts step 2/2 + // fontInfo.addFontProperties("F15", "OMEP", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F16", "Garamond-LightCondensed", "normal", FontInfo.NORMAL); + // fontInfo.addFontProperties("F17", "BauerBodoni", "italic", FontInfo.BOLD); + + /* for compatibility with PassiveTex */ + fontInfo.addFontProperties("F5", "Times-Roman", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "Times-Roman", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F7", "Times-Roman", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "Times-Roman", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F5", "Times Roman", "normal", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "oblique", FontInfo.NORMAL); + fontInfo.addFontProperties("F6", "Times Roman", "italic", FontInfo.NORMAL); + fontInfo.addFontProperties("F7", "Times Roman", "normal", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "oblique", FontInfo.BOLD); + fontInfo.addFontProperties("F8", "Times Roman", "italic", FontInfo.BOLD); + fontInfo.addFontProperties("F9", "Computer-Modern-Typewriter", + "normal", FontInfo.NORMAL); + + /* Add configured fonts */ + addConfiguredFonts(fontInfo, embedList, 15); + } + + /** + * Add fonts from configuration file starting with + * internalnames F<num> + * @param fontInfo the font info object to set up + * @param fontInfos ??? + * @param num starting index for internal font numbering + */ + public static void addConfiguredFonts(FontInfo fontInfo, List fontInfos, int num) { + if (fontInfos == null) { + return; //No fonts to process + } + + String internalName = null; + //FontReader reader = null; + + for (int i = 0; i < fontInfos.size(); i++) { + EmbedFontInfo configFontInfo = (EmbedFontInfo)fontInfos.get(i); + + String metricsFile = configFontInfo.getMetricsFile(); + if (metricsFile != null) { + internalName = "F" + num; + num++; + /* + reader = new FontReader(metricsFile); + reader.useKerning(configFontInfo.getKerning()); + reader.setFontEmbedPath(configFontInfo.getEmbedFile()); + fontInfo.addMetrics(internalName, reader.getFont()); + */ + LazyFont font = new LazyFont(configFontInfo.getEmbedFile(), + metricsFile, + configFontInfo.getKerning()); + fontInfo.addMetrics(internalName, font); + + List triplets = configFontInfo.getFontTriplets(); + for (int c = 0; c < triplets.size(); c++) { + FontTriplet triplet = (FontTriplet)triplets.get(c); + + int weight = 400; + try { + weight = Integer.parseInt(triplet.getWeight()); + weight = ((int)weight / 100) * 100; + weight = Math.min(weight, 100); + weight = Math.max(weight, 900); + } catch (NumberFormatException nfe) { + /**@todo log this exception */ + } + fontInfo.addFontProperties(internalName, + triplet.getName(), + triplet.getStyle(), + weight); + } + } + } + } + + /** + * Add the fonts in the font info to the PDF document + * + * @param doc PDF document to add fonts to + * @param resources PDFResources object to attach the font to + * @param fontInfo font info object to get font information from + */ + public static void addToResources(PDFDocument doc, PDFResources resources, FontInfo fontInfo) { + Map fonts = fontInfo.getUsedFonts(); + Iterator e = fonts.keySet().iterator(); + while (e.hasNext()) { + String f = (String)e.next(); + Font font = (Font)fonts.get(f); + FontDescriptor desc = null; + if (font instanceof FontDescriptor) { + desc = (FontDescriptor)font; + } + resources.addFont(doc.makeFont(f, font.getFontName(), + font.getEncoding(), font, desc)); + } + } +} + diff --git a/src/java/org/apache/fop/render/pdf/FontTriplet.java b/src/java/org/apache/fop/render/pdf/FontTriplet.java new file mode 100644 index 000000000..5faeee887 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/FontTriplet.java @@ -0,0 +1,96 @@ +/* + * $Id: FontTriplet.java,v 1.2 2003/03/07 09:46:32 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +/** + * FontTriplet contains information on name, weight, style of one font + */ +public class FontTriplet { + + private String name, weight, style; + + /** + * Creates a new font triplet. + * @param name font name + * @param weight font weight (normal, bold etc.) + * @param style font style (normal, italic etc.) + */ + public FontTriplet(String name, String weight, String style) { + this.name = name; + this.weight = weight; + this.style = style; + } + + /** + * Returns the font name. + * @return the font name + */ + public String getName() { + return name; + } + + /** + * Returns the font weight. + * @return the font weight + */ + public String getWeight() { + return weight; + } + + /** + * Returns the font style. + * @return the font style + */ + public String getStyle() { + return style; + } +} + diff --git a/src/java/org/apache/fop/render/pdf/FopPDFImage.java b/src/java/org/apache/fop/render/pdf/FopPDFImage.java new file mode 100644 index 000000000..4021a26ad --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/FopPDFImage.java @@ -0,0 +1,307 @@ +/* + * $Id: FopPDFImage.java,v 1.7 2003/03/07 09:46:32 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +import org.apache.fop.pdf.PDFImage; +import org.apache.fop.pdf.PDFFilter; +import org.apache.fop.pdf.PDFICCStream; +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.pdf.PDFStream; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.DCTFilter; +import org.apache.fop.pdf.PDFColorSpace; + +import org.apache.fop.image.FopImage; +import org.apache.fop.image.JpegImage; +import org.apache.fop.image.EPSImage; + +import java.io.IOException; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; +import java.util.Map; + +/** + * PDFImage implementation for the PDF renderer. + */ +public class FopPDFImage implements PDFImage { + + private FopImage fopImage; + private PDFICCStream pdfICCStream = null; + private PDFFilter pdfFilter = null; + private String maskRef; + private String softMaskRef; + private boolean isPS = false; + private Map filters; + private String key; + + /** + * Creates a new PDFImage from a FopImage + * @param image Image + * @param key XObject key + */ + public FopPDFImage(FopImage image, String key) { + fopImage = image; + this.key = key; + isPS = (fopImage instanceof EPSImage); + } + + /** + * @see org.apache.fop.pdf.PDFImage#getKey() + */ + public String getKey() { + // key to look up XObject + return this.key; + } + + /** + * @see org.apache.fop.pdf.PDFImage#setup(PDFDocument) + */ + public void setup(PDFDocument doc) { + filters = doc.getFilterMap(); + if ("image/jpeg".equals(fopImage.getMimeType())) { + pdfFilter = new DCTFilter(); + pdfFilter.setApplied(true); + + JpegImage jpegimage = (JpegImage) fopImage; + ICC_Profile prof = jpegimage.getICCProfile(); + PDFColorSpace pdfCS = toPDFColorSpace(jpegimage.getColorSpace()); + if (prof != null) { + pdfICCStream = doc.makePDFICCStream(); + pdfICCStream.setColorSpace(prof, pdfCS); + pdfICCStream.addDefaultFilters(filters, PDFStream.CONTENT_FILTER); + } + } + } + + /** + * @see org.apache.fop.pdf.PDFImage#isPS() + */ + public boolean isPS() { + return isPS; + } + + /** + * @see org.apache.fop.pdf.PDFImage#getWidth() + */ + public int getWidth() { + return fopImage.getWidth(); + } + + /** + * @see org.apache.fop.pdf.PDFImage#getHeight() + */ + public int getHeight() { + return fopImage.getHeight(); + } + + /** + * @see org.apache.fop.pdf.PDFImage#getColorSpace() + */ + public PDFColorSpace getColorSpace() { + // DeviceGray, DeviceRGB, or DeviceCMYK + return toPDFColorSpace(fopImage.getColorSpace()); + } + + /** + * @see org.apache.fop.pdf.PDFImage#getBitsPerPixel() + */ + public int getBitsPerPixel() { + return fopImage.getBitsPerPixel(); + } + + /** + * @see org.apache.fop.pdf.PDFImage#isTransparent() + */ + public boolean isTransparent() { + return fopImage.isTransparent(); + } + + /** + * @see org.apache.fop.pdf.PDFImage#getTransparentColor() + */ + public PDFColor getTransparentColor() { + return fopImage.getTransparentColor(); + } + + /** + * @see org.apache.fop.pdf.PDFImage#getMask() + */ + public String getMask() { + return maskRef; + } + + /** + * @see org.apache.fop.pdf.PDFImage#getSoftMask() + */ + public String getSoftMask() { + return softMaskRef; + } + + /** + * @see org.apache.fop.pdf.PDFImage#getDataStream() + */ + public PDFStream getDataStream() throws IOException { + if (isPS) { + return getPSDataStream(); + } else { + // delegate the stream work to PDFStream + PDFStream imgStream = new PDFStream(0); + + imgStream.setData(fopImage.getBitmaps()); + + /* + * Added by Eric Dalquist + * If the DCT filter hasn't been added to the object we add it here + */ + if (pdfFilter != null) { + imgStream.addFilter(pdfFilter); + } + + imgStream.addDefaultFilters(filters, PDFStream.IMAGE_FILTER); + return imgStream; + } + } + + /** + * Returns a PDFStream for an EPS image. + * @return PDFStream the newly creates PDFStream + * @throws IOException in case of an I/O problem + */ + protected PDFStream getPSDataStream() throws IOException { + int length = 0; + int i = 0; + EPSImage epsImage = (EPSImage) fopImage; + int[] bbox = epsImage.getBBox(); + int bboxw = bbox[2] - bbox[0]; + int bboxh = bbox[3] - bbox[1]; + + // delegate the stream work to PDFStream + PDFStream imgStream = new PDFStream(0); + + StringBuffer preamble = new StringBuffer(); + preamble.append("%%BeginDocument: " + epsImage.getDocName() + "\n"); + + preamble.append("userdict begin % Push userdict on dict stack\n"); + preamble.append("/PreEPS_state save def\n"); + preamble.append("/dict_stack countdictstack def\n"); + preamble.append("/ops_count count 1 sub def\n"); + preamble.append("/showpage {} def\n"); + + + preamble.append((double)(1f / (double) bboxw) + " " + + (double)(1f / (double) bboxh) + " scale\n"); + preamble.append(-bbox[0] + " " + (-bbox[1]) + " translate\n"); + preamble.append(bbox[0] + " " + bbox[1] + " " + + bboxw + " " + bboxh + " rectclip\n"); + preamble.append("newpath\n"); + + StringBuffer post = new StringBuffer(); + post.append("%%EndDocument\n"); + post.append("count ops_count sub {pop} repeat\n"); + post.append("countdictstack dict_stack sub {end} repeat\n"); + post.append("PreEPS_state restore\n"); + post.append("end % userdict\n"); + + byte[] preBytes = preamble.toString().getBytes(); + byte[] postBytes = post.toString().getBytes(); + byte[] epsBytes = ((EPSImage)fopImage).getEPSImage(); + int epsLength = epsBytes.length; + byte[] imgData = new byte[preBytes.length + + postBytes.length + + epsLength]; + + System.arraycopy (preBytes, 0, imgData, 0, preBytes.length); + System.arraycopy (epsBytes, 0, imgData, + preBytes.length, epsBytes.length); + System.arraycopy (postBytes, 0, imgData, + preBytes.length + epsBytes.length, + postBytes.length); + + + imgStream.setData(imgData); + imgStream.addDefaultFilters(filters, PDFStream.CONTENT_FILTER); + + return imgStream; + } + + /** + * @see org.apache.fop.pdf.PDFImage#getICCStream() + */ + public PDFICCStream getICCStream() { + return pdfICCStream; + } + + /** + * Converts a ColorSpace object to a PDFColorSpace object. + * @param cs ColorSpace instance + * @return PDFColorSpace new converted object + */ + public static PDFColorSpace toPDFColorSpace(ColorSpace cs) { + if (cs == null) { + return null; + } + + PDFColorSpace pdfCS = new PDFColorSpace(0); + switch(cs.getType()) { + case ColorSpace.TYPE_CMYK: + pdfCS.setColorSpace(PDFColorSpace.DEVICE_CMYK); + break; + case ColorSpace.TYPE_RGB: + pdfCS.setColorSpace(PDFColorSpace.DEVICE_RGB); + break; + case ColorSpace.TYPE_GRAY: + pdfCS.setColorSpace(PDFColorSpace.DEVICE_GRAY); + break; + } + return pdfCS; + } +} + diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java new file mode 100644 index 000000000..be3734964 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -0,0 +1,1361 @@ +/* + * $Id: PDFRenderer.java,v 1.137 2003/03/05 20:38:27 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +// Java +import java.io.IOException; +import java.io.OutputStream; +import java.awt.Color; +import java.awt.geom.Rectangle2D; +import java.awt.geom.AffineTransform; +import java.util.Map; +import java.util.List; + +// XML +import org.w3c.dom.Document; + +// Avalon +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; + +// FOP +import org.apache.fop.render.PrintRenderer; +import org.apache.fop.render.RendererContext; +import org.apache.fop.fo.FOUserAgent; +import org.apache.fop.image.FopImage; +import org.apache.fop.image.XMLImage; +import org.apache.fop.image.ImageFactory; +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.Version; +import org.apache.fop.fo.properties.RuleStyle; +import org.apache.fop.fo.properties.BackgroundRepeat; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontMetrics; +import org.apache.fop.pdf.PDFStream; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFInfo; +import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFState; +import org.apache.fop.pdf.PDFLink; +import org.apache.fop.pdf.PDFOutline; +import org.apache.fop.pdf.PDFAnnotList; +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.extensions.BookmarkData; + +import org.apache.fop.area.Trait; +import org.apache.fop.area.TreeExt; +import org.apache.fop.area.CTM; +import org.apache.fop.area.Title; +import org.apache.fop.area.PageViewport; +import org.apache.fop.area.Page; +import org.apache.fop.area.RegionViewport; +import org.apache.fop.area.Area; +import org.apache.fop.area.Block; +import org.apache.fop.area.BlockViewport; +import org.apache.fop.area.LineArea; +import org.apache.fop.area.inline.Character; +import org.apache.fop.area.inline.Word; +import org.apache.fop.area.inline.Viewport; +import org.apache.fop.area.inline.ForeignObject; +import org.apache.fop.area.inline.Image; +import org.apache.fop.area.inline.Leader; +import org.apache.fop.area.inline.InlineParent; +import org.apache.fop.layout.FontState; +import org.apache.fop.traits.BorderProps; +import org.apache.fop.datatypes.ColorType; + +/* +todo: + +word rendering and optimistion +pdf state optimisation +line and border +background pattern +writing mode +text decoration + + */ + +/** + * Renderer that renders areas to PDF + * + */ +public class PDFRenderer extends PrintRenderer { + /** + * The mime type for pdf + */ + public static final String MIME_TYPE = "application/pdf"; + + /** + * the PDF Document being created + */ + protected PDFDocument pdfDoc; + + /** + * Map of pages using the PageViewport as the key + * this is used for prepared pages that cannot be immediately + * rendered + */ + protected Map pages = null; + + /** + * Page references are stored using the PageViewport as the key + * when a reference is made the PageViewport is used + * for pdf this means we need the pdf page reference + */ + protected Map pageReferences = new java.util.HashMap(); + /** Page viewport references */ + protected Map pvReferences = new java.util.HashMap(); + + private String producer = "FOP"; + + private String creator = null; + + /** + * The output stream to write the document to + */ + protected OutputStream ostream; + + /** + * the /Resources object of the PDF document being created + */ + protected PDFResources pdfResources; + + /** + * the current stream to add PDF commands to + */ + protected PDFStream currentStream; + + /** + * the current annotation list to add annotations to + */ + protected PDFResourceContext currentContext = null; + + /** + * the current page to add annotations to + */ + protected PDFPage currentPage; + + /** drawing state */ + protected PDFState currentState = null; + + /** Name of currently selected font */ + protected String currentFontName = ""; + /** Size of currently selected font */ + protected int currentFontSize = 0; + /** page height */ + protected int pageHeight; + + /** Registry of PDF filters */ + protected Map filterMap = new java.util.HashMap(); + + /** + * true if a TJ command is left to be written + */ + protected boolean textOpen = false; + + /** + * the previous Y coordinate of the last word written. + * Used to decide if we can draw the next word on the same line. + */ + protected int prevWordY = 0; + + /** + * the previous X coordinate of the last word written. + * used to calculate how much space between two words + */ + protected int prevWordX = 0; + + /** + * The width of the previous word. Used to calculate space between + */ + protected int prevWordWidth = 0; + + /** + * reusable word area string buffer to reduce memory usage + */ + private StringBuffer wordAreaPDF = new StringBuffer(); + + /** + * create the PDF renderer + */ + public PDFRenderer() { + } + + /** + * Configure the PDF renderer. + * Get the configuration to be used for pdf stream filters, + * fonts etc. + * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration) + */ + public void configure(Configuration conf) throws ConfigurationException { + Configuration filters = conf.getChild("filterList"); + Configuration[] filt = filters.getChildren("value"); + List filterList = new java.util.ArrayList(); + for (int i = 0; i < filt.length; i++) { + String name = filt[i].getValue(); + filterList.add(name); + } + + filterMap.put(PDFStream.DEFAULT_FILTER, filterList); + + Configuration[] font = conf.getChildren("font"); + for (int i = 0; i < font.length; i++) { + Configuration[] triple = font[i].getChildren("font-triplet"); + List tripleList = new java.util.ArrayList(); + for (int j = 0; j < triple.length; j++) { + tripleList.add(new FontTriplet(triple[j].getAttribute("name"), + triple[j].getAttribute("style"), + triple[j].getAttribute("weight"))); + } + + EmbedFontInfo efi; + efi = new EmbedFontInfo(font[i].getAttribute("metrics-url"), + font[i].getAttributeAsBoolean("kerning"), + tripleList, font[i].getAttribute("embed-url")); + + if (fontList == null) { + fontList = new java.util.ArrayList(); + } + fontList.add(efi); + } + + } + + /** + * Set the document creator. + * + * @param creator string indicating application that is creating the document + */ + public void setCreator(String creator) { + this.creator = creator; + } + + /** + * Set the PDF document's producer. + * + * @param producer string indicating application producing PDF + */ + public void setProducer(String producer) { + this.producer = producer; + } + + /** + * @see org.apache.fop.render.Renderer#setUserAgent(FOUserAgent) + */ + public void setUserAgent(FOUserAgent agent) { + super.setUserAgent(agent); + PDFXMLHandler xmlHandler = new PDFXMLHandler(); + //userAgent.setDefaultXMLHandler(MIME_TYPE, xmlHandler); + String svg = "http://www.w3.org/2000/svg"; + userAgent.addXMLHandler(MIME_TYPE, svg, xmlHandler); + } + + /** + * @see org.apache.fop.render.Renderer#startRenderer(OutputStream) + */ + public void startRenderer(OutputStream stream) throws IOException { + ostream = stream; + producer = "FOP " + Version.getVersion(); + this.pdfDoc = new PDFDocument(producer); + this.pdfDoc.setCreator(creator); + this.pdfDoc.setFilterMap(filterMap); + pdfDoc.outputHeader(stream); + } + + /** + * @see org.apache.fop.render.Renderer#stopRenderer() + */ + public void stopRenderer() throws IOException { + FontSetup.addToResources(pdfDoc, pdfDoc.getResources(), fontInfo); + pdfDoc.outputTrailer(ostream); + + this.pdfDoc = null; + ostream = null; + + pages = null; + + pageReferences.clear(); + pvReferences.clear(); + pdfResources = null; + currentStream = null; + currentContext = null; + currentPage = null; + currentState = null; + currentFontName = ""; + wordAreaPDF = new StringBuffer(); + } + + /** + * @see org.apache.fop.render.Renderer#supportsOutOfOrder() + */ + public boolean supportsOutOfOrder() { + return true; + } + + /** + * @see org.apache.fop.render.Renderer#renderExtension(TreeExt) + */ + public void renderExtension(TreeExt ext) { + // render bookmark extension + if (ext instanceof BookmarkData) { + renderRootExtensions((BookmarkData)ext); + } + } + + /** + * Renders the root extension elements + * @param bookmarks the bookmarks to render + */ + protected void renderRootExtensions(BookmarkData bookmarks) { + for (int i = 0; i < bookmarks.getCount(); i++) { + BookmarkData ext = bookmarks.getSubData(i); + renderOutline(ext, null); + } + } + + private void renderOutline(BookmarkData outline, PDFOutline parentOutline) { + PDFOutline outlineRoot = pdfDoc.getOutlineRoot(); + PDFOutline pdfOutline = null; + PageViewport pv = outline.getPage(); + if (pv != null) { + Rectangle2D bounds = pv.getViewArea(); + double h = bounds.getHeight(); + float yoffset = (float)h / 1000f; + String intDest = (String)pageReferences.get(pv.getKey()); + if (parentOutline == null) { + pdfOutline = pdfDoc.makeOutline(outlineRoot, + outline.getLabel(), intDest, yoffset); + } else { + PDFOutline pdfParentOutline = parentOutline; + pdfOutline = pdfDoc.makeOutline(pdfParentOutline, + outline.getLabel(), intDest, yoffset); + } + } + + for (int i = 0; i < outline.getCount(); i++) { + renderOutline(outline.getSubData(i), pdfOutline); + } + } + + /** Saves the graphics state of the rendering engine. */ + protected void saveGraphicsState() { + currentStream.add("q\n"); + } + + /** Restores the last graphics state of the rendering engine. */ + protected void restoreGraphicsState() { + currentStream.add("Q\n"); + } + + /** Indicates the beginning of a text object. */ + protected void beginTextObject() { + currentStream.add("BT\n"); + } + + /** Indicates the end of a text object. */ + protected void endTextObject() { + currentStream.add("ET\n"); + } + + /** + * Start the next page sequence. + * For the pdf renderer there is no concept of page sequences + * but it uses the first available page sequence title to set + * as the title of the pdf document. + * + * @param seqTitle the title of the page sequence + */ + public void startPageSequence(Title seqTitle) { + if (seqTitle != null) { + String str = convertTitleToString(seqTitle); + PDFInfo info = this.pdfDoc.getInfo(); + info.setTitle(str); + } + } + + /** + * The pdf page is prepared by making the page. + * The page is made in the pdf document without any contents + * and then stored to add the contents later. + * The page objects is stored using the area tree PageViewport + * as a key. + * + * @param page the page to prepare + */ + public void preparePage(PageViewport page) { + this.pdfResources = this.pdfDoc.getResources(); + + Rectangle2D bounds = page.getViewArea(); + double w = bounds.getWidth(); + double h = bounds.getHeight(); + currentPage = this.pdfDoc.makePage(this.pdfResources, + (int) Math.round(w / 1000), (int) Math.round(h / 1000)); + if (pages == null) { + pages = new java.util.HashMap(); + } + pages.put(page, currentPage); + pageReferences.put(page.getKey(), currentPage.referencePDF()); + pvReferences.put(page.getKey(), page); + } + + /** + * This method creates a pdf stream for the current page + * uses it as the contents of a new page. The page is written + * immediately to the output stream. + * @see org.apache.fop.render.Renderer#renderPage(PageViewport) + */ + public void renderPage(PageViewport page) + throws IOException, FOPException { + if (pages != null + && (currentPage = (PDFPage) pages.get(page)) != null) { + pages.remove(page); + Rectangle2D bounds = page.getViewArea(); + double h = bounds.getHeight(); + pageHeight = (int) h; + } else { + this.pdfResources = this.pdfDoc.getResources(); + Rectangle2D bounds = page.getViewArea(); + double w = bounds.getWidth(); + double h = bounds.getHeight(); + pageHeight = (int) h; + currentPage = this.pdfDoc.makePage(this.pdfResources, + (int) Math.round(w / 1000), (int) Math.round(h / 1000)); + pageReferences.put(page.getKey(), currentPage.referencePDF()); + pvReferences.put(page.getKey(), page); + } + currentStream = + this.pdfDoc.makeStream(PDFStream.CONTENT_FILTER, false); + + currentState = new PDFState(); + currentState.setTransform(new AffineTransform(1, 0, 0, -1, 0, + (int) Math.round(pageHeight / 1000))); + // Transform origin at top left to origin at bottom left + currentStream.add("1 0 0 -1 0 " + + (int) Math.round(pageHeight / 1000) + " cm\n"); + currentFontName = ""; + + Page p = page.getPage(); + renderPageAreas(p); + + this.pdfDoc.addStream(currentStream); + currentPage.setContents(currentStream); + PDFAnnotList annots = currentPage.getAnnotations(); + if (annots != null) { + this.pdfDoc.addAnnotList(annots); + } + this.pdfDoc.addPage(currentPage); + this.pdfDoc.output(ostream); + } + + + /** + * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM) + */ + protected void startVParea(CTM ctm) { + // Set the given CTM in the graphics state + currentState.push(); + currentState.setTransform( + new AffineTransform(CTMHelper.toPDFArray(ctm))); + + saveGraphicsState(); + // multiply with current CTM + currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n"); + // Set clip? + beginTextObject(); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#endVParea() + */ + protected void endVParea() { + endTextObject(); + restoreGraphicsState(); + currentState.pop(); + } + + /** + * Handle the viewport traits. + * This is used to draw the traits for a viewport. + * + * @param region the viewport region to handle + */ + protected void handleViewportTraits(RegionViewport region) { + currentFontName = ""; + float startx = 0; + float starty = 0; + Rectangle2D viewArea = region.getViewArea(); + float width = (float)(viewArea.getWidth() / 1000f); + float height = (float)(viewArea.getHeight() / 1000f); + /* + Trait.Background back; + back = (Trait.Background)region.getTrait(Trait.BACKGROUND); + */ + drawBackAndBorders(region, startx, starty, width, height); + } + + /** + * Handle block traits. + * The block could be any sort of block with any positioning + * so this should render the traits such as border and background + * in its position. + * + * @param block the block to render the traits + */ + protected void handleBlockTraits(Block block) { + float startx = currentIPPosition / 1000f; + float starty = currentBPPosition / 1000f; + drawBackAndBorders(block, startx, starty, + block.getWidth() / 1000f, block.getHeight() / 1000f); + } + + /** + * Draw the background and borders. + * This draws the background and border traits for an area given + * the position. + * + * @param block the area to get the traits from + * @param startx the start x position + * @param starty the start y position + * @param width the width of the area + * @param height the height of the area + */ + protected void drawBackAndBorders(Area block, + float startx, float starty, + float width, float height) { + // draw background then border + + boolean started = false; + Trait.Background back; + back = (Trait.Background)block.getTrait(Trait.BACKGROUND); + if (back != null) { + started = true; + closeText(); + endTextObject(); + //saveGraphicsState(); + + if (back.getColor() != null) { + updateColor(back.getColor(), true, null); + currentStream.add(startx + " " + starty + " " + + width + " " + height + " re\n"); + currentStream.add("f\n"); + } + if (back.getURL() != null) { + ImageFactory fact = ImageFactory.getInstance(); + FopImage fopimage = fact.getImage(back.getURL(), userAgent); + if (fopimage != null && fopimage.load(FopImage.DIMENSIONS, userAgent)) { + if (back.getRepeat() == BackgroundRepeat.REPEAT) { + // create a pattern for the image + } else { + // place once + Rectangle2D pos; + pos = new Rectangle2D.Float((startx + back.getHoriz()) * 1000, + (starty + back.getVertical()) * 1000, + fopimage.getWidth() * 1000, + fopimage.getHeight() * 1000); + putImage(back.getURL(), pos); + } + } + } + } + + BorderProps bps = (BorderProps)block.getTrait(Trait.BORDER_BEFORE); + if (bps != null) { + float endx = startx + width; + + if (!started) { + started = true; + closeText(); + endTextObject(); + //saveGraphicsState(); + } + + float bwidth = bps.width / 1000f; + updateColor(bps.color, false, null); + currentStream.add(bwidth + " w\n"); + + drawLine(startx, starty + bwidth / 2, endx, starty + bwidth / 2); + } + bps = (BorderProps)block.getTrait(Trait.BORDER_START); + if (bps != null) { + float endy = starty + height; + + if (!started) { + started = true; + closeText(); + endTextObject(); + //saveGraphicsState(); + } + + float bwidth = bps.width / 1000f; + updateColor(bps.color, false, null); + currentStream.add(bwidth + " w\n"); + + drawLine(startx + bwidth / 2, starty, startx + bwidth / 2, endy); + } + bps = (BorderProps)block.getTrait(Trait.BORDER_AFTER); + if (bps != null) { + float sy = starty + height; + float endx = startx + width; + + if (!started) { + started = true; + closeText(); + endTextObject(); + //saveGraphicsState(); + } + + float bwidth = bps.width / 1000f; + updateColor(bps.color, false, null); + currentStream.add(bwidth + " w\n"); + + drawLine(startx, sy - bwidth / 2, endx, sy - bwidth / 2); + } + bps = (BorderProps)block.getTrait(Trait.BORDER_END); + if (bps != null) { + float sx = startx + width; + float endy = starty + height; + + if (!started) { + started = true; + closeText(); + endTextObject(); + //saveGraphicsState(); + } + + float bwidth = bps.width / 1000f; + updateColor(bps.color, false, null); + currentStream.add(bwidth + " w\n"); + drawLine(sx - bwidth / 2, starty, sx - bwidth / 2, endy); + } + if (started) { + //restoreGraphicsState(); + beginTextObject(); + // font last set out of scope in text section + currentFontName = ""; + } + } + + /** + * Draw a line. + * + * @param startx the start x position + * @param starty the start y position + * @param endx the x end position + * @param endy the y end position + */ + private void drawLine(float startx, float starty, float endx, float endy) { + currentStream.add(startx + " " + starty + " m\n"); + currentStream.add(endx + " " + endy + " l\n"); + currentStream.add("S\n"); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderBlockViewport(BlockViewport, List) + */ + protected void renderBlockViewport(BlockViewport bv, List children) { + // clip and position viewport if necessary + + // save positions + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + String saveFontName = currentFontName; + + CTM ctm = bv.getCTM(); + + if (bv.getPositioning() == Block.ABSOLUTE) { + + currentIPPosition = 0; + currentBPPosition = 0; + + closeText(); + endTextObject(); + + if (bv.getClip()) { + saveGraphicsState(); + float x = (float)(bv.getXOffset() + containingIPPosition) / 1000f; + float y = (float)(bv.getYOffset() + containingBPPosition) / 1000f; + float width = (float)bv.getWidth() / 1000f; + float height = (float)bv.getHeight() / 1000f; + clip(x, y, width, height); + } + + CTM tempctm = new CTM(containingIPPosition, containingBPPosition); + ctm = tempctm.multiply(ctm); + + startVParea(ctm); + handleBlockTraits(bv); + renderBlocks(children); + endVParea(); + + if (bv.getClip()) { + restoreGraphicsState(); + } + beginTextObject(); + + // clip if necessary + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + } else { + + if (ctm != null) { + currentIPPosition = 0; + currentBPPosition = 0; + + closeText(); + endTextObject(); + + double[] vals = ctm.toArray(); + //boolean aclock = vals[2] == 1.0; + if (vals[2] == 1.0) { + ctm = ctm.translate(-saveBP - bv.getHeight(), -saveIP); + } else if (vals[0] == -1.0) { + ctm = ctm.translate(-saveIP - bv.getWidth(), -saveBP - bv.getHeight()); + } else { + ctm = ctm.translate(saveBP, saveIP - bv.getWidth()); + } + } + + // clip if necessary + if (bv.getClip()) { + if (ctm == null) { + closeText(); + endTextObject(); + } + saveGraphicsState(); + float x = (float)bv.getXOffset() / 1000f; + float y = (float)bv.getYOffset() / 1000f; + float width = (float)bv.getWidth() / 1000f; + float height = (float)bv.getHeight() / 1000f; + clip(x, y, width, height); + } + + if (ctm != null) { + startVParea(ctm); + } + handleBlockTraits(bv); + renderBlocks(children); + if (ctm != null) { + endVParea(); + } + + if (bv.getClip()) { + restoreGraphicsState(); + if (ctm == null) { + beginTextObject(); + } + } + if (ctm != null) { + beginTextObject(); + } + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + currentBPPosition += (int)(bv.getHeight()); + } + currentFontName = saveFontName; + } + + /** + * Clip an area. + * write a clipping operation given coordinates in the current + * transform. + * @param x the x coordinate + * @param y the y coordinate + * @param width the width of the area + * @param height the height of the area + */ + protected void clip(float x, float y, float width, float height) { + currentStream.add(x + " " + y + " m\n"); + currentStream.add((x + width) + " " + y + " l\n"); + currentStream.add((x + width) + " " + (y + height) + " l\n"); + currentStream.add(x + " " + (y + height) + " l\n"); + currentStream.add("h\n"); + currentStream.add("W\n"); + currentStream.add("n\n"); + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderLineArea(LineArea) + */ + protected void renderLineArea(LineArea line) { + super.renderLineArea(line); + closeText(); + } + + /** + * Render inline parent area. + * For pdf this handles the inline parent area traits such as + * links, border, background. + * @param ip the inline parent area + */ + public void renderInlineParent(InlineParent ip) { + float start = currentBlockIPPosition / 1000f; + float top = (ip.getOffset() + currentBPPosition) / 1000f; + float width = ip.getWidth() / 1000f; + float height = ip.getHeight() / 1000f; + drawBackAndBorders(ip, start, top, width, height); + + // render contents + super.renderInlineParent(ip); + + // place the link over the top + Object tr = ip.getTrait(Trait.INTERNAL_LINK); + boolean internal = false; + String dest = null; + float yoffset = 0; + if (tr == null) { + dest = (String)ip.getTrait(Trait.EXTERNAL_LINK); + } else { + String pvKey = (String)tr; + dest = (String)pageReferences.get(pvKey); + if (dest != null) { + PageViewport pv = (PageViewport)pvReferences.get(pvKey); + Rectangle2D bounds = pv.getViewArea(); + double h = bounds.getHeight(); + yoffset = (float)h / 1000f; + internal = true; + } + } + if (dest != null) { + // add link to pdf document + Rectangle2D rect = new Rectangle2D.Float(start, top, width, height); + // transform rect to absolute coords + AffineTransform transform = currentState.getTransform(); + rect = transform.createTransformedShape(rect).getBounds(); + + int type = internal ? PDFLink.INTERNAL : PDFLink.EXTERNAL; + PDFLink pdflink = pdfDoc.makeLink(rect, dest, type, yoffset); + currentPage.addAnnotation(pdflink); + } + } + + /** + * @see org.apache.fop.render.Renderer#renderCharacter(Character) + */ + public void renderCharacter(Character ch) { + + super.renderCharacter(ch); + } + + /** + * @see org.apache.fop.render.Renderer#renderWord(Word) + */ + public void renderWord(Word word) { + StringBuffer pdf = new StringBuffer(); + + String name = (String) word.getTrait(Trait.FONT_NAME); + int size = ((Integer) word.getTrait(Trait.FONT_SIZE)).intValue(); + + // This assumes that *all* CIDFonts use a /ToUnicode mapping + Font f = (Font) fontInfo.getFonts().get(name); + boolean useMultiByte = f.isMultiByte(); + + // String startText = useMultiByte ? "<FEFF" : "("; + String startText = useMultiByte ? "<" : "("; + String endText = useMultiByte ? "> " : ") "; + + updateFont(name, size, pdf); + ColorType ct = (ColorType)word.getTrait(Trait.COLOR); + if (ct != null) { + updateColor(ct, true, pdf); + } + + int rx = currentBlockIPPosition; + // int bl = pageHeight - currentBPPosition; + int bl = currentBPPosition + word.getOffset(); + + // Set letterSpacing + //float ls = fs.getLetterSpacing() / this.currentFontSize; + //pdf.append(ls).append(" Tc\n"); + + if (!textOpen || bl != prevWordY) { + closeText(); + + pdf.append("1 0 0 -1 " + (rx / 1000f) + " " + + (bl / 1000f) + " Tm [" + startText); + prevWordY = bl; + textOpen = true; + } else { + // express the space between words in thousandths of an em + int space = prevWordX - rx + prevWordWidth; + float emDiff = (float) space / (float) currentFontSize * 1000f; + // this prevents a problem in Acrobat Reader and other viewers + // where large numbers cause text to disappear or default to + // a limit + if (emDiff < -33000) { + closeText(); + + pdf.append("1 0 0 1 " + (rx / 1000f) + " " + + (bl / 1000f) + " Tm [" + startText); + textOpen = true; + } else { + pdf.append(Float.toString(emDiff)); + pdf.append(" "); + pdf.append(startText); + } + } + prevWordWidth = word.getWidth(); + prevWordX = rx; + + String s = word.getWord(); + + FontMetrics metrics = fontInfo.getMetricsFor(name); + FontState fs = new FontState(name, metrics, size); + escapeText(s, fs, useMultiByte, pdf); + pdf.append(endText); + + currentStream.add(pdf.toString()); + + super.renderWord(word); + } + + /** + * Escapes text according to PDF rules. + * @param s Text to escape + * @param fs Font state + * @param useMultiByte Indicates the use of multi byte convention + * @param pdf target buffer for the escaped text + */ + public void escapeText(String s, FontState fs, + boolean useMultiByte, StringBuffer pdf) { + String startText = useMultiByte ? "<" : "("; + String endText = useMultiByte ? "> " : ") "; + + boolean kerningAvailable = false; + Map kerning = fs.getKerning(); + if (kerning != null && !kerning.isEmpty()) { + kerningAvailable = true; + } + + int l = s.length(); + + for (int i = 0; i < l; i++) { + char ch = fs.mapChar(s.charAt(i)); + + if (!useMultiByte) { + if (ch > 127) { + pdf.append("\\"); + pdf.append(Integer.toOctalString((int) ch)); + } else { + switch (ch) { + case '(': + case ')': + case '\\': + pdf.append("\\"); + break; + } + pdf.append(ch); + } + } else { + pdf.append(getUnicodeString(ch)); + } + + if (kerningAvailable && (i + 1) < l) { + addKerning(pdf, (new Integer((int) ch)), + (new Integer((int) fs.mapChar(s.charAt(i + 1))) + ), kerning, startText, endText); + } + } + } + + /** + * Convert a char to a multibyte hex representation + */ + private String getUnicodeString(char c) { + StringBuffer buf = new StringBuffer(4); + + byte[] uniBytes = null; + try { + char[] a = {c}; + uniBytes = new String(a).getBytes("UnicodeBigUnmarked"); + } catch (java.io.UnsupportedEncodingException e) { + // This should never fail + } + + for (int i = 0; i < uniBytes.length; i++) { + int b = (uniBytes[i] < 0) ? (int)(256 + uniBytes[i]) + : (int) uniBytes[i]; + + String hexString = Integer.toHexString(b); + if (hexString.length() == 1) { + buf = buf.append("0" + hexString); + } else { + buf = buf.append(hexString); + } + } + return buf.toString(); + } + + private void addKerning(StringBuffer buf, Integer ch1, Integer ch2, + Map kerning, String startText, String endText) { + Map kernPair = (Map) kerning.get(ch1); + + if (kernPair != null) { + Integer width = (Integer) kernPair.get(ch2); + if (width != null) { + buf.append(endText).append(-width.intValue()); + buf.append(' ').append(startText); + } + } + } + + /** + * Checks to see if we have some text rendering commands open + * still and writes out the TJ command to the stream if we do + */ + protected void closeText() { + if (textOpen) { + currentStream.add("] TJ\n"); + textOpen = false; + prevWordX = 0; + prevWordY = 0; + } + } + + private void updateColor(ColorType col, boolean fill, StringBuffer pdf) { + Color newCol = new Color(col.getRed(), col.getGreen(), col.getBlue()); + boolean update = false; + if (fill) { + update = currentState.setBackColor(newCol); + } else { + update = currentState.setColor(newCol); + } + + if (update) { + PDFColor color = new PDFColor((double)col.getRed(), + (double)col.getGreen(), + (double)col.getBlue()); + + closeText(); + + if (pdf != null) { + pdf.append(color.getColorSpaceOut(fill)); + } else { + currentStream.add(color.getColorSpaceOut(fill)); + } + } + } + + private void updateFont(String name, int size, StringBuffer pdf) { + if ((!name.equals(this.currentFontName)) + || (size != this.currentFontSize)) { + closeText(); + + this.currentFontName = name; + this.currentFontSize = size; + pdf = pdf.append("/" + name + " " + ((float) size / 1000f) + + " Tf\n"); + } + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderImage(Image, Rectangle2D) + */ + public void renderImage(Image image, Rectangle2D pos) { + String url = image.getURL(); + putImage(url, pos); + } + + /** + * Adds a PDF XObject (a bitmap) to the PDF that will later be referenced. + * @param url URL of the bitmap + * @param pos Position of the bitmap + */ + protected void putImage(String url, Rectangle2D pos) { + PDFXObject xobject = pdfDoc.getImage(url); + if (xobject != null) { + int w = (int) pos.getWidth() / 1000; + int h = (int) pos.getHeight() / 1000; + placeImage((int) pos.getX() / 1000, + (int) pos.getY() / 1000, w, h, xobject.getXNumber()); + return; + } + + ImageFactory fact = ImageFactory.getInstance(); + FopImage fopimage = fact.getImage(url, userAgent); + if (fopimage == null) { + return; + } + if (!fopimage.load(FopImage.DIMENSIONS, userAgent)) { + return; + } + String mime = fopimage.getMimeType(); + if ("text/xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA, userAgent)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + String ns = ((XMLImage) fopimage).getNameSpace(); + + renderDocument(doc, ns, pos); + } else if ("image/svg+xml".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA, userAgent)) { + return; + } + Document doc = ((XMLImage) fopimage).getDocument(); + String ns = ((XMLImage) fopimage).getNameSpace(); + + renderDocument(doc, ns, pos); + } else if ("image/eps".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA, userAgent)) { + return; + } + FopPDFImage pdfimage = new FopPDFImage(fopimage, url); + int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber(); + fact.releaseImage(url, userAgent); + } else if ("image/jpeg".equals(mime)) { + if (!fopimage.load(FopImage.ORIGINAL_DATA, userAgent)) { + return; + } + FopPDFImage pdfimage = new FopPDFImage(fopimage, url); + int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber(); + fact.releaseImage(url, userAgent); + + int w = (int) pos.getWidth() / 1000; + int h = (int) pos.getHeight() / 1000; + placeImage((int) pos.getX() / 1000, + (int) pos.getY() / 1000, w, h, xobj); + } else { + if (!fopimage.load(FopImage.BITMAP, userAgent)) { + return; + } + FopPDFImage pdfimage = new FopPDFImage(fopimage, url); + int xobj = pdfDoc.addImage(currentContext, pdfimage).getXNumber(); + fact.releaseImage(url, userAgent); + + int w = (int) pos.getWidth() / 1000; + int h = (int) pos.getHeight() / 1000; + placeImage((int) pos.getX() / 1000, + (int) pos.getY() / 1000, w, h, xobj); + } + + // output new data + try { + this.pdfDoc.output(ostream); + } catch (IOException ioe) { + // ioexception will be caught later + } + } + + /** + * Places a previously registered image at a certain place on the page. + * @param x X coordinate + * @param y Y coordinate + * @param w width for image + * @param h height for image + * @param xobj object number of the referenced image + */ + protected void placeImage(int x, int y, int w, int h, int xobj) { + saveGraphicsState(); + currentStream.add(((float) w) + " 0 0 " + + ((float) -h) + " " + + (((float) currentBlockIPPosition) / 1000f + x) + " " + + (((float)(currentBPPosition + 1000 * h)) / 1000f + + y) + " cm\n" + "/Im" + xobj + " Do\n"); + restoreGraphicsState(); + + } + + /** + * @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, Rectangle2D) + */ + public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + Document doc = fo.getDocument(); + String ns = fo.getNameSpace(); + renderDocument(doc, ns, pos); + } + + /** + * Renders an XML document (SVG for example). + * @param doc DOM document representing the XML document + * @param ns Namespace for the document + * @param pos Position on the page + */ + public void renderDocument(Document doc, String ns, Rectangle2D pos) { + RendererContext context; + context = new RendererContext(MIME_TYPE); + context.setUserAgent(userAgent); + + context.setProperty(PDFXMLHandler.PDF_DOCUMENT, pdfDoc); + context.setProperty(PDFXMLHandler.OUTPUT_STREAM, ostream); + context.setProperty(PDFXMLHandler.PDF_STATE, currentState); + context.setProperty(PDFXMLHandler.PDF_PAGE, currentPage); + context.setProperty(PDFXMLHandler.PDF_CONTEXT, + currentContext == null ? currentPage : currentContext); + context.setProperty(PDFXMLHandler.PDF_CONTEXT, currentContext); + context.setProperty(PDFXMLHandler.PDF_STREAM, currentStream); + context.setProperty(PDFXMLHandler.PDF_XPOS, + new Integer(currentBlockIPPosition + (int) pos.getX())); + context.setProperty(PDFXMLHandler.PDF_YPOS, + new Integer(currentBPPosition + (int) pos.getY())); + context.setProperty(PDFXMLHandler.PDF_FONT_INFO, fontInfo); + context.setProperty(PDFXMLHandler.PDF_FONT_NAME, currentFontName); + context.setProperty(PDFXMLHandler.PDF_FONT_SIZE, + new Integer(currentFontSize)); + context.setProperty(PDFXMLHandler.PDF_WIDTH, + new Integer((int) pos.getWidth())); + context.setProperty(PDFXMLHandler.PDF_HEIGHT, + new Integer((int) pos.getHeight())); + userAgent.renderXML(context, doc, ns); + + } + + /** + * Render an inline viewport. + * This renders an inline viewport by clipping if necessary. + * @param viewport the viewport to handle + */ + public void renderViewport(Viewport viewport) { + closeText(); + + float x = currentBlockIPPosition / 1000f; + float y = (currentBPPosition + viewport.getOffset()) / 1000f; + float width = viewport.getWidth() / 1000f; + float height = viewport.getHeight() / 1000f; + drawBackAndBorders(viewport, x, y, width, height); + + endTextObject(); + + if (viewport.getClip()) { + saveGraphicsState();; + + clip(x, y, width, height); + } + super.renderViewport(viewport); + + if (viewport.getClip()) { + restoreGraphicsState(); + } + beginTextObject(); + } + + /** + * Render leader area. + * This renders a leader area which is an area with a rule. + * @param area the leader area to render + */ + public void renderLeader(Leader area) { + closeText(); + endTextObject(); + saveGraphicsState(); + int style = area.getRuleStyle(); + boolean alt = false; + switch(style) { + case RuleStyle.SOLID: + currentStream.add("[] 0 d\n"); + break; + case RuleStyle.DOTTED: + currentStream.add("[2] 0 d\n"); + break; + case RuleStyle.DASHED: + currentStream.add("[6 4] 0 d\n"); + break; + case RuleStyle.DOUBLE: + case RuleStyle.GROOVE: + case RuleStyle.RIDGE: + alt = true; + break; + } + float startx = ((float) currentBlockIPPosition) / 1000f; + float starty = ((currentBPPosition + area.getOffset()) / 1000f); + float endx = (currentBlockIPPosition + area.getWidth()) / 1000f; + if (!alt) { + currentStream.add(area.getRuleThickness() / 1000f + " w\n"); + drawLine(startx, starty, endx, starty); + } else { + if (style == RuleStyle.DOUBLE) { + float third = area.getRuleThickness() / 3000f; + currentStream.add(third + " w\n"); + drawLine(startx, starty, endx, starty); + + drawLine(startx, (starty + 2 * third), endx, (starty + 2 * third)); + } else { + float half = area.getRuleThickness() / 2000f; + + currentStream.add("1 g\n"); + currentStream.add(startx + " " + starty + " m\n"); + currentStream.add(endx + " " + starty + " l\n"); + currentStream.add(endx + " " + (starty + 2 * half) + " l\n"); + currentStream.add(startx + " " + (starty + 2 * half) + " l\n"); + currentStream.add("h\n"); + currentStream.add("f\n"); + if (style == RuleStyle.GROOVE) { + currentStream.add("0 g\n"); + currentStream.add(startx + " " + starty + " m\n"); + currentStream.add(endx + " " + starty + " l\n"); + currentStream.add(endx + " " + (starty + half) + " l\n"); + currentStream.add((startx + half) + " " + (starty + half) + " l\n"); + currentStream.add(startx + " " + (starty + 2 * half) + " l\n"); + } else { + currentStream.add("0 g\n"); + currentStream.add(endx + " " + starty + " m\n"); + currentStream.add(endx + " " + (starty + 2 * half) + " l\n"); + currentStream.add(startx + " " + (starty + 2 * half) + " l\n"); + currentStream.add(startx + " " + (starty + half) + " l\n"); + currentStream.add((endx - half) + " " + (starty + half) + " l\n"); + } + currentStream.add("h\n"); + currentStream.add("f\n"); + } + + } + + restoreGraphicsState(); + beginTextObject(); + super.renderLeader(area); + } +} + diff --git a/src/java/org/apache/fop/render/pdf/PDFXMLHandler.java b/src/java/org/apache/fop/render/pdf/PDFXMLHandler.java new file mode 100644 index 000000000..f58d71da6 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFXMLHandler.java @@ -0,0 +1,338 @@ +/* + * $Id: PDFXMLHandler.java,v 1.13 2003/03/07 09:46:32 jeremias Exp $ + * ============================================================================ + * The Apache Software License, Version 1.1 + * ============================================================================ + * + * Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modifica- + * tion, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The end-user documentation included with the redistribution, if any, must + * include the following acknowledgment: "This product includes software + * developed by the Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, if + * and wherever such third-party acknowledgments normally appear. + * + * 4. The names "FOP" and "Apache Software Foundation" must not be used to + * endorse or promote products derived from this software without prior + * written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", nor may + * "Apache" appear in their name, without prior written permission of the + * Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- + * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ============================================================================ + * + * This software consists of voluntary contributions made by many individuals + * on behalf of the Apache Software Foundation and was originally created by + * James Tauber <jtauber@jtauber.com>. For more information on the Apache + * Software Foundation, please see <http://www.apache.org/>. + */ +package org.apache.fop.render.pdf; + +import org.apache.fop.render.XMLHandler; +import org.apache.fop.render.RendererContext; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFState; +import org.apache.fop.pdf.PDFStream; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.svg.PDFTextElementBridge; +import org.apache.fop.svg.PDFAElementBridge; +import org.apache.fop.svg.PDFGraphics2D; +import org.apache.fop.svg.SVGUserAgent; +import org.apache.fop.layout.FontInfo; + +import org.w3c.dom.Document; + +import java.io.OutputStream; + +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.ViewBox; + +import org.apache.batik.gvt.GraphicsNode; + +import org.w3c.dom.svg.SVGDocument; +import org.w3c.dom.svg.SVGSVGElement; + +import java.awt.geom.AffineTransform; + +/** + * PDF XML handler. + * This handler handles XML for foreign objects when rendering to PDF. + * It renders SVG to the PDF document using the PDFGraphics2D. + * The properties from the PDF renderer are subject to change. + */ +public class PDFXMLHandler implements XMLHandler { + /** + * The PDF document that is being drawn into. + */ + public static final String PDF_DOCUMENT = "pdfDoc"; + + /** + * The output stream that the document is being sent to. + */ + public static final String OUTPUT_STREAM = "outputStream"; + + /** + * The current pdf state. + */ + public static final String PDF_STATE = "pdfState"; + + /** + * The current PDF page for page renference and as a resource context. + */ + public static final String PDF_PAGE = "pdfPage"; + + /** + * The current PDF page for page renference and as a resource context. + */ + public static final String PDF_CONTEXT = "pdfContext"; + + /** + * The current PDF stream to draw directly to. + */ + public static final String PDF_STREAM = "pdfStream"; + + /** + * The width of the current pdf page. + */ + public static final String PDF_WIDTH = "width"; + + /** + * The height of the current pdf page. + */ + public static final String PDF_HEIGHT = "height"; + + /** + * The current font information for the pdf renderer. + */ + public static final String PDF_FONT_INFO = "fontInfo"; + + /** + * The current pdf font name. + */ + public static final String PDF_FONT_NAME = "fontName"; + + /** + * The current pdf font size. + */ + public static final String PDF_FONT_SIZE = "fontSize"; + + /** + * The x position that this is being drawn at. + */ + public static final String PDF_XPOS = "xpos"; + + /** + * The y position that this is being drawn at. + */ + public static final String PDF_YPOS = "ypos"; + + /** + * Create a new PDF XML handler for use by the PDF renderer. + */ + public PDFXMLHandler() { + } + + /** + * Handle the XML. + * This checks the type of XML and handles appropraitely. + * + * @param context the renderer context + * @param doc the XML document to render + * @param ns the namespace of the XML document + * @throws Exception any sort of exception could be thrown and shuld be handled + */ + public void handleXML(RendererContext context, Document doc, + String ns) throws Exception { + PDFInfo pdfi = getPDFInfo(context); + + String svg = "http://www.w3.org/2000/svg"; + if (svg.equals(ns)) { + SVGHandler svghandler = new SVGHandler(); + svghandler.renderSVGDocument(context, doc, pdfi); + } else { + } + } + + /** + * Get the pdf information from the render context. + * + * @param context the renderer context + * @return the pdf information retrieved from the context + */ + public static PDFInfo getPDFInfo(RendererContext context) { + PDFInfo pdfi = new PDFInfo(); + pdfi.pdfDoc = (PDFDocument)context.getProperty(PDF_DOCUMENT); + pdfi.outputStream = (OutputStream)context.getProperty(OUTPUT_STREAM); + pdfi.pdfState = (PDFState)context.getProperty(PDF_STATE); + pdfi.pdfPage = (PDFPage)context.getProperty(PDF_PAGE); + pdfi.pdfContext = (PDFResourceContext)context.getProperty(PDF_CONTEXT); + pdfi.currentStream = (PDFStream)context.getProperty(PDF_STREAM); + pdfi.width = ((Integer)context.getProperty(PDF_WIDTH)).intValue(); + pdfi.height = ((Integer)context.getProperty(PDF_HEIGHT)).intValue(); + pdfi.fi = (FontInfo)context.getProperty(PDF_FONT_INFO); + pdfi.currentFontName = (String)context.getProperty(PDF_FONT_NAME); + pdfi.currentFontSize = ((Integer)context.getProperty(PDF_FONT_SIZE)).intValue(); + pdfi.currentXPosition = ((Integer)context.getProperty(PDF_XPOS)).intValue(); + pdfi.currentYPosition = ((Integer)context.getProperty(PDF_YPOS)).intValue(); + return pdfi; + } + + /** + * PDF information structure for drawing the XML document. + */ + public static class PDFInfo { + /** see PDF_DOCUMENT */ + public PDFDocument pdfDoc; + /** see OUTPUT_STREAM */ + public OutputStream outputStream; + /** see PDF_STATE */ + public PDFState pdfState; + /** see PDF_PAGE */ + public PDFPage pdfPage; + /** see PDF_CONTEXT */ + public PDFResourceContext pdfContext; + /** see PDF_STREAM */ + public PDFStream currentStream; + /** see PDF_WIDTH */ + public int width; + /** see PDF_HEIGHT */ + public int height; + /** see PDF_FONT_INFO */ + public FontInfo fi; + /** see PDF_FONT_NAME */ + public String currentFontName; + /** see PDF_FONT_SIZE */ + public int currentFontSize; + /** see PDF_XPOS */ + public int currentXPosition; + /** see PDF_YPOS */ + public int currentYPosition; + } + + /** + * This method is placed in an inner class so that we don't get class + * loading errors if batik is not present. + */ + protected class SVGHandler { + /** + * Render the svg document. + * @param context the renderer context + * @param doc the svg document + * @param pdfInfo the pdf information of the current context + */ + protected void renderSVGDocument(RendererContext context, Document doc, PDFInfo pdfInfo) { + int xOffset = pdfInfo.currentXPosition; + int yOffset = pdfInfo.currentYPosition; + + SVGUserAgent ua + = new SVGUserAgent(context.getUserAgent(), new AffineTransform()); + + GVTBuilder builder = new GVTBuilder(); + BridgeContext ctx = new BridgeContext(ua); + PDFTextElementBridge tBridge = new PDFTextElementBridge(pdfInfo.fi); + ctx.putBridge(tBridge); + + PDFAElementBridge aBridge = new PDFAElementBridge(); + // to get the correct transform we need to use the PDFState + AffineTransform transform = pdfInfo.pdfState.getTransform(); + transform.translate(xOffset / 1000f, yOffset / 1000f); + aBridge.setCurrentTransform(transform); + ctx.putBridge(aBridge); + + GraphicsNode root; + try { + root = builder.build(ctx, doc); + } catch (Exception e) { + context.getUserAgent().getLogger().error("svg graphic could not be built: " + + e.getMessage(), e); + return; + } + // get the 'width' and 'height' attributes of the SVG document + float w = (float)ctx.getDocumentSize().getWidth() * 1000f; + float h = (float)ctx.getDocumentSize().getHeight() * 1000f; + + float sx = pdfInfo.width / (float)w; + float sy = pdfInfo.height / (float)h; + + ctx = null; + builder = null; + + /* + * Clip to the svg area. + * Note: To have the svg overlay (under) a text area then use + * an fo:block-container + */ + pdfInfo.currentStream.add("q\n"); + // transform so that the coordinates (0,0) is from the top left + // and positive is down and to the right. (0,0) is where the + // viewBox puts it. + pdfInfo.currentStream.add(sx + " 0 0 " + sy + " " + xOffset / 1000f + " " + + yOffset / 1000f + " cm\n"); + + SVGSVGElement svg = ((SVGDocument)doc).getRootElement(); + AffineTransform at = ViewBox.getPreserveAspectRatioTransform(svg, w / 1000f, h / 1000f); + if (!at.isIdentity()) { + double[] vals = new double[6]; + at.getMatrix(vals); + pdfInfo.currentStream.add(PDFNumber.doubleOut(vals[0], 5) + " " + + PDFNumber.doubleOut(vals[1], 5) + " " + + PDFNumber.doubleOut(vals[2], 5) + " " + + PDFNumber.doubleOut(vals[3], 5) + " " + + PDFNumber.doubleOut(vals[4]) + " " + + PDFNumber.doubleOut(vals[5]) + " cm\n"); + } + + if (pdfInfo.pdfContext == null) { + pdfInfo.pdfContext = pdfInfo.pdfPage; + } + PDFGraphics2D graphics = new PDFGraphics2D(true, pdfInfo.fi, pdfInfo.pdfDoc, + pdfInfo.pdfContext, pdfInfo.pdfPage.referencePDF(), + pdfInfo.currentFontName, + pdfInfo.currentFontSize); + graphics.setGraphicContext(new org.apache.batik.ext.awt.g2d.GraphicContext()); + pdfInfo.pdfState.push(); + transform = new AffineTransform(); + // scale to viewbox + transform.translate(xOffset / 1000f, yOffset / 1000f); + pdfInfo.pdfState.setTransform(transform); + graphics.setPDFState(pdfInfo.pdfState); + graphics.setOutputStream(pdfInfo.outputStream); + try { + root.paint(graphics); + pdfInfo.currentStream.add(graphics.getString()); + } catch (Exception e) { + context.getUserAgent().getLogger().error("svg graphic could not be rendered: " + + e.getMessage(), e); + } + + pdfInfo.currentStream.add("Q\n"); + pdfInfo.pdfState.pop(); + } + } +} + diff --git a/src/java/org/apache/fop/render/pdf/package.html b/src/java/org/apache/fop/render/pdf/package.html new file mode 100644 index 000000000..1bcf2644f --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/package.html @@ -0,0 +1,6 @@ +<HTML> +<TITLE>org.apache.fop.render.pdf Package</TITLE> +<BODY> +<P>classes for rendering to PDF</P> +</BODY> +</HTML>
\ No newline at end of file |