| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.render.ps; |
| |
| import java.awt.geom.Rectangle2D; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.xmlgraphics.image.loader.ImageException; |
| import org.apache.xmlgraphics.image.loader.ImageFlavor; |
| import org.apache.xmlgraphics.image.loader.ImageInfo; |
| import org.apache.xmlgraphics.image.loader.ImageManager; |
| import org.apache.xmlgraphics.image.loader.ImageSessionContext; |
| import org.apache.xmlgraphics.image.loader.util.ImageUtil; |
| import org.apache.xmlgraphics.ps.DSCConstants; |
| import org.apache.xmlgraphics.ps.PSGenerator; |
| import org.apache.xmlgraphics.ps.PSResource; |
| import org.apache.xmlgraphics.ps.dsc.DSCException; |
| import org.apache.xmlgraphics.ps.dsc.DSCFilter; |
| import org.apache.xmlgraphics.ps.dsc.DSCListener; |
| import org.apache.xmlgraphics.ps.dsc.DSCParser; |
| import org.apache.xmlgraphics.ps.dsc.DSCParserConstants; |
| import org.apache.xmlgraphics.ps.dsc.DefaultNestedDocumentHandler; |
| import org.apache.xmlgraphics.ps.dsc.ResourceTracker; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCComment; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentNeededResources; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentDocumentSuppliedResources; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentIncludeResource; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentLanguageLevel; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPage; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCCommentPages; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCEvent; |
| import org.apache.xmlgraphics.ps.dsc.events.DSCHeaderComment; |
| import org.apache.xmlgraphics.ps.dsc.events.PostScriptComment; |
| import org.apache.xmlgraphics.ps.dsc.events.PostScriptLine; |
| import org.apache.xmlgraphics.ps.dsc.tools.DSCTools; |
| |
| import org.apache.fop.ResourceEventProducer; |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.render.ImageHandler; |
| import org.apache.fop.render.ImageHandlerRegistry; |
| |
| /** |
| * This class is used when two-pass production is used to generate the PostScript file (setting |
| * "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, PSSupportedFlavors { |
| |
| /** logging instance */ |
| private static Log log = LogFactory.getLog(ResourceHandler.class); |
| |
| private FOUserAgent userAgent; |
| private FontInfo fontInfo; |
| |
| private PSEventProducer eventProducer; |
| |
| private ResourceTracker resTracker; |
| |
| //key: URI, values PSImageFormResource |
| private Map globalFormResources = new java.util.HashMap(); |
| //key: PSResource, values PSImageFormResource |
| private Map inlineFormResources = new java.util.HashMap(); |
| |
| /** |
| * Main constructor. |
| * @param userAgent the FO user agent |
| * @param eventProducer the event producer |
| * @param fontInfo the font information |
| * @param resTracker the resource tracker to use |
| * @param formResources Contains all forms used by this document (maintained by PSRenderer) |
| */ |
| public ResourceHandler(FOUserAgent userAgent, PSEventProducer eventProducer, |
| FontInfo fontInfo, ResourceTracker resTracker, Map formResources) { |
| this.userAgent = userAgent; |
| this.eventProducer = eventProducer; |
| this.fontInfo = fontInfo; |
| this.resTracker = resTracker; |
| determineInlineForms(formResources); |
| } |
| |
| /** |
| * This method splits up the form resources map into two. One for global forms which |
| * have been referenced more than once, and one for inline forms which have only been |
| * used once. The latter is to conserve memory in the PostScript interpreter. |
| * @param formResources the original form resources map |
| */ |
| private void determineInlineForms(Map formResources) { |
| if (formResources == null) { |
| return; |
| } |
| for (Object o : formResources.entrySet()) { |
| Map.Entry entry = (Map.Entry) o; |
| PSResource res = (PSResource) entry.getValue(); |
| long count = resTracker.getUsageCount(res); |
| if (count > 1) { |
| //Make global form |
| this.globalFormResources.put(entry.getKey(), res); |
| } else { |
| //Inline resource |
| this.inlineFormResources.put(res, res); |
| resTracker.declareInlined(res); |
| } |
| } |
| } |
| |
| /** |
| * Rewrites the temporary PostScript file generated by PSRenderer adding all needed resources |
| * (fonts and images). |
| * @param in the InputStream for the temporary PostScript file |
| * @param out the OutputStream to write the finished file to |
| * @param pageCount the number of pages (given here because PSRenderer writes an "(atend)") |
| * @param documentBoundingBox the document's bounding box |
| * (given here because PSRenderer writes an "(atend)") |
| * @param psUtil |
| * @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 void process(InputStream in, OutputStream out, |
| int pageCount, Rectangle2D documentBoundingBox, PSRenderingUtil psUtil) |
| throws DSCException, IOException { |
| DSCParser parser = new DSCParser(in); |
| parser.setCheckEOF(false); |
| |
| PSGenerator gen = new PSGenerator(out); |
| gen.setAcrobatDownsample(psUtil.isAcrobatDownsample()); |
| parser.addListener(new DefaultNestedDocumentHandler(gen)); |
| parser.addListener(new IncludeResourceListener(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.BBOX); |
| filtered.add(DSCConstants.HIRES_BBOX); |
| 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); |
| new DSCCommentBoundingBox(documentBoundingBox).generate(gen); |
| new DSCCommentHiResBoundingBox(documentBoundingBox).generate(gen); |
| |
| PSFontUtils.determineSuppliedFonts(resTracker, fontInfo, fontInfo.getUsedFonts()); |
| registerSuppliedForms(resTracker, globalFormResources); |
| |
| //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(), eventProducer); |
| generateForms(globalFormResources, 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); |
| } |
| gen.flush(); |
| } |
| |
| 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 { |
| if (formResources == null) { |
| return; |
| } |
| for (Object o : formResources.values()) { |
| PSImageFormResource form = (PSImageFormResource) o; |
| resTracker.registerSuppliedResource(form); |
| } |
| } |
| |
| private void generateForms(Map formResources, PSGenerator gen) throws IOException { |
| if (formResources == null) { |
| return; |
| } |
| for (Object o : formResources.values()) { |
| PSImageFormResource form = (PSImageFormResource) o; |
| generateFormForImage(gen, form); |
| } |
| } |
| |
| private void generateFormForImage(PSGenerator gen, PSImageFormResource form) |
| throws IOException { |
| final String uri = form.getImageURI(); |
| |
| ImageManager manager = userAgent.getImageManager(); |
| ImageInfo info = null; |
| try { |
| ImageSessionContext sessionContext = userAgent.getImageSessionContext(); |
| info = manager.getImageInfo(uri, sessionContext); |
| |
| //Create a rendering context for form creation |
| PSRenderingContext formContext = new PSRenderingContext( |
| userAgent, gen, fontInfo, true); |
| |
| ImageFlavor[] flavors; |
| ImageHandlerRegistry imageHandlerRegistry |
| = userAgent.getImageHandlerRegistry(); |
| flavors = imageHandlerRegistry.getSupportedFlavors(formContext); |
| |
| Map hints = ImageUtil.getDefaultHints(sessionContext); |
| org.apache.xmlgraphics.image.loader.Image img = manager.getImage( |
| info, flavors, hints, sessionContext); |
| |
| ImageHandler basicHandler = imageHandlerRegistry.getHandler(formContext, img); |
| if (basicHandler == null) { |
| throw new UnsupportedOperationException( |
| "No ImageHandler available for image: " |
| + img.getInfo() + " (" + img.getClass().getName() + ")"); |
| } |
| |
| if (!(basicHandler instanceof PSImageHandler)) { |
| throw new IllegalStateException( |
| "ImageHandler implementation doesn't behave properly." |
| + " It should have returned false in isCompatible(). Class: " |
| + basicHandler.getClass().getName()); |
| } |
| PSImageHandler handler = (PSImageHandler)basicHandler; |
| if (log.isTraceEnabled()) { |
| log.trace("Using ImageHandler: " + handler.getClass().getName()); |
| } |
| handler.generateForm(formContext, img, form); |
| |
| } catch (ImageException ie) { |
| ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( |
| userAgent.getEventBroadcaster()); |
| eventProducer.imageError(resTracker, (info != null ? info.toString() : uri), |
| ie, null); |
| } catch (Exception e) { |
| ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( |
| userAgent.getEventBroadcaster()); |
| eventProducer.imageWritingError(this, e); |
| } |
| } |
| |
| /* not used |
| 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; |
| } |
| */ |
| |
| private class IncludeResourceListener implements DSCListener { |
| |
| private PSGenerator gen; |
| |
| public IncludeResourceListener(PSGenerator gen) { |
| this.gen = gen; |
| } |
| |
| /** {@inheritDoc} */ |
| public void processEvent(DSCEvent event, DSCParser parser) |
| throws IOException, DSCException { |
| if (event.isDSCComment() && event instanceof DSCCommentIncludeResource) { |
| DSCCommentIncludeResource include = (DSCCommentIncludeResource)event; |
| PSResource res = include.getResource(); |
| if (res.getType().equals(PSResource.TYPE_FORM)) { |
| if (inlineFormResources.containsValue(res)) { |
| PSImageFormResource form = (PSImageFormResource) |
| inlineFormResources.get(res); |
| //Create an inline form |
| //Wrap in save/restore pair to release memory |
| gen.writeln("save"); |
| generateFormForImage(gen, form); |
| boolean execformFound = false; |
| DSCEvent next = parser.nextEvent(); |
| if (next.isLine()) { |
| PostScriptLine line = next.asLine(); |
| if (line.getLine().endsWith(" execform")) { |
| line.generate(gen); |
| execformFound = true; |
| } |
| } |
| if (!execformFound) { |
| throw new IOException( |
| "Expected a PostScript line in the form: <form> execform"); |
| } |
| gen.writeln("restore"); |
| } else { |
| //Do nothing |
| } |
| parser.next(); |
| } |
| } |
| } |
| |
| } |
| |
| } |