aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.xml60
-rw-r--r--examples/plan/src/org/apache/fop/plan/PreloaderPlan.java11
-rw-r--r--examples/plan/src/org/apache/fop/plan/SimplePlanDrawer.java15
-rw-r--r--lib/xmlgraphics-commons-1.4svn.jarbin518201 -> 525860 bytes
-rw-r--r--src/java/META-INF/services/org.apache.fop.render.ImageHandler5
-rw-r--r--src/java/META-INF/services/org.apache.fop.render.Renderer1
-rw-r--r--src/java/META-INF/services/org.apache.fop.render.intermediate.IFPainter1
-rw-r--r--src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter1
-rw-r--r--src/java/org/apache/fop/apps/MimeConstants.java3
-rw-r--r--src/java/org/apache/fop/cli/CommandLineOptions.java31
-rw-r--r--src/java/org/apache/fop/events/EventFormatter.xml7
-rw-r--r--src/java/org/apache/fop/image/loader/batik/BatikImageFlavors.java36
-rw-r--r--src/java/org/apache/fop/image/loader/batik/ImageConverterG2D2SVG.java94
-rw-r--r--src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java2
-rw-r--r--src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java2
-rw-r--r--src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java2
-rw-r--r--src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java3
-rw-r--r--src/java/org/apache/fop/pdf/PDFNumber.java40
-rw-r--r--src/java/org/apache/fop/pdf/PDFTextUtil.java10
-rw-r--r--src/java/org/apache/fop/render/AbstractImageHandlerGraphics2D.java141
-rw-r--r--src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java3
-rw-r--r--src/java/org/apache/fop/render/AbstractRenderer.java6
-rw-r--r--src/java/org/apache/fop/render/AbstractRendererConfigurator.java3
-rw-r--r--src/java/org/apache/fop/render/AbstractRenderingContext.java49
-rw-r--r--src/java/org/apache/fop/render/Graphics2DImagePainter.java2
-rw-r--r--src/java/org/apache/fop/render/ImageAdapter.java8
-rw-r--r--src/java/org/apache/fop/render/ImageHandler.java78
-rw-r--r--src/java/org/apache/fop/render/ImageHandlerRegistry.java177
-rw-r--r--src/java/org/apache/fop/render/PrintRendererConfigurator.java15
-rw-r--r--src/java/org/apache/fop/render/RendererContext.java3
-rw-r--r--src/java/org/apache/fop/render/RendererFactory.java183
-rw-r--r--src/java/org/apache/fop/render/RenderingContext.java43
-rw-r--r--src/java/org/apache/fop/render/afp/AFPEventProducer.xml3
-rw-r--r--src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml3
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFPainter.java120
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java211
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractIFPainterMaker.java69
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFPainter.java268
-rw-r--r--src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java151
-rw-r--r--src/java/org/apache/fop/render/intermediate/DelegatingFragmentContentHandler.java68
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFConstants.java57
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFContentHandler.java89
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFException.java46
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFGraphicContext.java156
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFPainter.java303
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFPainterConfigurator.java42
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFParser.java607
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFRenderer.java805
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFRendererMaker.java56
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFSerializer.java430
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFState.java188
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLEventProducer.xml3
-rw-r--r--src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java12
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java52
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFContentGenerator.java331
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFEventProducer.xml3
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java19
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java77
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java31
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java30
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java30
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java200
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFPainter.java504
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFPainterMaker.java55
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderer.java685
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java176
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderingContext.java82
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java410
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFSVGHandler.java45
-rw-r--r--src/java/org/apache/fop/render/ps/PSEventProducer.xml3
-rw-r--r--src/java/org/apache/fop/render/rtf/RTFEventProducer.xml3
-rw-r--r--src/java/org/apache/fop/svg/PDFGraphics2D.java44
-rw-r--r--src/java/org/apache/fop/util/ConversionUtils.java8
-rw-r--r--src/java/org/apache/fop/util/DecimalFormatCache.java74
-rw-r--r--src/java/org/apache/fop/util/DelegatingContentHandler.java100
-rw-r--r--src/sandbox/META-INF/services/org.apache.fop.render.ImageHandler2
-rw-r--r--src/sandbox/META-INF/services/org.apache.fop.render.Renderer1
-rw-r--r--src/sandbox/META-INF/services/org.apache.fop.render.intermediate.IFPainter2
-rw-r--r--src/sandbox/org/apache/fop/render/svg/AbstractSVGPainter.java418
-rw-r--r--src/sandbox/org/apache/fop/render/svg/EmbeddedSVGImageHandler.java164
-rw-r--r--src/sandbox/org/apache/fop/render/svg/SVGConstants.java54
-rw-r--r--src/sandbox/org/apache/fop/render/svg/SVGDataUrlImageHandler.java106
-rw-r--r--src/sandbox/org/apache/fop/render/svg/SVGPainter.java268
-rw-r--r--src/sandbox/org/apache/fop/render/svg/SVGPainterMaker.java55
-rw-r--r--src/sandbox/org/apache/fop/render/svg/SVGPrintPainter.java177
-rw-r--r--src/sandbox/org/apache/fop/render/svg/SVGPrintPainterMaker.java55
-rw-r--r--src/sandbox/org/apache/fop/render/svg/SVGRenderingContext.java59
-rw-r--r--test/java/org/apache/fop/intermediate/AbstractIntermediateTestCase.java260
-rw-r--r--test/java/org/apache/fop/intermediate/AreaTreeParserTestCase.java152
-rw-r--r--test/java/org/apache/fop/intermediate/AreaTreeXMLFormatTestSuite.java71
-rw-r--r--test/java/org/apache/fop/intermediate/IFParserTestCase.java131
-rw-r--r--test/java/org/apache/fop/intermediate/IntermediateFormatTestSuite.java8
92 files changed, 8677 insertions, 990 deletions
diff --git a/build.xml b/build.xml
index a328e95df..4555438a9 100644
--- a/build.xml
+++ b/build.xml
@@ -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
index 6c836d46c..1b7e1d7da 100644
--- a/lib/xmlgraphics-commons-1.4svn.jar
+++ b/lib/xmlgraphics-commons-1.4svn.jar
Binary files differ
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(