/* * 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.Dimension2D; import java.awt.image.RenderedImage; import java.awt.geom.Rectangle2D; 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.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; 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.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; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fonts.FontInfo; /** * 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 { /** * 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)") * @param documentBoundingBox the document's bounding box * (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, Rectangle2D documentBoundingBox) 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.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, 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 { if (formResources == null) { return; } 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 { if (formResources == null) { return; } Iterator iter = formResources.values().iterator(); while (iter.hasNext()) { PSImageFormResource form = (PSImageFormResource)iter.next(); 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()); } } } 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; } }