text: /*
   * 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);
          }
      }
  
      /* 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();
                  }
              }
          }
  
      }
  
  }
  