diff options
Diffstat (limited to 'src/java/org/apache/fop/render')
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 { |