| /* |
| * 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.geom.Rectangle2D; |
| import java.awt.image.RenderedImage; |
| 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.events.ResourceEventProducer; |
| 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) { |
| ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( |
| userAgent.getEventBroadcaster()); |
| eventProducer.imageError(resTracker, (info != null ? info.toString() : uri), |
| ie, null); |
| } |
| } |
| } |
| |
| 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; |
| } |
| |
| } |