blob: 7144aafd62cc89c5f14813f0b92dee47e5abb63d [file] [log] [blame]
/*
* 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);
} catch (Exception e) {
ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
userAgent.getEventBroadcaster());
eventProducer.imageWritingError(this, e);
}
}
/* 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();
}
}
}
}
}