]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Added getUserAgent() to IFDocumentHandler (implemented by all implementations already).
authorJeremias Maerki <jeremias@apache.org>
Wed, 17 Dec 2008 15:01:21 +0000 (15:01 +0000)
committerJeremias Maerki <jeremias@apache.org>
Wed, 17 Dec 2008 15:01:21 +0000 (15:01 +0000)
Full image support for PSPainter.
PostScript output now uses the ImageHandler facility (IF and renderer) for both inline (ImageHandler interface) and form image production (PSImageHandler interface). No more hard-coded image flavor list.
Resource optimization extended so images that are only used once are inlined to lower memory requirements in the PostScript VM.
Added test cases using Commons' DSC parser to verify the new functionality.
Added IFDocumentHandler override possibility in FOUserAgent (just like for FOEventHandler and Renderer).
Subject support for PDF output.

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_AreaTreeNewDesign@727405 13f79535-47bb-0310-9956-ffa450edef68

25 files changed:
src/java/META-INF/services/org.apache.fop.render.ImageHandler
src/java/org/apache/fop/apps/FOUserAgent.java
src/java/org/apache/fop/render/RendererFactory.java
src/java/org/apache/fop/render/intermediate/AbstractIFDocumentHandler.java
src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java
src/java/org/apache/fop/render/intermediate/IFDocumentHandler.java
src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
src/java/org/apache/fop/render/ps/PSDocumentHandler.java
src/java/org/apache/fop/render/ps/PSImageHandler.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerEPS.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerRawCCITTFax.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerRawJPEG.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerRenderedImage.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSImageUtils.java
src/java/org/apache/fop/render/ps/PSPainter.java
src/java/org/apache/fop/render/ps/PSRenderer.java
src/java/org/apache/fop/render/ps/PSRenderingContext.java
src/java/org/apache/fop/render/ps/ResourceHandler.java
test/java/org/apache/fop/render/ps/AbstractPostScriptTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/ps/ImageHandlingTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/ps/ResourceOptimizationTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/ps/ps-jpeg-image.fo [new file with mode: 0644]
test/java/org/apache/fop/render/ps/ps-resources.fo [new file with mode: 0644]

index 58e2a52be9569c827aaa42f770850286319dcda4..730032927b6d75b6d6db5ddddd1d31812ab656fb 100644 (file)
@@ -7,3 +7,9 @@ org.apache.fop.render.java2d.Java2DImageHandlerRenderedImage
 org.apache.fop.render.java2d.Java2DImageHandlerGraphics2D\r
 org.apache.fop.render.pcl.PCLImageHandlerRenderedImage\r
 org.apache.fop.render.pcl.PCLImageHandlerGraphics2D\r
+org.apache.fop.render.ps.PSImageHandlerRenderedImage\r
+org.apache.fop.render.ps.PSImageHandlerEPS\r
+org.apache.fop.render.ps.PSImageHandlerRawCCITTFax\r
+org.apache.fop.render.ps.PSImageHandlerRawJPEG\r
+org.apache.fop.render.ps.PSImageHandlerGraphics2D\r
+org.apache.fop.render.ps.PSImageHandlerSVG\r
index f051dc28181dfc1b64e3dcd8a4ff5a3063903c01..ebd2e0594229e11e8e7157a64be2dc0ff0157f73 100644 (file)
@@ -46,6 +46,7 @@ import org.apache.fop.fo.FOEventHandler;
 import org.apache.fop.render.Renderer;
 import org.apache.fop.render.RendererFactory;
 import org.apache.fop.render.XMLHandlerRegistry;
+import org.apache.fop.render.intermediate.IFDocumentHandler;
 
 /**
  * This is the user agent for FOP.
@@ -92,6 +93,7 @@ public class FOUserAgent {
     private float targetResolution = FopFactoryConfigurator.DEFAULT_TARGET_RESOLUTION;
     private Map rendererOptions = new java.util.HashMap();
     private File outputFile = null;
+    private IFDocumentHandler documentHandlerOverride = null;
     private Renderer rendererOverride = null;
     private FOEventHandler foEventHandlerOverride = null;
     private boolean locatorEnabled = true; // true by default (for error messages).
@@ -116,6 +118,8 @@ public class FOUserAgent {
     protected String author = null;
     /** Title of the document. */
     protected String title = null;
+    /** Subject of the document. */
+    protected String subject = null;
     /** Set of keywords applicable to this document. */
     protected String keywords = null;
 
@@ -158,6 +162,24 @@ public class FOUserAgent {
 
     // ---------------------------------------------- rendering-run dependent stuff
 
+    /**
+     * Sets an explicit document handler to use which overrides the one that would be
+     * selected by default.
+     * @param documentHandler the document handler instance to use
+     */
+    public void setDocumentHandlerOverride(IFDocumentHandler documentHandler) {
+        this.documentHandlerOverride = documentHandler;
+
+    }
+
+    /**
+     * Returns the overriding {@link IFDocumentHandler} instance, if any.
+     * @return the overriding document handler or null
+     */
+    public IFDocumentHandler getDocumentHandlerOverride() {
+        return this.documentHandlerOverride;
+    }
+
     /**
      * Sets an explicit renderer to use which overrides the one defined by the
      * render type setting.
@@ -273,6 +295,22 @@ public class FOUserAgent {
         return title;
     }
 
+    /**
+     * Sets the subject of the document.
+     * @param subject of document
+     */
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    /**
+     * Returns the subject of the document
+     * @return the subject
+     */
+    public String getSubject() {
+        return subject;
+    }
+
     /**
      * Sets the keywords for the document.
      * @param keywords for the document
index 721ffa131047d5953886f5649e6aaf582c2dac1a..19cf76931a4dffa966c47dcf47b36c3035f7a776 100644 (file)
@@ -231,7 +231,9 @@ public class RendererFactory {
      */
     public Renderer createRenderer(FOUserAgent userAgent, String outputFormat)
                     throws FOPException {
-        if (userAgent.getRendererOverride() != null) {
+        if (userAgent.getDocumentHandlerOverride() != null) {
+            return createRendererForDocumentHandler(userAgent.getDocumentHandlerOverride());
+        } else if (userAgent.getRendererOverride() != null) {
             return userAgent.getRendererOverride();
         } else {
             AbstractRendererMaker maker = getRendererMaker(outputFormat);
@@ -247,12 +249,9 @@ public class RendererFactory {
                 AbstractIFDocumentHandlerMaker documentHandlerMaker
                     = getDocumentHandlerMaker(outputFormat);
                 if (documentHandlerMaker != null) {
-                    IFRenderer rend = new IFRenderer();
-                    rend.setUserAgent(userAgent);
                     IFDocumentHandler documentHandler = createDocumentHandler(
                             userAgent, outputFormat);
-                    rend.setDocumentHandler(documentHandler);
-                    return rend;
+                    return createRendererForDocumentHandler(documentHandler);
                 } else {
                     throw new UnsupportedOperationException(
                             "No renderer for the requested format available: " + outputFormat);
@@ -261,6 +260,13 @@ public class RendererFactory {
         }
     }
 
+    private Renderer createRendererForDocumentHandler(IFDocumentHandler documentHandler) {
+        IFRenderer rend = new IFRenderer();
+        rend.setUserAgent(documentHandler.getUserAgent());
+        rend.setDocumentHandler(documentHandler);
+        return rend;
+    }
+
     /**
      * Creates FOEventHandler instances based on the desired output.
      * @param userAgent the user agent for access to configuration
@@ -319,23 +325,17 @@ public class RendererFactory {
      */
     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);
-            IFDocumentHandlerConfigurator configurator = documentHandler.getConfigurator();
-            if (configurator != null) {
-                configurator.configure(documentHandler);
-            }
-            return documentHandler;
-        //}
+        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);
+        IFDocumentHandlerConfigurator configurator = documentHandler.getConfigurator();
+        if (configurator != null) {
+            configurator.configure(documentHandler);
+        }
+        return documentHandler;
     }
 
     /**
index 4894604a261035b24feab1503b51ab7f9eb5441f..a7b2757a860774d0ffb9056d9c6d1982310b0444 100644 (file)
@@ -48,10 +48,7 @@ public abstract class AbstractIFDocumentHandler implements IFDocumentHandler {
         this.userAgent = ua;
     }
 
-    /**
-     * Returns the user agent.
-     * @return the user agent
-     */
+    /** {@inheritDoc} */
     public FOUserAgent getUserAgent() {
         return this.userAgent;
     }
index 07d8334d5674394ef8fd6f89b814d2b8261a18e7..ea74a37c840dad5c2bee50a55cdf26a0597893c7 100644 (file)
@@ -208,6 +208,34 @@ public abstract class AbstractIFPainter implements IFPainter {
         handler.handleImage(context, effImage, rect);
     }
 
+    /**
+     * Returns an ImageInfo instance for the given URI. If there's an error, null is returned.
+     * The caller can assume that any exceptions have already been handled properly. The caller
+     * simply skips painting anything in this case.
+     * @param uri the URI identifying the image
+     * @return the ImageInfo instance or null if there has been an error.
+     */
+    protected ImageInfo getImageInfo(String uri) {
+        ImageManager manager = getFopFactory().getImageManager();
+        try {
+            ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
+            return manager.getImageInfo(uri, sessionContext);
+        } catch (ImageException ie) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageError(this, uri, ie, null);
+        } catch (FileNotFoundException fe) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageNotFound(this, uri, fe, null);
+        } catch (IOException ioe) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    getUserAgent().getEventBroadcaster());
+            eventProducer.imageIOError(this, uri, ioe, null);
+        }
+        return null;
+    }
+
     /**
      * Default drawing method for handling an image referenced by a URI.
      * @param uri the image's URI
index f1d6e2057beea25c99d0dc1d9ddd3397f46eb680..ea07deee64e9edf3184b745b1e9c30ef4ca29669 100644 (file)
@@ -81,6 +81,12 @@ public interface IFDocumentHandler {
      */
     void setUserAgent(FOUserAgent userAgent);
 
