diff options
92 files changed, 8677 insertions, 990 deletions
@@ -595,6 +595,7 @@ RetroWeaver will be added here --> <include name="org/apache/fop/render/pdf/**"/> <exclude name="org/apache/fop/render/pdf/PDFRenderer.class"/> <exclude name="org/apache/fop/render/pdf/PDFXMLHandler*"/> + <include name="org/apache/fop/render/intermediate/IFPainterConfigurator.class"/> <include name="org/apache/fop/render/*RendererConfigurator**"/> <include name="org/apache/fop/pdf/**"/> </patternset> @@ -881,25 +882,44 @@ RetroWeaver will be added here --> <test name="org.apache.fop.fotreetest.FOTreeTestSuite" todir="${junit.reports.dir}" outfile="TEST-FO-tree"/> </junit> </target> - <target name="junit-intermediate-format" depends="junit-compile, junit-layout" description="Runs FOP's intermediate format JUnit tests" if="xmlunit.present"> - <echo message="Running intermediate format tests"/> - <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure"> - <sysproperty key="basedir" value="${basedir}"/> - <sysproperty key="jawa.awt.headless" value="true"/> - <sysproperty key="fop.layoutengine.disabled" value="${layoutengine.disabled}"/> - <sysproperty key="fop.layoutengine.testset" value="standard"/> - <formatter type="brief" usefile="false"/> - <formatter type="plain" usefile="true"/> - <formatter type="xml" usefile="true"/> - <classpath> - <pathelement location="${build.dir}/test-classes"/> - <path refid="libs-build-classpath"/> - <fileset dir="build"> - <include name="fop.jar"/> - </fileset> - </classpath> - <test name="org.apache.fop.intermediate.IntermediateFormatTestSuite" todir="${junit.reports.dir}" outfile="TEST-intermediate-format"/> - </junit> + <macrodef name="junit-run"> + <attribute name="title"/> + <element name="tests"/> + <sequential> + <echo message="Running @{title} tests..."/> + <junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" errorproperty="fop.junit.error" failureproperty="fop.junit.failure"> + <sysproperty key="basedir" value="${basedir}"/> + <sysproperty key="jawa.awt.headless" value="true"/> + <sysproperty key="fop.layoutengine.disabled" value="${layoutengine.disabled}"/> + <formatter type="brief" usefile="false"/> + <formatter type="plain" usefile="true"/> + <formatter type="xml" usefile="true"/> + <classpath> + <pathelement location="${build.dir}/test-classes"/> + <path refid="libs-build-tools-classpath"/> + <fileset dir="build"> + <include name="fop.jar"/> + </fileset> + </classpath> + <tests/> + </junit> + </sequential> + </macrodef> + <target name="junit-area-tree-xml-format" depends="junit-compile" description="Runs FOP's area tree XML format JUnit tests" if="xmlunit.present"> + <junit-run title="area tree XML format"> + <tests> + <sysproperty key="fop.layoutengine.testset" value="standard"/> + <test name="org.apache.fop.intermediate.AreaTreeXMLFormatTestSuite" todir="${junit.reports.dir}" outfile="TEST-area-tree-xml-format"/> + </tests> + </junit-run> + </target> + <target name="junit-intermediate-format" depends="junit-compile" description="Runs FOP's intermediate format JUnit tests" if="xmlunit.present"> + <junit-run title="intermediate format"> + <tests> + <sysproperty key="fop.layoutengine.testset" value="standard"/> + <test name="org.apache.fop.intermediate.IntermediateFormatTestSuite" todir="${junit.reports.dir}" outfile="TEST-intermediate-format"/> + </tests> + </junit-run> </target> <target name="junit-text-linebreak" depends="junit-compile" description="Runs FOP's JUnit unicode linebreak tests" if="junit.present"> <echo message="Running tests for Unicode UAX#14 support"/> @@ -919,7 +939,7 @@ RetroWeaver will be added here --> <test name="org.apache.fop.text.linebreak.LineBreakStatusTest" todir="${junit.reports.dir}"/> </junit> </target> - <target name="junit" depends="junit-userconfig, junit-basic, junit-transcoder, junit-text-linebreak, junit-layout, junit-fotree, junit-intermediate-format" description="Runs all of FOP's JUnit tests" if="junit.present"> + <target name="junit" depends="junit-userconfig, junit-basic, junit-transcoder, junit-text-linebreak, junit-layout, junit-fotree, junit-area-tree-xml-format, junit-intermediate-format" description="Runs all of FOP's JUnit tests" if="junit.present"> <fail><condition><or><isset property="fop.junit.error"/><isset property="fop.junit.failure"/><not><isset property="hyphenation.present"/></not></or></condition> NOTE: ************************************************************************** diff --git a/examples/plan/src/org/apache/fop/plan/PreloaderPlan.java b/examples/plan/src/org/apache/fop/plan/PreloaderPlan.java index b8a7a612c..902560e96 100644 --- a/examples/plan/src/org/apache/fop/plan/PreloaderPlan.java +++ b/examples/plan/src/org/apache/fop/plan/PreloaderPlan.java @@ -66,7 +66,7 @@ public class PreloaderPlan extends AbstractImagePreloader { return info; } - private ImageInfo getImage(String uri, Source src, ImageContext context) { + private ImageInfo getImage(String uri, Source src, ImageContext context) throws IOException { InputStream in = new UnclosableInputStream(ImageUtil.needInputStream(src)); try { @@ -76,9 +76,16 @@ public class PreloaderPlan extends AbstractImagePreloader { DOMResult res = new DOMResult(); transformer.transform(source, res); + Document planDoc = (Document)res.getNode(); + if (!PlanElementMapping.NAMESPACE.equals( + planDoc.getDocumentElement().getNamespaceURI())) { + in.reset(); + return null; + } + //Have to render the plan to know its size PlanRenderer pr = new PlanRenderer(); - Document svgDoc = pr.createSVGDocument((Document)res.getNode()); + Document svgDoc = pr.createSVGDocument(planDoc); float width = pr.getWidth(); float height = pr.getHeight(); diff --git a/examples/plan/src/org/apache/fop/plan/SimplePlanDrawer.java b/examples/plan/src/org/apache/fop/plan/SimplePlanDrawer.java index b49256a31..46b07601a 100644 --- a/examples/plan/src/org/apache/fop/plan/SimplePlanDrawer.java +++ b/examples/plan/src/org/apache/fop/plan/SimplePlanDrawer.java @@ -29,6 +29,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.apache.batik.dom.svg.SVGDOMImplementation; + import org.apache.fop.svg.SVGUtilities; /** @@ -83,13 +84,15 @@ public class SimplePlanDrawer implements PlanDrawer { String title = ""; - DOMImplementation impl = - SVGDOMImplementation.getDOMImplementation(); + DOMImplementation impl + = SVGDOMImplementation.getDOMImplementation(); Document doc = impl.createDocument(SVG_NAMESPACE, "svg", null); Element svgRoot = doc.getDocumentElement(); - svgRoot.setAttributeNS(null, "width", "" + width); - svgRoot.setAttributeNS(null, "height", "" + height); + svgRoot.setAttributeNS(null, "width", Float.toString(width)); + svgRoot.setAttributeNS(null, "height", Float.toString(height)); + svgRoot.setAttributeNS(null, "viewBox", + "0 0 " + Float.toString(width) + " " + Float.toString(height)); svgRoot.setAttributeNS(null, "style", "font-size:" + 8 + ";font-family:" @@ -99,8 +102,8 @@ public class SimplePlanDrawer implements PlanDrawer { java.awt.Font.PLAIN, (int)fontSize); if (bord) { - Element border = - SVGUtilities.createRect(doc, 0, 0, width, height); + Element border + = SVGUtilities.createRect(doc, 0, 0, width, height); border.setAttributeNS(null, "style", "stroke:black;fill:none"); svgRoot.appendChild(border); } diff --git a/lib/xmlgraphics-commons-1.4svn.jar b/lib/xmlgraphics-commons-1.4svn.jar Binary files differindex 6c836d46c..1b7e1d7da 100644 --- a/lib/xmlgraphics-commons-1.4svn.jar +++ b/lib/xmlgraphics-commons-1.4svn.jar diff --git a/src/java/META-INF/services/org.apache.fop.render.ImageHandler b/src/java/META-INF/services/org.apache.fop.render.ImageHandler new file mode 100644 index 000000000..81accc88a --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.render.ImageHandler @@ -0,0 +1,5 @@ +org.apache.fop.render.pdf.PDFImageHandlerGraphics2D
+org.apache.fop.render.pdf.PDFImageHandlerRenderedImage
+org.apache.fop.render.pdf.PDFImageHandlerRawJPEG
+org.apache.fop.render.pdf.PDFImageHandlerRawCCITTFax
+org.apache.fop.render.pdf.PDFImageHandlerSVG
diff --git a/src/java/META-INF/services/org.apache.fop.render.Renderer b/src/java/META-INF/services/org.apache.fop.render.Renderer index 869798b0f..7548d7936 100644 --- a/src/java/META-INF/services/org.apache.fop.render.Renderer +++ b/src/java/META-INF/services/org.apache.fop.render.Renderer @@ -8,3 +8,4 @@ org.apache.fop.render.awt.AWTRendererMaker org.apache.fop.render.print.PrintRendererMaker org.apache.fop.render.afp.AFPRendererMaker org.apache.fop.render.pcl.PCLRendererMaker +org.apache.fop.render.intermediate.IFRendererMaker diff --git a/src/java/META-INF/services/org.apache.fop.render.intermediate.IFPainter b/src/java/META-INF/services/org.apache.fop.render.intermediate.IFPainter new file mode 100644 index 000000000..9bb645ebb --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.render.intermediate.IFPainter @@ -0,0 +1 @@ +org.apache.fop.render.pdf.PDFPainterMaker
\ No newline at end of file diff --git a/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter index c3c688a2c..804a66cc4 100644 --- a/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter +++ b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter @@ -1,2 +1,3 @@ org.apache.fop.image.loader.batik.ImageConverterSVG2G2D
+org.apache.fop.image.loader.batik.ImageConverterG2D2SVG
org.apache.fop.image.loader.batik.ImageConverterWMF2G2D
diff --git a/src/java/org/apache/fop/apps/MimeConstants.java b/src/java/org/apache/fop/apps/MimeConstants.java index 02a7ba81b..b8a9637a8 100644 --- a/src/java/org/apache/fop/apps/MimeConstants.java +++ b/src/java/org/apache/fop/apps/MimeConstants.java @@ -73,7 +73,8 @@ public interface MimeConstants { String MIME_FOP_PRINT = "application/X-fop-print"; /** Apache FOP's area tree XML */ String MIME_FOP_AREA_TREE = "application/X-fop-areatree"; - + /** Apache FOP's intermediate format XML */ + String MIME_FOP_IF = "application/X-fop-intermediate-format"; /** Proposed but non-registered MIME type for XSL-FO */ String MIME_XSL_FO = "text/xsl"; diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java index 07502c819..916745e4c 100644 --- a/src/java/org/apache/fop/cli/CommandLineOptions.java +++ b/src/java/org/apache/fop/cli/CommandLineOptions.java @@ -45,6 +45,7 @@ import org.apache.fop.pdf.PDFEncryptionParams; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.render.Renderer; import org.apache.fop.render.awt.AWTRenderer; +import org.apache.fop.render.intermediate.IFRenderer; import org.apache.fop.render.pdf.PDFRenderer; import org.apache.fop.render.print.PagesMode; import org.apache.fop.render.print.PrintRenderer; @@ -200,6 +201,13 @@ public class CommandLineOptions { //Make sure the prepared XMLRenderer is used foUserAgent.setRendererOverride(xmlRenderer); + } else if (MimeConstants.MIME_FOP_IF.equals(outputmode)) { + // render from FO to Intermediate Format + IFRenderer xml2Renderer = new IFRenderer(); + xml2Renderer.setUserAgent(foUserAgent); + + //Make sure the prepared IFRenderer is used + foUserAgent.setRendererOverride(xml2Renderer); } return true; } @@ -303,6 +311,8 @@ public class CommandLineOptions { i = i + parseCustomOutputOption(args, i); } else if (args[i].equals("-at")) { i = i + parseAreaTreeOption(args, i); + } else if (args[i].equals("-if")) { + i = i + parseIntermediateFormatOption(args, i); } else if (args[i].equals("-v")) { System.out.println("FOP Version " + Version.getVersion()); } else if (args[i].equals("-param")) { @@ -669,6 +679,23 @@ public class CommandLineOptions { } } + private int parseIntermediateFormatOption(String[] args, int i) throws FOPException { + setOutputMode(MimeConstants.MIME_FOP_IF); + if ((i + 1 == args.length) + || (args[i + 1].charAt(0) == '-')) { + throw new FOPException("you must specify the intermediate format output file"); + } else if ((i + 2 == args.length) + || (args[i + 2].charAt(0) == '-')) { + // only output file is specified + outfile = new File(args[i + 1]); + return 1; + } else { + // mimic format and output file have been specified + outfile = new File(args[i + 2]); + return 2; + } + } + private int parseAreaTreeInputOption(String[] args, int i) throws FOPException { inputmode = AREATREE_INPUT; if ((i + 1 == args.length) @@ -1081,6 +1108,7 @@ public class CommandLineOptions { + " -at [mime] out representation of area tree as XML (outfile req'd) \n" + " specify optional mime output to allow AT to be converted\n" + " to final format later\n" + + " -if out representation of area tree as intermediate format XML (outfile req'd)\n" + " -print input file will be rendered and sent to the printer \n" + " see options with \"-print help\" \n" + " -out mime outfile input will be rendered using the given MIME type\n" @@ -1173,6 +1201,9 @@ public class CommandLineOptions { } else { log.info("output file: " + outfile.toString()); } + } else if (MimeConstants.MIME_FOP_IF.equals(outputmode)) { + log.info("intermediate format"); + log.info("output file: " + outfile.toString()); } else { log.info(outputmode); if (this.useStdOut) { diff --git a/src/java/org/apache/fop/events/EventFormatter.xml b/src/java/org/apache/fop/events/EventFormatter.xml index 1f08456c9..82c976c27 100644 --- a/src/java/org/apache/fop/events/EventFormatter.xml +++ b/src/java/org/apache/fop/events/EventFormatter.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- +<?xml version="1.0" encoding="UTF-8"?><!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. @@ -14,9 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ---> -<!-- $Id$ --> -<catalogue xml:lang="en"> +--><!-- $Id$ --><catalogue xml:lang="en"> <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message> <message key="rule.markerDescendantOfFlow">An fo:marker is permitted only as the descendant of an fo:flow.</message> <message key="rule.retrieveMarkerDescendantOfStaticContent">An fo:retrieve-marker is permitted only as the descendant of an fo:static-content.</message> diff --git a/src/java/org/apache/fop/image/loader/batik/BatikImageFlavors.java b/src/java/org/apache/fop/image/loader/batik/BatikImageFlavors.java new file mode 100644 index 000000000..7766fdbcb --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/BatikImageFlavors.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import org.apache.batik.dom.svg.SVGDOMImplementation; + +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.XMLNamespaceEnabledImageFlavor; + +/** + * Image flavors used by Batik. + */ +public interface BatikImageFlavors { + + /** An SVG image in form of a W3C DOM instance */ + XMLNamespaceEnabledImageFlavor SVG_DOM = new XMLNamespaceEnabledImageFlavor( + ImageFlavor.XML_DOM, SVGDOMImplementation.SVG_NAMESPACE_URI); + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageConverterG2D2SVG.java b/src/java/org/apache/fop/image/loader/batik/ImageConverterG2D2SVG.java new file mode 100644 index 000000000..d56f543c7 --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageConverterG2D2SVG.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import java.awt.Dimension; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.apache.batik.dom.GenericDOMImplementation; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.svggen.SVGGeneratorContext; +import org.apache.batik.svggen.SVGGraphics2D; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageConverter; +import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; + +/** + * This ImageConverter converts Java2D images into SVG images. + */ +public class ImageConverterG2D2SVG extends AbstractImageConverter { + + /** {@inheritDoc} */ + public Image convert(Image src, Map hints) throws ImageException { + checkSourceFlavor(src); + ImageGraphics2D g2dImage = (ImageGraphics2D)src; + + DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation(); + + // Create an instance of org.w3c.dom.Document + Document document = domImpl.createDocument( + SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null); + Element root = document.getDocumentElement(); + + // Create an SVGGeneratorContext to customize SVG generation + SVGGeneratorContext genCtx = SVGGeneratorContext.createDefault(document); + genCtx.setComment("Generated by Apache Batik's SVGGraphics2D"); + genCtx.setEmbeddedFontsOn(true); + + // Create an instance of the SVG Generator + SVGGraphics2D g2d = new SVGGraphics2D(genCtx, true); + ImageSize size = src.getSize(); + Dimension dim = size.getDimensionMpt(); + g2d.setSVGCanvasSize(dim); + //SVGGraphics2D doesn't generate the viewBox by itself + root.setAttribute("viewBox", "0 0 " + dim.width + " " + dim.height); + + g2dImage.getGraphics2DImagePainter().paint(g2d, + new Rectangle2D.Float(0, 0, dim.width, dim.height)); + //Populate the document root with the generated SVG content. + g2d.getRoot(root); + + //Return the generated SVG image + ImageXMLDOM svgImage = new ImageXMLDOM(src.getInfo(), document, BatikImageFlavors.SVG_DOM); + g2d.dispose(); + return svgImage; + } + + /** {@inheritDoc} */ + public ImageFlavor getSourceFlavor() { + return ImageFlavor.GRAPHICS2D; + } + + /** {@inheritDoc} */ + public ImageFlavor getTargetFlavor() { + return BatikImageFlavors.SVG_DOM; + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java index 6c43e4878..b5d7511ca 100644 --- a/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java +++ b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java @@ -132,7 +132,7 @@ public class ImageConverterSVG2G2D extends AbstractImageConverter { /** {@inheritDoc} */ public ImageFlavor getSourceFlavor() { - return ImageFlavor.XML_DOM; + return BatikImageFlavors.SVG_DOM; } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java b/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java index 9203bf7e8..8935ac8d4 100644 --- a/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java +++ b/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java @@ -30,7 +30,7 @@ import org.apache.xmlgraphics.util.MimeConstants; public class ImageLoaderFactorySVG extends AbstractImageLoaderFactory { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { - ImageFlavor.XML_DOM}; + BatikImageFlavors.SVG_DOM}; private static final String[] MIMES = new String[] { MimeConstants.MIME_SVG}; diff --git a/src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java b/src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java index 483d6c502..c80f38c64 100644 --- a/src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java +++ b/src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java @@ -45,7 +45,7 @@ public class ImageLoaderSVG extends AbstractImageLoader { * @param targetFlavor the target flavor */ public ImageLoaderSVG(ImageFlavor targetFlavor) { - if (!(ImageFlavor.XML_DOM.equals(targetFlavor))) { + if (!(targetFlavor.isCompatible(ImageFlavor.XML_DOM))) { throw new IllegalArgumentException("Unsupported target ImageFlavor: " + targetFlavor); } this.targetFlavor = targetFlavor; diff --git a/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java index 916b80d02..5e4e37345 100644 --- a/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java +++ b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java @@ -34,7 +34,6 @@ import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.UnitProcessor; import org.apache.batik.bridge.UserAgent; import org.apache.batik.dom.svg.SAXSVGDocumentFactory; -import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.dom.svg.SVGOMDocument; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -198,7 +197,7 @@ public class PreloaderSVG extends AbstractImagePreloader { //The whole image had to be loaded for this, so keep it ImageXMLDOM xmlImage = new ImageXMLDOM(info, - doc, SVGDOMImplementation.SVG_NAMESPACE_URI); + doc, BatikImageFlavors.SVG_DOM); info.getCustomObjects().put(ImageInfo.ORIGINAL_IMAGE, xmlImage); return info; } diff --git a/src/java/org/apache/fop/pdf/PDFNumber.java b/src/java/org/apache/fop/pdf/PDFNumber.java index c2fc704da..3c103f3f2 100644 --- a/src/java/org/apache/fop/pdf/PDFNumber.java +++ b/src/java/org/apache/fop/pdf/PDFNumber.java @@ -19,13 +19,11 @@ package org.apache.fop.pdf; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; +import org.apache.fop.util.DecimalFormatCache; /** * This class represents a simple number object. It also contains contains some - * utility methods for outputing numbers to PDF. + * utility methods for outputting numbers to PDF. */ public class PDFNumber extends PDFObject { @@ -67,33 +65,6 @@ public class PDFNumber extends PDFObject { return doubleOut(doubleDown, 6); } - private static final String BASE_FORMAT = "0.################"; - - private static class DecimalFormatThreadLocal extends ThreadLocal { - - private int dec; - - public DecimalFormatThreadLocal(int dec) { - this.dec = dec; - } - - protected synchronized Object initialValue() { - String s = "0"; - if (dec > 0) { - s = BASE_FORMAT.substring(0, dec + 2); - } - DecimalFormat df = new DecimalFormat(s, new DecimalFormatSymbols(Locale.US)); - return df; - } - }; - //DecimalFormat is not thread-safe! - private static final ThreadLocal[] DECIMAL_FORMAT_CACHE = new DecimalFormatThreadLocal[17]; - static { - for (int i = 0, c = DECIMAL_FORMAT_CACHE.length; i < c; i++) { - DECIMAL_FORMAT_CACHE[i] = new DecimalFormatThreadLocal(i); - } - } - /** * Output a double value to a string suitable for PDF. * In this method it is possible to set the maximum @@ -104,12 +75,7 @@ public class PDFNumber extends PDFObject { * @return the value as a string */ public static String doubleOut(double doubleDown, int dec) { - if (dec < 0 || dec >= DECIMAL_FORMAT_CACHE.length) { - throw new IllegalArgumentException("Parameter dec must be between 1 and " - + (DECIMAL_FORMAT_CACHE.length + 1)); - } - DecimalFormat df = (DecimalFormat)DECIMAL_FORMAT_CACHE[dec].get(); - return df.format(doubleDown); + return DecimalFormatCache.getDecimalFormat(dec).format(doubleDown); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/pdf/PDFTextUtil.java b/src/java/org/apache/fop/pdf/PDFTextUtil.java index 735a7894d..6640f9b80 100644 --- a/src/java/org/apache/fop/pdf/PDFTextUtil.java +++ b/src/java/org/apache/fop/pdf/PDFTextUtil.java @@ -270,7 +270,15 @@ public abstract class PDFTextUtil { * @param adjust the glyph adjust value in thousands of text unit space. */ public void adjustGlyphTJ(double adjust) { - bufTJ.append(endText).append(" "); + if (bufTJ == null) { + bufTJ = new StringBuffer(); + } + if (bufTJ.length() > 0) { + bufTJ.append(endText).append(" "); + } + if (bufTJ.length() == 0) { + bufTJ.append("["); + } bufTJ.append(PDFNumber.doubleOut(adjust, DEC - 4)); bufTJ.append(" "); bufTJ.append(startText); diff --git a/src/java/org/apache/fop/render/AbstractImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/AbstractImageHandlerGraphics2D.java new file mode 100644 index 000000000..a79734d49 --- /dev/null +++ b/src/java/org/apache/fop/render/AbstractImageHandlerGraphics2D.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +import org.apache.fop.util.UnitConv; + +/** + * Abstract base class for ImageHandler implementations that process Java2D images through + * the Graphics2DImagePainter interface. + */ +public abstract class AbstractImageHandlerGraphics2D implements ImageHandler { + + /** + * Paints the image to a BufferedImage and returns that. + * @param painter the painter which will paint the actual image + * @param context the renderer context for the current renderer + * @param targetDimension the target dimensions of the image to be converted to a bitmap + * @param resolution the requested bitmap resolution + * @param gray true if the generated image should be in grayscales + * @param withAlpha true if an alpha channel should be created + * @return the generated BufferedImage + */ + protected BufferedImage paintToBufferedImage( + org.apache.xmlgraphics.java2d.Graphics2DImagePainter painter, + Dimension targetDimension, + int resolution, boolean gray, boolean withAlpha) { + int bmw = (int)Math.ceil(UnitConv.mpt2px(targetDimension.getWidth(), resolution)); + int bmh = (int)Math.ceil(UnitConv.mpt2px(targetDimension.getHeight(), resolution)); + BufferedImage bi; + if (gray) { + if (withAlpha) { + bi = createGrayBufferedImageWithAlpha(bmw, bmh); + } else { + bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_BYTE_GRAY); + } + } else { + if (withAlpha) { + bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_INT_ARGB); + } else { + bi = new BufferedImage(bmw, bmh, BufferedImage.TYPE_INT_RGB); + } + } + Graphics2D g2d = bi.createGraphics(); + try { + g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + setRenderingHintsForBufferedImage(g2d); + + g2d.setBackground(Color.white); + g2d.setColor(Color.black); + if (!withAlpha) { + g2d.clearRect(0, 0, bmw, bmh); + } + /* debug code + int off = 2; + g2d.drawLine(off, 0, off, bmh); + g2d.drawLine(bmw - off, 0, bmw - off, bmh); + g2d.drawLine(0, off, bmw, off); + g2d.drawLine(0, bmh - off, bmw, bmh - off); + */ + double sx = (double)bmw / targetDimension.getWidth(); + double sy = (double)bmh / targetDimension.getHeight(); + g2d.scale(sx, sy); + + //Paint the image on the BufferedImage + Rectangle2D area = new Rectangle2D.Double( + 0.0, 0.0, targetDimension.getWidth(), targetDimension.getHeight()); + painter.paint(g2d, area); + } finally { + g2d.dispose(); + } + return bi; + } + + private static BufferedImage createGrayBufferedImageWithAlpha(int width, int height) { + BufferedImage bi; + boolean alphaPremultiplied = true; + int bands = 2; + int[] bits = new int[bands]; + for (int i = 0; i < bands; i++) { + bits[i] = 8; + } + ColorModel cm = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_GRAY), + bits, + true, alphaPremultiplied, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + WritableRaster wr = Raster.createInterleavedRaster( + DataBuffer.TYPE_BYTE, + width, height, bands, + new Point(0, 0)); + bi = new BufferedImage(cm, wr, alphaPremultiplied, null); + return bi; + } + + /** + * Sets rendering hints on the Graphics2D created for painting to a BufferedImage. Subclasses + * can modify the settings to customize the behavior. + * @param g2d the Graphics2D instance + */ + protected void setRenderingHintsForBufferedImage(Graphics2D g2d) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + } + +} diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index bf9f660be..4805d4df9 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -469,7 +469,8 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { } - private static final QName FOX_TRANSFORM + /** Constant for the fox:transform extension attribute */ + protected static final QName FOX_TRANSFORM = new QName(ExtensionElementMapping.URI, "fox:transform"); /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index e80775890..18c540fed 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -860,8 +860,10 @@ public abstract class AbstractRenderer double[] matrix = new double[6]; at.getMatrix(matrix); //Convert to millipoints - matrix[4] = matrix[4] * 1000; - matrix[5] = matrix[5] * 1000; + //Math.round() because things like this can happen: 65.6 * 1000 = 65.599999999999999 + //which is bad for testing + matrix[4] = Math.round(matrix[4] * 1000); + matrix[5] = Math.round(matrix[5] * 1000); return new AffineTransform(matrix); } } diff --git a/src/java/org/apache/fop/render/AbstractRendererConfigurator.java b/src/java/org/apache/fop/render/AbstractRendererConfigurator.java index 33d5a3bcf..09540dfbb 100644 --- a/src/java/org/apache/fop/render/AbstractRendererConfigurator.java +++ b/src/java/org/apache/fop/render/AbstractRendererConfigurator.java @@ -23,6 +23,7 @@ import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.apps.FOUserAgent; /** @@ -68,7 +69,7 @@ public abstract class AbstractRendererConfigurator { * @param mimeType the MIME type of the renderer * @return the requested configuration subtree, null if there's no configuration */ - private Configuration getRendererConfig(String mimeType) { + protected Configuration getRendererConfig(String mimeType) { Configuration cfg = userAgent.getFactory().getUserConfig(); if (cfg == null) { if (log.isDebugEnabled()) { diff --git a/src/java/org/apache/fop/render/AbstractRenderingContext.java b/src/java/org/apache/fop/render/AbstractRenderingContext.java new file mode 100644 index 000000000..7bacac58d --- /dev/null +++ b/src/java/org/apache/fop/render/AbstractRenderingContext.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +import org.apache.fop.apps.FOUserAgent; + +/** + * Abstract base class for RenderingContext implementations. + */ +public abstract class AbstractRenderingContext implements RenderingContext { + + private FOUserAgent userAgent; + + /** + * Main constructor. + * @param userAgent the user agent + */ + public AbstractRenderingContext(FOUserAgent userAgent) { + this.userAgent = userAgent; + } + + /** + * Returns the user agent. + * + * @return The user agent + */ + public FOUserAgent getUserAgent() { + return userAgent; + } + +} + diff --git a/src/java/org/apache/fop/render/Graphics2DImagePainter.java b/src/java/org/apache/fop/render/Graphics2DImagePainter.java index da802418c..6b4754720 100644 --- a/src/java/org/apache/fop/render/Graphics2DImagePainter.java +++ b/src/java/org/apache/fop/render/Graphics2DImagePainter.java @@ -27,4 +27,4 @@ package org.apache.fop.render; public interface Graphics2DImagePainter extends org.apache.xmlgraphics.java2d.Graphics2DImagePainter { -}
\ No newline at end of file +} diff --git a/src/java/org/apache/fop/render/ImageAdapter.java b/src/java/org/apache/fop/render/ImageAdapter.java index a67d43bdc..757be43b1 100644 --- a/src/java/org/apache/fop/render/ImageAdapter.java +++ b/src/java/org/apache/fop/render/ImageAdapter.java @@ -34,10 +34,10 @@ public interface ImageAdapter { * Paints an image at the given position. * @param image the image which will be painted * @param context the renderer context for the current renderer - * @param x X position of the image - * @param y Y position of the image - * @param width width of the image - * @param height height of the image + * @param x X position of the image (in millipoints) + * @param y Y position of the image (in millipoints) + * @param width width of the image (in millipoints) + * @param height height of the image (in millipoints) * @throws IOException In case of an I/O error while writing the output format */ void paintImage(RenderedImage image, diff --git a/src/java/org/apache/fop/render/ImageHandler.java b/src/java/org/apache/fop/render/ImageHandler.java new file mode 100644 index 000000000..42ae5fd49 --- /dev/null +++ b/src/java/org/apache/fop/render/ImageHandler.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +import java.awt.Rectangle; +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; + +/** + * This interface is used for handling all sorts of image types for PDF output. + */ +public interface ImageHandler { + + /** + * Returns the priority for this image handler. A lower value means higher priority. This + * information is used to build the ordered/prioritized list of supported ImageFlavors. + * The built-in handlers use priorities between 100 and 999. + * @return a positive integer (>0) indicating the priority + */ + int getPriority(); + + /** + * Returns the {@link ImageFlavor}s supported by this instance + * @return the supported image flavors + */ + ImageFlavor[] getSupportedImageFlavors(); + + /** + * Indicates whether the image handler is compatible with the indicated target represented + * by the rendering context object and with the image to be processed. The image is also + * passed as a parameter because a handler might not support every subtype of image that is + * presented. For example: in the case of {@code ImageXMLDOM}, the image might carry an SVG + * or some other XML format. One handler might only handle SVG but no other XML format. + * @param targetContext the target rendering context + * @param image the image to be processed (or null if only to check based on the rendering + * context) + * @return true if this handler is compatible with the target rendering context + */ + boolean isCompatible(RenderingContext targetContext, Image image); + + /** + * Returns the {@link Image} subclass supported by this instance. + * @return the Image type + */ + Class getSupportedImageClass(); + + /** + * Handles the given {@link Image} instance painting it at the indicated position in the + * output format being generated. + * @param context the rendering context + * @param image the image to be handled + * @param pos the position and scaling of the image relative to the origin point of the + * current viewport (in millipoints) + * @throws IOException if an I/O error occurs + */ + void handleImage(RenderingContext context, Image image, + Rectangle pos) throws IOException; + +} diff --git a/src/java/org/apache/fop/render/ImageHandlerRegistry.java b/src/java/org/apache/fop/render/ImageHandlerRegistry.java new file mode 100644 index 000000000..3a241138a --- /dev/null +++ b/src/java/org/apache/fop/render/ImageHandlerRegistry.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.util.Service; + +/** + * This class holds references to various image handlers. It also + * supports automatic discovery of additional handlers available through + * the class path. + */ +public class ImageHandlerRegistry { + + /** the logger */ + private static Log log = LogFactory.getLog(ImageHandlerRegistry.class); + + private static final Comparator HANDLER_COMPARATOR = new Comparator() { + public int compare(Object o1, Object o2) { + ImageHandler h1 = (ImageHandler)o1; + ImageHandler h2 = (ImageHandler)o2; + return h1.getPriority() - h2.getPriority(); + } + }; + + /** Map containing image handlers for various {@code Image} subclasses. */ + private Map handlers = new java.util.HashMap(); + /** List containing the same handlers as above but ordered by priority */ + private List handlerList = new java.util.LinkedList(); + + private int handlerRegistrations; + + /** + * Default constructor. + */ + public ImageHandlerRegistry() { + discoverHandlers(); + } + + /** + * Add an PDFImageHandler. The handler itself is inspected to find out what it supports. + * @param classname the fully qualified class name + */ + public void addHandler(String classname) { + try { + ImageHandler handlerInstance + = (ImageHandler)Class.forName(classname).newInstance(); + addHandler(handlerInstance); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Could not find " + + classname); + } catch (InstantiationException e) { + throw new IllegalArgumentException("Could not instantiate " + + classname); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Could not access " + + classname); + } catch (ClassCastException e) { + throw new IllegalArgumentException(classname + + " is not an " + + ImageHandler.class.getName()); + } + } + + /** + * Add an image handler. The handler itself is inspected to find out what it supports. + * @param handler the ImageHandler instance + */ + public synchronized void addHandler(ImageHandler handler) { + Class imageClass = handler.getSupportedImageClass(); + //List + this.handlers.put(imageClass, handler); + + //Sorted insert (sort by priority) + ListIterator iter = this.handlerList.listIterator(); + while (iter.hasNext()) { + ImageHandler h = (ImageHandler)iter.next(); + if (HANDLER_COMPARATOR.compare(handler, h) < 0) { + iter.previous(); + break; + } + } + iter.add(handler); + this.handlerRegistrations++; + } + + /** + * Returns an {@code ImageHandler} which handles an specific image type given the MIME type + * of the image. + * @param targetContext the target rendering context that is used for identifying compatibility + * @param image the Image to be handled + * @return the image handler responsible for handling the image or null if none is available + */ + public ImageHandler getHandler(RenderingContext targetContext, Image image) { + ListIterator iter = this.handlerList.listIterator(); + while (iter.hasNext()) { + ImageHandler h = (ImageHandler)iter.next(); + if (h.isCompatible(targetContext, image)) { + //Return the first handler in the prioritized list that is compatible + return h; + } + } + return null; + } + + /** + * Returns the ordered array of supported image flavors. The array needs to be ordered by + * priority so the image loader framework can return the preferred image type. + * @return the array of image flavors + */ + public synchronized ImageFlavor[] getSupportedFlavors(RenderingContext context) { + //Extract all ImageFlavors into a single array + List flavors = new java.util.ArrayList(); + Iterator iter = this.handlerList.iterator(); + while (iter.hasNext()) { + ImageHandler handler = (ImageHandler)iter.next(); + if (handler.isCompatible(context, null)) { + ImageFlavor[] f = handler.getSupportedImageFlavors(); + for (int i = 0; i < f.length; i++) { + flavors.add(f[i]); + } + } + } + return (ImageFlavor[])flavors.toArray(new ImageFlavor[flavors.size()]); + } + + /** + * Discovers ImageHandler implementations through the classpath and dynamically + * registers them. + */ + private void discoverHandlers() { + // add mappings from available services + Iterator providers = Service.providers(ImageHandler.class); + if (providers != null) { + while (providers.hasNext()) { + ImageHandler handler = (ImageHandler)providers.next(); + try { + if (log.isDebugEnabled()) { + log.debug("Dynamically adding ImageHandler: " + + handler.getClass().getName()); + } + addHandler(handler); + } catch (IllegalArgumentException e) { + log.error("Error while adding ImageHandler", e); + } + + } + } + } +} diff --git a/src/java/org/apache/fop/render/PrintRendererConfigurator.java b/src/java/org/apache/fop/render/PrintRendererConfigurator.java index e8127ae34..001a99dca 100644 --- a/src/java/org/apache/fop/render/PrintRendererConfigurator.java +++ b/src/java/org/apache/fop/render/PrintRendererConfigurator.java @@ -86,6 +86,19 @@ public class PrintRendererConfigurator extends AbstractRendererConfigurator PrintRenderer printRenderer = (PrintRenderer)renderer; FontResolver fontResolver = printRenderer.getFontResolver(); + + List embedFontInfoList = buildFontList(cfg, fontResolver); + printRenderer.addFontList(embedFontInfoList); + } + + /** + * Builds the font list from configuration. + * @param cfg the configuration object + * @param fontResolver a font resolver + * @return the list of {@code EmbedFontInfo} objects + * @throws FOPException if an error occurs while processing the configuration + */ + protected List buildFontList(Configuration cfg, FontResolver fontResolver) throws FOPException { FopFactory factory = userAgent.getFactory(); FontManager fontManager = factory.getFontManager(); if (fontResolver == null) { @@ -104,7 +117,7 @@ public class PrintRendererConfigurator extends AbstractRendererConfigurator if (fontCache != null && fontCache.hasChanged()) { fontCache.save(); } - printRenderer.addFontList(embedFontInfoList); + return embedFontInfoList; } /** diff --git a/src/java/org/apache/fop/render/RendererContext.java b/src/java/org/apache/fop/render/RendererContext.java index 08ca76957..e52588176 100644 --- a/src/java/org/apache/fop/render/RendererContext.java +++ b/src/java/org/apache/fop/render/RendererContext.java @@ -22,7 +22,6 @@ package org.apache.fop.render; //Java import java.util.Map; -//FOP import org.apache.fop.apps.FOUserAgent; /** @@ -30,7 +29,7 @@ import org.apache.fop.apps.FOUserAgent; * so that external handlers can get information to be able to render to the * render target. */ -public class RendererContext { +public class RendererContext implements RenderingContext { private String mime; private AbstractRenderer renderer; diff --git a/src/java/org/apache/fop/render/RendererFactory.java b/src/java/org/apache/fop/render/RendererFactory.java index a77ee6a03..412f3e2cd 100644 --- a/src/java/org/apache/fop/render/RendererFactory.java +++ b/src/java/org/apache/fop/render/RendererFactory.java @@ -34,6 +34,10 @@ import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.fo.FOEventHandler; +import org.apache.fop.render.intermediate.AbstractIFPainterMaker; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; +import org.apache.fop.render.intermediate.IFRenderer; /** * Factory for FOEventHandlers and Renderers. @@ -45,7 +49,7 @@ public class RendererFactory { private Map rendererMakerMapping = new java.util.HashMap(); private Map eventHandlerMakerMapping = new java.util.HashMap(); - + private Map painterMakerMapping = new java.util.HashMap(); /** * Main constructor. @@ -53,6 +57,7 @@ public class RendererFactory { public RendererFactory() { discoverRenderers(); discoverFOEventHandlers(); + discoverPainters(); } /** @@ -90,6 +95,23 @@ public class RendererFactory { } /** + * Add a new painter maker. If another maker has already been registered for a + * particular MIME type, this call overwrites the existing one. + * @param maker the painter maker + */ + public void addPainterMaker(AbstractIFPainterMaker maker) { + String[] mimes = maker.getSupportedMimeTypes(); + for (int i = 0; i < mimes.length; i++) { + //This overrides any renderer previously set for a MIME type + if (painterMakerMapping.get(mimes[i]) != null) { + log.trace("Overriding painter for " + mimes[i] + + " with " + maker.getClass().getName()); + } + painterMakerMapping.put(mimes[i], maker); + } + } + + /** * Add a new RendererMaker. If another maker has already been registered for a * particular MIME type, this call overwrites the existing one. * @param className the fully qualified class name of the RendererMaker @@ -142,6 +164,32 @@ public class RendererFactory { } /** + * Add a new painter maker. If another maker has already been registered for a + * particular MIME type, this call overwrites the existing one. + * @param className the fully qualified class name of the painter maker + */ + public void addPainterMaker(String className) { + try { + AbstractIFPainterMaker makerInstance + = (AbstractIFPainterMaker)Class.forName(className).newInstance(); + addPainterMaker(makerInstance); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Could not find " + + className); + } catch (InstantiationException e) { + throw new IllegalArgumentException("Could not instantiate " + + className); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Could not access " + + className); + } catch (ClassCastException e) { + throw new IllegalArgumentException(className + + " is not an " + + AbstractIFPainterMaker.class.getName()); + } + } + + /** * Returns a RendererMaker which handles the given MIME type. * @param mime the requested output format * @return the requested RendererMaker or null if none is available @@ -164,6 +212,17 @@ public class RendererFactory { } /** + * Returns a RendererMaker which handles the given MIME type. + * @param mime the requested output format + * @return the requested RendererMaker or null if none is available + */ + public AbstractIFPainterMaker getPainterMaker(String mime) { + AbstractIFPainterMaker maker + = (AbstractIFPainterMaker)painterMakerMapping.get(mime); + return maker; + } + + /** * Creates a Renderer object based on render-type desired * @param userAgent the user agent for access to configuration * @param outputFormat the MIME type of the output format to use (ex. "application/pdf"). @@ -176,21 +235,30 @@ public class RendererFactory { return userAgent.getRendererOverride(); } else { AbstractRendererMaker maker = getRendererMaker(outputFormat); - if (maker == null) { - throw new UnsupportedOperationException( - "No renderer for the requested format available: " + outputFormat); - } - Renderer rend = maker.makeRenderer(userAgent); - rend.setUserAgent(userAgent); - RendererConfigurator configurator = maker.getConfigurator(userAgent); - if (configurator != null) { - configurator.configure(rend); + if (maker != null) { + Renderer rend = maker.makeRenderer(userAgent); + rend.setUserAgent(userAgent); + RendererConfigurator configurator = maker.getConfigurator(userAgent); + if (configurator != null) { + configurator.configure(rend); + } + return rend; + } else { + AbstractIFPainterMaker painterMaker = getPainterMaker(outputFormat); + if (painterMaker != null) { + IFRenderer rend = new IFRenderer(); + rend.setUserAgent(userAgent); + IFPainter painter = createPainter(userAgent, outputFormat); + rend.setPainter(painter); + return rend; + } else { + throw new UnsupportedOperationException( + "No renderer for the requested format available: " + outputFormat); + } } - return rend; } } - /** * Creates FOEventHandler instances based on the desired output. * @param userAgent the user agent for access to configuration @@ -206,30 +274,70 @@ public class RendererFactory { return userAgent.getFOEventHandlerOverride(); } else { AbstractFOEventHandlerMaker maker = getFOEventHandlerMaker(outputFormat); - if (maker == null) { + if (maker != null) { + return maker.makeFOEventHandler(userAgent, out); + } else { AbstractRendererMaker rendMaker = getRendererMaker(outputFormat); - if (rendMaker == null && userAgent.getRendererOverride() == null) { - throw new UnsupportedOperationException( - "Don't know how to handle \"" + outputFormat + "\" as an output format." - + " Neither an FOEventHandler, nor a Renderer could be found" - + " for this output format."); + AbstractIFPainterMaker painterMaker = null; + boolean outputStreamMissing = (userAgent.getRendererOverride() == null); + if (rendMaker == null) { + painterMaker = getPainterMaker(outputFormat); + if (painterMaker != null) { + outputStreamMissing &= (out == null) && (painterMaker.needsOutputStream()); + } } else { - if (out == null - && userAgent.getRendererOverride() == null - && rendMaker.needsOutputStream()) { + outputStreamMissing &= (out == null) && (rendMaker.needsOutputStream()); + } + if (userAgent.getRendererOverride() != null + || rendMaker != null + || painterMaker != null) { + if (outputStreamMissing) { throw new FOPException( "OutputStream has not been set"); } //Found a Renderer so we need to construct an AreaTreeHandler. return new AreaTreeHandler(userAgent, outputFormat, out); + } else { + throw new UnsupportedOperationException( + "Don't know how to handle \"" + outputFormat + "\" as an output format." + + " Neither an FOEventHandler, nor a Renderer could be found" + + " for this output format."); } - } else { - return maker.makeFOEventHandler(userAgent, out); } } } /** + * Creates a {@code IFPainter} object based on render-type desired + * @param userAgent the user agent for access to configuration + * @param outputFormat the MIME type of the output format to use (ex. "application/pdf"). + * @return the new {@code IFPainter} instance + * @throws FOPException if the painter cannot be properly constructed + */ + public IFPainter createPainter(FOUserAgent userAgent, String outputFormat) + throws FOPException { + /* + if (userAgent.getIFPainterOverride() != null) { + return userAgent.getIFPainterOverride(); + } else { + */ + AbstractIFPainterMaker maker = getPainterMaker(outputFormat); + if (maker == null) { + throw new UnsupportedOperationException( + "No renderer for the requested format available: " + outputFormat); + } + IFPainter painter = maker.makePainter(userAgent); + painter.setUserAgent(userAgent); + IFPainterConfigurator configurator = maker.getConfigurator(userAgent); + if (configurator != null) { + configurator.configure(painter); + configurator.setupFontInfo(painter); + } + return painter; + //} + } + + /** * @return an array of all supported MIME types */ public String[] listSupportedMimeTypes() { @@ -242,6 +350,10 @@ public class RendererFactory { while (iter.hasNext()) { lst.add(((String)iter.next())); } + iter = this.painterMakerMapping.keySet().iterator(); + while (iter.hasNext()) { + lst.add(((String)iter.next())); + } Collections.sort(lst); return (String[])lst.toArray(new String[lst.size()]); } @@ -296,4 +408,29 @@ public class RendererFactory { } } + /** + * Discovers {@code IFPainter} implementations through the classpath and dynamically + * registers them. + */ + private void discoverPainters() { + // add mappings from available services + Iterator providers + = Service.providers(IFPainter.class); + if (providers != null) { + while (providers.hasNext()) { + AbstractIFPainterMaker maker = (AbstractIFPainterMaker)providers.next(); + try { + if (log.isDebugEnabled()) { + log.debug("Dynamically adding maker for IFPainter: " + + maker.getClass().getName()); + } + addPainterMaker(maker); + } catch (IllegalArgumentException e) { + log.error("Error while adding maker for IFPainter", e); + } + + } + } + } + } diff --git a/src/java/org/apache/fop/render/RenderingContext.java b/src/java/org/apache/fop/render/RenderingContext.java new file mode 100644 index 000000000..a6f958328 --- /dev/null +++ b/src/java/org/apache/fop/render/RenderingContext.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render; + +import org.apache.fop.apps.FOUserAgent; + +/** + * Implementations of this interface provide context information needed by supporting classes + * during specific tasks (like image rendering). + */ +public interface RenderingContext { + + /** + * Returns the MIME type associated with the current output format. + * @return the MIME type (ex. application/pdf) + */ + String getMimeType(); + + /** + * Returns the user agent. The user agent is used to access configuration and other information + * for the rendering process. + * @return the user agent + */ + FOUserAgent getUserAgent(); + +} diff --git a/src/java/org/apache/fop/render/afp/AFPEventProducer.xml b/src/java/org/apache/fop/render/afp/AFPEventProducer.xml index 8eec9b656..b0eeeb202 100644 --- a/src/java/org/apache/fop/render/afp/AFPEventProducer.xml +++ b/src/java/org/apache/fop/render/afp/AFPEventProducer.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.afp.AFPEventProducer.warnDefaultFontSetup">No AFP fonts configured. Using default setup.</message> </catalogue> diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml index a05af3e21..47acd74ad 100644 --- a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml +++ b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoFilename">No filename information available. Stopping early after the first page.</message> <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoMultiWriter">Image writer does not support multiple images. Only the first page has been produced.</message> <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.noImageWriterFound">Could not get an ImageWriter to produce "{mime}". The most likely explanation for this is a class loading problem.</message> diff --git a/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFPainter.java new file mode 100644 index 000000000..7f82f3d93 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFPainter.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; + +import javax.xml.transform.Result; +import javax.xml.transform.stream.StreamResult; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import org.apache.fop.fonts.FontCollection; +import org.apache.fop.fonts.FontEventAdapter; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontManager; +import org.apache.fop.fonts.base14.Base14FontCollection; + +/** + * Abstract base class for binary-writing IFPainter implementations. + */ +public abstract class AbstractBinaryWritingIFPainter extends AbstractIFPainter { + + /** The output stream to write the document to */ + protected OutputStream outputStream; + + private boolean ownOutputStream; + + /** Font configuration */ + protected FontInfo fontInfo; + + /** {@inheritDoc} */ + public void setResult(Result result) throws IFException { + if (result instanceof StreamResult) { + StreamResult streamResult = (StreamResult)result; + OutputStream out = streamResult.getOutputStream(); + if (out == null) { + if (streamResult.getWriter() != null) { + throw new IllegalArgumentException( + "FOP cannot use a Writer. Please supply an OutputStream!"); + } + try { + URL url = new URL(streamResult.getSystemId()); + File f = FileUtils.toFile(url); + if (f != null) { + out = new java.io.FileOutputStream(f); + } else { + out = url.openConnection().getOutputStream(); + } + } catch (IOException ioe) { + throw new IFException("I/O error while opening output stream" , ioe); + } + out = new java.io.BufferedOutputStream(out); + this.ownOutputStream = true; + } + if (out == null) { + throw new IllegalArgumentException("Need a StreamResult with an OutputStream"); + } + this.outputStream = out; + } else { + throw new UnsupportedOperationException( + "Unsupported Result subclass: " + result.getClass().getName()); + } + } + + /** + * Returns the {@code FontInfo} object. + * @return the font info + */ + public FontInfo getFontInfo() { + return this.fontInfo; + } + + /** {@inheritDoc} */ + public void setFontInfo(FontInfo fontInfo) { + this.fontInfo = fontInfo; + } + + /** {@inheritDoc} */ + public void setDefaultFontInfo() { + FontManager fontManager = getUserAgent().getFactory().getFontManager(); + FontCollection[] fontCollections = new FontCollection[] { + new Base14FontCollection(fontManager.isBase14KerningEnabled()) + }; + + FontInfo fi = new FontInfo(); + fi.setEventListener(new FontEventAdapter(getUserAgent().getEventBroadcaster())); + fontManager.setup(fi, fontCollections); + setFontInfo(fi); + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + if (this.ownOutputStream) { + IOUtils.closeQuietly(this.outputStream); + this.outputStream = null; + } + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java new file mode 100644 index 000000000..c7d6a578b --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Map; + +import javax.xml.transform.dom.DOMSource; + +import org.w3c.dom.Document; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.events.ResourceEventProducer; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.ImageHandlerRegistry; +import org.apache.fop.render.RenderingContext; + +/** + * Abstract base class for IFPainter implementations. + */ +public abstract class AbstractIFPainter implements IFPainter { + + /** logging instance */ + private static Log log = LogFactory.getLog(AbstractIFPainter.class); + + /** non-URI that can be used in feedback messages that an image is an instream-object */ + protected static final String INSTREAM_OBJECT_URI = "(instream-object)"; + + private FOUserAgent userAgent; + + /** Image handler registry */ + protected ImageHandlerRegistry imageHandlerRegistry = new ImageHandlerRegistry(); + + /** + * Default constructor. + */ + public AbstractIFPainter() { + } + + /** {@inheritDoc} */ + public void setUserAgent(FOUserAgent ua) { + if (this.userAgent != null) { + throw new IllegalStateException("The user agent was already set"); + } + this.userAgent = ua; + } + + /** + * Returns the user agent. + * @return the user agent + */ + protected FOUserAgent getUserAgent() { + return this.userAgent; + } + + private AffineTransform combine(AffineTransform[] transforms) { + AffineTransform at = new AffineTransform(); + for (int i = 0, c = transforms.length; i < c; i++) { + at.concatenate(transforms[i]); + } + return at; + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) + throws IFException { + startViewport(combine(transforms), size, clipRect); + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform[] transforms) throws IFException { + startGroup(combine(transforms)); + } + + /** + * Creates a new RenderingContext instance. + * @return the new rendering context. + */ + protected abstract RenderingContext createRenderingContext(); + + /** + * Loads a preloaded image and draws it using a suitable image handler. + * @param info the information object of the preloaded image + * @param rect the rectangle in which to paint the image + * @throws ImageException if there's an error while processing the image + * @throws IOException if there's an I/O error while loading the image + */ + protected void drawImageUsingImageHandler(ImageInfo info, Rectangle rect) + throws ImageException, IOException { + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); + + //Load and convert the image to a supported format + RenderingContext context = createRenderingContext(); + Map hints = ImageUtil.getDefaultHints(sessionContext); + org.apache.xmlgraphics.image.loader.Image img = manager.getImage( + info, imageHandlerRegistry.getSupportedFlavors(context), + hints, sessionContext); + + //First check for a dynamically registered handler + ImageHandler handler = imageHandlerRegistry.getHandler(context, img); + if (handler == null) { + throw new UnsupportedOperationException( + "No ImageHandler available for image: " + + info + " (" + img.getClass().getName() + ")"); + } + + if (log.isDebugEnabled()) { + log.debug("Using ImageHandler: " + handler.getClass().getName()); + } + try { + //TODO foreign attributes + handler.handleImage(context, img, rect); + } catch (IOException ioe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageWritingError(this, ioe); + return; + } + } + + /** + * Default drawing method for handling an image referenced by a URI. + * @param uri the image's URI + * @param rect the rectangle in which to paint the image + */ + protected void drawImageUsingURI(String uri, Rectangle rect) { + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; + try { + ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); + info = manager.getImageInfo(uri, sessionContext); + + drawImageUsingImageHandler(info, rect); + } catch (ImageException ie) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); + } catch (FileNotFoundException fe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); + } catch (IOException ioe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); + } + } + + /** + * Default drawing method for handling a foreign object in the form of a DOM document. + * @param doc the DOM document containing the foreign object + * @param rect the rectangle in which to paint the image + */ + protected void drawImageUsingDocument(Document doc, Rectangle rect) { + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; + try { + info = manager.preloadImage(null, new DOMSource(doc)); + + drawImageUsingImageHandler(info, rect); + } catch (ImageException ie) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, + (info != null ? info.toString() : INSTREAM_OBJECT_URI), ie, null); + } catch (FileNotFoundException fe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, + (info != null ? info.toString() : INSTREAM_OBJECT_URI), fe, null); + } catch (IOException ioe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, + (info != null ? info.toString() : INSTREAM_OBJECT_URI), ioe, null); + } + } + + +} diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java new file mode 100644 index 000000000..aa653cd3e --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.apache.fop.apps.FOUserAgent; + +/** + * Base class for factory classes which instantiate {@code IFPainter}s and provide information + * about them. + */ +public abstract class AbstractIFPainterMaker { + + /** + * Instantiates a new {@code IFPainter}. + * @param userAgent the user agent + * @return the newly instantiated painter + */ + public abstract IFPainter makePainter(FOUserAgent userAgent); + + /** + * @return Indicates whether this painter requires an OutputStream to work with. + */ + public abstract boolean needsOutputStream(); + + /** + * @return an array of MIME types the painter supports. + */ + public abstract String[] getSupportedMimeTypes(); + + /** + * Returns a configurator object that can be used to + * configure the painter. + * @param userAgent the user agent + * @return a configurator object that can be used to configure the painter + */ + public abstract IFPainterConfigurator getConfigurator(FOUserAgent userAgent); + + /** + * Indicates whether a specific MIME type is supported by this painter. + * @param mimeType the MIME type (ex. "application/pdf") + * @return true if the MIME type is supported + */ + public boolean isMimeTypeSupported(String mimeType) { + String[] mimes = getSupportedMimeTypes(); + for (int i = 0; i < mimes.length; i++) { + if (mimes[i].equals(mimeType)) { + return true; + } + } + return false; + } +} diff --git a/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java new file mode 100644 index 000000000..63238354b --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.util.DecimalFormatCache; + +/** + * Abstract base class for XML-writing IFPainter implementations. + */ +public abstract class AbstractXMLWritingIFPainter extends AbstractIFPainter { + + private static final Attributes EMPTY_ATTS = new AttributesImpl(); + + /** Constant for the "CDATA" attribute type. */ + protected static final String CDATA = "CDATA"; + + /** + * Default SAXTransformerFactory that can be used by subclasses. + */ + protected SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + /** Main SAX ContentHandler to receive the generated SAX events. */ + protected ContentHandler handler; + + /** {@inheritDoc} */ + public void setResult(Result result) throws IFException { + if (result instanceof SAXResult) { + SAXResult saxResult = (SAXResult)result; + this.handler = saxResult.getHandler(); + } else { + this.handler = createContentHandler(result); + } + } + + /** {@inheritDoc} */ + public void setFontInfo(FontInfo fontInfo) { + //nop, not used + } + + /** {@inheritDoc} */ + public void setDefaultFontInfo() { + //nop, not used + } + + /** + * Returns the main namespace used for generated XML content. + * @return the main namespace + */ + protected abstract String getMainNamespace(); + + /** + * Creates a ContentHandler for the given JAXP Result instance. + * @param result the JAXP Result instance + * @return the requested SAX ContentHandler + * @throws IFException if an error occurs setting up the output + */ + protected ContentHandler createContentHandler(Result result) throws IFException { + try { + TransformerHandler tHandler = tFactory.newTransformerHandler(); + Transformer transformer = tHandler.getTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + tHandler.setResult(result); + return tHandler; + } catch (TransformerConfigurationException tce) { + throw new IFException( + "Error while setting up the serializer for SVG output", tce); + } + } + + /* ---=== helper methods ===--- */ + + private static String format(double value) { + if (value == -0.0) { + //Don't allow negative zero because of testing + //See http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2.3 + value = 0.0; + } + return DecimalFormatCache.getDecimalFormat(6).format(value); + } + + /** + * Converts an {@code AffineTransform} instance to an SVG style transform method. + * @param transform the transformation matrix + * @param sb the StringBuffer to write the transform method to + * @return the StringBuffer passed to this method + */ + public static StringBuffer toString(AffineTransform transform, StringBuffer sb) { + if (transform.isIdentity()) { + return sb; + } + double[] matrix = new double[6]; + transform.getMatrix(matrix); + if (matrix[0] == 1 && matrix[3] == 1 && matrix[1] == 0 && matrix[2] == 0) { + sb.append("translate("); + sb.append(format(matrix[4])); + if (matrix[5] != 0) { + sb.append(',').append(format(matrix[5])); + } + } else { + sb.append("matrix("); + for (int i = 0; i < 6; i++) { + if (i > 0) { + sb.append(','); + } + sb.append(format(matrix[i])); + } + } + sb.append(')'); + return sb; + } + + /** + * Converts an {@code AffineTransform} array to an SVG style transform method sequence. + * @param transforms the transformation matrix array + * @param sb the StringBuffer to write the transform method sequence to + * @return the StringBuffer passed to this method + */ + public static StringBuffer toString(AffineTransform[] transforms, StringBuffer sb) { + for (int i = 0, c = transforms.length; i < c; i++) { + if (i > 0) { + sb.append(' '); + } + toString(transforms[i], sb); + } + return sb; + } + + /** + * Converts an {@code AffineTransform} array to an SVG style transform method sequence. + * @param transforms the transformation matrix array + * @return the formatted array + */ + public static String toString(AffineTransform[] transforms) { + return toString(transforms, new StringBuffer()).toString(); + } + + /** + * Converts an {@code AffineTransform} instance to an SVG style transform method. + * @param transform the transformation matrix + * @return the formatted array + */ + public static String toString(AffineTransform transform) { + return toString(transform, new StringBuffer()).toString(); + } + + /** + * Convenience method to generate a startElement SAX event. + * @param localName the local name of the element + * @param atts the attributes + * @throws SAXException if a SAX exception occurs + */ + protected void startElement(String localName, Attributes atts) throws SAXException { + handler.startElement(getMainNamespace(), localName, localName, atts); + } + + /** + * Convenience method to generate a startElement SAX event. + * @param localName the local name of the element + * @throws SAXException if a SAX exception occurs + */ + protected void startElement(String localName) throws SAXException { + handler.startElement(getMainNamespace(), localName, localName, EMPTY_ATTS); + } + + /** + * Convenience method to generate a endElement SAX event. + * @param localName the local name of the element + * @throws SAXException if a SAX exception occurs + */ + protected void endElement(String localName) throws SAXException { + handler.endElement(getMainNamespace(), localName, localName); + } + + /** + * Convenience method to generate an empty element. + * @param localName the local name of the element + * @param atts the attributes + * @throws SAXException if a SAX exception occurs + */ + protected void element(String localName, Attributes atts) throws SAXException { + handler.startElement(getMainNamespace(), localName, localName, atts); + handler.endElement(getMainNamespace(), localName, localName); + } + + /** + * Adds an attribute to a given {@code AttributesImpl} instance. + * @param atts the attributes collection + * @param attribute the attribute to add + * @param value the attribute's CDATA value + */ + protected void addAttribute(AttributesImpl atts, QName attribute, String value) { + atts.addAttribute(attribute.getNamespaceURI(), + attribute.getLocalName(), attribute.getQName(), CDATA, value); + } + + /** + * Converts an array of integer coordinates into a space-separated string. + * @param coordinates the coordinates + * @return the space-separated array of coordinates + */ + protected String toString(int[] coordinates) { + if (coordinates == null) { + return ""; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0, c = coordinates.length; i < c; i++) { + if (i > 0) { + sb.append(' '); + } + sb.append(Integer.toString(coordinates[i])); + } + return sb.toString(); + } + + /** + * Converts a rectangle into a space-separated string. + * @param rect the rectangle + * @return the space-separated array of coordinates + */ + protected String toString(Rectangle rect) { + if (rect == null) { + return ""; + } + StringBuffer sb = new StringBuffer(); + sb.append(rect.x).append(' ').append(rect.y).append(' '); + sb.append(rect.width).append(' ').append(rect.height); + return sb.toString(); + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java b/src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java new file mode 100644 index 000000000..f13139bdb --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.geom.AffineTransform; +import java.io.Reader; +import java.util.List; + +import org.apache.batik.parser.ParseException; +import org.apache.batik.parser.TransformListHandler; +import org.apache.batik.parser.TransformListParser; + +/** + * This class parses a sequence of transformations into an array of {@code AffineTransform} + * instances. + */ +public class AffineTransformArrayParser implements TransformListHandler { + + private static final AffineTransform[] EMPTY_ARRAY = new AffineTransform[0]; + + private List transforms; + + /** + * Utility method for creating an AffineTransform array. + * @param r The reader used to read the transform specification. + * @return the AffineTransform array + * @throws ParseException if there's a parse error + */ + public static AffineTransform[] createAffineTransform(Reader r) + throws ParseException { + TransformListParser p = new TransformListParser(); + AffineTransformArrayParser th = new AffineTransformArrayParser(); + + p.setTransformListHandler(th); + p.parse(r); + + return th.getAffineTransforms(); + } + + /** + * Utility method for creating an AffineTransform. + * @param s The transform specification. + * @return the AffineTransform array + * @throws ParseException if there's a parse error + */ + public static AffineTransform[] createAffineTransform(String s) + throws ParseException { + if (s == null) { + return EMPTY_ARRAY; + } + TransformListParser p = new TransformListParser(); + AffineTransformArrayParser th = new AffineTransformArrayParser(); + + p.setTransformListHandler(th); + p.parse(s); + + return th.getAffineTransforms(); + } + + /** + * Returns the AffineTransform array initialized during the last parsing. + * @return the array or null if this handler has not been used by + * a parser. + */ + public AffineTransform[] getAffineTransforms() { + if (this.transforms == null) { + return null; + } else { + int count = this.transforms.size(); + return (AffineTransform[])this.transforms.toArray(new AffineTransform[count]); + } + } + + /** {@inheritDoc} */ + public void startTransformList() throws ParseException { + this.transforms = new java.util.ArrayList(); + } + + /** {@inheritDoc} */ + public void matrix(float a, float b, float c, float d, float e, float f) + throws ParseException { + this.transforms.add(new AffineTransform(a, b, c, d, e, f)); + } + + /** {@inheritDoc} */ + public void rotate(float theta) throws ParseException { + this.transforms.add(AffineTransform.getRotateInstance(Math.toRadians(theta))); + } + + /** {@inheritDoc} */ + public void rotate(float theta, float cx, float cy) throws ParseException { + AffineTransform at + = AffineTransform.getRotateInstance(Math.toRadians(theta), cx, cy); + this.transforms.add(at); + } + + /** {@inheritDoc} */ + public void translate(float tx) throws ParseException { + AffineTransform at = AffineTransform.getTranslateInstance(tx, 0); + this.transforms.add(at); + } + + /** {@inheritDoc} */ + public void translate(float tx, float ty) throws ParseException { + AffineTransform at = AffineTransform.getTranslateInstance(tx, ty); + this.transforms.add(at); + } + + /** {@inheritDoc} */ + public void scale(float sx) throws ParseException { + this.transforms.add(AffineTransform.getScaleInstance(sx, sx)); + } + + /** {@inheritDoc} */ + public void scale(float sx, float sy) throws ParseException { + this.transforms.add(AffineTransform.getScaleInstance(sx, sy)); + } + + /** {@inheritDoc} */ + public void skewX(float skx) throws ParseException { + this.transforms.add + (AffineTransform.getShearInstance(Math.tan(Math.toRadians(skx)), 0)); + } + + /** {@inheritDoc} */ + public void skewY(float sky) throws ParseException { + this.transforms.add + (AffineTransform.getShearInstance(0, Math.tan(Math.toRadians(sky)))); + } + + /** {@inheritDoc} */ + public void endTransformList() throws ParseException { + } +} diff --git a/src/java/org/apache/fop/render/intermediate/DelegatingFragmentContentHandler.java b/src/java/org/apache/fop/render/intermediate/DelegatingFragmentContentHandler.java new file mode 100644 index 000000000..cbd6798da --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/DelegatingFragmentContentHandler.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; + +import org.apache.fop.util.DelegatingContentHandler; + +/** + * This class is a {@code DelegatingContentHandler} subclass which swallows the + * {@code #startDocument()} and {@code #endDocument()} methods. This is useful for handling + * XML fragments. + */ +public class DelegatingFragmentContentHandler extends DelegatingContentHandler { + + /** + * Main constructor + * @param delegate the content handler to delegate the SAX events to + */ + public DelegatingFragmentContentHandler(ContentHandler delegate) { + setDelegateContentHandler(delegate); + if (delegate instanceof LexicalHandler) { + setDelegateLexicalHandler((LexicalHandler)delegate); + } + if (delegate instanceof DTDHandler) { + setDelegateDTDHandler((DTDHandler)delegate); + } + if (delegate instanceof EntityResolver) { + setDelegateEntityResolver((EntityResolver)delegate); + } + if (delegate instanceof ErrorHandler) { + setDelegateErrorHandler((ErrorHandler)delegate); + } + } + + /** {@inheritDoc} */ + public void startDocument() throws SAXException { + //nop/ignore + } + + /** {@inheritDoc} */ + public void endDocument() throws SAXException { + //nop/ignore + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFConstants.java b/src/java/org/apache/fop/render/intermediate/IFConstants.java new file mode 100644 index 000000000..b90e77d07 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFConstants.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.apps.MimeConstants; + +/** + * Constants for the intermediate format. + */ +public interface IFConstants { + + /** MIME type of the intermediate format. */ + String MIME_TYPE = MimeConstants.MIME_FOP_IF; + + /** XML namespace of the intermediate format. */ + String NAMESPACE = "http://xmlgraphics.apache.org/fop/intermediate"; + + /** XML namespace. */ + String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"; + + /** Namespace prefix for XLink */ + String XLINK_PREFIX = "xlink"; + /** XML namespace for XLink */ + String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"; + /** xlink:href attribute */ + QName XLINK_HREF = new QName(XLINK_NAMESPACE, XLINK_PREFIX, "href"); + + String EL_DOCUMENT = "document"; + String EL_HEADER = "header"; + String EL_PAGE_SEQUENCE = "page-sequence"; + String EL_PAGE = "page"; + String EL_PAGE_HEADER = "page-header"; + String EL_PAGE_TRAILER = "page-trailer"; + String EL_PAGE_CONTENT = "content"; + String EL_VIEWPORT = "viewport"; + String EL_GROUP = "g"; + String EL_IMAGE = "image"; +} diff --git a/src/java/org/apache/fop/render/intermediate/IFContentHandler.java b/src/java/org/apache/fop/render/intermediate/IFContentHandler.java new file mode 100644 index 000000000..55c65d82a --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFContentHandler.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + +public class IFContentHandler implements ContentHandler { + + public void characters(char[] arg0, int arg1, int arg2) throws SAXException { + // TODO Auto-generated method stub + + } + + public void endDocument() throws SAXException { + // TODO Auto-generated method stub + + } + + public void endElement(String arg0, String arg1, String arg2) + throws SAXException { + // TODO Auto-generated method stub + + } + + public void endPrefixMapping(String arg0) throws SAXException { + // TODO Auto-generated method stub + + } + + public void ignorableWhitespace(char[] arg0, int arg1, int arg2) + throws SAXException { + // TODO Auto-generated method stub + + } + + public void processingInstruction(String arg0, String arg1) + throws SAXException { + // TODO Auto-generated method stub + + } + + public void setDocumentLocator(Locator arg0) { + // TODO Auto-generated method stub + + } + + public void skippedEntity(String arg0) throws SAXException { + // TODO Auto-generated method stub + + } + + public void startDocument() throws SAXException { + // TODO Auto-generated method stub + + } + + public void startElement(String arg0, String arg1, String arg2, + Attributes arg3) throws SAXException { + // TODO Auto-generated method stub + + } + + public void startPrefixMapping(String arg0, String arg1) + throws SAXException { + // TODO Auto-generated method stub + + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFException.java b/src/java/org/apache/fop/render/intermediate/IFException.java new file mode 100644 index 000000000..c3f17f9ca --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFException.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +/** + * Exception thrown by code dealing with FOP's intermediate format. + */ +public class IFException extends Exception { + + private static final long serialVersionUID = 0L; + + /** + * Constructs a new exception with the specified detail message and + * cause. <p>Note that the detail message associated with + * <code>cause</code> is <i>not</i> automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A <tt>null</tt> value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public IFException(String message, Exception cause) { + super(message, cause); + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java b/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java new file mode 100644 index 000000000..6183bbbad --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.util.List; + +import org.apache.xmlgraphics.java2d.GraphicContext; + +/** + * Specialized graphic context class for the intermediate format renderer. + */ +public class IFGraphicContext extends GraphicContext { + + private List groupList = new java.util.ArrayList(); + + /** + * Default constructor. + */ + public IFGraphicContext() { + super(); + } + + /** + * Copy constructor. + * @param graphicContext the graphic context to make a copy of + */ + protected IFGraphicContext(IFGraphicContext graphicContext) { + super(graphicContext); + //We don't clone groupDepth! + } + + /** {@inheritDoc} */ + public Object clone() { + return new IFGraphicContext(this); + } + + public void pushGroup(Group group) { + //this.groupDepth++; + this.groupList.add(group); + for (int i = 0, c = group.getTransforms().length; i < c; i++) { + transform(group.getTransforms()[i]); + } + } + + public Group[] getGroups() { + return (Group[])this.groupList.toArray(new Group[getGroupStackSize()]); + } + + public Group[] dropGroups() { + Group[] groups = getGroups(); + this.groupList.clear(); + return groups; + } + + public int getGroupStackSize() { + return this.groupList.size(); + } + + public static class Group { + + private AffineTransform[] transforms; + + public Group(AffineTransform[] transforms) { + this.transforms = transforms; + } + + public Group(AffineTransform transform) { + this(new AffineTransform[] {transform}); + } + + public AffineTransform[] getTransforms() { + return this.transforms; + } + + public void start(IFPainter painter) throws IFException { + painter.startGroup(transforms); + } + + public void end(IFPainter painter) throws IFException { + painter.endGroup(); + } + + /** {@inheritDoc} */ + public String toString() { + StringBuffer sb = new StringBuffer("group: "); + AbstractXMLWritingIFPainter.toString(getTransforms(), sb); + return sb.toString(); + } + + } + + public static class Viewport extends Group { + + private Dimension size; + private Rectangle clipRect; + + public Viewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) { + super(transforms); + this.size = size; + this.clipRect = clipRect; + } + + public Viewport(AffineTransform transform, Dimension size, Rectangle clipRect) { + this(new AffineTransform[] {transform}, size, clipRect); + } + + public Dimension getSize() { + return this.size; + } + + public Rectangle getClipRect() { + return this.clipRect; + } + + public void start(IFPainter painter) throws IFException { + painter.startViewport(getTransforms(), size, clipRect); + } + + public void end(IFPainter painter) throws IFException { + painter.endViewport(); + } + + /** {@inheritDoc} */ + public String toString() { + StringBuffer sb = new StringBuffer("viewport: "); + AbstractXMLWritingIFPainter.toString(getTransforms(), sb); + sb.append(", ").append(getSize()); + if (getClipRect() != null) { + sb.append(", ").append(getClipRect()); + } + return sb.toString(); + } + + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java new file mode 100644 index 000000000..a201c4b06 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.util.Map; + +import javax.xml.transform.Result; + +import org.w3c.dom.Document; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.FontInfo; + +/** + * Interface used to paint whole documents layouted by Apache FOP. + * <p> + * Call sequence: + * <p> + * <pre> + * startDocument() + * startDocumentHeader() + * [handleExtension()]* + * endDocumentHeader() + * [ + * startPageSequence() + * [ + * startPage() + * startPageHeader() + * [handleExtension()]* + * endPageHeader() + * startPageContent() + * (#pageContent)+ + * endPageContent() + * startPageTrailer() + * (addTarget())* + * endPageTrailer() + * endPage() + * ]* + * endPageSequence() + * ]* + * endDocument() + * + * #box: + * startBox() + * (#pageContent)+ + * endBox() + * + * #pageContent: + * ( + * setFont() | + * drawText() | + * drawRect() | + * drawImage() | + * TODO etc. etc. | + * handleExtensionObject() + * ) + * </pre> + */ +public interface IFPainter { + + /** + * Set the user agent. + * @param userAgent The user agent + */ + void setUserAgent(FOUserAgent userAgent); + + /** + * Sets the JAXP Result object to receive the generated content. + * @param result the JAXP Result object to receive the generated content + * @throws IFException if an error occurs setting up the output + */ + void setResult(Result result) throws IFException; + + /** + * Sets the font set to work with. + * @param fontInfo the font info object + */ + void setFontInfo(FontInfo fontInfo); + + /** + * Sets the default font set (with no custom configuration). + */ + void setDefaultFontInfo(); + + /** + * Indicates whether the painter supports to handle the pages in mixed order rather than + * ascending order. + * @return true if out-of-order handling is supported + */ + boolean supportsPagesOutOfOrder(); + + /** + * Returns the MIME type of the output format that is generated by this implementation. + * @return the MIME type + */ + String getMimeType(); + + /** + * Indicates the start of a document. This method may only be called once before any other + * event method. + * @throws IFException if an error occurs while handling this event + */ + void startDocument() throws IFException; + + /** + * Indicates the end of a document. This method may only be called once after the whole + * document has been handled. Implementations can release resources (close streams). It is + * an error to call any event method after this method. + * @throws IFException if an error occurs while handling this event + */ + void endDocument() throws IFException; + + /** + * Indicates the start of the document header. This method is called right after the + * {@code #startDocument()} method. Extensions sent to this painter between + * {@code #startDocumentHeader()} and {@code #endDocumentHeader()} apply to the document as + * a whole (like document metadata). + * @throws IFException if an error occurs while handling this event + */ + void startDocumentHeader() throws IFException; + + /** + * Indicates the end of the document header. This method is called before the first + * page sequence. + * @throws IFException if an error occurs while handling this event + */ + void endDocumentHeader() throws IFException; + + /** + * Indicates the start of a new page sequence. + * @param id the page sequence's identifier (or null if none is available) + * @throws IFException if an error occurs while handling this event + */ + void startPageSequence(String id) throws IFException; + /** + * Indicates the end of a page sequence. + * @throws IFException if an error occurs while handling this event + */ + void endPageSequence() throws IFException; + + /** + * Indicates the start of a new page. + * @param index the index of the page (0-based) + * @param name the page name (usually the formatted page number) + * @param size the size of the page (equivalent to the MediaBox in PDF) + * @throws IFException if an error occurs while handling this event + */ + void startPage(int index, String name, Dimension size) throws IFException; + + /** + * Indicates the end of a page + * @throws IFException if an error occurs while handling this event + */ + void endPage() throws IFException; + + /** + * Indicates the start of the page header. + * @throws IFException if an error occurs while handling this event + */ + void startPageHeader() throws IFException; + + /** + * Indicates the end of the page header. + * @throws IFException if an error occurs while handling this event + */ + void endPageHeader() throws IFException; + + /** + * Indicates the start of the page content. + * @throws IFException if an error occurs while handling this event + */ + void startPageContent() throws IFException; + + /** + * Indicates the end of the page content. + * @throws IFException if an error occurs while handling this event + */ + void endPageContent() throws IFException; + + /** + * Indicates the start of the page trailer. The page trailer is used for writing down page + * elements which are only know after handling the page itself (like PDF targets). + * @throws IFException if an error occurs while handling this event + */ + void startPageTrailer() throws IFException; + + /** + * @todo Solve with extension because not all formats support that? + */ + void addTarget(String name, int x, int y) throws IFException; + + /** + * Indicates the end of the page trailer. + * @throws IFException if an error occurs while handling this event + */ + void endPageTrailer() throws IFException; + + void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) throws IFException; + void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) throws IFException; + //For transform, Batik's org.apache.batik.parser.TransformListHandler/Parser can be used + void endViewport() throws IFException; + + void startGroup(AffineTransform[] transforms) throws IFException; + void startGroup(AffineTransform transform) throws IFException; + void endGroup() throws IFException; + + /** + * Updates the current font. + * @param family the font family (or null if there's no change) + * @param style the font style (or null if there's no change) + * @param weight the font weight (or null if there's no change) + * @param variant the font variant (or null if there's no change) + * @param size the font size (or null if there's no change) + * @param color the text color (or null if there's no change) + * @throws IFException if an error occurs while handling this event + */ + void setFont(String family, String style, Integer weight, String variant, Integer size, + Color color) throws IFException; + + /** + * Draws text. The initial coordinates (x and y) point to the starting point at the normal + * baseline of the font. The arrays (dx and dy) are optional and can be used to achieve + * effects like kerning. + * @param x X-coordinate of the starting point of the text + * @param y Y-coordinate of the starting point of the text + * @param dx an array of adjustment values for each character in X-direction + * @param dy an array of adjustment values for each character in Y-direction + * @param text the text + * @throws IFException if an error occurs while handling this event + */ + void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException; + + /** + * Draws a rectangle. Either fill or stroke has to be specified. + * @param rect the rectangle's coordinates and extent + * @param fill the fill paint (may be null) + * @param stroke the stroke color (may be null) + * @throws IFException if an error occurs while handling this event + */ + void drawRect(Rectangle rect, Paint fill, Color stroke) throws IFException; + + /** + * Draws an image identified by a URI inside a given rectangle. This is the equivalent to + * an fo:external-graphic in XSL-FO. + * @param uri the image's URI + * @param rect the rectangle in which the image shall be painted + * @param foreignAttributes a optional Map with foreign attributes (Map<QName,String>) + * @throws IFException if an error occurs while handling this event + */ + void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException; + + /** + * Draws an image (represented by a DOM document) inside a given rectangle. This is the + * equivalent to an fo:instream-foreign-object in XSL-FO. + * @param doc the DOM document containing the foreign object + * @param rect the rectangle in which the image shall be painted + * @param foreignAttributes a optional Map with foreign attributes (Map<QName,String>) + * @throws IFException if an error occurs while handling this event + */ + void drawImage(Document doc, Rectangle rect, Map foreignAttributes) + throws IFException; + //Note: For now, all foreign objects are handled as DOM documents. At the moment, all known + //implementations use a DOM anyway, so optimizing this to work with SAX wouldn't result in + //any performance benefits. The IFRenderer itself has a DOM anyway. Only the IFParser could + //potentially profit if there's an image handler that can efficiently deal with the foreign + //object without building a DOM. + + //etc. etc. + + /** + * Handles an extension object. This can be a DOM document or any arbitrary + * object. If an implementation doesn't know how to handle a particular extension it is simply + * ignored. + * @param extension the extension object + * @throws IFException if an error occurs while handling this event + */ + void handleExtensionObject(Object extension) throws IFException; + + //TODO Prototype the following: + //ContentHandler handleExtension() throws Exception +} diff --git a/src/java/org/apache/fop/render/intermediate/IFPainterConfigurator.java b/src/java/org/apache/fop/render/intermediate/IFPainterConfigurator.java new file mode 100644 index 000000000..71a3a484a --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFPainterConfigurator.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.apache.fop.apps.FOPException; + +/** + * This interface is implemented by classes that configure an {@code IFPainter} instance. + */ +public interface IFPainterConfigurator { + + /** + * Configures a painter. + * @param painter the painter instance + * @throws FOPException if an error occurs while configuring the object + */ + void configure(IFPainter painter) throws FOPException; + + /** + * Sets up the {@code FontInfo} object for the IFPainter. + * @param painter the painter instance + * @throws FOPException if an error occurs while configuring the object + */ + void setupFontInfo(IFPainter painter) throws FOPException; +} diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java new file mode 100644 index 000000000..ef0efacb2 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -0,0 +1,607 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXTransformerFactory; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.ElementMappingRegistry; +import org.apache.fop.fo.expr.PropertyException; +import org.apache.fop.util.ColorUtil; +import org.apache.fop.util.ContentHandlerFactory; +import org.apache.fop.util.ContentHandlerFactoryRegistry; +import org.apache.fop.util.ConversionUtils; +import org.apache.fop.util.DOMBuilderContentHandlerFactory; +import org.apache.fop.util.DefaultErrorListener; + +/** + * This is a parser for the intermediate format XML which converts the intermediate file into + * {@code IFPainter} events. + */ +public class IFParser implements IFConstants { + + /** Logger instance */ + protected static Log log = LogFactory.getLog(IFParser.class); + + private static SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + /** + * Parses an intermediate file and paints it. + * @param src the Source instance pointing to the intermediate file + * @param painter the intermediate format painter used to process the IF events + * @param userAgent the user agent + * @throws TransformerException if an error occurs while parsing the area tree XML + */ + public void parse(Source src, IFPainter painter, FOUserAgent userAgent) + throws TransformerException { + Transformer transformer = tFactory.newTransformer(); + transformer.setErrorListener(new DefaultErrorListener(log)); + + SAXResult res = new SAXResult(getContentHandler(painter, userAgent)); + + transformer.transform(src, res); + } + + /** + * Creates a new ContentHandler instance that you can send the area tree XML to. The parsed + * pages are added to the AreaTreeModel instance you pass in as a parameter. + * @param painter the intermediate format painter used to process the IF events + * @param userAgent the user agent + * @return the ContentHandler instance to receive the SAX stream from the area tree XML + */ + public ContentHandler getContentHandler(IFPainter painter, FOUserAgent userAgent) { + ElementMappingRegistry elementMappingRegistry + = userAgent.getFactory().getElementMappingRegistry(); + return new Handler(painter, userAgent, elementMappingRegistry); + } + + private static class Handler extends DefaultHandler { + + private Map elementHandlers = new java.util.HashMap(); + + private IFPainter painter; + private FOUserAgent userAgent; + private ElementMappingRegistry elementMappingRegistry; + + private Attributes lastAttributes; + + private StringBuffer content = new StringBuffer(); + private boolean ignoreCharacters = true; + + //private Stack delegateStack = new Stack(); + private int delegateDepth; + private ContentHandler delegate; + private boolean inForeignObject; + private Document foreignObject; + + public Handler(IFPainter painter, FOUserAgent userAgent, + ElementMappingRegistry elementMappingRegistry) { + this.painter = painter; + this.userAgent = userAgent; + this.elementMappingRegistry = elementMappingRegistry; + elementHandlers.put(EL_DOCUMENT, new DocumentHandler()); + elementHandlers.put(EL_HEADER, new DocumentHeaderHandler()); + elementHandlers.put(EL_PAGE_SEQUENCE, new PageSequenceHandler()); + elementHandlers.put(EL_PAGE, new PageHandler()); + elementHandlers.put(EL_PAGE_HEADER, new PageHeaderHandler()); + elementHandlers.put(EL_PAGE_CONTENT, new PageContentHandler()); + elementHandlers.put(EL_PAGE_TRAILER, new PageTrailerHandler()); + //Page content + elementHandlers.put(EL_VIEWPORT, new ViewportHandler()); + elementHandlers.put(EL_GROUP, new GroupHandler()); + elementHandlers.put("font", new FontHandler()); + elementHandlers.put("text", new TextHandler()); + elementHandlers.put("rect", new RectHandler()); + elementHandlers.put(EL_IMAGE, new ImageHandler()); + } + + + /** {@inheritDoc} */ + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (delegate != null) { + //delegateStack.push(qName); + delegateDepth++; + delegate.startElement(uri, localName, qName, attributes); + } else { + boolean handled = true; + if (NAMESPACE.equals(uri)) { + lastAttributes = new AttributesImpl(attributes); + ElementHandler elementHandler = (ElementHandler)elementHandlers.get(localName); + content.setLength(0); + ignoreCharacters = true; + if (elementHandler != null) { + ignoreCharacters = elementHandler.ignoreCharacters(); + try { + elementHandler.startElement(attributes); + } catch (IFException ife) { + handleIFException(ife); + } + } else if ("extension-attachments".equals(localName)) { + //TODO implement me + } else { + handled = false; + } + } else { + ContentHandlerFactoryRegistry registry + = userAgent.getFactory().getContentHandlerFactoryRegistry(); + ContentHandlerFactory factory = registry.getFactory(uri); + if (factory == null) { + DOMImplementation domImplementation + = elementMappingRegistry.getDOMImplementationForNamespace(uri); + if (domImplementation == null) { + throw new SAXException("No DOMImplementation could be" + + " identified to handle namespace: " + uri); + } + factory = new DOMBuilderContentHandlerFactory(uri, domImplementation); + } + delegate = factory.createContentHandler(); + delegateDepth++; + delegate.startDocument(); + delegate.startElement(uri, localName, qName, attributes); + } + if (!handled) { + if (uri == null || uri.length() == 0) { + throw new SAXException("Unhandled element " + localName + + " in namespace: " + uri); + } else { + log.warn("Unhandled element " + localName + + " in namespace: " + uri); + } + } + } + } + + private void handleIFException(IFException ife) throws SAXException { + if (ife.getCause() instanceof SAXException) { + //unwrap + throw (SAXException)ife.getCause(); + } else { + //wrap + throw new SAXException(ife); + } + } + + + /** {@inheritDoc} */ + public void endElement(String uri, String localName, String qName) throws SAXException { + if (delegate != null) { + delegate.endElement(uri, localName, qName); + delegateDepth--; + if (delegateDepth == 0) { + delegate.endDocument(); + if (delegate instanceof ContentHandlerFactory.ObjectSource) { + Object obj = ((ContentHandlerFactory.ObjectSource)delegate).getObject(); + if (inForeignObject) { + this.foreignObject = (Document)obj; + } else { + handleExternallyGeneratedObject(obj); + } + } + delegate = null; //Sub-document is processed, return to normal processing + } + } else { + if (NAMESPACE.equals(uri)) { + ElementHandler elementHandler = (ElementHandler)elementHandlers.get(localName); + if (elementHandler != null) { + try { + elementHandler.endElement(); + } catch (IFException ife) { + handleIFException(ife); + } + content.setLength(0); + } + ignoreCharacters = true; + } else { + if (log.isTraceEnabled()) { + log.trace("Ignoring " + localName + " in namespace: " + uri); + } + } + } + } + + // ============== Element handlers for the intermediate format ============= + + private static interface ElementHandler { + void startElement(Attributes attributes) throws IFException, SAXException; + void endElement() throws IFException; + boolean ignoreCharacters(); + } + + private abstract class AbstractElementHandler implements ElementHandler { + + public void startElement(Attributes attributes) throws IFException, SAXException { + //nop + } + + public void endElement() throws IFException { + //nop + } + + public boolean ignoreCharacters() { + return true; + } + } + + private class DocumentHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startDocument(); + } + + public void endElement() throws IFException { + painter.endDocument(); + } + + } + + private class DocumentHeaderHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startDocumentHeader(); + } + + public void endElement() throws IFException { + painter.endDocumentHeader(); + } + + } + + private class PageSequenceHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + String id = attributes.getValue("id"); + painter.startPageSequence(id); + } + + public void endElement() throws IFException { + painter.endPageSequence(); + } + + } + + private class PageHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + int index = Integer.parseInt(attributes.getValue("index")); + String name = attributes.getValue("name"); + int width = Integer.parseInt(attributes.getValue("width")); + int height = Integer.parseInt(attributes.getValue("height")); + painter.startPage(index, name, new Dimension(width, height)); + } + + public void endElement() throws IFException { + painter.endPage(); + } + + } + + private class PageHeaderHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startPageHeader(); + } + + public void endElement() throws IFException { + painter.endPageHeader(); + } + + } + + private class PageContentHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startPageContent(); + } + + public void endElement() throws IFException { + painter.endPageContent(); + } + + } + + private class PageTrailerHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + painter.startPageTrailer(); + } + + public void endElement() throws IFException { + painter.endPageTrailer(); + } + + } + + private class ViewportHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + String transform = attributes.getValue("transform"); + AffineTransform[] transforms + = AffineTransformArrayParser.createAffineTransform(transform); + int width = Integer.parseInt(attributes.getValue("width")); + int height = Integer.parseInt(attributes.getValue("height")); + Rectangle clipRect = getAttributeAsRectangle(attributes, "clip-rect"); + painter.startViewport(transforms, new Dimension(width, height), clipRect); + } + + public void endElement() throws IFException { + painter.endViewport(); + } + + } + + private class GroupHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + String transform = attributes.getValue("transform"); + AffineTransform[] transforms + = AffineTransformArrayParser.createAffineTransform(transform); + painter.startGroup(transforms); + } + + public void endElement() throws IFException { + painter.endGroup(); + } + + } + + private class FontHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + String family = attributes.getValue("family"); + String style = attributes.getValue("style"); + Integer weight = getAttributeAsInteger(attributes, "weight"); + String variant = attributes.getValue("variant"); + Integer size = getAttributeAsInteger(attributes, "size"); + Color color; + try { + color = getAttributeAsColor(attributes, "color"); + } catch (PropertyException pe) { + throw new IFException("Error parsing the color attribute", pe); + } + painter.setFont(family, style, weight, variant, size, color); + } + + } + + private class TextHandler extends AbstractElementHandler { + + public void endElement() throws IFException { + int x = Integer.parseInt(lastAttributes.getValue("x")); + int y = Integer.parseInt(lastAttributes.getValue("y")); + int[] dx = getAttributeAsIntArray(lastAttributes, "dx"); + int[] dy = getAttributeAsIntArray(lastAttributes, "dy"); + painter.drawText(x, y, dx, dy, content.toString()); + } + + public boolean ignoreCharacters() { + return false; + } + + } + + private class RectHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + int x = Integer.parseInt(lastAttributes.getValue("x")); + int y = Integer.parseInt(lastAttributes.getValue("y")); + int width = Integer.parseInt(lastAttributes.getValue("width")); + int height = Integer.parseInt(lastAttributes.getValue("height")); + Color fillColor; + try { + fillColor = getAttributeAsColor(attributes, "fill"); + } catch (PropertyException pe) { + throw new IFException("Error parsing the fill attribute", pe); + } + Color strokeColor; + try { + strokeColor = getAttributeAsColor(attributes, "stroke"); + } catch (PropertyException pe) { + throw new IFException("Error parsing the stroke attribute", pe); + } + painter.drawRect(new Rectangle(x, y, width, height), fillColor, strokeColor); + } + + } + + private class ImageHandler extends AbstractElementHandler { + + public void startElement(Attributes attributes) throws IFException { + inForeignObject = true; + } + + public void endElement() throws IFException { + int x = Integer.parseInt(lastAttributes.getValue("x")); + int y = Integer.parseInt(lastAttributes.getValue("y")); + int width = Integer.parseInt(lastAttributes.getValue("width")); + int height = Integer.parseInt(lastAttributes.getValue("height")); + Map foreignAttributes = getForeignAttributes(lastAttributes); + if (foreignObject != null) { + painter.drawImage(foreignObject, + new Rectangle(x, y, width, height), foreignAttributes); + foreignObject = null; + } else { + String uri = lastAttributes.getValue( + XLINK_HREF.getNamespaceURI(), XLINK_HREF.getLocalName()); + if (uri == null) { + throw new IFException("xlink:href is missing on image", null); + } + painter.drawImage(uri, new Rectangle(x, y, width, height), foreignAttributes); + } + inForeignObject = false; + } + + public boolean ignoreCharacters() { + return false; + } + } + + + // ==================================================================== + + + private void assertObjectOfClass(Object obj, Class clazz) { + if (!clazz.isInstance(obj)) { + throw new IllegalStateException("Object is not an instance of " + + clazz.getName() + " but of " + obj.getClass().getName()); + } + } + + /** + * Handles objects created by "sub-parsers" that implement the ObjectSource interface. + * An example of object handled here are ExtensionAttachments. + * @param obj the Object to be handled. + * @throws SAXException if an error occurs while handling the extension object + */ + protected void handleExternallyGeneratedObject(Object obj) throws SAXException { + try { + painter.handleExtensionObject(obj); + } catch (IFException ife) { + handleIFException(ife); + } + } + + private static boolean getAttributeAsBoolean(Attributes attributes, String name, + boolean defaultValue) { + String s = attributes.getValue(name); + if (s == null) { + return defaultValue; + } else { + return Boolean.valueOf(s).booleanValue(); + } + } + + private static int getAttributeAsInteger(Attributes attributes, String name, + int defaultValue) { + String s = attributes.getValue(name); + if (s == null) { + return defaultValue; + } else { + return Integer.parseInt(s); + } + } + + private static Integer getAttributeAsInteger(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return new Integer(s); + } + } + + private Color getAttributeAsColor(Attributes attributes, String name) + throws PropertyException { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return ColorUtil.parseColorString(userAgent, s); + } + } + + private static Rectangle2D getAttributeAsRectangle2D(Attributes attributes, String name) { + String s = attributes.getValue(name).trim(); + double[] values = ConversionUtils.toDoubleArray(s, "\\s"); + if (values.length != 4) { + throw new IllegalArgumentException("Rectangle must consist of 4 double values!"); + } + return new Rectangle2D.Double(values[0], values[1], values[2], values[3]); + } + + private static Rectangle getAttributeAsRectangle(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } + int[] values = ConversionUtils.toIntArray(s.trim(), "\\s"); + if (values.length != 4) { + throw new IllegalArgumentException("Rectangle must consist of 4 int values!"); + } + return new Rectangle(values[0], values[1], values[2], values[3]); + } + + private static int[] getAttributeAsIntArray(Attributes attributes, String name) { + String s = attributes.getValue(name); + if (s == null) { + return null; + } else { + return ConversionUtils.toIntArray(s.trim(), "\\s"); + } + } + + private static Map getForeignAttributes(Attributes atts) { + Map foreignAttributes = null; + for (int i = 0, c = atts.getLength(); i < c; i++) { + String ns = atts.getURI(i); + if (ns.length() > 0) { + if ("http://www.w3.org/2000/xmlns/".equals(ns)) { + continue; + } else if (NAMESPACE.equals(ns)) { + continue; + } else if (XLINK_NAMESPACE.equals(ns)) { + continue; + } + if (foreignAttributes == null) { + foreignAttributes = new java.util.HashMap(); + } + QName qname = new QName(ns, atts.getQName(i)); + foreignAttributes.put(qname, atts.getValue(i)); + } + } + return foreignAttributes; + } + + /** {@inheritDoc} */ + public void characters(char[] ch, int start, int length) throws SAXException { + if (delegate != null) { + delegate.characters(ch, start, length); + } else if (!ignoreCharacters) { + this.content.append(ch, start, length); + } + } + } +} diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java new file mode 100644 index 000000000..641e4686c --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -0,0 +1,805 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; + +import org.xml.sax.SAXException; + +import org.apache.batik.parser.AWTTransformProducer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.xmp.Metadata; +import org.apache.xmlgraphics.xmp.schemas.DublinCoreAdapter; +import org.apache.xmlgraphics.xmp.schemas.DublinCoreSchema; +import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; +import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; + +import org.apache.fop.Version; +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.area.Block; +import org.apache.fop.area.BlockViewport; +import org.apache.fop.area.CTM; +import org.apache.fop.area.OffDocumentExtensionAttachment; +import org.apache.fop.area.OffDocumentItem; +import org.apache.fop.area.PageSequence; +import org.apache.fop.area.PageViewport; +import org.apache.fop.area.RegionViewport; +import org.apache.fop.area.Trait; +import org.apache.fop.area.inline.AbstractTextArea; +import org.apache.fop.area.inline.ForeignObject; +import org.apache.fop.area.inline.Image; +import org.apache.fop.area.inline.SpaceArea; +import org.apache.fop.area.inline.TextArea; +import org.apache.fop.area.inline.Viewport; +import org.apache.fop.area.inline.WordArea; +import org.apache.fop.datatypes.URISpecification; +import org.apache.fop.fo.extensions.ExtensionAttachment; +import org.apache.fop.fo.extensions.xmp.XMPMetadata; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontTriplet; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.render.AbstractPathOrientedRenderer; +import org.apache.fop.render.Renderer; + +/** + * This renderer implementation is an adapter to the {@code IFPainter} interface. It is used + * to generate content using FOP's intermediate format. + */ +public class IFRenderer extends AbstractPathOrientedRenderer { + + /** logging instance */ + protected static Log log = LogFactory.getLog(IFRenderer.class); + + /** XML MIME type */ + public static final String IF_MIME_TYPE = MimeConstants.MIME_FOP_IF; + + private IFPainter painter; + + /** If not null, the XMLRenderer will mimic another renderer by using its font setup. */ + protected Renderer mimic; + + private boolean inPageSequence = false; + + private Stack graphicContextStack = new Stack(); + private Stack viewportDimensionStack = new Stack(); + private IFGraphicContext graphicContext = new IFGraphicContext(); + //private Stack groupStack = new Stack(); + + private Metadata documentMetadata; + + /** + * Main constructor + */ + public IFRenderer() { + } + + /** {@inheritDoc} */ + public String getMimeType() { + return IF_MIME_TYPE; + } + + /** + * Sets the {@code IFPainter} to be used by the {@code IFRenderer}. + * @param painter the {@code IFPainter} + */ + public void setPainter(IFPainter painter) { + this.painter = painter; + } + + /** + * Call this method to make the XMLRenderer mimic a different renderer by using its font + * setup. This is useful when working with the intermediate format parser. + * @param renderer the renderer to mimic + */ + public void mimicRenderer(Renderer renderer) { + this.mimic = renderer; + } + + /** {@inheritDoc} */ + public void setupFontInfo(FontInfo inFontInfo) { + if (mimic != null) { + mimic.setupFontInfo(inFontInfo); + this.fontInfo = inFontInfo; + } else { + super.setupFontInfo(inFontInfo); + } + } + + private void handleIFException(IFException ife) { + if (ife.getCause() instanceof SAXException) { + throw new RuntimeException(ife.getCause()); + } else { + throw new RuntimeException(ife); + } + } + + private void handleIFExceptionWithIOException(IFException ife) throws IOException { + if (ife.getCause() instanceof IOException) { + throw (IOException)ife.getCause(); + } else { + handleIFException(ife); + } + } + + /** + * Creates a default {@code IFPainter} when none has been set. + * @return the default IFPainter + */ + protected IFPainter createDefaultPainter() { + return new IFSerializer(); + } + + /** {@inheritDoc} */ + public void startRenderer(OutputStream outputStream) + throws IOException { + try { + if (outputStream != null) { + StreamResult result = new StreamResult(outputStream); + if (getUserAgent().getOutputFile() != null) { + result.setSystemId( + getUserAgent().getOutputFile().toURI().toURL().toExternalForm()); + } + if (this.painter == null) { + this.painter = new IFSerializer(); + this.painter.setUserAgent(getUserAgent()); + } + this.painter.setFontInfo(fontInfo); + this.painter.setResult(result); + } + super.startRenderer(null); + if (log.isDebugEnabled()) { + log.debug("Rendering areas via painter (" + + this.painter.getClass().getName() + ")..."); + } + painter.startDocument(); + painter.startDocumentHeader(); + } catch (IFException e) { + handleIFExceptionWithIOException(e); + } + } + + /** {@inheritDoc} */ + public void stopRenderer() throws IOException { + try { + if (this.inPageSequence) { + painter.endPageSequence(); + this.inPageSequence = false; + } + painter.endDocument(); + } catch (IFException e) { + handleIFExceptionWithIOException(e); + } + super.stopRenderer(); + log.debug("Rendering finished."); + } + + /** {@inheritDoc} */ + public void processOffDocumentItem(OffDocumentItem odi) { + if (odi instanceof OffDocumentExtensionAttachment) { + ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment(); + if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) { + renderXMPMetadata((XMPMetadata)attachment); + } + } + } + + private void renderXMPMetadata(XMPMetadata metadata) { + this.documentMetadata = metadata.getMetadata(); + } + + /** {@inheritDoc} */ + public void startPageSequence(PageSequence pageSequence) { + try { + if (this.inPageSequence) { + painter.endPageSequence(); + } else { + if (this.documentMetadata == null) { + this.documentMetadata = createDefaultDocumentMetadata(); + } + painter.handleExtensionObject(this.documentMetadata); + painter.endDocumentHeader(); + this.inPageSequence = true; + } + //TODO Put the page-sequence's ID in the area tree + painter.startPageSequence(null); + } catch (IFException e) { + handleIFException(e); + } + } + + private Metadata createDefaultDocumentMetadata() { + Metadata xmp = new Metadata(); + DublinCoreAdapter dc = DublinCoreSchema.getAdapter(xmp); + if (getUserAgent().getTitle() != null) { + dc.setTitle(getUserAgent().getTitle()); + } + if (getUserAgent().getAuthor() != null) { + dc.addCreator(getUserAgent().getAuthor()); + } + if (getUserAgent().getKeywords() != null) { + dc.addSubject(getUserAgent().getKeywords()); + } + XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(xmp); + if (getUserAgent().getProducer() != null) { + xmpBasic.setCreatorTool(getUserAgent().getProducer()); + } else { + xmpBasic.setCreatorTool(Version.getVersion()); + } + xmpBasic.setMetadataDate(new java.util.Date()); + if (getUserAgent().getCreationDate() != null) { + xmpBasic.setCreateDate(getUserAgent().getCreationDate()); + } else { + xmpBasic.setCreateDate(xmpBasic.getMetadataDate()); + } + return xmp; + } + + /** {@inheritDoc} */ + public void renderPage(PageViewport page) throws IOException, FOPException { + if (log.isTraceEnabled()) { + log.trace("renderPage() " + page); + } + try { + Rectangle2D viewArea = page.getViewArea(); + Dimension dim = new Dimension( + (int)Math.ceil(viewArea.getWidth()), + (int)Math.ceil(viewArea.getHeight())); + painter.startPage(page.getPageIndex(), page.getPageNumberString(), dim); + painter.startPageHeader(); + //TODO Handle page header + painter.endPageHeader(); + painter.startPageContent(); + super.renderPage(page); + painter.endPageContent(); + painter.startPageTrailer(); + //TODO Handle page trailer + painter.endPageTrailer(); + painter.endPage(); + } catch (IFException e) { + handleIFException(e); + } + } + + /** {@inheritDoc} */ + protected void saveGraphicsState() { + graphicContextStack.push(graphicContext); + graphicContext = (IFGraphicContext)graphicContext.clone(); + } + + /** {@inheritDoc} */ + protected void restoreGraphicsState() { + while (graphicContext.getGroupStackSize() > 0) { + IFGraphicContext.Group[] groups = graphicContext.dropGroups(); + for (int i = groups.length - 1; i >= 0; i--) { + try { + groups[i].end(painter); + } catch (IFException ife) { + handleIFException(ife); + } + } + } + graphicContext = (IFGraphicContext)graphicContextStack.pop(); + } + + private void pushGroup(IFGraphicContext.Group group) { + graphicContext.pushGroup(group); + try { + group.start(painter); + } catch (IFException ife) { + handleIFException(ife); + } + } + + /** {@inheritDoc} */ + protected List breakOutOfStateStack() { + log.debug("Block.FIXED --> break out"); + List breakOutList = new java.util.ArrayList(); + while (!this.graphicContextStack.empty()) { + //Handle groups + IFGraphicContext.Group[] groups = graphicContext.getGroups(); + for (int j = groups.length - 1; j >= 0; j--) { + try { + groups[j].end(painter); + } catch (IFException ife) { + handleIFException(ife); + } + } + + breakOutList.add(0, this.graphicContext); + graphicContext = (IFGraphicContext)graphicContextStack.pop(); + } + return breakOutList; + } + + /** {@inheritDoc} */ + protected void restoreStateStackAfterBreakOut(List breakOutList) { + log.debug("Block.FIXED --> restoring context after break-out"); + for (int i = 0, c = breakOutList.size(); i < c; i++) { + graphicContextStack.push(graphicContext); + this.graphicContext = (IFGraphicContext)breakOutList.get(i); + + //Handle groups + IFGraphicContext.Group[] groups = graphicContext.getGroups(); + for (int j = 0, jc = groups.length; j < jc; j++) { + try { + groups[j].start(painter); + } catch (IFException ife) { + handleIFException(ife); + } + } + } + log.debug("restored."); + } + + /** {@inheritDoc} */ + protected void concatenateTransformationMatrix(AffineTransform at) { + if (!at.isIdentity()) { + concatenateTransformationMatrixMpt(ptToMpt(at)); + } + } + + private void concatenateTransformationMatrixMpt(AffineTransform at) { + if (!at.isIdentity()) { + if (log.isTraceEnabled()) { + log.trace("-----concatenateTransformationMatrix: " + at); + } + IFGraphicContext.Group group = new IFGraphicContext.Group(at); + pushGroup(group); + } + } + + /** {@inheritDoc} */ + protected void beginTextObject() { + //nop - Ignore, handled by painter internally + } + + /** {@inheritDoc} */ + protected void endTextObject() { + //nop - Ignore, handled by painter internally + } + + /** {@inheritDoc} */ + protected void renderRegionViewport(RegionViewport viewport) { + Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD()); + viewportDimensionStack.push(dim); + super.renderRegionViewport(viewport); + viewportDimensionStack.pop(); + } + + /** {@inheritDoc} */ + protected void renderBlockViewport(BlockViewport bv, List children) { + //Essentially the same code as in the super class but optimized for the IF + + //This is the content-rect + Dimension dim = new Dimension(bv.getIPD(), bv.getBPD()); + viewportDimensionStack.push(dim); + + // save positions + int saveIP = currentIPPosition; + int saveBP = currentBPPosition; + + CTM ctm = bv.getCTM(); + int borderPaddingStart = bv.getBorderAndPaddingWidthStart(); + int borderPaddingBefore = bv.getBorderAndPaddingWidthBefore(); + + if (bv.getPositioning() == Block.ABSOLUTE + || bv.getPositioning() == Block.FIXED) { + + //For FIXED, we need to break out of the current viewports to the + //one established by the page. We save the state stack for restoration + //after the block-container has been painted. See below. + List breakOutList = null; + if (bv.getPositioning() == Block.FIXED) { + breakOutList = breakOutOfStateStack(); + } + + AffineTransform positionTransform = new AffineTransform(); + positionTransform.translate(bv.getXOffset(), bv.getYOffset()); + + //"left/"top" (bv.getX/YOffset()) specify the position of the content rectangle + positionTransform.translate(-borderPaddingStart, -borderPaddingBefore); + + //Free transformation for the block-container viewport + String transf; + transf = bv.getForeignAttributeValue(FOX_TRANSFORM); + if (transf != null) { + AffineTransform freeTransform = AWTTransformProducer.createAffineTransform(transf); + positionTransform.concatenate(freeTransform); + } + + saveGraphicsState(); + //Viewport position + concatenateTransformationMatrixMpt(positionTransform); + + //Background and borders + float bpwidth = (borderPaddingStart + bv.getBorderAndPaddingWidthEnd()); + float bpheight = (borderPaddingBefore + bv.getBorderAndPaddingWidthAfter()); + drawBackAndBorders(bv, 0, 0, + (dim.width + bpwidth) / 1000f, (dim.height + bpheight) / 1000f); + + //Shift to content rectangle after border painting + AffineTransform contentRectTransform = new AffineTransform(); + contentRectTransform.translate(borderPaddingStart, borderPaddingBefore); + concatenateTransformationMatrixMpt(contentRectTransform); + + //Clipping + Rectangle clipRect = null; + if (bv.getClip()) { + clipRect = new Rectangle(0, 0, dim.width, dim.height); + //clipRect(0f, 0f, width, height); + } + + //saveGraphicsState(); + //Set up coordinate system for content rectangle + AffineTransform contentTransform = ctm.toAffineTransform(); + //concatenateTransformationMatrixMpt(contentTransform); + startViewport(contentTransform, clipRect); + + currentIPPosition = 0; + currentBPPosition = 0; + renderBlocks(bv, children); + + endViewport(); + //restoreGraphicsState(); + restoreGraphicsState(); + + if (breakOutList != null) { + restoreStateStackAfterBreakOut(breakOutList); + } + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + } else { + + currentBPPosition += bv.getSpaceBefore(); + + //borders and background in the old coordinate system + handleBlockTraits(bv); + + //Advance to start of content area + currentIPPosition += bv.getStartIndent(); + + CTM tempctm = new CTM(containingIPPosition, currentBPPosition); + ctm = tempctm.multiply(ctm); + + //Now adjust for border/padding + currentBPPosition += borderPaddingBefore; + + Rectangle2D clippingRect = null; + if (bv.getClip()) { + clippingRect = new Rectangle(currentIPPosition, currentBPPosition, + bv.getIPD(), bv.getBPD()); + } + + startVParea(ctm, clippingRect); + currentIPPosition = 0; + currentBPPosition = 0; + renderBlocks(bv, children); + endVParea(); + + currentIPPosition = saveIP; + currentBPPosition = saveBP; + + currentBPPosition += (int)(bv.getAllocBPD()); + } + viewportDimensionStack.pop(); + } + + /** {@inheritDoc} */ + public void renderViewport(Viewport viewport) { + Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD()); + viewportDimensionStack.push(dim); + super.renderViewport(viewport); + viewportDimensionStack.pop(); + } + + /** {@inheritDoc} */ + protected void startVParea(CTM ctm, Rectangle2D clippingRect) { + if (log.isTraceEnabled()) { + log.trace("startVParea() ctm=" + ctm + ", clippingRect=" + clippingRect); + } + AffineTransform at = new AffineTransform(ctm.toArray()); + Rectangle clipRect = null; + if (clippingRect != null) { + clipRect = new Rectangle( + (int)clippingRect.getMinX() - currentIPPosition, + (int)clippingRect.getMinY() - currentBPPosition, + (int)clippingRect.getWidth(), (int)clippingRect.getHeight()); + } + startViewport(at, clipRect); + if (log.isTraceEnabled()) { + log.trace("startVPArea: " + at + " --> " + graphicContext.getTransform()); + } + } + + private void startViewport(AffineTransform at, Rectangle clipRect) { + saveGraphicsState(); + try { + IFGraphicContext.Viewport viewport = new IFGraphicContext.Viewport( + at, (Dimension)viewportDimensionStack.peek(), clipRect); + graphicContext.pushGroup(viewport); + viewport.start(painter); + } catch (IFException e) { + handleIFException(e); + } + } + + /** {@inheritDoc} */ + protected void endVParea() { + log.trace("endVParea()"); + endViewport(); + if (log.isTraceEnabled()) { + log.trace("endVPArea() --> " + graphicContext.getTransform()); + } + } + + private void endViewport() { + restoreGraphicsState(); + } + + /* + protected void renderReferenceArea(Block block) { + // TODO Auto-generated method stub + }*/ + + /** {@inheritDoc} */ + protected void renderBlock(Block block) { + if (log.isTraceEnabled()) { + log.trace("renderBlock() " + block); + } + super.renderBlock(block); + } + + private Typeface getTypeface(String fontName) { + Typeface tf = (Typeface) fontInfo.getFonts().get(fontName); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + return tf; + } + + /** {@inheritDoc} */ + protected void renderText(TextArea text) { + if (log.isTraceEnabled()) { + log.trace("renderText() " + text); + } + renderInlineAreaBackAndBorders(text); + Color ct = (Color) text.getTrait(Trait.COLOR); + + beginTextObject(); + + String fontName = getInternalFontNameForArea(text); + int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); + + // This assumes that *all* CIDFonts use a /ToUnicode mapping + Typeface tf = getTypeface(fontName); + + FontTriplet triplet = (FontTriplet)text.getTrait(Trait.FONT); + try { + painter.setFont(triplet.getName(), triplet.getStyle(), new Integer(triplet.getWeight()), + "normal", new Integer(size), ct); + } catch (IFException e) { + handleIFException(e); + } + + super.renderText(text); + + int rx = currentIPPosition + text.getBorderAndPaddingWidthStart(); + int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset(); + renderTextDecoration(tf, size, text, bl, rx); + } + + /** {@inheritDoc} */ + protected void renderWord(WordArea word) { + Font font = getFontFromArea(word.getParentArea()); + String s = word.getWord(); + + renderText(s, word.getLetterAdjustArray(), + font, (AbstractTextArea)word.getParentArea()); + + super.renderWord(word); + } + + /** {@inheritDoc} */ + protected void renderSpace(SpaceArea space) { + Font font = getFontFromArea(space.getParentArea()); + String s = space.getSpace(); + + AbstractTextArea textArea = (AbstractTextArea)space.getParentArea(); + renderText(s, null, font, textArea); + + if (space.isAdjustable()) { + //Used for justified text, for example + int tws = -((TextArea) space.getParentArea()).getTextWordSpaceAdjust() + - 2 * textArea.getTextLetterSpaceAdjust(); + this.currentIPPosition -= tws; + } + super.renderSpace(space); + } + + /** + * Does low-level rendering of text. + * @param s text to render + * @param letterAdjust an array of widths for letter adjustment (may be null) + * @param font to font in use + * @param parentArea the parent text area to retrieve certain traits from + */ + protected void renderText(String s, + int[] letterAdjust, + Font font, AbstractTextArea parentArea) { + int curX = currentIPPosition; + float fontSize = font.getFontSize() / 1000f; + + int l = s.length(); + + int[] dx = new int[l]; + boolean hasDX = false; + for (int i = 0; i < l; i++) { + char ch = s.charAt(i); + float glyphAdjust = 0; + if (font.hasChar(ch)) { + int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0); + glyphAdjust -= tls; + } + curX += font.getCharWidth(ch); + if (letterAdjust != null && i < l) { + glyphAdjust -= letterAdjust[i]; + } + + float adjust = glyphAdjust / fontSize; + + if (adjust != 0) { + dx[i] = Math.round(adjust * -10); + if (dx[i] != 0) { + hasDX = true; + } + } + curX += adjust; + } + try { + int rx = currentIPPosition + parentArea.getBorderAndPaddingWidthStart(); + int bl = currentBPPosition + parentArea.getOffset() + parentArea.getBaselineOffset(); + painter.drawText(rx, bl, (hasDX ? dx : null), null, s); + } catch (IFException e) { + handleIFException(e); + } + this.currentIPPosition = curX; + } + + /** {@inheritDoc} */ + public void renderImage(Image image, Rectangle2D pos) { + drawImage(image.getURL(), pos, image.getForeignAttributes()); + } + + /** {@inheritDoc} */ + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + Rectangle posInt = new Rectangle( + currentIPPosition + (int)pos.getX(), + currentBPPosition + (int)pos.getY(), + (int)pos.getWidth(), + (int)pos.getHeight()); + uri = URISpecification.getURL(uri); + try { + painter.drawImage(uri, posInt, foreignAttributes); + } catch (IFException ife) { + handleIFException(ife); + } + } + + /** {@inheritDoc} */ + public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + endTextObject(); + Rectangle posInt = new Rectangle( + currentIPPosition + (int)pos.getX(), + currentBPPosition + (int)pos.getY(), + (int)pos.getWidth(), + (int)pos.getHeight()); + Document doc = fo.getDocument(); + try { + painter.drawImage(doc, posInt, fo.getForeignAttributes()); + } catch (IFException ife) { + handleIFException(ife); + } + } + + /** {@inheritDoc} */ + protected void clip() { + // TODO Auto-generated method stub + log.warn("clip() NYI"); + } + + /** {@inheritDoc} */ + protected void clipRect(float x, float y, float width, float height) { + // TODO Auto-generated method stub + log.warn("clipRect() NYI"); + } + + /** {@inheritDoc} */ + protected void closePath() { + // TODO Auto-generated method stub + log.warn("closePath() NYI"); + } + + /** {@inheritDoc} */ + protected void drawBorderLine(float x1, float y1, float x2, float y2, boolean horz, + boolean startOrBefore, int style, Color col) { + // TODO Auto-generated method stub + //log.warn("drawBorderLine() NYI"); + updateColor(col, true); + fillRect(x1, y1, x2 - x1, y2 - y1); + } + + private Rectangle toMillipointRectangle(float x, float y, float width, float height) { + return new Rectangle( + (int)(x * 1000), (int)(y * 1000), (int)(width * 1000), (int)(height * 1000)); + } + + /** {@inheritDoc} */ + protected void fillRect(float x, float y, float width, float height) { + try { + painter.drawRect( + toMillipointRectangle(x, y, width, height), + this.graphicContext.getPaint(), null); + } catch (IFException e) { + handleIFException(e); + } + } + + /** {@inheritDoc} */ + protected void moveTo(float x, float y) { + // TODO Auto-generated method stub + log.warn("moveTo() NYI"); + } + + /** {@inheritDoc} */ + protected void lineTo(float x, float y) { + // TODO Auto-generated method stub + log.warn("lineTo() NYI"); + } + + /** {@inheritDoc} */ + protected void updateColor(Color col, boolean fill) { + if (fill) { + this.graphicContext.setPaint(col); + } else { + this.graphicContext.setColor(col); + } + + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFRendererMaker.java b/src/java/org/apache/fop/render/intermediate/IFRendererMaker.java new file mode 100644 index 000000000..eb70f3028 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFRendererMaker.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.render.AbstractRendererMaker; +import org.apache.fop.render.PrintRendererConfigurator; +import org.apache.fop.render.Renderer; +import org.apache.fop.render.RendererConfigurator; + +/** + * RendererMaker for the Intermediate Format Renderer. + */ +public class IFRendererMaker extends AbstractRendererMaker { + + private static final String[] MIMES = new String[] {MimeConstants.MIME_FOP_IF}; + + /**{@inheritDoc} */ + public Renderer makeRenderer(FOUserAgent userAgent) { + return new IFRenderer(); + } + + /**{@inheritDoc} */ + public RendererConfigurator getConfigurator(FOUserAgent userAgent) { + return new PrintRendererConfigurator(userAgent); + } + + /** {@inheritDoc} */ + public boolean needsOutputStream() { + return false; + } + + /** {@inheritDoc} */ + public String[] getSupportedMimeTypes() { + return MIMES; + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java new file mode 100644 index 000000000..509746de6 --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -0,0 +1,430 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.util.Iterator; +import java.util.Map; + +import org.w3c.dom.Document; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.xmlgraphics.util.QName; +import org.apache.xmlgraphics.util.XMLizable; + +import org.apache.fop.render.RenderingContext; +import org.apache.fop.util.ColorUtil; +import org.apache.fop.util.DOM2SAX; + +/** + * IFPainter implementation that serializes the intermediate format to XML. + */ +public class IFSerializer extends AbstractXMLWritingIFPainter implements IFConstants { + + /** + * Default constructor. + */ + public IFSerializer() { + } + + /** {@inheritDoc} */ + protected String getMainNamespace() { + return NAMESPACE; + } + + /** {@inheritDoc} */ + public boolean supportsPagesOutOfOrder() { + return false; + //Theoretically supported but disabled to improve performance when + //rendering the IF to the final format later on + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MIME_TYPE; + } + + /** {@inheritDoc} */ + public void startDocument() throws IFException { + try { + handler.startDocument(); + handler.startPrefixMapping("", NAMESPACE); + handler.startPrefixMapping(XLINK_PREFIX, XLINK_NAMESPACE); + startElement(EL_DOCUMENT); + } catch (SAXException e) { + throw new IFException("SAX error in startDocument()", e); + } + } + + /** {@inheritDoc} */ + public void startDocumentHeader() throws IFException { + try { + startElement(EL_HEADER); + } catch (SAXException e) { + throw new IFException("SAX error in startDocumentHeader()", e); + } + } + + /** {@inheritDoc} */ + public void endDocumentHeader() throws IFException { + try { + endElement(EL_HEADER); + } catch (SAXException e) { + throw new IFException("SAX error in startDocumentHeader()", e); + } + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + try { + endElement(EL_DOCUMENT); + handler.endDocument(); + } catch (SAXException e) { + throw new IFException("SAX error in endDocument()", e); + } + } + + /** {@inheritDoc} */ + public void startPageSequence(String id) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + if (id != null) { + atts.addAttribute(XML_NAMESPACE, "id", "xml:id", CDATA, id); + } + startElement(EL_PAGE_SEQUENCE, atts); + } catch (SAXException e) { + throw new IFException("SAX error in startPageSequence()", e); + } + } + + /** {@inheritDoc} */ + public void endPageSequence() throws IFException { + try { + endElement(EL_PAGE_SEQUENCE); + } catch (SAXException e) { + throw new IFException("SAX error in endPageSequence()", e); + } + } + + /** {@inheritDoc} */ + public void startPage(int index, String name, Dimension size) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "index", "index", CDATA, Integer.toString(index)); + atts.addAttribute("", "name", "name", CDATA, name); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(size.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(size.height)); + startElement(EL_PAGE, atts); + } catch (SAXException e) { + throw new IFException("SAX error in startPage()", e); + } + } + + /** {@inheritDoc} */ + public void startPageHeader() throws IFException { + try { + startElement(EL_PAGE_HEADER); + } catch (SAXException e) { + throw new IFException("SAX error in startPageHeader()", e); + } + } + + /** {@inheritDoc} */ + public void endPageHeader() throws IFException { + try { + endElement(EL_PAGE_HEADER); + } catch (SAXException e) { + throw new IFException("SAX error in endPageHeader()", e); + } + } + + /** {@inheritDoc} */ + public void startPageContent() throws IFException { + try { + startElement(EL_PAGE_CONTENT); + } catch (SAXException e) { + throw new IFException("SAX error in startPageContent()", e); + } + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + try { + endElement(EL_PAGE_CONTENT); + } catch (SAXException e) { + throw new IFException("SAX error in endPageContent()", e); + } + } + + /** {@inheritDoc} */ + public void startPageTrailer() throws IFException { + try { + startElement(EL_PAGE_TRAILER); + } catch (SAXException e) { + throw new IFException("SAX error in startPageTrailer()", e); + } + } + + /** {@inheritDoc} */ + public void endPageTrailer() throws IFException { + try { + endElement(EL_PAGE_TRAILER); + } catch (SAXException e) { + throw new IFException("SAX error in endPageTrailer()", e); + } + } + + /** {@inheritDoc} */ + public void endPage() throws IFException { + try { + endElement(EL_PAGE); + } catch (SAXException e) { + throw new IFException("SAX error in endPage()", e); + } + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) + throws IFException { + startViewport(toString(transform), size, clipRect); + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) + throws IFException { + startViewport(toString(transforms), size, clipRect); + } + + private void startViewport(String transform, Dimension size, Rectangle clipRect) + throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + if (transform != null && transform.length() > 0) { + atts.addAttribute("", "transform", "transform", CDATA, transform); + } + atts.addAttribute("", "width", "width", CDATA, Integer.toString(size.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(size.height)); + if (clipRect != null) { + atts.addAttribute("", "clip-rect", "clip-rect", CDATA, toString(clipRect)); + } + startElement(EL_VIEWPORT, atts); + } catch (SAXException e) { + throw new IFException("SAX error in startViewport()", e); + } + } + + /** {@inheritDoc} */ + public void endViewport() throws IFException { + try { + endElement(EL_VIEWPORT); + } catch (SAXException e) { + throw new IFException("SAX error in endViewport()", e); + } + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform[] transforms) throws IFException { + startGroup(toString(transforms)); + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform transform) throws IFException { + startGroup(toString(transform)); + } + + private void startGroup(String transform) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + if (transform != null && transform.length() > 0) { + atts.addAttribute("", "transform", "transform", CDATA, transform); + } + startElement(EL_GROUP, atts); + } catch (SAXException e) { + throw new IFException("SAX error in startGroup()", e); + } + } + + /** {@inheritDoc} */ + public void endGroup() throws IFException { + try { + endElement(EL_GROUP); + } catch (SAXException e) { + throw new IFException("SAX error in endGroup()", e); + } + } + + /** {@inheritDoc} */ + public void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + addAttribute(atts, XLINK_HREF, uri); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(rect.x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(rect.y)); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(rect.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(rect.height)); + if (foreignAttributes != null) { + Iterator iter = foreignAttributes.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry)iter.next(); + addAttribute(atts, (QName)entry.getKey(), entry.getValue().toString()); + } + } + element(EL_IMAGE, atts); + } catch (SAXException e) { + throw new IFException("SAX error in startGroup()", e); + } + } + + /** {@inheritDoc} */ + public void drawImage(Document doc, Rectangle rect, Map foreignAttributes) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(rect.x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(rect.y)); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(rect.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(rect.height)); + if (foreignAttributes != null) { + Iterator iter = foreignAttributes.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry)iter.next(); + addAttribute(atts, (QName)entry.getKey(), entry.getValue().toString()); + } + } + startElement(EL_IMAGE, atts); + new DOM2SAX(handler).writeDocument(doc, true); + endElement(EL_IMAGE); + } catch (SAXException e) { + throw new IFException("SAX error in startGroup()", e); + } + } + + /** {@inheritDoc} */ + public void addTarget(String name, int x, int y) throws IFException { + // TODO Auto-generated method stub + + } + + private static String toString(Paint paint) { + if (paint instanceof Color) { + return ColorUtil.colorToString((Color)paint); + } else { + throw new UnsupportedOperationException("Paint not supported: " + paint); + } + } + + /** {@inheritDoc} */ + public void drawRect(Rectangle rect, Paint fill, Color stroke) throws IFException { + if (fill == null && stroke == null) { + return; + } + try { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(rect.x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(rect.y)); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(rect.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(rect.height)); + if (fill != null) { + atts.addAttribute("", "fill", "fill", CDATA, toString(fill)); + } + if (stroke != null) { + atts.addAttribute("", "stroke", "sroke", CDATA, toString(stroke)); + } + element("rect", atts); + } catch (SAXException e) { + throw new IFException("SAX error in drawRect()", e); + } + } + + /** {@inheritDoc} */ + public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(y)); + if (dx != null) { + atts.addAttribute("", "dx", "dx", CDATA, toString(dx)); + } + if (dy != null) { + atts.addAttribute("", "dy", "dy", CDATA, toString(dy)); + } + startElement("text", atts); + char[] chars = text.toCharArray(); + handler.characters(chars, 0, chars.length); + endElement("text"); + } catch (SAXException e) { + throw new IFException("SAX error in setFont()", e); + } + } + + /** {@inheritDoc} */ + public void setFont(String family, String style, Integer weight, String variant, Integer size, + Color color) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + if (family != null) { + atts.addAttribute("", "family", "family", CDATA, family); + } + if (style != null) { + atts.addAttribute("", "style", "style", CDATA, style); + } + if (weight != null) { + atts.addAttribute("", "weight", "weight", CDATA, weight.toString()); + } + if (variant != null) { + atts.addAttribute("", "variant", "variant", CDATA, variant); + } + if (size != null) { + atts.addAttribute("", "size", "size", CDATA, size.toString()); + } + if (color != null) { + atts.addAttribute("", "color", "color", CDATA, toString(color)); + } + element("font", atts); + } catch (SAXException e) { + throw new IFException("SAX error in setFont()", e); + } + } + + /** {@inheritDoc} */ + public void handleExtensionObject(Object extension) throws IFException { + if (extension instanceof XMLizable) { + try { + ((XMLizable)extension).toSAX(this.handler); + } catch (SAXException e) { + throw new IFException("SAX error while handling extension object", e); + } + } else { + throw new UnsupportedOperationException( + "Don't know how to handle extension object: " + extension); + } + } + + /** {@inheritDoc} */ + protected RenderingContext createRenderingContext() { + throw new IllegalStateException("Should never be called!"); + } + +} diff --git a/src/java/org/apache/fop/render/intermediate/IFState.java b/src/java/org/apache/fop/render/intermediate/IFState.java new file mode 100644 index 000000000..aa073d03c --- /dev/null +++ b/src/java/org/apache/fop/render/intermediate/IFState.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.intermediate; + +import java.awt.Color; + +public class IFState { + + private IFState parent; + + private String fontFamily; + private int fontSize; + private String fontStyle; + private int fontWeight; + private String fontVariant; + private boolean fontChanged = true; + + private Color textColor; + + private IFState() { + //nop + } + + private IFState(IFState parent) { + this.parent = parent; + + this.fontFamily = parent.fontFamily; + this.fontSize = parent.fontSize; + this.fontStyle = parent.fontStyle; + this.fontWeight = parent.fontWeight; + this.fontVariant = parent.fontVariant; + + this.textColor = parent.textColor; + } + + public static IFState create() { + return new IFState(); + } + + public IFState push() { + return new IFState(this); + } + + public IFState pop() { + return this.parent; + } + + public boolean isFontChanged() { + return this.fontChanged; + } + + public void resetFontChanged() { + this.fontChanged = false; + } + + /** + * Returns the font family. + * @return the font family + */ + public String getFontFamily() { + return fontFamily; + } + + /** + * Sets the font family. + * @param family the new font family + */ + public void setFontFamily(String family) { + if (!family.equals(this.fontFamily)) { + this.fontChanged = true; + } + this.fontFamily = family; + } + + /** + * Returns the font size. + * @return the font size (in mpt) + */ + public int getFontSize() { + return fontSize; + } + + /** + * Sets the font size. + * @param size the new font size (in mpt) + */ + public void setFontSize(int size) { + if (size != this.fontSize) { + this.fontChanged = true; + } + this.fontSize = size; + } + + /** + * Returns the font style. + * @return the font style + */ + public String getFontStyle() { + return fontStyle; + } + + /** + * Set the font style + * @param style the new font style + */ + public void setFontStyle(String style) { + if (!style.equals(this.fontStyle)) { + this.fontChanged = true; + } + this.fontStyle = style; + } + + /** + * Returns the font weight. + * @return the font weight + */ + public int getFontWeight() { + return fontWeight; + } + + /** + * Sets the font weight + * @param weight the new font weight + */ + public void setFontWeight(int weight) { + if (weight != this.fontWeight) { + this.fontChanged = true; + } + this.fontWeight = weight; + } + + /** + * Returns the font variant. + * @return the font variant + */ + public String getFontVariant() { + return fontVariant; + } + + /** + * Sets the font variant. + * @param variant the new font variant + */ + public void setFontVariant(String variant) { + if (!variant.equals(this.fontVariant)) { + this.fontChanged = true; + } + this.fontVariant = variant; + } + + /** + * Returns the text color. + * @return the text color + */ + public Color getTextColor() { + return textColor; + } + + /** + * Sets the text color. + * @param color the new text color + */ + public void setTextColor(Color color) { + if (!color.equals(this.textColor)) { + this.fontChanged = true; + } + this.textColor = color; + } + + +} diff --git a/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml index a3b36fd60..d4fe60b2f 100644 --- a/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml +++ b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.pcl.PCLEventProducer.paperTypeUnavailable">Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper}</message> </catalogue> diff --git a/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java index e8988244f..5ddcd06c6 100644 --- a/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java +++ b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java @@ -75,6 +75,18 @@ public class ImageRenderedAdapter extends AbstractImageAdapter { return ((ImageRendered)this.image); } + /** {@inheritDoc} */ + public int getWidth() { + RenderedImage ri = getImage().getRenderedImage(); + return ri.getWidth(); + } + + /** {@inheritDoc} */ + public int getHeight() { + RenderedImage ri = getImage().getRenderedImage(); + return ri.getHeight(); + } + private ColorModel getEffectiveColorModel() { return encodingHelper.getEncodedColorModel(); } diff --git a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java new file mode 100644 index 000000000..841dd7e01 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +/** + * Constants used for configuring PDF output. + */ +public interface PDFConfigurationConstants { + + /** PDF encryption parameter: all parameters as object, datatype: PDFEncryptionParams */ + String ENCRYPTION_PARAMS = "encryption-params"; + /** PDF encryption parameter: user password, datatype: String */ + String USER_PASSWORD = "user-password"; + /** PDF encryption parameter: owner password, datatype: String */ + String OWNER_PASSWORD = "owner-password"; + /** PDF encryption parameter: Forbids printing, datatype: Boolean or "true"/"false" */ + String NO_PRINT = "noprint"; + /** PDF encryption parameter: Forbids copying content, datatype: Boolean or "true"/"false" */ + String NO_COPY_CONTENT = "nocopy"; + /** PDF encryption parameter: Forbids editing content, datatype: Boolean or "true"/"false" */ + String NO_EDIT_CONTENT = "noedit"; + /** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */ + String NO_ANNOTATIONS = "noannotations"; + /** Rendering Options key for the PDF/A mode. */ + String PDF_A_MODE = "pdf-a-mode"; + /** Rendering Options key for the PDF/X mode. */ + String PDF_X_MODE = "pdf-x-mode"; + /** Rendering Options key for the ICC profile for the output intent. */ + String KEY_OUTPUT_PROFILE = "output-profile"; + /** + * Rendering Options key for disabling the sRGB color space (only possible if no PDF/A or + * PDF/X profile is active). + */ + String KEY_DISABLE_SRGB_COLORSPACE = "disable-srgb-colorspace"; +} diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java new file mode 100644 index 000000000..8d5b4ccb0 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Color; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFilterList; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFState; +import org.apache.fop.pdf.PDFStream; +import org.apache.fop.pdf.PDFTextUtil; +import org.apache.fop.pdf.PDFXObject; + +/** + * Generator class encapsulating all object references and state necessary to generate a + * PDF content stream. + */ +public class PDFContentGenerator { + + /** Controls whether comments are written to the PDF stream. */ + protected static final boolean WRITE_COMMENTS = true; + + private PDFDocument document; + private OutputStream outputStream; + private PDFResourceContext resourceContext; + + /** the current stream to add PDF commands to */ + private PDFStream currentStream; + + /** drawing state */ + protected PDFState currentState = null; + /** Text generation utility holding the current font status */ + protected PDFTextUtil textutil; + + + /** + * Main constructor. Creates a new PDF stream and additional helper classes for text painting + * and state management. + * @param document the PDF document + * @param out the output stream the PDF document is generated to + * @param resourceContext the resource context + */ + public PDFContentGenerator(PDFDocument document, OutputStream out, + PDFResourceContext resourceContext) { + this.document = document; + this.outputStream = out; + this.resourceContext = resourceContext; + this.currentStream = document.getFactory() + .makeStream(PDFFilterList.CONTENT_FILTER, false); + this.textutil = new PDFTextUtil() { + protected void write(String code) { + currentStream.add(code); + } + }; + + this.currentState = new PDFState(); + } + + /** + * Returns the applicable resource context for the generator. + * @return the resource context + */ + public PDFDocument getDocument() { + return this.document; + } + + /** + * Returns the output stream the PDF document is written to. + * @return the output stream + */ + public OutputStream getOutputStream() { + return this.outputStream; + } + + /** + * Returns the applicable resource context for the generator. + * @return the resource context + */ + public PDFResourceContext getResourceContext() { + return this.resourceContext; + } + + /** + * Returns the {@code PDFStream} associated with this instance. + * @return the PDF stream + */ + public PDFStream getStream() { + return this.currentStream; + } + + /** + * Returns the {@code PDFState} associated with this instance. + * @return the PDF state + */ + public PDFState getState() { + return this.currentState; + } + + /** + * Returns the {@code PDFTextUtil} associated with this instance. + * @return the text utility + */ + public PDFTextUtil getTextUtil() { + return this.textutil; + } + + /** + * Flushes all queued PDF objects ready to be written to the output stream. + * @throws IOException if an error occurs while flushing the PDF objects + */ + public void flushPDFDoc() throws IOException { + this.document.output(this.outputStream); + } + + /** + * Writes out a comment. + * @param text text for the comment + */ + protected void comment(String text) { + if (WRITE_COMMENTS) { + currentStream.add("% " + text + "\n"); + } + } + + /** {@inheritDoc} */ + protected void saveGraphicsState() { + endTextObject(); + currentState.push(); + currentStream.add("q\n"); + } + + /** + * Restored the graphics state valid before the previous {@code #saveGraphicsState()}. + * @param popState true if the state should also be popped, false if only the PDF command + * should be issued + */ + protected void restoreGraphicsState(boolean popState) { + endTextObject(); + currentStream.add("Q\n"); + if (popState) { + currentState.pop(); + } + } + + /** {@inheritDoc} */ + protected void restoreGraphicsState() { + restoreGraphicsState(true); + } + + /** Indicates the beginning of a text object. */ + protected void beginTextObject() { + if (!textutil.isInTextObject()) { + textutil.beginTextObject(); + } + } + + /** Indicates the end of a text object. */ + protected void endTextObject() { + if (textutil.isInTextObject()) { + textutil.endTextObject(); + } + } + + /** + * Converts a transformation matrix from millipoints to points. + * @param transform the transformation matrix (in millipoints) + * @return the converted transformation matrix (in points) + */ + public AffineTransform toPoints(AffineTransform transform) { + final double[] matrix = new double[6]; + transform.getMatrix(matrix); + //Convert from millipoints to points + matrix[4] /= 1000; + matrix[5] /= 1000; + return new AffineTransform(matrix); + } + + /** + * Concatenates the given transformation matrix with the current one. + * @param transform the transformation matrix (in points) + */ + public void concatenate(AffineTransform transform) { + if (!transform.isIdentity()) { + currentState.concatenate(transform); + currentStream.add(CTMHelper.toPDFString(transform, false) + " cm\n"); + } + } + + /** + * Adds content to the stream. + * @param content the PDF content + */ + public void add(String content) { + currentStream.add(content); + } + + /** + * Formats a float value (normally coordinates in points) as Strings. + * @param value the value + * @return the formatted value + */ + protected static String format(float value) { + return PDFNumber.doubleOut(value); + } + + /** + * Sets the current line width in points. + * @param width line width in points + */ + public void updateLineWidth(float width) { + if (currentState.setLineWidth(width)) { + //Only write if value has changed WRT the current line width + currentStream.add(format(width) + " w\n"); + } + } + + /** + * Establishes a new foreground or fill color. In contrast to updateColor + * this method does not check the PDFState for optimization possibilities. + * @param col the color to apply + * @param fill true to set the fill color, false for the foreground color + * @param pdf StringBuffer to write the PDF code to + *//* + public void setColor(Color col, boolean fill, StringBuffer pdf) { + assert pdf != null; + }*/ + + /** + * Establishes a new foreground or fill color. + * @param col the color to apply + * @param fill true to set the fill color, false for the foreground color + * @param stream the PDFStream to write the PDF code to + */ + public void setColor(Color col, boolean fill, PDFStream stream) { + assert stream != null; + PDFColor color = new PDFColor(this.document, col); + stream.add(color.getColorSpaceOut(fill)); + } + + /** + * Establishes a new foreground or fill color. + * @param col the color to apply + * @param fill true to set the fill color, false for the foreground color + */ + public void setColor(Color col, boolean fill) { + setColor(col, fill, getStream()); + } + + /** + * Establishes a new foreground or fill color. In contrast to updateColor + * this method does not check the PDFState for optimization possibilities. + * @param col the color to apply + * @param fill true to set the fill color, false for the foreground color + * @param pdf StringBuffer to write the PDF code to, if null, the code is + * written to the current stream. + */ + protected void setColor(Color col, boolean fill, StringBuffer pdf) { + if (pdf != null) { + PDFColor color = new PDFColor(this.document, col); + pdf.append(color.getColorSpaceOut(fill)); + } else { + setColor(col, fill, this.currentStream); + } + } + + /** + * Establishes a new foreground or fill color. + * @param col the color to apply (null skips this operation) + * @param fill true to set the fill color, false for the foreground color + * @param pdf StringBuffer to write the PDF code to, if null, the code is + * written to the current stream. + */ + public void updateColor(Color col, boolean fill, StringBuffer pdf) { + if (col == null) { + return; + } + boolean update = false; + if (fill) { + update = getState().setBackColor(col); + } else { + update = getState().setColor(col); + } + + if (update) { + setColor(col, fill, pdf); + } + } + + /** + * 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 the image XObject + */ + public void placeImage(float x, float y, float w, float h, PDFXObject xobj) { + saveGraphicsState(); + add(format(w) + " 0 0 " + + format(-h) + " " + + format(x) + " " + + format(y + h) + + " cm\n" + xobj.getName() + " Do\n"); + restoreGraphicsState(); + } + + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml index fd57d5099..420f16a09 100644 --- a/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml +++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.pdf.PDFEventProducer.nonFullyResolvedLinkTargets">{count} link target{count,equals,1,,s} could not be fully resolved and now point{count,equals,1,,s} to the top of the page or {count,equals,1,is,are} dysfunctional.</message> </catalogue> diff --git a/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java b/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java index f05a16f1a..f69937888 100644 --- a/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java @@ -55,6 +55,7 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { RendererContext context, int x, int y, int width, int height) throws IOException { + PDFContentGenerator generator = renderer.getGenerator(); PDFSVGHandler.PDFInfo pdfInfo = PDFSVGHandler.getPDFInfo(context); float fwidth = width / 1000f; float fheight = height / 1000f; @@ -69,16 +70,17 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { float sx = fwidth / (float)imw; float sy = fheight / (float)imh; - renderer.saveGraphicsState(); - renderer.setColor(Color.black, false, null); - renderer.setColor(Color.black, true, null); + generator.comment("G2D start"); + generator.saveGraphicsState(); + generator.updateColor(Color.black, false, null); + generator.updateColor(Color.black, true, null); //TODO Clip to the image area. // 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. - renderer.currentStream.add(sx + " 0 0 " + sy + " " + fx + " " + generator.add(sx + " 0 0 " + sy + " " + fx + " " + fy + " cm\n"); @@ -95,8 +97,8 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { AffineTransform transform = new AffineTransform(); transform.translate(fx, fy); - pdfInfo.pdfState.concatenate(transform); - graphics.setPDFState(pdfInfo.pdfState); + generator.getState().concatenate(transform); + graphics.setPDFState(generator.getState()); graphics.setOutputStream(pdfInfo.outputStream); if (pdfInfo.paintAsBitmap) { @@ -113,8 +115,9 @@ public class PDFGraphics2DAdapter extends AbstractGraphics2DAdapter { painter.paint(graphics, area); } - pdfInfo.currentStream.add(graphics.getString()); - renderer.restoreGraphicsState(); + generator.add(graphics.getString()); + generator.restoreGraphicsState(); + generator.comment("G2D end"); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java index a58fe5922..610fa274f 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java @@ -19,8 +19,12 @@ package org.apache.fop.render.pdf; +import java.awt.Color; +import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; import java.io.IOException; import org.apache.xmlgraphics.image.loader.Image; @@ -28,12 +32,16 @@ import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.AbstractImageHandlerGraphics2D; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.svg.PDFGraphics2D; /** * PDFImageHandler implementation which handles Graphics2D images. */ -public class PDFImageHandlerGraphics2D implements PDFImageHandler { +public class PDFImageHandlerGraphics2D extends AbstractImageHandlerGraphics2D + implements PDFImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.GRAPHICS2D, @@ -44,13 +52,74 @@ public class PDFImageHandlerGraphics2D implements PDFImageHandler { Point origin, Rectangle pos) throws IOException { PDFRenderer renderer = (PDFRenderer)context.getRenderer(); + /* ImageGraphics2D imageG2D = (ImageGraphics2D)image; renderer.getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(), context, origin.x + pos.x, origin.y + pos.y, pos.width, pos.height); + */ + PDFRenderingContext pdfContext = new PDFRenderingContext( + context.getUserAgent(), + renderer.getGenerator(), + renderer.currentPage, + renderer.getFontInfo()); + Rectangle effPos = new Rectangle(origin.x + pos.x, origin.y + pos.y, pos.width, pos.height); + handleImage(pdfContext, image, effPos); return null; } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageGraphics2D imageG2D = (ImageGraphics2D)image; + float fwidth = pos.width / 1000f; + float fheight = pos.height / 1000f; + float fx = pos.x / 1000f; + float fy = pos.y / 1000f; + + // get the 'width' and 'height' attributes of the SVG document + Dimension dim = image.getInfo().getSize().getDimensionMpt(); + float imw = (float)dim.getWidth() / 1000f; + float imh = (float)dim.getHeight() / 1000f; + + float sx = fwidth / (float)imw; + float sy = fheight / (float)imh; + + generator.comment("G2D start"); + generator.saveGraphicsState(); + generator.updateColor(Color.black, false, null); + generator.updateColor(Color.black, true, null); + + //TODO Clip to the image area. + + // 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. + generator.add(sx + " 0 0 " + sy + " " + fx + " " + fy + " cm\n"); + + final boolean textAsShapes = false; + PDFGraphics2D graphics = new PDFGraphics2D(textAsShapes, + pdfContext.getFontInfo(), generator.getDocument(), + generator.getResourceContext(), pdfContext.getPage().referencePDF(), + "", 0.0f); + graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + AffineTransform transform = new AffineTransform(); + transform.translate(fx, fy); + generator.getState().concatenate(transform); + graphics.setPDFState(generator.getState()); + graphics.setOutputStream(generator.getOutputStream()); + + Rectangle2D area = new Rectangle2D.Double(0.0, 0.0, imw, imh); + imageG2D.getGraphics2DImagePainter().paint(graphics, area); + + generator.add(graphics.getString()); + generator.restoreGraphicsState(); + generator.comment("G2D end"); + } + + /** {@inheritDoc} */ public int getPriority() { return 200; } @@ -65,4 +134,10 @@ public class PDFImageHandlerGraphics2D implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageGraphics2D) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java index 9f56ebfea..75b1d356e 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java @@ -31,12 +31,15 @@ import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RenderingContext; /** - * PDFImageHandler implementation which handles CCITT encoded images (CCITT fax group 3/4). + * Image handler implementation which handles CCITT encoded images (CCITT fax group 3/4) + * for PDF output. */ -public class PDFImageHandlerRawCCITTFax implements PDFImageHandler { +public class PDFImageHandlerRawCCITTFax implements PDFImageHandler, ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.RAW_CCITTFAX, @@ -66,6 +69,24 @@ public class PDFImageHandlerRawCCITTFax implements PDFImageHandler { } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageRawCCITTFax ccitt = (ImageRawCCITTFax)image; + + PDFImage pdfimage = new ImageRawCCITTFaxAdapter(ccitt, image.getInfo().getOriginalURI()); + PDFXObject xobj = generator.getDocument().addImage( + generator.getResourceContext(), pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + generator.placeImage(x, y, w, h, xobj); + } + + /** {@inheritDoc} */ public int getPriority() { return 100; } @@ -80,4 +101,10 @@ public class PDFImageHandlerRawCCITTFax implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRawCCITTFax) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java index f971a49ae..d47d5a439 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java @@ -31,12 +31,14 @@ import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RenderingContext; /** - * PDFImageHandler implementation which handles raw JPEG images. + * Image handler implementation which handles raw JPEG images for PDF output. */ -public class PDFImageHandlerRawJPEG implements PDFImageHandler { +public class PDFImageHandlerRawJPEG implements PDFImageHandler, ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.RAW_JPEG, @@ -66,6 +68,24 @@ public class PDFImageHandlerRawJPEG implements PDFImageHandler { } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageRawJPEG imageJPEG = (ImageRawJPEG)image; + + PDFImage pdfimage = new ImageRawJPEGAdapter(imageJPEG, image.getInfo().getOriginalURI()); + PDFXObject xobj = generator.getDocument().addImage( + generator.getResourceContext(), pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + generator.placeImage(x, y, w, h, xobj); + } + + /** {@inheritDoc} */ public int getPriority() { return 100; } @@ -80,4 +100,10 @@ public class PDFImageHandlerRawJPEG implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRawJPEG) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java index 783cb225c..3e57c7216 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java @@ -31,12 +31,14 @@ import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RenderingContext; /** - * PDFImageHandler implementation which handles RenderedImage instances. + * Image handler implementation which handles RenderedImage instances for PDF output. */ -public class PDFImageHandlerRenderedImage implements PDFImageHandler { +public class PDFImageHandlerRenderedImage implements PDFImageHandler, ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.BUFFERED_IMAGE, @@ -67,6 +69,24 @@ public class PDFImageHandlerRenderedImage implements PDFImageHandler { } /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageRendered imageRend = (ImageRendered)image; + + PDFImage pdfimage = new ImageRenderedAdapter(imageRend, image.getInfo().getOriginalURI()); + PDFXObject xobj = generator.getDocument().addImage( + generator.getResourceContext(), pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + generator.placeImage(x, y, w, h, xobj); + } + + /** {@inheritDoc} */ public int getPriority() { return 300; } @@ -81,4 +101,10 @@ public class PDFImageHandlerRenderedImage implements PDFImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRendered) + && targetContext instanceof PDFRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java new file mode 100644 index 000000000..975f72c06 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.IOException; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.util.SVGConstants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.image.loader.batik.BatikImageFlavors; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.svg.PDFAElementBridge; +import org.apache.fop.svg.PDFBridgeContext; +import org.apache.fop.svg.PDFGraphics2D; +import org.apache.fop.svg.SVGEventProducer; +import org.apache.fop.svg.SVGUserAgent; + +/** + * Image Handler implementation which handles SVG images. + */ +public class PDFImageHandlerSVG implements ImageHandler { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFImageHandlerSVG.class); + + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + PDFRenderingContext pdfContext = (PDFRenderingContext)context; + PDFContentGenerator generator = pdfContext.getGenerator(); + ImageXMLDOM imageSVG = (ImageXMLDOM)image; + + FOUserAgent userAgent = context.getUserAgent(); + final float deviceResolution = userAgent.getTargetResolution(); + if (log.isDebugEnabled()) { + log.debug("Generating SVG at " + deviceResolution + "dpi."); + } + + final float uaResolution = userAgent.getSourceResolution(); + SVGUserAgent ua = new SVGUserAgent(userAgent, new AffineTransform()); + + //Scale for higher resolution on-the-fly images from Batik + double s = uaResolution / deviceResolution; + AffineTransform resolutionScaling = new AffineTransform(); + resolutionScaling.scale(s, s); + + GVTBuilder builder = new GVTBuilder(); + + //Controls whether text painted by Batik is generated using text or path operations + boolean strokeText = false; + //TODO connect with configuration elsewhere. + + BridgeContext ctx = new PDFBridgeContext(ua, + (strokeText ? null : pdfContext.getFontInfo()), + userAgent.getFactory().getImageManager(), + userAgent.getImageSessionContext(), + new AffineTransform()); + + GraphicsNode root; + try { + root = builder.build(ctx, imageSVG.getDocument()); + builder = null; + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI()); + 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 = pos.width / (float)w; + float sy = pos.height / (float)h; + + //Scaling and translation for the bounding box of the image + AffineTransform scaling = new AffineTransform( + sx, 0, 0, sy, pos.x / 1000f, pos.y / 1000f); + + //Transformation matrix that establishes the local coordinate system for the SVG graphic + //in relation to the current coordinate system + AffineTransform imageTransform = new AffineTransform(); + imageTransform.concatenate(scaling); + imageTransform.concatenate(resolutionScaling); + + /* + * Clip to the svg area. + * Note: To have the svg overlay (under) a text area then use + * an fo:block-container + */ + generator.comment("SVG setup"); + generator.saveGraphicsState(); + generator.setColor(Color.black, false); + generator.setColor(Color.black, true); + + if (!scaling.isIdentity()) { + generator.comment("viewbox"); + generator.add(CTMHelper.toPDFString(scaling, false) + " cm\n"); + } + + //SVGSVGElement svg = ((SVGDocument)doc).getRootElement(); + + PDFGraphics2D graphics = new PDFGraphics2D(true, pdfContext.getFontInfo(), + generator.getDocument(), + generator.getResourceContext(), pdfContext.getPage().referencePDF(), + "", 0); + graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + if (!resolutionScaling.isIdentity()) { + generator.comment("resolution scaling for " + uaResolution + + " -> " + deviceResolution + "\n"); + generator.add( + CTMHelper.toPDFString(resolutionScaling, false) + " cm\n"); + graphics.scale(1 / s, 1 / s); + } + + generator.comment("SVG start"); + + //Save state and update coordinate system for the SVG image + generator.getState().push(); + generator.getState().concatenate(imageTransform); + + //Now that we have the complete transformation matrix for the image, we can update the + //transformation matrix for the AElementBridge. + PDFAElementBridge aBridge = (PDFAElementBridge)ctx.getBridge( + SVGDOMImplementation.SVG_NAMESPACE_URI, SVGConstants.SVG_A_TAG); + aBridge.getCurrentTransform().setTransform(generator.getState().getTransform()); + + graphics.setPDFState(generator.getState()); + graphics.setOutputStream(generator.getOutputStream()); + try { + root.paint(graphics); + generator.add(graphics.getString()); + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); + } + generator.getState().pop(); + generator.restoreGraphicsState(); + generator.comment("SVG end"); + } + + /** {@inheritDoc} */ + public int getPriority() { + return 400; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageXMLDOM.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return new ImageFlavor[] { + BatikImageFlavors.SVG_DOM + }; + } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null + || (image instanceof ImageXMLDOM + && image.getFlavor().isCompatible(BatikImageFlavors.SVG_DOM)) + && targetContext instanceof PDFRenderingContext); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java new file mode 100644 index 000000000..389025034 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -0,0 +1,504 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.util.Map; + +import org.w3c.dom.Document; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.xmp.Metadata; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.fo.extensions.xmp.XMPMetadata; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontTriplet; +import org.apache.fop.fonts.LazyFont; +import org.apache.fop.fonts.SingleByteFont; +import org.apache.fop.fonts.Typeface; +import org.apache.fop.pdf.PDFAnnotList; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFTextUtil; +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.intermediate.AbstractBinaryWritingIFPainter; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.IFState; +import org.apache.fop.util.CharUtilities; + +/** + * IFPainter implementation that produces PDF. + */ +public class PDFPainter extends AbstractBinaryWritingIFPainter { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFPainter.class); + + /** Holds the intermediate format state */ + protected IFState state; + + /** the PDF Document being created */ + protected PDFDocument pdfDoc; + + /** + * Utility class which enables all sorts of features that are not directly connected to the + * normal rendering process. + */ + protected PDFRenderingUtil pdfUtil; + + /** the /Resources object of the PDF document being created */ + protected PDFResources pdfResources; + + /** The current content generator */ + protected PDFContentGenerator generator; + + /** the current annotation list to add annotations to */ + protected PDFResourceContext currentContext; + + /** + * Map of pages using the PageViewport as the key + * this is used for prepared pages that cannot be immediately + * rendered + */ + protected Map pages; + + /** the current page to add annotations to */ + protected PDFPage currentPage; + + /** the current page's PDF reference string (to avoid numerous function calls) */ + protected String currentPageRef; + + /** + * Default constructor. + */ + public PDFPainter() { + } + + /** {@inheritDoc} */ + public boolean supportsPagesOutOfOrder() { + return true; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MimeConstants.MIME_PDF; + } + + /** {@inheritDoc} */ + public void setUserAgent(FOUserAgent ua) { + super.setUserAgent(ua); + this.pdfUtil = new PDFRenderingUtil(ua); + } + + PDFRenderingUtil getPDFUtil() { + return this.pdfUtil; + } + + /** {@inheritDoc} */ + public void startDocument() throws IFException { + try { + if (getUserAgent() == null) { + throw new IllegalStateException( + "User agent must be set before starting PDF generation"); + } + if (this.outputStream == null) { + throw new IllegalStateException("OutputStream hasn't been set through setResult()"); + } + this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); + } catch (IOException e) { + throw new IFException("I/O error in startDocument()", e); + } + } + + /** {@inheritDoc} */ + public void startDocumentHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void endDocumentHeader() throws IFException { + pdfUtil.generateDefaultXMPMetadata(); + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + try { + //finishOpenGoTos(); + + pdfDoc.getResources().addFonts(pdfDoc, fontInfo); + pdfDoc.outputTrailer(this.outputStream); + + this.pdfDoc = null; + + this.pages = null; + + //pageReferences.clear(); + pdfResources = null; + this.generator = null; + currentContext = null; + currentPage = null; + + //idPositions.clear(); + //idGoTos.clear(); + } catch (IOException ioe) { + throw new IFException("I/O error in endDocument()", ioe); + } + super.endDocument(); + } + + /** {@inheritDoc} */ + public void startPageSequence(String id) throws IFException { + //TODO page sequence title, country and language + } + + /** {@inheritDoc} */ + public void endPageSequence() throws IFException { + //nop + } + + /** {@inheritDoc} */ + public void startPage(int index, String name, Dimension size) throws IFException { + this.pdfResources = this.pdfDoc.getResources(); + + this.currentPage = this.pdfDoc.getFactory().makePage( + this.pdfResources, + (int)Math.round(size.getWidth() / 1000), + (int)Math.round(size.getHeight() / 1000), + index); + //pageReferences.put(new Integer(index)/*page.getKey()*/, currentPage.referencePDF()); + //pvReferences.put(page.getKey(), page); + + pdfUtil.generatePageLabel(index, name); + + currentPageRef = currentPage.referencePDF(); + + this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream, this.currentPage); + // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's + AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, + size.height / 1000f); + generator.concatenate(basicPageTransform); + } + + /** {@inheritDoc} */ + public void startPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void startPageContent() throws IFException { + this.state = IFState.create(); + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + assert this.state.pop() == null; + } + + /** {@inheritDoc} */ + public void startPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPage() throws IFException { + try { + this.pdfDoc.registerObject(generator.getStream()); + currentPage.setContents(generator.getStream()); + PDFAnnotList annots = currentPage.getAnnotations(); + if (annots != null) { + this.pdfDoc.addObject(annots); + } + this.pdfDoc.addObject(currentPage); + this.generator.flushPDFDoc(); + this.generator = null; + } catch (IOException ioe) { + throw new IFException("I/O error in endPage()", ioe); + } + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) + throws IFException { + generator.saveGraphicsState(); + generator.concatenate(generator.toPoints(transform)); + if (clipRect != null) { + StringBuffer sb = new StringBuffer(); + sb.append(format(clipRect.x)).append(' '); + sb.append(format(clipRect.y)).append(' '); + sb.append(format(clipRect.width)).append(' '); + sb.append(format(clipRect.height)).append(" re W n\n"); + generator.add(sb.toString()); + } + } + + /** {@inheritDoc} */ + public void endViewport() throws IFException { + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform transform) throws IFException { + generator.saveGraphicsState(); + generator.concatenate(generator.toPoints(transform)); + } + + /** {@inheritDoc} */ + public void endGroup() throws IFException { + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException { + PDFXObject xobject = pdfDoc.getXObject(uri); + if (xobject != null) { + placeImage(rect, xobject); + return; + } + + drawImageUsingURI(uri, rect); + + flushPDFDoc(); + } + + /** {@inheritDoc} */ + protected RenderingContext createRenderingContext() { + PDFRenderingContext pdfContext = new PDFRenderingContext( + getUserAgent(), generator, currentPage, getFontInfo()); + return pdfContext; + } + + /** + * 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 the image XObject + */ + private void placeImage(Rectangle rect, PDFXObject xobj) { + generator.saveGraphicsState(); + generator.add(format(rect.width) + " 0 0 " + + format(-rect.height) + " " + + format(rect.x) + " " + + format(rect.y + rect.height ) + + " cm " + xobj.getName() + " Do\n"); + generator.restoreGraphicsState(); + } + + /** {@inheritDoc} */ + public void drawImage(Document doc, Rectangle rect, Map foreignAttributes) throws IFException { + drawImageUsingDocument(doc, rect); + + flushPDFDoc(); + } + + private void flushPDFDoc() throws IFException { + // output new data + try { + generator.flushPDFDoc(); + } catch (IOException ioe) { + throw new IFException("I/O error flushing the PDF document", ioe); + } + } + + /** {@inheritDoc} */ + public void addTarget(String name, int x, int y) throws IFException { + // TODO Auto-generated method stub + + } + + /** + * Formats a integer value (normally coordinates in millipoints) to a String. + * @param value the value (in millipoints) + * @return the formatted value + */ + protected static String format(int value) { + return PDFNumber.doubleOut(value / 1000f); + } + + /** {@inheritDoc} */ + public void drawRect(Rectangle rect, Paint fill, Color stroke) throws IFException { + if (fill == null && stroke == null) { + return; + } + generator.endTextObject(); + if (rect.width != 0 && rect.height != 0) { + if (fill != null) { + if (fill instanceof Color) { + generator.updateColor((Color)fill, true, null); + } else { + throw new UnsupportedOperationException("Non-Color paints NYI"); + } + } + if (stroke != null) { + throw new UnsupportedOperationException("stroke NYI"); + } + StringBuffer sb = new StringBuffer(); + sb.append(format(rect.x)).append(' '); + sb.append(format(rect.y)).append(' '); + sb.append(format(rect.width)).append(' '); + sb.append(format(rect.height)).append(" re"); + if (fill != null) { + sb.append(" f"); + } + if (stroke != null) { + sb.append(" S"); + } + sb.append('\n'); + generator.add(sb.toString()); + } + } + + private Typeface getTypeface(String fontName) { + if (fontName == null) { + throw new NullPointerException("fontName must not be null"); + } + Typeface tf = (Typeface) fontInfo.getFonts().get(fontName); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + return tf; + } + + /** {@inheritDoc} */ + public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException { + //Note: dy is currently ignored + generator.beginTextObject(); + FontTriplet triplet = new FontTriplet( + state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); + //TODO Ignored: state.getFontVariant() + String fontKey = fontInfo.getInternalFontKey(triplet); + int sizeMillipoints = state.getFontSize(); + float fontSize = sizeMillipoints / 1000f; + generator.updateColor(state.getTextColor(), true, null); + + // This assumes that *all* CIDFonts use a /ToUnicode mapping + Typeface tf = getTypeface(fontKey); + SingleByteFont singleByteFont = null; + if (tf instanceof SingleByteFont) { + singleByteFont = (SingleByteFont)tf; + } + Font font = fontInfo.getFontInstance(triplet, sizeMillipoints); + String fontName = font.getFontName(); + + PDFTextUtil textutil = generator.getTextUtil(); + textutil.updateTf(fontKey, fontSize, tf.isMultiByte()); + + textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, x / 1000f, y / 1000f)); + int l = text.length(); + int dxl = (dx != null ? dx.length : 0); + + if (dx != null && dxl > 0 && dx[0] != 0) { + textutil.adjustGlyphTJ(dx[0]); + } + for (int i = 0; i < l; i++) { + char orgChar = text.charAt(i); + char ch; + float glyphAdjust = 0; + if (font.hasChar(orgChar)) { + ch = font.mapChar(orgChar); + if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) { + int encoding = ch / 256; + if (encoding == 0) { + textutil.updateTf(fontName, fontSize, tf.isMultiByte()); + } else { + textutil.updateTf(fontName + "_" + Integer.toString(encoding), + fontSize, tf.isMultiByte()); + ch = (char)(ch % 256); + } + } + } else { + if (CharUtilities.isFixedWidthSpace(orgChar)) { + //Fixed width space are rendered as spaces so copy/paste works in a reader + ch = font.mapChar(CharUtilities.SPACE); + glyphAdjust = font.getCharWidth(ch) - font.getCharWidth(orgChar); + } else { + ch = font.mapChar(orgChar); + } + } + textutil.writeTJMappedChar(ch); + + if (dx != null && i < dxl - 1) { + glyphAdjust += dx[i + 1]; + } + + if (glyphAdjust != 0) { + textutil.adjustGlyphTJ(-glyphAdjust / 10f); + } + + } + textutil.writeTJ(); + } + + /** {@inheritDoc} */ + public void setFont(String family, String style, Integer weight, String variant, Integer size, + Color color) throws IFException { + if (family != null) { + state.setFontFamily(family); + } + if (style != null) { + state.setFontStyle(style); + } + if (weight != null) { + state.setFontWeight(weight.intValue()); + } + if (variant != null) { + state.setFontVariant(variant); + } + if (size != null) { + state.setFontSize(size.intValue()); + } + if (color != null) { + state.setTextColor(color); + } + } + + /** {@inheritDoc} */ + public void handleExtensionObject(Object extension) throws IFException { + if (extension instanceof XMPMetadata) { + pdfUtil.renderXMPMetadata((XMPMetadata)extension); + } else if (extension instanceof Metadata) { + XMPMetadata wrapper = new XMPMetadata(((Metadata)extension)); + pdfUtil.renderXMPMetadata(wrapper); + } else { + throw new UnsupportedOperationException( + "Don't know how to handle extension object: " + extension); + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFPainterMaker.java b/src/java/org/apache/fop/render/pdf/PDFPainterMaker.java new file mode 100644 index 000000000..f1fbe48fd --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFPainterMaker.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.render.intermediate.AbstractIFPainterMaker; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; + +/** + * Painter factory for PDF output. + */ +public class PDFPainterMaker extends AbstractIFPainterMaker { + + //TODO Revert to normal MIME after stabilization! + private static final String[] MIMES = new String[] {MimeConstants.MIME_PDF + ";mode=painter"}; + + /** {@inheritDoc} */ + public IFPainter makePainter(FOUserAgent ua) { + return new PDFPainter(); + } + + /** {@inheritDoc} */ + public boolean needsOutputStream() { + return true; + } + + /** {@inheritDoc} */ + public String[] getSupportedMimeTypes() { + return MIMES; + } + + public IFPainterConfigurator getConfigurator(FOUserAgent userAgent) { + return new PDFRendererConfigurator(userAgent); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index e3aef5e7f..fbcf892a0 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -23,32 +23,22 @@ package org.apache.fop.render.pdf; import java.awt.Color; import java.awt.Point; import java.awt.Rectangle; -import java.awt.color.ICC_Profile; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; -import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Map; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; - -import org.apache.commons.io.IOUtils; - import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.image.loader.util.ImageUtil; -import org.apache.xmlgraphics.xmp.Metadata; -import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; -import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; @@ -84,30 +74,18 @@ import org.apache.fop.fonts.Typeface; import org.apache.fop.pdf.PDFAMode; import org.apache.fop.pdf.PDFAction; import org.apache.fop.pdf.PDFAnnotList; -import org.apache.fop.pdf.PDFColor; -import org.apache.fop.pdf.PDFConformanceException; -import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; -import org.apache.fop.pdf.PDFEncryptionManager; import org.apache.fop.pdf.PDFEncryptionParams; import org.apache.fop.pdf.PDFFactory; -import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFGoTo; -import org.apache.fop.pdf.PDFICCBasedColorSpace; -import org.apache.fop.pdf.PDFICCStream; import org.apache.fop.pdf.PDFInfo; import org.apache.fop.pdf.PDFLink; -import org.apache.fop.pdf.PDFMetadata; import org.apache.fop.pdf.PDFNumber; -import org.apache.fop.pdf.PDFNumsArray; import org.apache.fop.pdf.PDFOutline; -import org.apache.fop.pdf.PDFOutputIntent; import org.apache.fop.pdf.PDFPage; -import org.apache.fop.pdf.PDFPageLabels; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFState; -import org.apache.fop.pdf.PDFStream; import org.apache.fop.pdf.PDFTextUtil; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.pdf.PDFXObject; @@ -115,46 +93,18 @@ import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; import org.apache.fop.util.CharUtilities; -import org.apache.fop.util.ColorProfileUtil; /** * Renderer that renders areas to PDF. */ -public class PDFRenderer extends AbstractPathOrientedRenderer { +public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConfigurationConstants { - /** - * The mime type for pdf - */ + /** The MIME type for PDF */ public static final String MIME_TYPE = MimeConstants.MIME_PDF; /** Normal PDF resolution (72dpi) */ public static final int NORMAL_PDF_RESOLUTION = 72; - /** PDF encryption parameter: all parameters as object, datatype: PDFEncryptionParams */ - public static final String ENCRYPTION_PARAMS = "encryption-params"; - /** PDF encryption parameter: user password, datatype: String */ - public static final String USER_PASSWORD = "user-password"; - /** PDF encryption parameter: owner password, datatype: String */ - public static final String OWNER_PASSWORD = "owner-password"; - /** PDF encryption parameter: Forbids printing, datatype: Boolean or "true"/"false" */ - public static final String NO_PRINT = "noprint"; - /** PDF encryption parameter: Forbids copying content, datatype: Boolean or "true"/"false" */ - public static final String NO_COPY_CONTENT = "nocopy"; - /** PDF encryption parameter: Forbids editing content, datatype: Boolean or "true"/"false" */ - public static final String NO_EDIT_CONTENT = "noedit"; - /** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */ - public static final String NO_ANNOTATIONS = "noannotations"; - /** Rendering Options key for the PDF/A mode. */ - public static final String PDF_A_MODE = "pdf-a-mode"; - /** Rendering Options key for the PDF/X mode. */ - public static final String PDF_X_MODE = "pdf-x-mode"; - /** Rendering Options key for the ICC profile for the output intent. */ - public static final String KEY_OUTPUT_PROFILE = "output-profile"; - /** - * Rendering Options key for disabling the sRGB color space (only possible if no PDF/A or - * PDF/X profile is active). - */ - public static final String KEY_DISABLE_SRGB_COLORSPACE = "disable-srgb-colorspace"; /** Controls whether comments are written to the PDF stream. */ protected static final boolean WRITE_COMMENTS = true; @@ -164,11 +114,11 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected PDFDocument pdfDoc; - /** the PDF/A mode (Default: disabled) */ - protected PDFAMode pdfAMode = PDFAMode.DISABLED; - - /** the PDF/X mode (Default: disabled) */ - protected PDFXMode pdfXMode = PDFXMode.DISABLED; + /** + * Utility class which enables all sorts of features that are not directly connected to the + * normal rendering process. + */ + protected PDFRenderingUtil pdfUtil; /** * Map of pages using the PageViewport as the key @@ -185,7 +135,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** * Maps unique PageViewport key back to PageViewport itself */ - protected Map pvReferences = new java.util.HashMap(); + //protected Map pvReferences = new java.util.HashMap(); /** * Maps XSL-FO element IDs to their on-page XY-positions @@ -216,10 +166,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected PDFResources pdfResources; - /** - * the current stream to add PDF commands to - */ - protected PDFStream currentStream; + /** The current content generator to produce PDF commands with */ + protected PDFContentGenerator generator; /** * the current annotation list to add annotations to @@ -236,30 +184,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected String currentPageRef; - /** the (optional) encryption parameters */ - protected PDFEncryptionParams encryptionParams; - - /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */ - protected PDFICCStream outputProfile; - /** the default sRGB color space. */ - protected PDFICCBasedColorSpace sRGBColorSpace; - /** controls whether the sRGB color space should be installed */ - protected boolean disableSRGBColorSpace = false; - - /** Optional URI to an output profile to be used. */ - protected String outputProfileURI; - - /** drawing state */ - protected PDFState currentState = null; - - /** Text generation utility holding the current font status */ - protected PDFTextUtil textutil; /** page height */ protected int pageHeight; - /** Registry of PDF filters */ - protected Map filterMap; - /** Image handler registry */ private PDFImageHandlerRegistry imageHandlerRegistry = new PDFImageHandlerRegistry(); @@ -269,213 +196,31 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { public PDFRenderer() { } - private boolean booleanValueOf(Object obj) { - if (obj instanceof Boolean) { - return ((Boolean)obj).booleanValue(); - } else if (obj instanceof String) { - return Boolean.valueOf((String)obj).booleanValue(); - } else { - throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected."); - } - } - - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void setUserAgent(FOUserAgent agent) { super.setUserAgent(agent); - PDFEncryptionParams params - = (PDFEncryptionParams)agent.getRendererOptions().get(ENCRYPTION_PARAMS); - if (params != null) { - this.encryptionParams = params; //overwrite if available - } - String pwd; - pwd = (String)agent.getRendererOptions().get(USER_PASSWORD); - if (pwd != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setUserPassword(pwd); - } - pwd = (String)agent.getRendererOptions().get(OWNER_PASSWORD); - if (pwd != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setOwnerPassword(pwd); - } - Object setting; - setting = agent.getRendererOptions().get(NO_PRINT); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowPrint(!booleanValueOf(setting)); - } - setting = agent.getRendererOptions().get(NO_COPY_CONTENT); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting)); - } - setting = agent.getRendererOptions().get(NO_EDIT_CONTENT); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowEditContent(!booleanValueOf(setting)); - } - setting = agent.getRendererOptions().get(NO_ANNOTATIONS); - if (setting != null) { - if (encryptionParams == null) { - this.encryptionParams = new PDFEncryptionParams(); - } - this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting)); - } - String s = (String)agent.getRendererOptions().get(PDF_A_MODE); - if (s != null) { - this.pdfAMode = PDFAMode.valueOf(s); - } - s = (String)agent.getRendererOptions().get(PDF_X_MODE); - if (s != null) { - this.pdfXMode = PDFXMode.valueOf(s); - } - s = (String)agent.getRendererOptions().get(KEY_OUTPUT_PROFILE); - if (s != null) { - this.outputProfileURI = s; - } - setting = agent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE); - if (setting != null) { - this.disableSRGBColorSpace = booleanValueOf(setting); - } + this.pdfUtil = new PDFRenderingUtil(getUserAgent()); } - /** - * {@inheritDoc} - */ - public void startRenderer(OutputStream stream) throws IOException { - if (userAgent == null) { - throw new IllegalStateException("UserAgent must be set before starting the renderer"); - } - ostream = stream; - this.pdfDoc = new PDFDocument( - userAgent.getProducer() != null ? userAgent.getProducer() : ""); - this.pdfDoc.getProfile().setPDFAMode(this.pdfAMode); - this.pdfDoc.getProfile().setPDFXMode(this.pdfXMode); - this.pdfDoc.getInfo().setCreator(userAgent.getCreator()); - this.pdfDoc.getInfo().setCreationDate(userAgent.getCreationDate()); - this.pdfDoc.getInfo().setAuthor(userAgent.getAuthor()); - this.pdfDoc.getInfo().setTitle(userAgent.getTitle()); - this.pdfDoc.getInfo().setKeywords(userAgent.getKeywords()); - this.pdfDoc.setFilterMap(filterMap); - this.pdfDoc.outputHeader(ostream); - - //Setup encryption if necessary - PDFEncryptionManager.setupPDFEncryption(encryptionParams, this.pdfDoc); - - addsRGBColorSpace(); - if (this.outputProfileURI != null) { - addDefaultOutputProfile(); - } - if (pdfXMode != PDFXMode.DISABLED) { - log.debug(pdfXMode + " is active."); - log.warn("Note: " + pdfXMode - + " support is work-in-progress and not fully implemented, yet!"); - addPDFXOutputIntent(); - } - if (pdfAMode.isPDFA1LevelB()) { - log.debug("PDF/A is active. Conformance Level: " + pdfAMode); - addPDFA1OutputIntent(); - } - + PDFRenderingUtil getPDFUtil() { + return this.pdfUtil; } - private void addsRGBColorSpace() throws IOException { - if (disableSRGBColorSpace) { - if (this.pdfAMode != PDFAMode.DISABLED - || this.pdfXMode != PDFXMode.DISABLED - || this.outputProfileURI != null) { - throw new IllegalStateException("It is not possible to disable the sRGB color" - + " space if PDF/A or PDF/X functionality is enabled or an" - + " output profile is set!"); - } - } else { - if (this.sRGBColorSpace != null) { - return; - } - //Map sRGB as default RGB profile for DeviceRGB - this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc); - } + PDFContentGenerator getGenerator() { + return this.generator; } - private void addDefaultOutputProfile() throws IOException { - if (this.outputProfile != null) { - return; - } - ICC_Profile profile; - InputStream in = null; - if (this.outputProfileURI != null) { - this.outputProfile = pdfDoc.getFactory().makePDFICCStream(); - Source src = userAgent.resolveURI(this.outputProfileURI); - if (src == null) { - throw new IOException("Output profile not found: " + this.outputProfileURI); - } - if (src instanceof StreamSource) { - in = ((StreamSource)src).getInputStream(); - } else { - in = new URL(src.getSystemId()).openStream(); - } - try { - profile = ICC_Profile.getInstance(in); - } finally { - IOUtils.closeQuietly(in); - } - this.outputProfile.setColorSpace(profile, null); - } else { - //Fall back to sRGB profile - outputProfile = sRGBColorSpace.getICCStream(); - } - } - - /** - * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces - * are used (which is true if we use DeviceRGB to represent sRGB colors). - * @throws IOException in case of an I/O problem - */ - private void addPDFA1OutputIntent() throws IOException { - addDefaultOutputProfile(); - - String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); - PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); - outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1); - outputIntent.setDestOutputProfile(this.outputProfile); - outputIntent.setOutputConditionIdentifier(desc); - outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); - pdfDoc.getRoot().addOutputIntent(outputIntent); + PDFState getState() { + return getGenerator().getState(); } - /** - * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces - * are used (which is true if we use DeviceRGB to represent sRGB colors). - * @throws IOException in case of an I/O problem - */ - private void addPDFXOutputIntent() throws IOException { - addDefaultOutputProfile(); - - String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); - int deviceClass = this.outputProfile.getICCProfile().getProfileClass(); - if (deviceClass != ICC_Profile.CLASS_OUTPUT) { - throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that" - + " the DestOutputProfile be an Output Device Profile. " - + desc + " does not match that requirement."); + /** {@inheritDoc} */ + public void startRenderer(OutputStream stream) throws IOException { + if (userAgent == null) { + throw new IllegalStateException("UserAgent must be set before starting the renderer"); } - PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); - outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX); - outputIntent.setDestOutputProfile(this.outputProfile); - outputIntent.setOutputConditionIdentifier(desc); - outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); - pdfDoc.getRoot().addOutputIntent(outputIntent); + ostream = stream; + this.pdfDoc = pdfUtil.setupPDFDocument(stream); } /** @@ -498,9 +243,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void stopRenderer() throws IOException { finishOpenGoTos(); @@ -513,13 +256,14 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { pages = null; pageReferences.clear(); - pvReferences.clear(); + //pvReferences.clear(); pdfResources = null; - currentStream = null; + this.generator = null; + //currentStream = null; currentContext = null; currentPage = null; - currentState = null; - this.textutil = null; + //currentState = null; + //this.textutil = null; idPositions.clear(); idGoTos.clear(); @@ -546,7 +290,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } else if (odi instanceof OffDocumentExtensionAttachment) { ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment(); if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) { - renderXMPMetadata((XMPMetadata)attachment); + pdfUtil.renderXMPMetadata((XMPMetadata)attachment); } } } @@ -606,68 +350,29 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } - private void renderXMPMetadata(XMPMetadata metadata) { - Metadata docXMP = metadata.getMetadata(); - Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc); - //Merge FOP's own metadata into the one from the XSL-FO document - fopXMP.mergeInto(docXMP); - XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP); - //Metadata was changed so update metadata date - xmpBasic.setMetadataDate(new java.util.Date()); - PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo()); - - PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( - docXMP, metadata.isReadOnly()); - pdfDoc.getRoot().setMetadata(pdfMetadata); - } - /** {@inheritDoc} */ public Graphics2DAdapter getGraphics2DAdapter() { return new PDFGraphics2DAdapter(this); } - /** - * writes out a comment. - * @param text text for the comment - */ - protected void comment(String text) { - if (WRITE_COMMENTS) { - currentStream.add("% " + text + "\n"); - } - } - /** {@inheritDoc} */ protected void saveGraphicsState() { - endTextObject(); - currentState.push(); - currentStream.add("q\n"); - } - - private void restoreGraphicsState(boolean popState) { - endTextObject(); - currentStream.add("Q\n"); - if (popState) { - currentState.pop(); - } + generator.saveGraphicsState(); } /** {@inheritDoc} */ protected void restoreGraphicsState() { - restoreGraphicsState(true); + generator.restoreGraphicsState(); } /** Indicates the beginning of a text object. */ protected void beginTextObject() { - if (!textutil.isInTextObject()) { - textutil.beginTextObject(); - } + generator.beginTextObject(); } /** Indicates the end of a text object. */ protected void endTextObject() { - if (textutil.isInTextObject()) { - textutil.endTextObject(); - } + generator.endTextObject(); } /** @@ -698,14 +403,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { pdfDoc.getRoot().setLanguage(langCode); } } - if (pdfDoc.getRoot().getMetadata() == null) { - //If at this time no XMP metadata for the overall document has been set, create it - //from the PDFInfo object. - Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc); - PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( - xmp, true); - pdfDoc.getRoot().setMetadata(pdfMetadata); - } + pdfUtil.generateDefaultXMPMetadata(); } /** @@ -731,30 +429,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { Rectangle2D bounds = page.getViewArea(); double w = bounds.getWidth(); double h = bounds.getHeight(); - currentPage = this.pdfDoc.getFactory().makePage( + this.currentPage = this.pdfDoc.getFactory().makePage( this.pdfResources, (int) Math.round(w / 1000), (int) Math.round(h / 1000), page.getPageIndex()); pageReferences.put(page.getKey(), currentPage.referencePDF()); - pvReferences.put(page.getKey(), page); - - //Produce page labels - PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels(); - if (pageLabels == null) { - //Set up PageLabels - pageLabels = this.pdfDoc.getFactory().makePageLabels(); - this.pdfDoc.getRoot().setPageLabels(pageLabels); - } - PDFNumsArray nums = pageLabels.getNums(); - PDFDictionary dict = new PDFDictionary(nums); - dict.put("P", page.getPageNumberString()); - //TODO If the sequence of generated page numbers were inspected, this could be - //expressed in a more space-efficient way - nums.put(page.getPageIndex(), dict); + //pvReferences.put(page.getKey(), page); + + pdfUtil.generatePageLabel(page.getPageIndex(), page.getPageNumberString()); } /** - * This method creates a pdf stream for the current 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. * {@inheritDoc} @@ -774,6 +460,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { double h = bounds.getHeight(); pageHeight = (int) h; + this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage); + /* currentStream = this.pdfDoc.getFactory() .makeStream(PDFFilterList.CONTENT_FILTER, false); this.textutil = new PDFTextUtil() { @@ -783,31 +471,37 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { }; currentState = new PDFState(); + */ // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, pageHeight / 1000f); + generator.concatenate(basicPageTransform); + /* currentState.concatenate(basicPageTransform); currentStream.add(CTMHelper.toPDFString(basicPageTransform, false) + " cm\n"); + */ super.renderPage(page); - this.pdfDoc.registerObject(currentStream); - currentPage.setContents(currentStream); + this.pdfDoc.registerObject(generator.getStream()); + currentPage.setContents(generator.getStream()); PDFAnnotList annots = currentPage.getAnnotations(); if (annots != null) { this.pdfDoc.addObject(annots); } this.pdfDoc.addObject(currentPage); - this.pdfDoc.output(ostream); - this.textutil = null; + this.generator.flushPDFDoc(); + this.generator = null; } /** {@inheritDoc} */ protected void startVParea(CTM ctm, Rectangle2D clippingRect) { saveGraphicsState(); // Set the given CTM in the graphics state + /* currentState.concatenate( new AffineTransform(CTMHelper.toPDFArray(ctm))); + */ if (clippingRect != null) { clipRect((float)clippingRect.getX() / 1000f, @@ -816,7 +510,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { (float)clippingRect.getHeight() / 1000f); } // multiply with current CTM - currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n"); + generator.concatenate(new AffineTransform(CTMHelper.toPDFArray(ctm))); + //currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n"); } /** {@inheritDoc} */ @@ -826,10 +521,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { /** {@inheritDoc} */ protected void concatenateTransformationMatrix(AffineTransform at) { + generator.concatenate(at); + /* if (!at.isIdentity()) { currentState.concatenate(at); currentStream.add(CTMHelper.toPDFString(at, false) + " cm\n"); - } + }*/ } /** @@ -853,7 +550,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } switch (style) { case Constants.EN_DASHED: - setColor(col, false, null); + generator.setColor(col, false); if (horz) { float unit = Math.abs(2 * h); int rep = (int)(w / unit); @@ -861,10 +558,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { rep++; } unit = w / rep; - currentStream.add("[" + format(unit) + "] 0 d "); - currentStream.add(format(h) + " w\n"); + generator.add("[" + format(unit) + "] 0 d "); + generator.add(format(h) + " w\n"); float ym = y1 + (h / 2); - currentStream.add(format(x1) + " " + format(ym) + " m " + generator.add(format(x1) + " " + format(ym) + " m " + format(x2) + " " + format(ym) + " l S\n"); } else { float unit = Math.abs(2 * w); @@ -873,16 +570,16 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { rep++; } unit = h / rep; - currentStream.add("[" + format(unit) + "] 0 d "); - currentStream.add(format(w) + " w\n"); + generator.add("[" + format(unit) + "] 0 d "); + generator.add(format(w) + " w\n"); float xm = x1 + (w / 2); - currentStream.add(format(xm) + " " + format(y1) + " m " + generator.add(format(xm) + " " + format(y1) + " m " + format(xm) + " " + format(y2) + " l S\n"); } break; case Constants.EN_DOTTED: - setColor(col, false, null); - currentStream.add("1 J "); + generator.setColor(col, false); + generator.add("1 J "); if (horz) { float unit = Math.abs(2 * h); int rep = (int)(w / unit); @@ -890,10 +587,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { rep++; } unit = w / rep; - currentStream.add("[0 " + format(unit) + "] 0 d "); - currentStream.add(format(h) + " w\n"); + generator.add("[0 " + format(unit) + "] 0 d "); + generator.add(format(h) + " w\n"); float ym = y1 + (h / 2); - currentStream.add(format(x1) + " " + format(ym) + " m " + generator.add(format(x1) + " " + format(ym) + " m " + format(x2) + " " + format(ym) + " l S\n"); } else { float unit = Math.abs(2 * w); @@ -902,33 +599,33 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { rep++; } unit = h / rep; - currentStream.add("[0 " + format(unit) + " ] 0 d "); - currentStream.add(format(w) + " w\n"); + generator.add("[0 " + format(unit) + " ] 0 d "); + generator.add(format(w) + " w\n"); float xm = x1 + (w / 2); - currentStream.add(format(xm) + " " + format(y1) + " m " + generator.add(format(xm) + " " + format(y1) + " m " + format(xm) + " " + format(y2) + " l S\n"); } break; case Constants.EN_DOUBLE: - setColor(col, false, null); - currentStream.add("[] 0 d "); + generator.setColor(col, false); + generator.add("[] 0 d "); if (horz) { float h3 = h / 3; - currentStream.add(format(h3) + " w\n"); + generator.add(format(h3) + " w\n"); float ym1 = y1 + (h3 / 2); float ym2 = ym1 + h3 + h3; - currentStream.add(format(x1) + " " + format(ym1) + " m " + generator.add(format(x1) + " " + format(ym1) + " m " + format(x2) + " " + format(ym1) + " l S\n"); - currentStream.add(format(x1) + " " + format(ym2) + " m " + generator.add(format(x1) + " " + format(ym2) + " m " + format(x2) + " " + format(ym2) + " l S\n"); } else { float w3 = w / 3; - currentStream.add(format(w3) + " w\n"); + generator.add(format(w3) + " w\n"); float xm1 = x1 + (w3 / 2); float xm2 = xm1 + w3 + w3; - currentStream.add(format(xm1) + " " + format(y1) + " m " + generator.add(format(xm1) + " " + format(y1) + " m " + format(xm1) + " " + format(y2) + " l S\n"); - currentStream.add(format(xm2) + " " + format(y1) + " m " + generator.add(format(xm2) + " " + format(y1) + " m " + format(xm2) + " " + format(y2) + " l S\n"); } break; @@ -936,36 +633,36 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { case Constants.EN_RIDGE: { float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f); - currentStream.add("[] 0 d "); + generator.add("[] 0 d "); if (horz) { Color uppercol = lightenColor(col, -colFactor); Color lowercol = lightenColor(col, colFactor); float h3 = h / 3; - currentStream.add(format(h3) + " w\n"); + generator.add(format(h3) + " w\n"); float ym1 = y1 + (h3 / 2); - setColor(uppercol, false, null); - currentStream.add(format(x1) + " " + format(ym1) + " m " + generator.setColor(uppercol, false); + generator.add(format(x1) + " " + format(ym1) + " m " + format(x2) + " " + format(ym1) + " l S\n"); - setColor(col, false, null); - currentStream.add(format(x1) + " " + format(ym1 + h3) + " m " + generator.setColor(col, false); + generator.add(format(x1) + " " + format(ym1 + h3) + " m " + format(x2) + " " + format(ym1 + h3) + " l S\n"); - setColor(lowercol, false, null); - currentStream.add(format(x1) + " " + format(ym1 + h3 + h3) + " m " + generator.setColor(lowercol, false); + generator.add(format(x1) + " " + format(ym1 + h3 + h3) + " m " + format(x2) + " " + format(ym1 + h3 + h3) + " l S\n"); } else { Color leftcol = lightenColor(col, -colFactor); Color rightcol = lightenColor(col, colFactor); float w3 = w / 3; - currentStream.add(format(w3) + " w\n"); + generator.add(format(w3) + " w\n"); float xm1 = x1 + (w3 / 2); - setColor(leftcol, false, null); - currentStream.add(format(xm1) + " " + format(y1) + " m " + generator.setColor(leftcol, false); + generator.add(format(xm1) + " " + format(y1) + " m " + format(xm1) + " " + format(y2) + " l S\n"); - setColor(col, false, null); - currentStream.add(format(xm1 + w3) + " " + format(y1) + " m " + generator.setColor(col, false); + generator.add(format(xm1 + w3) + " " + format(y1) + " m " + format(xm1 + w3) + " " + format(y2) + " l S\n"); - setColor(rightcol, false, null); - currentStream.add(format(xm1 + w3 + w3) + " " + format(y1) + " m " + generator.setColor(rightcol, false); + generator.add(format(xm1 + w3 + w3) + " " + format(y1) + " m " + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n"); } break; @@ -974,21 +671,21 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { case Constants.EN_OUTSET: { float colFactor = (style == EN_OUTSET ? 0.4f : -0.4f); - currentStream.add("[] 0 d "); + generator.add("[] 0 d "); Color c = col; if (horz) { c = lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); - currentStream.add(format(h) + " w\n"); + generator.add(format(h) + " w\n"); float ym1 = y1 + (h / 2); - setColor(c, false, null); - currentStream.add(format(x1) + " " + format(ym1) + " m " + generator.setColor(c, false); + generator.add(format(x1) + " " + format(ym1) + " m " + format(x2) + " " + format(ym1) + " l S\n"); } else { c = lightenColor(c, (startOrBefore ? 1 : -1) * colFactor); - currentStream.add(format(w) + " w\n"); + generator.add(format(w) + " w\n"); float xm1 = x1 + (w / 2); - setColor(c, false, null); - currentStream.add(format(xm1) + " " + format(y1) + " m " + generator.setColor(c, false); + generator.add(format(xm1) + " " + format(y1) + " m " + format(xm1) + " " + format(y2) + " l S\n"); } break; @@ -996,36 +693,25 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { case Constants.EN_HIDDEN: break; default: - setColor(col, false, null); - currentStream.add("[] 0 d "); + generator.setColor(col, false); + generator.add("[] 0 d "); if (horz) { - currentStream.add(format(h) + " w\n"); + generator.add(format(h) + " w\n"); float ym = y1 + (h / 2); - currentStream.add(format(x1) + " " + format(ym) + " m " + generator.add(format(x1) + " " + format(ym) + " m " + format(x2) + " " + format(ym) + " l S\n"); } else { - currentStream.add(format(w) + " w\n"); + generator.add(format(w) + " w\n"); float xm = x1 + (w / 2); - currentStream.add(format(xm) + " " + format(y1) + " m " + generator.add(format(xm) + " " + format(y1) + " m " + format(xm) + " " + format(y2) + " l S\n"); } } } - /** - * Sets the current line width in points. - * @param width line width in points - */ - private void updateLineWidth(float width) { - if (currentState.setLineWidth(width)) { - //Only write if value has changed WRT the current line width - currentStream.add(format(width) + " w\n"); - } - } - /** {@inheritDoc} */ protected void clipRect(float x, float y, float width, float height) { - currentStream.add(format(x) + " " + format(y) + " " + generator.add(format(x) + " " + format(y) + " " + format(width) + " " + format(height) + " re "); clip(); } @@ -1034,8 +720,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * Clip an area. */ protected void clip() { - currentStream.add("W\n"); - currentStream.add("n\n"); + generator.add("W\n" + "n\n"); } /** @@ -1044,7 +729,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param y y coordinate */ protected void moveTo(float x, float y) { - currentStream.add(format(x) + " " + format(y) + " m "); + generator.add(format(x) + " " + format(y) + " m "); } /** @@ -1054,7 +739,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param y y coordinate */ protected void lineTo(float x, float y) { - currentStream.add(format(x) + " " + format(y) + " l "); + generator.add(format(x) + " " + format(y) + " l "); } /** @@ -1062,7 +747,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * the current point to the starting point of the subpath. */ protected void closePath() { - currentStream.add("h "); + generator.add("h "); } /** @@ -1070,7 +755,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected void fillRect(float x, float y, float w, float h) { if (w != 0 && h != 0) { - currentStream.add(format(x) + " " + format(y) + " " + generator.add(format(x) + " " + format(y) + " " + format(w) + " " + format(h) + " re f\n"); } } @@ -1084,8 +769,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param endy the y end position */ private void drawLine(float startx, float starty, float endx, float endy) { - currentStream.add(format(startx) + " " + format(starty) + " m "); - currentStream.add(format(endx) + " " + format(endy) + " l S\n"); + generator.add(format(startx) + " " + format(starty) + " m "); + generator.add(format(endx) + " " + format(endy) + " l S\n"); } /** @@ -1096,15 +781,15 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { List breakOutList = new java.util.ArrayList(); PDFState.Data data; while (true) { - data = currentState.getData(); - if (currentState.pop() == null) { + data = getState().getData(); + if (getState().pop() == null) { break; } if (breakOutList.size() == 0) { - comment("------ break out!"); + generator.comment("------ break out!"); } breakOutList.add(0, data); //Insert because of stack-popping - restoreGraphicsState(false); + generator.restoreGraphicsState(); } return breakOutList; } @@ -1114,7 +799,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param breakOutList the state stack to restore. */ protected void restoreStateStackAfterBreakOut(List breakOutList) { - comment("------ restoring context after break-out..."); + generator.comment("------ restoring context after break-out..."); PDFState.Data data; Iterator i = breakOutList.iterator(); while (i.hasNext()) { @@ -1126,7 +811,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { //Left out for now because all this painting stuff is very //inconsistent. Some values go over PDFState, some don't. } - comment("------ done."); + generator.comment("------ done."); } /** @@ -1258,7 +943,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ protected void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) { saveAbsolutePosition(id, currentPageRef, - relativeIPP, relativeBPP, currentState.getTransform()); + relativeIPP, relativeBPP, getState().getTransform()); } /** @@ -1282,8 +967,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { bpp += currentBPPosition; } AffineTransform tf = positioning == Block.FIXED - ? currentState.getBaseTransform() - : currentState.getTransform(); + ? getState().getBaseTransform() + : getState().getTransform(); saveAbsolutePosition(id, currentPageRef, ipp, bpp, tf); } } @@ -1346,7 +1031,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { int bpp = currentBPPosition + ip.getOffset(); ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f, ip.getIPD() / 1000f, ip.getBPD() / 1000f); - AffineTransform transform = currentState.getTransform(); + AffineTransform transform = getState().getTransform(); ipRect = transform.createTransformedShape(ipRect).getBounds2D(); factory = pdfDoc.getFactory(); @@ -1422,6 +1107,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontName); + PDFTextUtil textutil = generator.getTextUtil(); textutil.updateTf(fontName, size / 1000f, tf.isMultiByte()); @@ -1465,7 +1151,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { if (tws != 0) { float adjust = tws / (font.getFontSize() / 1000f); - textutil.adjustGlyphTJ(adjust); + generator.getTextUtil().adjustGlyphTJ(adjust); } } @@ -1504,6 +1190,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { if (tf instanceof SingleByteFont) { singleByteFont = (SingleByteFont)tf; } + PDFTextUtil textutil = generator.getTextUtil(); int l = s.length(); @@ -1549,50 +1236,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } - /** - * Establishes a new foreground or fill color. In contrast to updateColor - * this method does not check the PDFState for optimization possibilities. - * @param col the color to apply - * @param fill true to set the fill color, false for the foreground color - * @param pdf StringBuffer to write the PDF code to, if null, the code is - * written to the current stream. - */ - protected void setColor(Color col, boolean fill, StringBuffer pdf) { - PDFColor color = new PDFColor(this.pdfDoc, col); - - if (pdf != null) { - pdf.append(color.getColorSpaceOut(fill)); - } else { - currentStream.add(color.getColorSpaceOut(fill)); - } - } - - /** - * Establishes a new foreground or fill color. - * @param col the color to apply (null skips this operation) - * @param fill true to set the fill color, false for the foreground color - * @param pdf StringBuffer to write the PDF code to, if null, the code is - * written to the current stream. - */ - private void updateColor(Color col, boolean fill, StringBuffer pdf) { - if (col == null) { - return; - } - boolean update = false; - if (fill) { - update = currentState.setBackColor(col); - } else { - update = currentState.setColor(col); - } - - if (update) { - setColor(col, fill, pdf); - } - } - /** {@inheritDoc} */ protected void updateColor(Color col, boolean fill) { - updateColor(col, fill, null); + generator.updateColor(col, fill, null); } /** {@inheritDoc} */ @@ -1651,8 +1297,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { info = manager.getImageInfo(uri, sessionContext); Map hints = ImageUtil.getDefaultHints(sessionContext); + ImageFlavor[] supportedFlavors = imageHandlerRegistry.getSupportedFlavors(); org.apache.xmlgraphics.image.loader.Image img = manager.getImage( - info, imageHandlerRegistry.getSupportedFlavors(), hints, sessionContext); + info, supportedFlavors, hints, sessionContext); //First check for a dynamically registered handler PDFImageHandler handler = imageHandlerRegistry.getHandler(img.getClass()); @@ -1691,7 +1338,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { // output new data try { - this.pdfDoc.output(ostream); + this.generator.flushPDFDoc(); } catch (IOException ioe) { // ioexception will be caught later } @@ -1707,7 +1354,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { */ public void placeImage(float x, float y, float w, float h, PDFXObject xobj) { saveGraphicsState(); - currentStream.add(format(w) + " 0 0 " + generator.add(format(w) + " 0 0 " + format(-h) + " " + format(currentIPPosition / 1000f + x) + " " + format(currentBPPosition / 1000f + h + y) @@ -1722,12 +1369,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { x, y, width, height, foreignAttributes); context.setProperty(PDFRendererContextConstants.PDF_DOCUMENT, pdfDoc); context.setProperty(PDFRendererContextConstants.OUTPUT_STREAM, ostream); - context.setProperty(PDFRendererContextConstants.PDF_STATE, currentState); + context.setProperty(PDFRendererContextConstants.PDF_STATE, getState()); context.setProperty(PDFRendererContextConstants.PDF_PAGE, currentPage); context.setProperty(PDFRendererContextConstants.PDF_CONTEXT, currentContext == null ? currentPage : currentContext); context.setProperty(PDFRendererContextConstants.PDF_CONTEXT, currentContext); - context.setProperty(PDFRendererContextConstants.PDF_STREAM, currentStream); + context.setProperty(PDFRendererContextConstants.PDF_STREAM, generator.getStream()); context.setProperty(PDFRendererContextConstants.PDF_FONT_INFO, fontInfo); context.setProperty(PDFRendererContextConstants.PDF_FONT_NAME, ""); context.setProperty(PDFRendererContextConstants.PDF_FONT_SIZE, new Integer(0)); @@ -1742,7 +1389,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { public void renderLeader(Leader area) { renderInlineAreaBackAndBorders(area); - currentState.push(); + getState().push(); saveGraphicsState(); int style = area.getRuleStyle(); float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f; @@ -1763,7 +1410,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { clipRect(startx, starty, endx - startx, ruleThickness); //This displaces the dots to the right by half a dot's width //TODO There's room for improvement here - currentStream.add("1 0 0 1 " + format(ruleThickness / 2) + " 0 cm\n"); + generator.add("1 0 0 1 " + format(ruleThickness / 2) + " 0 cm\n"); drawBorderLine(startx, starty, endx, starty + ruleThickness, true, true, style, col); break; @@ -1771,36 +1418,36 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { case EN_RIDGE: float half = area.getRuleThickness() / 2000f; - setColor(lightenColor(col, 0.6f), true, null); - currentStream.add(format(startx) + " " + format(starty) + " m\n"); - currentStream.add(format(endx) + " " + format(starty) + " l\n"); - currentStream.add(format(endx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add("h\n"); - currentStream.add("f\n"); - setColor(col, true, null); + generator.setColor(lightenColor(col, 0.6f), true); + generator.add(format(startx) + " " + format(starty) + " m\n"); + generator.add(format(endx) + " " + format(starty) + " l\n"); + generator.add(format(endx) + " " + format(starty + 2 * half) + " l\n"); + generator.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); + generator.add("h\n"); + generator.add("f\n"); + generator.setColor(col, true); if (style == EN_GROOVE) { - currentStream.add(format(startx) + " " + format(starty) + " m\n"); - currentStream.add(format(endx) + " " + format(starty) + " l\n"); - currentStream.add(format(endx) + " " + format(starty + half) + " l\n"); - currentStream.add(format(startx + half) + " " + format(starty + half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); + generator.add(format(startx) + " " + format(starty) + " m\n"); + generator.add(format(endx) + " " + format(starty) + " l\n"); + generator.add(format(endx) + " " + format(starty + half) + " l\n"); + generator.add(format(startx + half) + " " + format(starty + half) + " l\n"); + generator.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); } else { - currentStream.add(format(endx) + " " + format(starty) + " m\n"); - currentStream.add(format(endx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); - currentStream.add(format(startx) + " " + format(starty + half) + " l\n"); - currentStream.add(format(endx - half) + " " + format(starty + half) + " l\n"); + generator.add(format(endx) + " " + format(starty) + " m\n"); + generator.add(format(endx) + " " + format(starty + 2 * half) + " l\n"); + generator.add(format(startx) + " " + format(starty + 2 * half) + " l\n"); + generator.add(format(startx) + " " + format(starty + half) + " l\n"); + generator.add(format(endx - half) + " " + format(starty + half) + " l\n"); } - currentStream.add("h\n"); - currentStream.add("f\n"); + generator.add("h\n"); + generator.add("f\n"); break; default: throw new UnsupportedOperationException("rule style not supported"); } restoreGraphicsState(); - currentState.pop(); + getState().pop(); beginTextObject(); super.renderLeader(area); } @@ -1815,7 +1462,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param mode the PDF/A mode */ public void setAMode(PDFAMode mode) { - this.pdfAMode = mode; + this.pdfUtil.setAMode(mode); } /** @@ -1823,7 +1470,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param mode the PDF/X mode */ public void setXMode(PDFXMode mode) { - this.pdfXMode = mode; + this.pdfUtil.setXMode(mode); } /** @@ -1831,7 +1478,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param outputProfileURI the URI to the output color profile */ public void setOutputProfileURI(String outputProfileURI) { - this.outputProfileURI = outputProfileURI; + this.pdfUtil.setOutputProfileURI(outputProfileURI); } /** @@ -1839,7 +1486,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param filterMap the filter map */ public void setFilterMap(Map filterMap) { - this.filterMap = filterMap; + this.pdfUtil.setFilterMap(filterMap); } /** @@ -1847,7 +1494,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param encryptionParams the encryption parameters */ public void setEncryptionParams(PDFEncryptionParams encryptionParams) { - this.encryptionParams = encryptionParams; + this.pdfUtil.setEncryptionParams(encryptionParams); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java index 4ac16d725..2504a4457 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java @@ -27,18 +27,29 @@ import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.CustomFontCollection; +import org.apache.fop.fonts.FontCollection; +import org.apache.fop.fonts.FontEventAdapter; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontManager; +import org.apache.fop.fonts.FontResolver; +import org.apache.fop.fonts.base14.Base14FontCollection; import org.apache.fop.pdf.PDFAMode; import org.apache.fop.pdf.PDFEncryptionParams; import org.apache.fop.pdf.PDFFilterList; import org.apache.fop.pdf.PDFXMode; +import org.apache.fop.render.DefaultFontResolver; import org.apache.fop.render.PrintRendererConfigurator; import org.apache.fop.render.Renderer; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; import org.apache.fop.util.LogUtil; /** * PDF renderer configurator */ -public class PDFRendererConfigurator extends PrintRendererConfigurator { +public class PDFRendererConfigurator extends PrintRendererConfigurator + implements IFPainterConfigurator { /** * Default constructor @@ -59,75 +70,82 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator { Configuration cfg = super.getRendererConfig(renderer); if (cfg != null) { PDFRenderer pdfRenderer = (PDFRenderer)renderer; - //PDF filters - try { - Map filterMap = buildFilterMapFromConfiguration(cfg); - if (filterMap != null) { - pdfRenderer.setFilterMap(filterMap); - } - } catch (ConfigurationException e) { - LogUtil.handleException(log, e, false); - } - super.configure(renderer); - String s = cfg.getChild(PDFRenderer.PDF_A_MODE, true).getValue(null); - if (s != null) { - pdfRenderer.setAMode(PDFAMode.valueOf(s)); - } - s = cfg.getChild(PDFRenderer.PDF_X_MODE, true).getValue(null); - if (s != null) { - pdfRenderer.setXMode(PDFXMode.valueOf(s)); + PDFRenderingUtil pdfUtil = pdfRenderer.getPDFUtil(); + configure(cfg, pdfUtil); + } + } + + private void configure(Configuration cfg, PDFRenderingUtil pdfUtil) throws FOPException { + //PDF filters + try { + Map filterMap = buildFilterMapFromConfiguration(cfg); + if (filterMap != null) { + pdfUtil.setFilterMap(filterMap); } - Configuration encryptionParamsConfig = cfg.getChild(PDFRenderer.ENCRYPTION_PARAMS, false); - if (encryptionParamsConfig != null) { - PDFEncryptionParams encryptionParams = new PDFEncryptionParams(); - Configuration ownerPasswordConfig = encryptionParamsConfig.getChild( - PDFRenderer.OWNER_PASSWORD, false); - if (ownerPasswordConfig != null) { - String ownerPassword = ownerPasswordConfig.getValue(null); - if (ownerPassword != null) { - encryptionParams.setOwnerPassword(ownerPassword); - } - } - Configuration userPasswordConfig = encryptionParamsConfig.getChild( - PDFRenderer.USER_PASSWORD, false); - if (userPasswordConfig != null) { - String userPassword = userPasswordConfig.getValue(null); - if (userPassword != null) { - encryptionParams.setUserPassword(userPassword); - } - } - Configuration noPrintConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_PRINT, false); - if (noPrintConfig != null) { - encryptionParams.setAllowPrint(false); - } - Configuration noCopyContentConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_COPY_CONTENT, false); - if (noCopyContentConfig != null) { - encryptionParams.setAllowCopyContent(false); - } - Configuration noEditContentConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_EDIT_CONTENT, false); - if (noEditContentConfig != null) { - encryptionParams.setAllowEditContent(false); + } catch (ConfigurationException e) { + LogUtil.handleException(log, e, false); + } + + String s = cfg.getChild(PDFRenderer.PDF_A_MODE, true).getValue(null); + if (s != null) { + pdfUtil.setAMode(PDFAMode.valueOf(s)); + } + s = cfg.getChild(PDFRenderer.PDF_X_MODE, true).getValue(null); + if (s != null) { + pdfUtil.setXMode(PDFXMode.valueOf(s)); + } + Configuration encryptionParamsConfig = cfg.getChild(PDFRenderer.ENCRYPTION_PARAMS, false); + if (encryptionParamsConfig != null) { + PDFEncryptionParams encryptionParams = new PDFEncryptionParams(); + Configuration ownerPasswordConfig = encryptionParamsConfig.getChild( + PDFRenderer.OWNER_PASSWORD, false); + if (ownerPasswordConfig != null) { + String ownerPassword = ownerPasswordConfig.getValue(null); + if (ownerPassword != null) { + encryptionParams.setOwnerPassword(ownerPassword); } - Configuration noAnnotationsConfig = encryptionParamsConfig.getChild( - PDFRenderer.NO_ANNOTATIONS, false); - if (noAnnotationsConfig != null) { - encryptionParams.setAllowEditAnnotations(false); + } + Configuration userPasswordConfig = encryptionParamsConfig.getChild( + PDFRenderer.USER_PASSWORD, false); + if (userPasswordConfig != null) { + String userPassword = userPasswordConfig.getValue(null); + if (userPassword != null) { + encryptionParams.setUserPassword(userPassword); } - pdfRenderer.setEncryptionParams(encryptionParams); } - s = cfg.getChild(PDFRenderer.KEY_OUTPUT_PROFILE, true).getValue(null); - if (s != null) { - pdfRenderer.setOutputProfileURI(s); + Configuration noPrintConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_PRINT, false); + if (noPrintConfig != null) { + encryptionParams.setAllowPrint(false); + } + Configuration noCopyContentConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_COPY_CONTENT, false); + if (noCopyContentConfig != null) { + encryptionParams.setAllowCopyContent(false); + } + Configuration noEditContentConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_EDIT_CONTENT, false); + if (noEditContentConfig != null) { + encryptionParams.setAllowEditContent(false); } - Configuration disableColorSpaceConfig = cfg.getChild(PDFRenderer.KEY_DISABLE_SRGB_COLORSPACE, false); - if (disableColorSpaceConfig != null) { - pdfRenderer.disableSRGBColorSpace = disableColorSpaceConfig.getValueAsBoolean(false); + Configuration noAnnotationsConfig = encryptionParamsConfig.getChild( + PDFRenderer.NO_ANNOTATIONS, false); + if (noAnnotationsConfig != null) { + encryptionParams.setAllowEditAnnotations(false); } + pdfUtil.setEncryptionParams(encryptionParams); + } + s = cfg.getChild(PDFRenderer.KEY_OUTPUT_PROFILE, true).getValue(null); + if (s != null) { + pdfUtil.setOutputProfileURI(s); + } + Configuration disableColorSpaceConfig + = cfg.getChild(PDFRenderer.KEY_DISABLE_SRGB_COLORSPACE, false); + if (disableColorSpaceConfig != null) { + pdfUtil.setDisableSRGBColorSpace( + disableColorSpaceConfig.getValueAsBoolean(false)); } } @@ -178,4 +196,38 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator { } return filterMap; } + + // ---=== IFPainter configuration ===--- + + /** {@inheritDoc} */ + public void configure(IFPainter painter) throws FOPException { + Configuration cfg = super.getRendererConfig(painter.getMimeType()); + if (cfg != null) { + PDFPainter pdfPainter = (PDFPainter)painter; + PDFRenderingUtil pdfUtil = pdfPainter.getPDFUtil(); + configure(cfg, pdfUtil); + } + } + + /** {@inheritDoc} */ + public void setupFontInfo(IFPainter painter) throws FOPException { + FontManager fontManager = userAgent.getFactory().getFontManager(); + List fontCollections = new java.util.ArrayList(); + fontCollections.add(new Base14FontCollection(fontManager.isBase14KerningEnabled())); + + Configuration cfg = super.getRendererConfig(painter.getMimeType()); + if (cfg != null) { + FontResolver fontResolver = new DefaultFontResolver(userAgent); + List fontList = buildFontList(cfg, fontResolver); + fontCollections.add(new CustomFontCollection(fontResolver, fontList)); + } + + FontInfo fontInfo = new FontInfo(); + fontInfo.setEventListener(new FontEventAdapter(userAgent.getEventBroadcaster())); + fontManager.setup(fontInfo, + (FontCollection[])fontCollections.toArray( + new FontCollection[fontCollections.size()])); + painter.setFontInfo(fontInfo); + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java new file mode 100644 index 000000000..98b0c8203 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import org.apache.xmlgraphics.util.MimeConstants; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.render.AbstractRenderingContext; + +/** + * Rendering context for PDF production. + */ +public class PDFRenderingContext extends AbstractRenderingContext { + + private PDFContentGenerator generator; + private FontInfo fontInfo; + private PDFPage page; + + /** + * Main constructor. + * @param userAgent the user agent + * @param generator the PDF content generator + * @param page the current PDF page + * @param fontInfo the font list + */ + public PDFRenderingContext(FOUserAgent userAgent, + PDFContentGenerator generator, PDFPage page, FontInfo fontInfo) { + super(userAgent); + this.generator = generator; + this.page = page; + this.fontInfo = fontInfo; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MimeConstants.MIME_PDF; + } + + /** + * Returns the PDF content generator. + * @return the PDF content generator + */ + public PDFContentGenerator getGenerator() { + return this.generator; + } + + /** + * Returns the current PDF page. + * @return the PDF page + */ + public PDFPage getPage() { + return this.page; + } + + /** + * Returns the font list. + * @return the font list + */ + public FontInfo getFontInfo() { + return this.fontInfo; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java new file mode 100644 index 000000000..e44edf8af --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -0,0 +1,410 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.color.ICC_Profile; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.Map; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.xmp.Metadata; +import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; +import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.extensions.xmp.XMPMetadata; +import org.apache.fop.pdf.PDFAMode; +import org.apache.fop.pdf.PDFConformanceException; +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFEncryptionManager; +import org.apache.fop.pdf.PDFEncryptionParams; +import org.apache.fop.pdf.PDFICCBasedColorSpace; +import org.apache.fop.pdf.PDFICCStream; +import org.apache.fop.pdf.PDFInfo; +import org.apache.fop.pdf.PDFMetadata; +import org.apache.fop.pdf.PDFNumsArray; +import org.apache.fop.pdf.PDFOutputIntent; +import org.apache.fop.pdf.PDFPageLabels; +import org.apache.fop.pdf.PDFXMode; +import org.apache.fop.util.ColorProfileUtil; + +/** + * Utility class which enables all sorts of features that are not directly connected to the + * normal rendering process. + */ +class PDFRenderingUtil implements PDFConfigurationConstants { + + /** logging instance */ + private static Log log = LogFactory.getLog(PDFRenderingUtil.class); + + private FOUserAgent userAgent; + + /** the PDF Document being created */ + protected PDFDocument pdfDoc; + + /** the PDF/A mode (Default: disabled) */ + protected PDFAMode pdfAMode = PDFAMode.DISABLED; + + /** the PDF/X mode (Default: disabled) */ + protected PDFXMode pdfXMode = PDFXMode.DISABLED; + + /** the (optional) encryption parameters */ + protected PDFEncryptionParams encryptionParams; + + /** Registry of PDF filters */ + protected Map filterMap; + + /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */ + protected PDFICCStream outputProfile; + /** the default sRGB color space. */ + protected PDFICCBasedColorSpace sRGBColorSpace; + /** controls whether the sRGB color space should be installed */ + protected boolean disableSRGBColorSpace = false; + + /** Optional URI to an output profile to be used. */ + protected String outputProfileURI; + + + PDFRenderingUtil(FOUserAgent userAgent) { + this.userAgent = userAgent; + initialize(); + } + + private static boolean booleanValueOf(Object obj) { + if (obj instanceof Boolean) { + return ((Boolean)obj).booleanValue(); + } else if (obj instanceof String) { + return Boolean.valueOf((String)obj).booleanValue(); + } else { + throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected."); + } + } + + private void initialize() { + PDFEncryptionParams params + = (PDFEncryptionParams)userAgent.getRendererOptions().get(ENCRYPTION_PARAMS); + if (params != null) { + this.encryptionParams = params; //overwrite if available + } + String pwd; + pwd = (String)userAgent.getRendererOptions().get(USER_PASSWORD); + if (pwd != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setUserPassword(pwd); + } + pwd = (String)userAgent.getRendererOptions().get(OWNER_PASSWORD); + if (pwd != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setOwnerPassword(pwd); + } + Object setting; + setting = userAgent.getRendererOptions().get(NO_PRINT); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowPrint(!booleanValueOf(setting)); + } + setting = userAgent.getRendererOptions().get(NO_COPY_CONTENT); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowCopyContent(!booleanValueOf(setting)); + } + setting = userAgent.getRendererOptions().get(NO_EDIT_CONTENT); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowEditContent(!booleanValueOf(setting)); + } + setting = userAgent.getRendererOptions().get(NO_ANNOTATIONS); + if (setting != null) { + if (encryptionParams == null) { + this.encryptionParams = new PDFEncryptionParams(); + } + this.encryptionParams.setAllowEditAnnotations(!booleanValueOf(setting)); + } + String s = (String)userAgent.getRendererOptions().get(PDF_A_MODE); + if (s != null) { + this.pdfAMode = PDFAMode.valueOf(s); + } + s = (String)userAgent.getRendererOptions().get(PDF_X_MODE); + if (s != null) { + this.pdfXMode = PDFXMode.valueOf(s); + } + s = (String)userAgent.getRendererOptions().get(KEY_OUTPUT_PROFILE); + if (s != null) { + this.outputProfileURI = s; + } + setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE); + if (setting != null) { + this.disableSRGBColorSpace = booleanValueOf(setting); + } + } + + public FOUserAgent getUserAgent() { + return this.userAgent; + } + + /** + * Sets the PDF/A mode for the PDF renderer. + * @param mode the PDF/A mode + */ + public void setAMode(PDFAMode mode) { + this.pdfAMode = mode; + } + + /** + * Sets the PDF/X mode for the PDF renderer. + * @param mode the PDF/X mode + */ + public void setXMode(PDFXMode mode) { + this.pdfXMode = mode; + } + + /** + * Sets the output color profile for the PDF renderer. + * @param outputProfileURI the URI to the output color profile + */ + public void setOutputProfileURI(String outputProfileURI) { + this.outputProfileURI = outputProfileURI; + } + + /** + * Enables or disables the default sRGB color space needed for the PDF document to preserve + * the sRGB colors used in XSL-FO. + * @param disable true to disable, false to enable + */ + public void setDisableSRGBColorSpace(boolean disable) { + this.disableSRGBColorSpace = disable; + } + + /** + * Sets the filter map to be used by the PDF renderer. + * @param filterMap the filter map + */ + public void setFilterMap(Map filterMap) { + this.filterMap = filterMap; + } + + /** + * Sets the encryption parameters used by the PDF renderer. + * @param encryptionParams the encryption parameters + */ + public void setEncryptionParams(PDFEncryptionParams encryptionParams) { + this.encryptionParams = encryptionParams; + } + + private void updateInfo() { + PDFInfo info = pdfDoc.getInfo(); + info.setCreator(userAgent.getCreator()); + info.setCreationDate(userAgent.getCreationDate()); + info.setAuthor(userAgent.getAuthor()); + info.setTitle(userAgent.getTitle()); + info.setKeywords(userAgent.getKeywords()); + } + + private void updatePDFProfiles() { + pdfDoc.getProfile().setPDFAMode(this.pdfAMode); + pdfDoc.getProfile().setPDFXMode(this.pdfXMode); + } + + private void addsRGBColorSpace() throws IOException { + if (disableSRGBColorSpace) { + if (this.pdfAMode != PDFAMode.DISABLED + || this.pdfXMode != PDFXMode.DISABLED + || this.outputProfileURI != null) { + throw new IllegalStateException("It is not possible to disable the sRGB color" + + " space if PDF/A or PDF/X functionality is enabled or an" + + " output profile is set!"); + } + } else { + if (this.sRGBColorSpace != null) { + return; + } + //Map sRGB as default RGB profile for DeviceRGB + this.sRGBColorSpace = PDFICCBasedColorSpace.setupsRGBAsDefaultRGBColorSpace(pdfDoc); + } + } + + private void addDefaultOutputProfile() throws IOException { + if (this.outputProfile != null) { + return; + } + ICC_Profile profile; + InputStream in = null; + if (this.outputProfileURI != null) { + this.outputProfile = pdfDoc.getFactory().makePDFICCStream(); + Source src = getUserAgent().resolveURI(this.outputProfileURI); + if (src == null) { + throw new IOException("Output profile not found: " + this.outputProfileURI); + } + if (src instanceof StreamSource) { + in = ((StreamSource)src).getInputStream(); + } else { + in = new URL(src.getSystemId()).openStream(); + } + try { + profile = ICC_Profile.getInstance(in); + } finally { + IOUtils.closeQuietly(in); + } + this.outputProfile.setColorSpace(profile, null); + } else { + //Fall back to sRGB profile + outputProfile = sRGBColorSpace.getICCStream(); + } + } + + /** + * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces + * are used (which is true if we use DeviceRGB to represent sRGB colors). + * @throws IOException in case of an I/O problem + */ + private void addPDFA1OutputIntent() throws IOException { + addDefaultOutputProfile(); + + String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); + PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); + outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1); + outputIntent.setDestOutputProfile(this.outputProfile); + outputIntent.setOutputConditionIdentifier(desc); + outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); + pdfDoc.getRoot().addOutputIntent(outputIntent); + } + + /** + * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces + * are used (which is true if we use DeviceRGB to represent sRGB colors). + * @throws IOException in case of an I/O problem + */ + private void addPDFXOutputIntent() throws IOException { + addDefaultOutputProfile(); + + String desc = ColorProfileUtil.getICCProfileDescription(this.outputProfile.getICCProfile()); + int deviceClass = this.outputProfile.getICCProfile().getProfileClass(); + if (deviceClass != ICC_Profile.CLASS_OUTPUT) { + throw new PDFConformanceException(pdfDoc.getProfile().getPDFXMode() + " requires that" + + " the DestOutputProfile be an Output Device Profile. " + + desc + " does not match that requirement."); + } + PDFOutputIntent outputIntent = pdfDoc.getFactory().makeOutputIntent(); + outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX); + outputIntent.setDestOutputProfile(this.outputProfile); + outputIntent.setOutputConditionIdentifier(desc); + outputIntent.setInfo(outputIntent.getOutputConditionIdentifier()); + pdfDoc.getRoot().addOutputIntent(outputIntent); + } + + public void renderXMPMetadata(XMPMetadata metadata) { + Metadata docXMP = metadata.getMetadata(); + Metadata fopXMP = PDFMetadata.createXMPFromPDFDocument(pdfDoc); + //Merge FOP's own metadata into the one from the XSL-FO document + fopXMP.mergeInto(docXMP); + XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP); + //Metadata was changed so update metadata date + xmpBasic.setMetadataDate(new java.util.Date()); + PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo()); + + PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( + docXMP, metadata.isReadOnly()); + pdfDoc.getRoot().setMetadata(pdfMetadata); + } + + public void generateDefaultXMPMetadata() { + if (pdfDoc.getRoot().getMetadata() == null) { + //If at this time no XMP metadata for the overall document has been set, create it + //from the PDFInfo object. + Metadata xmp = PDFMetadata.createXMPFromPDFDocument(pdfDoc); + PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata( + xmp, true); + pdfDoc.getRoot().setMetadata(pdfMetadata); + } + } + + public PDFDocument setupPDFDocument(OutputStream out) throws IOException { + if (this.pdfDoc != null) { + throw new IllegalStateException("PDFDocument already set up"); + } + this.pdfDoc = new PDFDocument( + userAgent.getProducer() != null ? userAgent.getProducer() : ""); + updateInfo(); + updatePDFProfiles(); + pdfDoc.setFilterMap(filterMap); + pdfDoc.outputHeader(out); + + //Setup encryption if necessary + PDFEncryptionManager.setupPDFEncryption(encryptionParams, pdfDoc); + + addsRGBColorSpace(); + if (this.outputProfileURI != null) { + addDefaultOutputProfile(); + } + if (pdfXMode != PDFXMode.DISABLED) { + log.debug(pdfXMode + " is active."); + log.warn("Note: " + pdfXMode + + " support is work-in-progress and not fully implemented, yet!"); + addPDFXOutputIntent(); + } + if (pdfAMode.isPDFA1LevelB()) { + log.debug("PDF/A is active. Conformance Level: " + pdfAMode); + addPDFA1OutputIntent(); + } + return this.pdfDoc; + } + + /** + * Generates a page label in the PDF document. + * @param pageIndex the index of the page + * @param pageNumber the formatted page number + */ + public void generatePageLabel(int pageIndex, String pageNumber) { + //Produce page labels + PDFPageLabels pageLabels = this.pdfDoc.getRoot().getPageLabels(); + if (pageLabels == null) { + //Set up PageLabels + pageLabels = this.pdfDoc.getFactory().makePageLabels(); + this.pdfDoc.getRoot().setPageLabels(pageLabels); + } + PDFNumsArray nums = pageLabels.getNums(); + PDFDictionary dict = new PDFDictionary(nums); + dict.put("P", pageNumber); + //TODO If the sequence of generated page numbers were inspected, this could be + //expressed in a more space-efficient way + nums.put(pageIndex, dict); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java index 864a82517..11d9b1c3f 100644 --- a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java @@ -44,8 +44,6 @@ import org.apache.fop.fonts.FontInfo; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFPage; import org.apache.fop.pdf.PDFResourceContext; -import org.apache.fop.pdf.PDFState; -import org.apache.fop.pdf.PDFStream; import org.apache.fop.render.AbstractGenericSVGHandler; import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererContext; @@ -78,10 +76,10 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler 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.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.currentStream = (PDFStream)context.getProperty(PDF_STREAM); pdfi.width = ((Integer)context.getProperty(WIDTH)).intValue(); pdfi.height = ((Integer)context.getProperty(HEIGHT)).intValue(); pdfi.fi = (FontInfo)context.getProperty(PDF_FONT_INFO); @@ -108,13 +106,13 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler /** see OUTPUT_STREAM */ public OutputStream outputStream; /** see PDF_STATE */ - public PDFState pdfState; + //public PDFState pdfState; /** see PDF_PAGE */ public PDFPage pdfPage; /** see PDF_CONTEXT */ public PDFResourceContext pdfContext; /** see PDF_STREAM */ - public PDFStream currentStream; + //public PDFStream currentStream; /** see PDF_WIDTH */ public int width; /** see PDF_HEIGHT */ @@ -216,14 +214,15 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler * Note: To have the svg overlay (under) a text area then use * an fo:block-container */ - pdfInfo.currentStream.add("%SVG setup\n"); - renderer.saveGraphicsState(); - renderer.setColor(Color.black, false, null); - renderer.setColor(Color.black, true, null); + PDFContentGenerator generator = renderer.getGenerator(); + generator.comment("SVG setup"); + generator.saveGraphicsState(); + generator.setColor(Color.black, false); + generator.setColor(Color.black, true); if (!scaling.isIdentity()) { - pdfInfo.currentStream.add("%viewbox\n"); - pdfInfo.currentStream.add(CTMHelper.toPDFString(scaling, false) + " cm\n"); + generator.comment("viewbox"); + generator.add(CTMHelper.toPDFString(scaling, false) + " cm\n"); } //SVGSVGElement svg = ((SVGDocument)doc).getRootElement(); @@ -238,38 +237,38 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); if (!resolutionScaling.isIdentity()) { - pdfInfo.currentStream.add("%resolution scaling for " + uaResolution + generator.comment("resolution scaling for " + uaResolution + " -> " + deviceResolution + "\n"); - pdfInfo.currentStream.add( + generator.add( CTMHelper.toPDFString(resolutionScaling, false) + " cm\n"); graphics.scale(1 / s, 1 / s); } - pdfInfo.currentStream.add("%SVG start\n"); + generator.comment("SVG start"); //Save state and update coordinate system for the SVG image - pdfInfo.pdfState.push(); - pdfInfo.pdfState.concatenate(imageTransform); + generator.getState().push(); + generator.getState().concatenate(imageTransform); //Now that we have the complete transformation matrix for the image, we can update the //transformation matrix for the AElementBridge. PDFAElementBridge aBridge = (PDFAElementBridge)ctx.getBridge( SVGDOMImplementation.SVG_NAMESPACE_URI, SVGConstants.SVG_A_TAG); - aBridge.getCurrentTransform().setTransform(pdfInfo.pdfState.getTransform()); + aBridge.getCurrentTransform().setTransform(generator.getState().getTransform()); - graphics.setPDFState(pdfInfo.pdfState); + graphics.setPDFState(generator.getState()); graphics.setOutputStream(pdfInfo.outputStream); try { root.paint(graphics); - pdfInfo.currentStream.add(graphics.getString()); + generator.add(graphics.getString()); } catch (Exception e) { SVGEventProducer eventProducer = SVGEventProducer.Provider.get( context.getUserAgent().getEventBroadcaster()); eventProducer.svgRenderingError(this, e, getDocumentURI(doc)); } - pdfInfo.pdfState.pop(); - renderer.restoreGraphicsState(); - pdfInfo.currentStream.add("%SVG end\n"); + generator.getState().pop(); + generator.restoreGraphicsState(); + generator.comment("SVG end"); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/ps/PSEventProducer.xml b/src/java/org/apache/fop/render/ps/PSEventProducer.xml index a0078223a..f2fe60497 100644 --- a/src/java/org/apache/fop/render/ps/PSEventProducer.xml +++ b/src/java/org/apache/fop/render/ps/PSEventProducer.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.ps.PSEventProducer.postscriptDictionaryParseError">Failed to parse dictionary string. Reason: {e}, content = "{content}"</message> </catalogue> diff --git a/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml b/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml index 8f1f21a81..e81a7515f 100644 --- a/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml +++ b/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message> <message key="org.apache.fop.render.rtf.RTFEventProducer.onlySPMSupported">Only simple-page-masters are supported on page-sequences. Using default simple-page-master from page-sequence-master "{masterReference}".{{locator}}</message> <message key="org.apache.fop.render.rtf.RTFEventProducer.noSPMFound">No simple-page-master could be determined.</message> diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index ca2245a12..0b2f0a45f 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -440,6 +440,14 @@ public class PDFGraphics2D extends AbstractGraphics2D { } PDFXObject xObject = this.pdfDoc.addImage(resourceContext, pdfImage); + flushPDFDocument(); + + AffineTransform at = new AffineTransform(); + at.translate(x, y); + useXObject(xObject, at, width, height); + } + + private void flushPDFDocument() { if (outputStream != null) { try { this.pdfDoc.output(outputStream); @@ -447,10 +455,6 @@ public class PDFGraphics2D extends AbstractGraphics2D { // ignore exception, will be thrown again later } } - - AffineTransform at = new AffineTransform(); - at.translate(x, y); - useXObject(xObject, at, width, height); } /** @@ -1044,13 +1048,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { this.pdfDoc.addObject(annots); } - if (outputStream != null) { - try { - this.pdfDoc.output(outputStream); - } catch (IOException ioe) { - // ignore exception, will be thrown again later - } - } + flushPDFDocument(); return true; } @@ -1147,26 +1145,14 @@ public class PDFGraphics2D extends AbstractGraphics2D { PDFImageXObject xobj = pdfDoc.addImage(resourceContext, fopimg); maskRef = xobj.referencePDF(); - if (outputStream != null) { - try { - this.pdfDoc.output(outputStream); - } catch (IOException ioe) { - // ignore exception, will be thrown again later - } - } + flushPDFDocument(); } BitmapImage fopimg; fopimg = new BitmapImage("TempImage:" + pctx.toString(), devW, devH, rgb, maskRef); fopimg.setTransparent(new PDFColor(255, 255, 255)); imageInfo = pdfDoc.addImage(resourceContext, fopimg); - if (outputStream != null) { - try { - this.pdfDoc.output(outputStream); - } catch (IOException ioe) { - // ignore exception, will be thrown again later - } - } + flushPDFDocument(); } currentStream.write("q\n"); @@ -1275,13 +1261,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { ImageRendered imgRend = new ImageRendered(info, img, null); ImageRenderedAdapter adapter = new ImageRenderedAdapter(imgRend, key); PDFXObject xObject = pdfDoc.addImage(resourceContext, adapter); - if (outputStream != null) { - try { - this.pdfDoc.output(outputStream); - } catch (IOException ioe) { - // ignore exception, will be thrown again later - } - } + flushPDFDocument(); return xObject; } diff --git a/src/java/org/apache/fop/util/ConversionUtils.java b/src/java/org/apache/fop/util/ConversionUtils.java index 82d06ba32..f445134fc 100644 --- a/src/java/org/apache/fop/util/ConversionUtils.java +++ b/src/java/org/apache/fop/util/ConversionUtils.java @@ -5,7 +5,7 @@ * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - + * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software @@ -14,7 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /* $Id$ */ + package org.apache.fop.util; /** @@ -48,7 +50,7 @@ public final class ConversionUtils { } if (separatorPattern == null || "".equals(separatorPattern)) { - return new int[] { Integer.parseInt(baseString) }; + return new int[] {Integer.parseInt(baseString)}; } String[] values = baseString.split(separatorPattern); @@ -90,7 +92,7 @@ public final class ConversionUtils { } if (separatorPattern == null || "".equals(separatorPattern)) { - return new double[] { Double.parseDouble(baseString) }; + return new double[] {Double.parseDouble(baseString)}; } String[] values = baseString.split(separatorPattern); diff --git a/src/java/org/apache/fop/util/DecimalFormatCache.java b/src/java/org/apache/fop/util/DecimalFormatCache.java new file mode 100644 index 000000000..b4d4de038 --- /dev/null +++ b/src/java/org/apache/fop/util/DecimalFormatCache.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.util; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; + +/** + * This class provides a cache for {@code DecimalFormat} instance. {@code DecimalFormat} itself + * is not thread-safe but since FOP needs to format a lot of numbers the same way, it shall + * be cached in a {@code ThreadLocal}. + */ +public class DecimalFormatCache { + + private static final String BASE_FORMAT = "0.################"; + + private static class DecimalFormatThreadLocal extends ThreadLocal { + + private int dec; + + public DecimalFormatThreadLocal(int dec) { + this.dec = dec; + } + + protected synchronized Object initialValue() { + String s = "0"; + if (dec > 0) { + s = BASE_FORMAT.substring(0, dec + 2); + } + DecimalFormat df = new DecimalFormat(s, new DecimalFormatSymbols(Locale.US)); + return df; + } + }; + + //DecimalFormat is not thread-safe! + private static final ThreadLocal[] DECIMAL_FORMAT_CACHE = new DecimalFormatThreadLocal[17]; + static { + for (int i = 0, c = DECIMAL_FORMAT_CACHE.length; i < c; i++) { + DECIMAL_FORMAT_CACHE[i] = new DecimalFormatThreadLocal(i); + } + } + + /** + * Returns a cached {@code DecimalFormat} instance for the given number of decimal digits. + * @param dec the number of decimal digits. + * @return the DecimalFormat instance + */ + public static DecimalFormat getDecimalFormat(int dec) { + if (dec < 0 || dec >= DECIMAL_FORMAT_CACHE.length) { + throw new IllegalArgumentException("Parameter dec must be between 1 and " + + (DECIMAL_FORMAT_CACHE.length + 1)); + } + return (DecimalFormat)DECIMAL_FORMAT_CACHE[dec].get(); + } + +} diff --git a/src/java/org/apache/fop/util/DelegatingContentHandler.java b/src/java/org/apache/fop/util/DelegatingContentHandler.java index 0db1105ce..ff712a82b 100644 --- a/src/java/org/apache/fop/util/DelegatingContentHandler.java +++ b/src/java/org/apache/fop/util/DelegatingContentHandler.java @@ -38,7 +38,6 @@ import org.xml.sax.ext.LexicalHandler; * <p> * The ContentHandler is the only instance that is required. All others (DTDHandler, * EntityResolver, LexicalHandler and ErrorHandler) may be ignored. - * */ public class DelegatingContentHandler implements EntityResolver, DTDHandler, ContentHandler, LexicalHandler, ErrorHandler { @@ -105,10 +104,9 @@ public class DelegatingContentHandler // ==== EntityResolver - /** - * {@inheritDoc} - */ - public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { + /** {@inheritDoc} */ + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { if (entityResolver != null) { return entityResolver.resolveEntity(publicId, systemId); } else { @@ -118,18 +116,14 @@ public class DelegatingContentHandler // ==== DTDHandler - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void notationDecl(String name, String publicId, String systemId) throws SAXException { if (dtdHandler != null) { dtdHandler.notationDecl(name, publicId, systemId); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { if (dtdHandler != null) { @@ -139,89 +133,65 @@ public class DelegatingContentHandler // ==== ContentHandler - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void setDocumentLocator(Locator locator) { delegate.setDocumentLocator(locator); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void startDocument() throws SAXException { delegate.startDocument(); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endDocument() throws SAXException { delegate.endDocument(); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void startPrefixMapping(String prefix, String uri) throws SAXException { delegate.startPrefixMapping(prefix, uri); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endPrefixMapping(String prefix) throws SAXException { delegate.endPrefixMapping(prefix); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { delegate.startElement(uri, localName, qName, atts); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endElement(String uri, String localName, String qName) throws SAXException { delegate.endElement(uri, localName, qName); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void characters(char[] ch, int start, int length) throws SAXException { delegate.characters(ch, start, length); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { delegate.ignorableWhitespace(ch, start, length); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void processingInstruction(String target, String data) throws SAXException { delegate.processingInstruction(target, data); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void skippedEntity(String name) throws SAXException { delegate.skippedEntity(name); } // ==== LexicalHandler - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void startDTD(String name, String publicId, String systemId) throws SAXException { if (lexicalHandler != null) { lexicalHandler.startDTD(name, publicId, systemId); @@ -229,54 +199,42 @@ public class DelegatingContentHandler } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endDTD() throws SAXException { if (lexicalHandler != null) { lexicalHandler.endDTD(); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void startEntity(String name) throws SAXException { if (lexicalHandler != null) { lexicalHandler.startEntity(name); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endEntity(String name) throws SAXException { if (lexicalHandler != null) { lexicalHandler.endEntity(name); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void startCDATA() throws SAXException { if (lexicalHandler != null) { lexicalHandler.startCDATA(); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endCDATA() throws SAXException { if (lexicalHandler != null) { lexicalHandler.endCDATA(); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void comment(char[] ch, int start, int length) throws SAXException { if (lexicalHandler != null) { lexicalHandler.comment(ch, start, length); @@ -285,27 +243,21 @@ public class DelegatingContentHandler // ==== ErrorHandler - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void warning(SAXParseException exception) throws SAXException { if (errorHandler != null) { errorHandler.warning(exception); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void error(SAXParseException exception) throws SAXException { if (errorHandler != null) { errorHandler.error(exception); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void fatalError(SAXParseException exception) throws SAXException { if (errorHandler != null) { errorHandler.fatalError(exception); diff --git a/src/sandbox/META-INF/services/org.apache.fop.render.ImageHandler b/src/sandbox/META-INF/services/org.apache.fop.render.ImageHandler new file mode 100644 index 000000000..49af6340e --- /dev/null +++ b/src/sandbox/META-INF/services/org.apache.fop.render.ImageHandler @@ -0,0 +1,2 @@ +org.apache.fop.render.svg.SVGDataUrlImageHandler
+org.apache.fop.render.svg.EmbeddedSVGImageHandler
diff --git a/src/sandbox/META-INF/services/org.apache.fop.render.Renderer b/src/sandbox/META-INF/services/org.apache.fop.render.Renderer deleted file mode 100644 index ea7c13f92..000000000 --- a/src/sandbox/META-INF/services/org.apache.fop.render.Renderer +++ /dev/null @@ -1 +0,0 @@ -org.apache.fop.render.svg.SVGRendererMaker
\ No newline at end of file diff --git a/src/sandbox/META-INF/services/org.apache.fop.render.intermediate.IFPainter b/src/sandbox/META-INF/services/org.apache.fop.render.intermediate.IFPainter new file mode 100644 index 000000000..7913529fa --- /dev/null +++ b/src/sandbox/META-INF/services/org.apache.fop.render.intermediate.IFPainter @@ -0,0 +1,2 @@ +org.apache.fop.render.svg.SVGPainterMaker
+org.apache.fop.render.svg.SVGPrintPainterMaker
\ No newline at end of file diff --git a/src/sandbox/org/apache/fop/render/svg/AbstractSVGPainter.java b/src/sandbox/org/apache/fop/render/svg/AbstractSVGPainter.java new file mode 100644 index 000000000..c5d72ad9f --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/AbstractSVGPainter.java @@ -0,0 +1,418 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Map; + +import org.w3c.dom.Document; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.util.MimeConstants; +import org.apache.xmlgraphics.util.QName; +import org.apache.xmlgraphics.xmp.Metadata; + +import org.apache.fop.events.ResourceEventProducer; +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.intermediate.AbstractXMLWritingIFPainter; +import org.apache.fop.render.intermediate.IFConstants; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.IFState; +import org.apache.fop.util.ColorUtil; + +/** + * Abstract base class for SVG Painter implementations. + */ +public abstract class AbstractSVGPainter extends AbstractXMLWritingIFPainter + implements SVGConstants { + + /** logging instance */ + private static Log log = LogFactory.getLog(AbstractSVGPainter.class); + + /** Holds the intermediate format state */ + protected IFState state; + + private static final int MODE_NORMAL = 0; + private static final int MODE_TEXT = 1; + + private int mode = MODE_NORMAL; + + /** {@inheritDoc} */ + protected String getMainNamespace() { + return NAMESPACE; + } + + /** {@inheritDoc} */ + public void startDocumentHeader() throws IFException { + try { + startElement("defs"); + } catch (SAXException e) { + throw new IFException("SAX error in startDocumentHeader()", e); + } + } + + /** {@inheritDoc} */ + public void endDocumentHeader() throws IFException { + try { + endElement("defs"); + } catch (SAXException e) { + throw new IFException("SAX error in startDocumentHeader()", e); + } + } + + /** {@inheritDoc} */ + public void startPageContent() throws IFException { + this.state = IFState.create(); + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + assert this.state.pop() == null; + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) + throws IFException { + startViewport(toString(transform), size, clipRect); + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) + throws IFException { + startViewport(toString(transforms), size, clipRect); + } + + private void startViewport(String transform, Dimension size, Rectangle clipRect) + throws IFException { + try { + establish(MODE_NORMAL); + AttributesImpl atts = new AttributesImpl(); + if (transform != null && transform.length() > 0) { + atts.addAttribute("", "transform", "transform", CDATA, transform); + } + startElement("g", atts); + + atts.clear(); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(size.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(size.height)); + if (clipRect != null) { + int[] v = new int[] { + clipRect.y, + -clipRect.x + size.width - clipRect.width, + -clipRect.y + size.height - clipRect.height, + clipRect.x}; + int sum = 0; + for (int i = 0; i < 4; i++) { + sum += Math.abs(v[i]); + } + if (sum != 0) { + StringBuffer sb = new StringBuffer("rect("); + sb.append(v[0]).append(','); + sb.append(v[1]).append(','); + sb.append(v[2]).append(','); + sb.append(v[3]).append(')'); + atts.addAttribute("", "clip", "clip", CDATA, sb.toString()); + } + atts.addAttribute("", "overflow", "overflow", CDATA, "hidden"); + } else { + atts.addAttribute("", "overflow", "overflow", CDATA, "visible"); + } + startElement("svg", atts); + } catch (SAXException e) { + throw new IFException("SAX error in startBox()", e); + } + } + + /** {@inheritDoc} */ + public void endViewport() throws IFException { + try { + establish(MODE_NORMAL); + endElement("svg"); + endElement("g"); + } catch (SAXException e) { + throw new IFException("SAX error in endBox()", e); + } + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform[] transforms) throws IFException { + startGroup(toString(transforms)); + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform transform) throws IFException { + startGroup(toString(transform)); + } + + private void startGroup(String transform) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + if (transform != null && transform.length() > 0) { + atts.addAttribute("", "transform", "transform", CDATA, transform); + } + startElement("g", atts); + } catch (SAXException e) { + throw new IFException("SAX error in startGroup()", e); + } + } + + /** {@inheritDoc} */ + public void endGroup() throws IFException { + try { + endElement("g"); + } catch (SAXException e) { + throw new IFException("SAX error in endGroup()", e); + } + } + + private static final QName CONVERSION_MODE + = new QName(ExtensionElementMapping.URI, null, "conversion-mode"); + + /** {@inheritDoc} */ + public void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException { + try { + establish(MODE_NORMAL); + + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; + try { + ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); + info = manager.getImageInfo(uri, sessionContext); + + String mime = info.getMimeType(); + String conversionMode = (String)foreignAttributes.get(CONVERSION_MODE); + if ("reference".equals(conversionMode) + && (MimeConstants.MIME_GIF.equals(mime) + || MimeConstants.MIME_JPEG.equals(mime) + || MimeConstants.MIME_PNG.equals(mime) + || MimeConstants.MIME_SVG.equals(mime))) { + //Just reference the image + //TODO Some additional URI rewriting might be necessary + AttributesImpl atts = new AttributesImpl(); + addAttribute(atts, IFConstants.XLINK_HREF, uri); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(rect.x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(rect.y)); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(rect.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(rect.height)); + element("image", atts); + } else { + drawImageUsingImageHandler(info, rect); + } + } catch (ImageException ie) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); + } catch (FileNotFoundException fe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); + } catch (IOException ioe) { + ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( + getUserAgent().getEventBroadcaster()); + eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); + } + } catch (SAXException e) { + throw new IFException("SAX error in drawImage()", e); + } + } + + /** {@inheritDoc} */ + public void drawImage(Document doc, Rectangle rect, Map foreignAttributes) throws IFException { + try { + establish(MODE_NORMAL); + + drawImageUsingDocument(doc, rect); + } catch (SAXException e) { + throw new IFException("SAX error in drawImage()", e); + } + } + + /** {@inheritDoc} */ + protected RenderingContext createRenderingContext() { + SVGRenderingContext svgContext = new SVGRenderingContext( + getUserAgent(), handler); + return svgContext; + } + + /** {@inheritDoc} */ + public void addTarget(String name, int x, int y) throws IFException { + //establish(MODE_NORMAL); + // TODO Auto-generated method stub + + } + + private static String toString(Paint paint) { + //TODO Paint serialization: Fine-tune and extend! + if (paint instanceof Color) { + return ColorUtil.colorToString((Color)paint); + } else { + throw new UnsupportedOperationException("Paint not supported: " + paint); + } + } + + /** {@inheritDoc} */ + public void drawRect(Rectangle rect, Paint fill, Color stroke) throws IFException { + if (fill == null && stroke == null) { + return; + } + try { + establish(MODE_NORMAL); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(rect.x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(rect.y)); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(rect.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(rect.height)); + if (fill != null) { + atts.addAttribute("", "fill", "fill", CDATA, toString(fill)); + } + if (stroke != null) { + atts.addAttribute("", "stroke", "sroke", CDATA, toString(stroke)); + } + element("rect", atts); + } catch (SAXException e) { + throw new IFException("SAX error in drawRect()", e); + } + } + + /** {@inheritDoc} */ + public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException { + try { + establish(MODE_TEXT); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(y)); + if (dx != null) { + atts.addAttribute("", "dx", "dx", CDATA, toString(dx)); + } + if (dy != null) { + atts.addAttribute("", "dy", "dy", CDATA, toString(dy)); + } + startElement("text", atts); + char[] chars = text.toCharArray(); + handler.characters(chars, 0, chars.length); + endElement("text"); + } catch (SAXException e) { + throw new IFException("SAX error in setFont()", e); + } + } + + /** {@inheritDoc} */ + public void setFont(String family, String style, Integer weight, String variant, Integer size, + Color color) throws IFException { + if (family != null) { + state.setFontFamily(family); + } + if (style != null) { + state.setFontStyle(style); + } + if (weight != null) { + state.setFontWeight(weight.intValue()); + } + if (variant != null) { + state.setFontVariant(variant); + } + if (size != null) { + state.setFontSize(size.intValue()); + } + if (color != null) { + state.setTextColor(color); + } + } + + private void leaveTextMode() throws SAXException { + assert this.mode == MODE_TEXT; + endElement("g"); + this.mode = MODE_NORMAL; + } + + private void establish(int newMode) throws SAXException { + switch (newMode) { + case MODE_TEXT: + enterTextMode(); + break; + default: + if (this.mode == MODE_TEXT) { + leaveTextMode(); + } + } + } + + private void enterTextMode() throws SAXException { + if (state.isFontChanged() && this.mode == MODE_TEXT) { + leaveTextMode(); + } + if (this.mode != MODE_TEXT) { + startTextGroup(); + this.mode = MODE_TEXT; + } + } + + private void startTextGroup() throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "font-family", "font-family", + CDATA, state.getFontFamily()); + atts.addAttribute("", "font-style", "font-style", + CDATA, state.getFontStyle()); + atts.addAttribute("", "font-weight", "font-weight", + CDATA, Integer.toString(state.getFontWeight())); + atts.addAttribute("", "font-variant", "font-variant", + CDATA, state.getFontVariant()); + atts.addAttribute("", "font-size", "font-size", + CDATA, Integer.toString(state.getFontSize())); + atts.addAttribute("", "fill", "fill", + CDATA, toString(state.getTextColor())); + startElement("g", atts); + state.resetFontChanged(); + } + + /** {@inheritDoc} */ + public void handleExtensionObject(Object extension) throws IFException { + if (extension instanceof Metadata) { + Metadata meta = (Metadata)extension; + try { + establish(MODE_NORMAL); + startElement("metadata"); + meta.toSAX(this.handler); + endElement("metadata"); + } catch (SAXException e) { + throw new IFException("SAX error while handling extension object", e); + } + } else { + throw new UnsupportedOperationException( + "Don't know how to handle extension object: " + extension); + } + } +} diff --git a/src/sandbox/org/apache/fop/render/svg/EmbeddedSVGImageHandler.java b/src/sandbox/org/apache/fop/render/svg/EmbeddedSVGImageHandler.java new file mode 100644 index 000000000..b20982d54 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/EmbeddedSVGImageHandler.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import java.awt.Rectangle; +import java.io.IOException; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.image.loader.batik.BatikImageFlavors; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.intermediate.DelegatingFragmentContentHandler; + +/** + * Image handler implementation that embeds SVG images in the target SVG file. + */ +public class EmbeddedSVGImageHandler implements ImageHandler, SVGConstants { + + /** logging instance */ + private static Log log = LogFactory.getLog(EmbeddedSVGImageHandler.class); + + /** Constant for the "CDATA" attribute type. */ + private static final String CDATA = "CDATA"; + + /** {@inheritDoc} */ + public int getPriority() { + return 500; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageRawStream.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return new ImageFlavor[] { + BatikImageFlavors.SVG_DOM + }; + } + + private void addAttribute(AttributesImpl atts, QName attribute, String value) { + atts.addAttribute(attribute.getNamespaceURI(), + attribute.getLocalName(), attribute.getQName(), CDATA, value); + } + + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, final Rectangle pos) + throws IOException { + SVGRenderingContext svgContext = (SVGRenderingContext)context; + ImageXMLDOM svg = (ImageXMLDOM)image; + ContentHandler handler = svgContext.getContentHandler(); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(pos.x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(pos.y)); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(pos.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(pos.height)); + try { + //handler.startElement(NAMESPACE, "svg", "svg", atts); + + Document doc = (Document)svg.getDocument(); + Element svgEl = (Element)doc.getDocumentElement(); + if (svgEl.getAttribute("viewBox").length() == 0) { + log.warn("SVG doesn't have a viewBox. The result might not be scaled correctly!"); + } + + TransformerFactory tFactory = TransformerFactory.newInstance(); + Transformer transformer = tFactory.newTransformer(); + DOMSource src = new DOMSource(svg.getDocument()); + SAXResult res = new SAXResult(new DelegatingFragmentContentHandler(handler) { + + private boolean topLevelSVGFound = false; + + private void setAttribute(AttributesImpl atts, String localName, String value) { + int index; + index = atts.getIndex("", localName); + if (index < 0) { + atts.addAttribute("", localName, localName, CDATA, value); + } else { + atts.setAttribute(index, "", localName, localName, CDATA, value); + } + } + + public void startElement(String uri, String localName, String name, Attributes atts) + throws SAXException { + if (!topLevelSVGFound + && SVG_ELEMENT.getNamespaceURI().equals(uri) + && SVG_ELEMENT.getLocalName().equals(localName)) { + topLevelSVGFound = true; + AttributesImpl modAtts = new AttributesImpl(atts); + setAttribute(modAtts, "x", Integer.toString(pos.x)); + setAttribute(modAtts, "y", Integer.toString(pos.y)); + setAttribute(modAtts, "width", Integer.toString(pos.width)); + setAttribute(modAtts, "height", Integer.toString(pos.height)); + super.startElement(uri, localName, name, modAtts); + } else { + super.startElement(uri, localName, name, atts); + } + } + + }); + transformer.transform(src, res); + //handler.endElement(NAMESPACE, "svg", "svg"); + //} catch (SAXException e) { + //throw new IOException(e.getMessage()); + } catch (TransformerException te) { + throw new IOException(te.getMessage()); + } + } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + if (targetContext instanceof SVGRenderingContext) { + if (image == null) { + return true; + } + if (image instanceof ImageXMLDOM) { + ImageXMLDOM svg = (ImageXMLDOM)image; + return NAMESPACE.equals(svg.getRootNamespace()); + } + } + return false; + } + +} diff --git a/src/sandbox/org/apache/fop/render/svg/SVGConstants.java b/src/sandbox/org/apache/fop/render/svg/SVGConstants.java new file mode 100644 index 000000000..54051faf2 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/SVGConstants.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.apps.MimeConstants; + +/** + * Constants for the intermediate format. + */ +public interface SVGConstants { + + /** MIME type for SVG. */ + String MIME_TYPE = MimeConstants.MIME_SVG; + + /** MIME type for SVG Print. */ + String MIME_SVG_PRINT = MimeConstants.MIME_SVG + ";profile=print"; + + /** File extension for SVG. */ + String FILE_EXTENSION_SVG = "svg"; + + /** XML namespace for SVG. */ + String NAMESPACE = "http://www.w3.org/2000/svg"; + + /** XML namespace. */ + String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"; + + /** Namespace prefix for XLink */ + String XLINK_PREFIX = "xlink"; + /** XML namespace for XLink */ + String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"; + + /** the SVG element */ + QName SVG_ELEMENT = new QName(NAMESPACE, null, "svg"); + +} diff --git a/src/sandbox/org/apache/fop/render/svg/SVGDataUrlImageHandler.java b/src/sandbox/org/apache/fop/render/svg/SVGDataUrlImageHandler.java new file mode 100644 index 000000000..4ff565331 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/SVGDataUrlImageHandler.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import java.awt.Rectangle; +import java.io.IOException; +import java.io.InputStream; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.commons.io.IOUtils; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.intermediate.IFConstants; +import org.apache.fop.util.DataURLUtil; + +/** + * Image handler implementation that embeds JPEG bitmaps as RFC 2397 data URLs in the target SVG + * file. + */ +public class SVGDataUrlImageHandler implements ImageHandler, SVGConstants { + + /** Constant for the "CDATA" attribute type. */ + private static final String CDATA = "CDATA"; + + /** {@inheritDoc} */ + public int getPriority() { + return 500; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageRawStream.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return new ImageFlavor[] { + ImageFlavor.RAW_PNG, + ImageFlavor.RAW_JPEG, + }; + } + + private void addAttribute(AttributesImpl atts, QName attribute, String value) { + atts.addAttribute(attribute.getNamespaceURI(), + attribute.getLocalName(), attribute.getQName(), CDATA, value); + } + + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + SVGRenderingContext svgContext = (SVGRenderingContext)context; + ImageRawStream raw = (ImageRawStream)image; + InputStream in = raw.createInputStream(); + try { + ContentHandler handler = svgContext.getContentHandler(); + String url = DataURLUtil.createDataURL(in, raw.getMimeType()); + AttributesImpl atts = new AttributesImpl(); + addAttribute(atts, IFConstants.XLINK_HREF, url); + atts.addAttribute("", "x", "x", CDATA, Integer.toString(pos.x)); + atts.addAttribute("", "y", "y", CDATA, Integer.toString(pos.y)); + atts.addAttribute("", "width", "width", CDATA, Integer.toString(pos.width)); + atts.addAttribute("", "height", "height", CDATA, Integer.toString(pos.height)); + try { + handler.startElement(NAMESPACE, "image", "image", atts); + handler.endElement(NAMESPACE, "image", "image"); + } catch (SAXException e) { + throw new IOException(e.getMessage()); + } + } finally { + IOUtils.closeQuietly(in); + } + } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRawStream) + && targetContext instanceof SVGRenderingContext; + } + +} diff --git a/src/sandbox/org/apache/fop/render/svg/SVGPainter.java b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java new file mode 100644 index 000000000..112144e73 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import java.awt.Dimension; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.commons.io.IOUtils; + +import org.apache.fop.render.bitmap.MultiFileRenderingUtil; +import org.apache.fop.render.intermediate.DelegatingFragmentContentHandler; +import org.apache.fop.render.intermediate.IFException; + +/** + * IFPainter implementation that writes SVG. + */ +public class SVGPainter extends AbstractSVGPainter { + + /** Helper class for generating multiple files */ + private MultiFileRenderingUtil multiFileUtil; + + private StreamResult firstStream; + private StreamResult currentStream; + + private Document reusedParts; + + /** + * Default constructor. + */ + public SVGPainter() { + //nop + } + + /** {@inheritDoc} */ + public boolean supportsPagesOutOfOrder() { + return true; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MIME_TYPE; + } + + /** {@inheritDoc} */ + public void setResult(Result result) throws IFException { + if (result instanceof StreamResult) { + multiFileUtil = new MultiFileRenderingUtil(FILE_EXTENSION_SVG, + getUserAgent().getOutputFile()); + this.firstStream = (StreamResult)result; + } else { + throw new UnsupportedOperationException("Result is not supported: " + result); + } + } + + /** {@inheritDoc} */ + public void startDocument() throws IFException { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setNamespaceAware(true); + builderFactory.setValidating(false); + try { + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + this.reusedParts = builder.newDocument(); + } catch (ParserConfigurationException e) { + throw new IFException("Error while setting up a DOM for SVG generation", e); + } + + try { + TransformerHandler toDOMHandler = tFactory.newTransformerHandler(); + toDOMHandler.setResult(new DOMResult(this.reusedParts)); + this.handler = toDOMHandler; + } catch (TransformerConfigurationException e) { + throw new IFException( + "Error while setting up a TransformerHandler for SVG generation", e); + } + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + } + + /** {@inheritDoc} */ + public void startPageSequence(String id) throws IFException { + //nop + } + + /** {@inheritDoc} */ + public void endPageSequence() throws IFException { + //nop + } + + /** {@inheritDoc} */ + public void startPage(int index, String name, Dimension size) throws IFException { + OutputStream out; + try { + if (index == 0) { + out = null; + } else { + out = this.multiFileUtil.createOutputStream(index); + if (out == null) { + //TODO Convert to event + throw new IFException( + "No filename information available. Stopping after first page.", null); + } + } + } catch (IOException ioe) { + throw new IFException("I/O exception while setting up output file", ioe); + } + if (out == null) { + this.handler = createContentHandler(this.firstStream); + } else { + this.currentStream = new StreamResult(out); + this.handler = createContentHandler(this.currentStream); + } + if (false) { + final ContentHandler originalHandler = this.handler; + this.handler = (ContentHandler)Proxy.newProxyInstance( + ContentHandler.class.getClassLoader(), + new Class[] {ContentHandler.class}, + new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + String methodName = method.getName(); + System.out.println(methodName + ":"); + if (args != null) { + for (int i = 0; i < args.length; i++) { + System.out.println(" " + args[i]); + } + } + return method.invoke(originalHandler, args); + } + }); + } + try { + handler.startDocument(); + handler.startPrefixMapping("", NAMESPACE); + handler.startPrefixMapping(XLINK_PREFIX, XLINK_NAMESPACE); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "version", "version", CDATA, "1.1"); //SVG 1.1 + /* + atts.addAttribute("", "index", "index", CDATA, Integer.toString(index)); + atts.addAttribute("", "name", "name", CDATA, name); + */ + atts.addAttribute("", "width", "width", CDATA, + Float.toString(size.width / 1000f) + "pt"); + atts.addAttribute("", "height", "height", CDATA, + Float.toString(size.height / 1000f) + "pt"); + atts.addAttribute("", "viewBox", "viewBox", CDATA, + "0 0 " + Integer.toString(size.width) + " " + Integer.toString(size.height)); + startElement("svg", atts); + + try { + Transformer transformer = tFactory.newTransformer(); + Source src = new DOMSource(this.reusedParts.getDocumentElement()); + Result res = new SAXResult(new DelegatingFragmentContentHandler(this.handler)); + transformer.transform(src, res); + } catch (TransformerConfigurationException tce) { + throw new IFException("Error setting up a Transformer", tce); + } catch (TransformerException te) { + if (te.getCause() instanceof SAXException) { + throw (SAXException)te.getCause(); + } else { + throw new IFException("Error while serializing reused parts", te); + } + } + } catch (SAXException e) { + throw new IFException("SAX error in startPage()", e); + } + } + + private void closeCurrentStream() { + if (this.currentStream != null) { + IOUtils.closeQuietly(currentStream.getOutputStream()); + currentStream.setOutputStream(null); + IOUtils.closeQuietly(currentStream.getWriter()); + currentStream.setWriter(null); + this.currentStream = null; + } + } + + /** {@inheritDoc} */ + public void startPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void startPageContent() throws IFException { + super.startPageContent(); + try { + startElement("g"); + } catch (SAXException e) { + throw new IFException("SAX error in startPageContent()", e); + } + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + try { + endElement("g"); + } catch (SAXException e) { + throw new IFException("SAX error in endPageContent()", e); + } + super.endPageContent(); + } + + /** {@inheritDoc} */ + public void startPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPage() throws IFException { + try { + endElement("svg"); + this.handler.endDocument(); + } catch (SAXException e) { + throw new IFException("SAX error in endPage()", e); + } + closeCurrentStream(); + } + +} diff --git a/src/sandbox/org/apache/fop/render/svg/SVGPainterMaker.java b/src/sandbox/org/apache/fop/render/svg/SVGPainterMaker.java new file mode 100644 index 000000000..a25a2f3b7 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/SVGPainterMaker.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.render.intermediate.AbstractIFPainterMaker; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; + +/** + * Painter factory for SVG output. + */ +public class SVGPainterMaker extends AbstractIFPainterMaker { + + private static final String[] MIMES = new String[] {SVGConstants.MIME_TYPE}; + + /** {@inheritDoc} */ + public IFPainter makePainter(FOUserAgent ua) { + return new SVGPainter(); + } + + /** {@inheritDoc} */ + public boolean needsOutputStream() { + return true; + } + + /** {@inheritDoc} */ + public String[] getSupportedMimeTypes() { + return MIMES; + } + + /** {@inheritDoc} */ + public IFPainterConfigurator getConfigurator(FOUserAgent userAgent) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/sandbox/org/apache/fop/render/svg/SVGPrintPainter.java b/src/sandbox/org/apache/fop/render/svg/SVGPrintPainter.java new file mode 100644 index 000000000..7f5c84cef --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/SVGPrintPainter.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import java.awt.Dimension; + +import javax.xml.transform.Result; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.apache.fop.render.intermediate.IFConstants; +import org.apache.fop.render.intermediate.IFException; + +/** + * IFPainter implementation that writes SVG Print. + */ +public class SVGPrintPainter extends AbstractSVGPainter { + + /** + * Default constructor. + */ + public SVGPrintPainter() { + //nop + } + + /** + * Creates a new SVGPrintPainter that sends the XML content it generates to the given + * SAX ContentHandler. + * @param result the JAXP Result object to receive the generated content + * @throws IFException if an error occurs setting up the output + */ + public SVGPrintPainter(Result result) throws IFException { + setResult(result); + } + + /** {@inheritDoc} */ + public boolean supportsPagesOutOfOrder() { + return false; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MIME_SVG_PRINT; + } + + /** {@inheritDoc} */ + public void startDocument() throws IFException { + try { + handler.startDocument(); + handler.startPrefixMapping("", NAMESPACE); + handler.startPrefixMapping(XLINK_PREFIX, XLINK_NAMESPACE); + handler.startPrefixMapping("if", IFConstants.NAMESPACE); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "version", "version", CDATA, "1.2"); //SVG Print is SVG 1.2 + startElement("svg", atts); + } catch (SAXException e) { + throw new IFException("SAX error in startDocument()", e); + } + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + try { + endElement("svg"); + handler.endDocument(); + } catch (SAXException e) { + throw new IFException("SAX error in endDocument()", e); + } + } + + /** {@inheritDoc} */ + public void startPageSequence(String id) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + if (id != null) { + atts.addAttribute(XML_NAMESPACE, "id", "xml:id", CDATA, id); + } + startElement("pageSet", atts); + } catch (SAXException e) { + throw new IFException("SAX error in startPageSequence()", e); + } + } + + /** {@inheritDoc} */ + public void endPageSequence() throws IFException { + try { + endElement("pageSet"); + } catch (SAXException e) { + throw new IFException("SAX error in endPageSequence()", e); + } + } + + /** {@inheritDoc} */ + public void startPage(int index, String name, Dimension size) throws IFException { + try { + AttributesImpl atts = new AttributesImpl(); + /* + atts.addAttribute("", "index", "index", CDATA, Integer.toString(index)); + atts.addAttribute("", "name", "name", CDATA, name); + */ + //NOTE: SVG Print doesn't support individual page sizes for each page + atts.addAttribute(IFConstants.NAMESPACE, "width", "if:width", + CDATA, Integer.toString(size.width)); + atts.addAttribute(IFConstants.NAMESPACE, "height", "if:height", + CDATA, Integer.toString(size.height)); + atts.addAttribute(IFConstants.NAMESPACE, "viewBox", "if:viewBox", CDATA, + "0 0 " + Integer.toString(size.width) + " " + Integer.toString(size.height)); + startElement("page", atts); + } catch (SAXException e) { + throw new IFException("SAX error in startPage()", e); + } + } + + /** {@inheritDoc} */ + public void startPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void startPageContent() throws IFException { + super.startPageContent(); + try { + startElement("g"); + } catch (SAXException e) { + throw new IFException("SAX error in startPageContent()", e); + } + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + try { + endElement("g"); + } catch (SAXException e) { + throw new IFException("SAX error in endPageContent()", e); + } + super.endPageContent(); + } + + /** {@inheritDoc} */ + public void startPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPageTrailer() throws IFException { + } + + /** {@inheritDoc} */ + public void endPage() throws IFException { + try { + endElement("page"); + } catch (SAXException e) { + throw new IFException("SAX error in endPage()", e); + } + } + +} diff --git a/src/sandbox/org/apache/fop/render/svg/SVGPrintPainterMaker.java b/src/sandbox/org/apache/fop/render/svg/SVGPrintPainterMaker.java new file mode 100644 index 000000000..55a7bf753 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/SVGPrintPainterMaker.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.render.intermediate.AbstractIFPainterMaker; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFPainterConfigurator; + +/** + * Painter factory for SVG Print output. + */ +public class SVGPrintPainterMaker extends AbstractIFPainterMaker { + + private static final String[] MIMES = new String[] {SVGConstants.MIME_SVG_PRINT}; + + /** {@inheritDoc} */ + public IFPainter makePainter(FOUserAgent ua) { + return new SVGPrintPainter(); + } + + /** {@inheritDoc} */ + public boolean needsOutputStream() { + return true; + } + + /** {@inheritDoc} */ + public String[] getSupportedMimeTypes() { + return MIMES; + } + + /** {@inheritDoc} */ + public IFPainterConfigurator getConfigurator(FOUserAgent userAgent) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/sandbox/org/apache/fop/render/svg/SVGRenderingContext.java b/src/sandbox/org/apache/fop/render/svg/SVGRenderingContext.java new file mode 100644 index 000000000..5e64af677 --- /dev/null +++ b/src/sandbox/org/apache/fop/render/svg/SVGRenderingContext.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.svg; + +import org.xml.sax.ContentHandler; + +import org.apache.xmlgraphics.util.MimeConstants; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.render.AbstractRenderingContext; + +/** + * Rendering context for SVG production. + */ +public class SVGRenderingContext extends AbstractRenderingContext { + + private ContentHandler handler; + + /** + * Main constructor. + * @param userAgent the user agent + * @param handler the target content handler + */ + public SVGRenderingContext(FOUserAgent userAgent, ContentHandler handler) { + super(userAgent); + this.handler = handler; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MimeConstants.MIME_SVG; + } + + /** + * Returns the target content handler. + * @return the content handler + */ + public ContentHandler getContentHandler() { + return this.handler; + } + +} diff --git a/test/java/org/apache/fop/intermediate/AbstractIntermediateTestCase.java b/test/java/org/apache/fop/intermediate/AbstractIntermediateTestCase.java new file mode 100644 index 000000000..63e293afe --- /dev/null +++ b/test/java/org/apache/fop/intermediate/AbstractIntermediateTestCase.java @@ -0,0 +1,260 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.intermediate; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; + +import javax.xml.transform.ErrorListener; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.custommonkey.xmlunit.XMLTestCase; +import org.w3c.dom.Document; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.ByteArrayOutputStream; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.FopFactory; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.util.ConsoleEventListenerForTests; + +/** + * Abstract base class for intermediate format tests. + */ +public abstract class AbstractIntermediateTestCase extends XMLTestCase { + + /** the FOP factory */ + protected static FopFactory fopFactory = FopFactory.newInstance(); + + /** the JAXP transformer factory */ + protected static SAXTransformerFactory tFactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + /** the main base directory for tests */ + protected File mainDir = new File("test/layoutengine"); + /** the directory containing the tests */ + protected File testDir = new File(mainDir, "standard-testcases"); + /** the output directory for any files generated by the tests */ + protected File outputDir; + + private static Templates stylesheet = null; + + /** the test file */ + protected File testFile; + /** the intermediate format document as DOM */ + protected Document intermediate; + + /** + * Constructor for the test suite that is used for each test file. + * @param testFile the test file to run + */ + public AbstractIntermediateTestCase(File testFile) { + super(testFile.getName()); + this.testFile = testFile; + } + + /** {@inheritDoc} */ + protected void setUp() throws Exception { + super.setUp(); + setupOutputDirectory(); + intermediate = buildIntermediateDocument( + new StreamSource(testFile), getStylesheet()); + if (outputDir != null) { + saveDOM(intermediate, new File(outputDir, + getName() + ".1" + getIntermediateFileExtension())); + } + } + + /** {@inheritDoc} */ + protected void tearDown() throws Exception { + this.intermediate = null; //Release memory + super.tearDown(); + } + + /** + * Returns the file extension for the intermediate file format. + * @return the file extension + */ + protected abstract String getIntermediateFileExtension(); + + /** + * Returns the MIME type for which to test or to mimic for the intermediate format. + * @return the MIME type + */ + protected String getTargetMIME() { + return MimeConstants.MIME_PDF; + } + + /** + * Builds an intermediate format document from a source file. + * @param source the source file + * @param templates the (optional) stylesheet + * @return the intermediate format document as a DOM + * @throws Exception if an error occurs while processing the document + */ + protected abstract Document buildIntermediateDocument( + Source source, Templates templates) throws Exception; + + /** + * Returns the stylesheet that transforms layout engine test cases into normal FO files. + * @return the stylesheet + * @throws TransformerConfigurationException if the stylesheet cannot be instantiated + */ + protected Templates getStylesheet() throws TransformerConfigurationException { + if (stylesheet == null) { + File xsltFile = new File(mainDir, "testcase2fo.xsl"); + stylesheet = tFactory.newTemplates(new StreamSource(xsltFile)); + } + return stylesheet; + } + + /** + * Saves a DOM to a file. + * @param doc the DOM Document + * @param tgtFile the target file + * @throws Exception if an error occurs while serializing the DOM + */ + protected void saveDOM(Document doc, File tgtFile) throws Exception { + Transformer transformer = tFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + Source src = new DOMSource(doc); + Result res = new StreamResult(tgtFile); + transformer.transform(src, res); + } + + /** + * Creates a new FOP user agent. + * @return the user agent + */ + protected FOUserAgent createUserAgent() { + FOUserAgent userAgent = fopFactory.newFOUserAgent(); + try { + userAgent.setBaseURL(testDir.toURI().toURL().toExternalForm()); + userAgent.getEventBroadcaster().addEventListener( + new ConsoleEventListenerForTests(testFile.getName())); + } catch (MalformedURLException e) { + //ignore, won't happen + } + return userAgent; + } + + /** + * Sets up the output directory. + */ + protected void setupOutputDirectory() { + String s = System.getProperty("fop.intermediate.outdir"); + if (s != null && s.length() > 0) { + outputDir = new File(s); + outputDir.mkdirs(); + } + } + + /** + * Tests the area tree parser by running the parsed area tree again through the area tree + * renderer. The source and result documents are compared to each other. + * @throws Exception if the test fails + */ + public void testParserToIntermediateFormat() throws Exception { + Source src = new DOMSource(intermediate); + Document doc = parseAndRenderToIntermediateFormat(src); + if (outputDir != null) { + File tgtFile = new File(outputDir, getName() + ".2" + getIntermediateFileExtension()); + saveDOM(doc, tgtFile); + } + + assertXMLEqual(intermediate, doc); + } + + /** + * Parses the intermediate file and renders it back to the intermediate format. + * @param src the source for the intermediate file + * @return a DOM Document with the re-created intermediate file + * @throws Exception if an error occurs while processing the document + */ + protected abstract Document parseAndRenderToIntermediateFormat(Source src) throws Exception; + + /** + * Tests the area tree parser by sending the parsed area tree to the PDF Renderer. Some + * errors might be caught by the PDFRenderer. + * @throws Exception if the test fails + */ + public void testParserToPDF() throws Exception { + OutputStream out; + if (outputDir != null) { + File tgtFile = new File(outputDir, getName() + ".pdf"); + out = new FileOutputStream(tgtFile); + out = new BufferedOutputStream(out); + } else { + out = new ByteArrayOutputStream(); + } + try { + Source src = new DOMSource(intermediate); + parseAndRender(src, out); + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Parses and renders an intermediate format document to a final format. + * @param src the source document + * @param out the target output stream + * @throws Exception if an error occurs while rendering the document + */ + protected abstract void parseAndRender(Source src, OutputStream out) + throws Exception; + + /** + * Sets an error listener which doesn't swallow errors like Xalan's default one. + * @param transformer the transformer to set the error listener on + */ + protected void setErrorListener(Transformer transformer) { + transformer.setErrorListener(new ErrorListener() { + + public void error(TransformerException exception) throws TransformerException { + throw exception; + } + + public void fatalError(TransformerException exception) throws TransformerException { + throw exception; + } + + public void warning(TransformerException exception) throws TransformerException { + //ignore + } + + }); + } + +} diff --git a/test/java/org/apache/fop/intermediate/AreaTreeParserTestCase.java b/test/java/org/apache/fop/intermediate/AreaTreeParserTestCase.java index 76cca1569..fb394b099 100644 --- a/test/java/org/apache/fop/intermediate/AreaTreeParserTestCase.java +++ b/test/java/org/apache/fop/intermediate/AreaTreeParserTestCase.java @@ -19,30 +19,21 @@ package org.apache.fop.intermediate; -import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.OutputStream; -import java.net.MalformedURLException; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.dom.DOMResult; -import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; -import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.output.ByteArrayOutputStream; +import org.w3c.dom.Document; + import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.Fop; -import org.apache.fop.apps.FopFactory; import org.apache.fop.apps.MimeConstants; import org.apache.fop.area.AreaTreeModel; import org.apache.fop.area.AreaTreeParser; @@ -50,142 +41,35 @@ import org.apache.fop.area.RenderPagesModel; import org.apache.fop.fonts.FontInfo; import org.apache.fop.render.Renderer; import org.apache.fop.render.xml.XMLRenderer; -import org.apache.fop.util.ConsoleEventListenerForTests; - -//XML Unit 1.0: See http://xmlunit.sourceforge.net (BSD-style License) -import org.custommonkey.xmlunit.XMLTestCase; -import org.w3c.dom.Document; /** * Tests the area tree parser. */ -public class AreaTreeParserTestCase extends XMLTestCase { - - // configure fopFactory as desired - private static FopFactory fopFactory = FopFactory.newInstance(); - - private static SAXTransformerFactory tFactory - = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); - private static Templates stylesheet = null; - - private File mainDir = new File("test/layoutengine"); - private File testDir = new File(mainDir, "standard-testcases"); - - private String name; - private File testFile; - - private File outputDir; - private Document intermediate; - - /** @see junit.framework.TestCase#TestCase(String) */ - public AreaTreeParserTestCase(String name) { - super(name); - } +public class AreaTreeParserTestCase extends AbstractIntermediateTestCase { /** * Constructor for the test suite that is used for each test file. * @param testFile the test file to run */ public AreaTreeParserTestCase(File testFile) { - super(testFile.getName()); - this.testFile = testFile; - } - - private Templates getStylesheet() throws TransformerConfigurationException { - if (stylesheet == null) { - File xsltFile = new File(mainDir, "testcase2fo.xsl"); - stylesheet = tFactory.newTemplates(new StreamSource(xsltFile)); - } - return stylesheet; - } - - /** @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { - super.setUp(); - String s = System.getProperty("fop.intermediate.outdir"); - if (s != null && s.length() > 0) { - outputDir = new File(s); - outputDir.mkdirs(); - } - File srcFile; - if (testFile != null) { - srcFile = testFile; - } else { - srcFile = new File(testDir, "block_font-style.xml"); - } - this.name = srcFile.getName(); - intermediate = buildAreaTreeXML(new StreamSource(srcFile), getStylesheet()); - if (outputDir != null) { - saveDOM(intermediate, new File(outputDir, name + ".at1.xml")); - } + super(testFile); } - - /** - * Tests the area tree parser by running the parsed area tree again through the area tree - * renderer. The source and result documents are compared to each other. - * @throws Exception if the test fails - */ - public void testParserToAT() throws Exception { - - Source src = new DOMSource(intermediate); - Document doc = parseAndRenderToAreaTree(src); - if (outputDir != null) { - File tgtFile = new File(outputDir, name + ".at2.xml"); - saveDOM(doc, tgtFile); - } - - assertXMLEqual(intermediate, doc); - } - - private void saveDOM(Document doc, File tgtFile) throws Exception { - Transformer transformer = tFactory.newTransformer(); - Source src = new DOMSource(doc); - Result res = new StreamResult(tgtFile); - transformer.transform(src, res); - } - - /** - * Tests the area tree parser by sending the parsed area tree to the PDF Renderer. Some - * errors might be caught by the PDFRenderer. - * @throws Exception if the test fails - */ - public void testParserToPDF() throws Exception { - OutputStream out; - if (outputDir != null) { - File tgtFile = new File(outputDir, name + ".pdf"); - out = new FileOutputStream(tgtFile); - out = new BufferedOutputStream(out); - } else { - out = new ByteArrayOutputStream(); - } - try { - Source src = new DOMSource(intermediate); - parseAndRender(src, out, MimeConstants.MIME_PDF); - } finally { - IOUtils.closeQuietly(out); - } - } - - private FOUserAgent createUserAgent() { - FOUserAgent userAgent = fopFactory.newFOUserAgent(); - try { - userAgent.setBaseURL(testDir.toURL().toExternalForm()); - userAgent.getEventBroadcaster().addEventListener( - new ConsoleEventListenerForTests(testFile.getName())); - } catch (MalformedURLException e) { - //ignore, won't happen - } - return userAgent; + /** {@inheritDoc} */ + protected String getIntermediateFileExtension() { + return ".at.xml"; } - private Document buildAreaTreeXML(Source src, Templates stylesheet) throws Exception { + /** {@inheritDoc} */ + protected Document buildIntermediateDocument(Source src, Templates templates) + throws Exception { Transformer transformer; - if (stylesheet != null) { - transformer = stylesheet.newTransformer(); + if (templates != null) { + transformer = templates.newTransformer(); } else { transformer = tFactory.newTransformer(); } + setErrorListener(transformer); //Set up XMLRenderer to render to a DOM TransformerHandler handler = tFactory.newTransformerHandler(); @@ -196,7 +80,7 @@ public class AreaTreeParserTestCase extends XMLTestCase { //Create an instance of the target renderer so the XMLRenderer can use its font setup Renderer targetRenderer = userAgent.getRendererFactory().createRenderer( - userAgent, MimeConstants.MIME_PDF); + userAgent, getTargetMIME()); XMLRenderer renderer = new XMLRenderer(); renderer.mimicRenderer(targetRenderer); @@ -212,18 +96,20 @@ public class AreaTreeParserTestCase extends XMLTestCase { return (Document)domResult.getNode(); } - private void parseAndRender(Source src, OutputStream out, String mime) throws Exception { + /** {@inheritDoc} */ + protected void parseAndRender(Source src, OutputStream out) throws Exception { AreaTreeParser parser = new AreaTreeParser(); FOUserAgent userAgent = createUserAgent(); FontInfo fontInfo = new FontInfo(); AreaTreeModel treeModel = new RenderPagesModel(userAgent, - mime, fontInfo, out); + getTargetMIME(), fontInfo, out); parser.parse(src, treeModel, userAgent); treeModel.endDocument(); } - private Document parseAndRenderToAreaTree(Source src) throws Exception { + /** {@inheritDoc} */ + protected Document parseAndRenderToIntermediateFormat(Source src) throws Exception { AreaTreeParser parser = new AreaTreeParser(); //Set up XMLRenderer to render to a DOM diff --git a/test/java/org/apache/fop/intermediate/AreaTreeXMLFormatTestSuite.java b/test/java/org/apache/fop/intermediate/AreaTreeXMLFormatTestSuite.java new file mode 100644 index 000000000..2c7bd09e4 --- /dev/null +++ b/test/java/org/apache/fop/intermediate/AreaTreeXMLFormatTestSuite.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.intermediate; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.fop.layoutengine.LayoutEngineTestSuite; + +/** + * JUnit test suite for the area tree XML format + */ +public class AreaTreeXMLFormatTestSuite { + + /** + * @return the test suite with all the tests (one for each XML file) + * @throws IOException in case of an I/O problem + */ + public static Test suite() throws IOException { + TestSuite suite = new TestSuite(); + + Collection files = LayoutEngineTestSuite.getTestFiles(); + + Iterator i = files.iterator(); + while (i.hasNext()) { + File f = (File)i.next(); + addATTestCase(suite, f); + } + + return suite; + } + + private static void addATTestCase(TestSuite suite, + final File f) { + suite.addTest(new AreaTreeParserTestCase(f) { + public void runTest() throws Exception { + try { + testParserToIntermediateFormat(); + testParserToPDF(); + } catch (Exception e) { + org.apache.commons.logging.LogFactory.getLog( + this.getClass()).error("Error on " + f.getName()); + throw e; + } + } + }); + } + +} diff --git a/test/java/org/apache/fop/intermediate/IFParserTestCase.java b/test/java/org/apache/fop/intermediate/IFParserTestCase.java new file mode 100644 index 000000000..4e5500298 --- /dev/null +++ b/test/java/org/apache/fop/intermediate/IFParserTestCase.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.intermediate; + +import java.io.File; +import java.io.OutputStream; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.Fop; +import org.apache.fop.render.Renderer; +import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.render.intermediate.IFParser; +import org.apache.fop.render.intermediate.IFRenderer; +import org.apache.fop.render.intermediate.IFSerializer; + +/** + * Tests the intermediate format parser. + */ +public class IFParserTestCase extends AbstractIntermediateTestCase { + + /** + * Constructor for the test suite that is used for each test file. + * @param testFile the test file to run + */ + public IFParserTestCase(File testFile) { + super(testFile); + } + + /** {@inheritDoc} */ + protected String getIntermediateFileExtension() { + return ".if.xml"; + } + + /** {@inheritDoc} */ + protected Document buildIntermediateDocument(Source src, Templates templates) + throws Exception { + Transformer transformer; + if (templates != null) { + transformer = templates.newTransformer(); + } else { + transformer = tFactory.newTransformer(); + } + + setErrorListener(transformer); + + //Set up XMLRenderer to render to a DOM + DOMResult domResult = new DOMResult(); + + FOUserAgent userAgent = createUserAgent(); + + //Create an instance of the target renderer so the XMLRenderer can use its font setup + Renderer targetRenderer = userAgent.getRendererFactory().createRenderer( + userAgent, getTargetMIME()); + + //Setup renderer + IFRenderer renderer = new IFRenderer(); + renderer.setUserAgent(userAgent); + renderer.mimicRenderer(targetRenderer); + + //Setup painter + IFSerializer serializer = new IFSerializer(); + serializer.setUserAgent(userAgent); + serializer.setResult(domResult); + + renderer.setPainter(serializer); + userAgent.setRendererOverride(renderer); + + Fop fop = fopFactory.newFop(userAgent); + Result res = new SAXResult(fop.getDefaultHandler()); + transformer.transform(src, res); + + return (Document)domResult.getNode(); + } + + /** {@inheritDoc} */ + protected void parseAndRender(Source src, OutputStream out) throws Exception { + IFParser parser = new IFParser(); + + FOUserAgent userAgent = createUserAgent(); + + IFPainter painter = userAgent.getRendererFactory().createPainter( + userAgent, getTargetMIME()); + painter.setResult(new StreamResult(out)); + painter.setDefaultFontInfo(); + parser.parse(src, painter, userAgent); + } + + /** {@inheritDoc} */ + protected Document parseAndRenderToIntermediateFormat(Source src) throws Exception { + IFParser parser = new IFParser(); + + FOUserAgent userAgent = createUserAgent(); + + IFSerializer serializer = new IFSerializer(); + serializer.setUserAgent(userAgent); + DOMResult domResult = new DOMResult(); + serializer.setResult(domResult); + + parser.parse(src, serializer, userAgent); + + return (Document)domResult.getNode(); + } + +} diff --git a/test/java/org/apache/fop/intermediate/IntermediateFormatTestSuite.java b/test/java/org/apache/fop/intermediate/IntermediateFormatTestSuite.java index 5960f746c..f8511cadc 100644 --- a/test/java/org/apache/fop/intermediate/IntermediateFormatTestSuite.java +++ b/test/java/org/apache/fop/intermediate/IntermediateFormatTestSuite.java @@ -46,18 +46,18 @@ public class IntermediateFormatTestSuite { Iterator i = files.iterator(); while (i.hasNext()) { File f = (File)i.next(); - addTestCase(suite, f); + addIFTestCase(suite, f); } return suite; } - private static void addTestCase(TestSuite suite, + private static void addIFTestCase(TestSuite suite, final File f) { - suite.addTest(new AreaTreeParserTestCase(f) { + suite.addTest(new IFParserTestCase(f) { public void runTest() throws Exception { try { - testParserToAT(); + testParserToIntermediateFormat(); testParserToPDF(); } catch (Exception e) { org.apache.commons.logging.LogFactory.getLog( |