diff options
Diffstat (limited to 'src/java')
91 files changed, 4855 insertions, 1472 deletions
diff --git a/src/java/META-INF/services/org.apache.fop.render.pdf.PDFImageHandler b/src/java/META-INF/services/org.apache.fop.render.pdf.PDFImageHandler new file mode 100644 index 000000000..b72796c2d --- /dev/null +++ b/src/java/META-INF/services/org.apache.fop.render.pdf.PDFImageHandler @@ -0,0 +1,5 @@ +org.apache.fop.render.pdf.PDFImageHandlerRawJPEG
+org.apache.fop.render.pdf.PDFImageHandlerRawCCITTFax
+org.apache.fop.render.pdf.PDFImageHandlerGraphics2D
+org.apache.fop.render.pdf.PDFImageHandlerRenderedImage
+org.apache.fop.render.pdf.PDFImageHandlerXML
\ No newline at end of file diff --git a/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter new file mode 100644 index 000000000..c3c688a2c --- /dev/null +++ b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageConverter @@ -0,0 +1,2 @@ +org.apache.fop.image.loader.batik.ImageConverterSVG2G2D
+org.apache.fop.image.loader.batik.ImageConverterWMF2G2D
diff --git a/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageLoaderFactory b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageLoaderFactory new file mode 100644 index 000000000..b607a5e64 --- /dev/null +++ b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImageLoaderFactory @@ -0,0 +1,2 @@ +org.apache.fop.image.loader.batik.ImageLoaderFactorySVG
+org.apache.fop.image.loader.batik.ImageLoaderFactoryWMF
\ No newline at end of file diff --git a/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImagePreloader b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImagePreloader new file mode 100644 index 000000000..c64d899b7 --- /dev/null +++ b/src/java/META-INF/services/org.apache.xmlgraphics.image.loader.spi.ImagePreloader @@ -0,0 +1,2 @@ +org.apache.fop.image.loader.batik.PreloaderWMF
+org.apache.fop.image.loader.batik.PreloaderSVG
diff --git a/src/java/org/apache/fop/apps/FOURIResolver.java b/src/java/org/apache/fop/apps/FOURIResolver.java index d68905e4c..f38be542a 100644 --- a/src/java/org/apache/fop/apps/FOURIResolver.java +++ b/src/java/org/apache/fop/apps/FOURIResolver.java @@ -32,13 +32,13 @@ import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; import javax.xml.transform.stream.StreamSource; -// commons logging import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.util.DataURIResolver; import org.apache.xmlgraphics.util.io.Base64EncodeStream; +import org.apache.fop.util.DataURIResolver; + /** * Provides FOP specific URI resolution. This is the default URIResolver * {@link FOUserAgent} will use unless overidden. @@ -132,10 +132,23 @@ public class FOURIResolver implements javax.xml.transform.URIResolver { // Fallback to default resolution mechanism if (source == null) { URL absoluteURL = null; - File file = new File(href); + int hashPos = href.indexOf('#'); + String fileURL, fragment; + if (hashPos >= 0) { + fileURL = href.substring(0, hashPos); + fragment = href.substring(hashPos); + } else { + fileURL = href; + fragment = null; + } + File file = new File(fileURL); if (file.canRead() && file.isFile()) { try { - absoluteURL = file.toURL(); + if (fragment != null) { + absoluteURL = new URL(file.toURL().toExternalForm() + fragment); + } else { + absoluteURL = file.toURL(); + } } catch (MalformedURLException mfue) { handleException(mfue, "Could not convert filename '" + href + "' to URL", throwExceptions); diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index 07928fef2..966e227fe 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -23,15 +23,18 @@ package org.apache.fop.apps; import java.io.File; import java.util.Date; import java.util.Map; + import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; -// commons logging import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -// FOP +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; + import org.apache.fop.Version; import org.apache.fop.fo.FOEventHandler; import org.apache.fop.pdf.PDFEncryptionParams; @@ -110,6 +113,22 @@ public class FOUserAgent { /** Set of keywords applicable to this document. */ protected String keywords = null; + private ImageSessionContext imageSessionContext = new AbstractImageSessionContext() { + + public ImageContext getParentContext() { + return getFactory(); + } + + public float getTargetResolution() { + return FOUserAgent.this.getTargetResolution(); + } + + public Source resolveURI(String uri) { + return FOUserAgent.this.resolveURI(uri); + } + + }; + /** * Default constructor * @see org.apache.fop.apps.FopFactory @@ -442,6 +461,14 @@ public class FOUserAgent { setTargetResolution((float)dpi); } + /** + * Returns the image session context for the image package. + * @return the ImageSessionContext instance for this rendering run + */ + public ImageSessionContext getImageSessionContext() { + return this.imageSessionContext; + } + // ---------------------------------------------- environment-level stuff // (convenience access to FopFactory methods) diff --git a/src/java/org/apache/fop/apps/FopFactory.java b/src/java/org/apache/fop/apps/FopFactory.java index c87a3041b..dc94d92fb 100644 --- a/src/java/org/apache/fop/apps/FopFactory.java +++ b/src/java/org/apache/fop/apps/FopFactory.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.util.Collection; import java.util.Collections; @@ -36,10 +37,12 @@ import javax.xml.transform.URIResolver; import org.xml.sax.SAXException; import org.apache.avalon.framework.configuration.Configuration; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageManager; + import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.ElementMappingRegistry; import org.apache.fop.fonts.FontCache; @@ -57,7 +60,7 @@ import org.apache.fop.util.ContentHandlerFactoryRegistry; * Information that may potentially be different for each rendering run can be * found and managed in the FOUserAgent. */ -public class FopFactory { +public class FopFactory implements ImageContext { /** logger instance */ private static Log log = LogFactory.getLog(FopFactory.class); @@ -83,6 +86,9 @@ public class FopFactory { /** Image factory for creating fop image objects */ private ImageFactory imageFactory; + /** Image manager for loading and caching image objects */ + private ImageManager imageManager; + /** Configuration layer used to configure fop */ private FopFactoryConfigurator config = null; @@ -150,7 +156,8 @@ public class FopFactory { this.elementMappingRegistry = new ElementMappingRegistry(this); this.foURIResolver = new FOURIResolver(validateUserConfigStrictly()); this.colorSpaceCache = new ColorSpaceCache(foURIResolver); - this.imageFactory = new ImageFactory(); + this.imageFactory = new ImageFactory(); + this.imageManager = new ImageManager(this); this.rendererFactory = new RendererFactory(); this.xmlHandlers = new XMLHandlerRegistry(); this.ignoredNamespaces = new java.util.HashSet(); @@ -293,6 +300,14 @@ public class FopFactory { } /** + * Returns the image manager. + * @return the image manager + */ + public ImageManager getImageManager() { + return this.imageManager; + } + + /** * Add the element mapping with the given class name. * @param elementMapping the class name representing the element mapping. */ @@ -748,5 +763,6 @@ public class FopFactory { */ public ColorSpace getColorSpace(String baseUri, String iccProfileSrc) { return colorSpaceCache.get(baseUri, iccProfileSrc); - } + } + } diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java index a107da833..7454f4667 100644 --- a/src/java/org/apache/fop/area/AreaTreeHandler.java +++ b/src/java/org/apache/fop/area/AreaTreeHandler.java @@ -189,12 +189,12 @@ public class AreaTreeHandler extends FOEventHandler { } } - /** - * {@inheritDoc} - * @param pageSequence - * is the pageSequence being started - */ + /** {@inheritDoc} */ public void startPageSequence(PageSequence pageSequence) { + startAbstractPageSequence(pageSequence); + } + + private void startAbstractPageSequence(AbstractPageSequence pageSequence) { rootFObj = pageSequence.getRoot(); finishPrevPageSequence(pageSequence.getInitialPageNumber()); pageSequence.initPageNumber(); @@ -238,18 +238,12 @@ public class AreaTreeHandler extends FOEventHandler { } } - /** - * @see org.apache.fop.fo.FOEventHandler#startExternalDocument(org.apache.fop.fo.extensions.ExternalDocument) - */ + /** {@inheritDoc} */ public void startExternalDocument(ExternalDocument document) { - rootFObj = document.getRoot(); - finishPrevPageSequence(document.getInitialPageNumber()); - document.initPageNumber(); + startAbstractPageSequence(document); } - /** - * @see org.apache.fop.fo.FOEventHandler#endExternalDocument(org.apache.fop.fo.extensions.ExternalDocument) - */ + /** {@inheritDoc} */ public void endExternalDocument(ExternalDocument document) { if (statistics != null) { statistics.end(); diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index b4a804712..d4cdf5239 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -47,6 +47,10 @@ import org.xml.sax.helpers.DefaultHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Trait.Background; import org.apache.fop.area.Trait.InternalLink; @@ -69,8 +73,6 @@ import org.apache.fop.fo.expr.PropertyException; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; import org.apache.fop.traits.BorderProps; import org.apache.fop.util.ColorUtil; import org.apache.fop.util.ContentHandlerFactory; @@ -1048,22 +1050,19 @@ public class AreaTreeParser { } catch (PropertyException e) { throw new IllegalArgumentException(e.getMessage()); } - String url = attributes.getValue("bkg-img"); - if (url != null) { - bkg.setURL(url); - - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage img = fact.getImage(url, userAgent); - if (img == null) { - log.error("Background image not available: " + url); - } else { - // load dimensions - if (!img.load(FopImage.DIMENSIONS)) { - log.error("Cannot read background image dimensions: " - + url); - } + String uri = attributes.getValue("bkg-img"); + if (uri != null) { + bkg.setURL(uri); + + try { + ImageManager manager = userAgent.getFactory().getImageManager(); + ImageSessionContext sessionContext + = userAgent.getImageSessionContext(); + ImageInfo info = manager.getImageInfo(uri, sessionContext); + bkg.setImageInfo(info); + } catch (Exception e) { + log.error("Background image not available: " + uri, e); } - bkg.setFopImage(img); String repeat = attributes.getValue("bkg-repeat"); if (repeat != null) { diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java index b3c3d3947..98bed098c 100644 --- a/src/java/org/apache/fop/area/Trait.java +++ b/src/java/org/apache/fop/area/Trait.java @@ -22,9 +22,10 @@ package org.apache.fop.area; import java.awt.Color; import java.io.Serializable; +import org.apache.xmlgraphics.image.loader.ImageInfo; + import org.apache.fop.fo.Constants; import org.apache.fop.fonts.FontTriplet; -import org.apache.fop.image.FopImage; import org.apache.fop.traits.BorderProps; import org.apache.fop.util.ColorUtil; @@ -559,7 +560,7 @@ public class Trait implements Serializable { private String url = null; /** The background image if any. */ - private FopImage fopimage = null; + private ImageInfo imageInfo = null; /** Background repeat enum for images. */ private int repeat; @@ -603,11 +604,11 @@ public class Trait implements Serializable { } /** - * Returns the FopImage representing the background image + * Returns the ImageInfo object representing the background image * @return the background image, null if n/a */ - public FopImage getFopImage() { - return fopimage; + public ImageInfo getImageInfo() { + return imageInfo; } /** @@ -659,11 +660,11 @@ public class Trait implements Serializable { } /** - * Sets the FopImage to use as the background image. - * @param fopimage The FopImage to use + * Sets the ImageInfo of the image to use as the background image. + * @param info The background image's info object */ - public void setFopImage(FopImage fopimage) { - this.fopimage = fopimage; + public void setImageInfo(ImageInfo info) { + this.imageInfo = info; } /** diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java index 63a817071..3bb0aae7f 100644 --- a/src/java/org/apache/fop/cli/CommandLineOptions.java +++ b/src/java/org/apache/fop/cli/CommandLineOptions.java @@ -29,6 +29,11 @@ import java.util.Vector; import javax.swing.UIManager; +import org.xml.sax.SAXException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.apache.fop.Version; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; @@ -38,19 +43,12 @@ import org.apache.fop.pdf.PDFAMode; import org.apache.fop.pdf.PDFEncryptionManager; import org.apache.fop.pdf.PDFEncryptionParams; import org.apache.fop.pdf.PDFXMode; -import org.apache.fop.render.awt.AWTRenderer; import org.apache.fop.render.Renderer; +import org.apache.fop.render.awt.AWTRenderer; import org.apache.fop.render.pdf.PDFRenderer; import org.apache.fop.render.xml.XMLRenderer; import org.apache.fop.util.CommandLineLogger; -// commons logging -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -// SAX -import org.xml.sax.SAXException; - /** * Options parses the commandline arguments */ @@ -71,6 +69,8 @@ public class CommandLineOptions { public static final int XSLT_INPUT = 2; /** input: Area Tree XML file */ public static final int AREATREE_INPUT = 3; + /** input: Image file */ + public static final int IMAGE_INPUT = 4; /* show configuration information */ private Boolean showConfiguration = Boolean.FALSE; @@ -86,6 +86,8 @@ public class CommandLineOptions { private File xmlfile = null; /* area tree input file */ private File areatreefile = null; + /* area tree input file */ + private File imagefile = null; /* output file */ private File outfile = null; /* input mode */ @@ -249,6 +251,8 @@ public class CommandLineOptions { i = i + parseXMLInputOption(args, i); } else if (args[i].equals("-atin")) { i = i + parseAreaTreeInputOption(args, i); + } else if (args[i].equals("-imagein")) { + i = i + parseImageInputOption(args, i); } else if (args[i].equals("-awt")) { i = i + parseAWTOutputOption(args, i); } else if (args[i].equals("-pdf")) { @@ -594,6 +598,17 @@ public class CommandLineOptions { } } + private int parseImageInputOption(String[] args, int i) throws FOPException { + inputmode = IMAGE_INPUT; + if ((i + 1 == args.length) + || (args[i + 1].charAt(0) == '-')) { + throw new FOPException("you must specify the image file for the '-imagein' option"); + } else { + imagefile = new File(args[i + 1]); + return 1; + } + } + private PDFEncryptionParams getPDFEncryptionParams() throws FOPException { PDFEncryptionParams params = (PDFEncryptionParams)renderingOptions.get( PDFRenderer.ENCRYPTION_PARAMS); @@ -768,6 +783,20 @@ public class CommandLineOptions { + areatreefile.getAbsolutePath() + " not found "); } + } else if (inputmode == IMAGE_INPUT) { + if (outputmode.equals(MimeConstants.MIME_XSL_FO)) { + throw new FOPException( + "FO output mode is only available if you use -xml and -xsl"); + } + if (xmlfile != null) { + log.warn("image input mode, but XML file is set:"); + log.error("XML file: " + xmlfile.toString()); + } + if (!imagefile.exists()) { + throw new FileNotFoundException("Error: image file " + + imagefile.getAbsolutePath() + + " not found "); + } } } // end checkSettings @@ -814,6 +843,8 @@ public class CommandLineOptions { return new AreaTreeInputHandler(areatreefile); case XSLT_INPUT: return new InputHandler(xmlfile, xsltfile, xsltParams); + case IMAGE_INPUT: + return new ImageInputHandler(imagefile, xsltfile, xsltParams); default: throw new IllegalArgumentException("Error creating InputHandler object."); } @@ -920,6 +951,7 @@ public class CommandLineOptions { + " -fo infile xsl:fo input file \n" + " -xml infile xml input file, must be used together with -xsl \n" + " -atin infile area tree input file \n" + + " -imagein infile image input file \n" + " -xsl stylesheet xslt stylesheet \n \n" + " -param name value <value> to use for parameter <name> in xslt stylesheet\n" + " (repeat '-param name value' for each parameter)\n \n" diff --git a/src/java/org/apache/fop/cli/ImageInputHandler.java b/src/java/org/apache/fop/cli/ImageInputHandler.java new file mode 100644 index 000000000..fe3d5021f --- /dev/null +++ b/src/java/org/apache/fop/cli/ImageInputHandler.java @@ -0,0 +1,60 @@ +/* + * 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.cli; + +import java.io.File; +import java.io.StringReader; +import java.util.Vector; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; + +/** + * InputHandler for the images (for example TIFF) as input. + */ +public class ImageInputHandler extends InputHandler { + + /** + * Main constructor. + * @param imagefile the image file + * @param xsltfile XSLT file (may be null in which case the default stylesheet is used) + * @param params Vector of command-line parameters (name, value, + * name, value, ...) for XSL stylesheet, null if none + */ + public ImageInputHandler(File imagefile, File xsltfile, Vector params) { + super(imagefile, xsltfile, params); + } + + /** {@inheritDoc} */ + protected Source createMainSource() { + return new StreamSource(new StringReader( + "<image>" + this.sourcefile.toURI().toASCIIString() + "</image>")); + } + + /** {@inheritDoc} */ + protected Source createXSLTSource() { + Source src = super.createXSLTSource(); + if (src == null) { + src = new StreamSource(getClass().getResource("image2fo.xsl").toExternalForm()); + } + return src; + } + +} diff --git a/src/java/org/apache/fop/cli/InputHandler.java b/src/java/org/apache/fop/cli/InputHandler.java index bfbce269d..89977beb7 100644 --- a/src/java/org/apache/fop/cli/InputHandler.java +++ b/src/java/org/apache/fop/cli/InputHandler.java @@ -24,7 +24,6 @@ import java.io.File; import java.io.OutputStream; import java.util.Vector; -// Imported TraX classes import javax.xml.transform.ErrorListener; import javax.xml.transform.Result; import javax.xml.transform.Source; @@ -37,6 +36,7 @@ import javax.xml.transform.stream.StreamSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.Fop; @@ -50,7 +50,8 @@ import org.apache.fop.render.awt.viewer.Renderable; */ public class InputHandler implements ErrorListener, Renderable { - private File sourcefile = null; // either FO or XML/XSLT usage + /** original source file */ + protected File sourcefile = null; private File stylesheet = null; // for XML/XSLT usage private Vector xsltParams = null; // for XML/XSLT usage @@ -132,6 +133,26 @@ public class InputHandler implements ErrorListener, Renderable { } /** + * Creates a Source for the main input file. + * @return the Source for the main input file + */ + protected Source createMainSource() { + return new StreamSource(this.sourcefile); + } + + /** + * Creates a Source for the selected stylesheet. + * @return the Source for the selected stylesheet or null if there's no stylesheet + */ + protected Source createXSLTSource() { + if (this.stylesheet != null) { + return new StreamSource(this.stylesheet); + } else { + return null; + } + } + + /** * Transforms the input document to the input format expected by FOP using XSLT. * @param result the Result object where the result of the XSL transformation is sent to * @throws FOPException in case of an error during processing @@ -142,11 +163,11 @@ public class InputHandler implements ErrorListener, Renderable { TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer; - if (stylesheet == null) { // FO Input + Source xsltSource = createXSLTSource(); + if (xsltSource == null) { // FO Input transformer = factory.newTransformer(); } else { // XML/XSLT input - transformer = factory.newTransformer(new StreamSource( - stylesheet)); + transformer = factory.newTransformer(xsltSource); // Set the value of parameters, if any, defined for stylesheet if (xsltParams != null) { @@ -159,7 +180,7 @@ public class InputHandler implements ErrorListener, Renderable { transformer.setErrorListener(this); // Create a SAXSource from the input Source file - Source src = new StreamSource(sourcefile); + Source src = createMainSource(); // Start XSLT transformation and FOP processing transformer.transform(src, result); diff --git a/src/java/org/apache/fop/cli/image2fo.xsl b/src/java/org/apache/fop/cli/image2fo.xsl new file mode 100644 index 000000000..3a2610230 --- /dev/null +++ b/src/java/org/apache/fop/cli/image2fo.xsl @@ -0,0 +1,45 @@ +<?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$ --> +<xsl:stylesheet + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" + version="1.0"> + + <xsl:output method="xml" indent="yes"/> + + <xsl:template name="masters"> + <fo:layout-master-set> + <!-- not really used but mandatory --> + <fo:simple-page-master master-name="dummy" page-height="29.7cm" page-width="21cm"> + <fo:region-body/> + </fo:simple-page-master> + </fo:layout-master-set> + </xsl:template> + + <xsl:template match="/image"> + <fo:root> + <xsl:call-template name="masters"/> + <fox:external-document> + <xsl:attribute name="src"><xsl:value-of select="."/></xsl:attribute> + </fox:external-document> + </fo:root> + </xsl:template> + +</xsl:stylesheet> diff --git a/src/java/org/apache/fop/fo/FOTreeBuilder.java b/src/java/org/apache/fop/fo/FOTreeBuilder.java index 980404ffd..63fc5cb5b 100644 --- a/src/java/org/apache/fop/fo/FOTreeBuilder.java +++ b/src/java/org/apache/fop/fo/FOTreeBuilder.java @@ -276,9 +276,7 @@ public class FOTreeBuilder extends DefaultHandler { */ private int nestedMarkerDepth = 0; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void startElement(String namespaceURI, String localName, String rawName, Attributes attlist) throws SAXException { @@ -359,19 +357,16 @@ public class FOTreeBuilder extends DefaultHandler { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void endElement(String uri, String localName, String rawName) throws SAXException { if (currentFObj == null) { - throw new IllegalStateException( + throw new SAXException( "endElement() called for " + rawName + " where there is no current element."); - } else - if (!currentFObj.getLocalName().equals(localName) + } else if (!currentFObj.getLocalName().equals(localName) || !currentFObj.getNamespaceURI().equals(uri)) { - log.warn("Mismatch: " + currentFObj.getLocalName() + throw new SAXException("Mismatch: " + currentFObj.getLocalName() + " (" + currentFObj.getNamespaceURI() + ") vs. " + localName + " (" + uri + ")"); } diff --git a/src/java/org/apache/fop/fo/flow/ExternalGraphic.java b/src/java/org/apache/fop/fo/flow/ExternalGraphic.java index f0285e6f4..dd45f585c 100644 --- a/src/java/org/apache/fop/fo/flow/ExternalGraphic.java +++ b/src/java/org/apache/fop/fo/flow/ExternalGraphic.java @@ -19,15 +19,22 @@ package org.apache.fop.fo.flow; +import java.io.IOException; + +import org.xml.sax.Locator; + +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; + import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.Length; +import org.apache.fop.datatypes.URISpecification; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.xml.sax.Locator; +import org.apache.fop.fo.properties.FixedLength; /** * Class modelling the fo:external-graphic object. @@ -63,21 +70,25 @@ public class ExternalGraphic extends AbstractGraphics { super.bind(pList); src = pList.get(PR_SRC).getString(); - //Additional processing: preload image - url = ImageFactory.getURL(getSrc()); + //Additional processing: obtain the image's intrinsic size and baseline information + url = URISpecification.getURL(src); FOUserAgent userAgent = getUserAgent(); - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage fopimage = fact.getImage(url, userAgent); - if (fopimage == null) { - log.error("Image not available: " + getSrc()); - } else { - // load dimensions - if (!fopimage.load(FopImage.DIMENSIONS)) { - log.error("Cannot read image dimensions: " + getSrc()); + ImageManager manager = userAgent.getFactory().getImageManager(); + ImageInfo info = null; + try { + info = manager.getImageInfo(url, userAgent.getImageSessionContext()); + } catch (ImageException e) { + log.error("Image not available: " + e.getMessage()); + } catch (IOException ioe) { + log.error("I/O error while loading image: " + ioe.getMessage()); + } + if (info != null) { + this.intrinsicWidth = info.getSize().getWidthMpt(); + this.intrinsicHeight = info.getSize().getHeightMpt(); + int baseline = info.getSize().getBaselinePositionFromBottom(); + if (baseline != 0) { + this.intrinsicAlignmentAdjust = new FixedLength(-baseline); } - this.intrinsicWidth = fopimage.getIntrinsicWidth(); - this.intrinsicHeight = fopimage.getIntrinsicHeight(); - this.intrinsicAlignmentAdjust = fopimage.getIntrinsicAlignmentAdjust(); } //TODO Report to caller so he can decide to throw an exception } diff --git a/src/java/org/apache/fop/fo/pagination/Root.java b/src/java/org/apache/fop/fo/pagination/Root.java index 139f7606a..6e079cf47 100644 --- a/src/java/org/apache/fop/fo/pagination/Root.java +++ b/src/java/org/apache/fop/fo/pagination/Root.java @@ -31,8 +31,8 @@ import org.apache.fop.fo.FONode; import org.apache.fop.fo.FObj; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; -import org.apache.fop.fo.pagination.bookmarks.BookmarkTree; import org.apache.fop.fo.extensions.destination.Destination; +import org.apache.fop.fo.pagination.bookmarks.BookmarkTree; /** * The fo:root formatting object. Contains page masters, page-sequences. @@ -73,20 +73,16 @@ public class Root extends FObj { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { mediaUsage = pList.get(PR_MEDIA_USAGE).getEnum(); } - /** - * Signal end of this xml element. - */ + /** {@inheritDoc} */ protected void endOfNode() throws FOPException { if (!pageSequenceFound || layoutMasterSet == null) { - missingChildElementError("(layout-master-set, declarations?, " + - "bookmark-tree?, (page-sequence+|fox:external-document))"); + missingChildElementError("(layout-master-set, declarations?, " + + "bookmark-tree?, (page-sequence|fox:external-document)+)"); } } diff --git a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java index 866a020e2..68d4bcab1 100755 --- a/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java +++ b/src/java/org/apache/fop/fo/properties/CommonBorderPaddingBackground.java @@ -21,13 +21,16 @@ package org.apache.fop.fo.properties; import java.awt.Color; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.Constants; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.expr.PropertyException; -import org.apache.fop.image.FopImage; import org.apache.fop.image.ImageFactory; /** @@ -66,7 +69,7 @@ public class CommonBorderPaddingBackground { public Length backgroundPositionVertical; - private FopImage fopimage; + private ImageInfo backgroundImageInfo; /** the "before" edge */ @@ -232,18 +235,16 @@ public class CommonBorderPaddingBackground { Constants.PR_BACKGROUND_POSITION_VERTICAL).getLength(); //Additional processing: preload image - String url = ImageFactory.getURL(backgroundImage); + String uri = ImageFactory.getURL(backgroundImage); FOUserAgent userAgent = pList.getFObj().getUserAgent(); - ImageFactory fact = userAgent.getFactory().getImageFactory(); - fopimage = fact.getImage(url, userAgent); - if (fopimage == null) { - Property.log.error("Background image not available: " + backgroundImage); - } else { - // load dimensions - if (!fopimage.load(FopImage.DIMENSIONS)) { - Property.log.error("Cannot read background image dimensions: " - + backgroundImage); - } + ImageManager manager = userAgent.getFactory().getImageManager(); + ImageSessionContext sessionContext = userAgent.getImageSessionContext(); + ImageInfo info; + try { + info = manager.getImageInfo(uri, sessionContext); + this.backgroundImageInfo = info; + } catch (Exception e) { + Property.log.error("Background image not available: " + uri); } //TODO Report to caller so he can decide to throw an exception } @@ -315,11 +316,11 @@ public class CommonBorderPaddingBackground { } /** - * @return the background image as a preloaded FopImage, null if there is + * @return the background image info object, null if there is * no background image. */ - public FopImage getFopImage() { - return this.fopimage; + public ImageInfo getImageInfo() { + return this.backgroundImageInfo; } /** @@ -455,7 +456,7 @@ public class CommonBorderPaddingBackground { * @return true if there is any kind of background to be painted */ public boolean hasBackground() { - return ((backgroundColor != null || getFopImage() != null)); + return ((backgroundColor != null || getImageInfo() != null)); } /** @return true if border is non-zero. */ diff --git a/src/java/org/apache/fop/image/loader/batik/BatikUtil.java b/src/java/org/apache/fop/image/loader/batik/BatikUtil.java new file mode 100644 index 000000000..1ae2a9917 --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/BatikUtil.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +/** + * Helper utilities for Apache Batik. + */ +public class BatikUtil { + + /** + * Checks whether Apache Batik is available in the classpath. + * @return true if Apache Batik is available + */ + public static boolean isBatikAvailable() { + try { + Class.forName("org.apache.batik.dom.svg.SVGDOMImplementation"); + return true; + } catch (Exception e) { + //ignore + } + return false; + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java new file mode 100644 index 000000000..81b3b4c07 --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageConverterSVG2G2D.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.gvt.GraphicsNode; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageProcessingHints; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageConverter; +import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; +import org.apache.xmlgraphics.util.UnitConv; + +import org.apache.fop.svg.SVGUserAgent; + +/** + * This ImageConverter converts SVG images to Java2D. + * <p> + * Note: The target flavor is "generic" Java2D. No Batik-specific bridges are hooked into the + * conversion process. Specialized renderers may want to provide specialized adapters to profit + * from target-format features (for example with PDF or PS). This converter is mainly for formats + * which only support bitmap images or rudimentary Java2D support. + */ +public class ImageConverterSVG2G2D extends AbstractImageConverter { + + /** {@inheritDoc} */ + public Image convert(Image src, Map hints) throws ImageException { + checkSourceFlavor(src); + final ImageXMLDOM svg = (ImageXMLDOM)src; + if (!SVGDOMImplementation.SVG_NAMESPACE_URI.equals(svg.getRootNamespace())) { + throw new IllegalArgumentException("XML DOM is not in the SVG namespace: " + + svg.getRootNamespace()); + } + + //Prepare + float pxToMillimeter = (float)UnitConv.mm2in(72); //default: 72dpi + Number ptm = (Number)hints.get(ImageProcessingHints.SOURCE_RESOLUTION); + if (ptm != null) { + pxToMillimeter = (float)UnitConv.mm2in(ptm.doubleValue()); + } + SVGUserAgent ua = new SVGUserAgent( + pxToMillimeter, + new AffineTransform()); + GVTBuilder builder = new GVTBuilder(); + final BridgeContext ctx = new BridgeContext(ua); + + //Build the GVT tree + final GraphicsNode root; + try { + root = builder.build(ctx, svg.getDocument()); + } catch (Exception e) { + throw new ImageException("GVT tree could not be built for SVG graphic", e); + } + + //Create the painter + Graphics2DImagePainter painter = new Graphics2DImagePainter() { + + public void paint(Graphics2D g2d, Rectangle2D area) { + // If no viewbox is defined in the svg file, a viewbox of 100x100 is + // assumed, as defined in SVGUserAgent.getViewportSize() + float iw = (float) ctx.getDocumentSize().getWidth(); + float ih = (float) ctx.getDocumentSize().getHeight(); + float w = (float) area.getWidth(); + float h = (float) area.getHeight(); + g2d.translate(area.getX(), area.getY()); + g2d.scale(w / iw, h / ih); + + root.paint(g2d); + } + + public Dimension getImageSize() { + return new Dimension(svg.getSize().getWidthMpt(), svg.getSize().getHeightMpt()); + } + + }; + + ImageGraphics2D g2dImage = new ImageGraphics2D(src.getInfo(), painter); + return g2dImage; + } + + /** {@inheritDoc} */ + public ImageFlavor getSourceFlavor() { + return ImageFlavor.XML_DOM; + } + + /** {@inheritDoc} */ + public ImageFlavor getTargetFlavor() { + return ImageFlavor.GRAPHICS2D; + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageConverterWMF2G2D.java b/src/java/org/apache/fop/image/loader/batik/ImageConverterWMF2G2D.java new file mode 100644 index 000000000..6babe4523 --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageConverterWMF2G2D.java @@ -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.image.loader.batik; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; +import java.util.Map; + +import org.apache.batik.transcoder.wmf.tosvg.WMFPainter; +import org.apache.batik.transcoder.wmf.tosvg.WMFRecordStore; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageConverter; +import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; + +/** + * This ImageConverter converts WMF (Windows Metafile) images (represented by Batik's + * WMFRecordStore) to Java2D. + */ +public class ImageConverterWMF2G2D extends AbstractImageConverter { + + /** logger */ + private static Log log = LogFactory.getLog(ImageConverterWMF2G2D.class); + + /** {@inheritDoc} */ + public Image convert(Image src, Map hints) { + checkSourceFlavor(src); + ImageWMF wmf = (ImageWMF)src; + + Graphics2DImagePainter painter; + painter = new Graphics2DImagePainterWMF(wmf); + + ImageGraphics2D g2dImage = new ImageGraphics2D(src.getInfo(), painter); + return g2dImage; + } + + /** {@inheritDoc} */ + public ImageFlavor getSourceFlavor() { + return ImageWMF.WMF_IMAGE; + } + + /** {@inheritDoc} */ + public ImageFlavor getTargetFlavor() { + return ImageFlavor.GRAPHICS2D; + } + + private static class Graphics2DImagePainterWMF implements Graphics2DImagePainter { + + private ImageWMF wmf; + + public Graphics2DImagePainterWMF(ImageWMF wmf) { + this.wmf = wmf; + } + + /** {@inheritDoc} */ + public Dimension getImageSize() { + return wmf.getSize().getDimensionMpt(); + } + + /** {@inheritDoc} */ + public void paint(Graphics2D g2d, Rectangle2D area) { + WMFRecordStore wmfStore = wmf.getRecordStore(); + double w = area.getWidth(); + double h = area.getHeight(); + + //Fit in paint area + g2d.translate(area.getX(), area.getY()); + double sx = w / wmfStore.getWidthPixels(); + double sy = h / wmfStore.getHeightPixels(); + if (sx != 1.0 || sy != 1.0) { + g2d.scale(sx, sy); + } + + WMFPainter painter = new WMFPainter(wmfStore, 1.0f); + long start = System.currentTimeMillis(); + painter.paint(g2d); + if (log.isDebugEnabled()) { + long duration = System.currentTimeMillis() - start; + log.debug("Painting WMF took " + duration + " ms."); + } + } + + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java b/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java new file mode 100644 index 000000000..fa58d8e4a --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactorySVG.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageLoaderFactory; +import org.apache.xmlgraphics.image.loader.spi.ImageLoader; +import org.apache.xmlgraphics.util.MimeConstants; + +/** + * Factory class for the ImageLoader for SVG. + */ +public class ImageLoaderFactorySVG extends AbstractImageLoaderFactory { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageFlavor.XML_DOM}; + + private static final String[] MIMES = new String[] { + MimeConstants.MIME_SVG}; + + /** {@inheritDoc} */ + public String[] getSupportedMIMETypes() { + return MIMES; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedFlavors(String mime) { + return FLAVORS; + } + + /** {@inheritDoc} */ + public ImageLoader newImageLoader(ImageFlavor targetFlavor) { + return new ImageLoaderSVG(targetFlavor); + } + + /** {@inheritDoc} */ + public int getUsagePenalty(String mime, ImageFlavor flavor) { + return 0; + } + + /** {@inheritDoc} */ + public boolean isAvailable() { + return BatikUtil.isBatikAvailable(); + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactoryWMF.java b/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactoryWMF.java new file mode 100644 index 000000000..bfa004d7e --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageLoaderFactoryWMF.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageLoaderFactory; +import org.apache.xmlgraphics.image.loader.spi.ImageLoader; + +/** + * Factory class for the ImageLoader for WMF (Windows Metafile). + */ +public class ImageLoaderFactoryWMF extends AbstractImageLoaderFactory { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageWMF.WMF_IMAGE}; + + private static final String[] MIMES = new String[] { + ImageWMF.MIME_WMF}; + + /** {@inheritDoc} */ + public String[] getSupportedMIMETypes() { + return MIMES; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedFlavors(String mime) { + return FLAVORS; + } + + /** {@inheritDoc} */ + public ImageLoader newImageLoader(ImageFlavor targetFlavor) { + return new ImageLoaderWMF(targetFlavor); + } + + /** {@inheritDoc} */ + public int getUsagePenalty(String mime, ImageFlavor flavor) { + return 0; + } + + /** {@inheritDoc} */ + public boolean isAvailable() { + return BatikUtil.isBatikAvailable(); + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java b/src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java new file mode 100644 index 000000000..64aff962b --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageLoaderSVG.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import java.io.IOException; +import java.util.Map; + +import org.apache.batik.dom.svg.SVGDOMImplementation; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageLoader; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.util.MimeConstants; + +/** + * ImageLoader for SVG (using Apache Batik). + */ +public class ImageLoaderSVG extends AbstractImageLoader { + + private ImageFlavor targetFlavor; + + /** + * Main constructor. + * @param targetFlavor the target flavor + */ + public ImageLoaderSVG(ImageFlavor targetFlavor) { + if (!(ImageFlavor.XML_DOM.equals(targetFlavor))) { + throw new IllegalArgumentException("Unsupported target ImageFlavor: " + targetFlavor); + } + this.targetFlavor = targetFlavor; + } + + /** {@inheritDoc} */ + public ImageFlavor getTargetFlavor() { + return this.targetFlavor; + } + + /** {@inheritDoc} */ + public Image loadImage(ImageInfo info, Map hints, ImageSessionContext session) + throws ImageException, IOException { + if (!MimeConstants.MIME_SVG.equals(info.getMimeType())) { + throw new IllegalArgumentException("ImageInfo must be from an SVG image"); + } + Image img = info.getOriginalImage(); + if (!(img instanceof ImageXMLDOM)) { + throw new IllegalArgumentException( + "ImageInfo was expected to contain the SVG document as DOM"); + } + ImageXMLDOM svgImage = (ImageXMLDOM)img; + if (!SVGDOMImplementation.SVG_NAMESPACE_URI.equals(svgImage.getRootNamespace())) { + throw new IllegalArgumentException( + "The Image is not in the SVG namespace: " + svgImage.getRootNamespace()); + } + return svgImage; + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageLoaderWMF.java b/src/java/org/apache/fop/image/loader/batik/ImageLoaderWMF.java new file mode 100644 index 000000000..e7f3e0f0a --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageLoaderWMF.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import java.io.IOException; +import java.util.Map; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageLoader; + +/** + * ImageLoader for WMF (Windows Metafile). Depends on the WMF preloader based on Apache Batik. + */ +public class ImageLoaderWMF extends AbstractImageLoader { + + private ImageFlavor targetFlavor; + + /** + * Main constructor. + * @param targetFlavor the target flavor + */ + public ImageLoaderWMF(ImageFlavor targetFlavor) { + if (!(ImageWMF.WMF_IMAGE.equals(targetFlavor))) { + throw new IllegalArgumentException("Unsupported target ImageFlavor: " + targetFlavor); + } + this.targetFlavor = targetFlavor; + } + + /** {@inheritDoc} */ + public ImageFlavor getTargetFlavor() { + return this.targetFlavor; + } + + /** {@inheritDoc} */ + public Image loadImage(ImageInfo info, Map hints, ImageSessionContext session) + throws ImageException, IOException { + if (!ImageWMF.MIME_WMF.equals(info.getMimeType())) { + throw new IllegalArgumentException("ImageInfo must be from a WMF image"); + } + Image img = info.getOriginalImage(); + if (!(img instanceof ImageWMF)) { + throw new IllegalArgumentException( + "ImageInfo was expected to contain the Windows Metafile (WMF)"); + } + ImageWMF wmfImage = (ImageWMF)img; + return wmfImage; + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/ImageWMF.java b/src/java/org/apache/fop/image/loader/batik/ImageWMF.java new file mode 100644 index 000000000..f785d27cc --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/ImageWMF.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import org.apache.batik.transcoder.wmf.tosvg.WMFRecordStore; + +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.impl.AbstractImage; + +/** + * This class is an implementation of the Image interface exposing a RenderedImage. + */ +public class ImageWMF extends AbstractImage { + + /** MIME type for WMF */ + public static final String MIME_WMF = "image/x-wmf"; + + /** ImageFlavor for Batik's WMFRecordStore */ + public static final ImageFlavor WMF_IMAGE = new ImageFlavor("WMFRecordStore"); + + private WMFRecordStore store; + + /** + * Main constructor. + * @param info the image info object + * @param store the WMF record store containing the loaded WMF file + */ + public ImageWMF(ImageInfo info, WMFRecordStore store) { + super(info); + this.store = store; + } + + /** {@inheritDoc} */ + public ImageFlavor getFlavor() { + return WMF_IMAGE; + } + + /** {@inheritDoc} */ + public boolean isCacheable() { + return true; + } + + /** + * Returns the contained WMF record store. + * @return the WMFRecordStore + */ + public WMFRecordStore getRecordStore() { + return this.store; + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java new file mode 100644 index 000000000..e83a302da --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/PreloaderSVG.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; + +import org.w3c.dom.Element; +import org.w3c.dom.svg.SVGDocument; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.UnitProcessor; +import org.apache.batik.dom.svg.SAXSVGDocumentFactory; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.dom.svg.SVGOMDocument; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.AbstractImagePreloader; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; +import org.apache.xmlgraphics.util.MimeConstants; + +import org.apache.fop.svg.SVGUserAgent; +import org.apache.fop.util.UnclosableInputStream; + +/** + * Image preloader for SVG images. + */ +public class PreloaderSVG extends AbstractImagePreloader { + + /** Logger instance */ + private static Log log = LogFactory.getLog(PreloaderSVG.class); + + private boolean batikAvailable = true; + + /** {@inheritDoc} */ + public ImageInfo preloadImage(String uri, Source src, ImageContext context) + throws IOException { + if (!ImageUtil.hasInputStream(src)) { + //TODO Remove this and support DOMSource and possibly SAXSource + return null; + } + ImageInfo info = null; + if (batikAvailable) { + try { + Loader loader = new Loader(); + info = loader.getImage(uri, src, context); + } catch (NoClassDefFoundError e) { + batikAvailable = false; + log.warn("Batik not in class path", e); + return null; + } + } + if (info != null) { + ImageUtil.closeQuietly(src); //Image is fully read + } + return info; + } + + /** + * Returns the fully qualified classname of an XML parser for + * Batik classes that apparently need it (error messages, perhaps) + * @return an XML parser classname + */ + public static String getParserName() { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + return factory.newSAXParser().getXMLReader().getClass().getName(); + } catch (Exception e) { + return null; + } + } + + /** + * This method is put in another class so that the class loader does not + * attempt to load Batik related classes when constructing the SVGPreloader + * class. + */ + class Loader { + private ImageInfo getImage(String uri, Source src, + ImageContext context) { + // parse document and get the size attributes of the svg element + + InputStream in = new UnclosableInputStream(ImageUtil.needInputStream(src)); + try { + int length = in.available(); + in.mark(length + 1); + SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory( + getParserName()); + SVGDocument doc = (SVGDocument) factory.createSVGDocument(src.getSystemId(), in); + + Element e = doc.getRootElement(); + float pxUnitToMillimeter = 25.4f / context.getSourceResolution(); + SVGUserAgent userAg = new SVGUserAgent(pxUnitToMillimeter, + new AffineTransform()); + BridgeContext ctx = new BridgeContext(userAg); + UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, e); + + String s; + // 'width' attribute - default is 100% + s = e.getAttributeNS(null, SVGOMDocument.SVG_WIDTH_ATTRIBUTE); + if (s.length() == 0) { + s = SVGOMDocument.SVG_SVG_WIDTH_DEFAULT_VALUE; + } + float width = UnitProcessor.svgHorizontalLengthToUserSpace( + s, SVGOMDocument.SVG_WIDTH_ATTRIBUTE, uctx); + + // 'height' attribute - default is 100% + s = e.getAttributeNS(null, SVGOMDocument.SVG_HEIGHT_ATTRIBUTE); + if (s.length() == 0) { + s = SVGOMDocument.SVG_SVG_HEIGHT_DEFAULT_VALUE; + } + float height = UnitProcessor.svgVerticalLengthToUserSpace( + s, SVGOMDocument.SVG_HEIGHT_ATTRIBUTE, uctx); + + ImageInfo info = new ImageInfo(uri, MimeConstants.MIME_SVG); + ImageSize size = new ImageSize(); + size.setSizeInMillipoints(Math.round(width * 1000), Math.round(height * 1000)); + //Set the resolution to that of the FOUserAgent + size.setResolution(context.getSourceResolution()); + size.calcPixelsFromSize(); + info.setSize(size); + + //The whole image had to be loaded for this, so keep it + ImageXMLDOM xmlImage = new ImageXMLDOM(info, + doc, SVGDOMImplementation.SVG_NAMESPACE_URI); + info.getCustomObjects().put(ImageInfo.ORIGINAL_IMAGE, xmlImage); + + return info; + } catch (NoClassDefFoundError ncdfe) { + try { + in.reset(); + } catch (IOException ioe) { + // we're more interested in the original exception + } + batikAvailable = false; + log.warn("Batik not in class path", ncdfe); + return null; + } catch (IOException e) { + // If the svg is invalid then it throws an IOException + // so there is no way of knowing if it is an svg document + + log.debug("Error while trying to load stream as an SVG file: " + + e.getMessage()); + // assuming any exception means this document is not svg + // or could not be loaded for some reason + try { + in.reset(); + } catch (IOException ioe) { + // we're more interested in the original exception + } + return null; + } + } + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/PreloaderWMF.java b/src/java/org/apache/fop/image/loader/batik/PreloaderWMF.java new file mode 100644 index 000000000..abb740411 --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/PreloaderWMF.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.image.loader.batik; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.transform.Source; + +import org.apache.batik.transcoder.wmf.WMFConstants; +import org.apache.batik.transcoder.wmf.tosvg.WMFRecordStore; +import org.apache.commons.io.EndianUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.AbstractImagePreloader; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; + +import org.apache.fop.util.UnclosableInputStream; + +/** + * Image preloader for WMF images (Windows Metafile). + */ +public class PreloaderWMF extends AbstractImagePreloader { + + /** Logger instance */ + private static Log log = LogFactory.getLog(PreloaderWMF.class); + + private boolean batikAvailable = true; + + /** {@inheritDoc} */ + public ImageInfo preloadImage(String uri, Source src, ImageContext context) + throws IOException { + if (!ImageUtil.hasInputStream(src)) { + return null; + } + ImageInfo info = null; + if (batikAvailable) { + try { + Loader loader = new Loader(); + info = loader.getImage(uri, src, context); + } catch (NoClassDefFoundError e) { + batikAvailable = false; + log.warn("Batik not in class path", e); + return null; + } + } + if (info != null) { + ImageUtil.closeQuietly(src); //Image is fully read + } + return info; + } + + /** + * This method is put in another class so that the class loader does not + * attempt to load Batik related classes when constructing the WMFPreloader + * class. + */ + class Loader { + private ImageInfo getImage(String uri, Source src, + ImageContext context) { + // parse document and get the size attributes of the svg element + + InputStream in = new UnclosableInputStream(ImageUtil.needInputStream(src)); + try { + in.mark(4 + 1); + + DataInputStream din = new DataInputStream(in); + int magic = EndianUtils.swapInteger(din.readInt()); + din.reset(); + if (magic != WMFConstants.META_ALDUS_APM) { + return null; //Not a WMF file + } + + WMFRecordStore wmfStore = new WMFRecordStore(); + wmfStore.read(din); + IOUtils.closeQuietly(din); + + int width = wmfStore.getWidthUnits(); + int height = wmfStore.getHeightUnits(); + int dpi = wmfStore.getMetaFileUnitsPerInch(); + + ImageInfo info = new ImageInfo(uri, "image/x-wmf"); + ImageSize size = new ImageSize(); + size.setSizeInPixels(width, height); + size.setResolution(dpi); + size.calcSizeFromPixels(); + info.setSize(size); + ImageWMF img = new ImageWMF(info, wmfStore); + info.getCustomObjects().put(ImageInfo.ORIGINAL_IMAGE, img); + + return info; + } catch (NoClassDefFoundError ncdfe) { + try { + in.reset(); + } catch (IOException ioe) { + // we're more interested in the original exception + } + batikAvailable = false; + log.warn("Batik not in class path", ncdfe); + return null; + } catch (IOException e) { + // If the svg is invalid then it throws an IOException + // so there is no way of knowing if it is an svg document + + log.debug("Error while trying to load stream as an WMF file: " + + e.getMessage()); + // assuming any exception means this document is not svg + // or could not be loaded for some reason + try { + in.reset(); + } catch (IOException ioe) { + // we're more interested in the original exception + } + return null; + } + } + } + +} diff --git a/src/java/org/apache/fop/image/loader/batik/package.html b/src/java/org/apache/fop/image/loader/batik/package.html new file mode 100644 index 000000000..f664913d7 --- /dev/null +++ b/src/java/org/apache/fop/image/loader/batik/package.html @@ -0,0 +1,26 @@ +<!-- + 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$ --> +<HTML> +<TITLE>org.apache.fop.image2.impl.batik Package</TITLE> +<BODY> +<P> + Contains implementations of image loaders and converters which are dependent + on Apache Batik (SVG and WMF). +</P> +</BODY> +</HTML>
\ No newline at end of file diff --git a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java index 5668f81ef..0e51517f2 100644 --- a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java @@ -21,10 +21,18 @@ package org.apache.fop.layoutmgr; import java.awt.Dimension; import java.awt.Rectangle; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; + import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.AreaTreeHandler; import org.apache.fop.area.Block; @@ -38,8 +46,6 @@ import org.apache.fop.area.inline.Viewport; import org.apache.fop.datatypes.FODimension; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExternalDocument; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; import org.apache.fop.layoutmgr.inline.ImageLayout; /** @@ -51,14 +57,13 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan private static Log log = LogFactory.getLog(ExternalDocumentLayoutManager.class); - private FopImage image; private ImageLayout imageLayout; /** * Constructor * * @param ath the area tree handler object - * @param pseq fo:page-sequence to process + * @param document fox:external-document to process */ public ExternalDocumentLayoutManager(AreaTreeHandler ath, ExternalDocument document) { super(ath, document); @@ -80,38 +85,78 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan public void activateLayout() { initialize(); - String uri = getExternalDocument().getSrc(); FOUserAgent userAgent = pageSeq.getUserAgent(); - ImageFactory fact = userAgent.getFactory().getImageFactory(); - this.image = fact.getImage(uri, userAgent); - if (this.image == null) { - log.error("Image not available: " + uri); - return; - } else { - // load dimensions - if (!this.image.load(FopImage.DIMENSIONS)) { - log.error("Cannot read image dimensions: " + uri); - return; - } - } - Dimension intrinsicSize = new Dimension( - image.getIntrinsicWidth(), - image.getIntrinsicHeight()); - this.imageLayout = new ImageLayout(getExternalDocument(), this, intrinsicSize); + ImageManager imageManager = userAgent.getFactory().getImageManager(); - areaTreeHandler.getAreaTreeModel().startPageSequence(null); - if (log.isDebugEnabled()) { - log.debug("Starting layout"); + String uri = getExternalDocument().getSrc(); + Integer firstPageIndex = ImageUtil.getPageIndexFromURI(uri); + boolean hasPageIndex = (firstPageIndex != null); + + try { + ImageInfo info = imageManager.getImageInfo(uri, userAgent.getImageSessionContext()); + + Object moreImages = info.getCustomObjects().get(ImageInfo.HAS_MORE_IMAGES); + boolean hasMoreImages = moreImages != null && !Boolean.FALSE.equals(moreImages); + + Dimension intrinsicSize = info.getSize().getDimensionMpt(); + ImageLayout layout = new ImageLayout(getExternalDocument(), this, intrinsicSize); + + areaTreeHandler.getAreaTreeModel().startPageSequence(null); + if (log.isDebugEnabled()) { + log.debug("Starting layout"); + } + + makePageForImage(info, layout); + + if (!hasPageIndex && hasMoreImages) { + if (log.isTraceEnabled()) { + log.trace("Starting multi-page processing..."); + } + URI originalURI; + try { + originalURI = new URI(uri); + int pageIndex = 1; + while (hasMoreImages) { + URI tempURI = new URI(originalURI.getScheme(), + originalURI.getSchemeSpecificPart(), + "page=" + Integer.toString(pageIndex + 1)); + if (log.isTraceEnabled()) { + log.trace("Subimage: " + tempURI.toASCIIString()); + } + ImageInfo subinfo = imageManager.getImageInfo( + tempURI.toASCIIString(), userAgent.getImageSessionContext()); + + moreImages = subinfo.getCustomObjects().get(ImageInfo.HAS_MORE_IMAGES); + hasMoreImages = moreImages != null && !Boolean.FALSE.equals(moreImages); + + intrinsicSize = subinfo.getSize().getDimensionMpt(); + layout = new ImageLayout( + getExternalDocument(), this, intrinsicSize); + + makePageForImage(subinfo, layout); + + pageIndex++; + } + } catch (URISyntaxException e) { + log.error("Error parsing or constructing URIs based on URI: " + uri); + return; + } + } + } catch (IOException ioe) { + log.error("Image not available: " + uri, ioe); + } catch (ImageException ie) { + log.error("Error while inspecting image: " + uri + " (" + ie.getMessage() + ")"); } + } + private void makePageForImage(ImageInfo info, ImageLayout layout) { + this.imageLayout = layout; curPage = makeNewPage(false, false); - - fillPage(); //TODO Implement multi-page documents (using new image package) - + fillPage(info.getOriginalURI()); finishPage(); } - - private void fillPage() { + + private void fillPage(String uri) { Dimension imageSize = this.imageLayout.getViewportSize(); @@ -119,7 +164,7 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan blockArea.setIPD(imageSize.width); LineArea lineArea = new LineArea(); - Image imageArea = new Image(getExternalDocument().getSrc()); + Image imageArea = new Image(uri); TraitSetter.setProducerID(imageArea, fobj.getId()); transferForeignAttributes(imageArea); diff --git a/src/java/org/apache/fop/layoutmgr/TraitSetter.java b/src/java/org/apache/fop/layoutmgr/TraitSetter.java index b50167b16..f1e5ffdc6 100644 --- a/src/java/org/apache/fop/layoutmgr/TraitSetter.java +++ b/src/java/org/apache/fop/layoutmgr/TraitSetter.java @@ -21,20 +21,19 @@ package org.apache.fop.layoutmgr; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.fop.traits.BorderProps; -import org.apache.fop.traits.MinOptMax; + import org.apache.fop.area.Area; import org.apache.fop.area.Trait; import org.apache.fop.datatypes.LengthBase; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.datatypes.SimplePercentBaseContext; import org.apache.fop.fo.Constants; -import org.apache.fop.fo.properties.CommonMarginBlock; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; +import org.apache.fop.fo.properties.CommonMarginBlock; import org.apache.fop.fo.properties.CommonTextDecoration; import org.apache.fop.fonts.Font; -import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontTriplet; +import org.apache.fop.traits.BorderProps; +import org.apache.fop.traits.MinOptMax; /** * This is a helper class used for setting common traits on areas. @@ -303,9 +302,9 @@ public class TraitSetter { Trait.Background back = new Trait.Background(); back.setColor(backProps.backgroundColor); - if (backProps.getFopImage() != null) { + if (backProps.getImageInfo() != null) { back.setURL(backProps.backgroundImage); - back.setFopImage(backProps.getFopImage()); + back.setImageInfo(backProps.getImageInfo()); back.setRepeat(backProps.backgroundRepeat); if (backProps.backgroundPositionHorizontal != null) { if (back.getRepeat() == Constants.EN_NOREPEAT @@ -317,7 +316,7 @@ public class TraitSetter { back.setHoriz(backProps.backgroundPositionHorizontal.getValue( new SimplePercentBaseContext(context, LengthBase.IMAGE_BACKGROUND_POSITION_HORIZONTAL, - (width - back.getFopImage().getIntrinsicWidth()) + (width - back.getImageInfo().getSize().getHeightMpt()) ) )); } else { @@ -338,7 +337,7 @@ public class TraitSetter { back.setVertical(backProps.backgroundPositionVertical.getValue( new SimplePercentBaseContext(context, LengthBase.IMAGE_BACKGROUND_POSITION_VERTICAL, - (height - back.getFopImage().getIntrinsicHeight()) + (height - back.getImageInfo().getSize().getHeightMpt()) ) )); } else { diff --git a/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java b/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java index b3ccea628..60247af39 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java +++ b/src/java/org/apache/fop/layoutmgr/inline/ImageLayout.java @@ -29,7 +29,11 @@ import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.PercentBaseContext; import org.apache.fop.fo.Constants; import org.apache.fop.fo.GraphicsProperties; +import org.apache.fop.fo.properties.LengthRangeProperty; +/** + * Helper class which calculates the size and position in the viewport of an image. + */ public class ImageLayout implements Constants { /** logging instance */ @@ -45,6 +49,12 @@ public class ImageLayout implements Constants { private Dimension viewportSize = new Dimension(-1, -1); private boolean clip; + /** + * Main constructor + * @param props the properties for the image + * @param percentBaseContext the context object for percentage calculations + * @param intrinsicSize the image's intrinsic size + */ public ImageLayout(GraphicsProperties props, PercentBaseContext percentBaseContext, Dimension intrinsicSize) { this.props = props; @@ -54,6 +64,9 @@ public class ImageLayout implements Constants { doLayout(); } + /** + * Does the actual calculations for the image. + */ protected void doLayout() { Length len; @@ -63,25 +76,26 @@ public class ImageLayout implements Constants { len = props.getBlockProgressionDimension().getOptimum(percentBaseContext).getLength(); if (len.getEnum() != EN_AUTO) { bpd = len.getValue(percentBaseContext); - } else { - len = props.getHeight(); - if (len.getEnum() != EN_AUTO) { - bpd = len.getValue(percentBaseContext); - } + } + len = props.getBlockProgressionDimension().getMinimum(percentBaseContext).getLength(); + if (bpd == -1 && len.getEnum() != EN_AUTO) { + //Establish minimum viewport size + bpd = len.getValue(percentBaseContext); } len = props.getInlineProgressionDimension().getOptimum(percentBaseContext).getLength(); if (len.getEnum() != EN_AUTO) { ipd = len.getValue(percentBaseContext); - } else { - len = props.getWidth(); - if (len.getEnum() != EN_AUTO) { - ipd = len.getValue(percentBaseContext); - } + } + len = props.getInlineProgressionDimension().getMinimum(percentBaseContext).getLength(); + if (ipd == -1 && len.getEnum() != EN_AUTO) { + //Establish minimum viewport size + ipd = len.getValue(percentBaseContext); } // if auto then use the intrinsic size of the content scaled // to the content-height and content-width + boolean constrainIntrinsicSize = false; int cwidth = -1; int cheight = -1; len = props.getContentWidth(); @@ -91,16 +105,19 @@ public class ImageLayout implements Constants { if (ipd != -1) { cwidth = ipd; } + constrainIntrinsicSize = true; break; case EN_SCALE_DOWN_TO_FIT: if (ipd != -1 && intrinsicSize.width > ipd) { cwidth = ipd; } + constrainIntrinsicSize = true; break; case EN_SCALE_UP_TO_FIT: if (ipd != -1 && intrinsicSize.width < ipd) { cwidth = ipd; } + constrainIntrinsicSize = true; break; default: cwidth = len.getValue(percentBaseContext); @@ -113,64 +130,43 @@ public class ImageLayout implements Constants { if (bpd != -1) { cheight = bpd; } + constrainIntrinsicSize = true; break; case EN_SCALE_DOWN_TO_FIT: if (bpd != -1 && intrinsicSize.height > bpd) { cheight = bpd; } + constrainIntrinsicSize = true; break; case EN_SCALE_UP_TO_FIT: if (bpd != -1 && intrinsicSize.height < bpd) { cheight = bpd; } + constrainIntrinsicSize = true; break; default: cheight = len.getValue(percentBaseContext); } } - int scaling = props.getScaling(); - if ((scaling == EN_UNIFORM) || (cwidth == -1) || cheight == -1) { - if (cwidth == -1 && cheight == -1) { - cwidth = intrinsicSize.width; - cheight = intrinsicSize.height; - } else if (cwidth == -1) { - if (intrinsicSize.height == 0) { - cwidth = 0; - } else { - cwidth = (int)(intrinsicSize.width * (double)cheight - / intrinsicSize.height); - } - } else if (cheight == -1) { - if (intrinsicSize.width == 0) { - cheight = 0; - } else { - cheight = (int)(intrinsicSize.height * (double)cwidth - / intrinsicSize.width); - } - } else { - // adjust the larger - if (intrinsicSize.width == 0 || intrinsicSize.height == 0) { - cwidth = 0; - cheight = 0; - } else { - double rat1 = (double) cwidth / intrinsicSize.width; - double rat2 = (double) cheight / intrinsicSize.height; - if (rat1 < rat2) { - // reduce cheight - cheight = (int)(rat1 * intrinsicSize.height); - } else if (rat1 > rat2) { - cwidth = (int)(rat2 * intrinsicSize.width); - } - } - } + Dimension constrainedIntrinsicSize; + if (constrainIntrinsicSize) { + constrainedIntrinsicSize = constrain(intrinsicSize); + } else { + constrainedIntrinsicSize = intrinsicSize; } + + //Derive content extents where not explicit + Dimension adjustedDim = adjustContentSize(cwidth, cheight, constrainedIntrinsicSize); + cwidth = adjustedDim.width; + cheight = adjustedDim.height; + //Adjust viewport if not explicit if (ipd == -1) { - ipd = cwidth; + ipd = constrainExtent(cwidth, props.getInlineProgressionDimension()); } if (bpd == -1) { - bpd = cheight; + bpd = constrainExtent(cheight, props.getBlockProgressionDimension()); } this.clip = false; @@ -193,6 +189,90 @@ public class ImageLayout implements Constants { this.placement = new Rectangle(xoffset, yoffset, cwidth, cheight); } + private int constrainExtent(int extent, LengthRangeProperty range) { + Length len; + len = range.getMaximum(percentBaseContext).getLength(); + if (len.getEnum() != EN_AUTO) { + int max = len.getValue(percentBaseContext); + if (max != -1) { + extent = Math.min(extent, max); + } + } + len = range.getMinimum(percentBaseContext).getLength(); + if (len.getEnum() != EN_AUTO) { + int min = len.getValue(percentBaseContext); + if (min != -1) { + extent = Math.max(extent, min); + } + } + return extent; + } + + private Dimension constrain(Dimension size) { + Dimension adjusted = new Dimension(size); + int effWidth = constrainExtent(size.width, props.getInlineProgressionDimension()); + int effHeight = constrainExtent(size.height, props.getBlockProgressionDimension()); + int scaling = props.getScaling(); + if (scaling == EN_UNIFORM) { + double rat1 = (double)effWidth / size.width; + double rat2 = (double)effHeight / size.height; + if (rat1 < rat2) { + adjusted.width = effWidth; + adjusted.height = (int)(rat1 * size.height); + } else if (rat1 > rat2) { + adjusted.width = (int)(rat2 * size.width); + adjusted.height = effHeight; + } + } else { + adjusted.width = effWidth; + adjusted.height = effHeight; + } + return adjusted; + } + + private Dimension adjustContentSize( + final int cwidth, final int cheight, + Dimension defaultSize) { + Dimension dim = new Dimension(cwidth, cheight); + int scaling = props.getScaling(); + if ((scaling == EN_UNIFORM) || (cwidth == -1) || cheight == -1) { + if (cwidth == -1 && cheight == -1) { + dim.width = defaultSize.width; + dim.height = defaultSize.height; + } else if (cwidth == -1) { + if (defaultSize.height == 0) { + dim.width = 0; + } else { + dim.width = (int)(defaultSize.width * (double)cheight + / defaultSize.height); + } + } else if (cheight == -1) { + if (defaultSize.width == 0) { + dim.height = 0; + } else { + dim.height = (int)(defaultSize.height * (double)cwidth + / defaultSize.width); + } + } else { + // adjust the larger + if (defaultSize.width == 0 || defaultSize.height == 0) { + dim.width = 0; + dim.height = 0; + } else { + double rat1 = (double)cwidth / defaultSize.width; + double rat2 = (double)cheight / defaultSize.height; + if (rat1 < rat2) { + // reduce height + dim.height = (int)(rat1 * defaultSize.height); + } else if (rat1 > rat2) { + dim.width = (int)(rat2 * defaultSize.width); + } + } + } + } + return dim; + } + /** * Given the ipd and the content width calculates the * required x offset based on the text-align property @@ -243,18 +323,34 @@ public class ImageLayout implements Constants { return yoffset; } + /** + * Returns the placement of the image inside the viewport. + * @return the placement of the image inside the viewport (coordinates in millipoints) + */ public Rectangle getPlacement() { return this.placement; } + /** + * Returns the size of the image's viewport. + * @return the viewport size (in millipoints) + */ public Dimension getViewportSize() { return this.viewportSize; } + /** + * Returns the size of the image's intrinsic (natural) size. + * @return the intrinsic size (in millipoints) + */ public Dimension getIntrinsicSize() { return this.intrinsicSize; } + /** + * Indicates whether the image is clipped. + * @return true if the image shall be clipped + */ public boolean isClipped() { return this.clip; } diff --git a/src/java/org/apache/fop/pdf/AlphaRasterImage.java b/src/java/org/apache/fop/pdf/AlphaRasterImage.java new file mode 100644 index 000000000..be476bdb2 --- /dev/null +++ b/src/java/org/apache/fop/pdf/AlphaRasterImage.java @@ -0,0 +1,181 @@ +/* + * 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.pdf; + +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.xmlgraphics.image.GraphicsUtil; + +/** + * PDFImage implementation for alpha channel "images". + */ +public class AlphaRasterImage implements PDFImage { + + private int bitsPerComponent; + private PDFDeviceColorSpace colorSpace; + private Raster alpha; + private String key; + + /** + * Create a alpha channel image. + * Creates a new bitmap image with the given data. + * + * @param k the key to be used to lookup the image + * @param alpha the alpha channel raster + */ + public AlphaRasterImage(String k, Raster alpha) { + this.key = k; + this.bitsPerComponent = 8; + this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY); + if (alpha == null) { + throw new NullPointerException("Parameter alpha must not be null"); + } + this.alpha = alpha; + } + + /** + * Create a alpha channel image. + * Extracts the alpha channel from the RenderedImage and creates a new bitmap image + * with the given data. + * + * @param k the key to be used to lookup the image + * @param image the image (must have an alpha channel) + */ + public AlphaRasterImage(String k, RenderedImage image) { + this(k, GraphicsUtil.getAlphaRaster(image)); + } + + /** {@inheritDoc} */ + public void setup(PDFDocument doc) { + //nop + } + + /** {@inheritDoc} */ + public String getKey() { + return key; + } + + /** {@inheritDoc} */ + public int getWidth() { + return alpha.getWidth(); + } + + /** {@inheritDoc} */ + public int getHeight() { + return alpha.getHeight(); + } + + /** {@inheritDoc} */ + public PDFDeviceColorSpace getColorSpace() { + return colorSpace; + } + + /** {@inheritDoc} */ + public int getBitsPerComponent() { + return bitsPerComponent; + } + + /** {@inheritDoc} */ + public boolean isTransparent() { + return false; + } + + /** {@inheritDoc} */ + public PDFColor getTransparentColor() { + return null; + } + + /** {@inheritDoc} */ + public String getMask() { + return null; + } + + /** {@inheritDoc} */ + public String getSoftMask() { + return null; + } + + /** {@inheritDoc} */ + public PDFReference getSoftMaskReference() { + return null; + } + + /** {@inheritDoc} */ + public boolean isInverted() { + return false; + } + + /** {@inheritDoc} */ + public void outputContents(OutputStream out) throws IOException { + int w = getWidth(); + int h = getHeight(); + + //Check Raster + int nbands = alpha.getNumBands(); + if (nbands != 1) { + throw new UnsupportedOperationException( + "Expected only one band/component for the alpha channel"); + } + int dataType = alpha.getDataBuffer().getDataType(); + if (dataType != DataBuffer.TYPE_BYTE) { + throw new UnsupportedOperationException("Unsupported DataBuffer type: " + + alpha.getDataBuffer().getClass().getName()); + } + + //...and write the Raster line by line with a reusable buffer + byte[] line = new byte[nbands * w]; + for (int y = 0; y < h; y++) { + alpha.getDataElements(0, y, w, 1, line); + out.write(line); + } + } + + /** {@inheritDoc} */ + public void populateXObjectDictionary(PDFDictionary dict) { + //nop + } + + /** {@inheritDoc} */ + public PDFICCStream getICCStream() { + return null; + } + + /** {@inheritDoc} */ + public boolean isPS() { + return false; + } + + /** {@inheritDoc} */ + public String getFilterHint() { + return PDFFilterList.IMAGE_FILTER; + } + + /** {@inheritDoc} */ + public PDFFilter getPDFFilter() { + return null; + } + +} + + diff --git a/src/java/org/apache/fop/pdf/BitmapImage.java b/src/java/org/apache/fop/pdf/BitmapImage.java index 34c78ffe3..69b51dac3 100644 --- a/src/java/org/apache/fop/pdf/BitmapImage.java +++ b/src/java/org/apache/fop/pdf/BitmapImage.java @@ -30,7 +30,7 @@ import java.io.OutputStream; public class BitmapImage implements PDFImage { private int height; private int width; - private int bitsPerPixel; + private int bitsPerComponent; private PDFDeviceColorSpace colorSpace; private byte[] bitmaps; private PDFReference maskRef; @@ -53,10 +53,12 @@ public class BitmapImage implements PDFImage { this.key = k; this.height = height; this.width = width; - this.bitsPerPixel = 8; + this.bitsPerComponent = 8; this.colorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); this.bitmaps = data; - maskRef = new PDFReference(mask); + if (mask != null) { + maskRef = new PDFReference(mask); + } } /** @@ -117,13 +119,9 @@ public class BitmapImage implements PDFImage { return colorSpace; } - /** - * Get the number of bits per pixel. - * - * @return the number of bits per pixel - */ - public int getBitsPerPixel() { - return bitsPerPixel; + /** {@inheritDoc} */ + public int getBitsPerComponent() { + return bitsPerComponent; } /** @@ -173,13 +171,16 @@ public class BitmapImage implements PDFImage { return false; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void outputContents(OutputStream out) throws IOException { out.write(bitmaps); } + /** {@inheritDoc} */ + public void populateXObjectDictionary(PDFDictionary dict) { + //nop + } + /** * Get the ICC stream. * @return always returns null since this has no icc color space diff --git a/src/java/org/apache/fop/pdf/FlateFilter.java b/src/java/org/apache/fop/pdf/FlateFilter.java index d7dc81d1f..a652c4534 100644 --- a/src/java/org/apache/fop/pdf/FlateFilter.java +++ b/src/java/org/apache/fop/pdf/FlateFilter.java @@ -19,8 +19,8 @@ package org.apache.fop.pdf; -import java.io.OutputStream; import java.io.IOException; +import java.io.OutputStream; import org.apache.xmlgraphics.util.io.FlateEncodeOutputStream; @@ -98,13 +98,13 @@ public class FlateFilter extends PDFFilter { if (predictor > PREDICTION_NONE) { PDFDictionary dict = new PDFDictionary(); dict.put("Predictor", predictor); - if (colors > 0) { + if (colors > 1) { dict.put("Colors", colors); } - if (bitsPerComponent > 0) { + if (bitsPerComponent > 0 && bitsPerComponent != 8) { dict.put("BitsPerComponent", bitsPerComponent); } - if (columns > 0) { + if (columns > 1) { dict.put("Columns", columns); } return dict; diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index f3069e85e..d0c48ee39 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -1490,9 +1490,6 @@ public class PDFFactory { */ public PDFICCStream makePDFICCStream() { PDFICCStream iccStream = new PDFICCStream(); - iccStream.getFilterList().addDefaultFilters( - getDocument().getFilterMap(), - PDFFilterList.CONTENT_FILTER); getDocument().registerObject(iccStream); //getDocument().applyEncryption(iccStream); diff --git a/src/java/org/apache/fop/pdf/PDFFilterList.java b/src/java/org/apache/fop/pdf/PDFFilterList.java index 607ae174b..84131707b 100644 --- a/src/java/org/apache/fop/pdf/PDFFilterList.java +++ b/src/java/org/apache/fop/pdf/PDFFilterList.java @@ -34,6 +34,8 @@ public class PDFFilterList { public static final String DEFAULT_FILTER = "default"; /** Key for the filter used for normal content*/ public static final String CONTENT_FILTER = "content"; + /** Key for the filter used for precompressed content */ + public static final String PRECOMPRESSED_FILTER = "precompressed"; /** Key for the filter used for images */ public static final String IMAGE_FILTER = "image"; /** Key for the filter used for JPEG images */ @@ -178,6 +180,9 @@ public class PDFFilterList { } else if (TIFF_FILTER.equals(type)) { //CCITT-encoded images are already well compressed addFilter(new NullFilter()); + } else if (PRECOMPRESSED_FILTER.equals(type)) { + //precompressed content doesn't need further compression + addFilter(new NullFilter()); } else { // built-in default to flate addFilter(new FlateFilter()); diff --git a/src/java/org/apache/fop/pdf/PDFICCStream.java b/src/java/org/apache/fop/pdf/PDFICCStream.java index da1e8353c..f242c0cae 100644 --- a/src/java/org/apache/fop/pdf/PDFICCStream.java +++ b/src/java/org/apache/fop/pdf/PDFICCStream.java @@ -66,9 +66,7 @@ public class PDFICCStream extends PDFStream { return length; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected void outputRawStreamData(OutputStream out) throws IOException { cp.write(out); } diff --git a/src/java/org/apache/fop/pdf/PDFImage.java b/src/java/org/apache/fop/pdf/PDFImage.java index f880fa6ac..e0aaa44fa 100644 --- a/src/java/org/apache/fop/pdf/PDFImage.java +++ b/src/java/org/apache/fop/pdf/PDFImage.java @@ -69,11 +69,11 @@ public interface PDFImage { PDFDeviceColorSpace getColorSpace(); /** - * Get the bits per pixel for this image. + * Get the bits per color component for this image. * - * @return the bits per pixel + * @return the bits per component */ - int getBitsPerPixel(); + int getBitsPerComponent(); /** * Check if this image is a PostScript image. @@ -130,6 +130,14 @@ public interface PDFImage { void outputContents(OutputStream out) throws IOException; /** + * Populates the XObject's dictionary with additional values. The values are added to the + * dictionary after all the values obtained from other methods from this interface have + * been put into the dictionary. That allows to override certain values. + * @param dict the dictionary to fill + */ + void populateXObjectDictionary(PDFDictionary dict); + + /** * Get the ICC stream for this image. * * @return the ICC stream for this image if any diff --git a/src/java/org/apache/fop/pdf/PDFImageXObject.java b/src/java/org/apache/fop/pdf/PDFImageXObject.java index 35f3e543d..14fbeed4d 100644 --- a/src/java/org/apache/fop/pdf/PDFImageXObject.java +++ b/src/java/org/apache/fop/pdf/PDFImageXObject.java @@ -90,7 +90,7 @@ public class PDFImageXObject extends PDFXObject { put("Subtype", new PDFName("Image")); put("Width", new Integer(pdfimage.getWidth())); put("Height", new Integer(pdfimage.getHeight())); - put("BitsPerComponent", new Integer(pdfimage.getBitsPerPixel())); + put("BitsPerComponent", new Integer(pdfimage.getBitsPerComponent())); PDFICCStream pdfICCStream = pdfimage.getICCStream(); if (pdfICCStream != null) { @@ -119,18 +119,25 @@ public class PDFImageXObject extends PDFXObject { if (pdfimage.isTransparent()) { PDFColor transp = pdfimage.getTransparentColor(); PDFArray mask = new PDFArray(this); - mask.add(new Integer(transp.red255())); - mask.add(new Integer(transp.red255())); - mask.add(new Integer(transp.green255())); - mask.add(new Integer(transp.green255())); - mask.add(new Integer(transp.blue255())); - mask.add(new Integer(transp.blue255())); + if (pdfimage.getColorSpace().isGrayColorSpace()) { + mask.add(new Integer(transp.red255())); + mask.add(new Integer(transp.red255())); + } else { + mask.add(new Integer(transp.red255())); + mask.add(new Integer(transp.red255())); + mask.add(new Integer(transp.green255())); + mask.add(new Integer(transp.green255())); + mask.add(new Integer(transp.blue255())); + mask.add(new Integer(transp.blue255())); + } put("Mask", mask); } PDFReference ref = pdfimage.getSoftMaskReference(); if (ref != null) { put("SMask", ref); } + //Important: do this at the end so previous values can be overwritten. + pdfimage.populateXObjectDictionary(this); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java index f53183574..4bfd805f4 100644 --- a/src/java/org/apache/fop/pdf/PDFObject.java +++ b/src/java/org/apache/fop/pdf/PDFObject.java @@ -276,14 +276,23 @@ public abstract class PDFObject implements PDFWritable { */ protected byte[] encodeString(String string) { return encodeText(string); - /* - final byte[] buf = encode(PDFText.escapeString(string)); + } + + /** + * Encodes binary data as hexadecimal string object. + * @param data the binary data + * @param out the OutputStream to write the encoded object to + * @throws IOException if an I/O error occurs + */ + protected void encodeBinaryToHexString(byte[] data, OutputStream out) throws IOException { + out.write('<'); if (getDocumentSafely().isEncryptionActive()) { - return PDFText.escapeByteArray( - getDocument().getEncryption().encrypt(buf, this)); - } else { - return buf; - }*/ + data = getDocument().getEncryption().encrypt(data, this); + } + String hex = PDFText.toHex(data, false); + byte[] encoded = hex.getBytes("US-ASCII"); + out.write(encoded); + out.write('>'); } /** @@ -307,6 +316,9 @@ public abstract class PDFObject implements PDFWritable { } } else if (obj instanceof Boolean) { writer.write(obj.toString()); + } else if (obj instanceof byte[]) { + writer.flush(); + encodeBinaryToHexString((byte[])obj, out); } else { writer.flush(); out.write(encodeText(obj.toString())); diff --git a/src/java/org/apache/fop/pdf/PDFReference.java b/src/java/org/apache/fop/pdf/PDFReference.java index 3b615735a..da388d368 100644 --- a/src/java/org/apache/fop/pdf/PDFReference.java +++ b/src/java/org/apache/fop/pdf/PDFReference.java @@ -51,6 +51,9 @@ public class PDFReference implements PDFWritable { * @param ref an object reference */ public PDFReference(String ref) { + if (ref == null) { + throw new NullPointerException("ref must not be null"); + } this.indirectReference = ref; } diff --git a/src/java/org/apache/fop/pdf/PDFText.java b/src/java/org/apache/fop/pdf/PDFText.java index 4c17148c1..d380ac8dd 100644 --- a/src/java/org/apache/fop/pdf/PDFText.java +++ b/src/java/org/apache/fop/pdf/PDFText.java @@ -25,12 +25,12 @@ import org.apache.avalon.framework.CascadingRuntimeException; /** * This class represents a simple number object. It also contains contains some - * utility methods for outputing numbers to PDF. + * utility methods for outputting numbers to PDF. */ public class PDFText extends PDFObject { - private static final char[] DIGITS = - {'0', '1', '2', '3', '4', '5', '6', '7', + private static final char[] DIGITS + = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; private String text; @@ -145,20 +145,34 @@ public class PDFText extends PDFObject { /** * Converts a byte array to a Hexadecimal String (3.2.3 in PDF 1.4 specs) * @param data the data to encode + * @param brackets true if enclosing brackets should be included * @return String the resulting string */ - public static final String toHex(byte[] data) { + public static final String toHex(byte[] data, boolean brackets) { final StringBuffer sb = new StringBuffer(data.length * 2); - sb.append("<"); + if (brackets) { + sb.append("<"); + } for (int i = 0; i < data.length; i++) { sb.append(DIGITS[(data[i] >>> 4) & 0x0F]); sb.append(DIGITS[data[i] & 0x0F]); } - sb.append(">"); + if (brackets) { + sb.append(">"); + } return sb.toString(); } /** + * Converts a byte array to a Hexadecimal String (3.2.3 in PDF 1.4 specs) + * @param data the data to encode + * @return String the resulting string + */ + public static final String toHex(byte[] data) { + return toHex(data, true); + } + + /** * Converts a String to UTF-16 (big endian). * @param text text to convert * @return byte[] UTF-16 stream diff --git a/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java b/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java index 7d21d78c0..f74699fd5 100644 --- a/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java +++ b/src/java/org/apache/fop/render/AbstractGenericSVGHandler.java @@ -26,28 +26,18 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.io.IOException; -// DOM import org.w3c.dom.Document; -// Batik -import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.dom.svg.SVGDOMImplementation; import org.apache.batik.gvt.GraphicsNode; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; -// FOP -import org.apache.fop.render.Graphics2DAdapter; -import org.apache.fop.render.Graphics2DImagePainter; -import org.apache.fop.render.RendererContextConstants; -import org.apache.fop.render.XMLHandler; -import org.apache.fop.render.RendererContext; import org.apache.fop.render.RendererContext.RendererContextWrapper; import org.apache.fop.svg.SVGUserAgent; -// Commons-Logging -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Generic XML handler for SVG. Uses Apache Batik for SVG processing and simply paints to * a Graphics2DAdapter and thus ultimatively to Graphics2D interface that is presented. @@ -76,6 +66,7 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC */ protected void renderSVGDocument(final RendererContext context, final Document doc) throws IOException { + updateRendererContext(context); final RendererContextWrapper wrappedContext = RendererContext.wrapRendererContext(context); int x = wrappedContext.getCurrentXPosition(); int y = wrappedContext.getCurrentYPosition(); @@ -123,6 +114,15 @@ public abstract class AbstractGenericSVGHandler implements XMLHandler, RendererC x, y, wrappedContext.getWidth(), wrappedContext.getHeight()); } + /** + * Override this method to update the renderer context if it needs special settings for + * certain conditions. + * @param context the renderer context + */ + protected void updateRendererContext(RendererContext context) { + //nop + } + /** {@inheritDoc} */ public String getNamespace() { return SVGDOMImplementation.SVG_NAMESPACE_URI; diff --git a/src/java/org/apache/fop/render/AbstractGraphics2DAdapter.java b/src/java/org/apache/fop/render/AbstractGraphics2DAdapter.java index b51b71d61..bc7bb95a1 100644 --- a/src/java/org/apache/fop/render/AbstractGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/AbstractGraphics2DAdapter.java @@ -33,8 +33,6 @@ import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.WritableRaster; -import org.apache.fop.render.Graphics2DAdapter; -import org.apache.fop.render.Graphics2DImagePainter; import org.apache.fop.render.RendererContext.RendererContextWrapper; import org.apache.fop.util.UnitConv; @@ -49,9 +47,11 @@ public abstract class AbstractGraphics2DAdapter implements Graphics2DAdapter { * @param context the renderer context for the current renderer * @param resolution the requested bitmap resolution * @param gray true if the generated image should be in grayscales + * @param withAlpha true if an alpha channel should be created * @return the generated BufferedImage */ - protected BufferedImage paintToBufferedImage(Graphics2DImagePainter painter, + protected BufferedImage paintToBufferedImage( + org.apache.xmlgraphics.java2d.Graphics2DImagePainter painter, RendererContextWrapper context, int resolution, boolean gray, boolean withAlpha) { int bmw = (int)Math.ceil(UnitConv.mpt2px(context.getWidth(), resolution)); int bmh = (int)Math.ceil(UnitConv.mpt2px(context.getHeight(), resolution)); diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index 4e5edece6..ac11d56d5 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -25,6 +25,10 @@ import java.awt.geom.Rectangle2D; import java.util.List; import java.util.Map; +import org.w3c.dom.Document; + +import org.apache.xmlgraphics.image.loader.ImageSize; + import org.apache.fop.area.Area; import org.apache.fop.area.Block; import org.apache.fop.area.BlockViewport; @@ -36,9 +40,7 @@ import org.apache.fop.area.inline.InlineArea; import org.apache.fop.area.inline.Viewport; import org.apache.fop.fo.Constants; import org.apache.fop.fonts.FontMetrics; -import org.apache.fop.image.FopImage; import org.apache.fop.traits.BorderProps; -import org.w3c.dom.Document; /** * Abstract base class for renderers like PDF and PostScript where many painting operations @@ -151,51 +153,47 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { updateColor(back.getColor(), true); fillRect(sx, sy, paddRectWidth, paddRectHeight); } - if (back.getFopImage() != null) { - FopImage fopimage = back.getFopImage(); - if (fopimage != null && fopimage.load(FopImage.DIMENSIONS)) { - saveGraphicsState(); - clipRect(sx, sy, paddRectWidth, paddRectHeight); - int horzCount = (int)((paddRectWidth - * 1000 / fopimage.getIntrinsicWidth()) + 1.0f); - int vertCount = (int)((paddRectHeight - * 1000 / fopimage.getIntrinsicHeight()) + 1.0f); - if (back.getRepeat() == EN_NOREPEAT) { - horzCount = 1; - vertCount = 1; - } else if (back.getRepeat() == EN_REPEATX) { - vertCount = 1; - } else if (back.getRepeat() == EN_REPEATY) { - horzCount = 1; - } - //change from points to millipoints - sx *= 1000; - sy *= 1000; - if (horzCount == 1) { - sx += back.getHoriz(); - } - if (vertCount == 1) { - sy += back.getVertical(); - } - for (int x = 0; x < horzCount; x++) { - for (int y = 0; y < vertCount; y++) { - // place once - Rectangle2D pos; - // Image positions are relative to the currentIP/BP - pos = new Rectangle2D.Float(sx - currentIPPosition - + (x * fopimage.getIntrinsicWidth()), - sy - currentBPPosition - + (y * fopimage.getIntrinsicHeight()), - fopimage.getIntrinsicWidth(), - fopimage.getIntrinsicHeight()); - drawImage(back.getURL(), pos); - } + if (back.getImageInfo() != null) { + ImageSize imageSize = back.getImageInfo().getSize(); + saveGraphicsState(); + clipRect(sx, sy, paddRectWidth, paddRectHeight); + int horzCount = (int)((paddRectWidth + * 1000 / imageSize.getWidthMpt()) + 1.0f); + int vertCount = (int)((paddRectHeight + * 1000 / imageSize.getHeightMpt()) + 1.0f); + if (back.getRepeat() == EN_NOREPEAT) { + horzCount = 1; + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATX) { + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATY) { + horzCount = 1; + } + //change from points to millipoints + sx *= 1000; + sy *= 1000; + if (horzCount == 1) { + sx += back.getHoriz(); + } + if (vertCount == 1) { + sy += back.getVertical(); + } + for (int x = 0; x < horzCount; x++) { + for (int y = 0; y < vertCount; y++) { + // place once + Rectangle2D pos; + // Image positions are relative to the currentIP/BP + pos = new Rectangle2D.Float(sx - currentIPPosition + + (x * imageSize.getWidthMpt()), + sy - currentBPPosition + + (y * imageSize.getHeightMpt()), + imageSize.getWidthMpt(), + imageSize.getHeightMpt()); + drawImage(back.getURL(), pos); } - - restoreGraphicsState(); - } else { - log.warn("Can't find background image: " + back.getURL()); } + + restoreGraphicsState(); } } diff --git a/src/java/org/apache/fop/render/Graphics2DAdapter.java b/src/java/org/apache/fop/render/Graphics2DAdapter.java index 2509b3864..e9b00f103 100644 --- a/src/java/org/apache/fop/render/Graphics2DAdapter.java +++ b/src/java/org/apache/fop/render/Graphics2DAdapter.java @@ -43,7 +43,7 @@ public interface Graphics2DAdapter { * @param height height of the image
* @throws IOException In case of an I/O error while writing the output format
*/
- void paintImage(Graphics2DImagePainter painter,
+ void paintImage(org.apache.xmlgraphics.java2d.Graphics2DImagePainter painter,
RendererContext context,
int x, int y, int width, int height) throws IOException;
diff --git a/src/java/org/apache/fop/render/Graphics2DImagePainter.java b/src/java/org/apache/fop/render/Graphics2DImagePainter.java index 3039a72a7..9301a6962 100644 --- a/src/java/org/apache/fop/render/Graphics2DImagePainter.java +++ b/src/java/org/apache/fop/render/Graphics2DImagePainter.java @@ -19,27 +19,12 @@ package org.apache.fop.render;
-import java.awt.Dimension;
-import java.awt.Graphics2D;
-import java.awt.geom.Rectangle2D;
-
/**
* This interface is used by the Graphics2DAdapter. Components that can paint using
* a Graphics2D instance can implement this interface to paint themselves.
+ * @deprecated use {@link org.apache.xmlgraphics.java2d.Graphics2DImagePainter} directly!
*/
-public interface Graphics2DImagePainter {
-
- /**
- * Called to paint the image. Implementations should scale so the image is
- * painted fully inside the given area indicated by then Rectangle2D object.
- * @param g2d the Graphics2D instance to paint on
- * @param area the target area for the image
- */
- void paint(Graphics2D g2d, Rectangle2D area);
+public interface Graphics2DImagePainter
+ extends org.apache.xmlgraphics.java2d.Graphics2DImagePainter {
- /**
- * @return the dimensions of the image to be painted in millipoints
- */
- Dimension getImageSize();
-
}
\ No newline at end of file diff --git a/src/java/org/apache/fop/render/afp/AFPGraphics2DAdapter.java b/src/java/org/apache/fop/render/afp/AFPGraphics2DAdapter.java index 8fa0d0f72..687a0373e 100644 --- a/src/java/org/apache/fop/render/afp/AFPGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/afp/AFPGraphics2DAdapter.java @@ -22,8 +22,9 @@ package org.apache.fop.render.afp; import java.awt.image.BufferedImage; import java.io.IOException; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; + import org.apache.fop.render.AbstractGraphics2DAdapter; -import org.apache.fop.render.Graphics2DImagePainter; import org.apache.fop.render.RendererContext; /** diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index de414fa10..cdb2f2ec8 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -20,10 +20,13 @@ package org.apache.fop.render.afp; import java.awt.Color; +import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; @@ -32,7 +35,22 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; + +import org.apache.xmlgraphics.image.codec.tiff.TIFFImage; +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.ImageRendered; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; +import org.apache.xmlgraphics.ps.ImageEncodingHelper; + import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.MimeConstants; import org.apache.fop.area.Block; @@ -49,6 +67,7 @@ import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; +import org.apache.fop.datatypes.URISpecification; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.FontInfo; @@ -56,10 +75,6 @@ import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.base14.Courier; import org.apache.fop.fonts.base14.Helvetica; import org.apache.fop.fonts.base14.TimesRoman; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.apache.fop.image.TIFFImage; -import org.apache.fop.image.XMLImage; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; @@ -74,7 +89,6 @@ import org.apache.fop.render.afp.modca.AFPConstants; import org.apache.fop.render.afp.modca.AFPDataStream; import org.apache.fop.render.afp.modca.ImageObject; import org.apache.fop.render.afp.modca.PageObject; -import org.w3c.dom.Document; /** * This is an implementation of a FOP Renderer that renders areas to AFP. @@ -735,20 +749,117 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { return context; } - /** - * {@inheritDoc} - */ - public void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] + {ImageFlavor.RAW_CCITTFAX, + ImageFlavor.GRAPHICS2D, + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE, + ImageFlavor.XML_DOM}; + + /** {@inheritDoc} */ + public void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + uri = URISpecification.getURL(uri); + Rectangle posInt = new Rectangle( + (int)pos.getX(), + (int)pos.getY(), + (int)pos.getWidth(), + (int)pos.getHeight()); + Point origin = new Point(currentIPPosition, currentBPPosition); + int x = origin.x + posInt.x; + int y = origin.y + posInt.y; + String name = null; if (pageSegmentsMap != null) { - name = (String) pageSegmentsMap.get(url); + name = (String) pageSegmentsMap.get(uri); } if (name != null) { - int x = mpts2units(pos.getX() + currentIPPosition); - int y = mpts2units(pos.getY() + currentBPPosition); - afpDataStream.createIncludePageSegment(name, x, y); + afpDataStream.createIncludePageSegment(name, mpts2units(x), mpts2units(y)); } else { - url = ImageFactory.getURL(url); + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; + try { + ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); + info = manager.getImageInfo(uri, sessionContext); + + //Only now fully load/prepare the image + Map hints = ImageUtil.getDefaultHints(sessionContext); + org.apache.xmlgraphics.image.loader.Image img = manager.getImage( + info, FLAVORS, hints, sessionContext); + + //...and process the image + if (img instanceof ImageGraphics2D) { + ImageGraphics2D imageG2D = (ImageGraphics2D)img; + RendererContext context = createRendererContext( + posInt.x, posInt.y, + posInt.width, posInt.height, foreignAttributes); + getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(), + context, + origin.x + posInt.x, origin.y + posInt.y, + posInt.width, posInt.height); + } else if (img instanceof ImageRendered) { + ImageRendered imgRend = (ImageRendered)img; + RenderedImage ri = imgRend.getRenderedImage(); + + drawBufferedImage(ri, getResolution(), + posInt.x + currentIPPosition, + posInt.y + currentBPPosition, + posInt.width, + posInt.height); + } else if (img instanceof ImageRawCCITTFax) { + ImageRawCCITTFax ccitt = (ImageRawCCITTFax)img; + int afpx = mpts2units(posInt.x + currentIPPosition); + int afpy = mpts2units(posInt.y + currentBPPosition); + int afpw = mpts2units(posInt.getWidth()); + int afph = mpts2units(posInt.getHeight()); + int afpres = getResolution(); + ImageObject io = afpDataStream.getImageObject(afpx, afpy, afpw, afph, + afpres, afpres); + io.setImageParameters( + (int) (ccitt.getSize().getDpiHorizontal() * 10), + (int) (ccitt.getSize().getDpiVertical() * 10), + ccitt.getSize().getWidthPx(), + ccitt.getSize().getHeightPx()); + int compression = ccitt.getCompression(); + switch (compression) { + case TIFFImage.COMP_FAX_G3_1D : + io.setImageEncoding((byte) 0x80); + break; + case TIFFImage.COMP_FAX_G3_2D : + io.setImageEncoding((byte) 0x81); + break; + case TIFFImage.COMP_FAX_G4_2D : + io.setImageEncoding((byte) 0x82); + break; + default: + throw new IllegalStateException( + "Invalid compression scheme: " + compression); + } + InputStream in = ccitt.createInputStream(); + try { + byte[] buf = IOUtils.toByteArray(in); + io.setImageData(buf); + } finally { + IOUtils.closeQuietly(in); + } + } else if (img instanceof ImageXMLDOM) { + ImageXMLDOM imgXML = (ImageXMLDOM)img; + renderDocument(imgXML.getDocument(), imgXML.getRootNamespace(), + pos, foreignAttributes); + } else { + throw new UnsupportedOperationException("Unsupported image type: " + img); + } + + } catch (ImageException ie) { + log.error("Error while processing image: " + + (info != null ? info.toString() : uri), ie); + } catch (FileNotFoundException fe) { + log.error(fe.getMessage()); + } catch (IOException ioe) { + log.error("I/O error while processing image: " + + (info != null ? info.toString() : uri), ioe); + } + + /* ImageFactory fact = userAgent.getFactory().getImageFactory(); FopImage fopimage = fact.getImage(url, userAgent); if (fopimage == null) { @@ -768,6 +879,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { renderDocument(doc, ns, pos, foreignAttributes); } else if (MimeConstants.MIME_EPS.equals(mime)) { log.warn("EPS images are not supported by this renderer"); + */ /* * } else if (MimeConstants.MIME_JPEG.equals(mime)) { if * (!fopimage.load(FopImage.ORIGINAL_DATA)) { return; } @@ -785,7 +897,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { * io.setImageIDESize((byte)fopimage.getBitsPerPixel()); * io.setImageEncoding((byte)0x83); * io.setImageData(fopimage.getRessourceBytes()); - */ + *//* } else if (MimeConstants.MIME_TIFF.equals(mime) && fopimage instanceof TIFFImage) { TIFFImage tiffImage = (TIFFImage) fopimage; @@ -852,43 +964,30 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { convertToGrayScaleImage(io, fopimage.getBitmaps(), fopimage .getWidth(), fopimage.getHeight(), this.bitsPerPixel); } - } + }*/ } } /** - * Writes a BufferedImage to an OutputStream as raw sRGB bitmaps. + * Writes a RenderedImage to an OutputStream as raw sRGB bitmaps. * - * @param img - * the BufferedImage + * @param image + * the RenderedImage * @param out * the OutputStream * @throws IOException * In case of an I/O error. */ - public static void writeImage(BufferedImage img, OutputStream out) + public static void writeImage(RenderedImage image, OutputStream out) throws IOException { - int w = img.getWidth(); - int h = img.getHeight(); - int[] tmpMap = img.getRGB(0, 0, w, h, null, 0, w); - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - int p = tmpMap[i * w + j]; - int r = (p >> 16) & 0xFF; - int g = (p >> 8) & 0xFF; - int b = (p) & 0xFF; - out.write((byte) (r & 0xFF)); - out.write((byte) (g & 0xFF)); - out.write((byte) (b & 0xFF)); - } - } + ImageEncodingHelper.encodeRenderedImageAsRGB(image, out); } /** * Draws a BufferedImage to AFP. * - * @param bi - * the BufferedImage + * @param image + * the RenderedImage * @param imageResolution * the resolution of the BufferedImage * @param x @@ -900,7 +999,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { * @param h * the height of the viewport (in mpt) */ - public void drawBufferedImage(BufferedImage bi, int imageResolution, int x, + public void drawBufferedImage(RenderedImage image, int imageResolution, int x, int y, int w, int h) { int afpx = mpts2units(x); int afpy = mpts2units(y); @@ -910,21 +1009,24 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { ByteArrayOutputStream baout = new ByteArrayOutputStream(); try { // Serialize image - writeImage(bi, baout); + //TODO Eventually, this should be changed not to buffer as this increases the + //memory consumption (see PostScript output) + writeImage(image, baout); byte[] buf = baout.toByteArray(); // Generate image ImageObject io = afpDataStream.getImageObject(afpx, afpy, afpw, afph, afpres, afpres); io.setImageParameters(imageResolution, imageResolution, - bi.getWidth(), bi.getHeight()); + image.getWidth(), image.getHeight()); if (colorImages) { io.setImageIDESize((byte)24); io.setImageData(buf); } else { // TODO Teach it how to handle grayscale BufferedImages directly // because this is pretty inefficient - convertToGrayScaleImage(io, buf, bi.getWidth(), bi.getHeight(), this.bitsPerPixel); + convertToGrayScaleImage(io, buf, + image.getWidth(), image.getHeight(), this.bitsPerPixel); } } catch (IOException ioe) { log.error("Error while serializing bitmap: " + ioe.getMessage(), diff --git a/src/java/org/apache/fop/render/java2d/Java2DGraphics2DAdapter.java b/src/java/org/apache/fop/render/java2d/Java2DGraphics2DAdapter.java index f74df6cff..1182fe42d 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DGraphics2DAdapter.java @@ -21,12 +21,14 @@ package org.apache.fop.render.java2d; import java.awt.Color;
import java.awt.Dimension;
+import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
+import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
+
import org.apache.fop.render.Graphics2DAdapter;
-import org.apache.fop.render.Graphics2DImagePainter;
import org.apache.fop.render.RendererContext;
/**
@@ -34,16 +36,6 @@ import org.apache.fop.render.RendererContext; */
public class Java2DGraphics2DAdapter implements Graphics2DAdapter {
- private Java2DGraphicsState state;
-
- /**
- * Main constructor
- * @param state the state tracker for this rendering run
- */
- public Java2DGraphics2DAdapter(Java2DGraphicsState state) {
- this.state = state;
- }
-
/** {@inheritDoc} */
public void paintImage(Graphics2DImagePainter painter,
RendererContext context,
@@ -63,26 +55,28 @@ public class Java2DGraphics2DAdapter implements Graphics2DAdapter { float sy = fheight / (float)imh;
Java2DRenderer renderer = (Java2DRenderer)context.getRenderer();
- renderer.saveGraphicsState();
- state.getGraph().setColor(Color.black);
- state.getGraph().setBackground(Color.black);
+ Java2DGraphicsState state = renderer.state;
+
+ //Create copy and paint on that
+ Graphics2D g2d = (Graphics2D)state.getGraph().create();
+ g2d.setColor(Color.black);
+ g2d.setBackground(Color.black);
//TODO Clip to the image area.
// transform so that the coordinates (0,0) is from the top left
// and positive is down and to the right. (0,0) is where the
// viewBox puts it.
- state.getGraph().translate(fx, fy);
+ g2d.translate(fx, fy);
AffineTransform at = AffineTransform.getScaleInstance(sx, sy);
if (!at.isIdentity()) {
- state.getGraph().transform(at);
+ g2d.transform(at);
}
Rectangle2D area = new Rectangle2D.Double(0.0, 0.0, imw, imh);
- painter.paint(state.getGraph(), area);
+ painter.paint(g2d, area);
- renderer.restoreGraphicsState();
-
+ g2d.dispose();
}
}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index eededf13b..0f73c5761 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -25,7 +25,6 @@ import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; -import java.awt.color.ColorSpace; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; @@ -33,14 +32,6 @@ import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.ComponentColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.PixelInterleavedSampleModel; -import java.awt.image.Raster; -import java.awt.image.SampleModel; -import java.awt.image.WritableRaster; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; @@ -51,7 +42,15 @@ import java.util.List; import java.util.Map; import java.util.Stack; -import org.w3c.dom.Document; +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.ImageRendered; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; @@ -65,13 +64,11 @@ import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; +import org.apache.fop.datatypes.URISpecification; import org.apache.fop.fo.Constants; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.Typeface; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.apache.fop.image.XMLImage; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; @@ -184,7 +181,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem /** {@inheritDoc} */ public Graphics2DAdapter getGraphics2DAdapter() { - return new Java2DGraphics2DAdapter(state); + return new Java2DGraphics2DAdapter(); } /** @@ -884,76 +881,58 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem /** * {@inheritDoc} */ - protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { int x = currentIPPosition + (int)Math.round(pos.getX()); int y = currentBPPosition + (int)Math.round(pos.getY()); - url = ImageFactory.getURL(url); - - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage fopimage = fact.getImage(url, userAgent); - - if (fopimage == null) { - return; - } - if (!fopimage.load(FopImage.DIMENSIONS)) { - return; - } - int w = fopimage.getWidth(); - int h = fopimage.getHeight(); - String mime = fopimage.getMimeType(); - if ("text/xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - renderDocument(doc, ns, pos, foreignAttributes); - - } else if ("image/svg+xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - - renderDocument(doc, ns, pos, foreignAttributes); - } else if ("image/eps".equals(mime)) { - log.warn("EPS images are not supported by this renderer"); - } else { - if (!fopimage.load(FopImage.BITMAP)) { - log.warn("Loading of bitmap failed: " + url); - return; + uri = URISpecification.getURL(uri); + + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; + try { + ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); + info = manager.getImageInfo(uri, sessionContext); + final ImageFlavor[] flavors = new ImageFlavor[] + {ImageFlavor.GRAPHICS2D, + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE, + ImageFlavor.XML_DOM}; + Map hints = ImageUtil.getDefaultHints(sessionContext); + org.apache.xmlgraphics.image.loader.Image img = manager.getImage( + info, flavors, hints, sessionContext); + if (img instanceof ImageGraphics2D) { + ImageGraphics2D imageG2D = (ImageGraphics2D)img; + int width = (int)pos.getWidth(); + int height = (int)pos.getHeight(); + 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; + AffineTransform at = new AffineTransform(); + at.translate(x / 1000f, y / 1000f); + double sx = pos.getWidth() / info.getSize().getWidthMpt(); + double sy = pos.getHeight() / info.getSize().getHeightMpt(); + sx *= userAgent.getSourceResolution() / info.getSize().getDpiHorizontal(); + sy *= userAgent.getSourceResolution() / info.getSize().getDpiVertical(); + at.scale(sx, sy); + state.getGraph().drawRenderedImage(imgRend.getRenderedImage(), at); + } else if (img instanceof ImageXMLDOM) { + ImageXMLDOM imgXML = (ImageXMLDOM)img; + renderDocument(imgXML.getDocument(), imgXML.getRootNamespace(), + pos, foreignAttributes); } - - byte[] raw = fopimage.getBitmaps(); - - // TODO Hardcoded color and sample models, FIX ME! - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), - new int[] {8, 8, 8}, - false, false, - ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); - SampleModel sampleModel = new PixelInterleavedSampleModel( - DataBuffer.TYPE_BYTE, w, h, 3, w * 3, new int[] {0, 1, 2}); - DataBuffer dbuf = new DataBufferByte(raw, w * h * 3); - - WritableRaster raster = Raster.createWritableRaster(sampleModel, - dbuf, null); - - java.awt.Image awtImage; - // Combine the color model and raster into a buffered image - awtImage = new BufferedImage(cm, raster, false, null); - - state.getGraph().drawImage(awtImage, - (int)(x / 1000f), (int)(y / 1000f), - (int)(pos.getWidth() / 1000f), (int)(pos.getHeight() / 1000f), null); + } catch (ImageException ie) { + log.error("Error while processing image: " + + (info != null ? info.toString() : uri), ie); + } catch (IOException ioe) { + log.error("I/O error while processing image: " + + (info != null ? info.toString() : uri), ioe); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ protected RendererContext createRendererContext(int x, int y, int width, int height, Map foreignAttributes) { RendererContext context = super.createRendererContext( @@ -962,9 +941,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem return context; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public int print(Graphics g, PageFormat pageFormat, int pageIndex) throws PrinterException { if (pageIndex >= getNumberOfPages()) { @@ -1004,6 +981,10 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem //not necessary in Java2D } + /** + * Controls the page background. + * @param transparentPageBackground true if the background should be transparent + */ public void setTransparentPageBackground(boolean transparentPageBackground) { this.transparentPageBackground = transparentPageBackground; } diff --git a/src/java/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java b/src/java/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java index 80c010afc..06a4d37a0 100644 --- a/src/java/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/pcl/PCLGraphics2DAdapter.java @@ -28,11 +28,13 @@ import java.io.IOException; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.java2d.GraphicContext; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; + import org.apache.fop.render.AbstractGraphics2DAdapter; -import org.apache.fop.render.Graphics2DImagePainter; import org.apache.fop.render.RendererContext; import org.apache.fop.util.UnitConv; -import org.apache.xmlgraphics.java2d.GraphicContext; /** * Graphics2DAdapter implementation for PCL and HP GL/2. @@ -110,7 +112,8 @@ public class PCLGraphics2DAdapter extends AbstractGraphics2DAdapter { if (!painted) { //Fallback solution: Paint to a BufferedImage int resolution = (int)Math.round(context.getUserAgent().getTargetResolution()); - BufferedImage bi = paintToBufferedImage(painter, pclContext, resolution, true, false); + BufferedImage bi = paintToBufferedImage(painter, pclContext, + resolution, !pclContext.isColorCanvas(), false); pcl.setCursorPos(x, y); gen.paintBitmap(bi, new Dimension(width, height), pclContext.isSourceTransparency()); diff --git a/src/java/org/apache/fop/render/pcl/PCLRenderer.java b/src/java/org/apache/fop/render/pcl/PCLRenderer.java index 732ce0f8f..b48c28089 100644 --- a/src/java/org/apache/fop/render/pcl/PCLRenderer.java +++ b/src/java/org/apache/fop/render/pcl/PCLRenderer.java @@ -24,24 +24,17 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; +import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; -import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.ComponentColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.PixelInterleavedSampleModel; -import java.awt.image.Raster; import java.awt.image.RenderedImage; -import java.awt.image.SampleModel; -import java.awt.image.WritableRaster; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.util.List; @@ -52,7 +45,19 @@ 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.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.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; +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.java2d.GraphicContext; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.MimeConstants; @@ -70,16 +75,12 @@ import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.Viewport; import org.apache.fop.area.inline.WordArea; +import org.apache.fop.datatypes.URISpecification; import org.apache.fop.fo.extensions.ExtensionElementMapping; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontMetrics; -import org.apache.fop.image.EPSImage; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.apache.fop.image.XMLImage; import org.apache.fop.render.Graphics2DAdapter; -import org.apache.fop.render.Graphics2DImagePainter; import org.apache.fop.render.PrintRenderer; import org.apache.fop.render.RendererContext; import org.apache.fop.render.RendererContextConstants; @@ -135,6 +136,13 @@ public class PCLRenderer extends PrintRenderer { private boolean allTextAsBitmaps = false; /** + * Controls whether an RGB canvas is used when converting Java2D graphics to bitmaps. + * This can be used to work around problems with Apache Batik, for example, but setting + * this to true will increase memory consumption. + */ + private boolean useColorCanvas = false; + + /** * Controls whether the generation of PJL commands gets disabled. */ private boolean disabledPJL = false; @@ -991,87 +999,88 @@ public class PCLRenderer extends PrintRenderer { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ + protected RendererContext createRendererContext(int x, int y, int width, int height, + Map foreignAttributes) { + RendererContext context = super.createRendererContext( + x, y, width, height, foreignAttributes); + context.setProperty(PCLRendererContextConstants.PCL_COLOR_CANVAS, + Boolean.valueOf(this.useColorCanvas)); + return context; + } + + /** {@inheritDoc} */ public void renderImage(Image image, Rectangle2D pos) { drawImage(image.getURL(), pos, image.getForeignAttributes()); } + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] + {ImageFlavor.GRAPHICS2D, + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE, + ImageFlavor.XML_DOM}; /** * Draw an image at the indicated location. - * @param url the URI/URL of the image + * @param uri the URI/URL of the image * @param pos the position of the image * @param foreignAttributes an optional Map with foreign attributes, may be null */ - protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { - url = ImageFactory.getURL(url); - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage fopimage = fact.getImage(url, userAgent); - if (fopimage == null) { - return; - } - if (!fopimage.load(FopImage.DIMENSIONS)) { - return; - } - String mime = fopimage.getMimeType(); - if ("text/xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - - renderDocument(doc, ns, pos, foreignAttributes); - } else if ("image/svg+xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - - renderDocument(doc, ns, pos, foreignAttributes); - } else if (fopimage instanceof EPSImage) { - log.warn("EPS images are not supported by this renderer"); - } else { - if (!fopimage.load(FopImage.BITMAP)) { - log.error("Bitmap image could not be processed: " + fopimage); - return; - } - byte[] imgmap = fopimage.getBitmaps(); + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + uri = URISpecification.getURL(uri); + Rectangle posInt = new Rectangle( + (int)pos.getX(), + (int)pos.getY(), + (int)pos.getWidth(), + (int)pos.getHeight()); + Point origin = new Point(currentIPPosition, currentBPPosition); + int x = origin.x + posInt.x; + int y = origin.y + posInt.y; + + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; + try { + ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); + info = manager.getImageInfo(uri, sessionContext); - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), - new int[] {8, 8, 8}, - false, false, - ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); - int imgw = fopimage.getWidth(); - int imgh = fopimage.getHeight(); - SampleModel sampleModel = new PixelInterleavedSampleModel( - DataBuffer.TYPE_BYTE, imgw, imgh, 3, imgw * 3, new int[] {0, 1, 2}); - DataBuffer dbuf = new DataBufferByte(imgmap, imgw * imgh * 3); - - WritableRaster raster = Raster.createWritableRaster(sampleModel, - dbuf, null); - - // Combine the color model and raster into a buffered image - RenderedImage img = new BufferedImage(cm, raster, false, null); - - try { - setCursorPos(this.currentIPPosition + (int)pos.getX(), - this.currentBPPosition + (int)pos.getY()); - gen.paintBitmap(img, - new Dimension((int)pos.getWidth(), (int)pos.getHeight()), + //Only now fully load/prepare the image + Map hints = ImageUtil.getDefaultHints(sessionContext); + org.apache.xmlgraphics.image.loader.Image img = manager.getImage( + info, FLAVORS, hints, sessionContext); + + //...and process the image + if (img instanceof ImageGraphics2D) { + ImageGraphics2D imageG2D = (ImageGraphics2D)img; + RendererContext context = createRendererContext( + posInt.x, posInt.y, + posInt.width, posInt.height, foreignAttributes); + getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(), + context, x, y, posInt.width, posInt.height); + } else if (img instanceof ImageRendered) { + ImageRendered imgRend = (ImageRendered)img; + RenderedImage ri = imgRend.getRenderedImage(); + setCursorPos(x, y); + gen.paintBitmap(ri, + new Dimension(posInt.width, posInt.height), false); - } catch (IOException ioe) { - handleIOTrouble(ioe); + } else if (img instanceof ImageXMLDOM) { + ImageXMLDOM imgXML = (ImageXMLDOM)img; + renderDocument(imgXML.getDocument(), imgXML.getRootNamespace(), + pos, foreignAttributes); + } else { + throw new UnsupportedOperationException("Unsupported image type: " + img); } + + } catch (ImageException ie) { + log.error("Error while processing image: " + + (info != null ? info.toString() : uri), ie); + } catch (FileNotFoundException fe) { + log.error(fe.getMessage()); + } catch (IOException ioe) { + handleIOTrouble(ioe); } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { Document doc = fo.getDocument(); String ns = fo.getNameSpace(); @@ -1153,52 +1162,45 @@ public class PCLRenderer extends PrintRenderer { } // background image - if (back.getFopImage() != null) { - FopImage fopimage = back.getFopImage(); - if (fopimage != null && fopimage.load(FopImage.DIMENSIONS)) { - saveGraphicsState(); - clipRect(sx, sy, paddRectWidth, paddRectHeight); - int horzCount = (int) ((paddRectWidth * 1000 / fopimage - .getIntrinsicWidth()) + 1.0f); - int vertCount = (int) ((paddRectHeight * 1000 / fopimage - .getIntrinsicHeight()) + 1.0f); - if (back.getRepeat() == EN_NOREPEAT) { - horzCount = 1; - vertCount = 1; - } else if (back.getRepeat() == EN_REPEATX) { - vertCount = 1; - } else if (back.getRepeat() == EN_REPEATY) { - horzCount = 1; - } - // change from points to millipoints - sx *= 1000; - sy *= 1000; - if (horzCount == 1) { - sx += back.getHoriz(); - } - if (vertCount == 1) { - sy += back.getVertical(); - } - for (int x = 0; x < horzCount; x++) { - for (int y = 0; y < vertCount; y++) { - // place once - Rectangle2D pos; - // Image positions are relative to the currentIP/BP - pos = new Rectangle2D.Float( - sx - currentIPPosition - + (x * fopimage.getIntrinsicWidth()), - sy - currentBPPosition - + (y * fopimage.getIntrinsicHeight()), - fopimage.getIntrinsicWidth(), - fopimage.getIntrinsicHeight()); - drawImage(back.getURL(), pos, null); - } + if (back.getImageInfo() != null) { + ImageSize imageSize = back.getImageInfo().getSize(); + saveGraphicsState(); + clipRect(sx, sy, paddRectWidth, paddRectHeight); + int horzCount = (int) ((paddRectWidth * 1000 / imageSize.getWidthMpt()) + 1.0f); + int vertCount = (int) ((paddRectHeight * 1000 / imageSize.getHeightMpt()) + 1.0f); + if (back.getRepeat() == EN_NOREPEAT) { + horzCount = 1; + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATX) { + vertCount = 1; + } else if (back.getRepeat() == EN_REPEATY) { + horzCount = 1; + } + // change from points to millipoints + sx *= 1000; + sy *= 1000; + if (horzCount == 1) { + sx += back.getHoriz(); + } + if (vertCount == 1) { + sy += back.getVertical(); + } + for (int x = 0; x < horzCount; x++) { + for (int y = 0; y < vertCount; y++) { + // place once + Rectangle2D pos; + // Image positions are relative to the currentIP/BP + pos = new Rectangle2D.Float( + sx - currentIPPosition + + (x * imageSize.getWidthMpt()), + sy - currentBPPosition + + (y * imageSize.getHeightMpt()), + imageSize.getWidthMpt(), + imageSize.getHeightMpt()); + drawImage(back.getURL(), pos, null); } - restoreGraphicsState(); - } else { - log.warn( - "Can't find background image: " + back.getURL()); } + restoreGraphicsState(); } } @@ -1508,6 +1510,11 @@ public class PCLRenderer extends PrintRenderer { } } + /** + * Controls whether all text should be generated as bitmaps or only text for which there's + * no native font. + * @param allTextAsBitmaps true if all text should be painted as bitmaps + */ public void setAllTextAsBitmaps(boolean allTextAsBitmaps) { this.allTextAsBitmaps = allTextAsBitmaps; } diff --git a/src/java/org/apache/fop/render/pcl/PCLRendererContext.java b/src/java/org/apache/fop/render/pcl/PCLRendererContext.java index 32510137b..62d4bcaa4 100644 --- a/src/java/org/apache/fop/render/pcl/PCLRendererContext.java +++ b/src/java/org/apache/fop/render/pcl/PCLRendererContext.java @@ -62,11 +62,28 @@ public class PCLRendererContext extends RendererContext.RendererContextWrapper { && "true".equalsIgnoreCase((String)getForeignAttributes().get(qName)); } + /** + * Indicates whether the background should not be erased prior to painting. + * @return true if the background shouldn't be erased + */ public boolean isSourceTransparency() { QName qName = new QName(ExtensionElementMapping.URI, null, "source-transparency"); return getForeignAttributes() != null && "true".equalsIgnoreCase((String)getForeignAttributes().get(qName)); } + /** + * Indicates whether an RGB canvas should be used rather than one with grayscales. + * This can be used to work around limitations of Apache Batik if you get error while + * processing SVG graphics. Note, however, that RGB mode will use more memory. + * @return true if an EGB canvas should be used + */ + public boolean isColorCanvas() { + QName qName = new QName(ExtensionElementMapping.URI, null, "color-canvas"); + Boolean prop = (Boolean)context.getProperty(PCLRendererContextConstants.PCL_COLOR_CANVAS); + return Boolean.TRUE.equals(prop) + || (getForeignAttributes() != null + && "true".equalsIgnoreCase((String)getForeignAttributes().get(qName))); + } }
\ No newline at end of file diff --git a/src/java/org/apache/fop/render/pcl/PCLRendererContextConstants.java b/src/java/org/apache/fop/render/pcl/PCLRendererContextConstants.java new file mode 100644 index 000000000..66ce40f5e --- /dev/null +++ b/src/java/org/apache/fop/render/pcl/PCLRendererContextConstants.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pcl; + +import org.apache.fop.render.RendererContextConstants; + +/** + * Defines a number of standard constants (keys) for use by the RendererContext class. + */ +public interface PCLRendererContextConstants extends RendererContextConstants { + + /** The PDF document that this image is being drawn into. */ + String PCL_COLOR_CANVAS = "color-canvas"; + +} diff --git a/src/java/org/apache/fop/render/pcl/PCLSVGHandler.java b/src/java/org/apache/fop/render/pcl/PCLSVGHandler.java index 8c9179063..a016c692f 100644 --- a/src/java/org/apache/fop/render/pcl/PCLSVGHandler.java +++ b/src/java/org/apache/fop/render/pcl/PCLSVGHandler.java @@ -22,6 +22,7 @@ package org.apache.fop.render.pcl; // FOP import org.apache.fop.render.AbstractGenericSVGHandler; import org.apache.fop.render.Renderer; +import org.apache.fop.render.RendererContext; /** * PCL XML handler for SVG. Uses Apache Batik for SVG processing. @@ -36,5 +37,12 @@ public class PCLSVGHandler extends AbstractGenericSVGHandler { return (renderer instanceof PCLRenderer); } + /** {@inheritDoc} */ + protected void updateRendererContext(RendererContext context) { + //Work around a problem in Batik: Gradients cannot be done in ColorSpace.CS_GRAY + context.setProperty(PCLRendererContextConstants.PCL_COLOR_CANVAS, + Boolean.TRUE); + } + } diff --git a/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java b/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java new file mode 100644 index 000000000..cd80a6797 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/AbstractImageAdapter.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; + +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.pdf.PDFConformanceException; +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFICCBasedColorSpace; +import org.apache.fop.pdf.PDFICCStream; +import org.apache.fop.pdf.PDFImage; +import org.apache.fop.pdf.PDFReference; +import org.apache.fop.util.ColorProfileUtil; + +/** + * Abstract PDFImage implementation for the PDF renderer. + */ +public abstract class AbstractImageAdapter implements PDFImage { + + /** logging instance */ + private static Log log = LogFactory.getLog(AbstractImageAdapter.class); + + private String key; + /** the image */ + protected Image image; + + private PDFICCStream pdfICCStream = null; + + /** + * Creates a new PDFImage from an Image instance. + * @param image the image + * @param key XObject key + */ + public AbstractImageAdapter(Image image, String key) { + this.image = image; + this.key = key; + } + + /** {@inheritDoc} */ + public String getKey() { + // key to look up XObject + return this.key; + } + + /** + * Returns the image's color space. + * @return the color space + */ + protected ColorSpace getImageColorSpace() { + return image.getColorSpace(); + } + + /** {@inheritDoc} */ + public void setup(PDFDocument doc) { + + ICC_Profile prof = image.getICCProfile(); + PDFDeviceColorSpace pdfCS = toPDFColorSpace(getImageColorSpace()); + if (prof != null) { + boolean defaultsRGB = ColorProfileUtil.isDefaultsRGB(prof); + String desc = ColorProfileUtil.getICCProfileDescription(prof); + if (log.isDebugEnabled()) { + log.debug("Image returns ICC profile: " + desc + ", default sRGB=" + defaultsRGB); + } + PDFICCBasedColorSpace cs = doc.getResources().getICCColorSpaceByProfileName(desc); + if (!defaultsRGB) { + if (cs == null) { + pdfICCStream = doc.getFactory().makePDFICCStream(); + pdfICCStream.setColorSpace(prof, pdfCS); + cs = doc.getFactory().makeICCBasedColorSpace(null, null, pdfICCStream); + } else { + pdfICCStream = cs.getICCStream(); + } + } else { + if (cs == null && "sRGB".equals(desc)) { + //It's the default sRGB profile which we mapped to DefaultRGB in PDFRenderer + cs = doc.getResources().getColorSpace("DefaultRGB"); + } + pdfICCStream = cs.getICCStream(); + } + } + if (doc.getProfile().getPDFAMode().isPDFA1LevelB()) { + if (pdfCS != null + && pdfCS.getColorSpace() != PDFDeviceColorSpace.DEVICE_RGB + && pdfCS.getColorSpace() != PDFDeviceColorSpace.DEVICE_GRAY + && prof == null) { + //See PDF/A-1, ISO 19005:1:2005(E), 6.2.3.3 + //FOP is currently restricted to DeviceRGB if PDF/A-1 is active. + throw new PDFConformanceException( + "PDF/A-1 does not allow mixing DeviceRGB and DeviceCMYK: " + + image.getInfo()); + } + } + } + + /** {@inheritDoc} */ + public int getWidth() { + return image.getSize().getWidthPx(); + } + + /** {@inheritDoc} */ + public int getHeight() { + return image.getSize().getHeightPx(); + } + + /** {@inheritDoc} */ + public boolean isTransparent() { + return false; + } + + /** {@inheritDoc} */ + public PDFColor getTransparentColor() { + return null; + } + + /** {@inheritDoc} */ + public String getMask() { + return null; + } + + /** {@inheritDoc} */ + public String getSoftMask() { + return null; + } + + /** {@inheritDoc} */ + public PDFReference getSoftMaskReference() { + return null; + } + + /** {@inheritDoc} */ + public boolean isInverted() { + return false; + } + + /** {@inheritDoc} */ + public boolean isPS() { + return false; + } + + /** {@inheritDoc} */ + public PDFICCStream getICCStream() { + return pdfICCStream; + } + + /** {@inheritDoc} */ + public void populateXObjectDictionary(PDFDictionary dict) { + //nop + } + + /** + * Converts a ColorSpace object to a PDFColorSpace object. + * @param cs ColorSpace instance + * @return PDFColorSpace new converted object + */ + public static PDFDeviceColorSpace toPDFColorSpace(ColorSpace cs) { + if (cs == null) { + return null; + } + + PDFDeviceColorSpace pdfCS = new PDFDeviceColorSpace(0); + switch (cs.getType()) { + case ColorSpace.TYPE_CMYK: + pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_CMYK); + break; + case ColorSpace.TYPE_GRAY: + pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_GRAY); + break; + default: + pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_RGB); + } + return pdfCS; + } + +} + diff --git a/src/java/org/apache/fop/render/pdf/FopPDFImage.java b/src/java/org/apache/fop/render/pdf/FopPDFImage.java deleted file mode 100644 index e60c207d6..000000000 --- a/src/java/org/apache/fop/render/pdf/FopPDFImage.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* $Id$ */ - -package org.apache.fop.render.pdf; -import java.awt.color.ColorSpace; -import java.awt.color.ICC_Profile; -import java.io.IOException; -import java.io.OutputStream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.fop.image.EPSImage; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.TIFFImage; -import org.apache.fop.pdf.BitmapImage; -import org.apache.fop.pdf.CCFFilter; -import org.apache.fop.pdf.DCTFilter; -import org.apache.fop.pdf.PDFColor; -import org.apache.fop.pdf.PDFConformanceException; -import org.apache.fop.pdf.PDFDeviceColorSpace; -import org.apache.fop.pdf.PDFDictionary; -import org.apache.fop.pdf.PDFDocument; -import org.apache.fop.pdf.PDFFilter; -import org.apache.fop.pdf.PDFFilterList; -import org.apache.fop.pdf.PDFICCBasedColorSpace; -import org.apache.fop.pdf.PDFICCStream; -import org.apache.fop.pdf.PDFImage; -import org.apache.fop.pdf.PDFReference; -import org.apache.fop.util.ColorProfileUtil; - -/** - * PDFImage implementation for the PDF renderer. - */ -public class FopPDFImage implements PDFImage { - - /** logging instance */ - private static Log log = LogFactory.getLog(FopPDFImage.class); - - private FopImage fopImage; - private PDFICCStream pdfICCStream = null; - private PDFFilter pdfFilter = null; - private String maskRef; - private PDFReference softMask; - private boolean isPS = false; - private boolean isCCF = false; - private boolean isDCT = false; - private String key; - - /** - * Creates a new PDFImage from a FopImage - * @param image Image - * @param key XObject key - */ - public FopPDFImage(FopImage image, String key) { - fopImage = image; - this.key = key; - isPS = (fopImage instanceof EPSImage); - } - - /** - * {@inheritDoc} - */ - public String getKey() { - // key to look up XObject - return this.key; - } - - /** - * {@inheritDoc} - */ - public void setup(PDFDocument doc) { - if ("image/jpeg".equals(fopImage.getMimeType())) { - pdfFilter = new DCTFilter(); - pdfFilter.setApplied(true); - isDCT = true; - - } else if ("image/tiff".equals(fopImage.getMimeType()) - && fopImage instanceof TIFFImage) { - TIFFImage tiffImage = (TIFFImage) fopImage; - if (tiffImage.getStripCount() == 1) { - int comp = tiffImage.getCompression(); - if (comp == 1) { - // Nothing to do - } else if (comp == 3) { - pdfFilter = new CCFFilter(); - pdfFilter.setApplied(true); - isCCF = true; - } else if (comp == 4) { - pdfFilter = new CCFFilter(); - pdfFilter.setApplied(true); - PDFDictionary dict = new PDFDictionary(); - dict.put("K", -1); - dict.put("Columns", tiffImage.getWidth()); - ((CCFFilter)pdfFilter).setDecodeParms(dict); - isCCF = true; - } else if (comp == 6) { - pdfFilter = new DCTFilter(); - pdfFilter.setApplied(true); - isDCT = true; - } - } - } - if (isPS || isDCT || isCCF) { - fopImage.load(FopImage.ORIGINAL_DATA); - } else { - fopImage.load(FopImage.BITMAP); - } - ICC_Profile prof = fopImage.getICCProfile(); - PDFDeviceColorSpace pdfCS = toPDFColorSpace(fopImage.getColorSpace()); - if (prof != null) { - boolean defaultsRGB = ColorProfileUtil.isDefaultsRGB(prof); - String desc = ColorProfileUtil.getICCProfileDescription(prof); - if (log.isDebugEnabled()) { - log.debug("Image returns ICC profile: " + desc + ", default sRGB=" + defaultsRGB); - } - PDFICCBasedColorSpace cs = doc.getResources().getICCColorSpaceByProfileName(desc); - if (!defaultsRGB) { - if (cs == null) { - pdfICCStream = doc.getFactory().makePDFICCStream(); - pdfICCStream.setColorSpace(prof, pdfCS); - cs = doc.getFactory().makeICCBasedColorSpace(null, null, pdfICCStream); - } else { - pdfICCStream = cs.getICCStream(); - } - } else { - if (cs == null && "sRGB".equals(desc)) { - //It's the default sRGB profile which we mapped to DefaultRGB in PDFRenderer - cs = doc.getResources().getColorSpace("DefaultRGB"); - } - pdfICCStream = cs.getICCStream(); - } - } - //Handle transparency mask if applicable - if (fopImage.hasSoftMask()) { - doc.getProfile().verifyTransparencyAllowed(fopImage.getOriginalURI()); - //TODO Implement code to combine image with background color if transparency is not - //allowed (need BufferedImage support for that) - byte [] softMaskBitmap = fopImage.getSoftMask(); - if (softMaskBitmap == null) { - return; - } - BitmapImage fopimg = new BitmapImage - ("Mask:" + key, fopImage.getWidth(), fopImage.getHeight(), - softMaskBitmap, null); - fopimg.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY)); - softMask = doc.addImage(null, fopimg).makeReference(); - } - if (doc.getProfile().getPDFAMode().isPDFA1LevelB()) { - if (pdfCS != null - && pdfCS.getColorSpace() != PDFDeviceColorSpace.DEVICE_RGB - && pdfCS.getColorSpace() != PDFDeviceColorSpace.DEVICE_GRAY - && prof == null) { - //See PDF/A-1, ISO 19005:1:2005(E), 6.2.3.3 - //FOP is currently restricted to DeviceRGB if PDF/A-1 is active. - throw new PDFConformanceException( - "PDF/A-1 does not allow mixing DeviceRGB and DeviceCMYK: " - + fopImage.getOriginalURI()); - } - } - } - - /** - * {@inheritDoc} - */ - public int getWidth() { - return fopImage.getWidth(); - } - - /** - * {@inheritDoc} - */ - public int getHeight() { - return fopImage.getHeight(); - } - - /** - * {@inheritDoc} - */ - public PDFDeviceColorSpace getColorSpace() { - // DeviceGray, DeviceRGB, or DeviceCMYK - if (isCCF || isDCT || isPS) { - return toPDFColorSpace(fopImage.getColorSpace()); - } else { - return toPDFColorSpace(ColorSpace.getInstance(ColorSpace.CS_sRGB)); - } - } - - /** - * {@inheritDoc} - */ - public int getBitsPerPixel() { - if (isCCF) { - return fopImage.getBitsPerPixel(); - } else { - return 8; //TODO This is suboptimal, handling everything as RGB - //The image wrappers can mostly only return RGB bitmaps right now. This should - //be improved so the renderers can deal directly with RenderedImage instances. - } - } - - /** - * {@inheritDoc} - */ - public boolean isTransparent() { - return fopImage.isTransparent(); - } - - /** - * {@inheritDoc} - */ - public PDFColor getTransparentColor() { - return new PDFColor(fopImage.getTransparentColor().getRed(), - fopImage.getTransparentColor().getGreen(), - fopImage.getTransparentColor().getBlue()); - } - - /** - * {@inheritDoc} - */ - public String getMask() { - return maskRef; - } - - /** {@inheritDoc} */ - public PDFReference getSoftMaskReference() { - return softMask; - } - - /** @return true for CMYK images generated by Adobe Photoshop */ - public boolean isInverted() { - return fopImage.isInverted(); - } - - /** - * {@inheritDoc} - */ - public boolean isPS() { - return isPS; - } - - /** - * {@inheritDoc} - */ - public PDFFilter getPDFFilter() { - return pdfFilter; - } - - /** - * {@inheritDoc} - */ - public void outputContents(OutputStream out) throws IOException { - if (isPS) { - outputPostScriptContents(out); - } else { - if (fopImage.getBitmapsSize() > 0) { - out.write(fopImage.getBitmaps()); - } else { - out.write(fopImage.getRessourceBytes()); - } - } - } - - /** - * Serializes an EPS image to an OutputStream. - * @param out OutputStream to write to - * @throws IOException in case of an I/O problem - */ - protected void outputPostScriptContents(OutputStream out) throws IOException { - EPSImage epsImage = (EPSImage) fopImage; - int[] bbox = epsImage.getBBox(); - int bboxw = bbox[2] - bbox[0]; - int bboxh = bbox[3] - bbox[1]; - - // delegate the stream work to PDFStream - //PDFStream imgStream = new PDFStream(0); - - StringBuffer preamble = new StringBuffer(); - preamble.append("%%BeginDocument: " + epsImage.getDocName() + "\n"); - - preamble.append("userdict begin % Push userdict on dict stack\n"); - preamble.append("/PreEPS_state save def\n"); - preamble.append("/dict_stack countdictstack def\n"); - preamble.append("/ops_count count 1 sub def\n"); - preamble.append("/showpage {} def\n"); - - - preamble.append((double)(1f / (double) bboxw) + " " - + (double)(1f / (double) bboxh) + " scale\n"); - preamble.append(-bbox[0] + " " + (-bbox[1]) + " translate\n"); - preamble.append(bbox[0] + " " + bbox[1] + " " - + bboxw + " " + bboxh + " rectclip\n"); - preamble.append("newpath\n"); - - StringBuffer post = new StringBuffer(); - post.append("%%EndDocument\n"); - post.append("count ops_count sub {pop} repeat\n"); - post.append("countdictstack dict_stack sub {end} repeat\n"); - post.append("PreEPS_state restore\n"); - post.append("end % userdict\n"); - - //Write Preamble - out.write(PDFDocument.encode(preamble.toString())); - //Write EPS contents - out.write(((EPSImage)fopImage).getEPSImage()); - //Writing trailer - out.write(PDFDocument.encode(post.toString())); - } - - /** - * {@inheritDoc} - */ - public PDFICCStream getICCStream() { - return pdfICCStream; - } - - /** - * Converts a ColorSpace object to a PDFColorSpace object. - * @param cs ColorSpace instance - * @return PDFColorSpace new converted object - */ - public static PDFDeviceColorSpace toPDFColorSpace(ColorSpace cs) { - if (cs == null) { - return null; - } - - PDFDeviceColorSpace pdfCS = new PDFDeviceColorSpace(0); - switch(cs.getType()) { - case ColorSpace.TYPE_CMYK: - pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_CMYK); - break; - case ColorSpace.TYPE_RGB: - pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_RGB); - break; - case ColorSpace.TYPE_GRAY: - pdfCS.setColorSpace(PDFDeviceColorSpace.DEVICE_GRAY); - break; - } - return pdfCS; - } - - /** - * {@inheritDoc} - */ - public String getFilterHint() { - if (isPS) { - return PDFFilterList.CONTENT_FILTER; - } else if (isDCT) { - return PDFFilterList.JPEG_FILTER; - } else if (isCCF) { - return PDFFilterList.TIFF_FILTER; - } else { - return PDFFilterList.IMAGE_FILTER; - } - } - -} - diff --git a/src/java/org/apache/fop/render/pdf/ImageRawCCITTFaxAdapter.java b/src/java/org/apache/fop/render/pdf/ImageRawCCITTFaxAdapter.java new file mode 100644 index 000000000..b80e2c03e --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/ImageRawCCITTFaxAdapter.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; +import java.awt.color.ColorSpace; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.xmlgraphics.image.codec.tiff.TIFFImage; +import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; + +import org.apache.fop.pdf.CCFFilter; +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFilter; +import org.apache.fop.pdf.PDFFilterList; + +/** + * PDFImage implementation for the PDF renderer which handles raw CCITT fax images. + */ +public class ImageRawCCITTFaxAdapter extends AbstractImageAdapter { + + private PDFFilter pdfFilter = null; + + /** + * Creates a new PDFImage from an Image instance. + * @param image the CCITT encoded image + * @param key XObject key + */ + public ImageRawCCITTFaxAdapter(ImageRawCCITTFax image, String key) { + super(image, key); + } + + /** + * Returns the {@link ImageRawCCITTFax} instance for this adapter. + * @return the image instance + */ + public ImageRawCCITTFax getImage() { + return ((ImageRawCCITTFax)this.image); + } + + /** {@inheritDoc} */ + public void setup(PDFDocument doc) { + pdfFilter = new CCFFilter(); + pdfFilter.setApplied(true); + PDFDictionary dict = new PDFDictionary(); + dict.put("Columns", this.image.getSize().getWidthPx()); + int compression = getImage().getCompression(); + switch (compression) { + case TIFFImage.COMP_FAX_G3_1D : + dict.put("K", 0); + break; + case TIFFImage.COMP_FAX_G3_2D : + dict.put("K", 1); + break; + case TIFFImage.COMP_FAX_G4_2D : + dict.put("K", -1); + break; + default: + throw new IllegalStateException("Invalid compression scheme: " + compression); + } + ((CCFFilter)pdfFilter).setDecodeParms(dict); + + super.setup(doc); + } + + /** {@inheritDoc} */ + public PDFDeviceColorSpace getColorSpace() { + return toPDFColorSpace(ColorSpace.getInstance(ColorSpace.CS_GRAY)); + } + + /** {@inheritDoc} */ + public int getBitsPerComponent() { + return 1; + } + + /** {@inheritDoc} */ + public PDFFilter getPDFFilter() { + return pdfFilter; + } + + /** {@inheritDoc} */ + public void outputContents(OutputStream out) throws IOException { + getImage().writeTo(out); + } + + /** {@inheritDoc} */ + public String getFilterHint() { + return PDFFilterList.TIFF_FILTER; + } + +} + diff --git a/src/java/org/apache/fop/render/pdf/ImageRawJPEGAdapter.java b/src/java/org/apache/fop/render/pdf/ImageRawJPEGAdapter.java new file mode 100644 index 000000000..4b0ce4a85 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/ImageRawJPEGAdapter.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; + +import org.apache.fop.pdf.DCTFilter; +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFilter; +import org.apache.fop.pdf.PDFFilterList; + +/** + * PDFImage implementation for the PDF renderer which handles raw JPEG images. + */ +public class ImageRawJPEGAdapter extends AbstractImageAdapter { + + private PDFFilter pdfFilter = null; + + /** + * Creates a new PDFImage from an Image instance. + * @param image the JPEG image + * @param key XObject key + */ + public ImageRawJPEGAdapter(ImageRawJPEG image, String key) { + super(image, key); + } + + /** + * Returns the {@link ImageRawJPEG} instance for this adapter. + * @return the image instance + */ + public ImageRawJPEG getImage() { + return ((ImageRawJPEG)this.image); + } + + /** {@inheritDoc} */ + public void setup(PDFDocument doc) { + pdfFilter = new DCTFilter(); + pdfFilter.setApplied(true); + + super.setup(doc); + } + + /** {@inheritDoc} */ + public PDFDeviceColorSpace getColorSpace() { + // DeviceGray, DeviceRGB, or DeviceCMYK + return toPDFColorSpace(getImageColorSpace()); + } + + /** {@inheritDoc} */ + public int getBitsPerComponent() { + return 8; + } + + /** @return true for CMYK images generated by Adobe Photoshop */ + public boolean isInverted() { + return getImage().isInverted(); + } + + /** {@inheritDoc} */ + public PDFFilter getPDFFilter() { + return pdfFilter; + } + + /** {@inheritDoc} */ + public void outputContents(OutputStream out) throws IOException { + getImage().writeTo(out); + } + + /** {@inheritDoc} */ + public String getFilterHint() { + return PDFFilterList.JPEG_FILTER; + } + +} + diff --git a/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java new file mode 100644 index 000000000..62e83da81 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/ImageRenderedAdapter.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.commons.io.output.ByteArrayOutputStream; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.impl.ImageRendered; +import org.apache.xmlgraphics.ps.ImageEncodingHelper; + +import org.apache.fop.pdf.AlphaRasterImage; +import org.apache.fop.pdf.PDFArray; +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.pdf.PDFDeviceColorSpace; +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFilter; +import org.apache.fop.pdf.PDFFilterList; +import org.apache.fop.pdf.PDFName; +import org.apache.fop.pdf.PDFReference; + +/** + * PDFImage implementation for the PDF renderer which handles RenderedImages. + */ +public class ImageRenderedAdapter extends AbstractImageAdapter { + + /** logging instance */ + private static Log log = LogFactory.getLog(ImageRenderedAdapter.class); + + private ImageEncodingHelper encodingHelper; + + private PDFFilter pdfFilter = null; + private String maskRef; + private PDFReference softMask; + + /** + * Creates a new PDFImage from an Image instance. + * @param image the image + * @param key XObject key + */ + public ImageRenderedAdapter(ImageRendered image, String key) { + super(image, key); + this.encodingHelper = new ImageEncodingHelper(image.getRenderedImage()); + } + + /** + * Returns the ImageRendered instance for this adapter. + * @return the ImageRendered instance + */ + public ImageRendered getImage() { + return ((ImageRendered)this.image); + } + + private ColorModel getEffectiveColorModel() { + return encodingHelper.getEncodedColorModel(); + } + + /** {@inheritDoc} */ + protected ColorSpace getImageColorSpace() { + return getEffectiveColorModel().getColorSpace(); + } + + /** {@inheritDoc} */ + public void setup(PDFDocument doc) { + RenderedImage ri = getImage().getRenderedImage(); + ColorModel cm = getEffectiveColorModel(); + + super.setup(doc); + + //Handle transparency mask if applicable + ColorModel orgcm = ri.getColorModel(); + if (orgcm.hasAlpha() && orgcm.getTransparency() == ColorModel.TRANSLUCENT) { + doc.getProfile().verifyTransparencyAllowed(image.getInfo().getOriginalURI()); + //TODO Implement code to combine image with background color if transparency is not + //allowed (need BufferedImage support for that) + + AlphaRasterImage alphaImage = new AlphaRasterImage("Mask:" + getKey(), ri); + this.softMask = doc.addImage(null, alphaImage).makeReference(); + } + } + + /** {@inheritDoc} */ + public PDFDeviceColorSpace getColorSpace() { + // DeviceGray, DeviceRGB, or DeviceCMYK + return toPDFColorSpace(getEffectiveColorModel().getColorSpace()); + } + + /** {@inheritDoc} */ + public int getBitsPerComponent() { + ColorModel cm = getEffectiveColorModel(); + if (cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)cm; + return icm.getComponentSize(0); + } else { + return cm.getComponentSize(0); + } + } + + /** {@inheritDoc} */ + public boolean isTransparent() { + ColorModel cm = getEffectiveColorModel(); + if (cm instanceof IndexColorModel) { + if (cm.getTransparency() == IndexColorModel.TRANSLUCENT) { + return true; + } + } + return (getImage().getTransparentColor() != null); + } + + private static Integer getIndexOfFirstTransparentColorInPalette(RenderedImage image) { + ColorModel cm = image.getColorModel(); + if (cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)cm; + //Identify the transparent color in the palette + byte[] alphas = new byte[icm.getMapSize()]; + byte[] reds = new byte[icm.getMapSize()]; + byte[] greens = new byte[icm.getMapSize()]; + byte[] blues = new byte[icm.getMapSize()]; + icm.getAlphas(alphas); + icm.getReds(reds); + icm.getGreens(greens); + icm.getBlues(blues); + for (int i = 0; + i < ((IndexColorModel) cm).getMapSize(); + i++) { + if ((alphas[i] & 0xFF) == 0) { + return new Integer(i); + } + } + } + return null; + } + + /** {@inheritDoc} */ + public PDFColor getTransparentColor() { + ColorModel cm = getEffectiveColorModel(); + if (cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)cm; + if (cm.getTransparency() == IndexColorModel.TRANSLUCENT) { + int transPixel = icm.getTransparentPixel(); + return new PDFColor( + icm.getRed(transPixel), + icm.getGreen(transPixel), + icm.getBlue(transPixel)); + } + } + return new PDFColor(getImage().getTransparentColor()); + } + + /** {@inheritDoc} */ + public String getMask() { + return maskRef; + } + + /** {@inheritDoc} */ + public PDFReference getSoftMaskReference() { + return softMask; + } + + /** {@inheritDoc} */ + public PDFFilter getPDFFilter() { + return pdfFilter; + } + + /** {@inheritDoc} */ + public void outputContents(OutputStream out) throws IOException { + encodingHelper.encode(out); + } + + private static final int MAX_HIVAL = 255; + + /** {@inheritDoc} */ + public void populateXObjectDictionary(PDFDictionary dict) { + ColorModel cm = getEffectiveColorModel(); + if (cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)cm; + PDFArray indexed = new PDFArray(dict); + indexed.add(new PDFName("Indexed")); + + if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) { + log.warn("Indexed color space is not using RGB as base color space." + + " The image may not be handled correctly." + + " Base color space: " + icm.getColorSpace() + + " Image: " + image.getInfo()); + } + indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName())); + int c = icm.getMapSize(); + int hival = c - 1; + if (hival > MAX_HIVAL) { + throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL); + } + indexed.add(new Integer(hival)); + int[] palette = new int[c]; + icm.getRGBs(palette); + ByteArrayOutputStream baout = new ByteArrayOutputStream(); + for (int i = 0; i < c; i++) { + //TODO Probably doesn't work for non RGB based color spaces + //See log warning above + int entry = palette[i]; + baout.write((entry & 0xFF0000) >> 16); + baout.write((entry & 0xFF00) >> 8); + baout.write(entry & 0xFF); + } + indexed.add(baout.toByteArray()); + + dict.put("ColorSpace", indexed); + dict.put("BitsPerComponent", icm.getPixelSize()); + + Integer index = getIndexOfFirstTransparentColorInPalette(getImage().getRenderedImage()); + if (index != null) { + PDFArray mask = new PDFArray(dict); + mask.add(index); + mask.add(index); + dict.put("Mask", mask); + } + } + } + + /** {@inheritDoc} */ + public String getFilterHint() { + return PDFFilterList.IMAGE_FILTER; + } + +} + diff --git a/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java b/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java index eb0e4b378..d1e0abd5c 100644 --- a/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/pdf/PDFGraphics2DAdapter.java @@ -28,8 +28,9 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage;
import java.io.IOException;
+import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
+
import org.apache.fop.render.AbstractGraphics2DAdapter;
-import org.apache.fop.render.Graphics2DImagePainter;
import org.apache.fop.render.RendererContext;
import org.apache.fop.render.RendererContext.RendererContextWrapper;
import org.apache.fop.svg.PDFGraphics2D;
diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandler.java b/src/java/org/apache/fop/render/pdf/PDFImageHandler.java index 52043b38e..d62dcbc5b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandler.java @@ -19,11 +19,15 @@ package org.apache.fop.render.pdf; +import java.awt.Point; +import java.awt.Rectangle; import java.io.IOException; -import org.apache.fop.image.FopImage; -import org.apache.fop.pdf.PDFDocument; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; + import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RendererContext; /** * This interface is used for handling all sorts of image type for PDF output. @@ -31,20 +35,38 @@ import org.apache.fop.pdf.PDFXObject; public interface PDFImageHandler { /** - * Returns the MIME type supported by this instance. - * @return the MIME type + * Returns the priority for this image handler. A lower value means higher priority. This + * information is used to build the ordered/prioritized list of supported ImageFlavors for + * the PDF renderer. The built-in handlers use priorities between 100 and 999. + * @return a positive integer (>0) indicating the priority + */ + int getPriority(); + + /** + * Returns the {@link ImageFlavor}s supported by this instance + * @return the supported image flavors + */ + ImageFlavor[] getSupportedImageFlavors(); + + /** + * Returns the {@link Image} subclass supported by this instance. + * @return the Image type */ - String getSupportedMimeType(); + Class getSupportedImageClass(); /** - * Generates the PDF objects for the given FopImage instance and returns - * the resulting XObject. + * Generates the PDF objects for the given {@link Image} instance. If the handler generates + * an XObject, it shall return it or otherwise return null. A generated XObject shall be + * placed in the current viewport according to the two parameters "origin" and "pos". + * @param context the PDF renderer context * @param image the image to be handled - * @param uri the URI of the image - * @param pdfDoc the target PDF document - * @return the generated XObject + * @param origin the current position in the current viewport (in millipoints) + * @param pos the position and scaling of the image relative to the origin point + * (in millipoints) + * @return the generated XObject or null if no XObject was generated * @throws IOException if an I/O error occurs */ - PDFXObject generateImage(FopImage image, String uri, PDFDocument pdfDoc) throws IOException; + PDFXObject generateImage(RendererContext context, Image image, + Point origin, Rectangle pos) throws IOException; } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java new file mode 100644 index 000000000..f1825297e --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; + +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RendererContext; + +/** + * PDFImageHandler implementation which handles Graphics2D images. + */ +public class PDFImageHandlerGraphics2D implements PDFImageHandler { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageFlavor.GRAPHICS2D, + }; + + /** {@inheritDoc} */ + public PDFXObject generateImage(RendererContext context, Image image, + Point origin, Rectangle pos) + throws IOException { + PDFRenderer renderer = (PDFRenderer)context.getRenderer(); + ImageGraphics2D imageG2D = (ImageGraphics2D)image; + renderer.getGraphics2DAdapter().paintImage(imageG2D.getGraphics2DImagePainter(), + context, origin.x + pos.x, origin.y + pos.y, pos.width, pos.height); + return null; + } + + /** {@inheritDoc} */ + public int getPriority() { + return 200; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageGraphics2D.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return FLAVORS; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java new file mode 100644 index 000000000..65142878a --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawCCITTFax.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; + +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFImage; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RendererContext; + +/** + * PDFImageHandler implementation which handles CCITT encoded images (CCITT fax group 3/4). + */ +public class PDFImageHandlerRawCCITTFax implements PDFImageHandler { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageFlavor.RAW_CCITTFAX, + }; + + /** {@inheritDoc} */ + public PDFXObject generateImage(RendererContext context, Image image, + Point origin, Rectangle pos) + throws IOException { + PDFRenderer renderer = (PDFRenderer)context.getRenderer(); + ImageRawCCITTFax ccitt = (ImageRawCCITTFax)image; + PDFDocument pdfDoc = (PDFDocument)context.getProperty( + PDFRendererContextConstants.PDF_DOCUMENT); + PDFResourceContext resContext = (PDFResourceContext)context.getProperty( + PDFRendererContextConstants.PDF_CONTEXT); + + PDFImage pdfimage = new ImageRawCCITTFaxAdapter(ccitt, image.getInfo().getOriginalURI()); + PDFXObject xobj = pdfDoc.addImage(resContext, pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + renderer.placeImage(x, y, w, h, xobj); + + return xobj; + } + + /** {@inheritDoc} */ + public int getPriority() { + return 100; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageRawCCITTFax.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return FLAVORS; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java new file mode 100644 index 000000000..58c9f1f53 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; + +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFImage; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RendererContext; + +/** + * PDFImageHandler implementation which handles raw JPEG images. + */ +public class PDFImageHandlerRawJPEG implements PDFImageHandler { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageFlavor.RAW_JPEG, + }; + + /** {@inheritDoc} */ + public PDFXObject generateImage(RendererContext context, Image image, + Point origin, Rectangle pos) + throws IOException { + PDFRenderer renderer = (PDFRenderer)context.getRenderer(); + ImageRawJPEG jpeg = (ImageRawJPEG)image; + PDFDocument pdfDoc = (PDFDocument)context.getProperty( + PDFRendererContextConstants.PDF_DOCUMENT); + PDFResourceContext resContext = (PDFResourceContext)context.getProperty( + PDFRendererContextConstants.PDF_CONTEXT); + + PDFImage pdfimage = new ImageRawJPEGAdapter(jpeg, image.getInfo().getOriginalURI()); + PDFXObject xobj = pdfDoc.addImage(resContext, pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + renderer.placeImage(x, y, w, h, xobj); + + return xobj; + } + + /** {@inheritDoc} */ + public int getPriority() { + return 100; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageRawJPEG.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return FLAVORS; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRegistry.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRegistry.java index 36e4ea7e1..536fc19b0 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRegistry.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRegistry.java @@ -19,11 +19,17 @@ package org.apache.fop.render.pdf; +import java.util.Comparator; import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.util.Service; /** @@ -36,8 +42,23 @@ public class PDFImageHandlerRegistry { /** the logger */ private static Log log = LogFactory.getLog(PDFImageHandlerRegistry.class); + private static final Comparator HANDLER_COMPARATOR = new Comparator() { + public int compare(Object o1, Object o2) { + PDFImageHandler h1 = (PDFImageHandler)o1; + PDFImageHandler h2 = (PDFImageHandler)o2; + return h1.getPriority() - h2.getPriority(); + } + }; + /** Map containing PDF image handlers for various MIME types */ private Map handlers = new java.util.HashMap(); + /** List containing the same handlers as above but ordered by priority */ + private List handlerList = new java.util.LinkedList(); + + /** Sorted Set of registered handlers */ + private ImageFlavor[] supportedFlavors = new ImageFlavor[0]; + private int handlerRegistrations; + private int lastSync; /** * Default constructor. @@ -75,25 +96,74 @@ public class PDFImageHandlerRegistry { * Add an image handler. The handler itself is inspected to find out what it supports. * @param handler the PDFImageHandler instance */ - public void addHandler(PDFImageHandler handler) { - String mime = handler.getSupportedMimeType(); - handlers.put(mime, handler); + public synchronized void addHandler(PDFImageHandler handler) { + Class imageClass = handler.getSupportedImageClass(); + this.handlers.put(imageClass, handler); + + //Sorted insert + ListIterator iter = this.handlerList.listIterator(); + while (iter.hasNext()) { + PDFImageHandler h = (PDFImageHandler)iter.next(); + if (HANDLER_COMPARATOR.compare(handler, h) < 0) { + iter.previous(); + break; + } + } + iter.add(handler); + this.handlerRegistrations++; } /** * Returns an PDFImageHandler which handles an specific image type given the MIME type * of the image. - * @param mime the requested MIME type + * @param img the Image to be handled * @return the PDFImageHandler responsible for handling the image or null if none is available */ - public PDFImageHandler getHandler(String mime) { - PDFImageHandler handler; + public PDFImageHandler getHandler(Image img) { + return getHandler(img.getClass()); + } - handler = (PDFImageHandler)handlers.get(mime); + /** + * Returns an PDFImageHandler which handles an specific image type given the MIME type + * of the image. + * @param imageClass the Image subclass for which to get a handler + * @return the PDFImageHandler responsible for handling the image or null if none is available + */ + protected synchronized PDFImageHandler getHandler(Class imageClass) { + PDFImageHandler handler = null; + Class cl = imageClass; + while (cl != null) { + handler = (PDFImageHandler)handlers.get(cl); + if (handler != null) { + break; + } + cl = cl.getSuperclass(); + } return handler; } /** + * Returns the ordered array of supported image flavors. + * @return the array of image flavors + */ + public synchronized ImageFlavor[] getSupportedFlavors() { + if (this.lastSync != this.handlerRegistrations) { + //Extract all ImageFlavors into a single array + List flavors = new java.util.ArrayList(); + Iterator iter = this.handlerList.iterator(); + while (iter.hasNext()) { + ImageFlavor[] f = ((PDFImageHandler)iter.next()).getSupportedImageFlavors(); + for (int i = 0; i < f.length; i++) { + flavors.add(f[i]); + } + } + this.supportedFlavors = (ImageFlavor[])flavors.toArray(new ImageFlavor[flavors.size()]); + this.lastSync = this.handlerRegistrations; + } + return this.supportedFlavors; + } + + /** * Discovers PDFImageHandler implementations through the classpath and dynamically * registers them. */ diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java new file mode 100644 index 000000000..628883b9f --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRendered; + +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFImage; +import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RendererContext; + +/** + * PDFImageHandler implementation which handles RenderedImage instances. + */ +public class PDFImageHandlerRenderedImage implements PDFImageHandler { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE + }; + + /** {@inheritDoc} */ + public PDFXObject generateImage(RendererContext context, Image image, + Point origin, Rectangle pos) + throws IOException { + PDFRenderer renderer = (PDFRenderer)context.getRenderer(); + ImageRendered imageRend = (ImageRendered)image; + PDFDocument pdfDoc = (PDFDocument)context.getProperty( + PDFRendererContextConstants.PDF_DOCUMENT); + PDFResourceContext resContext = (PDFResourceContext)context.getProperty( + PDFRendererContextConstants.PDF_CONTEXT); + + PDFImage pdfimage = new ImageRenderedAdapter(imageRend, image.getInfo().getOriginalURI()); + PDFXObject xobj = pdfDoc.addImage(resContext, pdfimage); + + float x = (float)pos.getX() / 1000f; + float y = (float)pos.getY() / 1000f; + float w = (float)pos.getWidth() / 1000f; + float h = (float)pos.getHeight() / 1000f; + renderer.placeImage(x, y, w, h, xobj); + + return xobj; + } + + /** {@inheritDoc} */ + public int getPriority() { + return 300; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageRendered.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return FLAVORS; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerXML.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerXML.java new file mode 100644 index 000000000..ba47cce69 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerXML.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; +import java.util.Map; + +import org.w3c.dom.Document; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; + +import org.apache.fop.pdf.PDFXObject; +import org.apache.fop.render.RendererContext; + +/** + * PDFImageHandler implementation which handles XML-based images. + */ +public class PDFImageHandlerXML implements PDFImageHandler { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageFlavor.XML_DOM, + }; + + /** {@inheritDoc} */ + public PDFXObject generateImage(RendererContext context, Image image, + Point origin, Rectangle pos) + throws IOException { + PDFRenderer renderer = (PDFRenderer)context.getRenderer(); + ImageXMLDOM imgXML = (ImageXMLDOM)image; + Document doc = imgXML.getDocument(); + String ns = imgXML.getRootNamespace(); + Map foreignAttributes = (Map)context.getProperty( + PDFRendererContextConstants.FOREIGN_ATTRIBUTES); + renderer.renderDocument(doc, ns, pos, foreignAttributes); + return null; + } + + /** {@inheritDoc} */ + public int getPriority() { + return 400; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageXMLDOM.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return FLAVORS; + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index f4aa5ee90..3b28cc34c 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -21,6 +21,8 @@ package org.apache.fop.render.pdf; // Java import java.awt.Color; +import java.awt.Point; +import java.awt.Rectangle; import java.awt.color.ColorSpace; import java.awt.color.ICC_Profile; import java.awt.geom.AffineTransform; @@ -37,11 +39,13 @@ import java.util.Map; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; -import org.w3c.dom.Document; - import org.apache.commons.io.IOUtils; -import org.apache.commons.io.output.CountingOutputStream; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; import org.apache.xmlgraphics.xmp.Metadata; import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; @@ -68,14 +72,12 @@ import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; +import org.apache.fop.datatypes.URISpecification; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.extensions.xmp.XMPMetadata; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.Typeface; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.apache.fop.image.XMLImage; import org.apache.fop.pdf.PDFAMode; import org.apache.fop.pdf.PDFAction; import org.apache.fop.pdf.PDFAnnotList; @@ -1661,29 +1663,44 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ public void renderImage(Image image, Rectangle2D pos) { endTextObject(); String url = image.getURL(); - putImage(url, pos); + putImage(url, pos, image.getForeignAttributes()); } /** {@inheritDoc} */ protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { endTextObject(); - putImage(url, pos); + putImage(url, pos, foreignAttributes); } /** * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced. - * @param url URL of the bitmap + * @param uri URL of the bitmap * @param pos Position of the bitmap + * @deprecated Use {@link @putImage(String, Rectangle2D, Map)} instead. */ - protected void putImage(String url, Rectangle2D pos) { - url = ImageFactory.getURL(url); - PDFXObject xobject = pdfDoc.getXObject(url); + protected void putImage(String uri, Rectangle2D pos) { + putImage(uri, pos, null); + } + + /** + * Adds a PDF XObject (a bitmap or form) to the PDF that will later be referenced. + * @param uri URL of the bitmap + * @param pos Position of the bitmap + * @param foreignAttributes foreign attributes associated with the image + */ + protected void putImage(String uri, Rectangle2D pos, Map foreignAttributes) { + Rectangle posInt = new Rectangle( + (int)pos.getX(), + (int)pos.getY(), + (int)pos.getWidth(), + (int)pos.getHeight()); + + uri = URISpecification.getURL(uri); + PDFXObject xobject = pdfDoc.getXObject(uri); if (xobject != null) { float w = (float) pos.getWidth() / 1000f; float h = (float) pos.getHeight() / 1000f; @@ -1691,79 +1708,47 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { (float)pos.getY() / 1000f, w, h, xobject); return; } + Point origin = new Point(currentIPPosition, currentBPPosition); + int x = origin.x + posInt.x; + int y = origin.y + posInt.y; - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage fopimage = fact.getImage(url, userAgent); - if (fopimage == null) { - return; - } - if (!fopimage.load(FopImage.DIMENSIONS)) { - return; - } - String mime = fopimage.getMimeType(); - - //First check for a dynamically registered handler - PDFImageHandler handler = imageHandlerRegistry.getHandler(mime); - if (handler != null) { - PDFXObject xobj; - try { - xobj = handler.generateImage(fopimage, url, pdfDoc); - } catch (IOException ioe) { - log.error("I/O error while handling " + mime + " image", ioe); - return; - } - fact.releaseImage(url, userAgent); + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; + try { + ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); + info = manager.getImageInfo(uri, sessionContext); - float w = (float)pos.getWidth() / 1000f; - float h = (float)pos.getHeight() / 1000f; - placeImage((float) pos.getX() / 1000, - (float) pos.getY() / 1000, w, h, xobj); - } else if ("text/xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - - renderDocument(doc, ns, pos, null); - } else if ("image/svg+xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; - } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - - renderDocument(doc, ns, pos, null); - } else if ("image/eps".equals(mime)) { - FopPDFImage pdfimage = new FopPDFImage(fopimage, url); - PDFXObject xobj = pdfDoc.addImage(currentContext, pdfimage); - fact.releaseImage(url, userAgent); - float w = (float)pos.getWidth() / 1000f; - float h = (float)pos.getHeight() / 1000f; - placeImage((float) pos.getX() / 1000, - (float) pos.getY() / 1000, w, h, xobj); - } else if ("image/jpeg".equals(mime) || "image/tiff".equals(mime)) { - FopPDFImage pdfimage = new FopPDFImage(fopimage, url); - PDFXObject xobj = pdfDoc.addImage(currentContext, pdfimage); - fact.releaseImage(url, userAgent); - - float w = (float)pos.getWidth() / 1000f; - float h = (float)pos.getHeight() / 1000f; - placeImage((float) pos.getX() / 1000, - (float) pos.getY() / 1000, w, h, xobj); - } else { - if (!fopimage.load(FopImage.BITMAP)) { - return; + + Map hints = ImageUtil.getDefaultHints(sessionContext); + org.apache.xmlgraphics.image.loader.Image img = manager.getImage( + info, imageHandlerRegistry.getSupportedFlavors(), hints, sessionContext); + + //First check for a dynamically registered handler + PDFImageHandler handler = imageHandlerRegistry.getHandler(img.getClass()); + if (handler != null) { + if (log.isDebugEnabled()) { + log.debug("Using PDFImageHandler: " + handler.getClass().getName()); + } + try { + RendererContext context = createRendererContext( + x, y, posInt.width, posInt.height, foreignAttributes); + handler.generateImage(context, img, origin, posInt); + } catch (IOException ioe) { + log.error("I/O error while handling image: " + info, ioe); + return; + } + } else { + throw new UnsupportedOperationException( + "No PDFImageHandler available for image: " + + info + " (" + img.getClass().getName() + ")"); } - FopPDFImage pdfimage = new FopPDFImage(fopimage, url); - PDFXObject xobj = pdfDoc.addImage(currentContext, pdfimage); - fact.releaseImage(url, userAgent); - - float w = (float) pos.getWidth() / 1000f; - float h = (float) pos.getHeight() / 1000f; - placeImage((float) pos.getX() / 1000f, - (float) pos.getY() / 1000f, w, h, xobj); + } catch (ImageException ie) { + log.error("Error while processing image: " + + (info != null ? info.toString() : uri), ie); + } catch (IOException ioe) { + log.error("I/O error while processing image: " + + (info != null ? info.toString() : uri), ioe); } // output new data @@ -1782,7 +1767,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { * @param h height for image * @param xobj the image XObject */ - protected void placeImage(float x, float y, float w, float h, PDFXObject xobj) { + public void placeImage(float x, float y, float w, float h, PDFXObject xobj) { saveGraphicsState(); currentStream.add(format(w) + " 0 0 " + format(-h) + " " @@ -1792,10 +1777,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer { restoreGraphicsState(); } - /** - * {@inheritDoc} - * int, int, int, int, java.util.Map) - */ + /** {@inheritDoc} */ protected RendererContext createRendererContext(int x, int y, int width, int height, Map foreignAttributes) { RendererContext context = super.createRendererContext( diff --git a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java index 5c5894d3b..aec094e3b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java +++ b/src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java @@ -15,7 +15,7 @@ * limitations under the License. */ -/* $Id: $ */ +/* $Id$ */ package org.apache.fop.render.pdf; diff --git a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java index b0ae29067..cbc0a8ec9 100644 --- a/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFSVGHandler.java @@ -19,42 +19,40 @@ package org.apache.fop.render.pdf; +import java.awt.Color; +import java.awt.geom.AffineTransform; import java.io.IOException; import java.io.OutputStream; import java.util.Map; -import java.awt.Color; -import java.awt.geom.AffineTransform; import org.w3c.dom.Document; -import org.apache.fop.render.AbstractGenericSVGHandler; -import org.apache.fop.render.Renderer; -import org.apache.fop.render.RendererContext; -import org.apache.fop.render.RendererContextConstants; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.dom.svg.SVGDOMImplementation; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.util.SVGConstants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fonts.FontInfo; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFState; import org.apache.fop.pdf.PDFStream; -import org.apache.fop.pdf.PDFResourceContext; +import org.apache.fop.render.AbstractGenericSVGHandler; +import org.apache.fop.render.Renderer; +import org.apache.fop.render.RendererContext; +import org.apache.fop.render.RendererContextConstants; import org.apache.fop.svg.PDFAElementBridge; import org.apache.fop.svg.PDFBridgeContext; import org.apache.fop.svg.PDFGraphics2D; import org.apache.fop.svg.SVGUserAgent; import org.apache.fop.util.QName; -import org.apache.fop.fo.extensions.ExtensionElementMapping; -import org.apache.fop.fonts.FontInfo; - -// Commons-Logging -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.apache.avalon.framework.configuration.Configuration; - -import org.apache.batik.bridge.GVTBuilder; -import org.apache.batik.bridge.BridgeContext; -import org.apache.batik.dom.svg.SVGDOMImplementation; -import org.apache.batik.gvt.GraphicsNode; -import org.apache.batik.util.SVGConstants; /** * PDF XML handler for SVG (uses Apache Batik). @@ -154,10 +152,15 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler int xOffset = pdfInfo.currentXPosition; int yOffset = pdfInfo.currentYPosition; - final float deviceResolution = context.getUserAgent().getTargetResolution(); + FOUserAgent userAgent = context.getUserAgent(); + log.debug("Generating SVG at " + + userAgent.getTargetResolution() + + "dpi."); + final float deviceResolution = userAgent.getTargetResolution(); + log.debug("Generating SVG at " + deviceResolution + "dpi."); log.debug("Generating SVG at " + deviceResolution + "dpi."); - final float uaResolution = context.getUserAgent().getSourceResolution(); + final float uaResolution = userAgent.getSourceResolution(); SVGUserAgent ua = new SVGUserAgent(25.4f / uaResolution, new AffineTransform()); //Scale for higher resolution on-the-fly images from Batik @@ -176,6 +179,8 @@ public class PDFSVGHandler extends AbstractGenericSVGHandler BridgeContext ctx = new PDFBridgeContext(ua, (strokeText ? null : pdfInfo.fi), + userAgent.getFactory().getImageManager(), + userAgent.getImageSessionContext(), new AffineTransform()); GraphicsNode root; diff --git a/src/java/org/apache/fop/render/ps/ImageEncoderCCITTFax.java b/src/java/org/apache/fop/render/ps/ImageEncoderCCITTFax.java new file mode 100644 index 000000000..ab39966d4 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/ImageEncoderCCITTFax.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.ps; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.xmlgraphics.image.codec.tiff.TIFFImage; +import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; +import org.apache.xmlgraphics.ps.ImageEncoder; + +/** + * ImageEncoder implementation for CCITT encoded images. + */ +public class ImageEncoderCCITTFax implements ImageEncoder { + + private final ImageRawCCITTFax ccitt; + + /** + * Main constructor. + * @param ccitt the CCITT encoded image + */ + public ImageEncoderCCITTFax(ImageRawCCITTFax ccitt) { + this.ccitt = ccitt; + } + + /** {@inheritDoc} */ + public void writeTo(OutputStream out) throws IOException { + ccitt.writeTo(out); + } + + /** {@inheritDoc} */ + public String getImplicitFilter() { + PSDictionary dict = new PSDictionary(); + dict.put("/Columns", new Integer(ccitt.getSize().getWidthPx())); + int compression = ccitt.getCompression(); + switch (compression) { + case TIFFImage.COMP_FAX_G3_1D : + dict.put("/K", new Integer(0)); + break; + case TIFFImage.COMP_FAX_G3_2D : + dict.put("/K", new Integer(1)); + break; + case TIFFImage.COMP_FAX_G4_2D : + dict.put("/K", new Integer(-1)); + break; + default: + throw new IllegalStateException( + "Invalid compression scheme: " + compression); + } + + return dict.toString() + " /CCITTFaxDecode"; + } +}
\ No newline at end of file diff --git a/src/java/org/apache/fop/render/ps/ImageEncoderJPEG.java b/src/java/org/apache/fop/render/ps/ImageEncoderJPEG.java new file mode 100644 index 000000000..ef4b9f16c --- /dev/null +++ b/src/java/org/apache/fop/render/ps/ImageEncoderJPEG.java @@ -0,0 +1,51 @@ +/* + * 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 java.io.OutputStream; + +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; +import org.apache.xmlgraphics.ps.ImageEncoder; + +/** + * ImageEncoder implementation for JPEG images. + */ +public class ImageEncoderJPEG implements ImageEncoder { + private final ImageRawJPEG jpeg; + + /** + * Main constructor + * @param jpeg the JPEG image + */ + public ImageEncoderJPEG(ImageRawJPEG jpeg) { + this.jpeg = jpeg; + } + + /** {@inheritDoc} */ + public void writeTo(OutputStream out) throws IOException { + jpeg.writeTo(out); + } + + /** {@inheritDoc} */ + public String getImplicitFilter() { + return "<< >> /DCTDecode"; + } +}
\ No newline at end of file diff --git a/src/java/org/apache/fop/render/ps/PSGraphics2DAdapter.java b/src/java/org/apache/fop/render/ps/PSGraphics2DAdapter.java index 286bbca80..787af69a1 100644 --- a/src/java/org/apache/fop/render/ps/PSGraphics2DAdapter.java +++ b/src/java/org/apache/fop/render/ps/PSGraphics2DAdapter.java @@ -24,33 +24,43 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D;
import java.io.IOException;
-import org.apache.fop.render.Graphics2DAdapter;
-import org.apache.fop.render.Graphics2DImagePainter;
-import org.apache.fop.render.RendererContext;
+import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
import org.apache.xmlgraphics.java2d.ps.PSGraphics2D;
import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.fop.render.Graphics2DAdapter;
+import org.apache.fop.render.RendererContext;
+
/**
* Graphics2DAdapter implementation for PostScript.
*/
public class PSGraphics2DAdapter implements Graphics2DAdapter {
- private PSRenderer renderer;
+ private PSGenerator gen;
+ private boolean clip = true;
/**
* Main constructor
* @param renderer the Renderer instance to which this instance belongs
*/
public PSGraphics2DAdapter(PSRenderer renderer) {
- this.renderer = renderer;
+ this(renderer.gen, true);
+ }
+
+ /**
+ * Constructor for use without a PSRenderer instance.
+ * @param gen the PostScript generator
+ * @param clip true if the image should be clipped
+ */
+ public PSGraphics2DAdapter(PSGenerator gen, boolean clip) {
+ this.gen = gen;
+ this.clip = clip;
}
/** {@inheritDoc} */
public void paintImage(Graphics2DImagePainter painter,
RendererContext context,
int x, int y, int width, int height) throws IOException {
- PSGenerator gen = renderer.gen;
-
float fwidth = width / 1000f;
float fheight = height / 1000f;
float fx = x / 1000f;
@@ -66,10 +76,12 @@ public class PSGraphics2DAdapter implements Graphics2DAdapter { gen.commentln("%FOPBeginGraphics2D");
gen.saveGraphicsState();
- // Clip to the image area.
- gen.writeln("newpath");
- gen.defineRect(fx, fy, fwidth, fheight);
- gen.writeln("clip");
+ 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
diff --git a/src/java/org/apache/fop/render/ps/PSImageUtils.java b/src/java/org/apache/fop/render/ps/PSImageUtils.java index 0ab329077..004d5a22a 100644 --- a/src/java/org/apache/fop/render/ps/PSImageUtils.java +++ b/src/java/org/apache/fop/render/ps/PSImageUtils.java @@ -19,20 +19,15 @@ package org.apache.fop.render.ps; -import java.awt.Dimension; -import java.awt.geom.Rectangle2D; -import java.io.IOException; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.xmlgraphics.ps.PSGenerator; + import org.apache.fop.image.EPSImage; import org.apache.fop.image.FopImage; -import org.apache.fop.image.JpegImage; -import org.apache.xmlgraphics.ps.PSGenerator; -import org.apache.xmlgraphics.ps.PSResource; /** - * Utility code for rendering images in PostScript. + * Utility code for rendering images in PostScript. */ public class PSImageUtils extends org.apache.xmlgraphics.ps.PSImageUtils { @@ -40,93 +35,6 @@ public class PSImageUtils extends org.apache.xmlgraphics.ps.PSImageUtils { protected static Log log = LogFactory.getLog(PSImageUtils.class); /** - * Renders a bitmap image to PostScript. - * @param img image to render - * @param x x position - * @param y y position - * @param w width - * @param h height - * @param gen PS generator - * @throws IOException In case of an I/O problem while rendering the image - */ - public static void renderBitmapImage(FopImage img, - float x, float y, float w, float h, PSGenerator gen) - throws IOException { - boolean isJPEG = (img instanceof JpegImage && (gen.getPSLevel() >= 3)); - byte[] imgmap = convertImageToRawBitmapArray(img, isJPEG); - if (imgmap == null) { - gen.commentln("%Image data is not available: " + img); - return; //Image cannot be converted - } - - String imgDescription = img.getMimeType() + " " + img.getOriginalURI(); - Dimension imgDim = new Dimension(img.getWidth(), img.getHeight()); - Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h); - writeImage(imgmap, imgDim, imgDescription, targetRect, isJPEG, - img.getColorSpace(), gen); - } - - /** - * Renders a bitmap image (as form) to PostScript. - * @param img image to render - * @param form the form resource - * @param x x position - * @param y y position - * @param w width - * @param h height - * @param gen PS generator - * @throws IOException In case of an I/O problem while rendering the image - */ - public static void renderForm(FopImage img, PSResource form, - float x, float y, float w, float h, PSGenerator gen) - throws IOException { - Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h); - paintForm(form, targetRect, gen); - } - - /** - * Generates a form resource for a FopImage in PostScript. - * @param img image to render - * @param form the form resource - * @param gen PS generator - * @throws IOException In case of an I/O problem while rendering the image - */ - public static void generateFormResourceForImage(FopImage img, PSResource form, - PSGenerator gen) throws IOException { - boolean isJPEG = (img instanceof JpegImage && (gen.getPSLevel() >= 3)); - byte[] imgmap = convertImageToRawBitmapArray(img, isJPEG); - if (imgmap == null) { - gen.commentln("%Image data is not available: " + img); - return; //Image cannot be converted - } - - String imgDescription = img.getMimeType() + " " + img.getOriginalURI(); - Dimension imgDim = new Dimension(img.getWidth(), img.getHeight()); - writeReusableImage(imgmap, imgDim, form.getName(), imgDescription, isJPEG, - img.getColorSpace(), gen); - } - - private static byte[] convertImageToRawBitmapArray(FopImage img, boolean allowUndecodedJPEG) - throws IOException { - if (img instanceof JpegImage && allowUndecodedJPEG) { - if (!img.load(FopImage.ORIGINAL_DATA)) { - return null; - } - } else { - if (!img.load(FopImage.BITMAP)) { - return null; - } - } - byte[] imgmap; - if (img.getBitmapsSize() > 0) { - imgmap = img.getBitmaps(); - } else { - imgmap = img.getRessourceBytes(); - } - return imgmap; - } - - /** * Renders an EPS image to PostScript. * @param img EPS image to render * @param x x position @@ -134,6 +42,8 @@ public class PSImageUtils extends org.apache.xmlgraphics.ps.PSImageUtils { * @param w width * @param h height * @param gen PS generator + * @deprecated Use {@link #renderEPS(java.io.InputStream, String, java.awt.geom.Rectangle2D, + * java.awt.geom.Rectangle2D, PSGenerator)} instead */ public static void renderEPS(EPSImage img, float x, float y, float w, float h, diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index 7580bd0d5..21ae85259 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -24,6 +24,7 @@ import java.awt.Color; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.LineNumberReader; @@ -35,14 +36,28 @@ import java.util.Map; import javax.xml.transform.Source; -import org.w3c.dom.Document; - import org.apache.commons.io.IOUtils; 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.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.PSGenerator; +import org.apache.xmlgraphics.ps.PSImageUtils; import org.apache.xmlgraphics.ps.PSProcSets; import org.apache.xmlgraphics.ps.PSResource; import org.apache.xmlgraphics.ps.PSState; @@ -69,15 +84,12 @@ import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; import org.apache.fop.area.inline.WordArea; +import org.apache.fop.datatypes.URISpecification; import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.LazyFont; import org.apache.fop.fonts.Typeface; -import org.apache.fop.image.EPSImage; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.apache.fop.image.XMLImage; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.ImageAdapter; @@ -108,7 +120,8 @@ import org.apache.fop.util.CharUtilities; * @author <a href="mailto:fop-dev@xmlgraphics.apache.org">Apache FOP Development Team</a> * @version $Id$ */ -public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAdapter { +public class PSRenderer extends AbstractPathOrientedRenderer + implements ImageAdapter, PSSupportedFlavors { /** logging instance */ private static Log log = LogFactory.getLog(PSRenderer.class); @@ -377,58 +390,183 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } } + /** + * 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(); - uri = ImageFactory.getURL(uri); - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage fopimage = fact.getImage(uri, userAgent); - if (fopimage == null) { - return; - } - if (!fopimage.load(FopImage.DIMENSIONS)) { - return; + int x = currentIPPosition + (int)Math.round(pos.getX()); + int y = currentBPPosition + (int)Math.round(pos.getY()); + uri = URISpecification.getURL(uri); + if (log.isDebugEnabled()) { + log.debug("Handling image: " + uri); } - float x = (float)pos.getX() / 1000f; - x += currentIPPosition / 1000f; - float y = (float)pos.getY() / 1000f; - y += currentBPPosition / 1000f; - float w = (float)pos.getWidth() / 1000f; - float h = (float)pos.getHeight() / 1000f; + + ImageManager manager = getUserAgent().getFactory().getImageManager(); + ImageInfo info = null; try { - String mime = fopimage.getMimeType(); - if ("text/xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; + 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; + + if (isImageInlined(info)) { + if (log.isDebugEnabled()) { + log.debug("Image " + info + " is inlined"); } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - - renderDocument(doc, ns, pos, foreignAttributes); - } else if ("image/svg+xml".equals(mime)) { - if (!fopimage.load(FopImage.ORIGINAL_DATA)) { - return; + //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); + + //...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); } - Document doc = ((XMLImage) fopimage).getDocument(); - String ns = ((XMLImage) fopimage).getNameSpace(); - - renderDocument(doc, ns, pos, foreignAttributes); - } else if (fopimage instanceof EPSImage) { - PSImageUtils.renderEPS((EPSImage)fopimage, x, y, w, h, gen); } else { - if (isImageInlined(uri, fopimage)) { - PSImageUtils.renderBitmapImage(fopimage, x, y, w, h, gen); - } else { - PSResource form = getFormForImage(uri, fopimage); - PSImageUtils.renderForm(fopimage, form, x, y, w, h, gen); + 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); } + + } catch (ImageException ie) { + log.error("Error while processing image: " + + (info != null ? info.toString() : uri), ie); + } catch (FileNotFoundException fe) { + log.error(fe.getMessage()); } catch (IOException ioe) { handleIOTrouble(ioe); } } - protected PSResource getFormForImage(String uri, FopImage fopimage) { + 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 + * @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(); } @@ -439,11 +577,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } return form; } - - protected boolean isImageInlined(String uri, FopImage image) { - return !isOptimizeResources(); - } - + /** {@inheritDoc} */ public void paintImage(RenderedImage image, RendererContext context, int x, int y, int width, int height) throws IOException { diff --git a/src/java/org/apache/fop/render/ps/PSSupportedFlavors.java b/src/java/org/apache/fop/render/ps/PSSupportedFlavors.java new file mode 100644 index 000000000..8ccfa8e26 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSSupportedFlavors.java @@ -0,0 +1,67 @@ +/* + * 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 org.apache.xmlgraphics.image.loader.ImageFlavor; + +/** + * Defines the set of supported ImageFlavors for the PostScript renderer. + */ +public interface PSSupportedFlavors { + + /** The flavors supported inline with PostScript level 2. */ + ImageFlavor[] LEVEL_2_FLAVORS_INLINE = new ImageFlavor[] + {ImageFlavor.RAW_EPS, + ImageFlavor.RAW_CCITTFAX, + ImageFlavor.GRAPHICS2D, + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE, + ImageFlavor.XML_DOM}; + + /** The flavors supported inline with PostScript level 3 and higher. */ + ImageFlavor[] LEVEL_3_FLAVORS_INLINE = new ImageFlavor[] + {ImageFlavor.RAW_EPS, + ImageFlavor.RAW_JPEG, + ImageFlavor.RAW_CCITTFAX, + ImageFlavor.GRAPHICS2D, + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE, + ImageFlavor.XML_DOM}; + + /** The flavors supported as forms with PostScript level 2. */ + ImageFlavor[] LEVEL_2_FLAVORS_FORM = new ImageFlavor[] + {//ImageFlavor.RAW_EPS, + ImageFlavor.RAW_CCITTFAX, + ImageFlavor.GRAPHICS2D, + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE/*, + ImageFlavor.XML_DOM*/}; + + /** The flavors supported as forms with PostScript level 3 or higher. */ + ImageFlavor[] LEVEL_3_FLAVORS_FORM = new ImageFlavor[] + {//ImageFlavor.RAW_EPS, + ImageFlavor.RAW_JPEG, + ImageFlavor.RAW_CCITTFAX, + ImageFlavor.GRAPHICS2D, + ImageFlavor.BUFFERED_IMAGE, + ImageFlavor.RENDERED_IMAGE/*, + ImageFlavor.XML_DOM*/}; + +} diff --git a/src/java/org/apache/fop/render/ps/ResourceHandler.java b/src/java/org/apache/fop/render/ps/ResourceHandler.java index a0762e6e0..0dfb8029f 100644 --- a/src/java/org/apache/fop/render/ps/ResourceHandler.java +++ b/src/java/org/apache/fop/render/ps/ResourceHandler.java @@ -19,6 +19,8 @@ package org.apache.fop.render.ps; +import java.awt.geom.Dimension2D; +import java.awt.image.RenderedImage; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.InputStream; @@ -27,8 +29,25 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +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.dsc.DSCException; import org.apache.xmlgraphics.ps.dsc.DSCFilter; import org.apache.xmlgraphics.ps.dsc.DSCParser; @@ -50,8 +69,6 @@ import org.apache.xmlgraphics.ps.dsc.tools.DSCTools; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fonts.FontInfo; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; /** * This class is used when two-pass production is used to generate the PostScript file (setting @@ -59,7 +76,7 @@ import org.apache.fop.image.ImageFactory; * temporary file generated by the PSRenderer and adds all used fonts and images as resources * to the PostScript file. */ -public class ResourceHandler implements DSCParserConstants { +public class ResourceHandler implements DSCParserConstants, PSSupportedFlavors { /** * Rewrites the temporary PostScript file generated by PSRenderer adding all needed resources @@ -208,13 +225,129 @@ public class ResourceHandler implements DSCParserConstants { Iterator iter = formResources.values().iterator(); while (iter.hasNext()) { PSImageFormResource form = (PSImageFormResource)iter.next(); - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage image = fact.getImage(form.getImageURI(), userAgent); - if (image == null) { - throw new NullPointerException("Image not found: " + form.getImageURI()); + final String uri = form.getImageURI(); + + ImageManager manager = userAgent.getFactory().getImageManager(); + ImageInfo info = null; + try { + ImageSessionContext sessionContext = userAgent.getImageSessionContext(); + info = manager.getImageInfo(uri, sessionContext); + + 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"); + } + + }; + 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) { + throw new IOException("Error while generating form for image: " + ie.getMessage()); } - PSImageUtils.generateFormResourceForImage(image, form, gen); } } + private static FormGenerator createMissingForm(String formName, final Dimension2D dimensions) { + FormGenerator formGen = new FormGenerator(formName, null, dimensions) { + + protected void generatePaintProc(PSGenerator gen) throws IOException { + gen.writeln("0 setgray"); + gen.writeln("0 setlinewidth"); + String w = gen.formatDouble(dimensions.getWidth()); + String h = gen.formatDouble(dimensions.getHeight()); + gen.writeln(w + " " + h + " scale"); + gen.writeln("0 0 1 1 rectstroke"); + gen.writeln("newpath"); + gen.writeln("0 0 moveto"); + gen.writeln("1 1 lineto"); + gen.writeln("stroke"); + gen.writeln("newpath"); + gen.writeln("0 1 moveto"); + gen.writeln("1 0 lineto"); + gen.writeln("stroke"); + } + + }; + return formGen; + } + } diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java index eb50a3ea7..333e4ab95 100644 --- a/src/java/org/apache/fop/render/rtf/RTFHandler.java +++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java @@ -20,26 +20,33 @@ package org.apache.fop.render.rtf; // Java -import java.awt.color.ColorSpace; import java.awt.geom.Point2D; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.ComponentColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.PixelInterleavedSampleModel; -import java.awt.image.Raster; -import java.awt.image.SampleModel; -import java.awt.image.WritableRaster; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.Iterator; +import java.util.Map; -import org.apache.batik.dom.svg.SVGDOMImplementation; -import org.apache.commons.io.output.ByteArrayOutputStream; +import org.w3c.dom.Document; + +import org.xml.sax.SAXException; + +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.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.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; + import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.datatypes.LengthBase; @@ -79,9 +86,6 @@ import org.apache.fop.fo.pagination.SimplePageMaster; import org.apache.fop.fo.pagination.StaticContent; import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fonts.FontSetup; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.ImageFactory; -import org.apache.fop.image.XMLImage; import org.apache.fop.render.DefaultFontResolver; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfAfterContainer; import org.apache.fop.render.rtf.rtflib.rtfdoc.IRtfBeforeContainer; @@ -108,10 +112,6 @@ import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfTextrun; import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfListItem.RtfListItemLabel; import org.apache.fop.render.rtf.rtflib.tools.BuilderContext; import org.apache.fop.render.rtf.rtflib.tools.TableContext; -import org.apache.xmlgraphics.image.writer.ImageWriter; -import org.apache.xmlgraphics.image.writer.ImageWriterRegistry; -import org.w3c.dom.Document; -import org.xml.sax.SAXException; /** * RTF Handler: generates RTF output using the structure events from @@ -1104,24 +1104,18 @@ public class RTFHandler extends FOEventHandler { } try { - String url = eg.getURL(); + String uri = eg.getURL(); //set image data FOUserAgent userAgent = eg.getUserAgent(); - ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage fopimage = fact.getImage(url, userAgent); - if (fopimage == null) { - log.error("Image could not be found: " + url); + ImageManager manager = userAgent.getFactory().getImageManager(); + ImageInfo info = manager.getImageInfo(uri, userAgent.getImageSessionContext()); + if (info == null) { + log.error("Image could not be found: " + uri); return; } - if ("image/gif".equals(fopimage.getMimeType())) { - //GIF is not directly supported by RTF, so it must be converted to PNG - fopimage.load(FopImage.BITMAP); - } else { - fopimage.load(FopImage.ORIGINAL_DATA); - } - putGraphic(eg, fopimage); + putGraphic(eg, info); } catch (Exception e) { log.error("Error while handling an external-graphic: " + e.getMessage(), e); } @@ -1140,85 +1134,83 @@ public class RTFHandler extends FOEventHandler { Document doc = child.getDOMDocument(); String ns = child.getNamespaceURI(); - if (SVGDOMImplementation.SVG_NAMESPACE_URI.equals(ns)) { - // Build the image info. - FopImage.ImageInfo info = new FopImage.ImageInfo(); - info.mimeType = "image/svg+xml"; - info.str = SVGDOMImplementation.SVG_NAMESPACE_URI; - info.originalURI = ""; - info.data = doc; - - // Set the resolution to that of the FOUserAgent - FOUserAgent ua = ifo.getUserAgent(); - info.dpiHorizontal = 25.4f / ua.getSourcePixelUnitToMillimeter(); - info.dpiVertical = info.dpiHorizontal; - - // Set the image size to the size of the svg. - Point2D csize = new Point2D.Float(-1, -1); - Point2D intrinsicDimensions = child.getDimension(csize); - info.width = (int) intrinsicDimensions.getX(); - info.height = (int) intrinsicDimensions.getY(); - - FopImage fopImage = new XMLImage(info); - fopImage.load(FopImage.ORIGINAL_DATA); - - putGraphic(ifo, fopImage); - } else { - log.warn("The namespace " + ns - + " for instream-foreign-objects is not supported."); - } + ImageInfo info = new ImageInfo(null, null); + // Set the resolution to that of the FOUserAgent + FOUserAgent ua = ifo.getUserAgent(); + ImageSize size = new ImageSize(); + size.setResolution(ua.getSourceResolution()); + + // Set the image size to the size of the svg. + Point2D csize = new Point2D.Float(-1, -1); + Point2D intrinsicDimensions = child.getDimension(csize); + size.setSizeInMillipoints( + (int)Math.round(intrinsicDimensions.getX() * 1000), + (int)Math.round(intrinsicDimensions.getY() * 1000)); + size.calcPixelsFromSize(); + info.setSize(size); + + ImageXMLDOM image = new ImageXMLDOM(info, doc, ns); + FOUserAgent userAgent = ifo.getUserAgent(); + ImageManager manager = userAgent.getFactory().getImageManager(); + Image converted = manager.convertImage(image, FLAVORS); + putGraphic(ifo, converted); } catch (Exception e) { log.error("Error while handling an instream-foreign-object: " + e.getMessage(), e); } } - private BufferedImage createBufferedImageFromBitmaps(FopImage image) { - // TODO Hardcoded color and sample models, FIX ME! - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_sRGB), - new int[] {8, 8, 8}, - false, false, - ColorModel.OPAQUE, DataBuffer.TYPE_BYTE); - SampleModel sampleModel = new PixelInterleavedSampleModel( - DataBuffer.TYPE_BYTE, image.getWidth(), image.getHeight(), 3, image.getWidth() * 3, - new int[] {0, 1, 2}); - DataBuffer dbuf = new DataBufferByte(image.getBitmaps(), - image.getWidth() * image.getHeight() * 3); - - WritableRaster raster = Raster.createWritableRaster(sampleModel, - dbuf, null); - - // Combine the color model and raster into a buffered image - return new BufferedImage(cm, raster, false, null); + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + ImageFlavor.RAW_EMF, ImageFlavor.RAW_PNG, ImageFlavor.RAW_JPEG + }; + + /** + * Puts a graphic/image into the generated RTF file. + * @param abstractGraphic the graphic (external-graphic or instream-foreign-object) + * @param info the image info object + * @throws IOException In case of an I/O error + */ + private void putGraphic(AbstractGraphics abstractGraphic, ImageInfo info) + throws IOException { + try { + FOUserAgent userAgent = abstractGraphic.getUserAgent(); + ImageManager manager = userAgent.getFactory().getImageManager(); + ImageSessionContext sessionContext = userAgent.getImageSessionContext(); + Map hints = ImageUtil.getDefaultHints(sessionContext); + Image image = manager.getImage(info, FLAVORS, hints, sessionContext); + + putGraphic(abstractGraphic, image); + } catch (ImageException ie) { + log.error("Error while loading/processing image: " + info.getOriginalURI(), ie); + } } /** * Puts a graphic/image into the generated RTF file. * @param abstractGraphic the graphic (external-graphic or instream-foreign-object) - * @param fopImage the image + * @param image the image * @throws IOException In case of an I/O error */ - private void putGraphic(AbstractGraphics abstractGraphic, FopImage fopImage) + private void putGraphic(AbstractGraphics abstractGraphic, Image image) throws IOException { - byte[] rawData; - if ("image/svg+xml".equals(fopImage.getMimeType())) { - rawData = SVGConverter.convertToJPEG((XMLImage) fopImage); - } else if (fopImage.getRessourceBytes() != null) { - rawData = fopImage.getRessourceBytes(); - } else { - //TODO Revisit after the image library redesign!!! - //Convert the decoded bitmaps to a BufferedImage - BufferedImage bufImage = createBufferedImageFromBitmaps(fopImage); - ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor("image/png"); - ByteArrayOutputStream baout = new ByteArrayOutputStream(); - writer.writeImage(bufImage, baout); - rawData = baout.toByteArray(); + byte[] rawData = null; + + ImageInfo info = image.getInfo(); + + if (image instanceof ImageRawStream) { + ImageRawStream rawImage = (ImageRawStream)image; + InputStream in = rawImage.createInputStream(); + try { + rawData = IOUtils.toByteArray(in); + } finally { + IOUtils.closeQuietly(in); + } } + if (rawData == null) { log.warn(FONode.decorateWithContextInfo("Image could not be embedded: " - + fopImage.getOriginalURI(), abstractGraphic)); + + image, abstractGraphic)); return; } @@ -1229,7 +1221,9 @@ public class RTFHandler extends FOEventHandler { final RtfExternalGraphic rtfGraphic = c.getTextrun().newImage(); //set URL - rtfGraphic.setURL(fopImage.getOriginalURI()); + if (info.getOriginalURI() != null) { + rtfGraphic.setURL(info.getOriginalURI()); + } rtfGraphic.setImageData(rawData); //set scaling @@ -1240,7 +1234,7 @@ public class RTFHandler extends FOEventHandler { //get width int width = 0; if (abstractGraphic.getWidth().getEnum() == Constants.EN_AUTO) { - width = fopImage.getIntrinsicWidth(); + width = info.getSize().getWidthMpt(); } else { width = abstractGraphic.getWidth().getValue(); } @@ -1248,7 +1242,7 @@ public class RTFHandler extends FOEventHandler { //get height int height = 0; if (abstractGraphic.getWidth().getEnum() == Constants.EN_AUTO) { - height = fopImage.getIntrinsicHeight(); + height = info.getSize().getHeightMpt(); } else { height = abstractGraphic.getHeight().getValue(); } @@ -1257,7 +1251,7 @@ public class RTFHandler extends FOEventHandler { int contentwidth = 0; if (abstractGraphic.getContentWidth().getEnum() == Constants.EN_AUTO) { - contentwidth = fopImage.getIntrinsicWidth(); + contentwidth = info.getSize().getWidthMpt(); } else if (abstractGraphic.getContentWidth().getEnum() == Constants.EN_SCALE_TO_FIT) { contentwidth = width; @@ -1271,7 +1265,7 @@ public class RTFHandler extends FOEventHandler { if (abstractGraphic.getContentHeight().getEnum() == Constants.EN_AUTO) { - contentheight = fopImage.getIntrinsicHeight(); + contentheight = info.getSize().getHeightMpt(); } else if (abstractGraphic.getContentHeight().getEnum() == Constants.EN_SCALE_TO_FIT) { diff --git a/src/java/org/apache/fop/render/rtf/rtflib/rtfdoc/RtfExternalGraphic.java b/src/java/org/apache/fop/render/rtf/rtflib/rtfdoc/RtfExternalGraphic.java index a79679450..932198676 100644 --- a/src/java/org/apache/fop/render/rtf/rtflib/rtfdoc/RtfExternalGraphic.java +++ b/src/java/org/apache/fop/render/rtf/rtflib/rtfdoc/RtfExternalGraphic.java @@ -26,19 +26,17 @@ package org.apache.fop.render.rtf.rtflib.rtfdoc; * the FOP project. */ -import org.apache.commons.io.IOUtils; -import org.apache.fop.render.rtf.rtflib.tools.ImageConstants; -import org.apache.fop.render.rtf.rtflib.tools.ImageUtil; -//import org.apache.fop.render.rtf.rtflib.tools.jpeg.Encoder; -//import org.apache.fop.render.rtf.rtflib.tools.jpeg.JPEGException; - +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Writer; - -import java.io.File; -import java.net.URL; import java.net.MalformedURLException; +import java.net.URL; + +import org.apache.commons.io.IOUtils; + +import org.apache.fop.render.rtf.rtflib.tools.ImageConstants; +import org.apache.fop.render.rtf.rtflib.tools.ImageUtil; /** * Creates an RTF image from an external graphic file. @@ -342,13 +340,13 @@ public class RtfExternalGraphic extends RtfElement { } - if (url == null) { - throw new ExternalGraphicException("The attribute 'url' of " - + "<fo:external-graphic> is null."); + if (url == null && imagedata == null) { + throw new ExternalGraphicException( + "No image data is available (neither URL, nor in-memory)"); } String linkToRoot = System.getProperty("jfor_link_to_root"); - if (linkToRoot != null) { + if (url != null && linkToRoot != null) { writer.write("{\\field {\\* \\fldinst { INCLUDEPICTURE \""); writer.write(linkToRoot); File urlFile = new File(url.getFile()); @@ -380,7 +378,7 @@ public class RtfExternalGraphic extends RtfElement { } // Determine image file format - String file = url.getFile (); + String file = (url != null ? url.getFile() : "<unknown>"); imageformat = FormatBase.determineFormat(imagedata); if (imageformat != null) { imageformat = imageformat.convert(imageformat, imagedata); diff --git a/src/java/org/apache/fop/svg/PDFBridgeContext.java b/src/java/org/apache/fop/svg/PDFBridgeContext.java index 3ffad3335..9f235b6a9 100644 --- a/src/java/org/apache/fop/svg/PDFBridgeContext.java +++ b/src/java/org/apache/fop/svg/PDFBridgeContext.java @@ -26,6 +26,10 @@ import org.apache.batik.bridge.Bridge; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.DocumentLoader; import org.apache.batik.bridge.UserAgent; + +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + import org.apache.fop.fonts.FontInfo; /** @@ -36,6 +40,9 @@ public class PDFBridgeContext extends BridgeContext { /** The font list. */ private final FontInfo fontInfo; + private final ImageManager imageManager; + private final ImageSessionContext imageSessionContext; + private AffineTransform linkTransform; /** @@ -50,9 +57,13 @@ public class PDFBridgeContext extends BridgeContext { public PDFBridgeContext(UserAgent userAgent, DocumentLoader loader, FontInfo fontInfo, + ImageManager imageManager, + ImageSessionContext imageSessionContext, AffineTransform linkTransform) { super(userAgent, loader); this.fontInfo = fontInfo; + this.imageManager = imageManager; + this.imageSessionContext = imageSessionContext; this.linkTransform = linkTransform; } @@ -66,9 +77,13 @@ public class PDFBridgeContext extends BridgeContext { */ public PDFBridgeContext(UserAgent userAgent, FontInfo fontInfo, + ImageManager imageManager, + ImageSessionContext imageSessionContext, AffineTransform linkTransform) { super(userAgent); this.fontInfo = fontInfo; + this.imageManager = imageManager; + this.imageSessionContext = imageSessionContext; this.linkTransform = linkTransform; } @@ -78,10 +93,29 @@ public class PDFBridgeContext extends BridgeContext { * @param fontInfo the font list for the text painter, may be null * in which case text is painted as shapes */ - public PDFBridgeContext(UserAgent userAgent, FontInfo fontInfo) { - this(userAgent, fontInfo, null); + public PDFBridgeContext(UserAgent userAgent, + FontInfo fontInfo, + ImageManager imageManager, + ImageSessionContext imageSessionContext) { + this(userAgent, fontInfo, imageManager, imageSessionContext, null); } + /** + * Returns the ImageManager to be used by the ImageElementBridge. + * @return the image manager + */ + public ImageManager getImageManager() { + return this.imageManager; + } + + /** + * Returns the ImageSessionContext to be used by the ImageElementBridge. + * @return the image session context + */ + public ImageSessionContext getImageSessionContext() { + return this.imageSessionContext; + } + private void putPDFElementBridgeConditional(String className, String testFor) { try { Class.forName(testFor); @@ -136,7 +170,10 @@ public class PDFBridgeContext extends BridgeContext { //TODO There's no matching method in the super-class here public BridgeContext createBridgeContext() { return new PDFBridgeContext(getUserAgent(), getDocumentLoader(), - fontInfo, linkTransform); + fontInfo, + getImageManager(), + getImageSessionContext(), + linkTransform); } - + } diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index 2436e1a10..c3c336000 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -62,13 +62,17 @@ import org.apache.batik.ext.awt.RadialGradientPaint; import org.apache.batik.ext.awt.RenderingHintsKeyExt; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.PatternPaint; + +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; +import org.apache.xmlgraphics.java2d.AbstractGraphics2D; +import org.apache.xmlgraphics.java2d.GraphicContext; + import org.apache.fop.fonts.CIDFont; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontSetup; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.LazyFont; -import org.apache.fop.image.JpegImage; import org.apache.fop.pdf.BitmapImage; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFColor; @@ -76,6 +80,7 @@ import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFDeviceColorSpace; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFGState; +import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFImageXObject; import org.apache.fop.pdf.PDFLink; import org.apache.fop.pdf.PDFName; @@ -86,10 +91,8 @@ import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFState; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFXObject; -import org.apache.fop.render.pdf.FopPDFImage; +import org.apache.fop.render.pdf.ImageRawJPEGAdapter; import org.apache.fop.util.ColorExt; -import org.apache.xmlgraphics.java2d.AbstractGraphics2D; -import org.apache.xmlgraphics.java2d.GraphicContext; /** * PDF Graphics 2D. @@ -397,7 +400,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { * @param width the width to draw the image * @param height the height to draw the image */ - public void addJpegImage(JpegImage jpeg, float x, float y, + public void addJpegImage(ImageRawJPEG jpeg, float x, float y, float width, float height) { preparePainting(); // Need to include hash code as when invoked from FO you @@ -405,9 +408,9 @@ public class PDFGraphics2D extends AbstractGraphics2D { // count is not enough. String key = "__AddJPEG_" + hashCode() + "_" + jpegCount[0]; jpegCount[0]++; - FopPDFImage fopimage = new FopPDFImage(jpeg, key); + PDFImage pdfimage = new ImageRawJPEGAdapter(jpeg, key); PDFName imageName = this.pdfDoc.addImage(resourceContext, - fopimage).getName(); + pdfimage).getName(); AffineTransform at = getTransform(); double[] matrix = new double[6]; at.getMatrix(matrix); diff --git a/src/java/org/apache/fop/svg/PDFImageElementBridge.java b/src/java/org/apache/fop/svg/PDFImageElementBridge.java index 1545fc475..a2542e86f 100644 --- a/src/java/org/apache/fop/svg/PDFImageElementBridge.java +++ b/src/java/org/apache/fop/svg/PDFImageElementBridge.java @@ -19,24 +19,28 @@ package org.apache.fop.svg; -import org.apache.batik.bridge.SVGImageElementBridge; - -import java.awt.Shape; import java.awt.Graphics2D; +import java.awt.Shape; import java.awt.geom.Rectangle2D; -import java.io.BufferedInputStream; -import java.io.InputStream; import org.w3c.dom.Element; +import org.w3c.dom.svg.SVGDocument; import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.SVGImageElementBridge; import org.apache.batik.gvt.AbstractGraphicsNode; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.util.ParsedURL; -import org.apache.fop.image.JpegImage; -import org.apache.fop.image.FopImage; -import org.apache.fop.image.analyser.ImageReaderFactory; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.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.ImageRawJPEG; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; /** * Bridge class for the <image> element when jpeg images. @@ -50,6 +54,10 @@ public class PDFImageElementBridge extends SVGImageElementBridge { */ public PDFImageElementBridge() { } + private final ImageFlavor[] supportedFlavors = new ImageFlavor[] + {ImageFlavor.RAW_JPEG, + ImageFlavor.GRAPHICS2D, + ImageFlavor.XML_DOM}; /** * Create the raster image node. * THis checks if it is a jpeg file and creates a jpeg node @@ -60,51 +68,68 @@ public class PDFImageElementBridge extends SVGImageElementBridge { * @return a new graphics node */ protected GraphicsNode createImageGraphicsNode - (BridgeContext ctx, Element imageElement, ParsedURL purl) { + (BridgeContext ctx, Element imageElement, ParsedURL purl) { + PDFBridgeContext pdfCtx = (PDFBridgeContext)ctx; + + ImageManager manager = pdfCtx.getImageManager(); + ImageSessionContext sessionContext = pdfCtx.getImageSessionContext(); try { - InputStream is = purl.openStream(); - if (!is.markSupported()) { - is = new BufferedInputStream(is, 1024); - } + ImageInfo info = manager.getImageInfo(purl.toString(), sessionContext); + Image image = manager.getImage(info, supportedFlavors, sessionContext); - is.mark(3); - byte [] data = new byte[3]; - is.read(data); - is.reset(); - if ((data[0] == (byte)0xFF) - && (data[1] == (byte)0xD8) - && (data[2] == (byte)0xFF)) { - FopImage.ImageInfo ii = ImageReaderFactory.make - (purl.toString(), is, null); - JpegImage jpeg = new JpegImage(ii); - jpeg.load(FopImage.ORIGINAL_DATA); - PDFJpegNode node = new PDFJpegNode(jpeg, ctx, imageElement, purl); - - Rectangle2D imgBounds = getImageBounds(ctx, imageElement); - Rectangle2D bounds = node.getPrimitiveBounds(); - float [] vb = new float[4]; - vb[0] = 0; // x - vb[1] = 0; // y - vb[2] = (float) bounds.getWidth(); // width - vb[3] = (float) bounds.getHeight(); // height - - // handles the 'preserveAspectRatio', 'overflow' and 'clip' - // and sets the appropriate AffineTransform to the image node - initializeViewport(ctx, imageElement, node, vb, imgBounds); - return node; + AbstractGraphicsNode specializedNode = null; + if (image instanceof ImageXMLDOM) { + ImageXMLDOM xmlImage = (ImageXMLDOM)image; + if (xmlImage.getDocument() instanceof SVGDocument) { + return createSVGImageNode(ctx, imageElement, + (SVGDocument)xmlImage.getDocument()); + } else { + //Convert image to Graphics2D + image = manager.convertImage(xmlImage, + new ImageFlavor[] {ImageFlavor.GRAPHICS2D}); + } } - } catch (Exception ex) { - //TODO Handle this exception - } + if (image instanceof ImageRawJPEG) { + ImageRawJPEG jpegImage = (ImageRawJPEG)image; + specializedNode = new PDFJpegNode(jpegImage, ctx, imageElement, purl); + + } else if (image instanceof ImageGraphics2D) { + ImageGraphics2D g2dImage = (ImageGraphics2D)image; + specializedNode = new Graphics2DNode(g2dImage); + } else { + ctx.getUserAgent().displayError( + new ImageException("Cannot convert an image to a usable format: " + purl)); + } + + Rectangle2D imgBounds = getImageBounds(ctx, imageElement); + Rectangle2D bounds = specializedNode.getPrimitiveBounds(); + float [] vb = new float[4]; + vb[0] = 0; // x + vb[1] = 0; // y + vb[2] = (float) bounds.getWidth(); // width + vb[3] = (float) bounds.getHeight(); // height + // handles the 'preserveAspectRatio', 'overflow' and 'clip' + // and sets the appropriate AffineTransform to the image node + initializeViewport(ctx, imageElement, specializedNode, vb, imgBounds); + return specializedNode; + } catch (Exception e) { + ctx.getUserAgent().displayError(e); + } + return superCreateGraphicsNode(ctx, imageElement, purl); } /** + * Calls the superclass' createImageGraphicNode() method to create the normal GraphicsNode. + * @param ctx the bridge context + * @param imageElement the image element + * @param purl the parsed URL + * @return the newly created graphics node * @see org.apache.batik.bridge.SVGImageElementBridge#createGraphicsNode(BridgeContext, Element) */ protected GraphicsNode superCreateGraphicsNode - (BridgeContext ctx, Element imageElement, ParsedURL purl) { + (BridgeContext ctx, Element imageElement, ParsedURL purl) { return super.createImageGraphicsNode(ctx, imageElement, purl); } @@ -116,21 +141,21 @@ public class PDFImageElementBridge extends SVGImageElementBridge { */ public class PDFJpegNode extends AbstractGraphicsNode { - private JpegImage jpeg; + private ImageRawJPEG jpeg; private BridgeContext ctx; private Element imageElement; private ParsedURL purl; private GraphicsNode origGraphicsNode = null; /** - * Create a new pdf jpeg node for drawing jpeg images + * Create a new PDF JPEG node for drawing JPEG images * into pdf graphics. - * @param j the jpeg image + * @param j the JPEG image * @param ctx the bridge context * @param imageElement the SVG image element * @param purl the URL to the image */ - public PDFJpegNode(JpegImage j, BridgeContext ctx, + public PDFJpegNode(ImageRawJPEG j, BridgeContext ctx, Element imageElement, ParsedURL purl) { this.jpeg = j; this.ctx = ctx; @@ -138,32 +163,23 @@ public class PDFImageElementBridge extends SVGImageElementBridge { this.purl = purl; } - /** - * Get the outline of this image. - * @return the outline shape which is the primitive bounds - */ + /** {@inheritDoc} */ public Shape getOutline() { return getPrimitiveBounds(); } - /** - * Paint this jpeg image. - * As this is used for inserting jpeg into pdf - * it adds the jpeg image to the PDFGraphics2D. - * @param g2d the graphics to draw the image on - */ + /** {@inheritDoc} */ public void primitivePaint(Graphics2D g2d) { if (g2d instanceof PDFGraphics2D) { PDFGraphics2D pdfg = (PDFGraphics2D) g2d; float x = 0; float y = 0; try { - float width = jpeg.getWidth(); - float height = jpeg.getHeight(); + float width = jpeg.getSize().getWidthPx(); + float height = jpeg.getSize().getHeightPx(); pdfg.addJpegImage(jpeg, x, y, width, height); } catch (Exception e) { - //TODO Handle this exception properly - e.printStackTrace(); + ctx.getUserAgent().displayError(e); } } else { // Not going directly into PDF so use @@ -179,40 +195,71 @@ public class PDFImageElementBridge extends SVGImageElementBridge { } } - /** - * Get the geometrix bounds of the image. - * @return the primitive bounds - */ + /** {@inheritDoc} */ public Rectangle2D getGeometryBounds() { return getPrimitiveBounds(); } - /** - * Get the primitive bounds of this bridge element. - * @return the bounds of the jpeg image - */ + /** {@inheritDoc} */ public Rectangle2D getPrimitiveBounds() { - try { - return new Rectangle2D.Double(0, 0, jpeg.getWidth(), - jpeg.getHeight()); - } catch (Exception e) { - //TODO Handle this exception properly - e.printStackTrace(); - } - return null; + return new Rectangle2D.Double(0, 0, + jpeg.getSize().getWidthPx(), + jpeg.getSize().getHeightPx()); + } + + /** {@inheritDoc} */ + public Rectangle2D getSensitiveBounds() { + //No interactive features, just return primitive bounds + return getPrimitiveBounds(); } + } + + /** + * A node that holds a Graphics2D image. + */ + public class Graphics2DNode extends AbstractGraphicsNode { + + private ImageGraphics2D image; + /** - * Returns the bounds of the sensitive area covered by this node, - * This includes the stroked area but does not include the effects - * of clipping, masking or filtering. - * @return the bounds of the sensitive area + * Create a new Graphics2D node. + * @param g2d the Graphics2D image */ + public Graphics2DNode(ImageGraphics2D g2d) { + this.image = g2d; + } + + /** {@inheritDoc} */ + public Shape getOutline() { + return getPrimitiveBounds(); + } + + /** {@inheritDoc} */ + public void primitivePaint(Graphics2D g2d) { + int width = image.getSize().getWidthPx(); + int height = image.getSize().getHeightPx(); + Rectangle2D area = new Rectangle2D.Double(0, 0, width, height); + image.getGraphics2DImagePainter().paint(g2d, area); + } + + /** {@inheritDoc} */ + public Rectangle2D getGeometryBounds() { + return getPrimitiveBounds(); + } + + /** {@inheritDoc} */ + public Rectangle2D getPrimitiveBounds() { + return new Rectangle2D.Double(0, 0, + image.getSize().getWidthPx(), + image.getSize().getHeightPx()); + } + + /** {@inheritDoc} */ public Rectangle2D getSensitiveBounds() { //No interactive features, just return primitive bounds return getPrimitiveBounds(); } } - } diff --git a/src/java/org/apache/fop/svg/PDFTranscoder.java b/src/java/org/apache/fop/svg/PDFTranscoder.java index 417b82097..281df1b1d 100644 --- a/src/java/org/apache/fop/svg/PDFTranscoder.java +++ b/src/java/org/apache/fop/svg/PDFTranscoder.java @@ -23,6 +23,10 @@ import java.awt.Color; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.InputStream; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.w3c.dom.svg.SVGLength; @@ -41,6 +45,12 @@ import org.apache.batik.transcoder.TranscodingHints; import org.apache.batik.transcoder.image.ImageTranscoder; import org.apache.batik.transcoder.keys.BooleanKey; import org.apache.batik.transcoder.keys.FloatKey; +import org.apache.batik.util.ParsedURL; + +import org.apache.xmlgraphics.image.loader.ImageContext; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; import org.apache.fop.Version; import org.apache.fop.fonts.FontInfo; @@ -99,6 +109,9 @@ public class PDFTranscoder extends AbstractFOPTranscoder /** Graphics2D instance that is used to paint to */ protected PDFDocumentGraphics2D graphics = null; + private ImageManager imageManager; + private ImageSessionContext imageSessionContext; + /** * Constructs a new <tt>PDFTranscoder</tt>. */ @@ -141,6 +154,11 @@ public class PDFTranscoder extends AbstractFOPTranscoder graphics.getPDFDocument().getInfo().setProducer("Apache FOP Version " + Version.getVersion() + ": PDF Transcoder for Batik"); + if (hints.containsKey(KEY_DEVICE_RESOLUTION)) { + graphics.setDeviceDPI(((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue()); + } + + setupImageInfrastructure(uri); try { Configuration effCfg = this.cfg; @@ -196,9 +214,6 @@ public class PDFTranscoder extends AbstractFOPTranscoder //int h = (int)(height + 0.5); try { - if (hints.containsKey(KEY_DEVICE_RESOLUTION)) { - graphics.setDeviceDPI(((Float)hints.get(KEY_DEVICE_RESOLUTION)).floatValue()); - } OutputStream out = output.getOutputStream(); if (!(out instanceof BufferedOutputStream)) { out = new BufferedOutputStream(out); @@ -227,6 +242,39 @@ public class PDFTranscoder extends AbstractFOPTranscoder } } + private void setupImageInfrastructure(final String baseURI) { + final ImageContext imageContext = new ImageContext() { + public float getSourceResolution() { + return 25.4f / userAgent.getPixelUnitToMillimeter(); + } + }; + this.imageManager = new ImageManager(imageContext); + this.imageSessionContext = new AbstractImageSessionContext() { + + public ImageContext getParentContext() { + return imageContext; + } + + public float getTargetResolution() { + return graphics.getDeviceDPI(); + } + + public Source resolveURI(String uri) { + System.out.println("resolve " + uri); + try { + ParsedURL url = new ParsedURL(baseURI, uri); + InputStream in = url.openStream(); + StreamSource source = new StreamSource(in, url.toString()); + return source; + } catch (IOException ioe) { + userAgent.displayError(ioe); + return null; + } + } + + }; + } + /** {@inheritDoc} */ protected BridgeContext createBridgeContext() { //For compatibility with Batik 1.6 @@ -239,7 +287,8 @@ public class PDFTranscoder extends AbstractFOPTranscoder if (isTextStroked()) { fontInfo = null; } - BridgeContext ctx = new PDFBridgeContext(userAgent, fontInfo); + BridgeContext ctx = new PDFBridgeContext(userAgent, fontInfo, + this.imageManager, this.imageSessionContext); return ctx; } diff --git a/src/java/org/apache/fop/util/dijkstra/DefaultEdgeDirectory.java b/src/java/org/apache/fop/util/dijkstra/DefaultEdgeDirectory.java new file mode 100644 index 000000000..381b356a2 --- /dev/null +++ b/src/java/org/apache/fop/util/dijkstra/DefaultEdgeDirectory.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.util.dijkstra; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +/** + * Default implementation of an edge directory for the {@link DijkstraAlgorithm}. + */ +public class DefaultEdgeDirectory implements EdgeDirectory { + + /** The directory of edges */ + private Map edges = new java.util.HashMap(); + //Map<Vertex,Map<Vertex,Edge>> + + /** + * Adds a new edge between two vertices. + * @param edge the new edge + */ + public void addEdge(Edge edge) { + Map directEdges = (Map)edges.get(edge.getStart()); + if (directEdges == null) { + directEdges = new java.util.HashMap(); + edges.put(edge.getStart(), directEdges); + } + directEdges.put(edge.getEnd(), edge); + } + + /** {@inheritDoc} */ + public int getPenalty(Vertex start, Vertex end) { + Map edgeMap = (Map)edges.get(start); + if (edgeMap != null) { + Edge route = (Edge)edgeMap.get(end); + if (route != null) { + int penalty = route.getPenalty(); + if (penalty < 0) { + throw new IllegalStateException("Penalty must not be negative"); + } + return penalty; + } + } + return 0; + } + + /** {@inheritDoc} */ + public Iterator getDestinations(Vertex origin) { + Map directRoutes = (Map)edges.get(origin); + if (directRoutes != null) { + Iterator iter = directRoutes.keySet().iterator(); + return iter; + } + return Collections.EMPTY_LIST.iterator(); + } + + /** + * Returns an iterator over all edges with the given origin. + * @param origin the origin + * @return an iterator over Edge instances + */ + public Iterator getEdges(Vertex origin) { + Map directRoutes = (Map)edges.get(origin); + if (directRoutes != null) { + Iterator iter = directRoutes.values().iterator(); + return iter; + } + return Collections.EMPTY_LIST.iterator(); + } + + /** + * Returns the best edge (the edge with the lowest penalty) between two given vertices. + * @param start the start vertex + * @param end the end vertex + * @return the best vertex or null if none is found + */ + public Edge getBestEdge(Vertex start, Vertex end) { + Edge best = null; + Iterator iter = getEdges(start); + while (iter.hasNext()) { + Edge edge = (Edge)iter.next(); + if (edge.getEnd().equals(end)) { + if (best == null || edge.getPenalty() < best.getPenalty()) { + best = edge; + } + } + } + return best; + } + + +} diff --git a/src/java/org/apache/fop/util/dijkstra/DijkstraAlgorithm.java b/src/java/org/apache/fop/util/dijkstra/DijkstraAlgorithm.java new file mode 100644 index 000000000..a5b2fe202 --- /dev/null +++ b/src/java/org/apache/fop/util/dijkstra/DijkstraAlgorithm.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.util.dijkstra; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * This is an implementation of Dijkstra's algorithm to find the shortest path for a directed + * graph with non-negative edge weights. + * @see <a href="http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">WikiPedia on Dijkstra's + * Algorithm</a> + */ +public class DijkstraAlgorithm { + + /** Infinity value for distances. */ + public static final int INFINITE = Integer.MAX_VALUE; + + /** Compares penalties between two possible destinations. */ + private final Comparator penaltyComparator = new Comparator() { + public int compare(Object left, Object right) { + int leftPenalty = getLowestPenalty((Vertex)left); + int rightPenalty = getLowestPenalty((Vertex)right); + if (leftPenalty < rightPenalty) { + return -1; + } else if (leftPenalty == rightPenalty) { + return ((Comparable)left).compareTo(right); + } else { + return 1; + } + } + }; + + /** The directory of edges */ + private EdgeDirectory edgeDirectory; + + /** The priority queue for all vertices under inspection, ordered by penalties/distances. */ + private TreeSet priorityQueue = new TreeSet(penaltyComparator); + //Set<Vertex> + + /** The set of vertices for which the lowest penalty has been found. */ + private Set finishedVertices = new java.util.HashSet(); + //Set<Vertex> + + /** The currently known lowest penalties for all vertices. */ + private Map lowestPenalties = new java.util.HashMap(); + //Map<Vertex,Integer> + + /** Map of all predecessors in the spanning tree of best routes. */ + private Map predecessors = new java.util.HashMap(); + //Map<Vertex,Vertex> + + /** + * Main Constructor. + * @param edgeDirectory the edge directory this instance should work on + */ + public DijkstraAlgorithm(EdgeDirectory edgeDirectory) { + this.edgeDirectory = edgeDirectory; + } + + /** + * Returns the penalty between two vertices. + * @param start the start vertex + * @param end the end vertex + * @return the penalty between two vertices, or 0 if no single edge between the two vertices + * exists. + */ + protected int getPenalty(Vertex start, Vertex end) { + return this.edgeDirectory.getPenalty(start, end); + } + + /** + * Returns an iterator over all valid destinations for a given vertex. + * @param origin the origin from which to search for destinations + * @return the iterator over all valid destinations for a given vertex + */ + protected Iterator getDestinations(Vertex origin) { + return this.edgeDirectory.getDestinations(origin); + } + + private void reset() { + finishedVertices.clear(); + priorityQueue.clear(); + + lowestPenalties.clear(); + predecessors.clear(); + } + + /** + * Run Dijkstra's shortest path algorithm. After this method is finished you can use + * {@link #getPredecessor(Vertex)} to reconstruct the best/shortest path starting from the + * destination backwards. + * @param start the starting vertex + * @param destination the destination vertex. + */ + public void execute(Vertex start, Vertex destination) { + if (start == null || destination == null) { + throw new NullPointerException("start and destination may not be null"); + } + + reset(); + setShortestDistance(start, 0); + priorityQueue.add(start); + + // the current node + Vertex u; + + // extract the vertex with the shortest distance + while (priorityQueue.size() > 0) { + u = (Vertex)priorityQueue.first(); + priorityQueue.remove(u); + + if (destination.equals(u)) { + //Destination reached + break; + } + + finishedVertices.add(u); + relax(u); + } + } + + /** + * Compute new lowest penalties for neighboring vertices. Update the lowest penalties and the + * predecessor map if a better solution is found. + * @param u the vertex to process + */ + private void relax(Vertex u) { + Iterator iter = getDestinations(u); + while (iter.hasNext()) { + Vertex v = (Vertex)iter.next(); + // skip node already settled + if (isFinished(v)) { + continue; + } + + int shortDist = getLowestPenalty(u) + getPenalty(u, v); + + if (shortDist < getLowestPenalty(v)) { + // assign new shortest distance and mark unsettled + setShortestDistance(v, shortDist); + + // assign predecessor in shortest path + setPredecessor(v, u); + } + } + } + + private void setPredecessor(Vertex a, Vertex b) { + predecessors.put(a, b); + } + + /** + * Indicates whether a shortest route to a vertex has been found. + * @param v the vertex + * @return true if the shortest route to this vertex has been found. + */ + private boolean isFinished(Vertex v) { + return finishedVertices.contains(v); + } + + private void setShortestDistance(Vertex vertex, int distance) { + //Remove so it is inserted at the right position after the lowest penalty changes for this + //vertex. + priorityQueue.remove(vertex); + + //Update the lowest penalty. + lowestPenalties.put(vertex, new Integer(distance)); + + //Insert the vertex again at the new position based on the lowest penalty + priorityQueue.add(vertex); + } + + /** + * Returns the lowest penalty from the start point to a given vertex. + * @param vertex the vertex + * @return the lowest penalty or {@link DijkstraAlgorithm#INFINITE} if there is no route to + * the destination. + */ + public int getLowestPenalty(Vertex vertex) { + Integer d = ((Integer)lowestPenalties.get(vertex)); + return (d == null) ? INFINITE : d.intValue(); + } + + /** + * Returns the vertex's predecessor on the shortest path. + * @param vertex the vertex for which to find the predecessor + * @return the vertex's predecessor on the shortest path, or + * <code>null</code> if there is no route to the destination. + */ + public Vertex getPredecessor(Vertex vertex) { + return (Vertex)predecessors.get(vertex); + } + +} diff --git a/src/java/org/apache/fop/util/dijkstra/Edge.java b/src/java/org/apache/fop/util/dijkstra/Edge.java new file mode 100644 index 000000000..f709b9e26 --- /dev/null +++ b/src/java/org/apache/fop/util/dijkstra/Edge.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.util.dijkstra; + +/** + * Represents an edge (or direct route between two points) for the {@link DijkstraAlgorithm}. + * Implement this class to hold the start and end vertex for an edge and implement the + * <code>getPenalty()</code> method. + */ +public interface Edge { + + /** + * Returns the start vertex of the edge. + * @return the start vertex + */ + Vertex getStart(); + + /** + * Returns the end vertex of the edge. + * @return the end vertex + */ + Vertex getEnd(); + + /** + * Returns the penalty (or distance) for this edge. + * @return the penalty value (must be non-negative) + */ + int getPenalty(); + +} diff --git a/src/java/org/apache/fop/util/dijkstra/EdgeDirectory.java b/src/java/org/apache/fop/util/dijkstra/EdgeDirectory.java new file mode 100644 index 000000000..6a2dc848b --- /dev/null +++ b/src/java/org/apache/fop/util/dijkstra/EdgeDirectory.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.util.dijkstra; + +import java.util.Iterator; + +/** + * Represents a directory of edges for use by the {@link DijkstraAlgorithm}. + */ +public interface EdgeDirectory { + + /** + * Returns the penalty between two vertices. + * @param start the start vertex + * @param end the end vertex + * @return the penalty between two vertices, or 0 if no single edge between the two vertices + * exists. + */ + int getPenalty(Vertex start, Vertex end); + + /** + * Returns an iterator over all valid destinations for a given vertex. + * @param origin the origin from which to search for destinations + * @return the iterator over all valid destinations for a given vertex + */ + Iterator getDestinations(Vertex origin); + +}
\ No newline at end of file diff --git a/src/java/org/apache/fop/util/dijkstra/Vertex.java b/src/java/org/apache/fop/util/dijkstra/Vertex.java new file mode 100644 index 000000000..40c41b0b7 --- /dev/null +++ b/src/java/org/apache/fop/util/dijkstra/Vertex.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.util.dijkstra; + +/** + * Represents a vertex to be used by {@link DijkstraAlgorithm}. If you want to represent a city, + * you can do "public class City implements Vertex". The purpose of this interface is to make + * sure the Vertex implementation implements the Comparable interface so the sorting order is + * well-defined even when two vertices have the same penalty/distance from an origin point. + * Therefore, make sure you implement the <code>compareTo(Object)</code> and + * <code>equals(Object)</code> methods. + */ +public interface Vertex extends Comparable { + +} |