+    /**
+     * Returns the associated user agent.
+     * @return the user agent
+     */
+    FOUserAgent getUserAgent();
+
     /**
      * Sets the JAXP Result object to receive the generated content.
      * @param result the JAXP Result object to receive the generated content
@@ -199,7 +205,8 @@ public interface IFDocumentHandler {
      * @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, String pageMasterName, Dimension size) throws IFException;
+    void startPage(int index, String name, String pageMasterName, Dimension size)
+        throws IFException;
 
     /**
      * Indicates the end of a page
index e44edf8afd0c23e37e6041fb94d6ebeba0e1c610..2e3c8312636e6ebe4e58af2bfa9d917325c07fa1 100644 (file)
@@ -234,6 +234,7 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
         info.setCreationDate(userAgent.getCreationDate());
         info.setAuthor(userAgent.getAuthor());
         info.setTitle(userAgent.getTitle());
+        info.setSubject(userAgent.getSubject());
         info.setKeywords(userAgent.getKeywords());
     }
 
index 8223ec29b3af4a6cf951de6485ee7b06fc5f8baa..365df2fe3c05adbbb907da3e28655ec5f9e4c358 100644 (file)
@@ -268,8 +268,9 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
         in = new java.io.BufferedInputStream(in);
         try {
             try {
-                ResourceHandler.process(getUserAgent(), in, this.outputStream,
-                        this.fontInfo, resTracker, this.formResources,
+                ResourceHandler handler = new ResourceHandler(getUserAgent(), this.fontInfo,
+                        resTracker, this.formResources);
+                handler.process(in, this.outputStream,
                         this.currentPageNumber, this.documentBoundingBox);
                 this.outputStream.flush();
             } catch (DSCException e) {
@@ -519,4 +520,24 @@ public class PSDocumentHandler extends AbstractBinaryWritingIFDocumentHandler {
         return res;
     }
 
+    /**
+     * Returns a PSResource instance representing a image as a PostScript form.
+     * @param uri the image URI
+     * @return a PSResource instance
+     */
+    protected PSResource getFormForImage(String uri) {
+        if (uri == null || "".equals(uri)) {
+            throw new IllegalArgumentException("uri must not be empty or null");
+        }
+        if (this.formResources == null) {
+            this.formResources = new java.util.HashMap();
+        }
+        PSResource form = (PSResource)this.formResources.get(uri);
+        if (form == null) {
+            form = new PSImageFormResource(this.formResources.size() + 1, uri);
+            this.formResources.put(uri, form);
+        }
+        return form;
+    }
+
 }
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandler.java b/src/java/org/apache/fop/render/ps/PSImageHandler.java
new file mode 100644 (file)
index 0000000..ff94fdd
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.ps;
+
+import java.io.IOException;
+
+import org.apache.xmlgraphics.image.loader.Image;
+
+import org.apache.fop.render.ImageHandler;
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Specialized image handler interface for PostScript output. Implementations can optionally
+ * support creating PostScript forms. The implementation shall check the rendering context
+ * to see if forms functionality is enabled in the
+ * {@link #isCompatible(org.apache.fop.render.RenderingContext, org.apache.xmlgraphics.image.loader.Image)}
+ * method.
+ */
+public interface PSImageHandler extends ImageHandler {
+
+    /**
+     * Generates a PostScript form for the given {@link Image} instance.
+     * @param context the rendering context
+     * @param image the image to be handled
+     * @param form the associated form resource
+     * @throws IOException if an I/O error occurs
+     */
+    void generateForm(RenderingContext context, Image image, PSImageFormResource form)
+        throws IOException;
+
+}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerEPS.java b/src/java/org/apache/fop/render/ps/PSImageHandlerEPS.java
new file mode 100644 (file)
index 0000000..b77d481
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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.ps;
+
+import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.xmlgraphics.image.loader.Image;
+import org.apache.xmlgraphics.image.loader.ImageFlavor;
+import org.apache.xmlgraphics.image.loader.ImageInfo;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawEPS;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSImageUtils;
+
+import org.apache.fop.render.ImageHandler;
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation which handles EPS images for PostScript output.
+ */
+public class PSImageHandlerEPS implements ImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
+        ImageFlavor.RAW_EPS
+    };
+
+    /** {@inheritDoc} */
+    public void handleImage(RenderingContext context, Image image, Rectangle pos)
+                throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRawEPS eps = (ImageRawEPS)image;
+
+        float x = (float)pos.getX() / 1000f;
+        float y = (float)pos.getY() / 1000f;
+        float w = (float)pos.getWidth() / 1000f;
+        float h = (float)pos.getHeight() / 1000f;
+
+        ImageInfo info = image.getInfo();
+        Rectangle2D bbox = eps.getBoundingBox();
+        if (bbox == null) {
+            bbox = new Rectangle2D.Double();
+            bbox.setFrame(new Point2D.Double(), info.getSize().getDimensionPt());
+        }
+        InputStream in = eps.createInputStream();
+        try {
+            String resourceName = info.getOriginalURI();
+            if (resourceName == null) {
+                resourceName = "inline image";
+            }
+            PSImageUtils.renderEPS(in, resourceName,
+                    new Rectangle2D.Float(x, y, w, h),
+                    bbox,
+                    gen);
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 200;
+    }
+
+    /** {@inheritDoc} */
+    public Class getSupportedImageClass() {
+        return ImageRawEPS.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        if (targetContext instanceof PSRenderingContext) {
+            PSRenderingContext psContext = (PSRenderingContext)targetContext;
+            return !psContext.isCreateForms()
+                && (image == null || image instanceof ImageRawEPS);
+        }
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/ps/PSImageHandlerGraphics2D.java
new file mode 100644 (file)
index 0000000..b2934d4
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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.ps;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Dimension2D;
+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.xmlgraphics.java2d.Graphics2DImagePainter;
+import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
+import org.apache.xmlgraphics.ps.FormGenerator;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSProcSets;
+
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation which handles vector graphics (Java2D) for PostScript output.
+ */
+public class PSImageHandlerGraphics2D implements PSImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
+        ImageFlavor.GRAPHICS2D
+    };
+
+    /** {@inheritDoc} */
+    public void handleImage(RenderingContext context, Image image, Rectangle pos)
+                throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageGraphics2D imageG2D = (ImageGraphics2D)image;
+        Graphics2DImagePainter painter = imageG2D.getGraphics2DImagePainter();
+
+        float fx = (float)pos.getX() / 1000f;
+        float fy = (float)pos.getY() / 1000f;
+        float fwidth = (float)pos.getWidth() / 1000f;
+        float fheight = (float)pos.getHeight() / 1000f;
+
+        // get the 'width' and 'height' attributes of the SVG document
+        Dimension dim = painter.getImageSize();
+        float imw = (float)dim.getWidth() / 1000f;
+        float imh = (float)dim.getHeight() / 1000f;
+
+        float sx = fwidth / (float)imw;
+        float sy = fheight / (float)imh;
+
+        gen.commentln("%FOPBeginGraphics2D");
+        gen.saveGraphicsState();
+        final boolean clip = false;
+        if (clip) {
+            // Clip to the image area.
+            gen.writeln("newpath");
+            gen.defineRect(fx, fy, fwidth, fheight);
+            gen.writeln("clip");
+        }
+
+        // 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.
+        gen.concatMatrix(sx, 0, 0, sy, fx, fy);
+
+        final boolean textAsShapes = false;
+        PSGraphics2D graphics = new PSGraphics2D(textAsShapes, gen);
+        graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
+        AffineTransform transform = new AffineTransform();
+        // scale to viewbox
+        transform.translate(fx, fy);
+        gen.getCurrentState().concatMatrix(transform);
+        Rectangle2D area = new Rectangle2D.Double(0.0, 0.0, imw, imh);
+        painter.paint(graphics, area);
+        gen.restoreGraphicsState();
+        gen.commentln("%FOPEndGraphics2D");
+    }
+
+    /** {@inheritDoc} */
+    public void generateForm(RenderingContext context, Image image, PSImageFormResource form)
+            throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        final ImageGraphics2D imageG2D = (ImageGraphics2D)image;
+        ImageInfo info = image.getInfo();
+        String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
+        final Dimension2D dimensionsPt = info.getSize().getDimensionPt();
+        final Dimension2D dimensionsMpt = info.getSize().getDimensionMpt();
+
+        FormGenerator formGen = new FormGenerator(
+                form.getName(), imageDescription, dimensionsPt) {
+
+            protected void generatePaintProc(PSGenerator gen)
+                    throws IOException {
+                gen.getResourceTracker().notifyResourceUsageOnPage(
+                        PSProcSets.EPS_PROCSET);
+                gen.writeln("BeginEPSF");
+                PSGraphics2DAdapter adapter = new PSGraphics2DAdapter(gen, false);
+                adapter.paintImage(imageG2D.getGraphics2DImagePainter(),
+                        null,
+                        0, 0,
+                        (int)Math.round(dimensionsMpt.getWidth()),
+                        (int)Math.round(dimensionsMpt.getHeight()));
+                gen.writeln("EndEPSF");
+            }
+
+        };
+        formGen.generate(gen);
+    }
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 200;
+    }
+
+    /** {@inheritDoc} */
+    public Class getSupportedImageClass() {
+        return ImageGraphics2D.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        if (targetContext instanceof PSRenderingContext) {
+            return (image == null || image instanceof ImageGraphics2D);
+        }
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerRawCCITTFax.java b/src/java/org/apache/fop/render/ps/PSImageHandlerRawCCITTFax.java
new file mode 100644 (file)
index 0000000..1bbbf31
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.ps;
+
+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.ImageRawCCITTFax;
+import org.apache.xmlgraphics.ps.FormGenerator;
+import org.apache.xmlgraphics.ps.ImageEncoder;
+import org.apache.xmlgraphics.ps.ImageFormGenerator;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSImageUtils;
+
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation which handles CCITT fax images for PostScript output.
+ */
+public class PSImageHandlerRawCCITTFax implements PSImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
+        ImageFlavor.RAW_CCITTFAX
+    };
+
+    /** {@inheritDoc} */
+    public void handleImage(RenderingContext context, Image image, Rectangle pos)
+                throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRawCCITTFax ccitt = (ImageRawCCITTFax)image;
+
+        float x = (float)pos.getX() / 1000f;
+        float y = (float)pos.getY() / 1000f;
+        float w = (float)pos.getWidth() / 1000f;
+        float h = (float)pos.getHeight() / 1000f;
+        Rectangle2D targetRect = new Rectangle2D.Float(
+                x, y, w, h);
+
+        ImageInfo info = image.getInfo();
+
+        ImageEncoder encoder = new ImageEncoderCCITTFax(ccitt);
+        PSImageUtils.writeImage(encoder, info.getSize().getDimensionPx(),
+                info.getOriginalURI(), targetRect,
+                ccitt.getColorSpace(), 1, false, gen);
+    }
+
+    /** {@inheritDoc} */
+    public void generateForm(RenderingContext context, Image image, PSImageFormResource form)
+                throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRawCCITTFax ccitt = (ImageRawCCITTFax)image;
+        ImageInfo info = image.getInfo();
+        String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
+
+        ImageEncoder encoder = new ImageEncoderCCITTFax(ccitt);
+        FormGenerator formGen = new ImageFormGenerator(
+                form.getName(), imageDescription,
+                info.getSize().getDimensionPt(),
+                info.getSize().getDimensionPx(),
+                encoder,
+                ccitt.getColorSpace(), 1, false);
+        formGen.generate(gen);
+    }
+
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 200;
+    }
+
+    /** {@inheritDoc} */
+    public Class getSupportedImageClass() {
+        return ImageRawCCITTFax.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        if (targetContext instanceof PSRenderingContext) {
+            PSRenderingContext psContext = (PSRenderingContext)targetContext;
+            //The filters required for this implementation need PS level 2 or higher
+            if (psContext.getGenerator().getPSLevel() >= 2) {
+                return (image == null || image instanceof ImageRawCCITTFax);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerRawJPEG.java
new file mode 100644 (file)
index 0000000..deedf3b
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.ps;
+
+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.ImageRawJPEG;
+import org.apache.xmlgraphics.ps.FormGenerator;
+import org.apache.xmlgraphics.ps.ImageEncoder;
+import org.apache.xmlgraphics.ps.ImageFormGenerator;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSImageUtils;
+
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation which handles undecoded JPEG images for PostScript output.
+ */
+public class PSImageHandlerRawJPEG implements PSImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
+        ImageFlavor.RAW_JPEG
+    };
+
+    /** {@inheritDoc} */
+    public void handleImage(RenderingContext context, Image image, Rectangle pos)
+                throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRawJPEG jpeg = (ImageRawJPEG)image;
+
+        float x = (float)pos.getX() / 1000f;
+        float y = (float)pos.getY() / 1000f;
+        float w = (float)pos.getWidth() / 1000f;
+        float h = (float)pos.getHeight() / 1000f;
+        Rectangle2D targetRect = new Rectangle2D.Float(
+                x, y, w, h);
+
+        ImageInfo info = image.getInfo();
+
+        ImageEncoder encoder = new ImageEncoderJPEG(jpeg);
+        PSImageUtils.writeImage(encoder, info.getSize().getDimensionPx(),
+                info.getOriginalURI(), targetRect,
+                jpeg.getColorSpace(), 8, jpeg.isInverted(), gen);
+    }
+
+    /** {@inheritDoc} */
+    public void generateForm(RenderingContext context, Image image, PSImageFormResource form)
+            throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRawJPEG jpeg = (ImageRawJPEG)image;
+        ImageInfo info = image.getInfo();
+        String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
+
+        ImageEncoder encoder = new ImageEncoderJPEG(jpeg);
+        FormGenerator formGen = new ImageFormGenerator(
+                form.getName(), imageDescription,
+                info.getSize().getDimensionPt(),
+                info.getSize().getDimensionPx(),
+                encoder,
+                jpeg.getColorSpace(), jpeg.isInverted());
+        formGen.generate(gen);
+    }
+
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 200;
+    }
+
+    /** {@inheritDoc} */
+    public Class getSupportedImageClass() {
+        return ImageRawJPEG.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        if (targetContext instanceof PSRenderingContext) {
+            PSRenderingContext psContext = (PSRenderingContext)targetContext;
+            //The filters required for this implementation need PS level 2 or higher
+            if (psContext.getGenerator().getPSLevel() >= 2) {
+                return (image == null || image instanceof ImageRawJPEG);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/ps/PSImageHandlerRenderedImage.java
new file mode 100644 (file)
index 0000000..5a13c1c
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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.ps;
+
+import java.awt.Rectangle;
+import java.awt.image.RenderedImage;
+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.ImageRendered;
+import org.apache.xmlgraphics.ps.FormGenerator;
+import org.apache.xmlgraphics.ps.ImageFormGenerator;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSImageUtils;
+
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * Image handler implementation which handles RenderedImage instances for PostScript output.
+ */
+public class PSImageHandlerRenderedImage implements PSImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
+        ImageFlavor.BUFFERED_IMAGE,
+        ImageFlavor.RENDERED_IMAGE
+    };
+
+    /** {@inheritDoc} */
+    public void handleImage(RenderingContext context, Image image, Rectangle pos)
+                throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRendered imageRend = (ImageRendered)image;
+
+        float x = (float)pos.getX() / 1000f;
+        float y = (float)pos.getY() / 1000f;
+        float w = (float)pos.getWidth() / 1000f;
+        float h = (float)pos.getHeight() / 1000f;
+
+        RenderedImage ri = imageRend.getRenderedImage();
+        PSImageUtils.renderBitmapImage(ri, x, y, w, h, gen);
+    }
+
+    /** {@inheritDoc} */
+    public void generateForm(RenderingContext context, Image image, PSImageFormResource form)
+            throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageRendered imageRend = (ImageRendered)image;
+        ImageInfo info = image.getInfo();
+        String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
+
+        RenderedImage ri = imageRend.getRenderedImage();
+        FormGenerator formGen = new ImageFormGenerator(
+                form.getName(), imageDescription,
+                info.getSize().getDimensionPt(),
+                ri, false);
+        formGen.generate(gen);
+    }
+
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 300;
+    }
+
+    /** {@inheritDoc} */
+    public Class getSupportedImageClass() {
+        return ImageRendered.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        return (image == null || image instanceof ImageRendered)
+                && targetContext instanceof PSRenderingContext;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java b/src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java
new file mode 100644 (file)
index 0000000..2138e3c
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * 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.ps;
+
+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.gvt.GraphicsNode;
+
+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.xmlgraphics.java2d.ps.PSGraphics2D;
+import org.apache.xmlgraphics.ps.PSGenerator;
+
+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.SVGEventProducer;
+import org.apache.fop.svg.SVGUserAgent;
+
+/**
+ * Image handler implementation which handles SVG images for PostScript output.
+ */
+public class PSImageHandlerSVG implements ImageHandler {
+
+    private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
+        BatikImageFlavors.SVG_DOM
+    };
+
+    /** {@inheritDoc} */
+    public void handleImage(RenderingContext context, Image image, Rectangle pos)
+                throws IOException {
+        PSRenderingContext psContext = (PSRenderingContext)context;
+        PSGenerator gen = psContext.getGenerator();
+        ImageXMLDOM imageSVG = (ImageXMLDOM)image;
+
+        //Controls whether text painted by Batik is generated using text or path operations
+        boolean strokeText = false;
+        //TODO Configure text stroking
+
+        SVGUserAgent ua
+             = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
+
+        PSGraphics2D graphics = new PSGraphics2D(strokeText, gen);
+        graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
+
+        GVTBuilder builder = new GVTBuilder();
+        NativeTextHandler nativeTextHandler = null;
+        BridgeContext ctx = new BridgeContext(ua);
+        if (!strokeText) {
+            nativeTextHandler = new NativeTextHandler(graphics, psContext.getFontInfo());
+            graphics.setCustomTextHandler(nativeTextHandler);
+            PSTextPainter textPainter = new PSTextPainter(nativeTextHandler);
+            ctx.setTextPainter(textPainter);
+            PSTextElementBridge tBridge = new PSTextElementBridge(textPainter);
+            ctx.putBridge(tBridge);
+        }
+
+        GraphicsNode root;
+        try {
+            root = builder.build(ctx, imageSVG.getDocument());
+        } 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 / w;
+        float sy = pos.height / h;
+
+        ctx = null;
+        builder = null;
+
+        gen.commentln("%FOPBeginSVG");
+        gen.saveGraphicsState();
+        final boolean clip = false;
+        if (clip) {
+            /*
+             * Clip to the svg area.
+             * Note: To have the svg overlay (under) a text area then use
+             * an fo:block-container
+             */
+            gen.writeln("newpath");
+            gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f,
+                    pos.width / 1000f, pos.height / 1000f);
+            gen.writeln("clip");
+        }
+
+        // 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.
+        gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f);
+
+        AffineTransform transform = new AffineTransform();
+        // scale to viewbox
+        transform.translate(pos.getMinX(), pos.getMinY());
+        gen.getCurrentState().concatMatrix(transform);
+        try {
+            root.paint(graphics);
+        } catch (Exception e) {
+            SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                    context.getUserAgent().getEventBroadcaster());
+            eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI());
+        }
+
+        gen.restoreGraphicsState();
+        gen.commentln("%FOPEndSVG");
+    }
+
+    /** {@inheritDoc} */
+    public int getPriority() {
+        return 400;
+    }
+
+    /** {@inheritDoc} */
+    public Class getSupportedImageClass() {
+        return ImageXMLDOM.class;
+    }
+
+    /** {@inheritDoc} */
+    public ImageFlavor[] getSupportedImageFlavors() {
+        return FLAVORS;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible(RenderingContext targetContext, Image image) {
+        if (targetContext instanceof PSRenderingContext) {
+            PSRenderingContext psContext = (PSRenderingContext)targetContext;
+            return !psContext.isCreateForms()
+                && (image == null || (image instanceof ImageXMLDOM
+                        && image.getFlavor().isCompatible(BatikImageFlavors.SVG_DOM)));
+        }
+        return false;
+    }
+
+}
index 7a011fbecbe94e55fe437ee4f062b4d9ae70712c..431071701897110c44b20e46157296ff5b79f673 100644 (file)
 
 package org.apache.fop.render.ps;
 
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+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.pipeline.ImageProviderPipeline;
+import org.apache.xmlgraphics.ps.DSCConstants;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSResource;
+
+import org.apache.fop.render.ImageHandlerRegistry;
+import org.apache.fop.render.RenderingContext;
+
 /**
  * Utility code for rendering images in PostScript.
- * @deprecated Kept for compatibility with older FOP extensions (like Barcode4J). Use the
- * super-class instead.
  */
 public class PSImageUtils extends org.apache.xmlgraphics.ps.PSImageUtils {
 
+    /**
+     * Indicates whether the given image (identified by an {@link ImageInfo} object) shall be
+     * inlined rather than generated as a PostScript form.
+     * @param info the info object for the image
+     * @param renderingContext the rendering context
+     * @return true if the image shall be inlined, false if forms shall be used.
+     */
+    public static boolean isImageInlined(ImageInfo info, PSRenderingContext renderingContext) {
+        String uri = info.getOriginalURI();
+        if (uri == null || "".equals(uri)) {
+            return true;
+        }
+        //Investigate choice for inline mode
+        ImageFlavor[] inlineFlavors = determineSupportedImageFlavors(renderingContext);
+        ImageManager manager = renderingContext.getUserAgent().getFactory().getImageManager();
+        ImageProviderPipeline[] inlineCandidates
+            = manager.getPipelineFactory().determineCandidatePipelines(
+                    info, inlineFlavors);
+        ImageProviderPipeline inlineChoice = manager.choosePipeline(inlineCandidates);
+        ImageFlavor inlineFlavor = (inlineChoice != null
+                ? inlineChoice.getTargetFlavor() : null);
+
+        //Create a rendering context for form creation
+        PSRenderingContext formContext = renderingContext.toFormContext();
+
+        //Investigate choice for form mode
+        ImageFlavor[] formFlavors = determineSupportedImageFlavors(formContext);
+        ImageProviderPipeline[] formCandidates
+            = manager.getPipelineFactory().determineCandidatePipelines(
+                    info, formFlavors);
+        ImageProviderPipeline formChoice = manager.choosePipeline(formCandidates);
+        ImageFlavor formFlavor = (formChoice != null ? formChoice.getTargetFlavor() : null);
+
+        //Inline if form is not supported or if a better choice is available with inline mode
+        return formFlavor == null || !formFlavor.equals(inlineFlavor);
+    }
+
+    private static ImageFlavor[] determineSupportedImageFlavors(RenderingContext renderingContext) {
+        ImageFlavor[] inlineFlavors;
+        ImageHandlerRegistry imageHandlerRegistry
+            = renderingContext.getUserAgent().getFactory().getImageHandlerRegistry();
+        inlineFlavors = imageHandlerRegistry.getSupportedFlavors(renderingContext);
+        return inlineFlavors;
+    }
+
+    /**
+     * Draws a form at a given location.
+     * @param form the form resource
+     * @param info the image info object representing the image in the form
+     * @param rect the target rectangle (coordinates in millipoints)
+     * @param generator the PostScript generator
+     * @throws IOException if an I/O error occurs
+     */
+    public static void drawForm(PSResource form, ImageInfo info, Rectangle rect,
+            PSGenerator generator) throws IOException {
+        Rectangle2D targetRect = new Rectangle2D.Double(
+            rect.getMinX() / 1000.0,
+            rect.getMinY() / 1000.0,
+            rect.getWidth() / 1000.0,
+            rect.getHeight() / 1000.0);
+        generator.saveGraphicsState();
+        translateAndScale(generator,
+            info.getSize().getDimensionPt(), targetRect);
+
+        //The following %%IncludeResource marker is needed later by ResourceHandler!
+        generator.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, form);
+        generator.getResourceTracker().notifyResourceUsageOnPage(form);
+
+        generator.writeln(form.getName() + " execform");
+        generator.restoreGraphicsState();
+    }
+
+
 }
