aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/render
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org/apache/fop/render')
-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.java33
-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/PrintRenderer.java9
-rw-r--r--src/java/org/apache/fop/render/PrintRendererConfigurator.java21
-rw-r--r--src/java/org/apache/fop/render/Renderer.java3
-rw-r--r--src/java/org/apache/fop/render/RendererContext.java3
-rw-r--r--src/java/org/apache/fop/render/RendererFactory.java178
-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/bitmap/BitmapRenderingSettings.java109
-rw-r--r--src/java/org/apache/fop/render/bitmap/TIFFConstants.java49
-rw-r--r--src/java/org/apache/fop/render/bitmap/TIFFDocumentHandler.java272
-rw-r--r--src/java/org/apache/fop/render/bitmap/TIFFDocumentHandlerMaker.java58
-rw-r--r--src/java/org/apache/fop/render/bitmap/TIFFRenderer.java15
-rw-r--r--src/java/org/apache/fop/render/bitmap/TIFFRendererConfigurator.java134
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFDocumentHandler.java117
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java99
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandlerMaker.java69
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java207
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFDocumentHandler.java86
-rw-r--r--src/java/org/apache/fop/render/intermediate/AffineTransformArrayParser.java151
-rw-r--r--src/java/org/apache/fop/render/intermediate/BorderPainter.java219
-rw-r--r--src/java/org/apache/fop/render/intermediate/DelegatingFragmentContentHandler.java68
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFConstants.java53
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFContentHandler.java89
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java254
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFDocumentHandlerConfigurator.java45
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFException.java46
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFGraphicContext.java162
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFPainter.java190
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFParser.java614
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFRenderer.java1104
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFRendererMaker.java56
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFSerializer.java555
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFSerializerConfiguration.java80
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFState.java188
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFUtil.java140
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java29
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java133
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/BookmarkTree.java76
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java47
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java173
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java85
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java85
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/URIAction.java62
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java313
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java20
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java10
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DImageHandlerGraphics2D.java85
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DImageHandlerRenderedImage.java122
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DPainter.java271
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DRenderer.java9
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DRenderingContext.java61
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DRenderingSettings.java58
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DUtil.java59
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLConstants.java37
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java252
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java58
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLEventProducer.xml3
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLGenerator.java16
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLPainter.java601
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLRenderer.java21
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java147
-rw-r--r--src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java12
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFBorderPainter.java325
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java52
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFContentGenerator.java345
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java295
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java58
-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.java350
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderer.java781
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java180
-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/render/xml/XMLRenderer.java3
93 files changed, 11260 insertions, 893 deletions
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 65bb450f3..c57a9d566 100644
--- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
+++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
@@ -470,7 +470,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 c29dbea15..2be9150b0 100644
--- a/src/java/org/apache/fop/render/AbstractRenderer.java
+++ b/src/java/org/apache/fop/render/AbstractRenderer.java
@@ -21,6 +21,7 @@ package org.apache.fop.render;
// Java
import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.OutputStream;
@@ -113,7 +114,7 @@ public abstract class AbstractRenderer
private Set warnedXMLHandlers;
/** {@inheritDoc} */
- public abstract void setupFontInfo(FontInfo fontInfo);
+ public abstract void setupFontInfo(FontInfo fontInfo) throws FOPException;
/** {@inheritDoc} */
public void setUserAgent(FOUserAgent agent) {
@@ -835,4 +836,34 @@ public abstract class AbstractRenderer
public String getMimeType() {
return null;
}
+
+ /**
+ * Converts a millipoint-based transformation matrix to points.
+ * @param at a millipoint-based transformation matrix
+ * @return a point-based transformation matrix
+ */
+ protected AffineTransform mptToPt(AffineTransform at) {
+ double[] matrix = new double[6];
+ at.getMatrix(matrix);
+ //Convert to points
+ matrix[4] = matrix[4] / 1000;
+ matrix[5] = matrix[5] / 1000;
+ return new AffineTransform(matrix);
+ }
+
+ /**
+ * Converts a point-based transformation matrix to millipoints.
+ * @param at a point-based transformation matrix
+ * @return a millipoint-based transformation matrix
+ */
+ protected AffineTransform ptToMpt(AffineTransform at) {
+ double[] matrix = new double[6];
+ at.getMatrix(matrix);
+ //Convert to millipoints
+ //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/PrintRenderer.java b/src/java/org/apache/fop/render/PrintRenderer.java
index 56504ff53..818e31568 100644
--- a/src/java/org/apache/fop/render/PrintRenderer.java
+++ b/src/java/org/apache/fop/render/PrintRenderer.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.w3c.dom.Document;
+import org.apache.fop.apps.FOPException;
import org.apache.fop.area.Area;
import org.apache.fop.area.Trait;
import org.apache.fop.fonts.CustomFontCollection;
@@ -75,12 +76,8 @@ public abstract class PrintRenderer extends AbstractRenderer {
return this.embedFontInfoList;
}
- /**
- * Set up the font info
- *
- * @param inFontInfo font info to set up
- */
- public void setupFontInfo(FontInfo inFontInfo) {
+ /** {@inheritDoc} */
+ public void setupFontInfo(FontInfo inFontInfo) throws FOPException {
this.fontInfo = inFontInfo;
FontManager fontManager = userAgent.getFactory().getFontManager();
FontCollection[] fontCollections = new FontCollection[] {
diff --git a/src/java/org/apache/fop/render/PrintRendererConfigurator.java b/src/java/org/apache/fop/render/PrintRendererConfigurator.java
index e8127ae34..806331e88 100644
--- a/src/java/org/apache/fop/render/PrintRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/PrintRendererConfigurator.java
@@ -86,6 +86,23 @@ public class PrintRendererConfigurator extends AbstractRendererConfigurator
PrintRenderer printRenderer = (PrintRenderer)renderer;
FontResolver fontResolver = printRenderer.getFontResolver();
+
+ FontEventListener listener = new FontEventAdapter(
+ renderer.getUserAgent().getEventBroadcaster());
+ List embedFontInfoList = buildFontList(cfg, fontResolver, listener);
+ printRenderer.addFontList(embedFontInfoList);
+ }
+
+ /**
+ * Builds the font list from configuration.
+ * @param cfg the configuration object
+ * @param fontResolver a font resolver
+ * @param listener the font event listener
+ * @return the list of {@code EmbedFontInfo} objects
+ * @throws FOPException if an error occurs while processing the configuration
+ */
+ protected List buildFontList(Configuration cfg, FontResolver fontResolver,
+ FontEventListener listener) throws FOPException {
FopFactory factory = userAgent.getFactory();
FontManager fontManager = factory.getFontManager();
if (fontResolver == null) {
@@ -96,15 +113,13 @@ public class PrintRendererConfigurator extends AbstractRendererConfigurator
boolean strict = factory.validateUserConfigStrictly();
FontCache fontCache = fontManager.getFontCache();
- FontEventListener listener = new FontEventAdapter(
- renderer.getUserAgent().getEventBroadcaster());
List/*<EmbedFontInfo>*/ embedFontInfoList = buildFontListFromConfiguration(cfg,
fontResolver, strict, fontManager, listener);
if (fontCache != null && fontCache.hasChanged()) {
fontCache.save();
}
- printRenderer.addFontList(embedFontInfoList);
+ return embedFontInfoList;
}
/**
diff --git a/src/java/org/apache/fop/render/Renderer.java b/src/java/org/apache/fop/render/Renderer.java
index 0ff37db0e..5fa9ca5b2 100644
--- a/src/java/org/apache/fop/render/Renderer.java
+++ b/src/java/org/apache/fop/render/Renderer.java
@@ -98,8 +98,9 @@ public interface Renderer {
* Set up the given FontInfo.
*
* @param fontInfo The font information
+ * @throws FOPException if an error occurs while setting up the font info object
*/
- void setupFontInfo(FontInfo fontInfo);
+ void setupFontInfo(FontInfo fontInfo) throws FOPException;
/**
* Reports if out of order rendering is supported. <p>
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..5a82151e5 100644
--- a/src/java/org/apache/fop/render/RendererFactory.java
+++ b/src/java/org/apache/fop/render/RendererFactory.java
@@ -34,6 +34,9 @@ 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.AbstractIFDocumentHandlerMaker;
+import org.apache.fop.render.intermediate.IFDocumentHandler;
+import org.apache.fop.render.intermediate.IFRenderer;
/**
* Factory for FOEventHandlers and Renderers.
@@ -45,7 +48,7 @@ public class RendererFactory {
private Map rendererMakerMapping = new java.util.HashMap();
private Map eventHandlerMakerMapping = new java.util.HashMap();
-
+ private Map documentHandlerMakerMapping = new java.util.HashMap();
/**
* Main constructor.
@@ -53,6 +56,7 @@ public class RendererFactory {
public RendererFactory() {
discoverRenderers();
discoverFOEventHandlers();
+ discoverDocumentHandlers();
}
/**
@@ -90,6 +94,23 @@ public class RendererFactory {
}
/**
+ * Add a new document handler maker. If another maker has already been registered for a
+ * particular MIME type, this call overwrites the existing one.
+ * @param maker the intermediate format document handler maker
+ */
+ public void addDocumentHandlerMaker(AbstractIFDocumentHandlerMaker maker) {
+ String[] mimes = maker.getSupportedMimeTypes();
+ for (int i = 0; i < mimes.length; i++) {
+ //This overrides any renderer previously set for a MIME type
+ if (documentHandlerMakerMapping.get(mimes[i]) != null) {
+ log.trace("Overriding document handler for " + mimes[i]
+ + " with " + maker.getClass().getName());
+ }
+ documentHandlerMakerMapping.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 +163,32 @@ public class RendererFactory {
}
/**
+ * Add a new document handler 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 document handler maker
+ */
+ public void addDocumentHandlerMaker(String className) {
+ try {
+ AbstractIFDocumentHandlerMaker makerInstance
+ = (AbstractIFDocumentHandlerMaker)Class.forName(className).newInstance();
+ addDocumentHandlerMaker(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 "
+ + AbstractIFDocumentHandlerMaker.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 +211,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 AbstractIFDocumentHandlerMaker getDocumentHandlerMaker(String mime) {
+ AbstractIFDocumentHandlerMaker maker
+ = (AbstractIFDocumentHandlerMaker)documentHandlerMakerMapping.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 +234,32 @@ 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 {
+ AbstractIFDocumentHandlerMaker documentHandlerMaker
+ = getDocumentHandlerMaker(outputFormat);
+ if (documentHandlerMaker != null) {
+ IFRenderer rend = new IFRenderer();
+ rend.setUserAgent(userAgent);
+ IFDocumentHandler documentHandler = createDocumentHandler(
+ userAgent, outputFormat);
+ rend.setDocumentHandler(documentHandler);
+ 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 +275,65 @@ 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.");
+ AbstractIFDocumentHandlerMaker documentHandlerMaker = null;
+ boolean outputStreamMissing = (userAgent.getRendererOverride() == null);
+ if (rendMaker == null) {
+ documentHandlerMaker = getDocumentHandlerMaker(outputFormat);
+ if (documentHandlerMaker != null) {
+ outputStreamMissing &= (out == null)
+ && (documentHandlerMaker.needsOutputStream());
+ }
} else {
- if (out == null
- && userAgent.getRendererOverride() == null
- && rendMaker.needsOutputStream()) {
+ outputStreamMissing &= (out == null) && (rendMaker.needsOutputStream());
+ }
+ if (userAgent.getRendererOverride() != null
+ || rendMaker != null
+ || documentHandlerMaker != 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 IFDocumentHandler} object based on the desired output format.
+ * @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 IFDocumentHandler} instance
+ * @throws FOPException if the document handler cannot be properly constructed
+ */
+ public IFDocumentHandler createDocumentHandler(FOUserAgent userAgent, String outputFormat)
+ throws FOPException {
+ /*
+ if (userAgent.getIFDocumentHandlerOverride() != null) {
+ return userAgent.getIFDocumentHandlerOverride();
+ } else {
+ */
+ AbstractIFDocumentHandlerMaker maker = getDocumentHandlerMaker(outputFormat);
+ if (maker == null) {
+ throw new UnsupportedOperationException(
+ "No IF document handler for the requested format available: " + outputFormat);
+ }
+ IFDocumentHandler documentHandler = maker.makeIFDocumentHandler(userAgent);
+ return documentHandler;
+ //}
+ }
+
+ /**
* @return an array of all supported MIME types
*/
public String[] listSupportedMimeTypes() {
@@ -242,6 +346,10 @@ public class RendererFactory {
while (iter.hasNext()) {
lst.add(((String)iter.next()));
}
+ iter = this.documentHandlerMakerMapping.keySet().iterator();
+ while (iter.hasNext()) {
+ lst.add(((String)iter.next()));
+ }
Collections.sort(lst);
return (String[])lst.toArray(new String[lst.size()]);
}
@@ -296,4 +404,28 @@ public class RendererFactory {
}
}
+ /**
+ * Discovers {@code IFDocumentHandler} implementations through the classpath and dynamically
+ * registers them.
+ */
+ private void discoverDocumentHandlers() {
+ // add mappings from available services
+ Iterator providers = Service.providers(IFDocumentHandler.class);
+ if (providers != null) {
+ while (providers.hasNext()) {
+ AbstractIFDocumentHandlerMaker maker = (AbstractIFDocumentHandlerMaker)providers.next();
+ try {
+ if (log.isDebugEnabled()) {
+ log.debug("Dynamically adding maker for IFDocumentHandler: "
+ + maker.getClass().getName());
+ }
+ addDocumentHandlerMaker(maker);
+ } catch (IllegalArgumentException e) {
+ log.error("Error while adding maker for IFDocumentHandler", 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/bitmap/BitmapRenderingSettings.java b/src/java/org/apache/fop/render/bitmap/BitmapRenderingSettings.java
new file mode 100644
index 000000000..937a07465
--- /dev/null
+++ b/src/java/org/apache/fop/render/bitmap/BitmapRenderingSettings.java
@@ -0,0 +1,109 @@
+/*
+ * 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.bitmap;
+
+import java.awt.image.BufferedImage;
+
+import org.apache.xmlgraphics.image.writer.ImageWriterParams;
+
+import org.apache.fop.render.java2d.Java2DRenderingSettings;
+
+/**
+ * This class holds settings used when rendering to bitmaps.
+ */
+public class BitmapRenderingSettings extends Java2DRenderingSettings implements TIFFConstants {
+
+ /** ImageWriter parameters */
+ private ImageWriterParams writerParams;
+
+ /** Image Type as parameter for the BufferedImage constructor (see BufferedImage.TYPE_*) */
+ private int bufferedImageType = BufferedImage.TYPE_INT_ARGB;
+
+ /** true if anti-aliasing is set */
+ private boolean antialiasing = true;
+
+ /** true if qualityRendering is set */
+ private boolean qualityRendering = true;
+
+ /**
+ * Default constructor. Initializes the settings to their default values.
+ */
+ public BitmapRenderingSettings() {
+ writerParams = new ImageWriterParams();
+ writerParams.setCompressionMethod(COMPRESSION_PACKBITS);
+ }
+
+ /**
+ * Returns the image writer parameters used for encoding the bitmap images.
+ * @return the image writer parameters
+ */
+ public ImageWriterParams getWriterParams() {
+ return this.writerParams;
+ }
+
+ /**
+ * Returns the BufferedImage type.
+ * @return one of BufferedImage.TYPE_*
+ */
+ public int getBufferedImageType() {
+ return this.bufferedImageType;
+ }
+
+ /**
+ * Sets the type of the BufferedImage to use when preparing a new instance.
+ * @param bufferedImageType a BufferImage.TYPE_* value
+ */
+ public void setBufferedImageType(int bufferedImageType) {
+ this.bufferedImageType = bufferedImageType;
+ }
+
+ /**
+ * Enables or disables anti-aliasing.
+ * @param value true to enable anti-aliasing
+ */
+ public void setAntiAliasing(boolean value) {
+ this.antialiasing = value;
+ }
+
+ /**
+ * Indicates whether anti-aliasing is enabled.
+ * @return true if anti-aliasing is enabled
+ */
+ public boolean isAntiAliasingEnabled() {
+ return this.antialiasing;
+ }
+
+ /**
+ * Controls whether to optimize rendering for speed or for quality.
+ * @param quality true to optimize for quality, false to optimize for speed
+ */
+ public void setQualityRendering(boolean quality) {
+ this.qualityRendering = quality;
+ }
+
+ /**
+ * Indicates whether quality rendering is enabled.
+ * @return true indicates optimization for quality, false indicates optimization for speed
+ */
+ public boolean isQualityRenderingEnabled() {
+ return this.qualityRendering;
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/bitmap/TIFFConstants.java b/src/java/org/apache/fop/render/bitmap/TIFFConstants.java
new file mode 100644
index 000000000..437cf536a
--- /dev/null
+++ b/src/java/org/apache/fop/render/bitmap/TIFFConstants.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.bitmap;
+
+import org.apache.fop.apps.MimeConstants;
+
+/**
+ * Constants for TIFF output.
+ */
+public interface TIFFConstants {
+
+ /** The MIME type for tiff-Rendering */
+ String MIME_TYPE = MimeConstants.MIME_TIFF;
+
+ /** No compression */
+ String COMPRESSION_NONE = "NONE";
+ /** JPEG compression */
+ String COMPRESSION_JPEG = "JPEG";
+ /** Packbits (RLE) compression */
+ String COMPRESSION_PACKBITS = "PackBits";
+ /** Deflate compression */
+ String COMPRESSION_DEFLATE = "Deflate";
+ /** LZW compression */
+ String COMPRESSION_LZW = "LZW";
+ /** ZLib compression */
+ String COMPRESSION_ZLIB = "ZLib";
+ /** CCITT Group 4 (T.6) compression */
+ String COMPRESSION_CCITT_T6 = "CCITT T.6"; //CCITT Group 4
+ /** CCITT Group 3 (T.4) compression */
+ String COMPRESSION_CCITT_T4 = "CCITT T.4"; //CCITT Group 3
+
+}
diff --git a/src/java/org/apache/fop/render/bitmap/TIFFDocumentHandler.java b/src/java/org/apache/fop/render/bitmap/TIFFDocumentHandler.java
new file mode 100644
index 000000000..0da1c02fe
--- /dev/null
+++ b/src/java/org/apache/fop/render/bitmap/TIFFDocumentHandler.java
@@ -0,0 +1,272 @@
+/*
+ * 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.bitmap;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.xmlgraphics.image.writer.ImageWriter;
+import org.apache.xmlgraphics.image.writer.ImageWriterRegistry;
+import org.apache.xmlgraphics.image.writer.MultiImageWriter;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.FopFactoryConfigurator;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
+import org.apache.fop.render.intermediate.IFException;
+import org.apache.fop.render.intermediate.IFPainter;
+import org.apache.fop.render.java2d.Java2DPainter;
+import org.apache.fop.render.java2d.Java2DUtil;
+
+/**
+ * {@code IFDocumentHandler} implementation that produces PCL 5.
+ */
+public class TIFFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler
+ implements TIFFConstants {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(TIFFDocumentHandler.class);
+
+ private ImageWriter imageWriter;
+ private MultiImageWriter multiImageWriter;
+
+ private int pageCount;
+ private Dimension currentPageDimensions;
+ private BufferedImage currentImage;
+
+ private BitmapRenderingSettings bitmapSettings = new BitmapRenderingSettings();
+
+ private double scaleFactor = 1.0;
+
+ /**
+ * Default constructor.
+ */
+ public TIFFDocumentHandler() {
+ }
+
+ /** {@inheritDoc} */
+ public boolean supportsPagesOutOfOrder() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public String getMimeType() {
+ return MimeConstants.MIME_TIFF;
+ }
+
+ /** {@inheritDoc} */
+ public void setUserAgent(FOUserAgent ua) {
+ super.setUserAgent(ua);
+
+ //Set target resolution
+ int dpi = Math.round(ua.getTargetResolution());
+ getSettings().getWriterParams().setResolution(dpi);
+ }
+
+ /** {@inheritDoc} */
+ public IFDocumentHandlerConfigurator getConfigurator() {
+ return new TIFFRendererConfigurator(getUserAgent());
+ }
+
+ /**
+ * Returns the settings for bitmap rendering.
+ * @return the settings object
+ */
+ public BitmapRenderingSettings getSettings() {
+ return this.bitmapSettings;
+ }
+
+ /** {@inheritDoc} */
+ public void setDefaultFontInfo(FontInfo fontInfo) {
+ FontInfo fi = Java2DUtil.buildDefaultJava2DBasedFontInfo(fontInfo, getUserAgent());
+ setFontInfo(fi);
+ }
+
+ //----------------------------------------------------------------------------------------------
+
+ /** {@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()");
+ }
+
+ // Creates writer
+ this.imageWriter = ImageWriterRegistry.getInstance().getWriterFor(getMimeType());
+ if (this.imageWriter == null) {
+ BitmapRendererEventProducer eventProducer
+ = BitmapRendererEventProducer.Provider.get(
+ getUserAgent().getEventBroadcaster());
+ eventProducer.noImageWriterFound(this, getMimeType());
+ }
+ if (this.imageWriter.supportsMultiImageWriter()) {
+ this.multiImageWriter = this.imageWriter.createMultiImageWriter(outputStream);
+ }
+ this.pageCount = 0;
+ } catch (IOException e) {
+ throw new IFException("I/O error in startDocument()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endDocumentHeader() throws IFException {
+ }
+
+ /** {@inheritDoc} */
+ public void endDocument() throws IFException {
+ try {
+ if (this.multiImageWriter != null) {
+ this.multiImageWriter.close();
+ }
+ this.multiImageWriter = null;
+ this.imageWriter = null;
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in endDocument()", ioe);
+ }
+ super.endDocument();
+ }
+
+ /** {@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 {
+ this.pageCount++;
+ this.currentPageDimensions = new Dimension(size);
+ }
+
+ /** {@inheritDoc} */
+ public IFPainter startPageContent() throws IFException {
+ double scale = scaleFactor
+ * (25.4f / FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION)
+ / getUserAgent().getTargetPixelUnitToMillimeter();
+ int bitmapWidth = (int) ((this.currentPageDimensions.width * scale / 1000f) + 0.5f);
+ int bitmapHeight = (int) ((this.currentPageDimensions.height * scale / 1000f) + 0.5f);
+ this.currentImage = createBufferedImage(bitmapWidth, bitmapHeight);
+ Graphics2D graphics2D = this.currentImage.createGraphics();
+ // draw page background
+ if (!getSettings().hasTransparentPageBackground()) {
+ graphics2D.setBackground(getSettings().getPageBackgroundColor());
+ graphics2D.setPaint(getSettings().getPageBackgroundColor());
+ graphics2D.fillRect(0, 0, bitmapWidth, bitmapHeight);
+ }
+
+ graphics2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+ RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+ if (getSettings().isAntiAliasingEnabled()
+ && this.currentImage.getColorModel().getPixelSize() > 1) {
+ graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ } else {
+ graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_OFF);
+ graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+ }
+ if (getSettings().isQualityRenderingEnabled()) {
+ graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ } else {
+ graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_SPEED);
+ }
+ graphics2D.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+ RenderingHints.VALUE_STROKE_PURE);
+ graphics2D.scale(scale / 1000f, scale / 1000f);
+
+ return new Java2DPainter(graphics2D, getUserAgent(), getFontInfo());
+ }
+
+ /**
+ * Creates a new BufferedImage.
+ * @param bitmapWidth the desired width in pixels
+ * @param bitmapHeight the desired height in pixels
+ * @return the new BufferedImage instance
+ */
+ protected BufferedImage createBufferedImage(int bitmapWidth, int bitmapHeight) {
+ return new BufferedImage(bitmapWidth, bitmapHeight, getSettings().getBufferedImageType());
+ }
+
+ /** {@inheritDoc} */
+ public void endPageContent() throws IFException {
+ try {
+ if (this.multiImageWriter == null) {
+ switch (this.pageCount) {
+ case 1:
+ this.imageWriter.writeImage(
+ this.currentImage, this.outputStream,
+ getSettings().getWriterParams());
+ break;
+ case 2:
+ BitmapRendererEventProducer eventProducer
+ = BitmapRendererEventProducer.Provider.get(
+ getUserAgent().getEventBroadcaster());
+ eventProducer.stoppingAfterFirstPageNoFilename(this);
+ break;
+ default:
+ //ignore
+ }
+ } else {
+ this.multiImageWriter.writeImage(this.currentImage,
+ getSettings().getWriterParams());
+ }
+ this.currentImage = null;
+ } catch (IOException ioe) {
+ throw new IFException("I/O error while encoding BufferedImage", ioe);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endPage() throws IFException {
+ this.currentPageDimensions = null;
+ }
+
+ /** {@inheritDoc} */
+ public void handleExtensionObject(Object extension) throws IFException {
+ if (false) {
+ //TODO Handle extensions
+ } else {
+ log.debug("Don't know how to handle extension object. Ignoring: "
+ + extension + " (" + extension.getClass().getName() + ")");
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/bitmap/TIFFDocumentHandlerMaker.java b/src/java/org/apache/fop/render/bitmap/TIFFDocumentHandlerMaker.java
new file mode 100644
index 000000000..dd1cb10be
--- /dev/null
+++ b/src/java/org/apache/fop/render/bitmap/TIFFDocumentHandlerMaker.java
@@ -0,0 +1,58 @@
+/*
+ * 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.bitmap;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.render.intermediate.AbstractIFDocumentHandlerMaker;
+import org.apache.fop.render.intermediate.IFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
+
+/**
+ * Document handler factory for TIFF output.
+ */
+public class TIFFDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker {
+
+ //TODO Revert to normal MIME after stabilization!
+ private static final String[] MIMES = new String[] {MimeConstants.MIME_TIFF + ";mode=painter"};
+
+ /** {@inheritDoc} */
+ public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) {
+ TIFFDocumentHandler handler = new TIFFDocumentHandler();
+ handler.setUserAgent(ua);
+ return handler;
+ }
+
+ /** {@inheritDoc} */
+ public boolean needsOutputStream() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getSupportedMimeTypes() {
+ return MIMES;
+ }
+
+ /** {@inheritDoc} */
+ public IFDocumentHandlerConfigurator getConfigurator(FOUserAgent userAgent) {
+ return new TIFFRendererConfigurator(userAgent);
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java b/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java
index fea831a9b..c524ccc3a 100644
--- a/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java
+++ b/src/java/org/apache/fop/render/bitmap/TIFFRenderer.java
@@ -43,7 +43,6 @@ import org.apache.xmlgraphics.image.writer.MultiImageWriter;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
-import org.apache.fop.apps.MimeConstants;
import org.apache.fop.render.java2d.Java2DRenderer;
/**
@@ -67,19 +66,7 @@ import org.apache.fop.render.java2d.Java2DRenderer;
* <code>org.apache.fop.render.java2D.Java2DRenderer</code> and just encode
* rendering results into TIFF format using Batik's image codec
*/
-public class TIFFRenderer extends Java2DRenderer {
-
- /** The MIME type for tiff-Rendering */
- public static final String MIME_TYPE = MimeConstants.MIME_TIFF;
-
- //private static final String COMPRESSION_NONE = "NONE";
- //private static final String COMPRESSION_JPEG = "JPEG";
- public static final String COMPRESSION_PACKBITS = "PackBits";
- //private static final String COMPRESSION_DEFLATE = "Deflate";
- //private static final String COMPRESSION_LZW = "LZW";
- //private static final String COMPRESSION_ZLIB = "ZLib";
- public static final String COMPRESSION_CCITT_T6 = "CCITT T.6"; //CCITT Group 4
- public static final String COMPRESSION_CCITT_T4 = "CCITT T.4"; //CCITT Group 3
+public class TIFFRenderer extends Java2DRenderer implements TIFFConstants {
/** ImageWriter parameters */
private ImageWriterParams writerParams;
diff --git a/src/java/org/apache/fop/render/bitmap/TIFFRendererConfigurator.java b/src/java/org/apache/fop/render/bitmap/TIFFRendererConfigurator.java
index ff5e22ceb..a5b8e5531 100644
--- a/src/java/org/apache/fop/render/bitmap/TIFFRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/bitmap/TIFFRendererConfigurator.java
@@ -19,18 +19,38 @@
package org.apache.fop.render.bitmap;
+import java.awt.Color;
+import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
+import java.util.List;
import org.apache.avalon.framework.configuration.Configuration;
+
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
-import org.apache.fop.render.PrintRendererConfigurator;
+import org.apache.fop.fonts.FontCollection;
+import org.apache.fop.fonts.FontEventAdapter;
+import org.apache.fop.fonts.FontEventListener;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontManager;
+import org.apache.fop.fonts.FontResolver;
+import org.apache.fop.render.DefaultFontResolver;
import org.apache.fop.render.Renderer;
+import org.apache.fop.render.intermediate.IFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
+import org.apache.fop.render.java2d.Base14FontCollection;
+import org.apache.fop.render.java2d.ConfiguredFontCollection;
+import org.apache.fop.render.java2d.InstalledFontCollection;
+import org.apache.fop.render.java2d.Java2DFontMetrics;
+import org.apache.fop.render.java2d.Java2DRenderer;
+import org.apache.fop.render.java2d.Java2DRendererConfigurator;
+import org.apache.fop.util.ColorUtil;
/**
* TIFF Renderer configurator
*/
-public class TIFFRendererConfigurator extends PrintRendererConfigurator {
+public class TIFFRendererConfigurator extends Java2DRendererConfigurator
+ implements IFDocumentHandlerConfigurator {
/**
* Default constructor
@@ -52,15 +72,9 @@ public class TIFFRendererConfigurator extends PrintRendererConfigurator {
if (cfg != null) {
TIFFRenderer tiffRenderer = (TIFFRenderer)renderer;
//set compression
- String name = cfg.getChild("compression").getValue(TIFFRenderer.COMPRESSION_PACKBITS);
+ String name = cfg.getChild("compression").getValue(TIFFConstants.COMPRESSION_PACKBITS);
//Some compression formats need a special image format:
- if (name.equalsIgnoreCase(TIFFRenderer.COMPRESSION_CCITT_T6)) {
- tiffRenderer.setBufferedImageType(BufferedImage.TYPE_BYTE_BINARY);
- } else if (name.equalsIgnoreCase(TIFFRenderer.COMPRESSION_CCITT_T4)) {
- tiffRenderer.setBufferedImageType(BufferedImage.TYPE_BYTE_BINARY);
- } else {
- tiffRenderer.setBufferedImageType(BufferedImage.TYPE_INT_ARGB);
- }
+ tiffRenderer.setBufferedImageType(getBufferedImageTypeFor(name));
if (!"NONE".equalsIgnoreCase(name)) {
tiffRenderer.getWriterParams().setCompressionMethod(name);
}
@@ -70,4 +84,104 @@ public class TIFFRendererConfigurator extends PrintRendererConfigurator {
}
super.configure(renderer);
}
+
+ private int getBufferedImageTypeFor(String compressionName) {
+ if (compressionName.equalsIgnoreCase(TIFFConstants.COMPRESSION_CCITT_T6)) {
+ return BufferedImage.TYPE_BYTE_BINARY;
+ } else if (compressionName.equalsIgnoreCase(TIFFConstants.COMPRESSION_CCITT_T4)) {
+ return BufferedImage.TYPE_BYTE_BINARY;
+ } else {
+ return BufferedImage.TYPE_INT_ARGB;
+ }
+ }
+
+ // ---=== IFDocumentHandler configuration ===---
+
+ /** {@inheritDoc} */
+ public void configure(IFDocumentHandler documentHandler) throws FOPException {
+ Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
+ if (cfg != null) {
+ TIFFDocumentHandler tiffHandler = (TIFFDocumentHandler)documentHandler;
+ BitmapRenderingSettings settings = tiffHandler.getSettings();
+ //set compression
+ String name = cfg.getChild("compression").getValue(TIFFConstants.COMPRESSION_PACKBITS);
+ //Some compression formats need a special image format:
+ settings.setBufferedImageType(getBufferedImageTypeFor(name));
+ if (!"NONE".equalsIgnoreCase(name)) {
+ settings.getWriterParams().setCompressionMethod(name);
+ }
+ if (log.isInfoEnabled()) {
+ log.info("TIFF compression set to " + name);
+ }
+
+ boolean transparent = cfg.getChild(
+ Java2DRenderer.JAVA2D_TRANSPARENT_PAGE_BACKGROUND).getValueAsBoolean(
+ settings.hasTransparentPageBackground());
+ if (transparent) {
+ settings.setPageBackgroundColor(null);
+ } else {
+ String background = cfg.getChild("background-color").getValue(null);
+ if (background != null) {
+ settings.setPageBackgroundColor(
+ ColorUtil.parseColorString(this.userAgent, background));
+ } else {
+ settings.setPageBackgroundColor(Color.WHITE);
+ }
+ }
+
+ boolean antiAliasing = cfg.getChild("anti-aliasing").getValueAsBoolean(
+ settings.isAntiAliasingEnabled());
+ settings.setAntiAliasing(antiAliasing);
+
+ String optimization = cfg.getChild("rendering").getValue(null);
+ if ("quality".equalsIgnoreCase(optimization)) {
+ settings.setQualityRendering(true);
+ } else if ("speed".equalsIgnoreCase(optimization)) {
+ settings.setQualityRendering(false);
+ }
+
+ String color = cfg.getChild("color-mode").getValue(null);
+ if (color != null) {
+ if ("rgba".equalsIgnoreCase(color)) {
+ settings.setBufferedImageType(BufferedImage.TYPE_INT_ARGB);
+ } else if ("rgb".equalsIgnoreCase(color)) {
+ settings.setBufferedImageType(BufferedImage.TYPE_INT_RGB);
+ } else if ("gray".equalsIgnoreCase(color)) {
+ settings.setBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
+ } else if ("binary".equalsIgnoreCase(color)) {
+ settings.setBufferedImageType(BufferedImage.TYPE_BYTE_BINARY);
+ } else {
+ throw new FOPException("Invalid value for color-mode: " + color);
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo)
+ throws FOPException {
+ FontManager fontManager = userAgent.getFactory().getFontManager();
+
+ Graphics2D graphics2D = Java2DFontMetrics.createFontMetricsGraphics2D();
+
+ List fontCollections = new java.util.ArrayList();
+ fontCollections.add(new Base14FontCollection(graphics2D));
+ fontCollections.add(new InstalledFontCollection(graphics2D));
+
+ Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
+ if (cfg != null) {
+ FontResolver fontResolver = new DefaultFontResolver(userAgent);
+ FontEventListener listener = new FontEventAdapter(
+ userAgent.getEventBroadcaster());
+ List fontList = buildFontList(cfg, fontResolver, listener);
+ fontCollections.add(new ConfiguredFontCollection(fontResolver, fontList));
+ }
+
+ fontManager.setup(fontInfo,
+ (FontCollection[])fontCollections.toArray(
+ new FontCollection[fontCollections.size()]));
+ documentHandler.setFontInfo(fontInfo);
+ }
+
+
}
diff --git a/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFDocumentHandler.java
new file mode 100644
index 000000000..758b18dbb
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/AbstractBinaryWritingIFDocumentHandler.java
@@ -0,0 +1,117 @@
+/*
+ * 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 {@code IFDocumentHandler} implementations.
+ */
+public abstract class AbstractBinaryWritingIFDocumentHandler extends AbstractIFDocumentHandler {
+
+ /** 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());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public FontInfo getFontInfo() {
+ return this.fontInfo;
+ }
+
+ /** {@inheritDoc} */
+ public void setFontInfo(FontInfo fontInfo) {
+ this.fontInfo = fontInfo;
+ }
+
+ /** {@inheritDoc} */
+ public void setDefaultFontInfo(FontInfo fontInfo) {
+ FontManager fontManager = getUserAgent().getFactory().getFontManager();
+ FontCollection[] fontCollections = new FontCollection[] {
+ new Base14FontCollection(fontManager.isBase14KerningEnabled())
+ };
+
+ FontInfo fi = (fontInfo != null ? fontInfo : 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/AbstractIFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java
new file mode 100644
index 000000000..b1f1f20f9
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java
@@ -0,0 +1,99 @@
+/*
+ * 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.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.apps.FOUserAgent;
+
+/**
+ * Abstract base class for {@code IFDocumentHandler} implementations.
+ */
+public abstract class AbstractIFDocumentHandler implements IFDocumentHandler {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(AbstractIFDocumentHandler.class);
+
+ private FOUserAgent userAgent;
+
+ /**
+ * Default constructor.
+ */
+ public AbstractIFDocumentHandler() {
+ }
+
+ /** {@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
+ */
+ public FOUserAgent getUserAgent() {
+ return this.userAgent;
+ }
+
+ /** {@inheritDoc} */
+ public void startDocumentHeader() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void endDocumentHeader() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void startDocumentTrailer() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void endDocumentTrailer() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void startPageHeader() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void endPageHeader() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void startPageTrailer() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void endPageTrailer() throws IFException {
+ //nop
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandlerMaker.java b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandlerMaker.java
new file mode 100644
index 000000000..f163c3776
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandlerMaker.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 IFDocumentHandler}s and provide
+ * information about them.
+ */
+public abstract class AbstractIFDocumentHandlerMaker {
+
+ /**
+ * Instantiates a new {@code IFDocumentHandler}.
+ * @param userAgent the user agent
+ * @return the newly instantiated document handler
+ */
+ public abstract IFDocumentHandler makeIFDocumentHandler(FOUserAgent userAgent);
+
+ /**
+ * @return Indicates whether this document handler requires an OutputStream to work with.
+ */
+ public abstract boolean needsOutputStream();
+
+ /**
+ * @return an array of MIME types the document handler supports.
+ */
+ public abstract String[] getSupportedMimeTypes();
+
+ /**
+ * Returns a configurator object that can be used to
+ * configure the document handler.
+ * @param userAgent the user agent
+ * @return a configurator object that can be used to configure the document handler
+ */
+ public abstract IFDocumentHandlerConfigurator getConfigurator(FOUserAgent userAgent);
+
+ /**
+ * Indicates whether a specific MIME type is supported by this document handler.
+ * @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/AbstractIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java
new file mode 100644
index 000000000..a54e62bd0
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java
@@ -0,0 +1,207 @@
+/*
+ * 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.apps.FopFactory;
+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)";
+
+
+ /**
+ * Default constructor.
+ */
+ public AbstractIFPainter() {
+ }
+
+ /**
+ * Returns the user agent.
+ * @return the user agent
+ */
+ protected abstract FOUserAgent getUserAgent();
+
+ /**
+ * Returns the FOP factory.
+ * @return the FOP factory.
+ */
+ protected FopFactory getFopFactory() {
+ return getUserAgent().getFactory();
+ }
+
+ 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 = getFopFactory().getImageManager();
+ ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
+ ImageHandlerRegistry imageHandlerRegistry = getFopFactory().getImageHandlerRegistry();
+
+ //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 = getFopFactory().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 = getFopFactory().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/AbstractXMLWritingIFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFDocumentHandler.java
new file mode 100644
index 000000000..98706c09b
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/AbstractXMLWritingIFDocumentHandler.java
@@ -0,0 +1,86 @@
+/*
+ * 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 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.ContentHandler;
+
+import org.apache.fop.util.GenerationHelperContentHandler;
+
+/**
+ * Abstract base class for XML-writing {@code IFDocumentHandler} implementations.
+ */
+public abstract class AbstractXMLWritingIFDocumentHandler extends AbstractIFDocumentHandler {
+
+ /**
+ * Default SAXTransformerFactory that can be used by subclasses.
+ */
+ protected SAXTransformerFactory tFactory
+ = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
+
+ /** Main SAX ContentHandler to receive the generated SAX events. */
+ protected GenerationHelperContentHandler handler;
+
+ /** {@inheritDoc} */
+ public void setResult(Result result) throws IFException {
+ if (result instanceof SAXResult) {
+ SAXResult saxResult = (SAXResult)result;
+ this.handler = new GenerationHelperContentHandler(
+ saxResult.getHandler(), getMainNamespace());
+ } else {
+ this.handler = new GenerationHelperContentHandler(
+ createContentHandler(result), getMainNamespace());
+ }
+ }
+
+ /**
+ * 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 XML output", tce);
+ }
+ }
+
+}
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/BorderPainter.java b/src/java/org/apache/fop/render/intermediate/BorderPainter.java
new file mode 100644
index 000000000..e8874dc69
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/BorderPainter.java
@@ -0,0 +1,219 @@
+/*
+ * 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.Point;
+import java.awt.Rectangle;
+
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+
+/**
+ * This is an abstract base class for handling border painting.
+ */
+public abstract class BorderPainter {
+
+ /**
+ * Draws borders.
+ * @param borderRect the border rectangle
+ * @param bpsBefore the border specification on the before side
+ * @param bpsAfter the border specification on the after side
+ * @param bpsStart the border specification on the start side
+ * @param bpsEnd the border specification on the end side
+ */
+ public void drawBorders(Rectangle borderRect,
+ BorderProps bpsBefore, BorderProps bpsAfter,
+ BorderProps bpsStart, BorderProps bpsEnd) {
+ int startx = borderRect.x;
+ int starty = borderRect.y;
+ int width = borderRect.width;
+ int height = borderRect.height;
+ boolean[] b = new boolean[] {
+ (bpsBefore != null), (bpsEnd != null),
+ (bpsAfter != null), (bpsStart != null)};
+ if (!b[0] && !b[1] && !b[2] && !b[3]) {
+ return;
+ }
+ int[] bw = new int[] {
+ (b[0] ? bpsBefore.width : 0),
+ (b[1] ? bpsEnd.width : 0),
+ (b[2] ? bpsAfter.width : 0),
+ (b[3] ? bpsStart.width : 0)};
+ int[] clipw = new int[] {
+ BorderProps.getClippedWidth(bpsBefore),
+ BorderProps.getClippedWidth(bpsEnd),
+ BorderProps.getClippedWidth(bpsAfter),
+ BorderProps.getClippedWidth(bpsStart)};
+ starty += clipw[0];
+ height -= clipw[0];
+ height -= clipw[2];
+ startx += clipw[3];
+ width -= clipw[3];
+ width -= clipw[1];
+
+ boolean[] slant = new boolean[] {
+ (b[3] && b[0]), (b[0] && b[1]), (b[1] && b[2]), (b[2] && b[3])};
+ if (bpsBefore != null) {
+ int sx1 = startx;
+ int sx2 = (slant[0] ? sx1 + bw[3] - clipw[3] : sx1);
+ int ex1 = startx + width;
+ int ex2 = (slant[1] ? ex1 - bw[1] + clipw[1] : ex1);
+ int outery = starty - clipw[0];
+ int clipy = outery + clipw[0];
+ int innery = outery + bw[0];
+
+ saveGraphicsState();
+ moveTo(sx1, clipy);
+ int sx1a = sx1;
+ int ex1a = ex1;
+ if (bpsBefore.mode == BorderProps.COLLAPSE_OUTER) {
+ if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) {
+ sx1a -= clipw[3];
+ }
+ if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) {
+ ex1a += clipw[1];
+ }
+ lineTo(sx1a, outery);
+ lineTo(ex1a, outery);
+ }
+ lineTo(ex1, clipy);
+ lineTo(ex2, innery);
+ lineTo(sx2, innery);
+ closePath();
+ clip();
+ drawBorderLine(sx1a, outery, ex1a, innery, true, true,
+ bpsBefore.style, bpsBefore.color);
+ restoreGraphicsState();
+ }
+ if (bpsEnd != null) {
+ int sy1 = starty;
+ int sy2 = (slant[1] ? sy1 + bw[0] - clipw[0] : sy1);
+ int ey1 = starty + height;
+ int ey2 = (slant[2] ? ey1 - bw[2] + clipw[2] : ey1);
+ int outerx = startx + width + clipw[1];
+ int clipx = outerx - clipw[1];
+ int innerx = outerx - bw[1];
+
+ saveGraphicsState();
+ moveTo(clipx, sy1);
+ int sy1a = sy1;
+ int ey1a = ey1;
+ if (bpsEnd.mode == BorderProps.COLLAPSE_OUTER) {
+ if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) {
+ sy1a -= clipw[0];
+ }
+ if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) {
+ ey1a += clipw[2];
+ }
+ lineTo(outerx, sy1a);
+ lineTo(outerx, ey1a);
+ }
+ lineTo(clipx, ey1);
+ lineTo(innerx, ey2);
+ lineTo(innerx, sy2);
+ closePath();
+ clip();
+ drawBorderLine(innerx, sy1a, outerx, ey1a, false, false, bpsEnd.style, bpsEnd.color);
+ restoreGraphicsState();
+ }
+ if (bpsAfter != null) {
+ int sx1 = startx;
+ int sx2 = (slant[3] ? sx1 + bw[3] - clipw[3] : sx1);
+ int ex1 = startx + width;
+ int ex2 = (slant[2] ? ex1 - bw[1] + clipw[1] : ex1);
+ int outery = starty + height + clipw[2];
+ int clipy = outery - clipw[2];
+ int innery = outery - bw[2];
+
+ saveGraphicsState();
+ moveTo(ex1, clipy);
+ int sx1a = sx1;
+ int ex1a = ex1;
+ if (bpsAfter.mode == BorderProps.COLLAPSE_OUTER) {
+ if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) {
+ sx1a -= clipw[3];
+ }
+ if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) {
+ ex1a += clipw[1];
+ }
+ lineTo(ex1a, outery);
+ lineTo(sx1a, outery);
+ }
+ lineTo(sx1, clipy);
+ lineTo(sx2, innery);
+ lineTo(ex2, innery);
+ closePath();
+ clip();
+ drawBorderLine(sx1a, innery, ex1a, outery, true, false, bpsAfter.style, bpsAfter.color);
+ restoreGraphicsState();
+ }
+ if (bpsStart != null) {
+ int sy1 = starty;
+ int sy2 = (slant[0] ? sy1 + bw[0] - clipw[0] : sy1);
+ int ey1 = sy1 + height;
+ int ey2 = (slant[3] ? ey1 - bw[2] + clipw[2] : ey1);
+ int outerx = startx - clipw[3];
+ int clipx = outerx + clipw[3];
+ int innerx = outerx + bw[3];
+
+ saveGraphicsState();
+ moveTo(clipx, ey1);
+ int sy1a = sy1;
+ int ey1a = ey1;
+ if (bpsStart.mode == BorderProps.COLLAPSE_OUTER) {
+ if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) {
+ sy1a -= clipw[0];
+ }
+ if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) {
+ ey1a += clipw[2];
+ }
+ lineTo(outerx, ey1a);
+ lineTo(outerx, sy1a);
+ }
+ lineTo(clipx, sy1);
+ lineTo(innerx, sy2);
+ lineTo(innerx, ey2);
+ closePath();
+ clip();
+ drawBorderLine(outerx, sy1a, innerx, ey1a, false, true, bpsStart.style, bpsStart.color);
+ restoreGraphicsState();
+ }
+ }
+
+
+ protected abstract void drawBorderLine(int x1, int y1, int x2, int y2,
+ boolean horz, boolean startOrBefore, int style, Color color);
+
+ public abstract void drawLine(Point start, Point end,
+ int width, Color color, RuleStyle style);
+
+ protected abstract void moveTo(int x, int y);
+
+ protected abstract void lineTo(int x, int y);
+
+ protected abstract void closePath();
+
+ protected abstract void clip();
+
+ protected abstract void saveGraphicsState();
+ protected abstract void restoreGraphicsState();
+
+}
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..e7f7e1a00
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFConstants.java
@@ -0,0 +1,53 @@
+/*
+ * 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.MimeConstants;
+import org.apache.fop.util.XMLConstants;
+
+/**
+ * Constants for the intermediate format.
+ */
+public interface IFConstants extends XMLConstants {
+
+ /** 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";
+
+ String EL_DOCUMENT = "document";
+ String EL_HEADER = "header";
+ String EL_TRAILER = "trailer";
+ 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";
+ String EL_CLIP_RECT = "clip-rect";
+ String EL_RECT = "rect";
+ String EL_LINE = "line";
+ String EL_BORDER_RECT = "border-rect";
+ String EL_FONT = "font";
+ String EL_TEXT = "text";
+}
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/IFDocumentHandler.java b/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java
new file mode 100644
index 000000000..61093769b
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java
@@ -0,0 +1,254 @@
+/*
+ * 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 javax.xml.transform.Result;
+
+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()
+ * (#box)+
+ * endPageContent()
+ * startPageTrailer()
+ * (addTarget())*
+ * endPageTrailer()
+ * endPage()
+ * ]*
+ * endPageSequence()
+ * ]*
+ * startDocumentTrailer()
+ * [handleExtension()]*
+ * endDocumentTrailer()
+ * endDocument()
+ *
+ * #box:
+ * startBox() (#pageContent)+ endBox() |
+ * startViewport() (#pageContext)+ endViewport()
+ *
+ * #pageContent:
+ * (
+ * setFont() |
+ * drawText() |
+ * drawRect() |
+ * drawImage() |
+ * TODO etc. etc. |
+ * handleExtensionObject()
+ * )
+ * </pre>
+ */
+public interface IFDocumentHandler {
+
+ /**
+ * 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);
+
+ /**
+ * Returns the font set to work with.
+ * @return the font info object
+ */
+ FontInfo getFontInfo();
+
+ /**
+ * Sets the default font set (with no custom configuration).
+ * @param fontInfo the font info object to populate
+ */
+ void setDefaultFontInfo(FontInfo fontInfo);
+
+ /**
+ * Returns the configurator for this document handler, if any.
+ * @return the configurator or null if there's no configurator
+ */
+ IFDocumentHandlerConfigurator getConfigurator();
+
+ /**
+ * 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 the document trailer. This method is called after the last
+ * page sequence. Extensions sent to the painter between
+ * {@code #startDocumentTrailer()} and {@code #endDocumentTrailer()} apply to the document as
+ * a whole and is used for document-level content that is only known after all pages have
+ * been rendered (like named destinations or the bookmark tree).
+ * @throws IFException if an error occurs while handling this event
+ */
+ void startDocumentTrailer() throws IFException;
+
+ /**
+ * Indicates the end of the document trailer. This method is called right before the
+ * {@code #endDocument()} method.
+ * @throws IFException if an error occurs while handling this event
+ */
+ void endDocumentTrailer() 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. The method returns an {@code IFPainter} interface
+ * which is used to paint the page contents.
+ * @throws IFException if an error occurs while handling this event
+ * @return the IFPainter for the page content
+ */
+ IFPainter startPageContent() throws IFException;
+
+ /**
+ * Indicates the end of the page content. Calls to the {@code IFPainter} returned by the
+ * respective {@code #startPageContent()} method are illegal.
+ * @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;
+
+ /**
+ * Indicates the end of the page trailer.
+ * @throws IFException if an error occurs while handling this event
+ */
+ void endPageTrailer() throws IFException;
+
+ /**
+ * 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/IFDocumentHandlerConfigurator.java b/src/java/org/apache/fop/render/intermediate/IFDocumentHandlerConfigurator.java
new file mode 100644
index 000000000..633e564a7
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFDocumentHandlerConfigurator.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+import org.apache.fop.fonts.FontInfo;
+
+/**
+ * This interface is implemented by classes that configure an {@code IFDocumentHandler} instance.
+ */
+public interface IFDocumentHandlerConfigurator {
+
+ /**
+ * Configures a intermediate format document handler.
+ * @param documentHandler the document handler instance
+ * @throws FOPException if an error occurs while configuring the object
+ */
+ void configure(IFDocumentHandler documentHandler) throws FOPException;
+
+ /**
+ * Sets up the {@code FontInfo} object for the IFDocumentHandler.
+ * @param documentHandler the document handler instance
+ * @param fontInfo the font info object to set up
+ * @throws FOPException if an error occurs while configuring the object
+ */
+ void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo) throws FOPException;
+
+}
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..34ac0bcb2
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFGraphicContext.java
@@ -0,0 +1,162 @@
+/*
+ * 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 static final AffineTransform[] EMPTY_TRANSFORM_ARRAY = new AffineTransform[0];
+
+ 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 Group() {
+ this(EMPTY_TRANSFORM_ARRAY);
+ }
+
+ 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: ");
+ IFUtil.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: ");
+ IFUtil.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..2c704db8b
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java
@@ -0,0 +1,190 @@
+/*
+ * 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.Point;
+import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+
+/**
+ * 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()
+ * ]*
+ * startDocumentTrailer()
+ * [handleExtension()]*
+ * endDocumentTrailer()
+ * endDocument()
+ *
+ * #box:
+ * startBox()
+ * (#pageContent)+
+ * endBox()
+ *
+ * #pageContent:
+ * (
+ * setFont() |
+ * drawText() |
+ * drawRect() |
+ * drawImage() |
+ * TODO etc. etc. |
+ * handleExtensionObject()
+ * )
+ * </pre>
+ */
+public interface IFPainter {
+
+ 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;
+
+ /**
+ * Restricts the current clipping region with the given rectangle.
+ * @param rect the rectangle's coordinates and extent
+ * @throws IFException if an error occurs while handling this event
+ */
+ void clipRect(Rectangle rect) throws IFException;
+ //TODO clipRect() shall be considered temporary until verified with SVG and PCL
+
+ /**
+ * Fills a rectangular area.
+ * @param rect the rectangle's coordinates and extent
+ * @param fill the fill paint
+ * @throws IFException if an error occurs while handling this event
+ */
+ void fillRect(Rectangle rect, Paint fill) throws IFException;
+
+ /**
+ * Draws a border rectangle. The border segments are specified through {@code BorderProps}
+ * instances.
+ * @param rect the rectangle's coordinates and extent
+ * @param before the border segment on the before-side (top)
+ * @param after the border segment on the after-side (bottom)
+ * @param start the border segment on the start-side (left)
+ * @param end the border segment on the end-side (right)
+ * @throws IFException if an error occurs while handling this event
+ */
+ void drawBorderRect(Rectangle rect,
+ BorderProps before, BorderProps after,
+ BorderProps start, BorderProps end) throws IFException;
+
+ /**
+ * Draws a line. NOTE: Currently, only horizontal lines are implemented!
+ * @param start the start point of the line
+ * @param end the end point of the line
+ * @param width the line width
+ * @param color the line color
+ * @param style the line style (using the Constants.EN_* constants for the rule-style property)
+ * @throws IFException if an error occurs while handling this event
+ */
+ void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
+ 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.
+
+}
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..5eaef13ae
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFParser.java
@@ -0,0 +1,614 @@
+/*
+ * 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.Point;
+import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
+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.ElementMapping;
+import org.apache.fop.fo.ElementMappingRegistry;
+import org.apache.fop.fo.expr.PropertyException;
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+import org.apache.fop.util.ColorUtil;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.ContentHandlerFactoryRegistry;
+import org.apache.fop.util.DOMBuilderContentHandlerFactory;
+import org.apache.fop.util.DefaultErrorListener;
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * 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 documentHandler the intermediate format document handler 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, IFDocumentHandler documentHandler, FOUserAgent userAgent)
+ throws TransformerException {
+ Transformer transformer = tFactory.newTransformer();
+ transformer.setErrorListener(new DefaultErrorListener(log));
+
+ SAXResult res = new SAXResult(getContentHandler(documentHandler, 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 documentHandler the intermediate format document handler 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(IFDocumentHandler documentHandler,
+ FOUserAgent userAgent) {
+ ElementMappingRegistry elementMappingRegistry
+ = userAgent.getFactory().getElementMappingRegistry();
+ return new Handler(documentHandler, userAgent, elementMappingRegistry);
+ }
+
+ private static class Handler extends DefaultHandler {
+
+ private Map elementHandlers = new java.util.HashMap();
+
+ private IFDocumentHandler documentHandler;
+ 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(IFDocumentHandler documentHandler, FOUserAgent userAgent,
+ ElementMappingRegistry elementMappingRegistry) {
+ this.documentHandler = documentHandler;
+ this.userAgent = userAgent;
+ this.elementMappingRegistry = elementMappingRegistry;
+ elementHandlers.put(EL_DOCUMENT, new DocumentHandler());
+ elementHandlers.put(EL_HEADER, new DocumentHeaderHandler());
+ elementHandlers.put(EL_TRAILER, new DocumentTrailerHandler());
+ 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(EL_FONT, new FontHandler());
+ elementHandlers.put(EL_TEXT, new TextHandler());
+ elementHandlers.put(EL_CLIP_RECT, new ClipRectHandler());
+ elementHandlers.put(EL_RECT, new RectHandler());
+ elementHandlers.put(EL_LINE, new LineHandler());
+ elementHandlers.put(EL_BORDER_RECT, new BorderRectHandler());
+ 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) {
+ domImplementation = ElementMapping.getDefaultDOMImplementation();
+ /*
+ 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 {
+ documentHandler.startDocument();
+ }
+
+ public void endElement() throws IFException {
+ documentHandler.endDocument();
+ }
+
+ }
+
+ private class DocumentHeaderHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ documentHandler.startDocumentHeader();
+ }
+
+ public void endElement() throws IFException {
+ documentHandler.endDocumentHeader();
+ }
+
+ }
+
+ private class DocumentTrailerHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ documentHandler.startDocumentTrailer();
+ }
+
+ public void endElement() throws IFException {
+ documentHandler.endDocumentTrailer();
+ }
+
+ }
+
+ private class PageSequenceHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ String id = attributes.getValue("id");
+ documentHandler.startPageSequence(id);
+ }
+
+ public void endElement() throws IFException {
+ documentHandler.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"));
+ documentHandler.startPage(index, name, new Dimension(width, height));
+ }
+
+ public void endElement() throws IFException {
+ documentHandler.endPage();
+ }
+
+ }
+
+ private class PageHeaderHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ documentHandler.startPageHeader();
+ }
+
+ public void endElement() throws IFException {
+ documentHandler.endPageHeader();
+ }
+
+ }
+
+ private class PageContentHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ painter = documentHandler.startPageContent();
+ }
+
+ public void endElement() throws IFException {
+ painter = null;
+ documentHandler.endPageContent();
+ }
+
+ }
+
+ private class PageTrailerHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ documentHandler.startPageTrailer();
+ }
+
+ public void endElement() throws IFException {
+ documentHandler.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 = XMLUtil.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 = XMLUtil.getAttributeAsInteger(attributes, "weight");
+ String variant = attributes.getValue("variant");
+ Integer size = XMLUtil.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 = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx");
+ int[] dy = XMLUtil.getAttributeAsIntArray(lastAttributes, "dy");
+ painter.drawText(x, y, dx, dy, content.toString());
+ }
+
+ public boolean ignoreCharacters() {
+ return false;
+ }
+
+ }
+
+ private class ClipRectHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ int x = Integer.parseInt(attributes.getValue("x"));
+ int y = Integer.parseInt(attributes.getValue("y"));
+ int width = Integer.parseInt(attributes.getValue("width"));
+ int height = Integer.parseInt(attributes.getValue("height"));
+ painter.clipRect(new Rectangle(x, y, width, height));
+ }
+
+ }
+
+ private class RectHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ int x = Integer.parseInt(attributes.getValue("x"));
+ int y = Integer.parseInt(attributes.getValue("y"));
+ int width = Integer.parseInt(attributes.getValue("width"));
+ int height = Integer.parseInt(attributes.getValue("height"));
+ Color fillColor;
+ try {
+ fillColor = getAttributeAsColor(attributes, "fill");
+ } catch (PropertyException pe) {
+ throw new IFException("Error parsing the fill attribute", pe);
+ }
+ painter.fillRect(new Rectangle(x, y, width, height), fillColor);
+ }
+
+ }
+
+ private class LineHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ int x1 = Integer.parseInt(attributes.getValue("x1"));
+ int y1 = Integer.parseInt(attributes.getValue("y1"));
+ int x2 = Integer.parseInt(attributes.getValue("x2"));
+ int y2 = Integer.parseInt(attributes.getValue("y2"));
+ int width = Integer.parseInt(attributes.getValue("stroke-width"));
+ Color color;
+ try {
+ color = getAttributeAsColor(attributes, "color");
+ } catch (PropertyException pe) {
+ throw new IFException("Error parsing the fill attribute", pe);
+ }
+ RuleStyle style = RuleStyle.valueOf(attributes.getValue("style"));
+ painter.drawLine(new Point(x1, y1), new Point(x2, y2), width, color, style);
+ }
+
+ }
+
+ private static final String[] SIDES = new String[] {"before", "after", "start", "end"};
+
+ private class BorderRectHandler extends AbstractElementHandler {
+
+ public void startElement(Attributes attributes) throws IFException {
+ int x = Integer.parseInt(attributes.getValue("x"));
+ int y = Integer.parseInt(attributes.getValue("y"));
+ int width = Integer.parseInt(attributes.getValue("width"));
+ int height = Integer.parseInt(attributes.getValue("height"));
+ BorderProps[] borders = new BorderProps[4];
+ for (int i = 0; i < 4; i++) {
+ String b = attributes.getValue(SIDES[i]);
+ if (b != null) {
+ borders[i] = BorderProps.valueOf(userAgent, b);
+ }
+ }
+
+ painter.drawBorderRect(new Rectangle(x, y, width, height),
+ borders[0], borders[1], borders[2], borders[3]);
+ }
+
+ }
+
+ 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;
+ }
+ }
+
+
+ // ====================================================================
+
+ /**
+ * 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 {
+ documentHandler.handleExtensionObject(obj);
+ } catch (IFException ife) {
+ handleIFException(ife);
+ }
+ }
+
+ 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 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..eddd5f1bd
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java
@@ -0,0 +1,1104 @@
+/*
+ * 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.Point;
+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.Arrays;
+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.Area;
+import org.apache.fop.area.Block;
+import org.apache.fop.area.BlockViewport;
+import org.apache.fop.area.BookmarkData;
+import org.apache.fop.area.CTM;
+import org.apache.fop.area.DestinationData;
+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.InlineArea;
+import org.apache.fop.area.inline.Leader;
+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;
+import org.apache.fop.render.intermediate.extensions.Bookmark;
+import org.apache.fop.render.intermediate.extensions.BookmarkTree;
+import org.apache.fop.render.intermediate.extensions.GoToXYAction;
+import org.apache.fop.render.intermediate.extensions.NamedDestination;
+import org.apache.fop.render.pdf.PDFEventProducer;
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+
+/**
+ * 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 {
+
+ //TODO Many parts of the Renderer infrastructure are using floats (coordinates in points)
+ //instead of ints (in millipoints). A lot of conversion to and from is performed.
+ //When the new IF is established, the Renderer infrastructure should be revisited so check
+ //if optimizations can be done to avoid int->float->int conversions.
+
+ /** 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 IFDocumentHandler documentHandler;
+ 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;
+
+ /**
+ * Maps XSL-FO element IDs to their on-page XY-positions
+ * Must be used in conjunction with the page reference to fully specify the details
+ * of a "go-to" action.
+ */
+ private Map idPositions = new java.util.HashMap();
+
+ /**
+ * Maps XSL-FO element IDs to "go-to" actions targeting the corresponding areas
+ * These objects may not all be fully filled in yet
+ */
+ private Map idGoTos = new java.util.HashMap();
+
+ /**
+ * The "go-to" actions in idGoTos that are not complete yet
+ */
+ private List unfinishedGoTos = new java.util.ArrayList();
+ // can't use a Set because PDFGoTo.equals returns true if the target is the same,
+ // even if the object number differs
+
+ private BookmarkTree bookmarkTree;
+
+ private TextUtil textUtil = new TextUtil();
+
+ /**
+ * Main constructor
+ */
+ public IFRenderer() {
+ }
+
+ /** {@inheritDoc} */
+ public String getMimeType() {
+ return IF_MIME_TYPE;
+ }
+
+ /**
+ * Sets the {@code IFDocumentHandler} to be used by the {@code IFRenderer}.
+ * @param documentHandler the {@code IFDocumentHandler}
+ */
+ public void setDocumentHandler(IFDocumentHandler documentHandler) {
+ this.documentHandler = documentHandler;
+ }
+
+ /** {@inheritDoc} */
+ public void setupFontInfo(FontInfo inFontInfo) throws FOPException {
+ if (this.documentHandler == null) {
+ this.documentHandler = createDefaultDocumentHandler();
+ }
+ IFDocumentHandlerConfigurator configurator = this.documentHandler.getConfigurator();
+ if (configurator != null) {
+ configurator.setupFontInfo(documentHandler, inFontInfo);
+ } else {
+ this.documentHandler.setDefaultFontInfo(inFontInfo);
+ }
+ this.fontInfo = 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 IFDocumentHandler} when none has been set.
+ * @return the default IFDocumentHandler
+ */
+ protected IFDocumentHandler createDefaultDocumentHandler() {
+ IFSerializer serializer = new IFSerializer();
+ serializer.setUserAgent(getUserAgent());
+ return serializer;
+ }
+
+ /** {@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.documentHandler == null) {
+ this.documentHandler = createDefaultDocumentHandler();
+ }
+ this.documentHandler.setResult(result);
+ }
+ super.startRenderer(null);
+ if (log.isDebugEnabled()) {
+ log.debug("Rendering areas via IF document handler ("
+ + this.documentHandler.getClass().getName() + ")...");
+ }
+ documentHandler.startDocument();
+ documentHandler.startDocumentHeader();
+ } catch (IFException e) {
+ handleIFExceptionWithIOException(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void stopRenderer() throws IOException {
+ try {
+ if (this.inPageSequence) {
+ documentHandler.endPageSequence();
+ this.inPageSequence = false;
+ }
+ documentHandler.startDocumentTrailer();
+ finishOpenGoTos();
+ if (this.bookmarkTree != null) {
+ documentHandler.handleExtensionObject(this.bookmarkTree);
+ }
+ documentHandler.endDocumentTrailer();
+ documentHandler.endDocument();
+ } catch (IFException e) {
+ handleIFExceptionWithIOException(e);
+ }
+ idPositions.clear();
+ idGoTos.clear();
+ super.stopRenderer();
+ log.debug("Rendering finished.");
+ }
+
+ /** {@inheritDoc} */
+ public void processOffDocumentItem(OffDocumentItem odi) {
+ if (odi instanceof DestinationData) {
+ // render Destinations
+ renderDestination((DestinationData) odi);
+ } else if (odi instanceof BookmarkData) {
+ // render Bookmark-Tree
+ renderBookmarkTree((BookmarkData) odi);
+ } else if (odi instanceof OffDocumentExtensionAttachment) {
+ ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
+ if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
+ renderXMPMetadata((XMPMetadata)attachment);
+ }
+ }
+ }
+
+ private void renderDestination(DestinationData dd) {
+ String targetID = dd.getIDRef();
+ if (targetID == null || targetID.length() == 0) {
+ throw new IllegalArgumentException("DestinationData must contain a ID reference");
+ }
+ PageViewport pv = dd.getPageViewport();
+ if (pv != null) {
+ GoToXYAction action = getGoToActionForID(targetID, pv.getPageIndex());
+ NamedDestination namedDestination = new NamedDestination(targetID, action);
+ try {
+ documentHandler.handleExtensionObject(namedDestination);
+ } catch (IFException ife) {
+ handleIFException(ife);
+ }
+ } else {
+ //Warning already issued by AreaTreeHandler (debug level is sufficient)
+ log.debug("Unresolved destination item received: " + dd.getIDRef());
+ }
+ }
+
+ /**
+ * Renders a Bookmark-Tree object
+ * @param bookmarks the BookmarkData object containing all the Bookmark-Items
+ */
+ protected void renderBookmarkTree(BookmarkData bookmarks) {
+ assert this.bookmarkTree == null;
+ this.bookmarkTree = new BookmarkTree();
+ for (int i = 0; i < bookmarks.getCount(); i++) {
+ BookmarkData ext = bookmarks.getSubData(i);
+ Bookmark b = renderBookmarkItem(ext);
+ bookmarkTree.addBookmark(b);
+ }
+ }
+
+ private Bookmark renderBookmarkItem(BookmarkData bookmarkItem) {
+
+ String targetID = bookmarkItem.getIDRef();
+ if (targetID == null || targetID.length() == 0) {
+ throw new IllegalArgumentException("DestinationData must contain a ID reference");
+ }
+ GoToXYAction action = null;
+ PageViewport pv = bookmarkItem.getPageViewport();
+
+ if (pv != null) {
+ action = getGoToActionForID(targetID, pv.getPageIndex());
+ } else {
+ //Warning already issued by AreaTreeHandler (debug level is sufficient)
+ log.debug("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
+ }
+
+ Bookmark b = new Bookmark(
+ bookmarkItem.getBookmarkTitle(),
+ bookmarkItem.showChildItems(),
+ action);
+ for (int i = 0; i < bookmarkItem.getCount(); i++) {
+ b.addChildBookmark(renderBookmarkItem(bookmarkItem.getSubData(i)));
+ }
+ return b;
+ }
+
+ private void renderXMPMetadata(XMPMetadata metadata) {
+ this.documentMetadata = metadata.getMetadata();
+ }
+
+ private GoToXYAction getGoToActionForID(String targetID, int pageIndex) {
+ // Already a PDFGoTo present for this target? If not, create.
+ GoToXYAction action = (GoToXYAction)idGoTos.get(targetID);
+ if (action == null) {
+ //String pdfPageRef = (String) pageReferences.get(pvKey);
+ Point position = (Point)idPositions.get(targetID);
+ // can the GoTo already be fully filled in?
+ if (/*pdfPageRef != null &&*/ position != null) {
+ // getPDFGoTo shares PDFGoTo objects as much as possible.
+ // It also takes care of assignObjectNumber and addTrailerObject.
+ //action = pdfDoc.getFactory().getPDFGoTo(pdfPageRef, position);
+ action = new GoToXYAction(pageIndex, position);
+ } else {
+ // Not complete yet, can't use getPDFGoTo:
+ action = new GoToXYAction(pageIndex, null);
+ unfinishedGoTos.add(action);
+ }
+ idGoTos.put(targetID, action);
+ }
+ return action;
+ }
+
+ private void finishOpenGoTos() {
+ int count = unfinishedGoTos.size();
+ if (count > 0) {
+ Point defaultPos = new Point(0, 0); // top-o-page
+ while (!unfinishedGoTos.isEmpty()) {
+ GoToXYAction action = (GoToXYAction)unfinishedGoTos.get(0);
+ noteGoToPosition(action, defaultPos);
+ }
+ PDFEventProducer eventProducer = PDFEventProducer.Provider.get(
+ getUserAgent().getEventBroadcaster());
+ eventProducer.nonFullyResolvedLinkTargets(this, count);
+ // dysfunctional if pageref is null
+ }
+ }
+
+ private void noteGoToPosition(GoToXYAction action, Point position) {
+ action.setTargetLocation(position);
+ unfinishedGoTos.remove(action);
+ }
+
+ private void noteGoToPosition(GoToXYAction action, PageViewport pv, Point position) {
+ noteGoToPosition(action, position);
+ }
+
+ private void saveAbsolutePosition(String id, PageViewport pv,
+ int relativeIPP, int relativeBPP, AffineTransform tf) {
+ Point position = new Point(relativeIPP, relativeBPP);
+ tf.transform(position, position);
+ idPositions.put(id, position);
+ // is there already a PDFGoTo waiting to be completed?
+ GoToXYAction action = (GoToXYAction)idGoTos.get(id);
+ if (action != null) {
+ noteGoToPosition(action, pv, position);
+ }
+ }
+
+ private void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) {
+ saveAbsolutePosition(id, this.currentPageViewport,
+ relativeIPP, relativeBPP, graphicContext.getTransform());
+ }
+
+ protected void saveBlockPosIfTargetable(Block block) {
+ String id = getTargetableID(block);
+ if (id != null) {
+ // FIXME: Like elsewhere in the renderer code, absolute and relative
+ // directions are happily mixed here. This makes sure that the
+ // links point to the right location, but it is not correct.
+ int ipp = block.getXOffset();
+ int bpp = block.getYOffset() + block.getSpaceBefore();
+ int positioning = block.getPositioning();
+ if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
+ ipp += currentIPPosition;
+ bpp += currentBPPosition;
+ }
+ saveAbsolutePosition(id, currentPageViewport, ipp, bpp, graphicContext.getTransform());
+ }
+ }
+
+ private void saveInlinePosIfTargetable(InlineArea inlineArea) {
+ String id = getTargetableID(inlineArea);
+ if (id != null) {
+ int extraMarginBefore = 5000; // millipoints
+ int ipp = currentIPPosition;
+ int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
+ saveAbsolutePosition(id, ipp, bpp);
+ }
+ }
+
+ private String getTargetableID(Area area) {
+ String id = (String) area.getTrait(Trait.PROD_ID);
+ if (id == null || id.length() == 0
+ || !currentPageViewport.isFirstWithID(id)
+ || idPositions.containsKey(id)) {
+ return null;
+ } else {
+ return id;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void startPageSequence(PageSequence pageSequence) {
+ try {
+ if (this.inPageSequence) {
+ documentHandler.endPageSequence();
+ } else {
+ if (this.documentMetadata == null) {
+ this.documentMetadata = createDefaultDocumentMetadata();
+ }
+ documentHandler.handleExtensionObject(this.documentMetadata);
+ documentHandler.endDocumentHeader();
+ this.inPageSequence = true;
+ }
+ documentHandler.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()));
+ documentHandler.startPage(page.getPageIndex(), page.getPageNumberString(), dim);
+ documentHandler.startPageHeader();
+ //TODO Handle page header
+ documentHandler.endPageHeader();
+ this.painter = documentHandler.startPageContent();
+ super.renderPage(page);
+ this.painter = null;
+ documentHandler.endPageContent();
+ documentHandler.startPageTrailer();
+ //TODO Handle page trailer
+ documentHandler.endPageTrailer();
+ documentHandler.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), false);
+ }
+ }
+
+ private void concatenateTransformationMatrixMpt(AffineTransform at, boolean force) {
+ if (force || !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, false);
+
+ //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, false);
+
+ //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();
+ }
+
+ /** {@inheritDoc} */
+ protected void renderInlineArea(InlineArea inlineArea) {
+ saveInlinePosIfTargetable(inlineArea);
+ super.renderInlineArea(inlineArea);
+ }
+
+ /** {@inheritDoc} */
+ protected void renderBlock(Block block) {
+ if (log.isTraceEnabled()) {
+ log.trace("renderBlock() " + block);
+ }
+ saveBlockPosIfTargetable(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);
+ }
+
+ int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
+ int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
+ textUtil.flush();
+ textUtil.setStartPosition(rx, bl);
+ super.renderText(text);
+
+ textUtil.flush();
+ 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();
+ if (tws != 0) {
+ float fontSize = font.getFontSize() / 1000f;
+ textUtil.adjust(Math.round(tws / fontSize * 10));
+ }
+ }
+ 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) {
+ float fontSize = font.getFontSize() / 1000f;
+
+ int l = s.length();
+
+ for (int i = 0; i < l; i++) {
+ char ch = s.charAt(i);
+ textUtil.addChar(ch);
+ float glyphAdjust = 0;
+ if (font.hasChar(ch)) {
+ int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0);
+ glyphAdjust += tls;
+ }
+ if (letterAdjust != null && i < l) {
+ glyphAdjust += letterAdjust[i];
+ }
+
+ float adjust = glyphAdjust / fontSize;
+
+ textUtil.adjust(Math.round(adjust * 10));
+ }
+ }
+
+ private class TextUtil {
+ private static final int INITIAL_BUFFER_SIZE = 16;
+ private int[] dx = new int[INITIAL_BUFFER_SIZE];
+ private boolean hasDX = false;
+ private StringBuffer text = new StringBuffer();
+ private int startx, starty;
+
+ void addChar(char ch) {
+ text.append(ch);
+ }
+
+ void adjust(int adjust) {
+ if (adjust != 0) {
+ int idx = text.length();
+ if (idx > dx.length - 1) {
+ int newSize = Math.max(dx.length, idx + 1) + INITIAL_BUFFER_SIZE;
+ int[] newDX = new int[newSize];
+ System.arraycopy(dx, 0, newDX, 0, dx.length);
+ dx = newDX;
+ }
+ dx[idx] += adjust;
+ hasDX = true;
+ }
+ }
+
+ void reset() {
+ if (text.length() > 0) {
+ text.setLength(0);
+ Arrays.fill(dx, 0);
+ hasDX = false;
+ }
+ }
+
+ void setStartPosition(int x, int y) {
+ this.startx = x;
+ this.starty = y;
+ }
+
+ void flush() {
+ if (text.length() > 0) {
+ try {
+ painter.drawText(startx, starty, (hasDX ? dx : null), null, text.toString());
+ } catch (IFException e) {
+ handleIFException(e);
+ }
+ reset();
+ }
+ }
+ }
+
+ /** {@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} */
+ public void renderLeader(Leader area) {
+ renderInlineAreaBackAndBorders(area);
+
+ int style = area.getRuleStyle();
+ int ruleThickness = area.getRuleThickness();
+ int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
+ int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2);
+ int endx = currentIPPosition
+ + area.getBorderAndPaddingWidthStart()
+ + area.getIPD();
+ Color col = (Color)area.getTrait(Trait.COLOR);
+
+ Point start = new Point(startx, starty);
+ Point end = new Point(endx, starty);
+ try {
+ painter.drawLine(start, end, ruleThickness, col, RuleStyle.valueOf(style));
+ } catch (IFException ife) {
+ handleIFException(ife);
+ }
+
+ super.renderLeader(area);
+ }
+
+ /** {@inheritDoc} */
+ protected void clip() {
+ throw new IllegalStateException("Not used");
+ }
+
+ /** {@inheritDoc} */
+ protected void clipRect(float x, float y, float width, float height) {
+ pushGroup(new IFGraphicContext.Group());
+ try {
+ painter.clipRect(toMillipointRectangle(x, y, width, height));
+ } catch (IFException ife) {
+ handleIFException(ife);
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected void closePath() {
+ throw new IllegalStateException("Not used");
+ }
+
+ /** {@inheritDoc} */
+ protected void drawBorders(float startx, float starty,
+ float width, float height,
+ BorderProps bpsBefore, BorderProps bpsAfter,
+ BorderProps bpsStart, BorderProps bpsEnd) {
+ Rectangle rect = toMillipointRectangle(startx, starty, width, height);
+ try {
+ painter.drawBorderRect(rect, bpsBefore, bpsAfter, bpsStart, bpsEnd);
+ } catch (IFException ife) {
+ handleIFException(ife);
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected void drawBorderLine(float x1, float y1, float x2, float y2, boolean horz,
+ boolean startOrBefore, int style, Color col) {
+ //Simplified implementation that is only used by renderTextDecoration()
+ //drawBorders() is overridden and uses the Painter's high-level method drawBorderRect()
+ updateColor(col, true);
+ fillRect(x1, y1, x2 - x1, y2 - y1);
+ }
+
+ private int toMillipoints(float coordinate) {
+ return Math.round(coordinate * 1000);
+ }
+
+ private Rectangle toMillipointRectangle(float x, float y, float width, float height) {
+ return new Rectangle(
+ toMillipoints(x),
+ toMillipoints(y),
+ toMillipoints(width),
+ toMillipoints(height));
+ }
+
+ /** {@inheritDoc} */
+ protected void fillRect(float x, float y, float width, float height) {
+ try {
+ painter.fillRect(
+ toMillipointRectangle(x, y, width, height),
+ this.graphicContext.getPaint());
+ } catch (IFException e) {
+ handleIFException(e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected void moveTo(float x, float y) {
+ throw new IllegalStateException("Not used");
+ }
+
+ /** {@inheritDoc} */
+ protected void lineTo(float x, float y) {
+ throw new IllegalStateException("Not used");
+ }
+
+ /** {@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..95d1f20fe
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java
@@ -0,0 +1,555 @@
+/*
+ * 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.Point;
+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.fonts.FontInfo;
+import org.apache.fop.render.RenderingContext;
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+import org.apache.fop.util.ColorUtil;
+import org.apache.fop.util.DOM2SAX;
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * IFPainter implementation that serializes the intermediate format to XML.
+ */
+public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements IFConstants, IFPainter {
+
+ private IFDocumentHandler mimicHandler;
+
+ /**
+ * 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 IFDocumentHandlerConfigurator getConfigurator() {
+ if (this.mimicHandler != null) {
+ return getMimickedDocumentHandler().getConfigurator();
+ } else {
+ return new IFSerializerConfiguration(getUserAgent());
+ }
+ }
+
+ public void mimicDocumentHandler(IFDocumentHandler targetHandler) {
+ this.mimicHandler = targetHandler;
+ }
+
+ public IFDocumentHandler getMimickedDocumentHandler() {
+ return this.mimicHandler;
+ }
+
+ /** {@inheritDoc} */
+ public FontInfo getFontInfo() {
+ if (this.mimicHandler != null) {
+ return this.mimicHandler.getFontInfo();
+ } else {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void setFontInfo(FontInfo fontInfo) {
+ //nop, not used
+ }
+
+ /** {@inheritDoc} */
+ public void setDefaultFontInfo(FontInfo fontInfo) {
+ //nop, not used
+ }
+
+ /** {@inheritDoc} */
+ public void startDocument() throws IFException {
+ try {
+ handler.startDocument();
+ handler.startPrefixMapping("", NAMESPACE);
+ handler.startPrefixMapping(XLINK_PREFIX, XLINK_NAMESPACE);
+ handler.startElement(EL_DOCUMENT);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startDocument()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void startDocumentHeader() throws IFException {
+ try {
+ handler.startElement(EL_HEADER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startDocumentHeader()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endDocumentHeader() throws IFException {
+ try {
+ handler.endElement(EL_HEADER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startDocumentHeader()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void startDocumentTrailer() throws IFException {
+ try {
+ handler.startElement(EL_TRAILER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startDocumentTrailer()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endDocumentTrailer() throws IFException {
+ try {
+ handler.endElement(EL_TRAILER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in endDocumentTrailer()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endDocument() throws IFException {
+ try {
+ handler.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", XMLUtil.CDATA, id);
+ }
+ handler.startElement(EL_PAGE_SEQUENCE, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startPageSequence()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endPageSequence() throws IFException {
+ try {
+ handler.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();
+ addAttribute(atts, "index", Integer.toString(index));
+ addAttribute(atts, "name", name);
+ addAttribute(atts, "width", Integer.toString(size.width));
+ addAttribute(atts, "height", Integer.toString(size.height));
+ handler.startElement(EL_PAGE, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startPage()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void startPageHeader() throws IFException {
+ try {
+ handler.startElement(EL_PAGE_HEADER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startPageHeader()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endPageHeader() throws IFException {
+ try {
+ handler.endElement(EL_PAGE_HEADER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in endPageHeader()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public IFPainter startPageContent() throws IFException {
+ try {
+ handler.startElement(EL_PAGE_CONTENT);
+ return this;
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startPageContent()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endPageContent() throws IFException {
+ try {
+ handler.endElement(EL_PAGE_CONTENT);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in endPageContent()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void startPageTrailer() throws IFException {
+ try {
+ handler.startElement(EL_PAGE_TRAILER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startPageTrailer()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endPageTrailer() throws IFException {
+ try {
+ handler.endElement(EL_PAGE_TRAILER);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in endPageTrailer()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endPage() throws IFException {
+ try {
+ handler.endElement(EL_PAGE);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in endPage()", e);
+ }
+ }
+
+ //---=== IFPainter ===---
+
+ /** {@inheritDoc} */
+ public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
+ throws IFException {
+ startViewport(IFUtil.toString(transform), size, clipRect);
+ }
+
+ /** {@inheritDoc} */
+ public void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect)
+ throws IFException {
+ startViewport(IFUtil.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) {
+ addAttribute(atts, "transform", transform);
+ }
+ addAttribute(atts, "width", Integer.toString(size.width));
+ addAttribute(atts, "height", Integer.toString(size.height));
+ if (clipRect != null) {
+ addAttribute(atts, "clip-rect", IFUtil.toString(clipRect));
+ }
+ handler.startElement(EL_VIEWPORT, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startViewport()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endViewport() throws IFException {
+ try {
+ handler.endElement(EL_VIEWPORT);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in endViewport()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void startGroup(AffineTransform[] transforms) throws IFException {
+ startGroup(IFUtil.toString(transforms));
+ }
+
+ /** {@inheritDoc} */
+ public void startGroup(AffineTransform transform) throws IFException {
+ startGroup(IFUtil.toString(transform));
+ }
+
+ private void startGroup(String transform) throws IFException {
+ try {
+ AttributesImpl atts = new AttributesImpl();
+ if (transform != null && transform.length() > 0) {
+ addAttribute(atts, "transform", transform);
+ }
+ handler.startElement(EL_GROUP, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startGroup()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endGroup() throws IFException {
+ try {
+ handler.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);
+ addAttribute(atts, "x", Integer.toString(rect.x));
+ addAttribute(atts, "y", Integer.toString(rect.y));
+ addAttribute(atts, "width", Integer.toString(rect.width));
+ addAttribute(atts, "height", 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());
+ }
+ }
+ handler.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();
+ addAttribute(atts, "x", Integer.toString(rect.x));
+ addAttribute(atts, "y", Integer.toString(rect.y));
+ addAttribute(atts, "width", Integer.toString(rect.width));
+ addAttribute(atts, "height", 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());
+ }
+ }
+ handler.startElement(EL_IMAGE, atts);
+ new DOM2SAX(handler).writeDocument(doc, true);
+ handler.endElement(EL_IMAGE);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in startGroup()", e);
+ }
+ }
+
+ 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 clipRect(Rectangle rect) throws IFException {
+ try {
+ AttributesImpl atts = new AttributesImpl();
+ addAttribute(atts, "x", Integer.toString(rect.x));
+ addAttribute(atts, "y", Integer.toString(rect.y));
+ addAttribute(atts, "width", Integer.toString(rect.width));
+ addAttribute(atts, "height", Integer.toString(rect.height));
+ handler.element(EL_CLIP_RECT, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in clipRect()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void fillRect(Rectangle rect, Paint fill) throws IFException {
+ if (fill == null) {
+ return;
+ }
+ try {
+ AttributesImpl atts = new AttributesImpl();
+ addAttribute(atts, "x", Integer.toString(rect.x));
+ addAttribute(atts, "y", Integer.toString(rect.y));
+ addAttribute(atts, "width", Integer.toString(rect.width));
+ addAttribute(atts, "height", Integer.toString(rect.height));
+ addAttribute(atts, "fill", toString(fill));
+ handler.element(EL_RECT, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in fillRect()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after,
+ BorderProps start, BorderProps end) throws IFException {
+ if (before == null && after == null && start == null && end == null) {
+ return;
+ }
+ try {
+ AttributesImpl atts = new AttributesImpl();
+ addAttribute(atts, "x", Integer.toString(rect.x));
+ addAttribute(atts, "y", Integer.toString(rect.y));
+ addAttribute(atts, "width", Integer.toString(rect.width));
+ addAttribute(atts, "height", Integer.toString(rect.height));
+ if (before != null) {
+ addAttribute(atts, "before", before.toString());
+ }
+ if (after != null) {
+ addAttribute(atts, "after", after.toString());
+ }
+ if (start != null) {
+ addAttribute(atts, "start", start.toString());
+ }
+ if (end != null) {
+ addAttribute(atts, "end", end.toString());
+ }
+ handler.element(EL_BORDER_RECT, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in drawBorderRect()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
+ throws IFException {
+ try {
+ AttributesImpl atts = new AttributesImpl();
+ addAttribute(atts, "x1", Integer.toString(start.x));
+ addAttribute(atts, "y1", Integer.toString(start.y));
+ addAttribute(atts, "x2", Integer.toString(end.x));
+ addAttribute(atts, "y2", Integer.toString(end.y));
+ addAttribute(atts, "stroke-width", Integer.toString(width));
+ addAttribute(atts, "color", Integer.toString(width));
+ addAttribute(atts, "style", style.getName());
+ handler.element(EL_LINE, atts);
+ } catch (SAXException e) {
+ throw new IFException("SAX error in drawLine()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
+ try {
+ AttributesImpl atts = new AttributesImpl();
+ addAttribute(atts, "x", Integer.toString(x));
+ addAttribute(atts, "y", Integer.toString(y));
+ if (dx != null) {
+ addAttribute(atts, "dx", IFUtil.toString(dx));
+ }
+ if (dy != null) {
+ addAttribute(atts, "dy", IFUtil.toString(dy));
+ }
+ handler.startElement(EL_TEXT, atts);
+ char[] chars = text.toCharArray();
+ handler.characters(chars, 0, chars.length);
+ handler.endElement(EL_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) {
+ addAttribute(atts, "family", family);
+ }
+ if (style != null) {
+ addAttribute(atts, "style", style);
+ }
+ if (weight != null) {
+ addAttribute(atts, "weight", weight.toString());
+ }
+ if (variant != null) {
+ addAttribute(atts, "variant", variant);
+ }
+ if (size != null) {
+ addAttribute(atts, "size", size.toString());
+ }
+ if (color != null) {
+ addAttribute(atts, "color", toString(color));
+ }
+ handler.element(EL_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 + " (" + extension.getClass().getName() + ")");
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected RenderingContext createRenderingContext() {
+ throw new IllegalStateException("Should never be called!");
+ }
+
+ private void addAttribute(AttributesImpl atts,
+ org.apache.xmlgraphics.util.QName attribute, String value) {
+ XMLUtil.addAttribute(atts, attribute, value);
+ }
+
+ private void addAttribute(AttributesImpl atts, String localName, String value) {
+ XMLUtil.addAttribute(atts, localName, value);
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializerConfiguration.java b/src/java/org/apache/fop/render/intermediate/IFSerializerConfiguration.java
new file mode 100644
index 000000000..8f47c5511
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFSerializerConfiguration.java
@@ -0,0 +1,80 @@
+/*
+ * 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.util.List;
+
+import org.apache.avalon.framework.configuration.Configuration;
+
+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.FontEventListener;
+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.render.DefaultFontResolver;
+import org.apache.fop.render.PrintRendererConfigurator;
+
+/**
+ * Configurator for the IFSerializer.
+ */
+public class IFSerializerConfiguration extends PrintRendererConfigurator
+ implements IFDocumentHandlerConfigurator {
+
+ /**
+ * Default constructor
+ * @param userAgent user agent
+ */
+ public IFSerializerConfiguration(FOUserAgent userAgent) {
+ super(userAgent);
+ }
+
+ /** {@inheritDoc} */
+ public void configure(IFDocumentHandler documentHandler) throws FOPException {
+ //nothing to do here
+ }
+
+ /** {@inheritDoc} */
+ public void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo)
+ throws FOPException {
+ FontManager fontManager = userAgent.getFactory().getFontManager();
+ List fontCollections = new java.util.ArrayList();
+ fontCollections.add(new Base14FontCollection(fontManager.isBase14KerningEnabled()));
+
+ Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
+ if (cfg != null) {
+ FontResolver fontResolver = new DefaultFontResolver(userAgent);
+ FontEventListener listener = new FontEventAdapter(
+ userAgent.getEventBroadcaster());
+ List fontList = buildFontList(cfg, fontResolver, listener);
+ fontCollections.add(new CustomFontCollection(fontResolver, fontList));
+ }
+
+ fontManager.setup(fontInfo,
+ (FontCollection[])fontCollections.toArray(
+ new FontCollection[fontCollections.size()]));
+ documentHandler.setFontInfo(fontInfo);
+ }
+
+}
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/intermediate/IFUtil.java b/src/java/org/apache/fop/render/intermediate/IFUtil.java
new file mode 100644
index 000000000..513e1c786
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/IFUtil.java
@@ -0,0 +1,140 @@
+/*
+ * 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 org.apache.fop.util.DecimalFormatCache;
+
+/**
+ * Utility functions for the intermediate format.
+ */
+public class IFUtil {
+
+ 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();
+ }
+
+ /**
+ * Converts an array of integer coordinates into a space-separated string.
+ * @param coordinates the coordinates
+ * @return the space-separated array of coordinates
+ */
+ public static 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
+ */
+ public static 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/extensions/AbstractAction.java b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java
new file mode 100644
index 000000000..37a3032cd
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java
@@ -0,0 +1,29 @@
+/*
+ * 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.extensions;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+/**
+ * Abstract base class for document actions, like "go-to" actions with absolute page coordinates.
+ */
+public abstract class AbstractAction implements XMLizable {
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java b/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java
new file mode 100644
index 000000000..446da6ef9
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/Bookmark.java
@@ -0,0 +1,133 @@
+/*
+ * 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.extensions;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * This class is a bookmark element for use in the intermediate format.
+ */
+public class Bookmark implements XMLizable, DocumentNavigationExtensionConstants {
+
+ private String title;
+ private boolean show;
+ private List childBookmarks;
+ private AbstractAction action;
+
+ /**
+ * Creates a new bookmark.
+ * @param title the bookmark's title
+ * @param show true if the bookmark shall be shown, false for hidden
+ * @param action the action performed when the bookmark is clicked
+ */
+ public Bookmark(String title, boolean show, AbstractAction action) {
+ this.title = title;
+ this.show = show;
+ this.action = action;
+ }
+
+ /**
+ * Returns the bookmark's title.
+ * @return the title
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Indicates whether the bookmark shall be shown initially.
+ * @return true if it shall be shown
+ */
+ public boolean isShown() {
+ return this.show;
+ }
+
+ /**
+ * Returns the action performed when the bookmark is clicked.
+ * @return the action
+ */
+ public AbstractAction getAction() {
+ return this.action;
+ }
+
+ /**
+ * Sets the action performed when the bookmark is clicked.
+ * @param action the action
+ */
+ public void setAction(AbstractAction action) {
+ this.action = action;
+ }
+
+ /**
+ * Adds a child bookmark.
+ * @param bookmark the child bookmark
+ */
+ public void addChildBookmark(Bookmark bookmark) {
+ if (this.childBookmarks == null) {
+ this.childBookmarks = new java.util.ArrayList();
+ }
+ this.childBookmarks.add(bookmark);
+ }
+
+ /**
+ * Returns a list of child bookmarks.
+ * @return the child bookmarks
+ */
+ public List getChildBookmarks() {
+ if (this.childBookmarks == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections.unmodifiableList(this.childBookmarks);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ atts.addAttribute(null, "title", "title", XMLUtil.CDATA, getTitle());
+ atts.addAttribute(null, "starting-state", "starting-state",
+ XMLUtil.CDATA, isShown() ? "show" : "hide");
+ handler.startElement(BOOKMARK.getNamespaceURI(),
+ BOOKMARK.getLocalName(), BOOKMARK.getQName(), atts);
+ if (getAction() != null) {
+ getAction().toSAX(handler);
+ }
+ if (this.childBookmarks != null) {
+ Iterator iter = this.childBookmarks.iterator();
+ while (iter.hasNext()) {
+ Bookmark b = (Bookmark)iter.next();
+ b.toSAX(handler);
+ }
+ }
+ handler.endElement(BOOKMARK.getNamespaceURI(),
+ BOOKMARK.getLocalName(), BOOKMARK.getQName());
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/BookmarkTree.java b/src/java/org/apache/fop/render/intermediate/extensions/BookmarkTree.java
new file mode 100644
index 000000000..77e9726b3
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/BookmarkTree.java
@@ -0,0 +1,76 @@
+/*
+ * 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.extensions;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+/**
+ * This class is the root of the bookmark tree for use in the intermediate format.
+ */
+public class BookmarkTree implements XMLizable, DocumentNavigationExtensionConstants {
+
+ private List bookmarks = new java.util.ArrayList();
+
+ /**
+ * Constructs a new bookmark tree.
+ */
+ public BookmarkTree() {
+ //nop
+ }
+
+ /**
+ * Adds a new top-level bookmark.
+ * @param bookmark the bookmark
+ */
+ public void addBookmark(Bookmark bookmark) {
+ this.bookmarks.add(bookmark);
+ }
+
+ /**
+ * Returns a list of top-level bookmarks.
+ * @return the top-level bookmarks
+ */
+ public List getBookmarks() {
+ return Collections.unmodifiableList(this.bookmarks);
+ }
+
+ /** {@inheritDoc} */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ handler.startElement(BOOKMARK_TREE.getNamespaceURI(),
+ BOOKMARK_TREE.getLocalName(), BOOKMARK_TREE.getQName(), atts);
+ Iterator iter = this.bookmarks.iterator();
+ while (iter.hasNext()) {
+ Bookmark b = (Bookmark)iter.next();
+ b.toSAX(handler);
+ }
+ handler.endElement(BOOKMARK_TREE.getNamespaceURI(),
+ BOOKMARK_TREE.getLocalName(), BOOKMARK_TREE.getQName());
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java
new file mode 100644
index 000000000..a8f458f97
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionConstants.java
@@ -0,0 +1,47 @@
+/*
+ * 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.extensions;
+
+import org.apache.xmlgraphics.util.QName;
+
+import org.apache.fop.render.intermediate.IFConstants;
+
+/**
+ * Constants for the IF document-level navigation extension.
+ */
+public interface DocumentNavigationExtensionConstants {
+
+ /** Namespace URI for the bookmark extension */
+ String NAMESPACE = IFConstants.NAMESPACE + "/document-navigation";
+
+ /** the bookmark-tree element */
+ QName BOOKMARK_TREE = new QName(NAMESPACE, "bookmark-tree");
+ /** the bookmark element */
+ QName BOOKMARK = new QName(NAMESPACE, "bookmark");
+
+ /** the named-destination element */
+ QName NAMED_DESTINATION = new QName(NAMESPACE, "named-destination");
+
+ /** the goto-xy element */
+ QName GOTO_XY = new QName(NAMESPACE, "goto-xy");
+ /** the goto-uri element */
+ QName GOTO_URI = new QName(NAMESPACE, "goto-uri");
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java
new file mode 100644
index 000000000..2822ff2ad
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationExtensionHandlerFactory.java
@@ -0,0 +1,173 @@
+/*
+ * 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.extensions;
+
+import java.awt.Point;
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.render.ps.extensions.PSExtensionAttachment;
+import org.apache.fop.util.ContentHandlerFactory;
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * Factory for the ContentHandler that handles the IF document navigation namespace.
+ */
+public class DocumentNavigationExtensionHandlerFactory
+ implements ContentHandlerFactory, DocumentNavigationExtensionConstants {
+
+ /** Logger instance */
+ protected static Log log = LogFactory.getLog(DocumentNavigationExtensionHandlerFactory.class);
+
+ /** {@inheritDoc} */
+ public String[] getSupportedNamespaces() {
+ return new String[] {NAMESPACE};
+ }
+
+ /** {@inheritDoc} */
+ public ContentHandler createContentHandler() {
+ return new Handler();
+ }
+
+ private static class Handler extends DefaultHandler
+ implements ContentHandlerFactory.ObjectSource {
+
+ private StringBuffer content = new StringBuffer();
+ private Stack objectStack = new Stack();
+
+ private Object objectBuilt;
+ private ObjectBuiltListener listener;
+
+ /** {@inheritDoc} */
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ boolean handled = false;
+ if (NAMESPACE.equals(uri)) {
+ if (BOOKMARK_TREE.getLocalName().equals(localName)) {
+ if (!objectStack.isEmpty()) {
+ throw new SAXException(localName + " must be the root element!");
+ }
+ BookmarkTree bookmarkTree = new BookmarkTree();
+ objectStack.push(bookmarkTree);
+ } else if (BOOKMARK.getLocalName().equals(localName)) {
+ String title = attributes.getValue("title");
+ String s = attributes.getValue("starting-state");
+ boolean show = !"hide".equals(s);
+ Bookmark b = new Bookmark(title, show, null);
+ Object o = objectStack.peek();
+ if (o instanceof AbstractAction) {
+ AbstractAction action = (AbstractAction)objectStack.pop();
+ o = objectStack.peek();
+ ((Bookmark)o).setAction(action);
+ }
+ if (o instanceof BookmarkTree) {
+ ((BookmarkTree)o).addBookmark(b);
+ } else {
+ ((Bookmark)o).addChildBookmark(b);
+ }
+ objectStack.push(b);
+ } else if (NAMED_DESTINATION.getLocalName().equals(localName)) {
+ if (!objectStack.isEmpty()) {
+ throw new SAXException(localName + " must be the root element!");
+ }
+ String name = attributes.getValue("name");
+ NamedDestination dest = new NamedDestination(name, null);
+ objectStack.push(dest);
+ } else if (GOTO_XY.getLocalName().equals(localName)) {
+ int pageIndex = XMLUtil.getAttributeAsInt(attributes, "page-index");
+ int x = XMLUtil.getAttributeAsInt(attributes, "x");
+ int y = XMLUtil.getAttributeAsInt(attributes, "y");
+ GoToXYAction action = new GoToXYAction(pageIndex, new Point(x, y));
+ objectStack.push(action);
+ } else if (GOTO_URI.getLocalName().equals(localName)) {
+ String gotoURI = attributes.getValue("uri");
+ URIAction action = new URIAction(gotoURI);
+ objectStack.push(action);
+ } else {
+ throw new SAXException(
+ "Invalid element " + localName + " in namespace: " + uri);
+ }
+ handled = true;
+ }
+ if (!handled) {
+ if (PSExtensionAttachment.CATEGORY.equals(uri)) {
+ throw new SAXException("Unhandled element " + localName + " in namespace: "
+ + uri);
+ } else {
+ log.warn("Unhandled element " + localName + " in namespace: " + uri);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if (NAMESPACE.equals(uri)) {
+ if (BOOKMARK_TREE.getLocalName().equals(localName)) {
+ //nop
+ } else if (BOOKMARK.getLocalName().equals(localName)) {
+ if (objectStack.peek() instanceof AbstractAction) {
+ AbstractAction action = (AbstractAction)objectStack.pop();
+ Bookmark b = (Bookmark)objectStack.pop();
+ b.setAction(action);
+ } else {
+ objectStack.pop();
+ }
+ } else if (NAMED_DESTINATION.getLocalName().equals(localName)) {
+ AbstractAction action = (AbstractAction)objectStack.pop();
+ NamedDestination dest = (NamedDestination)objectStack.peek();
+ dest.setAction(action);
+ }
+ }
+ content.setLength(0); // Reset text buffer (see characters())
+ }
+
+ /** {@inheritDoc} */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ content.append(ch, start, length);
+ }
+
+ /** {@inheritDoc} */
+ public void endDocument() throws SAXException {
+ this.objectBuilt = objectStack.pop();
+ assert objectStack.isEmpty();
+ if (listener != null) {
+ listener.notifyObjectBuilt(this.objectBuilt);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object getObject() {
+ return objectBuilt;
+ }
+
+ /** {@inheritDoc} */
+ public void setObjectBuiltListener(ObjectBuiltListener listener) {
+ this.listener = listener;
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java b/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java
new file mode 100644
index 000000000..67cd5b592
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/GoToXYAction.java
@@ -0,0 +1,85 @@
+/*
+ * 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.extensions;
+
+import java.awt.Point;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * Action class which represents a "go-to" action to an absolute coordinate on a page.
+ */
+public class GoToXYAction extends AbstractAction implements DocumentNavigationExtensionConstants {
+
+ private int pageIndex;
+ private Point targetLocation;
+
+ /**
+ * Creates a new instance.
+ * @param pageIndex the page index (0-based) of the target page
+ * @param targetLocation the absolute location on the page (coordinates in millipoints)
+ */
+ public GoToXYAction(int pageIndex, Point targetLocation) {
+ this.pageIndex = pageIndex;
+ this.targetLocation = targetLocation;
+ }
+
+ /**
+ * Returns the page index of the target page.
+ * @return the page index (0-based)
+ */
+ public int getPageIndex() {
+ return this.pageIndex;
+ }
+
+ /**
+ * Returns the absolute coordinates of the target location on the page.
+ * @return the target location (coordinates in millipoints)
+ */
+ public Point getTargetLocation() {
+ return this.targetLocation;
+ }
+
+ /**
+ * Sets the absolute coordinates of the target location on the page.
+ * @param location the location (coordinates in millipoints)
+ */
+ public void setTargetLocation(Point location) {
+ this.targetLocation = location;
+ }
+
+ /** {@inheritDoc} */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ atts.addAttribute(null, "page-index", "page-index",
+ XMLUtil.CDATA, Integer.toString(pageIndex));
+ atts.addAttribute(null, "x", "x", XMLUtil.CDATA, Integer.toString(targetLocation.x));
+ atts.addAttribute(null, "y", "y", XMLUtil.CDATA, Integer.toString(targetLocation.y));
+ handler.startElement(GOTO_XY.getNamespaceURI(),
+ GOTO_XY.getLocalName(), GOTO_XY.getQName(), atts);
+ handler.endElement(GOTO_XY.getNamespaceURI(),
+ GOTO_XY.getLocalName(), GOTO_XY.getQName());
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java b/src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java
new file mode 100644
index 000000000..85b6aca19
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/NamedDestination.java
@@ -0,0 +1,85 @@
+/*
+ * 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.extensions;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.xmlgraphics.util.XMLizable;
+
+import org.apache.fop.util.XMLConstants;
+
+/**
+ * This class is a named destination element for use in the intermediate format.
+ */
+public class NamedDestination implements XMLizable, DocumentNavigationExtensionConstants {
+
+ private String name;
+ private AbstractAction action;
+
+ /**
+ * Creates a new named destination.
+ * @param name the destination's name
+ * @param action the action performed when the destination is selected
+ */
+ public NamedDestination(String name, AbstractAction action) {
+ this.name = name;
+ this.action = action;
+ }
+
+ /**
+ * Returns the destination's name.
+ * @return the name
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Returns the action performed when the destination is selected.
+ * @return the action
+ */
+ public AbstractAction getAction() {
+ return this.action;
+ }
+
+ /**
+ * Sets the action performed when the destination is selected.
+ * @param action the action
+ */
+ public void setAction(AbstractAction action) {
+ this.action = action;
+ }
+
+ /** {@inheritDoc} */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ atts.addAttribute(null, "name", "name", XMLConstants.CDATA, getName());
+ handler.startElement(NAMED_DESTINATION.getNamespaceURI(),
+ NAMED_DESTINATION.getLocalName(), NAMED_DESTINATION.getQName(), atts);
+ if (getAction() != null) {
+ getAction().toSAX(handler);
+ }
+ handler.endElement(NAMED_DESTINATION.getNamespaceURI(),
+ NAMED_DESTINATION.getLocalName(), NAMED_DESTINATION.getQName());
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java b/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java
new file mode 100644
index 000000000..e3020dac3
--- /dev/null
+++ b/src/java/org/apache/fop/render/intermediate/extensions/URIAction.java
@@ -0,0 +1,62 @@
+/*
+ * 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.extensions;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.apache.fop.util.XMLUtil;
+
+/**
+ * Action class which represents a "URI" action, i.e. an action that will call up an external
+ * resource identified by a URI.
+ */
+public class URIAction extends AbstractAction implements DocumentNavigationExtensionConstants {
+
+ private String uri;
+
+ /**
+ * Creates a new instance.
+ * @param uri the target URI
+ */
+ public URIAction(String uri) {
+ this.uri = uri;
+ }
+
+ /**
+ * Returns the target URI.
+ * @return the target URI
+ */
+ public String getURI() {
+ return this.uri;
+ }
+
+ /** {@inheritDoc} */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ atts.addAttribute(null, "uri", "uri", XMLUtil.CDATA, getURI());
+ handler.startElement(GOTO_URI.getNamespaceURI(),
+ GOTO_URI.getLocalName(), GOTO_URI.getQName(), atts);
+ handler.endElement(GOTO_URI.getNamespaceURI(),
+ GOTO_URI.getLocalName(), GOTO_URI.getQName());
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java b/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java
new file mode 100644
index 000000000..cd0cec0f8
--- /dev/null
+++ b/src/java/org/apache/fop/render/java2d/Java2DBorderPainter.java
@@ -0,0 +1,313 @@
+/*
+ * 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.java2d;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.fo.Constants;
+import org.apache.fop.render.intermediate.BorderPainter;
+import org.apache.fop.traits.RuleStyle;
+import org.apache.fop.util.ColorUtil;
+
+/**
+ * Java2D-specific implementation of the {@code BorderPainter}.
+ */
+public class Java2DBorderPainter extends BorderPainter {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(Java2DBorderPainter.class);
+
+ private Java2DPainter painter;
+
+ private GeneralPath currentPath = null;
+
+ public Java2DBorderPainter(Java2DPainter painter) {
+ this.painter = painter;
+ }
+
+ private Java2DGraphicsState getG2DState() {
+ return this.painter.g2dState;
+ }
+
+ private Graphics2D getG2D() {
+ return getG2DState().getGraph();
+ }
+
+ /** {@inheritDoc} */
+ protected void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz,
+ boolean startOrBefore, int style, Color color) {
+ float w = x2 - x1;
+ float h = y2 - y1;
+ if ((w < 0) || (h < 0)) {
+ log.error("Negative extent received. Border won't be painted.");
+ return;
+ }
+ switch (style) {
+ case Constants.EN_DASHED:
+ getG2D().setColor(color);
+ if (horz) {
+ float unit = Math.abs(2 * h);
+ int rep = (int)(w / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = w / rep;
+ float ym = y1 + (h / 2);
+ BasicStroke s = new BasicStroke(h, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0);
+ getG2D().setStroke(s);
+ getG2D().draw(new Line2D.Float(x1, ym, x2, ym));
+ } else {
+ float unit = Math.abs(2 * w);
+ int rep = (int)(h / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = h / rep;
+ float xm = x1 + (w / 2);
+ BasicStroke s = new BasicStroke(w, BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_MITER, 10.0f, new float[] {unit}, 0);
+ getG2D().setStroke(s);
+ getG2D().draw(new Line2D.Float(xm, y1, xm, y2));
+ }
+ break;
+ case Constants.EN_DOTTED:
+ getG2D().setColor(color);
+ if (horz) {
+ float unit = Math.abs(2 * h);
+ int rep = (int)(w / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = w / rep;
+ float ym = y1 + (h / 2);
+ BasicStroke s = new BasicStroke(h, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0);
+ getG2D().setStroke(s);
+ getG2D().draw(new Line2D.Float(x1, ym, x2, ym));
+ } else {
+ float unit = Math.abs(2 * w);
+ int rep = (int)(h / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = h / rep;
+ float xm = x1 + (w / 2);
+ BasicStroke s = new BasicStroke(w, BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_MITER, 10.0f, new float[] {0, unit}, 0);
+ getG2D().setStroke(s);
+ getG2D().draw(new Line2D.Float(xm, y1, xm, y2));
+ }
+ break;
+ case Constants.EN_DOUBLE:
+ getG2D().setColor(color);
+ if (horz) {
+ float h3 = h / 3;
+ float ym1 = y1 + (h3 / 2);
+ float ym2 = ym1 + h3 + h3;
+ BasicStroke s = new BasicStroke(h3);
+ getG2D().setStroke(s);
+ getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1));
+ getG2D().draw(new Line2D.Float(x1, ym2, x2, ym2));
+ } else {
+ float w3 = w / 3;
+ float xm1 = x1 + (w3 / 2);
+ float xm2 = xm1 + w3 + w3;
+ BasicStroke s = new BasicStroke(w3);
+ getG2D().setStroke(s);
+ getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2));
+ getG2D().draw(new Line2D.Float(xm2, y1, xm2, y2));
+ }
+ break;
+ case Constants.EN_GROOVE:
+ case Constants.EN_RIDGE:
+ float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f);
+ if (horz) {
+ Color uppercol = ColorUtil.lightenColor(color, -colFactor);
+ Color lowercol = ColorUtil.lightenColor(color, colFactor);
+ float h3 = h / 3;
+ float ym1 = y1 + (h3 / 2);
+ getG2D().setStroke(new BasicStroke(h3));
+ getG2D().setColor(uppercol);
+ getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1));
+ getG2D().setColor(color);
+ getG2D().draw(new Line2D.Float(x1, ym1 + h3, x2, ym1 + h3));
+ getG2D().setColor(lowercol);
+ getG2D().draw(new Line2D.Float(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3));
+ } else {
+ Color leftcol = ColorUtil.lightenColor(color, -colFactor);
+ Color rightcol = ColorUtil.lightenColor(color, colFactor);
+ float w3 = w / 3;
+ float xm1 = x1 + (w3 / 2);
+ getG2D().setStroke(new BasicStroke(w3));
+ getG2D().setColor(leftcol);
+ getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2));
+ getG2D().setColor(color);
+ getG2D().draw(new Line2D.Float(xm1 + w3, y1, xm1 + w3, y2));
+ getG2D().setColor(rightcol);
+ getG2D().draw(new Line2D.Float(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2));
+ }
+ break;
+ case Constants.EN_INSET:
+ case Constants.EN_OUTSET:
+ colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f);
+ if (horz) {
+ color = ColorUtil.lightenColor(color, (startOrBefore ? 1 : -1) * colFactor);
+ getG2D().setStroke(new BasicStroke(h));
+ float ym1 = y1 + (h / 2);
+ getG2D().setColor(color);
+ getG2D().draw(new Line2D.Float(x1, ym1, x2, ym1));
+ } else {
+ color = ColorUtil.lightenColor(color, (startOrBefore ? 1 : -1) * colFactor);
+ float xm1 = x1 + (w / 2);
+ getG2D().setStroke(new BasicStroke(w));
+ getG2D().setColor(color);
+ getG2D().draw(new Line2D.Float(xm1, y1, xm1, y2));
+ }
+ break;
+ case Constants.EN_HIDDEN:
+ break;
+ default:
+ getG2D().setColor(color);
+ if (horz) {
+ float ym = y1 + (h / 2);
+ getG2D().setStroke(new BasicStroke(h));
+ getG2D().draw(new Line2D.Float(x1, ym, x2, ym));
+ } else {
+ float xm = x1 + (w / 2);
+ getG2D().setStroke(new BasicStroke(w));
+ getG2D().draw(new Line2D.Float(xm, y1, xm, y2));
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) {
+ if (start.y != end.y) {
+ //TODO Support arbitrary lines if necessary
+ throw new UnsupportedOperationException(
+ "Can only deal with horizontal lines right now");
+ }
+
+ saveGraphicsState();
+ int half = width / 2;
+ int starty = start.y - half;
+ Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width);
+ getG2DState().updateClip(boundingRect);
+
+ switch (style.getEnumValue()) {
+ case Constants.EN_SOLID:
+ case Constants.EN_DASHED:
+ case Constants.EN_DOUBLE:
+ drawBorderLine(start.x, start.y - half, end.x, end.y + half,
+ true, true, style.getEnumValue(), color);
+ break;
+ case Constants.EN_DOTTED:
+ int shift = half; //This shifts the dots to the right by half a dot's width
+ drawBorderLine(start.x + shift, start.y - half, end.x + shift, end.y + half,
+ true, true, style.getEnumValue(), color);
+ break;
+ case Constants.EN_GROOVE:
+ case Constants.EN_RIDGE:
+ getG2DState().updateColor(ColorUtil.lightenColor(color, 0.6f));
+ moveTo(start.x, starty);
+ lineTo(end.x, starty);
+ lineTo(end.x, starty + 2 * half);
+ lineTo(start.x, starty + 2 * half);
+ closePath();
+ getG2D().fill(currentPath);
+ currentPath = null;
+ getG2DState().updateColor(color);
+ if (style.getEnumValue() == Constants.EN_GROOVE) {
+ moveTo(start.x, starty);
+ lineTo(end.x, starty);
+ lineTo(end.x, starty + half);
+ lineTo(start.x + half, starty + half);
+ lineTo(start.x, starty + 2 * half);
+ } else {
+ moveTo(end.x, starty);
+ lineTo(end.x, starty + 2 * half);
+ lineTo(start.x, starty + 2 * half);
+ lineTo(start.x, starty + half);
+ lineTo(end.x - half, starty + half);
+ }
+ closePath();
+ getG2D().fill(currentPath);
+ currentPath = null;
+
+ case Constants.EN_NONE:
+ // No rule is drawn
+ break;
+ default:
+ } // end switch
+ restoreGraphicsState();
+ }
+
+ /** {@inheritDoc} */
+ protected void clip() {
+ if (currentPath == null) {
+ throw new IllegalStateException("No current path available!");
+ }
+ getG2DState().updateClip(currentPath);
+ currentPath = null;
+ }
+
+ /** {@inheritDoc} */
+ protected void closePath() {
+ currentPath.closePath();
+ }
+
+ /** {@inheritDoc} */
+ protected void lineTo(int x, int y) {
+ if (currentPath == null) {
+ currentPath = new GeneralPath();
+ }
+ currentPath.lineTo(x, y);
+ }
+
+ /** {@inheritDoc} */
+ protected void moveTo(int x, int y) {
+ if (currentPath == null) {
+ currentPath = new GeneralPath();
+ }
+ currentPath.moveTo(x, y);
+ }
+
+ /** {@inheritDoc} */
+ protected void saveGraphicsState() {
+ this.painter.saveGraphicsState();
+ }
+
+ /** {@inheritDoc} */
+ protected void restoreGraphicsState() {
+ this.painter.restoreGraphicsState();
+ this.currentPath = null;
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java b/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
index 10af3aa86..88ceb1270 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DFontMetrics.java
@@ -21,12 +21,14 @@ package org.apache.fop.render.java2d;
// Java
import java.awt.Font;
-import java.awt.Graphics2D;
-import java.awt.geom.Rectangle2D;
import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
import java.awt.font.LineMetrics;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
import java.util.Map;
/**
@@ -110,6 +112,20 @@ public class Java2DFontMetrics {
private Graphics2D graphics;
/**
+ * Creates a Graphics2D object for the sole purpose of getting font metrics.
+ * @return a Graphics2D object
+ */
+ public static Graphics2D createFontMetricsGraphics2D() {
+ BufferedImage fontImage = new BufferedImage(100, 100,
+ BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics2D = fontImage.createGraphics();
+ //The next line is important to get accurate font metrics!
+ graphics2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+ RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+ return graphics2D;
+ }
+
+ /**
* Constructs a new Font-metrics.
* @param graphics a temp graphics object - this is needed so
* that we can get an instance of java.awt.FontMetrics
diff --git a/src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java b/src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java
index 1c5fa8427..a40ee1d5c 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DGraphicsState.java
@@ -222,7 +222,7 @@ public class Java2DGraphicsState {
getGraph().setPaint(p);
return true;
}
- } else if (p.equals(getGraph().getPaint())) {
+ } else if (!p.equals(getGraph().getPaint())) {
getGraph().setPaint(p);
return true;
}
@@ -252,10 +252,12 @@ public class Java2DGraphicsState {
* according to the rule last-specified-first-applied.
* @see java.awt.Graphics2D#transform(AffineTransform)
*
- * @param tf the transform to concatonate to the current level transform
+ * @param tf the transform to concatenate to the current level transform
*/
public void transform(AffineTransform tf) {
- getGraph().transform(tf);
+ if (!tf.isIdentity()) {
+ getGraph().transform(tf);
+ }
}
/**
@@ -270,7 +272,7 @@ public class Java2DGraphicsState {
/** {@inheritDoc} */
public String toString() {
- String s = "AWTGraphicsState " + currentGraphics.toString()
+ String s = "Java2DGraphicsState " + currentGraphics.toString()
+ ", Stroke (width: " + currentStrokeWidth + " style: "
+ currentStrokeStyle + "), " + getTransform();
return s;
diff --git a/src/java/org/apache/fop/render/java2d/Java2DImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/java2d/Java2DImageHandlerGraphics2D.java
new file mode 100644
index 000000000..3fc1787f4
--- /dev/null
+++ b/src/java/org/apache/fop/render/java2d/Java2DImageHandlerGraphics2D.java
@@ -0,0 +1,85 @@
+/*
+ * 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.java2d;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+import org.apache.xmlgraphics.image.loader.Image;
+import org.apache.xmlgraphics.image.loader.ImageFlavor;
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
+
+import org.apache.fop.render.ImageHandler;
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation that paints {@code Graphics2D} image on another {@code Graphics2D}
+ * target.
+ */
+public class Java2DImageHandlerGraphics2D implements ImageHandler {
+
+ /** {@inheritDoc} */
+ public int getPriority() {
+ return 200;
+ }
+
+ /** {@inheritDoc} */
+ public Class getSupportedImageClass() {
+ return ImageGraphics2D.class;
+ }
+
+ /** {@inheritDoc} */
+ public ImageFlavor[] getSupportedImageFlavors() {
+ return new ImageFlavor[] {
+ ImageFlavor.GRAPHICS2D
+ };
+ }
+
+ /** {@inheritDoc} */
+ public void handleImage(RenderingContext context, Image image, Rectangle pos)
+ throws IOException {
+ Java2DRenderingContext java2dContext = (Java2DRenderingContext)context;
+ ImageInfo info = image.getInfo();
+ ImageGraphics2D imageG2D = (ImageGraphics2D)image;
+
+ Dimension dim = info.getSize().getDimensionMpt();
+
+ Graphics2D g2d = (Graphics2D)java2dContext.getGraphics2D().create();
+ g2d.translate(pos.x, pos.y);
+ double sx = pos.width / dim.getWidth();
+ double sy = pos.height / dim.getHeight();
+ g2d.scale(sx, sy);
+
+ Rectangle2D area = new Rectangle2D.Double(0.0, 0.0, dim.getWidth(), dim.getHeight());
+ imageG2D.getGraphics2DImagePainter().paint(g2d, area);
+ g2d.dispose();
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCompatible(RenderingContext targetContext, Image image) {
+ return (image == null || image instanceof ImageGraphics2D)
+ && targetContext instanceof Java2DRenderingContext;
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/java2d/Java2DImageHandlerRenderedImage.java
new file mode 100644
index 000000000..9c2d24c32
--- /dev/null
+++ b/src/java/org/apache/fop/render/java2d/Java2DImageHandlerRenderedImage.java
@@ -0,0 +1,122 @@
+/*
+ * 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.java2d;
+
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+
+import org.apache.xmlgraphics.image.loader.Image;
+import org.apache.xmlgraphics.image.loader.ImageFlavor;
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
+import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
+
+import org.apache.fop.render.ImageHandler;
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation that paints {@code RenderedImage} instances on a {@code Graphics2D}
+ * object.
+ */
+public class Java2DImageHandlerRenderedImage implements ImageHandler {
+
+ /** {@inheritDoc} */
+ public int getPriority() {
+ return 300;
+ }
+
+ /** {@inheritDoc} */
+ public Class getSupportedImageClass() {
+ return ImageRawStream.class;
+ }
+
+ /** {@inheritDoc} */
+ public ImageFlavor[] getSupportedImageFlavors() {
+ return new ImageFlavor[] {
+ ImageFlavor.BUFFERED_IMAGE,
+ ImageFlavor.RENDERED_IMAGE,
+ };
+ }
+
+ /** {@inheritDoc} */
+ public void handleImage(RenderingContext context, Image image, Rectangle pos)
+ throws IOException {
+ Java2DRenderingContext java2dContext = (Java2DRenderingContext)context;
+ ImageInfo info = image.getInfo();
+ ImageRendered imageRend = (ImageRendered)image;
+ Graphics2D g2d = java2dContext.getGraphics2D();
+
+ AffineTransform at = new AffineTransform();
+ at.translate(pos.x, pos.y);
+ //scaling based on layout instructions
+ double sx = pos.getWidth() / (double)info.getSize().getWidthMpt();
+ double sy = pos.getHeight() / (double)info.getSize().getHeightMpt();
+
+ //scaling because of image resolution
+ //float sourceResolution = java2dContext.getUserAgent().getSourceResolution();
+ //source resolution seems to be a bad idea, not sure why
+ float sourceResolution = 72;
+ sourceResolution *= 1000; //we're working in the millipoint area
+ sx *= sourceResolution / info.getSize().getDpiHorizontal();
+ sy *= sourceResolution / info.getSize().getDpiVertical();
+ at.scale(sx, sy);
+ RenderedImage rend = imageRend.getRenderedImage();
+ if (imageRend.getTransparentColor() != null && !rend.getColorModel().hasAlpha()) {
+ int transCol = imageRend.getTransparentColor().getRGB();
+ BufferedImage bufImage = makeTransparentImage(rend);
+ WritableRaster alphaRaster = bufImage.getAlphaRaster();
+ //TODO Masked images: Does anyone know a more efficient method to do this?
+ final int[] transparent = new int[] {0x00};
+ for (int y = 0, maxy = bufImage.getHeight(); y < maxy; y++) {
+ for (int x = 0, maxx = bufImage.getWidth(); x < maxx; x++) {
+ int col = bufImage.getRGB(x, y);
+ if (col == transCol) {
+ //Mask out all pixels that match the transparent color
+ alphaRaster.setPixel(x, y, transparent);
+ }
+ }
+ }
+ g2d.drawRenderedImage(bufImage, at);
+ } else {
+ g2d.drawRenderedImage(rend, at);
+ }
+ }
+
+ private BufferedImage makeTransparentImage(RenderedImage src) {
+ BufferedImage bufImage = new BufferedImage(src.getWidth(), src.getHeight(),
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = bufImage.createGraphics();
+ g2d.drawRenderedImage(src, new AffineTransform());
+ g2d.dispose();
+ return bufImage;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCompatible(RenderingContext targetContext, Image image) {
+ return (image == null || image instanceof ImageRendered)
+ && targetContext instanceof Java2DRenderingContext;
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java
new file mode 100644
index 000000000..9a68f62d0
--- /dev/null
+++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java
@@ -0,0 +1,271 @@
+/*
+ * 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.java2d;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Stack;
+
+import org.w3c.dom.Document;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.render.RenderingContext;
+import org.apache.fop.render.intermediate.AbstractIFPainter;
+import org.apache.fop.render.intermediate.IFException;
+import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+
+/**
+ * {@code IFPainter} implementation that paints on a Graphics2D instance.
+ */
+public class Java2DPainter extends AbstractIFPainter {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(Java2DPainter.class);
+
+ /** the FO user agent */
+ protected FOUserAgent userAgent;
+
+ /** The font information */
+ protected FontInfo fontInfo;
+
+ /** Holds the intermediate format state */
+ protected IFState state;
+
+ private Java2DBorderPainter borderPainter;
+
+ /** The current state, holds a Graphics2D and its context */
+ protected Java2DGraphicsState g2dState;
+ private Stack g2dStateStack = new Stack();
+
+ /**
+ * Main constructor.
+ * @param g2d the target Graphics2D instance
+ * @param userAgent the user agent
+ * @param fontInfo the font information
+ */
+ public Java2DPainter(Graphics2D g2d, FOUserAgent userAgent, FontInfo fontInfo) {
+ super();
+ this.userAgent = userAgent;
+ this.state = IFState.create();
+ this.fontInfo = fontInfo;
+ this.g2dState = new Java2DGraphicsState(g2d, fontInfo, g2d.getTransform());
+ this.borderPainter = new Java2DBorderPainter(this);
+ }
+
+ /** {@inheritDoc} */
+ public FOUserAgent getUserAgent() {
+ return this.userAgent;
+ }
+
+ /**
+ * Returns the associated {@code FontInfo} object.
+ * @return the font info
+ */
+ protected FontInfo getFontInfo() {
+ return this.fontInfo;
+ }
+
+ /**
+ * Returns the Java2D graphics state.
+ * @return the graphics state
+ */
+ protected Java2DGraphicsState getState() {
+ return this.g2dState;
+ }
+
+ //----------------------------------------------------------------------------------------------
+
+
+ /** {@inheritDoc} */
+ public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
+ throws IFException {
+ saveGraphicsState();
+ try {
+ concatenateTransformationMatrix(transform);
+ //TODO CLIP!
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in startViewport()", ioe);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endViewport() throws IFException {
+ restoreGraphicsState();
+ }
+
+ /** {@inheritDoc} */
+ public void startGroup(AffineTransform transform) throws IFException {
+ saveGraphicsState();
+ try {
+ concatenateTransformationMatrix(transform);
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in startGroup()", ioe);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endGroup() throws IFException {
+ restoreGraphicsState();
+ }
+
+ /** {@inheritDoc} */
+ public void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException {
+ drawImageUsingURI(uri, rect);
+ }
+
+ /** {@inheritDoc} */
+ protected RenderingContext createRenderingContext() {
+ Java2DRenderingContext java2dContext = new Java2DRenderingContext(
+ getUserAgent(), g2dState.getGraph(), getFontInfo());
+ return java2dContext;
+ }
+
+ /** {@inheritDoc} */
+ public void drawImage(Document doc, Rectangle rect, Map foreignAttributes) throws IFException {
+ drawImageUsingDocument(doc, rect);
+ }
+
+ /** {@inheritDoc} */
+ public void clipRect(Rectangle rect) throws IFException {
+ }
+
+ /** {@inheritDoc} */
+ public void fillRect(Rectangle rect, Paint fill) throws IFException {
+ if (fill == null) {
+ return;
+ }
+ if (rect.width != 0 && rect.height != 0) {
+ g2dState.updatePaint(fill);
+ g2dState.getGraph().fill(rect);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after,
+ BorderProps start, BorderProps end) throws IFException {
+ if (before != null || after != null || start != null || end != null) {
+ this.borderPainter.drawBorders(rect, before, after, start, end);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
+ throws IFException {
+ this.borderPainter.drawLine(start, end, width, color, style);
+ }
+
+ /** {@inheritDoc} */
+ public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
+ //Note: dy is currently ignored
+ g2dState.updateColor(state.getTextColor());
+ FontTriplet triplet = new FontTriplet(
+ state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
+ //TODO Ignored: state.getFontVariant()
+ //TODO Opportunity for font caching if font state is more heavily used
+ Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());
+ //String fontName = font.getFontName();
+ //float fontSize = state.getFontSize() / 1000f;
+ g2dState.updateFont(font.getFontName(), state.getFontSize() * 1000);
+
+ Graphics2D g2d = this.g2dState.getGraph();
+ GlyphVector gv = g2d.getFont().createGlyphVector(g2d.getFontRenderContext(), text);
+ Point2D cursor = new Point2D.Float(0, 0);
+
+ int l = text.length();
+ int dxl = (dx != null ? dx.length : 0);
+
+ if (dx != null && dxl > 0 && dx[0] != 0) {
+ cursor.setLocation(cursor.getX() - (dx[0] / 10f), cursor.getY());
+ gv.setGlyphPosition(0, cursor);
+ }
+ for (int i = 0; i < l; i++) {
+ char orgChar = text.charAt(i);
+ float glyphAdjust = 0;
+ int cw = font.getCharWidth(orgChar);
+
+ if (dx != null && i < dxl - 1) {
+ glyphAdjust += dx[i + 1];
+ }
+
+ cursor.setLocation(cursor.getX() + cw - glyphAdjust, cursor.getY());
+ gv.setGlyphPosition(i + 1, cursor);
+ }
+ g2d.drawGlyphVector(gv, x, y);
+ }
+
+ /** {@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);
+ }
+ }
+
+ //----------------------------------------------------------------------------------------------
+
+ /** Saves the current graphics state on the stack. */
+ protected void saveGraphicsState() {
+ g2dStateStack.push(g2dState);
+ g2dState = new Java2DGraphicsState(g2dState);
+ }
+
+ /** Restores the last graphics state from the stack. */
+ protected void restoreGraphicsState() {
+ g2dState.dispose();
+ g2dState = (Java2DGraphicsState)g2dStateStack.pop();
+ }
+
+ private void concatenateTransformationMatrix(AffineTransform transform) throws IOException {
+ g2dState.transform(transform);
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
index e3f79dea2..65e6ac0fe 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
@@ -123,7 +123,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
/** The 0-based current page number */
private int currentPageNumber = 0;
- /** true if antialiasing is set */
+ /** true if anti-aliasing is set */
protected boolean antialiasing = true;
/** true if qualityRendering is set */
@@ -171,12 +171,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
//Don't call super.setupFontInfo() here! Java2D needs a special font setup
// create a temp Image to test font metrics on
this.fontInfo = inFontInfo;
- BufferedImage fontImage = new BufferedImage(100, 100,
- BufferedImage.TYPE_INT_RGB);
- Graphics2D graphics2D = fontImage.createGraphics();
- //The next line is important to get accurate font metrics!
- graphics2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
- RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+ Graphics2D graphics2D = Java2DFontMetrics.createFontMetricsGraphics2D();
FontCollection[] fontCollections = new FontCollection[] {
new Base14FontCollection(graphics2D),
diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderingContext.java b/src/java/org/apache/fop/render/java2d/Java2DRenderingContext.java
new file mode 100644
index 000000000..7bc55502a
--- /dev/null
+++ b/src/java/org/apache/fop/render/java2d/Java2DRenderingContext.java
@@ -0,0 +1,61 @@
+/*
+ * 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.java2d;
+
+import java.awt.Graphics2D;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.render.AbstractRenderingContext;
+
+/**
+ * Rendering context for PDF production.
+ */
+public class Java2DRenderingContext extends AbstractRenderingContext {
+
+ private FontInfo fontInfo;
+ private Graphics2D g2d;
+
+ /**
+ * Main constructor.
+ * @param userAgent the user agent
+ * @param g2d the target Graphics2D instance
+ * @param fontInfo the font list
+ */
+ public Java2DRenderingContext(FOUserAgent userAgent, Graphics2D g2d, FontInfo fontInfo) {
+ super(userAgent);
+ this.g2d = g2d;
+ this.fontInfo = fontInfo;
+ }
+
+ /** {@inheritDoc} */
+ public String getMimeType() {
+ return null; //not applicable
+ }
+
+ /**
+ * Returns the target Graphics2D object.
+ * @return the Graphics2D object
+ */
+ public Graphics2D getGraphics2D() {
+ return this.g2d;
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderingSettings.java b/src/java/org/apache/fop/render/java2d/Java2DRenderingSettings.java
new file mode 100644
index 000000000..f7bad64a7
--- /dev/null
+++ b/src/java/org/apache/fop/render/java2d/Java2DRenderingSettings.java
@@ -0,0 +1,58 @@
+/*
+ * 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.java2d;
+
+import java.awt.Color;
+
+/**
+ * This class holds settings used when rendering with Java2D.
+ */
+public class Java2DRenderingSettings {
+
+ /** false: paints a non-transparent white background, true: for a transparent background */
+ private Color pageBackgroundColor = Color.WHITE;
+
+ /**
+ * Returns the page background color.
+ * @return the page background color or null if the page background is transparent
+ */
+ public Color getPageBackgroundColor() {
+ return this.pageBackgroundColor;
+ }
+
+ /**
+ * Sets the page background color.
+ * @param color the page background color or null if the page background shall be transparent
+ */
+ public void setPageBackgroundColor(Color color) {
+ this.pageBackgroundColor = color;
+ }
+
+ /**
+ * Indicates whether the pages have a transparent background or if it's painted in a
+ * particular color.
+ * @return true if the pages have a transparent background
+ */
+ public boolean hasTransparentPageBackground() {
+ return this.pageBackgroundColor == null;
+ }
+
+
+}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DUtil.java b/src/java/org/apache/fop/render/java2d/Java2DUtil.java
new file mode 100644
index 000000000..30f84ba22
--- /dev/null
+++ b/src/java/org/apache/fop/render/java2d/Java2DUtil.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.java2d;
+
+import java.awt.Graphics2D;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.fonts.FontCollection;
+import org.apache.fop.fonts.FontEventAdapter;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontManager;
+
+/**
+ * Rendering-related utilities for Java2D.
+ */
+public class Java2DUtil {
+
+ /**
+ * Builds a default {@code FontInfo} object for use with output formats using the Java2D
+ * font setup.
+ * @param fontInfo the font info object to populate
+ * @param userAgent the user agent
+ * @return the populated font information object
+ */
+ public static FontInfo buildDefaultJava2DBasedFontInfo(
+ FontInfo fontInfo, FOUserAgent userAgent) {
+ Graphics2D graphics2D = Java2DFontMetrics.createFontMetricsGraphics2D();
+
+ FontManager fontManager = userAgent.getFactory().getFontManager();
+ FontCollection[] fontCollections = new FontCollection[] {
+ new org.apache.fop.render.java2d.Base14FontCollection(graphics2D),
+ new InstalledFontCollection(graphics2D)
+ };
+
+ FontInfo fi = (fontInfo != null ? fontInfo : new FontInfo());
+ fi.setEventListener(new FontEventAdapter(userAgent.getEventBroadcaster()));
+ fontManager.setup(fi, fontCollections);
+ return fi;
+ }
+
+
+}
diff --git a/src/java/org/apache/fop/render/pcl/PCLConstants.java b/src/java/org/apache/fop/render/pcl/PCLConstants.java
new file mode 100644
index 000000000..167800cc9
--- /dev/null
+++ b/src/java/org/apache/fop/render/pcl/PCLConstants.java
@@ -0,0 +1,37 @@
+/*
+ * 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.pcl;
+
+import org.apache.xmlgraphics.util.QName;
+
+import org.apache.fop.fo.extensions.ExtensionElementMapping;
+
+/**
+ * Constants used for PCL output.
+ */
+public interface PCLConstants {
+
+ /** Image conversion mode */
+ QName CONV_MODE = new QName(ExtensionElementMapping.URI, null, "conversion-mode");
+
+ /** Source transparency mode */
+ QName SRC_TRANSPARENCY = new QName(ExtensionElementMapping.URI, null, "source-transparency");
+
+}
diff --git a/src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java b/src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java
new file mode 100644
index 000000000..ce032ace5
--- /dev/null
+++ b/src/java/org/apache/fop/render/pcl/PCLDocumentHandler.java
@@ -0,0 +1,252 @@
+/*
+ * 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.pcl;
+
+import java.awt.Dimension;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
+import org.apache.fop.render.intermediate.IFException;
+import org.apache.fop.render.intermediate.IFPainter;
+
+/**
+ * {@code IFDocumentHandler} implementation that produces PCL 5.
+ */
+public class PCLDocumentHandler extends AbstractBinaryWritingIFDocumentHandler
+ implements PCLConstants {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(PCLDocumentHandler.class);
+
+ /** Utility class for handling all sorts of peripheral tasks around PCL generation. */
+ protected PCLRenderingUtil pclUtil;
+
+ /** The PCL generator */
+ private PCLGenerator gen;
+
+ private PCLPageDefinition currentPageDefinition;
+
+ /** contains the pageWith of the last printed page */
+ private long pageWidth = 0;
+ /** contains the pageHeight of the last printed page */
+ private long pageHeight = 0;
+
+ /**
+ * Default constructor.
+ */
+ public PCLDocumentHandler() {
+ }
+
+ /** {@inheritDoc} */
+ public boolean supportsPagesOutOfOrder() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public String getMimeType() {
+ return MimeConstants.MIME_PCL;
+ }
+
+ /** {@inheritDoc} */
+ public void setUserAgent(FOUserAgent ua) {
+ super.setUserAgent(ua);
+ this.pclUtil = new PCLRenderingUtil(ua);
+ }
+
+ /** {@inheritDoc} */
+ public IFDocumentHandlerConfigurator getConfigurator() {
+ return null; //No configurator, yet.
+ }
+
+ PCLRenderingUtil getPCLUtil() {
+ return this.pclUtil;
+ }
+
+ PCLGenerator getPCLGenerator() {
+ return this.gen;
+ }
+
+ /** @return the target resolution */
+ protected int getResolution() {
+ int resolution = (int)Math.round(getUserAgent().getTargetResolution());
+ if (resolution <= 300) {
+ return 300;
+ } else {
+ return 600;
+ }
+ }
+
+ //----------------------------------------------------------------------------------------------
+
+ /** {@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()");
+ }
+ log.debug("Rendering areas to PCL...");
+ this.gen = new PCLGenerator(this.outputStream, getResolution());
+
+ if (!pclUtil.isPJLDisabled()) {
+ gen.universalEndOfLanguage();
+ gen.writeText("@PJL COMMENT Produced by " + getUserAgent().getProducer() + "\n");
+ if (getUserAgent().getTitle() != null) {
+ gen.writeText("@PJL JOB NAME = \"" + getUserAgent().getTitle() + "\"\n");
+ }
+ gen.writeText("@PJL SET RESOLUTION = " + getResolution() + "\n");
+ gen.writeText("@PJL ENTER LANGUAGE = PCL\n");
+ }
+ gen.resetPrinter();
+ gen.setUnitOfMeasure(getResolution());
+ gen.setRasterGraphicsResolution(getResolution());
+ } catch (IOException e) {
+ throw new IFException("I/O error in startDocument()", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endDocumentHeader() throws IFException {
+ }
+
+ /** {@inheritDoc} */
+ public void endDocument() throws IFException {
+ try {
+ gen.separateJobs();
+ gen.resetPrinter();
+ if (!pclUtil.isPJLDisabled()) {
+ gen.universalEndOfLanguage();
+ }
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in endDocument()", ioe);
+ }
+ super.endDocument();
+ }
+
+ /** {@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 {
+
+ try {
+ //TODO Add support for paper-source and duplex-mode
+ /*
+ //Paper source
+ String paperSource = page.getForeignAttributeValue(
+ new QName(PCLElementMapping.NAMESPACE, null, "paper-source"));
+ if (paperSource != null) {
+ gen.selectPaperSource(Integer.parseInt(paperSource));
+ }
+
+ // Is Page duplex?
+ String pageDuplex = page.getForeignAttributeValue(
+ new QName(PCLElementMapping.NAMESPACE, null, "duplex-mode"));
+ if (pageDuplex != null) {
+ gen.selectDuplexMode(Integer.parseInt(pageDuplex));
+ }*/
+
+ //Page size
+ final long pagewidth = size.width;
+ final long pageheight = size.height;
+ selectPageFormat(pagewidth, pageheight);
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in startPage()", ioe);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public IFPainter startPageContent() throws IFException {
+ return new PCLPainter(this, this.currentPageDefinition);
+ }
+
+ /** {@inheritDoc} */
+ public void endPageContent() throws IFException {
+ //nop
+ }
+
+ /** {@inheritDoc} */
+ public void endPage() throws IFException {
+ try {
+ //Eject page
+ gen.formFeed();
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in endPage()", ioe);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void handleExtensionObject(Object extension) throws IFException {
+ if (false) {
+ //TODO Handle extensions
+ } else {
+ log.debug("Don't know how to handle extension object. Ignoring: "
+ + extension + " (" + extension.getClass().getName() + ")");
+ }
+ }
+
+ private void selectPageFormat(long pagewidth, long pageheight) throws IOException {
+ //Only set the page format if it changes (otherwise duplex printing won't work)
+ if ((pagewidth != this.pageWidth) || (pageheight != this.pageHeight)) {
+ this.pageWidth = pagewidth;
+ this.pageHeight = pageheight;
+
+ this.currentPageDefinition = PCLPageDefinition.getPageDefinition(
+ pagewidth, pageheight, 1000);
+
+ if (this.currentPageDefinition == null) {
+ this.currentPageDefinition = PCLPageDefinition.getDefaultPageDefinition();
+ log.warn("Paper type could not be determined. Falling back to: "
+ + this.currentPageDefinition.getName());
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("page size: " + currentPageDefinition.getPhysicalPageSize());
+ log.debug("logical page: " + currentPageDefinition.getLogicalPageRect());
+ }
+
+ if (this.currentPageDefinition.isLandscapeFormat()) {
+ gen.writeCommand("&l1O"); //Landscape Orientation
+ } else {
+ gen.writeCommand("&l0O"); //Portrait Orientation
+ }
+ gen.selectPageSize(this.currentPageDefinition.getSelector());
+
+ gen.clearHorizontalMargins();
+ gen.setTopMargin(0);
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java b/src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java
new file mode 100644
index 000000000..5d42d3320
--- /dev/null
+++ b/src/java/org/apache/fop/render/pcl/PCLDocumentHandlerMaker.java
@@ -0,0 +1,58 @@
+/*
+ * 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.pcl;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.render.intermediate.AbstractIFDocumentHandlerMaker;
+import org.apache.fop.render.intermediate.IFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
+
+/**
+ * Document handler factory for PCL output.
+ */
+public class PCLDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker {
+
+ //TODO Revert to normal MIME after stabilization!
+ private static final String[] MIMES = new String[] {MimeConstants.MIME_PCL + ";mode=painter"};
+
+ /** {@inheritDoc} */
+ public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) {
+ PCLDocumentHandler handler = new PCLDocumentHandler();
+ handler.setUserAgent(ua);
+ return handler;
+ }
+
+ /** {@inheritDoc} */
+ public boolean needsOutputStream() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getSupportedMimeTypes() {
+ return MIMES;
+ }
+
+ /** {@inheritDoc} */
+ public IFDocumentHandlerConfigurator getConfigurator(FOUserAgent userAgent) {
+ return null;
+ }
+
+}
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/pcl/PCLGenerator.java b/src/java/org/apache/fop/render/pcl/PCLGenerator.java
index 45af4df89..3a451c9ad 100644
--- a/src/java/org/apache/fop/render/pcl/PCLGenerator.java
+++ b/src/java/org/apache/fop/render/pcl/PCLGenerator.java
@@ -52,6 +52,10 @@ import org.apache.xmlgraphics.util.UnitConv;
*/
public class PCLGenerator {
+ private static final String US_ASCII = "US-ASCII";
+
+ private static final String ISO_8859_1 = "ISO-8859-1";
+
/** The ESC (escape) character */
public static final char ESC = '\033';
@@ -113,6 +117,14 @@ public class PCLGenerator {
return this.out;
}
+ /**
+ * Returns the currently active text encoding.
+ * @return the text encoding
+ */
+ public String getTextEncoding() {
+ return ISO_8859_1;
+ }
+
/** @return the maximum resolution to encode bitmap images at */
public int getMaximumBitmapResolution() {
return this.maxBitmapResolution;
@@ -125,7 +137,7 @@ public class PCLGenerator {
*/
public void writeCommand(String cmd) throws IOException {
out.write(27); //ESC
- out.write(cmd.getBytes("US-ASCII"));
+ out.write(cmd.getBytes(US_ASCII));
}
/**
@@ -134,7 +146,7 @@ public class PCLGenerator {
* @throws IOException In case of an I/O error
*/
public void writeText(String s) throws IOException {
- out.write(s.getBytes("ISO-8859-1"));
+ out.write(s.getBytes(ISO_8859_1));
}
/**
diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java
new file mode 100644
index 000000000..d4e04175e
--- /dev/null
+++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java
@@ -0,0 +1,601 @@
+/*
+ * 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.pcl;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Paint;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Stack;
+
+import org.w3c.dom.Document;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.xmlgraphics.java2d.GraphicContext;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.fonts.Font;
+import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.pdf.PDFXObject;
+import org.apache.fop.render.RenderingContext;
+import org.apache.fop.render.intermediate.AbstractIFPainter;
+import org.apache.fop.render.intermediate.IFException;
+import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+import org.apache.fop.util.CharUtilities;
+import org.apache.fop.util.UnitConv;
+
+/**
+ * {@code IFPainter} implementation that produces PCL 5.
+ */
+public class PCLPainter extends AbstractIFPainter implements PCLConstants {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(PCLPainter.class);
+
+ private PCLDocumentHandler parent;
+
+ /** Holds the intermediate format state */
+ protected IFState state;
+
+ /** The PCL generator */
+ private PCLGenerator gen;
+
+ private PCLPageDefinition currentPageDefinition;
+ private int currentPrintDirection = 0;
+ //private GeneralPath currentPath = null;
+
+ private Stack graphicContextStack = new Stack();
+ private GraphicContext graphicContext = new GraphicContext();
+
+ /**
+ * Main constructor.
+ * @param parent the parent document handler
+ */
+ public PCLPainter(PCLDocumentHandler parent, PCLPageDefinition pageDefinition) {
+ this.parent = parent;
+ this.gen = parent.getPCLGenerator();
+ this.state = IFState.create();
+ this.currentPageDefinition = pageDefinition;
+ }
+
+ /** {@inheritDoc} */
+ public FOUserAgent getUserAgent() {
+ return this.parent.getUserAgent();
+ }
+
+ PCLRenderingUtil getPCLUtil() {
+ return this.parent.getPCLUtil();
+ }
+
+ /** @return the target resolution */
+ protected int getResolution() {
+ int resolution = (int)Math.round(getUserAgent().getTargetResolution());
+ if (resolution <= 300) {
+ return 300;
+ } else {
+ return 600;
+ }
+ }
+
+ //----------------------------------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
+ throws IFException {
+ saveGraphicsState();
+ try {
+ concatenateTransformationMatrix(transform);
+ /* PCL cannot clip!
+ if (clipRect != null) {
+ clipRect(clipRect);
+ }*/
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in startViewport()", ioe);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endViewport() throws IFException {
+ restoreGraphicsState();
+ }
+
+ /** {@inheritDoc} */
+ public void startGroup(AffineTransform transform) throws IFException {
+ saveGraphicsState();
+ try {
+ concatenateTransformationMatrix(transform);
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in startGroup()", ioe);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endGroup() throws IFException {
+ 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() {
+ /*
+ PCLRenderingContext pdfContext = new PCLRenderingContext(
+ getUserAgent(), generator, currentPage, getFontInfo());
+ return pdfContext;
+ */
+ return null;
+ }
+
+ /**
+ * 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();
+ */
+ }
+
+ /** {@inheritDoc} */
+ public void clipRect(Rectangle rect) throws IFException {
+ //PCL cannot clip (only HP GL/2 can)
+ /*
+ generator.endTextObject();
+ generator.clipRect(rect);
+ */
+ }
+
+ /** {@inheritDoc} */
+ public void fillRect(Rectangle rect, Paint fill) throws IFException {
+ if (fill == null) {
+ return;
+ }
+ if (rect.width != 0 && rect.height != 0) {
+ Color fillColor = null;
+ if (fill != null) {
+ if (fill instanceof Color) {
+ fillColor = (Color)fill;
+ } else {
+ throw new UnsupportedOperationException("Non-Color paints NYI");
+ }
+ try {
+ setCursorPos(rect.x, rect.y);
+ gen.fillRect(rect.width, rect.height, fillColor);
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in fillRect()", ioe);
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after,
+ BorderProps start, BorderProps end) throws IFException {
+ if (before != null || after != null || start != null || end != null) {
+ /*
+ generator.endTextObject();
+ this.borderPainter.drawBorders(rect, before, after, start, end);
+ */
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
+ throws IFException {
+ /*
+ generator.endTextObject();
+ this.borderPainter.drawLine(start, end, width, color, style);
+ */
+ }
+
+ /** {@inheritDoc} */
+ public void drawText(int x, int y, int[] dx, int[] dy, String text) throws IFException {
+ //Note: dy is currently ignored
+ try {
+ FontTriplet triplet = new FontTriplet(
+ state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
+ //TODO Ignored: state.getFontVariant()
+ //TODO Opportunity for font caching if font state is more heavily used
+ String fontKey = parent.getFontInfo().getInternalFontKey(triplet);
+ boolean pclFont = getPCLUtil().isAllTextAsBitmaps()
+ ? false
+ : setFont(fontKey, state.getFontSize(), text);
+ if (true || pclFont) {
+ drawTextNative(x, y, dx, text, triplet);
+ } else {
+ drawTextAsBitmap(x, y, dx, dy, text, triplet);
+ }
+ } catch (IOException ioe) {
+ throw new IFException("I/O error in drawText()", ioe);
+ }
+ }
+
+ private void drawTextNative(int x, int y, int[] dx, String text, FontTriplet triplet)
+ throws IOException {
+ Color textColor = state.getTextColor();
+ if (textColor != null) {
+ gen.setTransparencyMode(true, false);
+ gen.selectGrayscale(textColor);
+ }
+
+ gen.setTransparencyMode(true, true);
+ setCursorPos(x, y);
+
+ float fontSize = state.getFontSize() / 1000f;
+ Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());
+ int l = text.length();
+ int dxl = (dx != null ? dx.length : 0);
+
+ StringBuffer sb = new StringBuffer(Math.max(16, l));
+ if (dx != null && dxl > 0 && dx[0] != 0) {
+ sb.append("\u001B&a+").append(gen.formatDouble2(dx[0] / 100.0)).append('H');
+ }
+ 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);
+ } else {
+ if (CharUtilities.isFixedWidthSpace(orgChar)) {
+ //Fixed width space are rendered as spaces so copy/paste works in a reader
+ ch = font.mapChar(CharUtilities.SPACE);
+ int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
+ glyphAdjust = -(10 * spaceDiff / fontSize);
+ } else {
+ ch = font.mapChar(orgChar);
+ }
+ }
+ sb.append(ch);
+
+ if (dx != null && i < dxl - 1) {
+ glyphAdjust += dx[i + 1];
+ }
+
+ if (glyphAdjust != 0) {
+ sb.append("\u001B&a+").append(gen.formatDouble2(glyphAdjust / 100.0)).append('H');
+ }
+
+ }
+ gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
+
+ }
+
+ private void drawTextAsBitmap(int x, int y, int[] dx, int[] dy,
+ String text, FontTriplet triplet) throws IOException {
+ /*
+ //Use Java2D to paint different fonts via bitmap
+ final Font font = getFontFromArea(text);
+ final int baseline = text.getBaselineOffset();
+
+ //for cursive fonts, so the text isn't clipped
+ int extraWidth = font.getFontSize() / 3;
+ final FontMetricsMapper mapper = (FontMetricsMapper)fontInfo.getMetricsFor(
+ font.getFontName());
+ int maxAscent = mapper.getMaxAscent(font.getFontSize()) / 1000;
+ final int additionalBPD = maxAscent - baseline;
+
+ Graphics2DAdapter g2a = getGraphics2DAdapter();
+ final Rectangle paintRect = new Rectangle(
+ rx, currentBPPosition + text.getOffset() - additionalBPD,
+ text.getIPD() + extraWidth, text.getBPD() + additionalBPD);
+ RendererContext rc = createRendererContext(paintRect.x, paintRect.y,
+ paintRect.width, paintRect.height, null);
+ Map atts = new java.util.HashMap();
+ atts.put(CONV_MODE, "bitmap");
+ atts.put(SRC_TRANSPARENCY, "true");
+ rc.setProperty(RendererContextConstants.FOREIGN_ATTRIBUTES, atts);
+
+ Graphics2DImagePainter painter = new Graphics2DImagePainter() {
+
+ public void paint(Graphics2D g2d, Rectangle2D area) {
+ g2d.setFont(mapper.getFont(font.getFontSize()));
+ g2d.translate(0, baseline + additionalBPD);
+ g2d.scale(1000, 1000);
+ g2d.setColor(col);
+ Java2DRenderer.renderText(text, g2d, font);
+ renderTextDecoration(g2d, mapper, fontsize, text, 0, 0);
+ }
+
+ public Dimension getImageSize() {
+ return paintRect.getSize();
+ }
+
+ };
+ g2a.paintImage(painter, rc,
+ paintRect.x, paintRect.y, paintRect.width, paintRect.height);
+ currentIPPosition = saveIP + text.getAllocIPD();
+ */
+ }
+
+ /** {@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);
+ }
+ }
+
+ //----------------------------------------------------------------------------------------------
+
+ /** Saves the current graphics state on the stack. */
+ private void saveGraphicsState() {
+ graphicContextStack.push(graphicContext);
+ graphicContext = (GraphicContext)graphicContext.clone();
+ }
+
+ /** Restores the last graphics state from the stack. */
+ private void restoreGraphicsState() {
+ graphicContext = (GraphicContext)graphicContextStack.pop();
+ }
+
+ private void concatenateTransformationMatrix(AffineTransform transform) throws IOException {
+ if (!transform.isIdentity()) {
+ graphicContext.transform(transform);
+ changePrintDirection();
+ }
+ }
+
+ private Point2D transformedPoint(int x, int y) {
+ AffineTransform at = graphicContext.getTransform();
+ if (log.isTraceEnabled()) {
+ log.trace("Current transform: " + at);
+ }
+ Point2D.Float orgPoint = new Point2D.Float(x, y);
+ Point2D.Float transPoint = new Point2D.Float();
+ at.transform(orgPoint, transPoint);
+ //At this point we have the absolute position in FOP's coordinate system
+
+ //Now get PCL coordinates taking the current print direction and the logical page
+ //into account.
+ Dimension pageSize = currentPageDefinition.getPhysicalPageSize();
+ Rectangle logRect = currentPageDefinition.getLogicalPageRect();
+ switch (currentPrintDirection) {
+ case 0:
+ transPoint.x -= logRect.x;
+ transPoint.y -= logRect.y;
+ break;
+ case 90:
+ float ty = transPoint.x;
+ transPoint.x = pageSize.height - transPoint.y;
+ transPoint.y = ty;
+ transPoint.x -= logRect.y;
+ transPoint.y -= logRect.x;
+ break;
+ case 180:
+ transPoint.x = pageSize.width - transPoint.x;
+ transPoint.y = pageSize.height - transPoint.y;
+ transPoint.x -= pageSize.width - logRect.x - logRect.width;
+ transPoint.y -= pageSize.height - logRect.y - logRect.height;
+ //The next line is odd and is probably necessary due to the default value of the
+ //Text Length command: "1/2 inch less than maximum text length"
+ //I wonder why this isn't necessary for the 90 degree rotation. *shrug*
+ transPoint.y -= UnitConv.in2mpt(0.5);
+ break;
+ case 270:
+ float tx = transPoint.y;
+ transPoint.y = pageSize.width - transPoint.x;
+ transPoint.x = tx;
+ transPoint.x -= pageSize.height - logRect.y - logRect.height;
+ transPoint.y -= pageSize.width - logRect.x - logRect.width;
+ break;
+ default:
+ throw new IllegalStateException("Illegal print direction: " + currentPrintDirection);
+ }
+ return transPoint;
+ }
+
+ private void changePrintDirection() throws IOException {
+ AffineTransform at = graphicContext.getTransform();
+ int newDir;
+ newDir = PCLRenderingUtil.determinePrintDirection(at);
+ if (newDir != this.currentPrintDirection) {
+ this.currentPrintDirection = newDir;
+ gen.changePrintDirection(this.currentPrintDirection);
+ }
+ }
+
+ /**
+ * Sets the current cursor position. The coordinates are transformed to the absolute position
+ * on the logical PCL page and then passed on to the PCLGenerator.
+ * @param x the x coordinate (in millipoints)
+ * @param y the y coordinate (in millipoints)
+ */
+ void setCursorPos(int x, int y) throws IOException {
+ Point2D transPoint = transformedPoint(x, y);
+ gen.setCursorPos(transPoint.getX(), transPoint.getY());
+ }
+
+ /**
+ * Sets the current font (NOTE: Hard-coded font mappings ATM!)
+ * @param name the font name (internal F* names for now)
+ * @param size the font size (in millipoints)
+ * @param text the text to be rendered (used to determine if there are non-printable chars)
+ * @return true if the font can be mapped to PCL
+ * @throws IOException if an I/O problem occurs
+ */
+ public boolean setFont(String name, int size, String text) throws IOException {
+ byte[] encoded = text.getBytes("ISO-8859-1");
+ for (int i = 0, c = encoded.length; i < c; i++) {
+ if (encoded[i] == 0x3F && text.charAt(i) != '?') {
+ return false;
+ }
+ }
+ int fontcode = 0;
+ if (name.length() > 1 && name.charAt(0) == 'F') {
+ try {
+ fontcode = Integer.parseInt(name.substring(1));
+ } catch (Exception e) {
+ log.error(e);
+ }
+ }
+ //Note "(ON" selects ISO 8859-1 symbol set as used by PCLGenerator
+ String formattedSize = gen.formatDouble2(size / 1000.0);
+ switch (fontcode) {
+ case 1: // F1 = Helvetica
+ // gen.writeCommand("(8U");
+ // gen.writeCommand("(s1p" + formattedSize + "v0s0b24580T");
+ // Arial is more common among PCL5 printers than Helvetica - so use Arial
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v0s0b16602T");
+ break;
+ case 2: // F2 = Helvetica Oblique
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v1s0b16602T");
+ break;
+ case 3: // F3 = Helvetica Bold
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v0s3b16602T");
+ break;
+ case 4: // F4 = Helvetica Bold Oblique
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v1s3b16602T");
+ break;
+ case 5: // F5 = Times Roman
+ // gen.writeCommand("(8U");
+ // gen.writeCommand("(s1p" + formattedSize + "v0s0b25093T");
+ // Times New is more common among PCL5 printers than Times - so use Times New
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v0s0b16901T");
+ break;
+ case 6: // F6 = Times Italic
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v1s0b16901T");
+ break;
+ case 7: // F7 = Times Bold
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v0s3b16901T");
+ break;
+ case 8: // F8 = Times Bold Italic
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s1p" + formattedSize + "v1s3b16901T");
+ break;
+ case 9: // F9 = Courier
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f))
+ + "h0s0b4099T");
+ break;
+ case 10: // F10 = Courier Oblique
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f))
+ + "h1s0b4099T");
+ break;
+ case 11: // F11 = Courier Bold
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f))
+ + "h0s3b4099T");
+ break;
+ case 12: // F12 = Courier Bold Oblique
+
+ gen.writeCommand("(0N");
+ gen.writeCommand("(s0p" + gen.formatDouble2(120.01f / (size / 1000.00f))
+ + "h1s3b4099T");
+ break;
+ case 13: // F13 = Symbol
+
+ return false;
+ //gen.writeCommand("(19M");
+ //gen.writeCommand("(s1p" + formattedSize + "v0s0b16686T");
+ // ECMA Latin 1 Symbol Set in Times Roman???
+ // gen.writeCommand("(9U");
+ // gen.writeCommand("(s1p" + formattedSize + "v0s0b25093T");
+ //break;
+ case 14: // F14 = Zapf Dingbats
+
+ return false;
+ //gen.writeCommand("(14L");
+ //gen.writeCommand("(s1p" + formattedSize + "v0s0b45101T");
+ //break;
+ default:
+ //gen.writeCommand("(0N");
+ //gen.writeCommand("(s" + formattedSize + "V");
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/pcl/PCLRenderer.java b/src/java/org/apache/fop/render/pcl/PCLRenderer.java
index 5aad34391..44631af4f 100644
--- a/src/java/org/apache/fop/render/pcl/PCLRenderer.java
+++ b/src/java/org/apache/fop/render/pcl/PCLRenderer.java
@@ -82,7 +82,6 @@ import org.apache.fop.area.inline.Viewport;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.datatypes.URISpecification;
import org.apache.fop.events.ResourceEventProducer;
-import org.apache.fop.fo.extensions.ExtensionElementMapping;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontCollection;
import org.apache.fop.fonts.FontInfo;
@@ -109,7 +108,7 @@ import org.apache.fop.traits.BorderProps;
/**
* Renderer for the PCL 5 printer language. It also uses HP GL/2 for certain graphic elements.
*/
-public class PCLRenderer extends PrintRenderer {
+public class PCLRenderer extends PrintRenderer implements PCLConstants {
/** logging instance */
private static Log log = LogFactory.getLog(PCLRenderer.class);
@@ -117,11 +116,6 @@ public class PCLRenderer extends PrintRenderer {
/** The MIME type for PCL */
public static final String MIME_TYPE = MimeConstants.MIME_PCL_ALT;
- private static final QName CONV_MODE
- = new QName(ExtensionElementMapping.URI, null, "conversion-mode");
- private static final QName SRC_TRANSPARENCY
- = new QName(ExtensionElementMapping.URI, null, "source-transparency");
-
/** The OutputStream to write the PCL stream to */
protected OutputStream out;
@@ -549,18 +543,7 @@ public class PCLRenderer extends PrintRenderer {
AffineTransform at = graphicContext.getTransform();
int newDir;
try {
- if (at.getScaleX() == 0 && at.getScaleY() == 0
- && at.getShearX() == 1 && at.getShearY() == -1) {
- newDir = 90;
- } else if (at.getScaleX() == -1 && at.getScaleY() == -1
- && at.getShearX() == 0 && at.getShearY() == 0) {
- newDir = 180;
- } else if (at.getScaleX() == 0 && at.getScaleY() == 0
- && at.getShearX() == -1 && at.getShearY() == 1) {
- newDir = 270;
- } else {
- newDir = 0;
- }
+ newDir = PCLRenderingUtil.determinePrintDirection(at);
if (newDir != this.currentPrintDirection) {
this.currentPrintDirection = newDir;
gen.changePrintDirection(this.currentPrintDirection);
diff --git a/src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java b/src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java
new file mode 100644
index 000000000..63906232d
--- /dev/null
+++ b/src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java
@@ -0,0 +1,147 @@
+/*
+ * 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.pcl;
+
+import java.awt.geom.AffineTransform;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.apps.FOUserAgent;
+
+/**
+ * Utility class for handling all sorts of peripheral tasks around PCL generation.
+ */
+public class PCLRenderingUtil {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(PCLRenderingUtil.class);
+
+ private FOUserAgent userAgent;
+
+ /**
+ * Controls whether appearance is more important than speed. False can cause some FO feature
+ * to be ignored (like the advanced borders).
+ */
+ private boolean qualityBeforeSpeed = false;
+
+ /**
+ * Controls whether all text should be painted as text. This is a fallback setting in case
+ * the mixture of native and bitmapped text does not provide the necessary quality.
+ */
+ private boolean allTextAsBitmaps = false;
+
+ /**
+ * Controls whether an RGB canvas is used when converting Java2D graphics to bitmaps.
+ * This can be used to work around problems with Apache Batik, for example, but setting
+ * this to true will increase memory consumption.
+ */
+ private boolean useColorCanvas = false;
+
+ /**
+ * Controls whether the generation of PJL commands gets disabled.
+ */
+ private boolean disabledPJL = false;
+
+ PCLRenderingUtil(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() {
+ }
+
+ /**
+ * Returns the user agent.
+ * @return the user agent
+ */
+ public FOUserAgent getUserAgent() {
+ return this.userAgent;
+ }
+
+ /**
+ * Configures the renderer to trade speed for quality if desired. One example here is the way
+ * that borders are rendered.
+ * @param qualityBeforeSpeed true if quality is more important than speed
+ */
+ public void setQualityBeforeSpeed(boolean qualityBeforeSpeed) {
+ this.qualityBeforeSpeed = qualityBeforeSpeed;
+ }
+
+ /**
+ * Controls whether PJL commands shall be generated by the PCL renderer.
+ * @param disable true to disable PJL commands
+ */
+ public void setPJLDisabled(boolean disable) {
+ this.disabledPJL = disable;
+ }
+
+ /**
+ * Indicates whether PJL generation is disabled.
+ * @return true if PJL generation is disabled.
+ */
+ public boolean isPJLDisabled() {
+ return this.disabledPJL;
+ }
+
+ /**
+ * Indicates whether all text shall be painted as bitmaps.
+ * @return true if all text shall be painted as bitmaps
+ */
+ public boolean isAllTextAsBitmaps() {
+ return this.allTextAsBitmaps;
+ }
+
+ /**
+ * Determines the print direction based on the given transformation matrix. This method
+ * only detects right angles (0, 90, 180, 270). If any other angle is determined, 0 is
+ * returned.
+ * @param transform the transformation matrix
+ * @return the angle in degrees of the print direction.
+ */
+ public static int determinePrintDirection(AffineTransform transform) {
+ int newDir;
+ if (transform.getScaleX() == 0 && transform.getScaleY() == 0
+ && transform.getShearX() == 1 && transform.getShearY() == -1) {
+ newDir = 90;
+ } else if (transform.getScaleX() == -1 && transform.getScaleY() == -1
+ && transform.getShearX() == 0 && transform.getShearY() == 0) {
+ newDir = 180;
+ } else if (transform.getScaleX() == 0 && transform.getScaleY() == 0
+ && transform.getShearX() == -1 && transform.getShearY() == 1) {
+ newDir = 270;
+ } else {
+ newDir = 0;
+ }
+ return newDir;
+ }
+
+
+}
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/PDFBorderPainter.java b/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java
new file mode 100644
index 000000000..019ec82a0
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/PDFBorderPainter.java
@@ -0,0 +1,325 @@
+/*
+ * 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.Point;
+import java.awt.Rectangle;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.fo.Constants;
+import org.apache.fop.render.intermediate.BorderPainter;
+import org.apache.fop.traits.RuleStyle;
+import org.apache.fop.util.ColorUtil;
+
+/**
+ * PDF-specific implementation of the {@code BorderPainter}.
+ */
+public class PDFBorderPainter extends BorderPainter {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(PDFBorderPainter.class);
+
+ private PDFContentGenerator generator;
+
+ public PDFBorderPainter(PDFContentGenerator generator) {
+ this.generator = generator;
+ }
+
+ /** {@inheritDoc} */
+ protected void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz,
+ boolean startOrBefore, int style, Color col) {
+ drawBorderLine(generator, x1 / 1000f, y1 / 1000f, x2 / 1000f, y2 / 1000f,
+ horz, startOrBefore, style, col);
+ }
+
+ /** {@inheritDoc} */
+ public static void drawBorderLine(PDFContentGenerator generator,
+ float x1, float y1, float x2, float y2, boolean horz,
+ boolean startOrBefore, int style, Color col) {
+ float w = x2 - x1;
+ float h = y2 - y1;
+ if ((w < 0) || (h < 0)) {
+ log.error("Negative extent received (w=" + w + ", h=" + h
+ + "). Border won't be painted.");
+ return;
+ }
+ switch (style) {
+ case Constants.EN_DASHED:
+ generator.setColor(col, false);
+ if (horz) {
+ float unit = Math.abs(2 * h);
+ int rep = (int)(w / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = w / rep;
+ generator.add("[" + format(unit) + "] 0 d ");
+ generator.add(format(h) + " w\n");
+ float ym = y1 + (h / 2);
+ generator.add(format(x1) + " " + format(ym) + " m "
+ + format(x2) + " " + format(ym) + " l S\n");
+ } else {
+ float unit = Math.abs(2 * w);
+ int rep = (int)(h / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = h / rep;
+ generator.add("[" + format(unit) + "] 0 d ");
+ generator.add(format(w) + " w\n");
+ float xm = x1 + (w / 2);
+ generator.add(format(xm) + " " + format(y1) + " m "
+ + format(xm) + " " + format(y2) + " l S\n");
+ }
+ break;
+ case Constants.EN_DOTTED:
+ generator.setColor(col, false);
+ generator.add("1 J ");
+ if (horz) {
+ float unit = Math.abs(2 * h);
+ int rep = (int)(w / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = w / rep;
+ generator.add("[0 " + format(unit) + "] 0 d ");
+ generator.add(format(h) + " w\n");
+ float ym = y1 + (h / 2);
+ generator.add(format(x1) + " " + format(ym) + " m "
+ + format(x2) + " " + format(ym) + " l S\n");
+ } else {
+ float unit = Math.abs(2 * w);
+ int rep = (int)(h / unit);
+ if (rep % 2 == 0) {
+ rep++;
+ }
+ unit = h / rep;
+ generator.add("[0 " + format(unit) + " ] 0 d ");
+ generator.add(format(w) + " w\n");
+ float xm = x1 + (w / 2);
+ generator.add(format(xm) + " " + format(y1) + " m "
+ + format(xm) + " " + format(y2) + " l S\n");
+ }
+ break;
+ case Constants.EN_DOUBLE:
+ generator.setColor(col, false);
+ generator.add("[] 0 d ");
+ if (horz) {
+ float h3 = h / 3;
+ generator.add(format(h3) + " w\n");
+ float ym1 = y1 + (h3 / 2);
+ float ym2 = ym1 + h3 + h3;
+ generator.add(format(x1) + " " + format(ym1) + " m "
+ + format(x2) + " " + format(ym1) + " l S\n");
+ generator.add(format(x1) + " " + format(ym2) + " m "
+ + format(x2) + " " + format(ym2) + " l S\n");
+ } else {
+ float w3 = w / 3;
+ generator.add(format(w3) + " w\n");
+ float xm1 = x1 + (w3 / 2);
+ float xm2 = xm1 + w3 + w3;
+ generator.add(format(xm1) + " " + format(y1) + " m "
+ + format(xm1) + " " + format(y2) + " l S\n");
+ generator.add(format(xm2) + " " + format(y1) + " m "
+ + format(xm2) + " " + format(y2) + " l S\n");
+ }
+ break;
+ case Constants.EN_GROOVE:
+ case Constants.EN_RIDGE:
+ {
+ float colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f);
+ generator.add("[] 0 d ");
+ if (horz) {
+ Color uppercol = ColorUtil.lightenColor(col, -colFactor);
+ Color lowercol = ColorUtil.lightenColor(col, colFactor);
+ float h3 = h / 3;
+ generator.add(format(h3) + " w\n");
+ float ym1 = y1 + (h3 / 2);
+ generator.setColor(uppercol, false);
+ generator.add(format(x1) + " " + format(ym1) + " m "
+ + format(x2) + " " + format(ym1) + " l S\n");
+ generator.setColor(col, false);
+ generator.add(format(x1) + " " + format(ym1 + h3) + " m "
+ + format(x2) + " " + format(ym1 + h3) + " l S\n");
+ 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 = ColorUtil.lightenColor(col, -colFactor);
+ Color rightcol = ColorUtil.lightenColor(col, colFactor);
+ float w3 = w / 3;
+ generator.add(format(w3) + " w\n");
+ float xm1 = x1 + (w3 / 2);
+ generator.setColor(leftcol, false);
+ generator.add(format(xm1) + " " + format(y1) + " m "
+ + format(xm1) + " " + format(y2) + " l S\n");
+ generator.setColor(col, false);
+ generator.add(format(xm1 + w3) + " " + format(y1) + " m "
+ + format(xm1 + w3) + " " + format(y2) + " l S\n");
+ generator.setColor(rightcol, false);
+ generator.add(format(xm1 + w3 + w3) + " " + format(y1) + " m "
+ + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n");
+ }
+ break;
+ }
+ case Constants.EN_INSET:
+ case Constants.EN_OUTSET:
+ {
+ float colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f);
+ generator.add("[] 0 d ");
+ Color c = col;
+ if (horz) {
+ c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
+ generator.add(format(h) + " w\n");
+ float ym1 = y1 + (h / 2);
+ generator.setColor(c, false);
+ generator.add(format(x1) + " " + format(ym1) + " m "
+ + format(x2) + " " + format(ym1) + " l S\n");
+ } else {
+ c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
+ generator.add(format(w) + " w\n");
+ float xm1 = x1 + (w / 2);
+ generator.setColor(c, false);
+ generator.add(format(xm1) + " " + format(y1) + " m "
+ + format(xm1) + " " + format(y2) + " l S\n");
+ }
+ break;
+ }
+ case Constants.EN_HIDDEN:
+ break;
+ default:
+ generator.setColor(col, false);
+ generator.add("[] 0 d ");
+ if (horz) {
+ generator.add(format(h) + " w\n");
+ float ym = y1 + (h / 2);
+ generator.add(format(x1) + " " + format(ym) + " m "
+ + format(x2) + " " + format(ym) + " l S\n");
+ } else {
+ generator.add(format(w) + " w\n");
+ float xm = x1 + (w / 2);
+ generator.add(format(xm) + " " + format(y1) + " m "
+ + format(xm) + " " + format(y2) + " l S\n");
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawLine(Point start, Point end,
+ int width, Color color, RuleStyle style) {
+ if (start.y != end.y) {
+ //TODO Support arbitrary lines if necessary
+ throw new UnsupportedOperationException(
+ "Can only deal with horizontal lines right now");
+ }
+
+ saveGraphicsState();
+ int half = width / 2;
+ int starty = start.y - half;
+ Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width);
+ switch (style.getEnumValue()) {
+ case Constants.EN_SOLID:
+ case Constants.EN_DASHED:
+ case Constants.EN_DOUBLE:
+ drawBorderLine(start.x, start.y - half, end.x, end.y + half,
+ true, true, style.getEnumValue(), color);
+ break;
+ case Constants.EN_DOTTED:
+ generator.clipRect(boundingRect);
+ //This displaces the dots to the right by half a dot's width
+ //TODO There's room for improvement here
+ generator.add("1 0 0 1 " + format(half) + " 0 cm\n");
+ drawBorderLine(start.x, start.y - half, end.x, end.y + half,
+ true, true, style.getEnumValue(), color);
+ break;
+ case Constants.EN_GROOVE:
+ case Constants.EN_RIDGE:
+ generator.setColor(ColorUtil.lightenColor(color, 0.6f), true);
+ generator.add(format(start.x) + " " + format(starty) + " m\n");
+ generator.add(format(end.x) + " " + format(starty) + " l\n");
+ generator.add(format(end.x) + " " + format(starty + 2 * half) + " l\n");
+ generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n");
+ generator.add("h\n");
+ generator.add("f\n");
+ generator.setColor(color, true);
+ if (style == RuleStyle.GROOVE) {
+ generator.add(format(start.x) + " " + format(starty) + " m\n");
+ generator.add(format(end.x) + " " + format(starty) + " l\n");
+ generator.add(format(end.x) + " " + format(starty + half) + " l\n");
+ generator.add(format(start.x + half) + " " + format(starty + half) + " l\n");
+ generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n");
+ } else {
+ generator.add(format(end.x) + " " + format(starty) + " m\n");
+ generator.add(format(end.x) + " " + format(starty + 2 * half) + " l\n");
+ generator.add(format(start.x) + " " + format(starty + 2 * half) + " l\n");
+ generator.add(format(start.x) + " " + format(starty + half) + " l\n");
+ generator.add(format(end.x - half) + " " + format(starty + half) + " l\n");
+ }
+ generator.add("h\n");
+ generator.add("f\n");
+ break;
+ default:
+ throw new UnsupportedOperationException("rule style not supported");
+ }
+ restoreGraphicsState();
+ }
+
+ static final String format(int coordinate) {
+ return format(coordinate / 1000f);
+ }
+
+ static final String format(float coordinate) {
+ return PDFContentGenerator.format(coordinate);
+ }
+
+ /** {@inheritDoc} */
+ protected void moveTo(int x, int y) {
+ generator.add(format(x) + " " + format(y) + " m ");
+ }
+
+ /** {@inheritDoc} */
+ protected void lineTo(int x, int y) {
+ generator.add(format(x) + " " + format(y) + " l ");
+ }
+
+ /** {@inheritDoc} */
+ protected void closePath() {
+ generator.add("h ");
+ }
+
+ /** {@inheritDoc} */
+ protected void clip() {
+ generator.add("W\n" + "n\n");
+ }
+
+ /** {@inheritDoc} */
+ protected void saveGraphicsState() {
+ generator.add("q\n");
+ }
+
+ /** {@inheritDoc} */
+ protected void restoreGraphicsState() {
+ generator.add("Q\n");
+ }
+
+}
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..35dc0960a
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java
@@ -0,0 +1,345 @@
+/*
+ * 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 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");
+ }
+ }
+
+ /**
+ * Intersects the current clip region with the given rectangle.
+ * @param rect the clip rectangle
+ */
+ public void clipRect(Rectangle rect) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(format(rect.x / 1000f)).append(' ');
+ sb.append(format(rect.y / 1000f)).append(' ');
+ sb.append(format(rect.width / 1000f)).append(' ');
+ sb.append(format(rect.height / 1000f)).append(" re W n\n");
+ add(sb.toString());
+ }
+
+ /**
+ * 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
+ */
+ public static final 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/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java
new file mode 100644
index 000000000..8937d0d1d
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java
@@ -0,0 +1,295 @@
+/*
+ * 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.Dimension;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+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.pdf.PDFAction;
+import org.apache.fop.pdf.PDFAnnotList;
+import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFOutline;
+import org.apache.fop.pdf.PDFPage;
+import org.apache.fop.pdf.PDFReference;
+import org.apache.fop.pdf.PDFResourceContext;
+import org.apache.fop.pdf.PDFResources;
+import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
+import org.apache.fop.render.intermediate.IFException;
+import org.apache.fop.render.intermediate.IFPainter;
+import org.apache.fop.render.intermediate.extensions.AbstractAction;
+import org.apache.fop.render.intermediate.extensions.Bookmark;
+import org.apache.fop.render.intermediate.extensions.BookmarkTree;
+import org.apache.fop.render.intermediate.extensions.GoToXYAction;
+import org.apache.fop.render.intermediate.extensions.NamedDestination;
+
+/**
+ * {@code IFDocumentHandler} implementation that produces PDF.
+ */
+public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(PDFDocumentHandler.class);
+
+ /** 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;
+
+ /** 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;
+
+ /** Used for bookmarks/outlines. */
+ protected Map pageReferences = new java.util.HashMap();
+
+ /**
+ * Default constructor.
+ */
+ public PDFDocumentHandler() {
+ }
+
+ /** {@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);
+ }
+
+ /** {@inheritDoc} */
+ public IFDocumentHandlerConfigurator getConfigurator() {
+ return new PDFRendererConfigurator(getUserAgent());
+ }
+
+ 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 endDocumentHeader() throws IFException {
+ pdfUtil.generateDefaultXMPMetadata();
+ }
+
+ /** {@inheritDoc} */
+ public void endDocument() throws IFException {
+ try {
+ pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
+ pdfDoc.outputTrailer(this.outputStream);
+
+ this.pdfDoc = null;
+
+ pdfResources = null;
+ this.generator = null;
+ currentContext = null;
+ currentPage = null;
+ } 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.pageReferences.put(new Integer(index), new PageReference(currentPage, size));
+
+ 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 IFPainter startPageContent() throws IFException {
+ return new PDFPainter(this);
+ }
+
+ /** {@inheritDoc} */
+ public void endPageContent() throws IFException {
+ //nop
+ }
+
+ /** {@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);
+ }
+ }
+
+ private void renderBookmarkTree(BookmarkTree tree) {
+ Iterator iter = tree.getBookmarks().iterator();
+ while (iter.hasNext()) {
+ Bookmark b = (Bookmark)iter.next();
+ renderBookmark(b, null);
+ }
+ }
+
+ private void renderBookmark(Bookmark bookmark, PDFOutline parent) {
+ if (parent == null) {
+ parent = pdfDoc.getOutlineRoot();
+ }
+ PDFAction action = getAction(bookmark.getAction());
+ PDFOutline pdfOutline = pdfDoc.getFactory().makeOutline(parent,
+ bookmark.getTitle(), action, bookmark.isShown());
+ Iterator iter = bookmark.getChildBookmarks().iterator();
+ while (iter.hasNext()) {
+ Bookmark b = (Bookmark)iter.next();
+ renderBookmark(b, pdfOutline);
+ }
+ }
+
+ private void renderNamedDestination(NamedDestination destination) {
+ PDFAction action = getAction(destination.getAction());
+ pdfDoc.getFactory().makeDestination(
+ destination.getName(), action.makeReference());
+ }
+
+ private PDFAction getAction(AbstractAction action) {
+ if (action instanceof GoToXYAction) {
+ GoToXYAction a = (GoToXYAction)action;
+ PageReference pageRef = (PageReference)this.pageReferences.get(
+ new Integer(a.getPageIndex()));
+ //Convert target location from millipoints to points and adjust for different
+ //page origin
+ Point2D p2d = new Point2D.Double(
+ a.getTargetLocation().x / 1000.0,
+ (pageRef.pageDimension.height - a.getTargetLocation().y) / 1000.0);
+ return pdfDoc.getFactory().getPDFGoTo(pageRef.pageRef.toString(), p2d);
+ } else {
+ throw new UnsupportedOperationException("Unsupported action type: "
+ + action + " (" + action.getClass().getName() + ")");
+ }
+ }
+
+ /** {@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 if (extension instanceof BookmarkTree) {
+ renderBookmarkTree((BookmarkTree)extension);
+ } else if (extension instanceof NamedDestination) {
+ renderNamedDestination((NamedDestination)extension);
+ } else {
+ log.debug("Don't know how to handle extension object. Ignoring: "
+ + extension + " (" + extension.getClass().getName() + ")");
+ }
+ }
+
+ private static final class PageReference {
+
+ private PDFReference pageRef;
+ private Dimension pageDimension;
+
+ private PageReference(PDFPage page, Dimension dim) {
+ this.pageRef = page.makeReference();
+ this.pageDimension = new Dimension(dim);
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java
new file mode 100644
index 000000000..88ae60d2c
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandlerMaker.java
@@ -0,0 +1,58 @@
+/*
+ * 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.AbstractIFDocumentHandlerMaker;
+import org.apache.fop.render.intermediate.IFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
+
+/**
+ * Intermediate format document handler factory for PDF output.
+ */
+public class PDFDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker {
+
+ //TODO Revert to normal MIME after stabilization!
+ private static final String[] MIMES = new String[] {MimeConstants.MIME_PDF + ";mode=painter"};
+
+ /** {@inheritDoc} */
+ public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) {
+ PDFDocumentHandler handler = new PDFDocumentHandler();
+ handler.setUserAgent(ua);
+ return handler;
+ }
+
+ /** {@inheritDoc} */
+ public boolean needsOutputStream() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getSupportedMimeTypes() {
+ return MIMES;
+ }
+
+ /** {@inheritDoc} */
+ public IFDocumentHandlerConfigurator getConfigurator(FOUserAgent userAgent) {
+ return new PDFRendererConfigurator(userAgent);
+ }
+
+}
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 2aa11227a..4da7f13cb 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 = pdfInfo.paintAsBitmap ? 1.0f : (fwidth / (float)imw);
float sy = pdfInfo.paintAsBitmap ? 1.0f : (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..3764486b7
--- /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..5273220e7
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java
@@ -0,0 +1,350 @@
+/*
+ * 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.Point;
+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.fop.apps.FOUserAgent;
+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.SingleByteFont;
+import org.apache.fop.fonts.Typeface;
+import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFNumber;
+import org.apache.fop.pdf.PDFTextUtil;
+import org.apache.fop.pdf.PDFXObject;
+import org.apache.fop.render.RenderingContext;
+import org.apache.fop.render.intermediate.AbstractIFPainter;
+import org.apache.fop.render.intermediate.IFException;
+import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.RuleStyle;
+import org.apache.fop.util.CharUtilities;
+
+/**
+ * IFPainter implementation that produces PDF.
+ */
+public class PDFPainter extends AbstractIFPainter {
+
+ /** logging instance */
+ private static Log log = LogFactory.getLog(PDFPainter.class);
+
+ private PDFDocumentHandler documentHandler;
+
+ /** Holds the intermediate format state */
+ protected IFState state;
+
+ /** The current content generator */
+ protected PDFContentGenerator generator;
+
+ private PDFBorderPainter borderPainter;
+
+ /**
+ * Default constructor.
+ * @param documentHandler the parent document handler
+ */
+ public PDFPainter(PDFDocumentHandler documentHandler) {
+ super();
+ this.documentHandler = documentHandler;
+ this.generator = documentHandler.generator;
+ this.borderPainter = new PDFBorderPainter(this.generator);
+ this.state = IFState.create();
+ }
+
+ /** {@inheritDoc} */
+ protected FOUserAgent getUserAgent() {
+ return this.documentHandler.getUserAgent();
+ }
+
+ PDFRenderingUtil getPDFUtil() {
+ return this.documentHandler.pdfUtil;
+ }
+
+ PDFDocument getPDFDoc() {
+ return this.documentHandler.pdfDoc;
+ }
+
+ FontInfo getFontInfo() {
+ return this.documentHandler.getFontInfo();
+ }
+
+ /** {@inheritDoc} */
+ public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
+ throws IFException {
+ generator.saveGraphicsState();
+ generator.concatenate(generator.toPoints(transform));
+ if (clipRect != null) {
+ clipRect(clipRect);
+ }
+ }
+
+ /** {@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 = getPDFDoc().getXObject(uri);
+ if (xobject != null) {
+ placeImage(rect, xobject);
+ return;
+ }
+
+ drawImageUsingURI(uri, rect);
+
+ flushPDFDoc();
+ }
+
+ /** {@inheritDoc} */
+ protected RenderingContext createRenderingContext() {
+ PDFRenderingContext pdfContext = new PDFRenderingContext(
+ getUserAgent(), generator, this.documentHandler.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);
+ }
+ }
+
+ /**
+ * 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 clipRect(Rectangle rect) throws IFException {
+ generator.endTextObject();
+ generator.clipRect(rect);
+ }
+
+ /** {@inheritDoc} */
+ public void fillRect(Rectangle rect, Paint fill) throws IFException {
+ if (fill == 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");
+ }
+ }
+ 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");
+ }
+ /* Removed from method signature as it is currently not used
+ if (stroke != null) {
+ sb.append(" S");
+ }*/
+ sb.append('\n');
+ generator.add(sb.toString());
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after,
+ BorderProps start, BorderProps end) throws IFException {
+ if (before != null || after != null || start != null || end != null) {
+ generator.endTextObject();
+ this.borderPainter.drawBorders(rect, before, after, start, end);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
+ throws IFException {
+ generator.endTextObject();
+ this.borderPainter.drawLine(start, end, width, color, style);
+ }
+
+ private Typeface getTypeface(String fontName) {
+ if (fontName == null) {
+ throw new NullPointerException("fontName must not be null");
+ }
+ Typeface tf = (Typeface)getFontInfo().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.updateColor(state.getTextColor(), true, null);
+ generator.beginTextObject();
+ FontTriplet triplet = new FontTriplet(
+ state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
+ //TODO Ignored: state.getFontVariant()
+ //TODO Opportunity for font caching if font state is more heavily used
+ String fontKey = getFontInfo().getInternalFontKey(triplet);
+ int sizeMillipoints = state.getFontSize();
+ float fontSize = sizeMillipoints / 1000f;
+
+ // 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 = getFontInfo().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] / 10f);
+ }
+ 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);
+ int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
+ glyphAdjust = -(10 * spaceDiff / fontSize);
+ } 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);
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java
index 8c016198b..730acb540 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;
@@ -74,7 +64,6 @@ import org.apache.fop.area.inline.TextArea;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.datatypes.URISpecification;
import org.apache.fop.events.ResourceEventProducer;
-import org.apache.fop.fo.Constants;
import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fo.extensions.xmp.XMPMetadata;
import org.apache.fop.fonts.Font;
@@ -84,78 +73,39 @@ 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;
import org.apache.fop.render.AbstractPathOrientedRenderer;
import org.apache.fop.render.Graphics2DAdapter;
import org.apache.fop.render.RendererContext;
+import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
-import org.apache.fop.util.ColorProfileUtil;
import org.apache.fop.util.ColorUtil;
/**
* 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;
@@ -165,11 +115,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
@@ -186,7 +136,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
@@ -217,10 +167,9 @@ 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;
+ private PDFBorderPainter borderPainter;
/**
* the current annotation list to add annotations to
@@ -237,246 +186,44 @@ 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();
+
/**
* create the PDF renderer
*/
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);
}
/**
@@ -499,9 +246,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
public void stopRenderer() throws IOException {
finishOpenGoTos();
@@ -514,13 +259,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();
@@ -547,7 +293,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);
}
}
}
@@ -607,68 +353,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();
}
/**
@@ -699,14 +406,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();
}
/**
@@ -732,30 +432,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}
@@ -775,40 +463,39 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
double h = bounds.getHeight();
pageHeight = (int) h;
- currentStream = this.pdfDoc.getFactory()
- .makeStream(PDFFilterList.CONTENT_FILTER, false);
- this.textutil = new PDFTextUtil() {
- protected void write(String code) {
- currentStream.add(code);
- }
- };
+ this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage);
+ this.borderPainter = new PDFBorderPainter(this.generator);
- 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,
@@ -817,7 +504,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} */
@@ -827,10 +515,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");
- }
+ }*/
}
/**
@@ -845,188 +535,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
/** {@inheritDoc} */
protected void drawBorderLine(float x1, float y1, float x2, float y2,
boolean horz, boolean startOrBefore, int style, Color col) {
- float w = x2 - x1;
- float h = y2 - y1;
- if ((w < 0) || (h < 0)) {
- log.error("Negative extent received (w=" + w + ", h=" + h
- + "). Border won't be painted.");
- return;
- }
- switch (style) {
- case Constants.EN_DASHED:
- setColor(col, false, null);
- if (horz) {
- float unit = Math.abs(2 * h);
- int rep = (int)(w / unit);
- if (rep % 2 == 0) {
- rep++;
- }
- unit = w / rep;
- currentStream.add("[" + format(unit) + "] 0 d ");
- currentStream.add(format(h) + " w\n");
- float ym = y1 + (h / 2);
- currentStream.add(format(x1) + " " + format(ym) + " m "
- + format(x2) + " " + format(ym) + " l S\n");
- } else {
- float unit = Math.abs(2 * w);
- int rep = (int)(h / unit);
- if (rep % 2 == 0) {
- rep++;
- }
- unit = h / rep;
- currentStream.add("[" + format(unit) + "] 0 d ");
- currentStream.add(format(w) + " w\n");
- float xm = x1 + (w / 2);
- currentStream.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 ");
- if (horz) {
- float unit = Math.abs(2 * h);
- int rep = (int)(w / unit);
- if (rep % 2 == 0) {
- rep++;
- }
- unit = w / rep;
- currentStream.add("[0 " + format(unit) + "] 0 d ");
- currentStream.add(format(h) + " w\n");
- float ym = y1 + (h / 2);
- currentStream.add(format(x1) + " " + format(ym) + " m "
- + format(x2) + " " + format(ym) + " l S\n");
- } else {
- float unit = Math.abs(2 * w);
- int rep = (int)(h / unit);
- if (rep % 2 == 0) {
- rep++;
- }
- unit = h / rep;
- currentStream.add("[0 " + format(unit) + " ] 0 d ");
- currentStream.add(format(w) + " w\n");
- float xm = x1 + (w / 2);
- currentStream.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 ");
- if (horz) {
- float h3 = h / 3;
- currentStream.add(format(h3) + " w\n");
- float ym1 = y1 + (h3 / 2);
- float ym2 = ym1 + h3 + h3;
- currentStream.add(format(x1) + " " + format(ym1) + " m "
- + format(x2) + " " + format(ym1) + " l S\n");
- currentStream.add(format(x1) + " " + format(ym2) + " m "
- + format(x2) + " " + format(ym2) + " l S\n");
- } else {
- float w3 = w / 3;
- currentStream.add(format(w3) + " w\n");
- float xm1 = x1 + (w3 / 2);
- float xm2 = xm1 + w3 + w3;
- currentStream.add(format(xm1) + " " + format(y1) + " m "
- + format(xm1) + " " + format(y2) + " l S\n");
- currentStream.add(format(xm2) + " " + format(y1) + " m "
- + format(xm2) + " " + format(y2) + " l S\n");
- }
- break;
- case Constants.EN_GROOVE:
- case Constants.EN_RIDGE:
- {
- float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
- currentStream.add("[] 0 d ");
- if (horz) {
- Color uppercol = ColorUtil.lightenColor(col, -colFactor);
- Color lowercol = ColorUtil.lightenColor(col, colFactor);
- float h3 = h / 3;
- currentStream.add(format(h3) + " w\n");
- float ym1 = y1 + (h3 / 2);
- setColor(uppercol, false, null);
- currentStream.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 "
- + format(x2) + " " + format(ym1 + h3) + " l S\n");
- setColor(lowercol, false, null);
- currentStream.add(format(x1) + " " + format(ym1 + h3 + h3) + " m "
- + format(x2) + " " + format(ym1 + h3 + h3) + " l S\n");
- } else {
- Color leftcol = ColorUtil.lightenColor(col, -colFactor);
- Color rightcol = ColorUtil.lightenColor(col, colFactor);
- float w3 = w / 3;
- currentStream.add(format(w3) + " w\n");
- float xm1 = x1 + (w3 / 2);
- setColor(leftcol, false, null);
- currentStream.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 "
- + format(xm1 + w3) + " " + format(y2) + " l S\n");
- setColor(rightcol, false, null);
- currentStream.add(format(xm1 + w3 + w3) + " " + format(y1) + " m "
- + format(xm1 + w3 + w3) + " " + format(y2) + " l S\n");
- }
- break;
- }
- case Constants.EN_INSET:
- case Constants.EN_OUTSET:
- {
- float colFactor = (style == EN_OUTSET ? 0.4f : -0.4f);
- currentStream.add("[] 0 d ");
- Color c = col;
- if (horz) {
- c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
- currentStream.add(format(h) + " w\n");
- float ym1 = y1 + (h / 2);
- setColor(c, false, null);
- currentStream.add(format(x1) + " " + format(ym1) + " m "
- + format(x2) + " " + format(ym1) + " l S\n");
- } else {
- c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
- currentStream.add(format(w) + " w\n");
- float xm1 = x1 + (w / 2);
- setColor(c, false, null);
- currentStream.add(format(xm1) + " " + format(y1) + " m "
- + format(xm1) + " " + format(y2) + " l S\n");
- }
- break;
- }
- case Constants.EN_HIDDEN:
- break;
- default:
- setColor(col, false, null);
- currentStream.add("[] 0 d ");
- if (horz) {
- currentStream.add(format(h) + " w\n");
- float ym = y1 + (h / 2);
- currentStream.add(format(x1) + " " + format(ym) + " m "
- + format(x2) + " " + format(ym) + " l S\n");
- } else {
- currentStream.add(format(w) + " w\n");
- float xm = x1 + (w / 2);
- currentStream.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");
- }
+ PDFBorderPainter.drawBorderLine(generator, x1, y1, x2, y2, horz, startOrBefore, style, col);
}
/** {@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();
}
@@ -1035,8 +549,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");
}
/**
@@ -1045,7 +558,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 ");
}
/**
@@ -1055,7 +568,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 ");
}
/**
@@ -1063,7 +576,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 ");
}
/**
@@ -1071,7 +584,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");
}
}
@@ -1085,8 +598,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");
}
/**
@@ -1097,15 +610,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(false);
}
return breakOutList;
}
@@ -1115,7 +628,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()) {
@@ -1127,7 +640,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.");
}
/**
@@ -1259,7 +772,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());
}
/**
@@ -1283,8 +796,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);
}
}
@@ -1347,7 +860,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();
@@ -1423,6 +936,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());
@@ -1466,7 +980,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
if (tws != 0) {
float adjust = tws / (font.getFontSize() / 1000f);
- textutil.adjustGlyphTJ(adjust);
+ generator.getTextUtil().adjustGlyphTJ(adjust);
}
}
@@ -1505,6 +1019,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
if (tf instanceof SingleByteFont) {
singleByteFont = (SingleByteFont)tf;
}
+ PDFTextUtil textutil = generator.getTextUtil();
int l = s.length();
@@ -1550,50 +1065,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} */
@@ -1652,8 +1126,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());
@@ -1692,7 +1167,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
}
@@ -1708,7 +1183,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)
@@ -1723,12 +1198,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));
@@ -1743,66 +1218,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
public void renderLeader(Leader area) {
renderInlineAreaBackAndBorders(area);
- currentState.push();
- saveGraphicsState();
int style = area.getRuleStyle();
- float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
- float starty = (currentBPPosition + area.getOffset()) / 1000f;
- float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
- + area.getIPD()) / 1000f;
- float ruleThickness = area.getRuleThickness() / 1000f;
+ int ruleThickness = area.getRuleThickness();
+ int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
+ int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2);
+ int endx = currentIPPosition
+ + area.getBorderAndPaddingWidthStart()
+ + area.getIPD();
Color col = (Color)area.getTrait(Trait.COLOR);
- switch (style) {
- case EN_SOLID:
- case EN_DASHED:
- case EN_DOUBLE:
- drawBorderLine(startx, starty, endx, starty + ruleThickness,
- true, true, style, col);
- break;
- case EN_DOTTED:
- 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");
- drawBorderLine(startx, starty, endx, starty + ruleThickness,
- true, true, style, col);
- break;
- case EN_GROOVE:
- case EN_RIDGE:
- float half = area.getRuleThickness() / 2000f;
-
- setColor(ColorUtil.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);
- 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");
- } 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");
- }
- currentStream.add("h\n");
- currentStream.add("f\n");
- break;
- default:
- throw new UnsupportedOperationException("rule style not supported");
- }
-
- restoreGraphicsState();
- currentState.pop();
- beginTextObject();
+ endTextObject();
+ borderPainter.drawLine(new Point(startx, starty), new Point(endx, starty),
+ ruleThickness, col, RuleStyle.valueOf(style));
super.renderLeader(area);
}
@@ -1816,7 +1243,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
* @param mode the PDF/A mode
*/
public void setAMode(PDFAMode mode) {
- this.pdfAMode = mode;
+ this.pdfUtil.setAMode(mode);
}
/**
@@ -1824,7 +1251,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
* @param mode the PDF/X mode
*/
public void setXMode(PDFXMode mode) {
- this.pdfXMode = mode;
+ this.pdfUtil.setXMode(mode);
}
/**
@@ -1832,7 +1259,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);
}
/**
@@ -1840,7 +1267,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
* @param filterMap the filter map
*/
public void setFilterMap(Map filterMap) {
- this.filterMap = filterMap;
+ this.pdfUtil.setFilterMap(filterMap);
}
/**
@@ -1848,7 +1275,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..d416f1147 100644
--- a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
@@ -27,18 +27,30 @@ 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.FontEventListener;
+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.IFDocumentHandler;
+import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator;
import org.apache.fop.util.LogUtil;
/**
- * PDF renderer configurator
+ * PDF renderer configurator.
*/
-public class PDFRendererConfigurator extends PrintRendererConfigurator {
+public class PDFRendererConfigurator extends PrintRendererConfigurator
+ implements IFDocumentHandlerConfigurator {
/**
* Default constructor
@@ -59,75 +71,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 +197,39 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
}
return filterMap;
}
+
+ // ---=== IFDocumentHandler configuration ===---
+
+ /** {@inheritDoc} */
+ public void configure(IFDocumentHandler documentHandler) throws FOPException {
+ Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
+ if (cfg != null) {
+ PDFDocumentHandler pdfDocumentHandler = (PDFDocumentHandler)documentHandler;
+ PDFRenderingUtil pdfUtil = pdfDocumentHandler.getPDFUtil();
+ configure(cfg, pdfUtil);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo)
+ throws FOPException {
+ FontManager fontManager = userAgent.getFactory().getFontManager();
+ List fontCollections = new java.util.ArrayList();
+ fontCollections.add(new Base14FontCollection(fontManager.isBase14KerningEnabled()));
+
+ Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
+ if (cfg != null) {
+ FontResolver fontResolver = new DefaultFontResolver(userAgent);
+ FontEventListener listener = new FontEventAdapter(
+ userAgent.getEventBroadcaster());
+ List fontList = buildFontList(cfg, fontResolver, listener);
+ fontCollections.add(new CustomFontCollection(fontResolver, fontList));
+ }
+
+ fontManager.setup(fontInfo,
+ (FontCollection[])fontCollections.toArray(
+ new FontCollection[fontCollections.size()]));
+ documentHandler.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/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java
index 38db7abdf..6e03d68dd 100644
--- a/src/java/org/apache/fop/render/xml/XMLRenderer.java
+++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java
@@ -82,7 +82,6 @@ import org.apache.fop.fo.Constants;
import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontTriplet;
-import org.apache.fop.render.PrintRenderer;
import org.apache.fop.render.Renderer;
import org.apache.fop.render.RendererContext;
import org.apache.fop.render.XMLHandler;
@@ -137,7 +136,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
}
/** {@inheritDoc} */
- public void setupFontInfo(FontInfo inFontInfo) {
+ public void setupFontInfo(FontInfo inFontInfo) throws FOPException {
if (mimic != null) {
mimic.setupFontInfo(inFontInfo);
} else {