/*
* 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 627367 2008-02-13 12:03:30Z maxberger $ */
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;
}
}