index e43b0f2c451b83e065f258c039f932af7c2c6e6d..3c937addf9012492736e873af7e45dbb7158e516 100644 (file)
@@ -33,6 +33,10 @@ 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.ImageProcessingHints;
+import org.apache.xmlgraphics.image.loader.ImageSessionContext;
 import org.apache.xmlgraphics.ps.PSGenerator;
 import org.apache.xmlgraphics.ps.PSResource;
 
@@ -135,19 +139,57 @@ public class PSPainter extends AbstractIFPainter {
     }
 
     /** {@inheritDoc} */
-    public void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException {
-        //TODO Implement me
+    protected Map createDefaultImageProcessingHints(ImageSessionContext sessionContext) {
+        Map hints = super.createDefaultImageProcessingHints(sessionContext);
+
+        //PostScript doesn't support alpha channels
+        hints.put(ImageProcessingHints.TRANSPARENCY_INTENT,
+                ImageProcessingHints.TRANSPARENCY_INTENT_IGNORE);
+        //TODO We might want to support image masks in the future.
+        return hints;
     }
 
     /** {@inheritDoc} */
     protected RenderingContext createRenderingContext() {
         PSRenderingContext psContext = new PSRenderingContext(
-                getUserAgent(), getFontInfo());
+                getUserAgent(), getGenerator(), getFontInfo());
         return psContext;
     }
 
