blob: 14924996e0fb373ceacf9b7a5c8a6ba430042e46 [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;
// Java
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.transform.Source;
import org.apache.commons.io.IOUtils;
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.PSDictionary;
import org.apache.xmlgraphics.ps.PSDictionaryFormatException;
import org.apache.xmlgraphics.ps.PSGenerator;
import org.apache.xmlgraphics.ps.PSPageDeviceDictionary;
import org.apache.xmlgraphics.ps.PSProcSets;
import org.apache.xmlgraphics.ps.PSResource;
import org.apache.xmlgraphics.ps.PSState;
import org.apache.xmlgraphics.ps.dsc.DSCException;
import org.apache.xmlgraphics.ps.dsc.ResourceTracker;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentBoundingBox;
import org.apache.xmlgraphics.ps.dsc.events.DSCCommentHiResBoundingBox;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.area.Area;
import org.apache.fop.area.BlockViewport;
import org.apache.fop.area.CTM;
import org.apache.fop.area.OffDocumentExtensionAttachment;
import org.apache.fop.area.OffDocumentItem;
import org.apache.fop.area.PageViewport;
import org.apache.fop.area.RegionViewport;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.AbstractTextArea;
import org.apache.fop.area.inline.Image;
import org.apache.fop.area.inline.InlineParent;
import org.apache.fop.area.inline.Leader;
import org.apache.fop.area.inline.SpaceArea;
import org.apache.fop.area.inline.TextArea;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.datatypes.URISpecification;
import org.apache.fop.events.ResourceEventProducer;
import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.LazyFont;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.render.AbstractPathOrientedRenderer;
import org.apache.fop.render.Graphics2DAdapter;
import org.apache.fop.render.ImageAdapter;
import org.apache.fop.render.ImageHandler;
import org.apache.fop.render.ImageHandlerRegistry;
import org.apache.fop.render.RendererContext;
import org.apache.fop.render.RendererEventProducer;
import org.apache.fop.render.ps.extensions.PSCommentAfter;
import org.apache.fop.render.ps.extensions.PSCommentBefore;
import org.apache.fop.render.ps.extensions.PSExtensionAttachment;
import org.apache.fop.render.ps.extensions.PSSetPageDevice;
import org.apache.fop.render.ps.extensions.PSSetupCode;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
/**
* Renderer that renders to PostScript.
* <br>
* This class currently generates PostScript Level 2 code. The only exception
* is the FlateEncode filter which is a Level 3 feature. The filters in use
* are hardcoded at the moment.
* <br>
* This class follows the Document Structuring Conventions (DSC) version 3.0.
* If anyone modifies this renderer please make
* sure to also follow the DSC to make it simpler to programmatically modify
* the generated Postscript files (ex. extract pages etc.).
* <br>
* This renderer inserts FOP-specific comments into the PostScript stream which
* may help certain users to do certain types of post-processing of the output.
* These comments all start with "%FOP".
*
* @author <a href="mailto:fop-dev@xmlgraphics.apache.org">Apache FOP Development Team</a>
* @version $Id$
*/
public class PSRenderer extends AbstractPathOrientedRenderer
implements ImageAdapter, PSSupportedFlavors, PSConfigurationConstants {
/** logging instance */
private static Log log = LogFactory.getLog(PSRenderer.class);
/** The MIME type for PostScript */
public static final String MIME_TYPE = "application/postscript";
/** The application producing the PostScript */
private int currentPageNumber = 0;
/** the OutputStream the PS file is written to */
private OutputStream outputStream;
/** the temporary file in case of two-pass processing */
private File tempFile;
/** The PostScript generator used to output the PostScript */
protected PSGenerator gen;
private boolean ioTrouble = false;
private boolean inTextMode = false;
/** Used to temporarily store PSSetupCode instance until they can be written. */
private List setupCodeList;
/** This is a map of PSResource instances of all fonts defined (key: font key) */
private Map fontResources;
/** This is a map of PSResource instances of all forms (key: uri) */
private Map formResources;
/** encapsulation of dictionary used in setpagedevice instruction **/
private PSPageDeviceDictionary pageDeviceDictionary;
/**
* Utility class which enables all sorts of features that are not directly connected to the
* normal rendering process.
*/
protected PSRenderingUtil psUtil;
private PSBorderPainter borderPainter;
/** Is used to determine the document's bounding box */
private Rectangle2D documentBoundingBox;
/** This is a collection holding all document header comments */
private Collection headerComments;
/** This is a collection holding all document footer comments */
private Collection footerComments;
/** {@inheritDoc} */
public void setUserAgent(FOUserAgent agent) {
super.setUserAgent(agent);
this.psUtil = new PSRenderingUtil(getUserAgent());
}
PSRenderingUtil getPSUtil() {
return this.psUtil;
}
/**
* Sets the landscape mode for this renderer.
* @param value false will normally generate a "pseudo-portrait" page, true will rotate
* a "wider-than-long" page by 90 degrees.
*/
public void setAutoRotateLandscape(boolean value) {
getPSUtil().setAutoRotateLandscape(value);
}
/** @return true if the renderer is configured to rotate landscape pages */
public boolean isAutoRotateLandscape() {
return getPSUtil().isAutoRotateLandscape();
}
/**
* Sets the PostScript language level that the renderer should produce.
* @param level the language level (currently allowed: 2 or 3)
*/
public void setLanguageLevel(int level) {
getPSUtil().setLanguageLevel(level);
}
/**
* Return the PostScript language level that the renderer produces.
* @return the language level
*/
public int getLanguageLevel() {
return getPSUtil().getLanguageLevel();
}
/**
* Sets the resource optimization mode. If set to true, the renderer does two passes to
* only embed the necessary resources in the PostScript file. This is slower, but produces
* smaller files.
* @param value true to enable the resource optimization
*/
public void setOptimizeResources(boolean value) {
getPSUtil().setOptimizeResources(value);
}
/** @return true if the renderer does two passes to optimize PostScript resources */
public boolean isOptimizeResources() {
return getPSUtil().isOptimizeResources();
}
/** {@inheritDoc} */
public Graphics2DAdapter getGraphics2DAdapter() {
return new PSGraphics2DAdapter(this);
}
/** {@inheritDoc} */
public ImageAdapter getImageAdapter() {
return this;
}
/**
* Write out a command
* @param cmd PostScript command
*/
protected void writeln(String cmd) {
try {
gen.writeln(cmd);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/**
* Central exception handler for I/O exceptions.
* @param ioe IOException to handle
*/
protected void handleIOTrouble(IOException ioe) {
if (!ioTrouble) {
RendererEventProducer eventProducer = RendererEventProducer.Provider.get(
getUserAgent().getEventBroadcaster());
eventProducer.ioError(this, ioe);
ioTrouble = true;
}
}
/**
* Write out a comment
* @param comment Comment to write
*/
protected void comment(String comment) {
try {
if (comment.startsWith("%")) {
gen.commentln(comment);
writeln(comment);
} else {
gen.commentln("%" + comment);
}
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/**
* Make sure the cursor is in the right place.
*/
protected void movetoCurrPosition() {
moveTo(this.currentIPPosition, this.currentBPPosition);
}
/** {@inheritDoc} */
protected void clip() {
writeln("clip newpath");
}
/** {@inheritDoc} */
protected void clipRect(float x, float y, float width, float height) {
try {
gen.defineRect(x, y, width, height);
clip();
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** {@inheritDoc} */
protected void moveTo(float x, float y) {
writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " M");
}
/**
* Moves the current point by (x, y) relative to the current position,
* omitting any connecting line segment.
* @param x x coordinate
* @param y y coordinate
*/
protected void rmoveTo(float x, float y) {
writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " RM");
}
/** {@inheritDoc} */
protected void lineTo(float x, float y) {
writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " lineto");
}
/** {@inheritDoc} */
protected void closePath() {
writeln("cp");
}
/** {@inheritDoc} */
protected void fillRect(float x, float y, float width, float height) {
if (width != 0 && height != 0) {
try {
gen.defineRect(x, y, width, height);
gen.writeln("fill");
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
}
/** {@inheritDoc} */
protected void updateColor(Color col, boolean fill) {
try {
useColor(col);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** {@inheritDoc} */
protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) {
endTextObject();
int x = currentIPPosition + (int)Math.round(pos.getX());
int y = currentBPPosition + (int)Math.round(pos.getY());
uri = URISpecification.getURL(uri);
if (log.isDebugEnabled()) {
log.debug("Handling image: " + uri);
}
int width = (int)pos.getWidth();
int height = (int)pos.getHeight();
Rectangle targetRect = new Rectangle(x, y, width, height);
ImageManager manager = getUserAgent().getFactory().getImageManager();
ImageInfo info = null;
try {
ImageSessionContext sessionContext = getUserAgent().getImageSessionContext();
info = manager.getImageInfo(uri, sessionContext);
PSRenderingContext renderingContext = new PSRenderingContext(
getUserAgent(), gen, getFontInfo());
if (!isOptimizeResources()
|| PSImageUtils.isImageInlined(info, renderingContext)) {
if (log.isDebugEnabled()) {
log.debug("Image " + info + " is inlined");
}
//Determine supported flavors
ImageFlavor[] flavors;
ImageHandlerRegistry imageHandlerRegistry
= userAgent.getFactory().getImageHandlerRegistry();
flavors = imageHandlerRegistry.getSupportedFlavors(renderingContext);
//Only now fully load/prepare the image
Map hints = ImageUtil.getDefaultHints(sessionContext);
org.apache.xmlgraphics.image.loader.Image img = manager.getImage(
info, flavors, hints, sessionContext);
//Get handler for image
ImageHandler basicHandler = imageHandlerRegistry.getHandler(renderingContext, img);
//...and embed as inline image
basicHandler.handleImage(renderingContext, img, targetRect);
} else {
if (log.isDebugEnabled()) {
log.debug("Image " + info + " is embedded as a form later");
}
//Don't load image at this time, just put a form placeholder in the stream
PSResource form = getFormForImage(info.getOriginalURI());
PSImageUtils.drawForm(form, info, targetRect, gen);
}
} catch (ImageException ie) {
ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
getUserAgent().getEventBroadcaster());
eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null);
} catch (FileNotFoundException fe) {
ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
getUserAgent().getEventBroadcaster());
eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null);
} catch (IOException ioe) {
ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get(
getUserAgent().getEventBroadcaster());
eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null);
}
}
/**
* Returns a PSResource instance representing a image as a PostScript form.
* @param uri the image URI
* @return a PSResource instance
*/
protected PSResource getFormForImage(String uri) {
if (uri == null || "".equals(uri)) {
throw new IllegalArgumentException("uri must not be empty or null");
}
if (this.formResources == null) {
this.formResources = new java.util.HashMap();
}
PSResource form = (PSResource)this.formResources.get(uri);
if (form == null) {
form = new PSImageFormResource(this.formResources.size() + 1, uri);
this.formResources.put(uri, form);
}
return form;
}
/** {@inheritDoc} */
public void paintImage(RenderedImage image, RendererContext context,
int x, int y, int width, int height) throws IOException {
float fx = x / 1000f;
x += currentIPPosition / 1000f;
float fy = y / 1000f;
y += currentBPPosition / 1000f;
float fw = width / 1000f;
float fh = height / 1000f;
PSImageUtils.renderBitmapImage(image, fx, fy, fw, fh, gen);
}
/**
* Draw a line.
*
* @param startx the start x position
* @param starty the start y position
* @param endx the x end position
* @param endy the y end position
*/
private void drawLine(float startx, float starty, float endx, float endy) {
writeln(gen.formatDouble(startx) + " "
+ gen.formatDouble(starty) + " M "
+ gen.formatDouble(endx) + " "
+ gen.formatDouble(endy) + " lineto stroke newpath");
}
/** Saves the graphics state of the rendering engine. */
public void saveGraphicsState() {
endTextObject();
try {
//delegate
gen.saveGraphicsState();
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** Restores the last graphics state of the rendering engine. */
public void restoreGraphicsState() {
try {
endTextObject();
//delegate
gen.restoreGraphicsState();
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/**
* Concats the transformation matrix.
* @param a A part
* @param b B part
* @param c C part
* @param d D part
* @param e E part
* @param f F part
*/
protected void concatMatrix(double a, double b,
double c, double d,
double e, double f) {
try {
gen.concatMatrix(a, b, c, d, e, f);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/**
* Concats the transformations matrix.
* @param matrix Matrix to use
*/
protected void concatMatrix(double[] matrix) {
try {
gen.concatMatrix(matrix);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** {@inheritDoc} */
protected void concatenateTransformationMatrix(AffineTransform at) {
try {
gen.concatMatrix(at);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
private String getPostScriptNameForFontKey(String key) {
int pos = key.indexOf('_');
String postFix = null;
if (pos > 0) {
postFix = key.substring(pos);
key = key.substring(0, pos);
}
Map fonts = fontInfo.getFonts();
Typeface tf = (Typeface)fonts.get(key);
if (tf instanceof LazyFont) {
tf = ((LazyFont)tf).getRealFont();
}
if (tf == null) {
throw new IllegalStateException("Font not available: " + key);
}
if (postFix == null) {
return tf.getFontName();
} else {
return tf.getFontName() + postFix;
}
}
/**
* Returns the PSResource for the given font key.
* @param key the font key ("F*")
* @return the matching PSResource
*/
protected PSResource getPSResourceForFontKey(String key) {
PSResource res = null;
if (this.fontResources != null) {
res = (PSResource)this.fontResources.get(key);
} else {
this.fontResources = new java.util.HashMap();
}
if (res == null) {
res = new PSResource(PSResource.TYPE_FONT, getPostScriptNameForFontKey(key));
this.fontResources.put(key, res);
}
return res;
}
/**
* Changes the currently used font.
* @param key key of the font ("F*")
* @param size font size
*/
protected void useFont(String key, int size) {
try {
PSResource res = getPSResourceForFontKey(key);
gen.useFont("/" + res.getName(), size / 1000f);
gen.getResourceTracker().notifyResourceUsageOnPage(res);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
private void useColor(Color col) throws IOException {
gen.useColor(col);
}
/** {@inheritDoc} */
protected void drawBackAndBorders(Area area, float startx, float starty,
float width, float height) {
if (area.hasTrait(Trait.BACKGROUND)
|| area.hasTrait(Trait.BORDER_BEFORE)
|| area.hasTrait(Trait.BORDER_AFTER)
|| area.hasTrait(Trait.BORDER_START)
|| area.hasTrait(Trait.BORDER_END)) {
comment("%FOPBeginBackgroundAndBorder: "
+ startx + " " + starty + " " + width + " " + height);
super.drawBackAndBorders(area, startx, starty, width, height);
comment("%FOPEndBackgroundAndBorder");
}
}
/** {@inheritDoc} */
protected void drawBorderLine(float x1, float y1, float x2, float y2,
boolean horz, boolean startOrBefore, int style, Color col) {
try {
PSBorderPainter.drawBorderLine(gen, x1, y1, x2, y2, horz, startOrBefore, style, col);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** {@inheritDoc} */
public void startRenderer(OutputStream outputStream)
throws IOException {
log.debug("Rendering areas to PostScript...");
this.outputStream = outputStream;
OutputStream out;
if (isOptimizeResources()) {
this.tempFile = File.createTempFile("fop", null);
out = new java.io.FileOutputStream(this.tempFile);
out = new java.io.BufferedOutputStream(out);
} else {
out = this.outputStream;
}
//Setup for PostScript generation
this.gen = new PSGenerator(out) {
/** Need to subclass PSGenerator to have better URI resolution */
public Source resolveURI(String uri) {
return userAgent.resolveURI(uri);
}
};
this.gen.setPSLevel(getLanguageLevel());
this.borderPainter = new PSBorderPainter(this.gen);
this.currentPageNumber = 0;
//Initial default page device dictionary settings
this.pageDeviceDictionary = new PSPageDeviceDictionary();
pageDeviceDictionary.setFlushOnRetrieval(!getPSUtil().isDSCComplianceEnabled());
pageDeviceDictionary.put("/ImagingBBox", "null");
}
private void writeHeader() throws IOException {
//PostScript Header
writeln(DSCConstants.PS_ADOBE_30);
gen.writeDSCComment(DSCConstants.CREATOR, new String[] {userAgent.getProducer()});
gen.writeDSCComment(DSCConstants.CREATION_DATE, new Object[] {new java.util.Date()});
gen.writeDSCComment(DSCConstants.LANGUAGE_LEVEL, new Integer(gen.getPSLevel()));
gen.writeDSCComment(DSCConstants.PAGES, new Object[] {DSCConstants.ATEND});
gen.writeDSCComment(DSCConstants.BBOX, DSCConstants.ATEND);
gen.writeDSCComment(DSCConstants.HIRES_BBOX, DSCConstants.ATEND);
this.documentBoundingBox = new Rectangle2D.Double();
gen.writeDSCComment(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES,
new Object[] {DSCConstants.ATEND});
if (headerComments != null) {
for (Iterator iter = headerComments.iterator(); iter.hasNext();) {
PSExtensionAttachment comment = (PSExtensionAttachment)iter.next();
gen.writeln("%" + comment.getContent());
}
}
gen.writeDSCComment(DSCConstants.END_COMMENTS);
//Defaults
gen.writeDSCComment(DSCConstants.BEGIN_DEFAULTS);
gen.writeDSCComment(DSCConstants.END_DEFAULTS);
//Prolog and Setup written right before the first page-sequence, see startPageSequence()
//Do this only once, as soon as we have all the content for the Setup section!
//Prolog
gen.writeDSCComment(DSCConstants.BEGIN_PROLOG);
PSProcSets.writeStdProcSet(gen);
PSProcSets.writeEPSProcSet(gen);
gen.writeDSCComment(DSCConstants.END_PROLOG);
//Setup
gen.writeDSCComment(DSCConstants.BEGIN_SETUP);
PSRenderingUtil.writeSetupCodeList(gen, setupCodeList, "SetupCode");
if (!isOptimizeResources()) {
this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo);
} else {
gen.commentln("%FOPFontSetup"); //Place-holder, will be replaced in the second pass
}
gen.writeDSCComment(DSCConstants.END_SETUP);
}
/** {@inheritDoc} */
public void stopRenderer() throws IOException {
//Write trailer
gen.writeDSCComment(DSCConstants.TRAILER);
if (footerComments != null) {
for (Iterator iter = footerComments.iterator(); iter.hasNext();) {
PSExtensionAttachment comment = (PSExtensionAttachment)iter.next();
gen.commentln("%" + comment.getContent());
}
footerComments.clear();
}
gen.writeDSCComment(DSCConstants.PAGES, new Integer(this.currentPageNumber));
new DSCCommentBoundingBox(this.documentBoundingBox).generate(gen);
new DSCCommentHiResBoundingBox(this.documentBoundingBox).generate(gen);
gen.getResourceTracker().writeResources(false, gen);
gen.writeDSCComment(DSCConstants.EOF);
gen.flush();
log.debug("Rendering to PostScript complete.");
if (isOptimizeResources()) {
IOUtils.closeQuietly(gen.getOutputStream());
rewritePostScriptFile();
}
if (footerComments != null) {
headerComments.clear();
}
if (pageDeviceDictionary != null) {
pageDeviceDictionary.clear();
}
this.borderPainter = null;
this.gen = null;
}
/**
* Used for two-pass production. This will rewrite the PostScript file from the temporary
* file while adding all needed resources.
* @throws IOException In case of an I/O error.
*/
private void rewritePostScriptFile() throws IOException {
log.debug("Processing PostScript resources...");
long startTime = System.currentTimeMillis();
ResourceTracker resTracker = gen.getResourceTracker();
InputStream in = new java.io.FileInputStream(this.tempFile);
in = new java.io.BufferedInputStream(in);
try {
try {
ResourceHandler handler = new ResourceHandler(this.userAgent, this.fontInfo,
resTracker, this.formResources);
handler.process(in, this.outputStream,
this.currentPageNumber, this.documentBoundingBox);
this.outputStream.flush();
} catch (DSCException e) {
throw new RuntimeException(e.getMessage());
}
} finally {
IOUtils.closeQuietly(in);
if (!this.tempFile.delete()) {
this.tempFile.deleteOnExit();
log.warn("Could not delete temporary file: " + this.tempFile);
}
}
if (log.isDebugEnabled()) {
long duration = System.currentTimeMillis() - startTime;
log.debug("Resource Processing complete in " + duration + " ms.");
}
}
/** {@inheritDoc} */
public void processOffDocumentItem(OffDocumentItem oDI) {
if (log.isDebugEnabled()) {
log.debug("Handling OffDocumentItem: " + oDI.getName());
}
if (oDI instanceof OffDocumentExtensionAttachment) {
ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment();
if (attachment != null) {
if (PSExtensionAttachment.CATEGORY.equals(attachment.getCategory())) {
if (attachment instanceof PSSetupCode) {
if (setupCodeList == null) {
setupCodeList = new java.util.ArrayList();
}
if (!setupCodeList.contains(attachment)) {
setupCodeList.add(attachment);
}
} else if (attachment instanceof PSSetPageDevice) {
/**
* Extract all PSSetPageDevice instances from the
* attachment list on the s-p-m and add all dictionary
* entries to our internal representation of the the
* page device dictionary.
*/
PSSetPageDevice setPageDevice = (PSSetPageDevice)attachment;
String content = setPageDevice.getContent();
if (content != null) {
try {
this.pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
} catch (PSDictionaryFormatException e) {
PSEventProducer eventProducer = PSEventProducer.Provider.get(
getUserAgent().getEventBroadcaster());
eventProducer.postscriptDictionaryParseError(this, content, e);
}
}
} else if (attachment instanceof PSCommentBefore) {
if (headerComments == null) {
headerComments = new java.util.ArrayList();
}
headerComments.add(attachment);
} else if (attachment instanceof PSCommentAfter) {
if (footerComments == null) {
footerComments = new java.util.ArrayList();
}
footerComments.add(attachment);
}
}
}
}
super.processOffDocumentItem(oDI);
}
/** {@inheritDoc} */
public void renderPage(PageViewport page)
throws IOException, FOPException {
log.debug("renderPage(): " + page);
if (this.currentPageNumber == 0) {
writeHeader();
}
this.currentPageNumber++;
gen.getResourceTracker().notifyStartNewPage();
gen.getResourceTracker().notifyResourceUsageOnPage(PSProcSets.STD_PROCSET);
gen.writeDSCComment(DSCConstants.PAGE, new Object[]
{page.getPageNumberString(),
new Integer(this.currentPageNumber)});
double pageWidth = page.getViewArea().width / 1000f;
double pageHeight = page.getViewArea().height / 1000f;
boolean rotate = false;
List pageSizes = new java.util.ArrayList();
if (getPSUtil().isAutoRotateLandscape() && (pageHeight < pageWidth)) {
rotate = true;
pageSizes.add(new Long(Math.round(pageHeight)));
pageSizes.add(new Long(Math.round(pageWidth)));
} else {
pageSizes.add(new Long(Math.round(pageWidth)));
pageSizes.add(new Long(Math.round(pageHeight)));
}
pageDeviceDictionary.put("/PageSize", pageSizes);
if (page.hasExtensionAttachments()) {
for (Iterator iter = page.getExtensionAttachments().iterator();
iter.hasNext();) {
ExtensionAttachment attachment = (ExtensionAttachment) iter.next();
if (attachment instanceof PSSetPageDevice) {
/**
* Extract all PSSetPageDevice instances from the
* attachment list on the s-p-m and add all
* dictionary entries to our internal representation
* of the the page device dictionary.
*/
PSSetPageDevice setPageDevice = (PSSetPageDevice)attachment;
String content = setPageDevice.getContent();
if (content != null) {
try {
pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
} catch (PSDictionaryFormatException e) {
PSEventProducer eventProducer = PSEventProducer.Provider.get(
getUserAgent().getEventBroadcaster());
eventProducer.postscriptDictionaryParseError(this, content, e);
}
}
}
}
}
try {
if (setupCodeList != null) {
PSRenderingUtil.writeEnclosedExtensionAttachments(gen, setupCodeList);
setupCodeList.clear();
}
} catch (IOException e) {
log.error(e.getMessage());
}
final Integer zero = new Integer(0);
Rectangle2D pageBoundingBox = new Rectangle2D.Double();
if (rotate) {
pageBoundingBox.setRect(0, 0, pageHeight, pageWidth);
gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
zero, zero, new Long(Math.round(pageHeight)),
new Long(Math.round(pageWidth)) });
gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
zero, zero, new Double(pageHeight),
new Double(pageWidth) });
gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Landscape");
} else {
pageBoundingBox.setRect(0, 0, pageWidth, pageHeight);
gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
zero, zero, new Long(Math.round(pageWidth)),
new Long(Math.round(pageHeight)) });
gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
zero, zero, new Double(pageWidth),
new Double(pageHeight) });
if (getPSUtil().isAutoRotateLandscape()) {
gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION,
"Portrait");
}
}
this.documentBoundingBox.add(pageBoundingBox);
gen.writeDSCComment(DSCConstants.PAGE_RESOURCES,
new Object[] {DSCConstants.ATEND});
gen.commentln("%FOPSimplePageMaster: " + page.getSimplePageMasterName());
gen.writeDSCComment(DSCConstants.BEGIN_PAGE_SETUP);
if (page.hasExtensionAttachments()) {
List extensionAttachments = page.getExtensionAttachments();
for (int i = 0; i < extensionAttachments.size(); i++) {
Object attObj = extensionAttachments.get(i);
if (attObj instanceof PSExtensionAttachment) {
PSExtensionAttachment attachment = (PSExtensionAttachment)attObj;
if (attachment instanceof PSCommentBefore) {
gen.commentln("%" + attachment.getContent());
} else if (attachment instanceof PSSetupCode) {
gen.writeln(attachment.getContent());
}
}
}
}
// Write any unwritten changes to page device dictionary
if (!pageDeviceDictionary.isEmpty()) {
String content = pageDeviceDictionary.getContent();
if (getPSUtil().isSafeSetPageDevice()) {
content += " SSPD";
} else {
content += " setpagedevice";
}
PSRenderingUtil.writeEnclosedExtensionAttachment(gen, new PSSetPageDevice(content));
}
if (rotate) {
gen.writeln(Math.round(pageHeight) + " 0 translate");
gen.writeln("90 rotate");
}
concatMatrix(1, 0, 0, -1, 0, pageHeight);
gen.writeDSCComment(DSCConstants.END_PAGE_SETUP);
//Process page
super.renderPage(page);
//Show page
gen.showPage();
gen.writeDSCComment(DSCConstants.PAGE_TRAILER);
if (page.hasExtensionAttachments()) {
List extensionAttachments = page.getExtensionAttachments();
for (int i = 0; i < extensionAttachments.size(); i++) {
Object attObj = extensionAttachments.get(i);
if (attObj instanceof PSExtensionAttachment) {
PSExtensionAttachment attachment = (PSExtensionAttachment)attObj;
if (attachment instanceof PSCommentAfter) {
gen.commentln("%" + attachment.getContent());
}
}
}
}
gen.getResourceTracker().writeResources(true, gen);
}
/** {@inheritDoc} */
protected void renderRegionViewport(RegionViewport port) {
if (port != null) {
comment("%FOPBeginRegionViewport: " + port.getRegionReference().getRegionName());
super.renderRegionViewport(port);
comment("%FOPEndRegionViewport");
}
}
/** Indicates the beginning of a text object. */
protected void beginTextObject() {
if (!inTextMode) {
saveGraphicsState();
writeln("BT");
inTextMode = true;
}
}
/** Indicates the end of a text object. */
protected void endTextObject() {
if (inTextMode) {
inTextMode = false; //set before restoreGraphicsState() to avoid recursion
writeln("ET");
restoreGraphicsState();
}
}
/** {@inheritDoc} */
public void renderText(TextArea area) {
renderInlineAreaBackAndBorders(area);
String fontkey = getInternalFontNameForArea(area);
int fontsize = area.getTraitAsInteger(Trait.FONT_SIZE);
// This assumes that *all* CIDFonts use a /ToUnicode mapping
Typeface tf = (Typeface) fontInfo.getFonts().get(fontkey);
//Determine position
int rx = currentIPPosition + area.getBorderAndPaddingWidthStart();
int bl = currentBPPosition + area.getOffset() + area.getBaselineOffset();
Color ct = (Color)area.getTrait(Trait.COLOR);
if (ct != null) {
try {
useColor(ct);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
beginTextObject();
writeln("1 0 0 -1 " + gen.formatDouble(rx / 1000f)
+ " " + gen.formatDouble(bl / 1000f) + " Tm");
super.renderText(area); //Updates IPD
renderTextDecoration(tf, fontsize, area, bl, rx);
}
/** {@inheritDoc} */
protected void renderWord(WordArea word) {
renderText((TextArea)word.getParentArea(), word.getWord(), word.getLetterAdjustArray());
super.renderWord(word);
}
/** {@inheritDoc} */
protected void renderSpace(SpaceArea space) {
AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
String s = space.getSpace();
char sp = s.charAt(0);
Font font = getFontFromArea(textArea);
int tws = (space.isAdjustable()
? ((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
+ 2 * textArea.getTextLetterSpaceAdjust()
: 0);
rmoveTo((font.getCharWidth(sp) + tws) / 1000f, 0);
super.renderSpace(space);
}
private Typeface getTypeface(String fontName) {
Typeface tf = (Typeface)fontInfo.getFonts().get(fontName);
if (tf instanceof LazyFont) {
tf = ((LazyFont)tf).getRealFont();
}
return tf;
}
private void renderText(AbstractTextArea area, String text, int[] letterAdjust) {
String fontkey = getInternalFontNameForArea(area);
int fontSize = area.getTraitAsInteger(Trait.FONT_SIZE);
Font font = getFontFromArea(area);
Typeface tf = getTypeface(font.getFontName());
SingleByteFont singleByteFont = null;
if (tf instanceof SingleByteFont) {
singleByteFont = (SingleByteFont)tf;
}
int textLen = text.length();
if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
int start = 0;
int currentEncoding = -1;
for (int i = 0; i < textLen; i++) {
char c = text.charAt(i);
char mapped = tf.mapChar(c);
int encoding = mapped / 256;
if (currentEncoding != encoding) {
if (i > 0) {
writeText(area, text, start, i - start, letterAdjust, fontSize, tf);
}
if (encoding == 0) {
useFont(fontkey, fontSize);
} else {
useFont(fontkey + "_" + Integer.toString(encoding), fontSize);
}
currentEncoding = encoding;
start = i;
}
}
writeText(area, text, start, textLen - start, letterAdjust, fontSize, tf);
} else {
useFont(fontkey, fontSize);
writeText(area, text, 0, textLen, letterAdjust, fontSize, tf);
}
}
private void writeText(AbstractTextArea area, String text, int start, int len,
int[] letterAdjust, int fontsize, Typeface tf) {
int end = start + len;
int initialSize = text.length();
initialSize += initialSize / 2;
StringBuffer sb = new StringBuffer(initialSize);
if (letterAdjust == null
&& area.getTextLetterSpaceAdjust() == 0
&& area.getTextWordSpaceAdjust() == 0) {
sb.append("(");
for (int i = start; i < end; i++) {
final char c = text.charAt(i);
final char mapped = (char)(tf.mapChar(c) % 256);
PSGenerator.escapeChar(mapped, sb);
}
sb.append(") t");
} else {
sb.append("(");
int[] offsets = new int[len];
for (int i = start; i < end; i++) {
final char c = text.charAt(i);
final char mapped = tf.mapChar(c);
char codepoint = (char)(mapped % 256);
int wordSpace;
if (CharUtilities.isAdjustableSpace(mapped)) {
wordSpace = area.getTextWordSpaceAdjust();
} else {
wordSpace = 0;
}
int cw = tf.getWidth(mapped, fontsize) / 1000;
int ladj = (letterAdjust != null && i < end - 1 ? letterAdjust[i + 1] : 0);
int tls = (i < end - 1 ? area.getTextLetterSpaceAdjust() : 0);
offsets[i - start] = cw + ladj + tls + wordSpace;
PSGenerator.escapeChar(codepoint, sb);
}
sb.append(")" + PSGenerator.LF + "[");
for (int i = 0; i < len; i++) {
if (i > 0) {
if (i % 8 == 0) {
sb.append(PSGenerator.LF);
} else {
sb.append(" ");
}
}
sb.append(gen.formatDouble(offsets[i] / 1000f));
}
sb.append("]" + PSGenerator.LF + "xshow");
}
writeln(sb.toString());
}
/** {@inheritDoc} */
protected List breakOutOfStateStack() {
try {
List breakOutList = new java.util.ArrayList();
PSState state;
while (true) {
if (breakOutList.size() == 0) {
endTextObject();
comment("------ break out!");
}
state = gen.getCurrentState();
if (!gen.restoreGraphicsState()) {
break;
}
breakOutList.add(0, state); //Insert because of stack-popping
}
return breakOutList;
} catch (IOException ioe) {
handleIOTrouble(ioe);
return null;
}
}
/** {@inheritDoc} */
protected void restoreStateStackAfterBreakOut(List breakOutList) {
try {
comment("------ restoring context after break-out...");
PSState state;
Iterator i = breakOutList.iterator();
while (i.hasNext()) {
state = (PSState)i.next();
saveGraphicsState();
state.reestablish(gen);
}
comment("------ done.");
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/**
* {@inheritDoc}
*/
protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
saveGraphicsState();
if (clippingRect != null) {
clipRect((float)clippingRect.getX() / 1000f,
(float)clippingRect.getY() / 1000f,
(float)clippingRect.getWidth() / 1000f,
(float)clippingRect.getHeight() / 1000f);
}
// multiply with current CTM
final double[] matrix = ctm.toArray();
matrix[4] /= 1000f;
matrix[5] /= 1000f;
concatMatrix(matrix);
}
/**
* {@inheritDoc}
*/
protected void endVParea() {
restoreGraphicsState();
}
/** {@inheritDoc} */
protected void renderBlockViewport(BlockViewport bv, List children) {
comment("%FOPBeginBlockViewport: " + bv.toString());
super.renderBlockViewport(bv, children);
comment("%FOPEndBlockViewport");
}
/** {@inheritDoc} */
protected void renderInlineParent(InlineParent ip) {
super.renderInlineParent(ip);
}
/** {@inheritDoc} */
public void renderLeader(Leader area) {
renderInlineAreaBackAndBorders(area);
int style = area.getRuleStyle();
int ruleThickness = area.getRuleThickness();
int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2);
int endx = currentIPPosition
+ area.getBorderAndPaddingWidthStart()
+ area.getIPD();
Color col = (Color)area.getTrait(Trait.COLOR);
endTextObject();
try {
borderPainter.drawLine(new Point(startx, starty), new Point(endx, starty),
ruleThickness, col, RuleStyle.valueOf(style));
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
super.renderLeader(area);
}
/**
* {@inheritDoc}
*/
public void renderImage(Image image, Rectangle2D pos) {
drawImage(image.getURL(), pos, image.getForeignAttributes());
}
/**
* {@inheritDoc}
*/
protected RendererContext createRendererContext(int x, int y, int width, int height,
Map foreignAttributes) {
RendererContext context = super.createRendererContext(
x, y, width, height, foreignAttributes);
context.setProperty(PSRendererContextConstants.PS_GENERATOR, this.gen);
context.setProperty(PSRendererContextConstants.PS_FONT_INFO, fontInfo);
return context;
}
/** {@inheritDoc} */
public String getMimeType() {
return MIME_TYPE;
}
/**
* Sets whether or not the safe set page device macro should be used
* (as opposed to directly invoking setpagedevice) when setting the
* postscript page device.
*
* This option is a useful option when you want to guard against the possibility
* of invalid/unsupported postscript key/values being placed in the page device.
*
* @param safeSetPageDevice setting to false and the renderer will make a
* standard "setpagedevice" call, setting to true will make a safe set page
* device macro call (default is false).
*/
public void setSafeSetPageDevice(boolean safeSetPageDevice) {
getPSUtil().setSafeSetPageDevice(safeSetPageDevice);
}
/**
* Sets whether or not PostScript Document Structuring Conventions (dsc) compliance are
* enforced.
* <p>
* It can cause problems (unwanted PostScript subsystem initgraphics/erasepage calls)
* on some printers when the pagedevice is set. If this causes problems on a
* particular implementation then use this setting with a 'false' value to try and
* minimize the number of setpagedevice calls in the postscript document output.
* <p>
* Set this value to false if you experience unwanted blank pages in your
* postscript output.
* @param dscCompliant boolean value (default is true)
*/
public void setDSCCompliant(boolean dscCompliant) {
getPSUtil().setDSCComplianceEnabled(dscCompliant);
}
}