diff options
-rw-r--r-- | lib/xmlgraphics-commons-1.1.jar | bin | 301249 -> 0 bytes | |||
-rw-r--r-- | lib/xmlgraphics-commons-1.2svn.jar | bin | 0 -> 343511 bytes | |||
-rw-r--r-- | src/foschema/fop-configuration.xsd | 29 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/ps/PSFontUtils.java | 160 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/ps/PSImageFormResource.java | 60 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/ps/PSImageUtils.java | 82 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/ps/PSRenderer.java | 239 | ||||
-rw-r--r-- | src/java/org/apache/fop/render/ps/ResourceHandler.java | 203 | ||||
-rw-r--r-- | status.xml | 4 |
9 files changed, 672 insertions, 105 deletions
diff --git a/lib/xmlgraphics-commons-1.1.jar b/lib/xmlgraphics-commons-1.1.jar Binary files differdeleted file mode 100644 index 7fd9648b6..000000000 --- a/lib/xmlgraphics-commons-1.1.jar +++ /dev/null diff --git a/lib/xmlgraphics-commons-1.2svn.jar b/lib/xmlgraphics-commons-1.2svn.jar Binary files differnew file mode 100644 index 000000000..becb45ad5 --- /dev/null +++ b/lib/xmlgraphics-commons-1.2svn.jar diff --git a/src/foschema/fop-configuration.xsd b/src/foschema/fop-configuration.xsd index ad959f947..c4d9ac47f 100644 --- a/src/foschema/fop-configuration.xsd +++ b/src/foschema/fop-configuration.xsd @@ -116,18 +116,33 @@ <xsd:documentation>Configuration elements used by the PostScript renderer, MIME type application/postscript</xsd:documentation> </xsd:annotation> - <xsd:element name="auto-rotate-landscape"> + <xsd:element name="auto-rotate-landscape" type="xsd:boolean" default="false" minOccurs="0"> <xsd:annotation> - <xsd:documentation>auto-rotate-landscape is used by the PostScript renderer, - MIME type application/postscript.</xsd:documentation> + <xsd:documentation>When set to "true" a landscape page is automatically + rotated and specified as a landscape page in PostScript.</xsd:documentation> + </xsd:annotation> + </xsd:element> + <xsd:element name="language-level" default="3" minOccurs="0"> + <xsd:annotation> + <xsd:documentation>Specifies the PostScript language level to use when + generating PostScript code. + language-level is used by the PostScript renderer, + MIME type application/postscript.</xsd:documentation> </xsd:annotation> <xsd:simpleType> - <xsd:restriction base="xsd:string"> - <xsd:enumeration value="false"/> - <xsd:enumeration value="true"/> + <xsd:restriction base="xsd:positiveInteger"> + <xsd:enumeration value="2"/> + <xsd:enumeration value="3"/> </xsd:restriction> </xsd:simpleType> </xsd:element> + <xsd:element name="optimize-resources" type="xsd:boolean" default="false" minOccurs="0"> + <xsd:annotation> + <xsd:documentation>When set to "true" PostScript resources are optimized by making a + second pass over the PostScript file (rewriting it). Optimized means that no duplicate + images are written to the stream and only used fonts are added to the PostScript file.</xsd:documentation> + </xsd:annotation> + </xsd:element> </xsd:sequence> <xsd:sequence> <xsd:annotation> @@ -193,7 +208,7 @@ <xsd:sequence> <xsd:element name="font-triplet" type="fontTripletType" maxOccurs="unbounded"/> </xsd:sequence> - <xsd:attribute name="metrics-url" type="xsd:anyURI" use="required"/> + <xsd:attribute name="metrics-url" type="xsd:anyURI" use="optional"/> <xsd:attribute name="embed-url" type="xsd:anyURI" use="optional"/> <xsd:attribute name="kerning" use="optional" default="no"> <xsd:simpleType> diff --git a/src/java/org/apache/fop/render/ps/PSFontUtils.java b/src/java/org/apache/fop/render/ps/PSFontUtils.java index 5ece1fae9..c568fe826 100644 --- a/src/java/org/apache/fop/render/ps/PSFontUtils.java +++ b/src/java/org/apache/fop/render/ps/PSFontUtils.java @@ -29,6 +29,8 @@ import java.util.Map; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.fop.fonts.CustomFont; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; @@ -38,12 +40,16 @@ import org.apache.fop.fonts.Typeface; import org.apache.xmlgraphics.ps.DSCConstants; import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.xmlgraphics.ps.PSResource; +import org.apache.xmlgraphics.ps.dsc.ResourceTracker; /** * Utility code for font handling in PostScript. */ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { + /** logging instance */ + protected static Log log = LogFactory.getLog(PSFontUtils.class); + /** * Generates the PostScript code for the font dictionary. * @param gen PostScript generator to use for output @@ -53,59 +59,41 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { */ public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo) throws IOException { + return writeFontDict(gen, fontInfo, fontInfo.getFonts()); + } + + /** + * Generates the PostScript code for the font dictionary. + * @param gen PostScript generator to use for output + * @param fontInfo available fonts + * @param fonts the set of fonts to work with + * @return a Map of PSResource instances representing all defined fonts (key: font key) + * @throws IOException in case of an I/O problem + */ + public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, Map fonts) + throws IOException { gen.commentln("%FOPBeginFontDict"); - gen.writeln("/FOPFonts 100 dict dup begin"); - // write("/gfF1{/Helvetica findfont} bd"); - // write("/gfF3{/Helvetica-Bold findfont} bd"); - Map fonts = fontInfo.getFonts(); Map fontResources = new java.util.HashMap(); Iterator iter = fonts.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); - Typeface tf = (Typeface)fonts.get(key); - if (tf instanceof LazyFont) { - tf = ((LazyFont)tf).getRealFont(); - } - if (tf == null) { - //This is to avoid an NPE if a malconfigured font is in the configuration but not - //used in the document. If it were used, we wouldn't get this far. - String fallbackKey = fontInfo.getInternalFontKey(Font.DEFAULT_FONT); - tf = (Typeface)fonts.get(fallbackKey); - } + Typeface tf = getTypeFace(fontInfo, fonts, key); PSResource fontRes = new PSResource("font", tf.getFontName()); fontResources.put(key, fontRes); - boolean embeddedFont = false; - if (FontType.TYPE1 == tf.getFontType()) { - if (tf instanceof CustomFont) { - CustomFont cf = (CustomFont)tf; - InputStream in = getInputStreamOnFont(gen, cf); - if (in != null) { - gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, - fontRes); - embedType1Font(gen, in); - gen.writeDSCComment(DSCConstants.END_RESOURCE); - gen.notifyResourceUsage(fontRes, false); - embeddedFont = true; - } - } - } - if (!embeddedFont) { - gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); - //Resource usage shall be handled by renderer - //gen.notifyResourceUsage(fontRes, true); - } - gen.commentln("%FOPBeginFontKey: " + key); - gen.writeln("/" + key + " /" + tf.getFontName() + " def"); - gen.commentln("%FOPEndFontKey"); + embedFont(gen, tf, fontRes); } - gen.writeln("end def"); gen.commentln("%FOPEndFontDict"); + reencodeFonts(gen, fonts); + return fontResources; + } + + private static void reencodeFonts(PSGenerator gen, Map fonts) throws IOException { gen.commentln("%FOPBeginFontReencode"); defineWinAnsiEncoding(gen); //Rewrite font encodings - iter = fonts.keySet().iterator(); + Iterator iter = fonts.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); Typeface fm = (Typeface)fonts.get(key); @@ -115,25 +103,72 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { //ignore (ZapfDingbats and Symbol run through here //TODO: ZapfDingbats and Symbol should get getEncoding() fixed! } else if ("WinAnsiEncoding".equals(fm.getEncoding())) { - gen.writeln("/" + fm.getFontName() + " findfont"); - gen.writeln("dup length dict begin"); - gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall"); - gen.writeln(" /Encoding " + fm.getEncoding() + " def"); - gen.writeln(" currentdict"); - gen.writeln("end"); - gen.writeln("/" + fm.getFontName() + " exch definefont pop"); + redefineFontEncoding(gen, fm.getFontName(), fm.getEncoding()); } else { gen.commentln("%WARNING: Only WinAnsiEncoding is supported. Font '" + fm.getFontName() + "' asks for: " + fm.getEncoding()); } } gen.commentln("%FOPEndFontReencode"); - return fontResources; } + private static Typeface getTypeFace(FontInfo fontInfo, Map fonts, String key) { + Typeface tf = (Typeface)fonts.get(key); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + if (tf == null) { + //This is to avoid an NPE if a malconfigured font is in the configuration but not + //used in the document. If it were used, we wouldn't get this far. + String fallbackKey = fontInfo.getInternalFontKey(Font.DEFAULT_FONT); + tf = (Typeface)fonts.get(fallbackKey); + } + return tf; + } + + /** + * Embeds a font in the PostScript file. + * @param gen the PostScript generator + * @param tf the font + * @param fontRes the PSResource associated with the font + * @throws IOException In case of an I/O error + */ + public static void embedFont(PSGenerator gen, Typeface tf, PSResource fontRes) + throws IOException { + boolean embeddedFont = false; + if (FontType.TYPE1 == tf.getFontType()) { + if (tf instanceof CustomFont) { + CustomFont cf = (CustomFont)tf; + if (isEmbeddable(cf)) { + InputStream in = getInputStreamOnFont(gen, cf); + if (in != null) { + gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, + fontRes); + embedType1Font(gen, in); + gen.writeDSCComment(DSCConstants.END_RESOURCE); + gen.getResourceTracker().registerSuppliedResource(fontRes); + embeddedFont = true; + } else { + gen.commentln("%WARNING: Could not embed font: " + cf.getFontName()); + log.warn("Font " + cf.getFontName() + " is marked as supplied in the" + + " PostScript file but could not be embedded!"); + } + } + } + } + if (!embeddedFont) { + gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); + } + } + + private static boolean isEmbeddable(CustomFont font) { + return font.isEmbeddable() + && (font.getEmbedFileName() != null || font.getEmbedResourceName() != null); + } + private static InputStream getInputStreamOnFont(PSGenerator gen, CustomFont font) throws IOException { - if (font.isEmbeddable()) { + if (isEmbeddable(font)) { Source source = font.getEmbedFileSource(); if (source == null && font.getEmbedResourceName() != null) { source = new StreamSource(PSFontUtils.class @@ -168,4 +203,33 @@ public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { } } + /** + * Determines the set of fonts that will be supplied with the PS file and registers them + * with the resource tracker. All the fonts that are being processed are returned as a Map. + * @param resTracker the resource tracker + * @param fontInfo available fonts + * @param fonts the set of fonts to work with + * @return a Map of PSResource instances representing all defined fonts (key: font key) + */ + public static Map determineSuppliedFonts(ResourceTracker resTracker, + FontInfo fontInfo, Map fonts) { + Map fontResources = new java.util.HashMap(); + Iterator iter = fonts.keySet().iterator(); + while (iter.hasNext()) { + String key = (String)iter.next(); + Typeface tf = getTypeFace(fontInfo, fonts, key); + PSResource fontRes = new PSResource("font", tf.getFontName()); + fontResources.put(key, fontRes); + if (FontType.TYPE1 == tf.getFontType()) { + if (tf instanceof CustomFont) { + CustomFont cf = (CustomFont)tf; + if (isEmbeddable(cf)) { + resTracker.registerSuppliedResource(fontRes); + } + } + } + } + return fontResources; + } + } diff --git a/src/java/org/apache/fop/render/ps/PSImageFormResource.java b/src/java/org/apache/fop/render/ps/PSImageFormResource.java new file mode 100644 index 000000000..b00e2201d --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSImageFormResource.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.render.ps; + +import org.apache.xmlgraphics.ps.PSResource; + +/** + * PostScript Resource class representing a FOP form. This is used by PSRenderer to keep track + * of images. + */ +public class PSImageFormResource extends PSResource { + + private String uri; + + /** + * Create a new Form Resource. + * @param id An ID for the form + * @param uri the URI to the image + */ + public PSImageFormResource(int id, String uri) { + this("FOPForm:" + Integer.toString(id), uri); + } + + /** + /** + * Create a new Form Resource. + * @param name the name of the resource + * @param uri the URI to the image + */ + public PSImageFormResource(String name, String uri) { + super(PSResource.TYPE_FORM, name); + this.uri = uri; + } + + /** + * Returns the image URI. + * @return the image URI + */ + public String getImageURI() { + return this.uri; + } + +} diff --git a/src/java/org/apache/fop/render/ps/PSImageUtils.java b/src/java/org/apache/fop/render/ps/PSImageUtils.java index a5b6675e3..0ab329077 100644 --- a/src/java/org/apache/fop/render/ps/PSImageUtils.java +++ b/src/java/org/apache/fop/render/ps/PSImageUtils.java @@ -29,6 +29,7 @@ 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. @@ -51,15 +52,69 @@ public class PSImageUtils extends org.apache.xmlgraphics.ps.PSImageUtils { public static void renderBitmapImage(FopImage img, float x, float y, float w, float h, PSGenerator gen) throws IOException { - if (img instanceof JpegImage) { + 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)) { - gen.commentln("%JPEG image could not be processed: " + img); - return; + return null; } } else { if (!img.load(FopImage.BITMAP)) { - gen.commentln("%Bitmap image could not be processed: " + img); - return; + return null; } } byte[] imgmap; @@ -68,15 +123,18 @@ public class PSImageUtils extends org.apache.xmlgraphics.ps.PSImageUtils { } else { imgmap = img.getRessourceBytes(); } - - String imgName = img.getMimeType() + " " + img.getOriginalURI(); - Dimension imgDim = new Dimension(img.getWidth(), img.getHeight()); - Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h); - boolean isJPEG = (img instanceof JpegImage); - writeImage(imgmap, imgDim, imgName, targetRect, isJPEG, - img.getColorSpace(), gen); + return imgmap; } + /** + * Renders an EPS image to PostScript. + * @param img EPS image to render + * @param x x position + * @param y y position + * @param w width + * @param h height + * @param gen PS generator + */ public static void renderEPS(EPSImage img, float x, float y, float w, float h, PSGenerator gen) { diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index 6553f06b6..24595368f 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -23,7 +23,9 @@ package org.apache.fop.render.ps; import java.awt.Color; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.LineNumberReader; import java.io.OutputStream; import java.util.Iterator; @@ -35,6 +37,9 @@ import javax.xml.transform.Source; // FOP import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.fop.apps.FOPException; import org.apache.fop.area.Area; import org.apache.fop.area.BlockViewport; @@ -57,6 +62,7 @@ import org.apache.fop.fo.Constants; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontSetup; +import org.apache.fop.fonts.LazyFont; import org.apache.fop.fonts.Typeface; import org.apache.fop.image.EPSImage; import org.apache.fop.image.FopImage; @@ -66,7 +72,6 @@ import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.ImageAdapter; import org.apache.fop.render.RendererContext; -import org.apache.fop.render.pdf.PDFRendererContextConstants; import org.apache.fop.render.ps.extensions.PSSetupCode; import org.apache.fop.util.CharUtilities; @@ -75,6 +80,8 @@ import org.apache.xmlgraphics.ps.PSGenerator; import org.apache.xmlgraphics.ps.PSProcSets; import org.apache.xmlgraphics.ps.PSResource; import org.apache.xmlgraphics.ps.PSState; +import org.apache.xmlgraphics.ps.dsc.DSCException; +import org.apache.xmlgraphics.ps.dsc.ResourceTracker; import org.w3c.dom.Document; @@ -99,17 +106,32 @@ import org.w3c.dom.Document; */ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAdapter { + /** logging instance */ + private static Log log = LogFactory.getLog(PSRenderer.class); + /** The MIME type for PostScript */ public static final String MIME_TYPE = "application/postscript"; + private static final String AUTO_ROTATE_LANDSCAPE = "auto-rotate-landscape"; + private static final String OPTIMIZE_RESOURCES = "optimize-resources"; + private static final String LANGUAGE_LEVEL = "language-level"; + /** The application producing the PostScript */ private int currentPageNumber = 0; private boolean enableComments = true; private boolean autoRotateLandscape = false; + private int languageLevel = PSGenerator.DEFAULT_LANGUAGE_LEVEL; + /** the OutputStream the PS file is written to */ + private OutputStream outputStream; + /** the temporary file in case of two-pass processing */ + private File tempFile; + /** The PostScript generator used to output the PostScript */ protected PSGenerator gen; + /** Determines whether the PS file is generated in two passes to minimize file size */ + private boolean twoPassGeneration = false; private boolean ioTrouble = false; private boolean inTextMode = false; @@ -120,13 +142,17 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda /** This is a map of PSResource instances of all fonts defined (key: font key) */ private Map fontResources; + /** This is a map of PSResource instances of all forms (key: uri) */ + private Map formResources; /** * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration) */ public void configure(Configuration cfg) throws ConfigurationException { super.configure(cfg); - this.autoRotateLandscape = cfg.getChild("auto-rotate-landscape").getValueAsBoolean(false); + this.autoRotateLandscape = cfg.getChild(AUTO_ROTATE_LANDSCAPE).getValueAsBoolean(false); + this.languageLevel = cfg.getChild(LANGUAGE_LEVEL).getValueAsInteger(this.languageLevel); + this.twoPassGeneration = cfg.getChild(OPTIMIZE_RESOURCES).getValueAsBoolean(false); //Font configuration List cfgFonts = FontSetup.buildFontListFromConfiguration(cfg, this); @@ -138,6 +164,46 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } /** + * @see org.apache.fop.render.Renderer#setUserAgent(FOUserAgent) + */ + public void setUserAgent(FOUserAgent agent) { + super.setUserAgent(agent); + Object obj; + obj = agent.getRendererOptions().get(AUTO_ROTATE_LANDSCAPE); + if (obj != null) { + this.autoRotateLandscape = booleanValueOf(obj); + } + obj = agent.getRendererOptions().get(LANGUAGE_LEVEL); + if (obj != null) { + this.languageLevel = intValueOf(obj); + } + obj = agent.getRendererOptions().get(OPTIMIZE_RESOURCES); + if (obj != null) { + this.twoPassGeneration = booleanValueOf(obj); + } + } + + private boolean booleanValueOf(Object obj) { + if (obj instanceof Boolean) { + return ((Boolean)obj).booleanValue(); + } else if (obj instanceof String) { + return Boolean.valueOf((String)obj).booleanValue(); + } else { + throw new IllegalArgumentException("Boolean or \"true\" or \"false\" expected."); + } + } + + private int intValueOf(Object obj) { + if (obj instanceof Integer) { + return ((Integer)obj).intValue(); + } else if (obj instanceof String) { + return Integer.parseInt((String)obj); + } else { + throw new IllegalArgumentException("Integer or String with a number expected."); + } + } + + /** * Sets the landscape mode for this renderer. * @param value false will normally generate a "pseudo-portrait" page, true will rotate * a "wider-than-long" page by 90 degrees. @@ -151,13 +217,6 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda return this.autoRotateLandscape; } - /** - * @see org.apache.fop.render.Renderer#setUserAgent(FOUserAgent) - */ - public void setUserAgent(FOUserAgent agent) { - super.setUserAgent(agent); - } - /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */ public Graphics2DAdapter getGraphics2DAdapter() { return new PSGraphics2DAdapter(this); @@ -282,11 +341,11 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } /** @see org.apache.fop.render.AbstractPathOrientedRenderer */ - protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { endTextObject(); - url = ImageFactory.getURL(url); + uri = ImageFactory.getURL(uri); ImageFactory fact = userAgent.getFactory().getImageFactory(); - FopImage fopimage = fact.getImage(url, userAgent); + FopImage fopimage = fact.getImage(uri, userAgent); if (fopimage == null) { return; } @@ -320,13 +379,34 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } else if (fopimage instanceof EPSImage) { PSImageUtils.renderEPS((EPSImage)fopimage, x, y, w, h, gen); } else { - PSImageUtils.renderBitmapImage(fopimage, x, y, w, h, gen); + 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); + } } } catch (IOException ioe) { handleIOTrouble(ioe); } } + protected PSResource getFormForImage(String uri, FopImage fopimage) { + if (this.formResources == null) { + this.formResources = new java.util.HashMap(); + } + PSResource form = (PSResource)this.formResources.get(uri); + if (form == null) { + form = new PSImageFormResource(this.formResources.size() + 1, uri); + this.formResources.put(uri, form); + } + return form; + } + + protected boolean isImageInlined(String uri, FopImage image) { + return !this.twoPassGeneration; + } + /** @see org.apache.fop.render.ImageAdapter */ public void paintImage(RenderedImage image, RendererContext context, int x, int y, int width, int height) throws IOException { @@ -406,14 +486,48 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } } + private String getPostScriptNameForFontKey(String key) { + Map fonts = fontInfo.getFonts(); + Typeface tf = (Typeface)fonts.get(key); + if (tf instanceof LazyFont) { + tf = ((LazyFont)tf).getRealFont(); + } + if (tf == null) { + throw new IllegalStateException("Font not available: " + key); + } + return tf.getFontName(); + } + + /** + * Returns the PSResource for the given font key. + * @param key the font key ("F*") + * @return the matching PSResource + */ + protected PSResource getPSResourceForFontKey(String key) { + PSResource res = null; + if (this.fontResources != null) { + res = (PSResource)this.fontResources.get(key); + } else { + this.fontResources = new java.util.HashMap(); + } + if (res == null) { + res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key)); + this.fontResources.put(key, res); + } + return res; + } + /** * Changes the currently used font. - * @param name name of the font + * @param key key of the font ("F*") * @param size font size */ - public void useFont(String name, int size) { + protected void useFont(String key, int size) { try { - gen.useFont(name, size / 1000f); + PSResource res = getPSResourceForFontKey(key); + //gen.useFont(key, size / 1000f); + gen.useFont("/" + res.getName(), size / 1000f); + gen.getResourceTracker().notifyResourceUsageOnPage(res); } catch (IOException ioe) { handleIOTrouble(ioe); } @@ -598,15 +712,26 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda */ public void startRenderer(OutputStream outputStream) throws IOException { - log.debug("rendering areas to PostScript"); - + log.debug("Rendering areas to PostScript..."); + + this.outputStream = outputStream; + OutputStream out; + if (twoPassGeneration) { + this.tempFile = File.createTempFile("fop", null); + out = new java.io.FileOutputStream(this.tempFile); + out = new java.io.BufferedOutputStream(out); + } else { + out = this.outputStream; + } + //Setup for PostScript generation - this.gen = new PSGenerator(outputStream) { + this.gen = new PSGenerator(out) { /** Need to subclass PSGenerator to have better URI resolution */ public Source resolveURI(String uri) { return userAgent.resolveURI(uri); } }; + this.gen.setPSLevel(this.languageLevel); this.currentPageNumber = 0; //PostScript Header @@ -614,9 +739,9 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda gen.writeDSCComment(DSCConstants.CREATOR, new String[] {userAgent.getProducer()}); gen.writeDSCComment(DSCConstants.CREATION_DATE, new Object[] {new java.util.Date()}); gen.writeDSCComment(DSCConstants.LANGUAGE_LEVEL, new Integer(gen.getPSLevel())); - gen.writeDSCComment(DSCConstants.PAGES, new Object[] {PSGenerator.ATEND}); + gen.writeDSCComment(DSCConstants.PAGES, new Object[] {DSCConstants.ATEND}); gen.writeDSCComment(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES, - new Object[] {PSGenerator.ATEND}); + new Object[] {DSCConstants.ATEND}); gen.writeDSCComment(DSCConstants.END_COMMENTS); //Defaults @@ -631,29 +756,65 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda */ public void stopRenderer() throws IOException { //Notify resource usage for font which are not supplied + /* done in useFont now Map fonts = fontInfo.getUsedFonts(); Iterator e = fonts.keySet().iterator(); while (e.hasNext()) { String key = (String)e.next(); - //Typeface font = (Typeface)fonts.get(key); PSResource res = (PSResource)this.fontResources.get(key); - boolean supplied = gen.isResourceSupplied(res); - if (!supplied) { - gen.notifyResourceUsage(res, true); - } - } + gen.notifyResourceUsage(res); + }*/ //Write trailer gen.writeDSCComment(DSCConstants.TRAILER); gen.writeDSCComment(DSCConstants.PAGES, new Integer(this.currentPageNumber)); - gen.writeResources(false); + gen.getResourceTracker().writeResources(false, gen); gen.writeDSCComment(DSCConstants.EOF); gen.flush(); + log.debug("Rendering to PostScript complete."); + if (twoPassGeneration) { + IOUtils.closeQuietly(gen.getOutputStream()); + rewritePostScriptFile(); + } + } + + /** + * Used for two-pass production. This will rewrite the PostScript file from the temporary + * file while adding all needed resources. + * @throws IOException In case of an I/O error. + */ + private void rewritePostScriptFile() throws IOException { + log.debug("Processing PostScript resources..."); + long startTime = System.currentTimeMillis(); + ResourceTracker resTracker = gen.getResourceTracker(); + InputStream in = new java.io.FileInputStream(this.tempFile); + in = new java.io.BufferedInputStream(in); + try { + try { + ResourceHandler.process(this.userAgent, in, this.outputStream, + this.fontInfo, resTracker, this.formResources, this.currentPageNumber); + this.outputStream.flush(); + } catch (DSCException e) { + throw new RuntimeException(e.getMessage()); + } + } finally { + IOUtils.closeQuietly(in); + if (!this.tempFile.delete()) { + this.tempFile.deleteOnExit(); + log.warn("Could not delete temporary file: " + this.tempFile); + } + } + if (log.isDebugEnabled()) { + long duration = System.currentTimeMillis() - startTime; + log.debug("Resource Processing complete in " + duration + " ms."); + } } /** @see org.apache.fop.render.Renderer */ public void processOffDocumentItem(OffDocumentItem oDI) { - log.debug("Handling OffDocumentItem: " + oDI.getName()); + if (log.isDebugEnabled()) { + log.debug("Handling OffDocumentItem: " + oDI.getName()); + } if (oDI instanceof OffDocumentExtensionAttachment) { ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment(); if (PSSetupCode.CATEGORY.equals(attachment.getCategory())) { @@ -675,15 +836,18 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda try { //Prolog gen.writeDSCComment(DSCConstants.BEGIN_PROLOG); - PSProcSets.writeFOPStdProcSet(gen); - PSProcSets.writeFOPEPSProcSet(gen); + PSProcSets.writeStdProcSet(gen); + PSProcSets.writeEPSProcSet(gen); gen.writeDSCComment(DSCConstants.END_PROLOG); //Setup gen.writeDSCComment(DSCConstants.BEGIN_SETUP); writeSetupCodeList(setupCodeList, "SetupCode"); - this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo); - gen.writeln("FOPFonts begin"); + if (!twoPassGeneration) { + this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo); + } else { + gen.commentln("%FOPFontSetup"); + } gen.writeDSCComment(DSCConstants.END_SETUP); } catch (IOException ioe) { handleIOTrouble(ioe); @@ -729,8 +893,8 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda log.debug("renderPage(): " + page); this.currentPageNumber++; - gen.notifyStartNewPage(); - gen.notifyResourceUsage(PSProcSets.STD_PROCSET, false); + gen.getResourceTracker().notifyStartNewPage(); + gen.getResourceTracker().notifyResourceUsageOnPage(PSProcSets.STD_PROCSET); gen.writeDSCComment(DSCConstants.PAGE, new Object[] {page.getPageNumberString(), new Integer(this.currentPageNumber)}); @@ -769,7 +933,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda } } gen.writeDSCComment(DSCConstants.PAGE_RESOURCES, - new Object[] {PSGenerator.ATEND}); + new Object[] {DSCConstants.ATEND}); gen.commentln("%FOPSimplePageMaster: " + page.getSimplePageMasterName()); gen.writeDSCComment(DSCConstants.BEGIN_PAGE_SETUP); @@ -807,8 +971,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda writeln("showpage"); gen.writeDSCComment(DSCConstants.PAGE_TRAILER); - gen.writeResources(true); - gen.writeDSCComment(DSCConstants.END_PAGE); + gen.getResourceTracker().writeResources(true, gen); } /** @see org.apache.fop.render.AbstractRenderer */ diff --git a/src/java/org/apache/fop/render/ps/ResourceHandler.java b/src/java/org/apache/fop/render/ps/ResourceHandler.java new file mode 100644 index 000000000..7d68092bf --- /dev/null +++ b/src/java/org/apache/fop/render/ps/ResourceHandler.java @@ -0,0 +1,203 @@ +/* + * 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.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.image.FopImage; +import org.apache.fop.image.ImageFactory; +import org.apache.xmlgraphics.ps.DSCConstants; +import org.apache.xmlgraphics.ps.PSGenerator; +import org.apache.xmlgraphics.ps.dsc.DSCException; +import org.apache.xmlgraphics.ps.dsc.DSCFilter; +import org.apache.xmlgraphics.ps.dsc.DSCParser; +import org.apache.xmlgraphics.ps.dsc.DSCParserConstants; +import org.apache.xmlgraphics.ps.dsc.DefaultNestedDocumentHandler; +import org.apache.xmlgraphics.ps.dsc.ResourceTracker; +import org.apache.xmlgraphics.ps.dsc.events.DSCComment; +import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentNeededResources; +import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentSuppliedResources; +import org.apache.xmlgraphics.ps.dsc.events.DSCCommentLanguageLevel; +import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPage; +import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPages; +import org.apache.xmlgraphics.ps.dsc.events.DSCEvent; +import org.apache.xmlgraphics.ps.dsc.events.DSCHeaderComment; +import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment; +import org.apache.xmlgraphics.ps.dsc.tools.DSCTools; + +/** + * This class is used when two-pass production is used to generate the PostScript file (setting + * "optimize-resources"). It uses the DSC parser from XML Graphics Commons to go over the + * temporary file generated by the PSRenderer and adds all used fonts and images as resources + * to the PostScript file. + */ +public class ResourceHandler implements DSCParserConstants { + + /** + * Rewrites the temporary PostScript file generated by PSRenderer adding all needed resources + * (fonts and images). + * @param userAgent the FO user agent + * @param in the InputStream for the temporary PostScript file + * @param out the OutputStream to write the finished file to + * @param fontInfo the font information + * @param resTracker the resource tracker to use + * @param formResources Contains all forms used by this document (maintained by PSRenderer) + * @param pageCount the number of pages (given here because PSRenderer writes an "(atend)") + * @throws DSCException If there's an error in the DSC structure of the PS file + * @throws IOException In case of an I/O error + */ + public static void process(FOUserAgent userAgent, InputStream in, OutputStream out, + FontInfo fontInfo, ResourceTracker resTracker, Map formResources, int pageCount) + throws DSCException, IOException { + DSCParser parser = new DSCParser(in); + PSGenerator gen = new PSGenerator(out); + parser.setNestedDocumentHandler(new DefaultNestedDocumentHandler(gen)); + + //Skip DSC header + DSCHeaderComment header = DSCTools.checkAndSkipDSC30Header(parser); + header.generate(gen); + + parser.setFilter(new DSCFilter() { + private final Set filtered = new java.util.HashSet(); + { + //We rewrite those as part of the processing + filtered.add(DSCConstants.PAGES); + filtered.add(DSCConstants.DOCUMENT_NEEDED_RESOURCES); + filtered.add(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES); + } + public boolean accept(DSCEvent event) { + if (event.isDSCComment()) { + //Filter %%Pages which we add manually from a parameter + return !(filtered.contains(event.asDSCComment().getName())); + } else { + return true; + } + } + }); + + //Get PostScript language level (may be missing) + while (true) { + DSCEvent event = parser.nextEvent(); + if (event == null) { + reportInvalidDSC(); + } + if (DSCTools.headerCommentsEndHere(event)) { + //Set number of pages + DSCCommentPages pages = new DSCCommentPages(pageCount); + pages.generate(gen); + + PSFontUtils.determineSuppliedFonts(resTracker, fontInfo, fontInfo.getUsedFonts()); + registerSuppliedForms(resTracker, formResources); + + //Supplied Resources + DSCCommentDocumentSuppliedResources supplied + = new DSCCommentDocumentSuppliedResources( + resTracker.getDocumentSuppliedResources()); + supplied.generate(gen); + + //Needed Resources + DSCCommentDocumentNeededResources needed + = new DSCCommentDocumentNeededResources( + resTracker.getDocumentNeededResources()); + needed.generate(gen); + + //Write original comment that ends the header comments + event.generate(gen); + break; + } + if (event.isDSCComment()) { + DSCComment comment = event.asDSCComment(); + if (DSCConstants.LANGUAGE_LEVEL.equals(comment.getName())) { + DSCCommentLanguageLevel level = (DSCCommentLanguageLevel)comment; + gen.setPSLevel(level.getLanguageLevel()); + } + } + event.generate(gen); + } + + //Skip to the FOPFontSetup + PostScriptComment fontSetupPlaceholder = parser.nextPSComment("FOPFontSetup", gen); + if (fontSetupPlaceholder == null) { + throw new DSCException("Didn't find %FOPFontSetup comment in stream"); + } + PSFontUtils.writeFontDict(gen, fontInfo, fontInfo.getUsedFonts()); + generateForms(resTracker, userAgent, formResources, gen); + + //Skip the prolog and to the first page + DSCComment pageOrTrailer = parser.nextDSCComment(DSCConstants.PAGE, gen); + if (pageOrTrailer == null) { + throw new DSCException("Page expected, but none found"); + } + + //Process individual pages (and skip as necessary) + while (true) { + DSCCommentPage page = (DSCCommentPage)pageOrTrailer; + page.generate(gen); + pageOrTrailer = DSCTools.nextPageOrTrailer(parser, gen); + if (pageOrTrailer == null) { + reportInvalidDSC(); + } else if (!DSCConstants.PAGE.equals(pageOrTrailer.getName())) { + pageOrTrailer.generate(gen); + break; + } + } + + //Write the rest + while (parser.hasNext()) { + DSCEvent event = parser.nextEvent(); + event.generate(gen); + } + } + + private static void reportInvalidDSC() throws DSCException { + throw new DSCException("File is not DSC-compliant: Unexpected end of file"); + } + + private static void registerSuppliedForms(ResourceTracker resTracker, Map formResources) + throws IOException { + Iterator iter = formResources.values().iterator(); + while (iter.hasNext()) { + PSImageFormResource form = (PSImageFormResource)iter.next(); + resTracker.registerSuppliedResource(form); + } + } + + private static void generateForms(ResourceTracker resTracker, FOUserAgent userAgent, + Map formResources, PSGenerator gen) throws IOException { + 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()); + } + PSImageUtils.generateFormResourceForImage(image, form, gen); + } + } + +} diff --git a/status.xml b/status.xml index fdeb3c62f..9e40ad4fd 100644 --- a/status.xml +++ b/status.xml @@ -28,6 +28,10 @@ <changes> <release version="FOP Trunk"> + <action context="Code" dev="JM" type="add"> + Add support for a two-pass production for PostScript output to minimize file size. This + adds images only once and adds only the fonts that are really used. + </action> <action context="Code" dev="AD" type="fix" fixes-bug="41652"> If a line contained nothing but a linefeed, this didn't produce empty lines. Replaced the auxiliary zero-width box with a glue the width of a line, |