+    /** {@inheritDoc} */
+    protected void drawImageUsingImageHandler(ImageInfo info, Rectangle rect)
+            throws ImageException, IOException {
+        if (!getPSUtil().isOptimizeResources()
+                || PSImageUtils.isImageInlined(info,
+                        (PSRenderingContext)createRenderingContext())) {
+            super.drawImageUsingImageHandler(info, rect);
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("Image " + info + " is embedded as a form later");
+            }
+            //Don't load image at this time, just put a form placeholder in the stream
+            PSResource form = documentHandler.getFormForImage(info.getOriginalURI());
+            PSImageUtils.drawForm(form, info, rect, getGenerator());
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void drawImage(String uri, Rectangle rect, Map foreignAttributes) throws IFException {
+        try {
+            endTextObject();
+        } catch (IOException ioe) {
+            throw new IFException("I/O error in drawImage()", ioe);
+        }
+        drawImageUsingURI(uri, rect);
+    }
+
     /** {@inheritDoc} */
     public void drawImage(Document doc, Rectangle rect, Map foreignAttributes) throws IFException {
+        try {
+            endTextObject();
+        } catch (IOException ioe) {
+            throw new IFException("I/O error in drawImage()", ioe);
+        }
         drawImageUsingDocument(doc, rect);
     }
 
index 83dbd4b62585ac217a24159aac487f52b889dbe0..55f714f319be7a61f35e90d8f03b2e0fe7557bbb 100644 (file)
@@ -21,6 +21,7 @@ package org.apache.fop.render.ps;
 
 // Java
 import java.awt.Color;
+import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
 import java.awt.image.RenderedImage;
@@ -36,6 +37,7 @@ import java.util.Map;
 
 import javax.xml.transform.Source;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -45,21 +47,11 @@ 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.impl.ImageGraphics2D;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawEPS;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
-import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
-import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
-import org.apache.xmlgraphics.image.loader.pipeline.ImageProviderPipeline;
 import org.apache.xmlgraphics.image.loader.util.ImageUtil;
 import org.apache.xmlgraphics.ps.DSCConstants;
-import org.apache.xmlgraphics.ps.ImageEncoder;
 import org.apache.xmlgraphics.ps.PSDictionary;
 import org.apache.xmlgraphics.ps.PSDictionaryFormatException;
 import org.apache.xmlgraphics.ps.PSGenerator;
-import org.apache.xmlgraphics.ps.PSImageUtils;
 import org.apache.xmlgraphics.ps.PSPageDeviceDictionary;
 import org.apache.xmlgraphics.ps.PSProcSets;
 import org.apache.xmlgraphics.ps.PSResource;
@@ -97,6 +89,8 @@ import org.apache.fop.fonts.Typeface;
 import org.apache.fop.render.AbstractPathOrientedRenderer;
 import org.apache.fop.render.Graphics2DAdapter;
 import org.apache.fop.render.ImageAdapter;
+import org.apache.fop.render.ImageHandler;
+import org.apache.fop.render.ImageHandlerRegistry;
 import org.apache.fop.render.RendererContext;
 import org.apache.fop.render.RendererEventProducer;
 import org.apache.fop.render.ps.extensions.PSCommentAfter;
@@ -350,50 +344,6 @@ public class PSRenderer extends AbstractPathOrientedRenderer
         }
     }
 
-    /**
-     * Indicates whether an image should be inlined or added as a PostScript form.
-     * @param uri the URI of the image
-     * @return true if the image should be inlined rather than added as a form
-     */
-    protected boolean isImageInlined(String uri) {
-        return !isOptimizeResources() || uri == null || "".equals(uri);
-    }
-
-    /**
-     * Indicates whether an image should be inlined or added as a PostScript form.
-     * @param info the ImageInfo object of the image
-     * @return true if the image should be inlined rather than added as a form
-     */
-    protected boolean isImageInlined(ImageInfo info) {
-        if (isImageInlined(info.getOriginalURI())) {
-            return true;
-        }
-
-        if (!isOptimizeResources()) {
-            throw new IllegalStateException("Must not get here if form support is enabled");
-        }
-
-        //Investigate choice for inline mode
-        ImageFlavor[] inlineFlavors = getInlineFlavors();
-        ImageManager manager = getUserAgent().getFactory().getImageManager();
-        ImageProviderPipeline[] inlineCandidates
-            = manager.getPipelineFactory().determineCandidatePipelines(
-                    info, inlineFlavors);
-        ImageProviderPipeline inlineChoice = manager.choosePipeline(inlineCandidates);
-        ImageFlavor inlineFlavor = (inlineChoice != null ? inlineChoice.getTargetFlavor() : null);
-
-        //Investigate choice for form mode
-        ImageFlavor[] formFlavors = getFormFlavors();
-        ImageProviderPipeline[] formCandidates
-            = manager.getPipelineFactory().determineCandidatePipelines(
-                    info, formFlavors);
-        ImageProviderPipeline formChoice = manager.choosePipeline(formCandidates);
-        ImageFlavor formFlavor = (formChoice != null ? formChoice.getTargetFlavor() : null);
-
-        //Inline if form is not supported or if a better choice is available with inline mode
-        return formFlavor == null || !formFlavor.equals(inlineFlavor);
-    }
-
     /** {@inheritDoc} */
     protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) {
         endTextObject();
@@ -403,89 +353,48 @@ public class PSRenderer extends AbstractPathOrientedRenderer
         if (log.isDebugEnabled()) {
             log.debug("Handling image: " + uri);
         }
+        int width = (int)pos.getWidth();
+        int height = (int)pos.getHeight();
+        Rectangle targetRect = new Rectangle(x, y, width, height);
 
         ImageManager manager = getUserAgent().getFactory().getImageManager();
         ImageInfo info = null;
         try {
             ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
             info = manager.getImageInfo(uri, sessionContext);
-            int width = (int)pos.getWidth();
-            int height = (int)pos.getHeight();
 
-            //millipoints --> points for PostScript
-            float ptx = x / 1000f;
-            float pty = y / 1000f;
-            float ptw = width / 1000f;
-            float pth = height / 1000f;
+            PSRenderingContext renderingContext = new PSRenderingContext(
+                    getUserAgent(), gen, getFontInfo());
 
-            if (isImageInlined(info)) {
+            if (!isOptimizeResources()
+                    || PSImageUtils.isImageInlined(info, renderingContext)) {
                 if (log.isDebugEnabled()) {
                     log.debug("Image " + info + " is inlined");
                 }
+
+                //Determine supported flavors
+                ImageFlavor[] flavors;
+                ImageHandlerRegistry imageHandlerRegistry
+                    = userAgent.getFactory().getImageHandlerRegistry();
+                flavors = imageHandlerRegistry.getSupportedFlavors(renderingContext);
+
                 //Only now fully load/prepare the image
                 Map hints = ImageUtil.getDefaultHints(sessionContext);
                 org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
-                        info, getInlineFlavors(), hints, sessionContext);
+                        info, flavors, hints, sessionContext);
+
+                //Get handler for image
+                ImageHandler basicHandler = imageHandlerRegistry.getHandler(renderingContext, img);
 
                 //...and embed as inline image
-                if (img instanceof ImageGraphics2D) {
-                    ImageGraphics2D imageG2D = (ImageGraphics2D)img;
-                    RendererContext context = createRendererContext(
-                            x, y, width, height, foreignAttributes);
-                    getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(),
-                            context, x, y, width, height);
-                } else if (img instanceof ImageRendered) {
-                    ImageRendered imgRend = (ImageRendered)img;
-                    RenderedImage ri = imgRend.getRenderedImage();
-                    PSImageUtils.renderBitmapImage(ri, ptx, pty, ptw, pth, gen);
-                } else if (img instanceof ImageXMLDOM) {
-                    ImageXMLDOM imgXML = (ImageXMLDOM)img;
-                    renderDocument(imgXML.getDocument(), imgXML.getRootNamespace(),
-                            pos, foreignAttributes);
-                } else if (img instanceof ImageRawStream) {
-                    final ImageRawStream raw = (ImageRawStream)img;
-                    if (raw instanceof ImageRawEPS) {
-                        ImageRawEPS eps = (ImageRawEPS)raw;
-                        Rectangle2D bbox = eps.getBoundingBox();
-                        InputStream in = raw.createInputStream();
-                        try {
-                            PSImageUtils.renderEPS(in, uri,
-                                    new Rectangle2D.Float(ptx, pty, ptw, pth),
-                                    bbox,
-                                    gen);
-                        } finally {
-                            IOUtils.closeQuietly(in);
-                        }
-                    } else if (raw instanceof ImageRawCCITTFax) {
-                        final ImageRawCCITTFax ccitt = (ImageRawCCITTFax)raw;
-                        ImageEncoder encoder = new ImageEncoderCCITTFax(ccitt);
-                        Rectangle2D targetRect = new Rectangle2D.Float(
-                                ptx, pty, ptw, pth);
-                        PSImageUtils.writeImage(encoder, info.getSize().getDimensionPx(),
-                                uri, targetRect,
-                                ccitt.getColorSpace(), 1, false, gen);
-                    } else if (raw instanceof ImageRawJPEG) {
-                        ImageRawJPEG jpeg = (ImageRawJPEG)raw;
-                        ImageEncoder encoder = new ImageEncoderJPEG(jpeg);
-                        Rectangle2D targetRect = new Rectangle2D.Float(
-                                ptx, pty, ptw, pth);
-                        PSImageUtils.writeImage(encoder, info.getSize().getDimensionPx(),
-                                uri, targetRect,
-                                jpeg.getColorSpace(), 8, jpeg.isInverted(), gen);
-                    } else {
-                        throw new UnsupportedOperationException("Unsupported raw image: " + info);
-                    }
-                } else {
-                    throw new UnsupportedOperationException("Unsupported image type: " + img);
-                }
+                basicHandler.handleImage(renderingContext, img, targetRect);
             } else {
                 if (log.isDebugEnabled()) {
                     log.debug("Image " + info + " is embedded as a form later");
                 }
                 //Don't load image at this time, just put a form placeholder in the stream
-                PSResource form = getFormForImage(uri);
-                Rectangle2D targetRect = new Rectangle2D.Double(ptx, pty, ptw, pth);
-                PSImageUtils.paintForm(form, info.getSize().getDimensionPt(), targetRect, gen);
+                PSResource form = getFormForImage(info.getOriginalURI());
+                PSImageUtils.drawForm(form, info, targetRect, gen);
             }
 
         } catch (ImageException ie) {
@@ -503,26 +412,6 @@ public class PSRenderer extends AbstractPathOrientedRenderer
         }
     }
 
