/*
 * 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: ResourceHandler.java 1809628 2017-09-25 13:42:23Z ssteiner $ */

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.
/** * 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
/** 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.
Params:
  • userAgent – the FO user agent
  • eventProducer – the event producer
  • fontInfo – the font information
  • resTracker – the resource tracker to use
  • formResources – Contains all forms used by this document (maintained by PSRenderer)
/** * 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.
Params:
  • formResources – the original form resources map
/** * 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).
Params:
  • in – the InputStream for the temporary PostScript file
  • out – the OutputStream to write the finished file to
  • pageCount – the number of pages (given here because PSRenderer writes an "(atend)")
  • documentBoundingBox – the document's bounding box (given here because PSRenderer writes an "(atend)")
  • psUtil –
Throws:
/** * 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); } } /* 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}
/** {@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(); } } } } }