-    private ImageFlavor[] getInlineFlavors() {
-        ImageFlavor[] flavors;
-        if (gen.getPSLevel() >= 3) {
-            flavors = LEVEL_3_FLAVORS_INLINE;
-        } else {
-            flavors = LEVEL_2_FLAVORS_INLINE;
-        }
-        return flavors;
-    }
-
-    private ImageFlavor[] getFormFlavors() {
-        ImageFlavor[] flavors;
-        if (gen.getPSLevel() >= 3) {
-            flavors = LEVEL_3_FLAVORS_FORM;
-        } else {
-            flavors = LEVEL_2_FLAVORS_FORM;
-        }
-        return flavors;
-    }
-
     /**
      * Returns a PSResource instance representing a image as a PostScript form.
      * @param uri the image URI
@@ -978,8 +867,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer
         in = new java.io.BufferedInputStream(in);
         try {
             try {
-                ResourceHandler.process(this.userAgent, in, this.outputStream,
-                        this.fontInfo, resTracker, this.formResources,
+                ResourceHandler handler = new ResourceHandler(this.userAgent, this.fontInfo,
+                        resTracker, this.formResources);
+                handler.process(in, this.outputStream,
                         this.currentPageNumber, this.documentBoundingBox);
                 this.outputStream.flush();
             } catch (DSCException e) {
index 63f00ea1a8588935ff40579f915cf8379b6cc483..b34874ffad21dab5ea02f0e33a97449107536687 100644 (file)
@@ -19,6 +19,7 @@
 
 package org.apache.fop.render.ps;
 
+import org.apache.xmlgraphics.ps.PSGenerator;
 import org.apache.xmlgraphics.util.MimeConstants;
 
 import org.apache.fop.apps.FOUserAgent;
@@ -30,17 +31,34 @@ import org.apache.fop.render.AbstractRenderingContext;
  */
 public class PSRenderingContext extends AbstractRenderingContext {
 
+    private PSGenerator gen;
     private FontInfo fontInfo;
+    private boolean createForms;
 
     /**
      * Main constructor.
      * @param userAgent the user agent
+     * @param gen the PostScript generator
      * @param fontInfo the font list
      */
     public PSRenderingContext(FOUserAgent userAgent,
-            FontInfo fontInfo) {
+            PSGenerator gen, FontInfo fontInfo) {
+        this(userAgent, gen, fontInfo, false);
+    }
+
+    /**
+     * Special constructor.
+     * @param userAgent the user agent
+     * @param gen the PostScript generator
+     * @param fontInfo the font list
+     * @param createForms true if form generation mode should be enabled
+     */
+    public PSRenderingContext(FOUserAgent userAgent,
+            PSGenerator gen, FontInfo fontInfo, boolean createForms) {
         super(userAgent);
+        this.gen = gen;
         this.fontInfo = fontInfo;
+        this.createForms = createForms;
     }
 
     /** {@inheritDoc} */
@@ -48,6 +66,14 @@ public class PSRenderingContext extends AbstractRenderingContext {
         return MimeConstants.MIME_POSTSCRIPT;
     }
 
+    /**
+     * Returns the PostScript generator.
+     * @return the PostScript generator
+     */
+    public PSGenerator getGenerator() {
+        return this.gen;
+    }
+
     /**
      * Returns the font list.
      * @return the font list
@@ -56,4 +82,21 @@ public class PSRenderingContext extends AbstractRenderingContext {
         return this.fontInfo;
     }
 
+    /**
+     * Indicates whether PS forms should be created for the images instead of inline images.
+     * Note that not all image handlers will support this!
+     * @return true if PS forms shall be created
+     */
+    public boolean isCreateForms() {
+        return this.createForms;
+    }
+
+    /**
+     * Create a copy of this rendering context and activate form mode.
+     * @return the form-enabled rendering context
+     */
+    public PSRenderingContext toFormContext() {
+        return new PSRenderingContext(getUserAgent(), getGenerator(), getFontInfo(), true);
+    }
+
 }
index bdd3051640b281300e2d3def49c82df510e8a870..0b2f174f5329334c4f4b803ddcbc055ecbd7516b 100644 (file)
@@ -21,7 +21,6 @@ package org.apache.fop.render.ps;
 
 import java.awt.geom.Dimension2D;
 import java.awt.geom.Rectangle2D;
-import java.awt.image.RenderedImage;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -29,27 +28,22 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
+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.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.impl.ImageGraphics2D;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawEPS;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG;
-import org.apache.xmlgraphics.image.loader.impl.ImageRawStream;
-import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
-import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
 import org.apache.xmlgraphics.image.loader.util.ImageUtil;
 import org.apache.xmlgraphics.ps.DSCConstants;
 import org.apache.xmlgraphics.ps.FormGenerator;
-import org.apache.xmlgraphics.ps.ImageEncoder;
-import org.apache.xmlgraphics.ps.ImageFormGenerator;
 import org.apache.xmlgraphics.ps.PSGenerator;
-import org.apache.xmlgraphics.ps.PSProcSets;
+import org.apache.xmlgraphics.ps.PSResource;
 import org.apache.xmlgraphics.ps.dsc.DSCException;
 import org.apache.xmlgraphics.ps.dsc.DSCFilter;
+import org.apache.xmlgraphics.ps.dsc.DSCListener;
 import org.apache.xmlgraphics.ps.dsc.DSCParser;
 import org.apache.xmlgraphics.ps.dsc.DSCParserConstants;
 import org.apache.xmlgraphics.ps.dsc.DefaultNestedDocumentHandler;
@@ -59,17 +53,21 @@ import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox;
 import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentNeededResources;
 import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentSuppliedResources;
 import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentIncludeResource;
 import org.apache.xmlgraphics.ps.dsc.events.DSCCommentLanguageLevel;
 import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPage;
 import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPages;
 import org.apache.xmlgraphics.ps.dsc.events.DSCEvent;
 import org.apache.xmlgraphics.ps.dsc.events.DSCHeaderComment;
 import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment;
+import org.apache.xmlgraphics.ps.dsc.events.PostScriptLine;
 import org.apache.xmlgraphics.ps.dsc.tools.DSCTools;
 
 import org.apache.fop.apps.FOUserAgent;
 import org.apache.fop.events.ResourceEventProducer;
 import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.render.ImageHandler;
+import org.apache.fop.render.ImageHandlerRegistry;
 
 /**
  * This class is used when two-pass production is used to generate the PostScript file (setting
@@ -79,28 +77,79 @@ import org.apache.fop.fonts.FontInfo;
  */
 public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
 
+    /** logging instance */
+    private static Log log = LogFactory.getLog(ResourceHandler.class);
+
+    private FOUserAgent userAgent;
+    private FontInfo fontInfo;
+
+    private ResourceTracker resTracker;
+
+    //key: URI, values PSImageFormResource
+    private Map globalFormResources = new java.util.HashMap();
+    //key: PSResource, values PSImageFormResource
+    private Map inlineFormResources = new java.util.HashMap();
+
     /**
-     * Rewrites the temporary PostScript file generated by PSRenderer adding all needed resources
-     * (fonts and images).
+     * Main constructor.
      * @param userAgent the FO user agent
-     * @param in the InputStream for the temporary PostScript file
-     * @param out the OutputStream to write the finished file to
      * @param fontInfo the font information
      * @param resTracker the resource tracker to use
      * @param formResources Contains all forms used by this document (maintained by PSRenderer)
+     */
+    public ResourceHandler(FOUserAgent userAgent, FontInfo fontInfo,
+            ResourceTracker resTracker, Map formResources) {
+        this.userAgent = userAgent;
+        this.fontInfo = fontInfo;
+        this.resTracker = resTracker;
+        determineInlineForms(formResources);
+    }
+
+    /**
+     * This method splits up the form resources map into two. One for global forms which
+     * have been referenced more than once, and one for inline forms which have only been
+     * used once. The latter is to conserve memory in the PostScript interpreter.
+     * @param formResources the original form resources map
+     */
+    private void determineInlineForms(Map formResources) {
+        if (formResources == null) {
+            return;
+        }
+        Iterator iter = formResources.entrySet().iterator();
+        while (iter.hasNext()) {
+            Map.Entry entry = (Map.Entry)iter.next();
+            PSResource res = (PSResource)entry.getValue();
+            long count = resTracker.getUsageCount(res);
+            if (count > 1) {
+                //Make global form
+                this.globalFormResources.put(entry.getKey(), res);
+            } else {
+                //Inline resource
+                this.inlineFormResources.put(res, res);
+                resTracker.declareInlined(res);
+            }
+        }
+    }
+
+    /**
+     * Rewrites the temporary PostScript file generated by PSRenderer adding all needed resources
+     * (fonts and images).
+     * @param in the InputStream for the temporary PostScript file
+     * @param out the OutputStream to write the finished file to
      * @param pageCount the number of pages (given here because PSRenderer writes an "(atend)")
      * @param documentBoundingBox the document's bounding box
      *                                  (given here because PSRenderer writes an "(atend)")
      * @throws DSCException If there's an error in the DSC structure of the PS file
      * @throws IOException In case of an I/O error
      */
-    public static void process(FOUserAgent userAgent, InputStream in, OutputStream out,
-            FontInfo fontInfo, ResourceTracker resTracker, Map formResources,
+    public void process(InputStream in, OutputStream out,
             int pageCount, Rectangle2D documentBoundingBox)
                     throws DSCException, IOException {
         DSCParser parser = new DSCParser(in);
+
         PSGenerator gen = new PSGenerator(out);
-        parser.setNestedDocumentHandler(new DefaultNestedDocumentHandler(gen));
+        parser.addListener(new DefaultNestedDocumentHandler(gen));
+        parser.addListener(new IncludeResourceListener(gen));
 
         //Skip DSC header
         DSCHeaderComment header = DSCTools.checkAndSkipDSC30Header(parser);
@@ -140,7 +189,7 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
                 new DSCCommentHiResBoundingBox(documentBoundingBox).generate(gen);
 
                 PSFontUtils.determineSuppliedFonts(resTracker, fontInfo, fontInfo.getUsedFonts());
-                registerSuppliedForms(resTracker, formResources);
+                registerSuppliedForms(resTracker, globalFormResources);
 
                 //Supplied Resources
                 DSCCommentDocumentSuppliedResources supplied
@@ -174,7 +223,7 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
             throw new DSCException("Didn't find %FOPFontSetup comment in stream");
         }
         PSFontUtils.writeFontDict(gen, fontInfo, fontInfo.getUsedFonts());
-        generateForms(resTracker, userAgent, formResources, gen);
+        generateForms(globalFormResources, gen);
 
         //Skip the prolog and to the first page
         DSCComment pageOrTrailer = parser.nextDSCComment(DSCConstants.PAGE, gen);
@@ -218,115 +267,64 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
         }
     }
 
-    private static void generateForms(ResourceTracker resTracker, FOUserAgent userAgent,
-            Map formResources, PSGenerator gen) throws IOException {
+    private void generateForms(Map formResources, PSGenerator gen) throws IOException {
         if (formResources == null) {
             return;
         }
         Iterator iter = formResources.values().iterator();
         while (iter.hasNext()) {
             PSImageFormResource form = (PSImageFormResource)iter.next();
-            final String uri = form.getImageURI();
+            generateFormForImage(gen, form);
+        }
+    }
 
-            ImageManager manager = userAgent.getFactory().getImageManager();
-            ImageInfo info = null;
-            try {
-                ImageSessionContext sessionContext = userAgent.getImageSessionContext();
-                info = manager.getImageInfo(uri, sessionContext);
+    private void generateFormForImage(PSGenerator gen, PSImageFormResource form)
+                throws IOException {
+        final String uri = form.getImageURI();
 
-                ImageFlavor[] flavors;
-                if (gen.getPSLevel() >= 3) {
-                    flavors = LEVEL_3_FLAVORS_FORM;
-                } else {
-                    flavors = LEVEL_2_FLAVORS_FORM;
-                }
-                Map hints = ImageUtil.getDefaultHints(sessionContext);
-                org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
-                        info, flavors, hints, sessionContext);
-
-                String imageDescription = info.getMimeType() + " " + info.getOriginalURI();
-                final Dimension2D dimensionsPt = info.getSize().getDimensionPt();
-                final Dimension2D dimensionsMpt = info.getSize().getDimensionMpt();
-
-                if (img instanceof ImageGraphics2D) {
-                    final ImageGraphics2D imageG2D = (ImageGraphics2D)img;
-                    FormGenerator formGen = new FormGenerator(
-                            form.getName(), imageDescription, dimensionsPt) {
-
-                        protected void generatePaintProc(PSGenerator gen)
-                                throws IOException {
-                            gen.getResourceTracker().notifyResourceUsageOnPage(
-                                    PSProcSets.EPS_PROCSET);
-                            gen.writeln("BeginEPSF");
-                            PSGraphics2DAdapter adapter = new PSGraphics2DAdapter(gen, false);
-                            adapter.paintImage(imageG2D.getGraphics2DImagePainter(),
-                                    null,
-                                    0, 0,
-                                    (int)Math.round(dimensionsMpt.getWidth()),
-                                    (int)Math.round(dimensionsMpt.getHeight()));
-                            gen.writeln("EndEPSF");
-                        }
+        ImageManager manager = userAgent.getFactory().getImageManager();
+        ImageInfo info = null;
+        try {
+            ImageSessionContext sessionContext = userAgent.getImageSessionContext();
+            info = manager.getImageInfo(uri, sessionContext);
 
-                    };
-                    formGen.generate(gen);
-                } else if (img instanceof ImageRendered) {
-                    ImageRendered imgRend = (ImageRendered)img;
-                    RenderedImage ri = imgRend.getRenderedImage();
-                    FormGenerator formGen = new ImageFormGenerator(
-                            form.getName(), imageDescription,
-                            info.getSize().getDimensionPt(),
-                            ri, false);
-                    formGen.generate(gen);
-                } else if (img instanceof ImageXMLDOM) {
-                    throw new UnsupportedOperationException(
-                            "Embedding an ImageXMLDOM as a form isn't supported, yet");
-                } else if (img instanceof ImageRawStream) {
-                    final ImageRawStream raw = (ImageRawStream)img;
-                    if (raw instanceof ImageRawEPS) {
-                        final ImageRawEPS eps = (ImageRawEPS)raw;
-                        throw new UnsupportedOperationException(
-                                "Embedding EPS as forms isn't supported, yet");
-                        /*
-                        InputStream in = eps.createInputStream();
-                        try {
-                            FormGenerator formGen = new EPSFormGenerator(form.getName(),
-                                    imageDescription, dimensions, in);
-                            formGen.generate(gen);
-                        } finally {
-                            IOUtils.closeQuietly(in);
-                        }*/
-                    } else if (raw instanceof ImageRawCCITTFax) {
-                        ImageRawCCITTFax jpeg = (ImageRawCCITTFax)raw;
-                        ImageEncoder encoder = new ImageEncoderCCITTFax(jpeg);
-                        FormGenerator formGen = new ImageFormGenerator(
-                                form.getName(), imageDescription,
-                                info.getSize().getDimensionPt(),
-                                info.getSize().getDimensionPx(),
-                                encoder,
-                                jpeg.getColorSpace(), 1, false);
-                        formGen.generate(gen);
-                    } else if (raw instanceof ImageRawJPEG) {
-                        ImageRawJPEG jpeg = (ImageRawJPEG)raw;
-                        ImageEncoder encoder = new ImageEncoderJPEG(jpeg);
-                        FormGenerator formGen = new ImageFormGenerator(
-                                form.getName(), imageDescription,
-                                info.getSize().getDimensionPt(),
-                                info.getSize().getDimensionPx(),
-                                encoder,
-                                jpeg.getColorSpace(), jpeg.isInverted());
-                        formGen.generate(gen);
-                    } else {
-                        throw new UnsupportedOperationException("Unsupported raw image: " + info);
-                    }
-                } else {
-                    throw new UnsupportedOperationException("Unsupported image type: " + img);
-                }
-            } catch (ImageException ie) {
-                ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
-                        userAgent.getEventBroadcaster());
-                eventProducer.imageError(resTracker, (info != null ? info.toString() : uri),
-                        ie, null);
+            //Create a rendering context for form creation
+            PSRenderingContext formContext = new PSRenderingContext(
+                    userAgent, gen, fontInfo, true);
+
+            ImageFlavor[] flavors;
+            ImageHandlerRegistry imageHandlerRegistry
+                = userAgent.getFactory().getImageHandlerRegistry();
+            flavors = imageHandlerRegistry.getSupportedFlavors(formContext);
+
+            Map hints = ImageUtil.getDefaultHints(sessionContext);
+            org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
+                    info, flavors, hints, sessionContext);
+
+            ImageHandler basicHandler = imageHandlerRegistry.getHandler(formContext, img);
+            if (basicHandler == null) {
+                throw new UnsupportedOperationException(
+                        "No ImageHandler available for image: "
+                            + img.getInfo() + " (" + img.getClass().getName() + ")");
             }
+
+            if (!(basicHandler instanceof PSImageHandler)) {
+                throw new IllegalStateException(
+                        "ImageHandler implementation doesn't behave properly."
+                        + " It should have returned false in isCompatible(). Class: "
+                        + basicHandler.getClass().getName());
+            }
+            PSImageHandler handler = (PSImageHandler)basicHandler;
+            if (log.isTraceEnabled()) {
+                log.trace("Using ImageHandler: " + handler.getClass().getName());
+            }
+            handler.generateForm(formContext, img, form);
+
+        } catch (ImageException ie) {
+            ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
+                    userAgent.getEventBroadcaster());
+            eventProducer.imageError(resTracker, (info != null ? info.toString() : uri),
+                    ie, null);
         }
     }
 
@@ -354,4 +352,50 @@ public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors {
         return formGen;
     }
 
+    private class IncludeResourceListener implements DSCListener {
+
+        private PSGenerator gen;
+
+        public IncludeResourceListener(PSGenerator gen) {
+            this.gen = gen;
+        }
+
+        /** {@inheritDoc} */
+        public void processEvent(DSCEvent event, DSCParser parser)
+                    throws IOException, DSCException {
+            if (event.isDSCComment() && event instanceof DSCCommentIncludeResource) {
+                DSCCommentIncludeResource include = (DSCCommentIncludeResource)event;
+                PSResource res = include.getResource();
+                if (res.getType().equals(PSResource.TYPE_FORM)) {
+                    if (inlineFormResources.containsValue(res)) {
+                        PSImageFormResource form = (PSImageFormResource)
+                                    inlineFormResources.get(res);
+                        //Create an inline form
+                        //Wrap in save/restore pair to release memory
+                        gen.writeln("save");
+                        generateFormForImage(gen, form);
+                        boolean execformFound = false;
+                        DSCEvent next = parser.nextEvent();
+                        if (next.isLine()) {
+                            PostScriptLine line = next.asLine();
+                            if (line.getLine().endsWith(" execform")) {
+                                line.generate(gen);
+                                execformFound = true;
+                            }
+                        }
+                        if (!execformFound) {
+                            throw new IOException(
+                                "Expected a PostScript line in the form: <form> execform");
+                        }
+                        gen.writeln("restore");
+                    } else {
+                        //Do nothing
+                    }
+                    parser.next();
+                }
+            }
+        }
+
+    }
+
 }
diff --git a/test/java/org/apache/fop/render/ps/AbstractPostScriptTestCase.java b/test/java/org/apache/fop/render/ps/AbstractPostScriptTestCase.java
new file mode 100644 (file)
index 0000000..522d193
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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.ps;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.MissingResourceException;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.stream.StreamSource;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import org.apache.xmlgraphics.ps.PSResource;
+import org.apache.xmlgraphics.ps.dsc.DSCException;
+import org.apache.xmlgraphics.ps.dsc.DSCParser;
+import org.apache.xmlgraphics.ps.dsc.events.AbstractResourceDSCComment;
+import org.apache.xmlgraphics.ps.dsc.events.DSCComment;
+import org.apache.xmlgraphics.ps.dsc.events.DSCEvent;
+
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.Fop;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.apps.MimeConstants;
+
+/**
+ * Abstract base class for PostScript verification tests.
+ */
+public abstract class AbstractPostScriptTestCase extends TestCase {
+
+    /** the JAXP TransformerFactory */
+    protected TransformerFactory tFactory = TransformerFactory.newInstance();
+    /** the FopFactory */
+    protected FopFactory fopFactory = FopFactory.newInstance();
+
+    /**
+     * Renders a test file.
+     * @param ua the user agent (with override set!)
+     * @param resourceName the resource name for the FO file
+     * @param suffix a suffix for the output filename
+     * @return the output file
+     * @throws Exception if an error occurs
+     */
+    protected File renderFile(FOUserAgent ua, String resourceName, String suffix)
+                throws Exception {
+        File outputFile = new File("build/test-results/" + resourceName + suffix + ".ps");
+        File outputDir = outputFile.getParentFile();
+        FileUtils.forceMkdir(outputDir);
+
+        // Prepare input file
+        InputStream in = getClass().getResourceAsStream(resourceName);
+        if (in == null) {
+            throw new MissingResourceException(resourceName + " not found in resources",
+                    getClass().getName(), null);
+        }
+        try {
+            Source src = new StreamSource(in);
+
+            // Create PostScript
+            OutputStream out = new java.io.FileOutputStream(outputFile);
+            out = new java.io.BufferedOutputStream(out);
+            try {
+                Fop fop = fopFactory.newFop(MimeConstants.MIME_POSTSCRIPT, ua, out);
+                SAXResult res = new SAXResult(fop.getDefaultHandler());
+
+                Transformer transformer = tFactory.newTransformer();
+                transformer.transform(src, res);
+            } finally {
+                IOUtils.closeQuietly(out);
+            }
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+        return outputFile;
+    }
+
+    /**
+     * Scans for a certain resource DSC comment and checks against a given resource.
+     * @param parser the DSC parser
+     * @param comment the comment to scan for
+     * @param resource the resource to check against
+     * @throws IOException if an I/O error occurs
+     * @throws DSCException if a DSC error occurs
+     */
+    protected void checkResourceComment(DSCParser parser, String comment, PSResource resource)
+                throws IOException, DSCException {
+        AbstractResourceDSCComment resComment;
+        resComment = (AbstractResourceDSCComment)gotoDSCComment(parser, comment);
+        assertEquals(resource, resComment.getResource());
+    }
+
+    /**
+     * Advances the DSC parser to a DSC comment with the given name.
+     * @param parser the DSC parser
+     * @param name the name of the DSC comment
+     * @return the DSC comment
+     * @throws IOException if an I/O error occurs
+     * @throws DSCException if a DSC error occurs
+     */
+    protected static DSCComment gotoDSCComment(DSCParser parser, String name)
+            throws IOException, DSCException {
+        while (parser.hasNext()) {
+            DSCEvent event = parser.nextEvent();
+            if (event.isDSCComment()) {
+                DSCComment comment = event.asDSCComment();
+                if (comment.getName().equals(name)) {
+                    return comment;
+                }
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/test/java/org/apache/fop/render/ps/ImageHandlingTestCase.java b/test/java/org/apache/fop/render/ps/ImageHandlingTestCase.java
new file mode 100644 (file)
index 0000000..51c4dc3
--- /dev/null
@@ -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.ps;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.xmlgraphics.ps.DSCConstants;
+import org.apache.xmlgraphics.ps.PSResource;
+import org.apache.xmlgraphics.ps.dsc.DSCException;
+import org.apache.xmlgraphics.ps.dsc.DSCParser;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPage;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPages;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentTitle;
+import org.apache.xmlgraphics.ps.dsc.events.DSCEvent;
+
+import org.apache.fop.apps.FOUserAgent;
+
+/**
+ * Tests the image handling in PostScript output.
+ */
+public class ImageHandlingTestCase extends AbstractPostScriptTestCase {
+
+    /**
+     * Tests JPEG handling with the {@link PSRenderer}.
+     * @throws Exception if an error occurs
+     */
+    public void testJPEGImageWithRendererLevel3() throws Exception {
+        innerTestJPEGImageWithRenderer(3);
+    }
+
+    /**
+     * Tests JPEG handling with the {@link PSRenderer}.
+     * @throws Exception if an error occurs
+     */
+    public void testJPEGImageWithRendererLevel2() throws Exception {
+        innerTestJPEGImageWithRenderer(2);
+    }
+
+    /**
+     * Tests JPEG handling with the {@link PSDocumentHandler}.
+     * @throws Exception if an error occurs
+     */
+    public void testJPEGImageWithIFLevel3() throws Exception {
+        innerTestJPEGImageWithIF(3);
+    }
+
+    /**
+     * Tests JPEG handling with the {@link PSDocumentHandler}.
+     * @throws Exception if an error occurs
+     */
+    public void testJPEGImageWithIFLevel2() throws Exception {
+        innerTestJPEGImageWithIF(2);
+    }
+
+    private void innerTestJPEGImageWithRenderer(int level) throws Exception {
+        FOUserAgent ua = fopFactory.newFOUserAgent();
+        PSRenderer renderer = new PSRenderer();
+        renderer.setUserAgent(ua);
+        PSRenderingUtil psUtil = renderer.getPSUtil();
+        psUtil.setLanguageLevel(level);
+        psUtil.setOptimizeResources(true);
+        ua.setRendererOverride(renderer);
+
+        // Prepare output file
+        File outputFile = renderFile(ua, "ps-jpeg-image.fo", "-rend-l" + psUtil.getLanguageLevel());
+        verifyPostScriptFile(outputFile, psUtil.getLanguageLevel());
+    }
+
+    private void innerTestJPEGImageWithIF(int level) throws Exception {
+        FOUserAgent ua = fopFactory.newFOUserAgent();
+        PSDocumentHandler handler = new PSDocumentHandler();
+        handler.setUserAgent(ua);
+        PSRenderingUtil psUtil = handler.getPSUtil();
+        psUtil.setLanguageLevel(level);
+        psUtil.setOptimizeResources(true);
+        ua.setDocumentHandlerOverride(handler);
+
+        // Prepare output file
+        File outputFile = renderFile(ua, "ps-jpeg-image.fo", "-if-l" + psUtil.getLanguageLevel());
+        verifyPostScriptFile(outputFile, psUtil.getLanguageLevel());
+    }
+
+    private void verifyPostScriptFile(File psFile, int level)
+                throws IOException, DSCException {
+        InputStream in = new java.io.FileInputStream(psFile);
+        in = new java.io.BufferedInputStream(in);
+        try {
+            DSCParser parser = new DSCParser(in);
+
+            DSCCommentPages pages = (DSCCommentPages)gotoDSCComment(parser, DSCConstants.PAGES);
+            assertEquals(1, pages.getPageCount());
+
+            //Skip procsets and encoding
+            gotoDSCComment(parser, DSCConstants.BEGIN_RESOURCE);
+            gotoDSCComment(parser, DSCConstants.BEGIN_RESOURCE);
+            gotoDSCComment(parser, DSCConstants.BEGIN_RESOURCE);
+
+            PSResource form2 = new PSResource(PSResource.TYPE_FORM, "FOPForm:2");
+            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE, form2);
+            DSCCommentTitle title = (DSCCommentTitle)parser.nextEvent().asDSCComment();
+            assertEquals("image/jpeg test/resources/images/bgimg300dpi.jpg", title.getTitle());
+
+            String resourceContent = getResourceContent(parser);
+
+            if (level == 3) {
+                assertContains(resourceContent, "/FOPForm:2");
+                assertContains(resourceContent, "/DCTDecode filter");
+                assertContains(resourceContent, "/ReusableStreamDecode filter");
+            } else {
+                assertContains(resourceContent, "/FOPForm:2");
+                assertContains(resourceContent, "/DCTDecode filter");
+                assertAbsent(resourceContent, "/ReusableStreamDecode filter");
+            }
+
+            //---=== Page 1 ===---
+            DSCCommentPage page = (DSCCommentPage)gotoDSCComment(parser, DSCConstants.PAGE);
+            assertEquals(1, page.getPagePosition());
+
+            PSResource form1 = new PSResource(PSResource.TYPE_FORM, "FOPForm:1");
+            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE, form1);
+            title = (DSCCommentTitle)parser.nextEvent().asDSCComment();
+            assertEquals("image/jpeg test/resources/images/bgimg72dpi.jpg", title.getTitle());
+            resourceContent = getResourceContent(parser);
+
+            if (level == 3) {
+                assertContains(resourceContent, "/FOPForm:1");
+                assertContains(resourceContent, "/DCTDecode filter");
+                assertContains(resourceContent, "/ReusableStreamDecode filter");
+            } else {
+                assertContains(resourceContent, "/FOPForm:1");
+                assertContains(resourceContent, "/DCTDecode filter");
+                assertAbsent(resourceContent, "/ReusableStreamDecode filter");
+            }
+
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    private void assertMatches(String text, String regex) {
+        assertTrue("Text didn't match '" + regex + "'", text.matches(regex));
+    }
+
+    private void assertContains(String text, String searchString) {
+        assertTrue("Text doesn't contain '" + searchString + "'", text.indexOf(searchString) >= 0);
+    }
+
+    private void assertAbsent(String text, String searchString) {
+        assertTrue("Text contains '" + searchString + "'", text.indexOf(searchString) < 0);
+    }
+
+    private String getResourceContent(DSCParser parser) throws IOException, DSCException {
+        StringBuffer sb = new StringBuffer();
+        while (parser.hasNext()) {
+            DSCEvent event = parser.nextEvent();
+            if (event.isLine()) {
+                sb.append(event.asLine().getLine()).append('\n');
+            } else if (event.isDSCComment()) {
+                if (DSCConstants.END_RESOURCE.equals(event.asDSCComment().getName())) {
+                    break;
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/test/java/org/apache/fop/render/ps/ResourceOptimizationTestCase.java b/test/java/org/apache/fop/render/ps/ResourceOptimizationTestCase.java
new file mode 100644 (file)
index 0000000..ace96e8
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * 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.ps;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.xmlgraphics.ps.DSCConstants;
+import org.apache.xmlgraphics.ps.PSResource;
+import org.apache.xmlgraphics.ps.dsc.DSCException;
+import org.apache.xmlgraphics.ps.dsc.DSCListener;
+import org.apache.xmlgraphics.ps.dsc.DSCParser;
+import org.apache.xmlgraphics.ps.dsc.DefaultNestedDocumentHandler;
+import org.apache.xmlgraphics.ps.dsc.events.AbstractResourcesDSCComment;
+import org.apache.xmlgraphics.ps.dsc.events.DSCAtend;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBeginDocument;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentNeededResources;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentSuppliedResources;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentEndOfFile;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentIncludeResource;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPage;
+import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPages;
+
+import org.apache.fop.apps.FOUserAgent;
+
+/**
+ * Tests the PostScript resource optimization (selective de-duplication of
+ * images that are used multiple times).
+ */
+public class ResourceOptimizationTestCase extends AbstractPostScriptTestCase {
+
+    /**
+     * Tests resource optimization with the {@link PSRenderer}.
+     * @throws Exception if an error occurs
+     */
+    public void testResourceOptimizationWithRenderer() throws Exception {
+        FOUserAgent ua = fopFactory.newFOUserAgent();
+        PSRenderer renderer = new PSRenderer();
+        renderer.setUserAgent(ua);
+        // This is the important part: we're enabling resource optimization
+        renderer.getPSUtil().setOptimizeResources(true);
+        ua.setRendererOverride(renderer);
+
+        // Prepare output file
+        File outputFile = renderFile(ua, "ps-resources.fo",
+                "-rend-l" + renderer.getPSUtil().getLanguageLevel());
+        verifyPostScriptFile(outputFile);
+    }
+
+    /**
+     * Tests resource optimization with the {@link PSDocumentHandler}.
+     * @throws Exception if an error occurs
+     */
+    public void testResourceOptimizationWithIF() throws Exception {
+        FOUserAgent ua = fopFactory.newFOUserAgent();
+        PSDocumentHandler handler = new PSDocumentHandler();
+        handler.setUserAgent(ua);
+        // This is the important part: we're enabling resource optimization
+        handler.getPSUtil().setOptimizeResources(true);
+        ua.setDocumentHandlerOverride(handler);
+
+        // Prepare output file
+        File outputFile = renderFile(ua, "ps-resources.fo",
+                "-if-l" + handler.getPSUtil().getLanguageLevel());
+        verifyPostScriptFile(outputFile);
+    }
+
+    private void verifyPostScriptFile(File psFile) throws IOException, DSCException {
+        InputStream in = new java.io.FileInputStream(psFile);
+        in = new java.io.BufferedInputStream(in);
+        try {
+            DSCParser parser = new DSCParser(in);
+
+            //The first form is for arrow_down_small.png (to be reused)
+            PSResource form1 = new PSResource(PSResource.TYPE_FORM, "FOPForm:1");
+            PSResource helvetica = new PSResource(PSResource.TYPE_FONT, "Helvetica");
+            PSResource helveticaBold = new PSResource(PSResource.TYPE_FONT, "Helvetica-Bold");
+
+            PSResource res;
+            DSCCommentPages pages = (DSCCommentPages)gotoDSCComment(parser, DSCConstants.PAGES);
+            assertEquals(2, pages.getPageCount());
+
+            DSCCommentDocumentSuppliedResources supplied
+                = (DSCCommentDocumentSuppliedResources)gotoDSCComment(parser,
+                        DSCConstants.DOCUMENT_SUPPLIED_RESOURCES);
+            Set resources = supplied.getResources();
+            assertEquals(4, resources.size());
+            assertTrue(resources.contains(form1));
+            assertTrue("Expected barcode.eps as supplied resource",
+                    resources.contains(new PSResource(PSResource.TYPE_FILE,
+                            "test/resources/images/barcode.eps")));
+
+            DSCCommentDocumentNeededResources needed
+                = (DSCCommentDocumentNeededResources)gotoDSCComment(parser,
+                        DSCConstants.DOCUMENT_NEEDED_RESOURCES);
+            resources = needed.getResources();
+            assertEquals(2, resources.size());
+            assertTrue("Expected Helvetica as needed resource",
+                    resources.contains(new PSResource(PSResource.TYPE_FONT, "Helvetica")));
+            assertTrue("Expected Helvetica-Bold as needed resource",
+                    resources.contains(new PSResource(PSResource.TYPE_FONT, "Helvetica-Bold")));
+
+            //Some document structure checking
+            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_DEFAULTS));
+            assertNotNull(gotoDSCComment(parser, DSCConstants.END_DEFAULTS));
+            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_PROLOG));
+            assertNotNull(gotoDSCComment(parser, DSCConstants.END_PROLOG));
+            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_SETUP));
+
+            //Check includes for the two referenced base 14 fonts
+            DSCCommentIncludeResource include;
+            Collection strings = new java.util.HashSet(
+                    Arrays.asList(new String[] {"Helvetica", "Helvetica-Bold"}));
+            for (int i = 0; i < 2; i++) {
+                include = (DSCCommentIncludeResource)gotoDSCComment(
+                        parser, DSCConstants.INCLUDE_RESOURCE);
+                res = include.getResource();
+                assertEquals(PSResource.TYPE_FONT, res.getType());
+                strings.remove(res.getName());
+            }
+            assertEquals(0, strings.size());
+
+            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE,
+                    new PSResource(PSResource.TYPE_ENCODING, "WinAnsiEncoding"));
+
+            //Here, we encounter form 1 again
+            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE, form1);
+
+            assertNotNull(gotoDSCComment(parser, DSCConstants.END_SETUP));
+            //Now the actual pages begin
+
+            //---=== Page 1 ===---
+            DSCCommentPage page = (DSCCommentPage)gotoDSCComment(parser, DSCConstants.PAGE);
+            assertEquals(1, page.getPagePosition());
+
+            assertEquals(DSCAtend.class,
+                    gotoDSCComment(parser, DSCConstants.PAGE_RESOURCES).getClass());
+            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_PAGE_SETUP));
+            assertNotNull(gotoDSCComment(parser, DSCConstants.END_PAGE_SETUP));
+
+            PSResource form2 = new PSResource(PSResource.TYPE_FORM, "FOPForm:2");
+            checkResourceComment(parser, DSCConstants.BEGIN_RESOURCE, form2);
+            assertNotNull(gotoDSCComment(parser, DSCConstants.PAGE_TRAILER));
+
+            AbstractResourcesDSCComment pageResources;
+            pageResources = (AbstractResourcesDSCComment)gotoDSCComment(
+                    parser, DSCConstants.PAGE_RESOURCES);
+            resources = pageResources.getResources();
+            assertEquals(5, resources.size());
+            assertTrue(resources.contains(form1));
+            assertTrue(resources.contains(form2));
+            assertTrue(resources.contains(helvetica));
+            assertTrue(resources.contains(helveticaBold));
+
+            //---=== Page 2 ===---
+            page = (DSCCommentPage)gotoDSCComment(parser, DSCConstants.PAGE);
+            assertEquals(2, page.getPagePosition());
+
+            assertEquals(DSCAtend.class,
+                    gotoDSCComment(parser, DSCConstants.PAGE_RESOURCES).getClass());
+            assertNotNull(gotoDSCComment(parser, DSCConstants.BEGIN_PAGE_SETUP));
+            assertNotNull(gotoDSCComment(parser, DSCConstants.END_PAGE_SETUP));
+
+            DSCCommentBeginDocument beginDocument;
+            beginDocument = (DSCCommentBeginDocument)gotoDSCComment(
+                    parser, DSCConstants.BEGIN_DOCUMENT);
+            assertEquals("test/resources/images/barcode.eps",
+                    beginDocument.getResource().getName());
+            DSCListener listener = new DefaultNestedDocumentHandler(null);
+            listener.processEvent(beginDocument, parser);
+
+            //And again (the barcode is generated twice)
+            beginDocument = (DSCCommentBeginDocument)gotoDSCComment(
+                    parser, DSCConstants.BEGIN_DOCUMENT);
+            assertEquals("test/resources/images/barcode.eps",
+                    beginDocument.getResource().getName());
+            listener.processEvent(beginDocument, parser);
+
+            assertNotNull(gotoDSCComment(parser, DSCConstants.PAGE_TRAILER));
+            pageResources = (AbstractResourcesDSCComment)gotoDSCComment(
+                    parser, DSCConstants.PAGE_RESOURCES);
+            resources = pageResources.getResources();
+            assertEquals(6, resources.size());
+            assertTrue(resources.contains(form1));
+            assertFalse(resources.contains(form2));
+            assertTrue(resources.contains(helvetica));
+            assertTrue(resources.contains(helveticaBold));
+            assertTrue(resources.contains(beginDocument.getResource()));
+
+            assertNotNull(gotoDSCComment(parser, DSCConstants.TRAILER));
+            //No headers in between, as they should have been put at the beginning of the file
+            assertEquals(DSCCommentEndOfFile.class, parser.nextEvent().asDSCComment().getClass());
+
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+}
diff --git a/test/java/org/apache/fop/render/ps/ps-jpeg-image.fo b/test/java/org/apache/fop/render/ps/ps-jpeg-image.fo
new file mode 100644 (file)
index 0000000..50ed2d6
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  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$ -->
+<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
+  <fo:layout-master-set>
+    <fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin="2cm">
+      <fo:region-body/>
+    </fo:simple-page-master>
+  </fo:layout-master-set>
+  <fo:page-sequence master-reference="A4">
+    <fo:flow flow-name="xsl-region-body">
+      <fo:block>JPEG image:</fo:block>
+      <fo:block><fo:external-graphic src="test/resources/images/bgimg72dpi.jpg"/></fo:block>
+      <fo:block>
+        <fo:external-graphic src="test/resources/images/bgimg300dpi.jpg"/>
+        <fo:external-graphic src="test/resources/images/bgimg300dpi.jpg"/>
+      </fo:block>
+    </fo:flow>
+  </fo:page-sequence>
+</fo:root>
diff --git a/test/java/org/apache/fop/render/ps/ps-resources.fo b/test/java/org/apache/fop/render/ps/ps-resources.fo
new file mode 100644 (file)
index 0000000..7982d35
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  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$ -->
+<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
+  <fo:layout-master-set>
+    <fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin="2cm">
+      <fo:region-body/>
+    </fo:simple-page-master>
+  </fo:layout-master-set>
+  <fo:page-sequence master-reference="A4">
+    <fo:flow flow-name="xsl-region-body">
+      <fo:block font-weight="bold">PostScript Resource Optimization Test</fo:block>
+      <fo:block>Used again later:</fo:block>
+      <fo:block><fo:external-graphic src="test/resources/images/arrow_down_small.png"/></fo:block>
+      <fo:block>Used only once in the whole document:</fo:block>
+      <fo:block><fo:external-graphic src="test/resources/images/arrow_up_small.png"/></fo:block>
+    </fo:flow>
+  </fo:page-sequence>
+  <fo:page-sequence master-reference="A4">
+    <fo:flow flow-name="xsl-region-body">
+      <fo:block font-weight="bold">PostScript Resource Optimization Test</fo:block>
+      <fo:block>Image already used in previous page-sequence:</fo:block>
+      <fo:block>
+        <fo:external-graphic src="test/resources/images/arrow_down_small.png" content-width="80%"/>
+        <fo:external-graphic src="test/resources/images/arrow_down_small.png"/>
+        <fo:external-graphic src="test/resources/images/arrow_down_small.png" content-width="120%"/>
+      </fo:block>
+      <fo:block>Can't currently reuse EPS images:</fo:block>
+      <fo:block>
+        <fo:external-graphic src="test/resources/images/barcode.eps"/>
+        <fo:external-graphic src="test/resources/images/barcode.eps"/>
+      </fo:block>
+    </fo:flow>
+  </fo:page-sequence>
+</fo:root>