/*
* Copyright 1999-2006 The Apache Software Foundation.
*
* Licensed 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.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.transform.Source;
// FOP
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.fop.apps.FOPException;
import org.apache.fop.area.Area;
import org.apache.fop.area.BlockViewport;
import org.apache.fop.area.CTM;
import org.apache.fop.area.LineArea;
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.ForeignObject;
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.ColorType;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontSetup;
import org.apache.fop.fonts.Typeface;
import org.apache.fop.image.EPSImage;
import org.apache.fop.image.FopImage;
import org.apache.fop.image.ImageFactory;
import org.apache.fop.image.XMLImage;
import org.apache.fop.render.Graphics2DAdapter;
import org.apache.fop.render.AbstractPathOrientedRenderer;
import org.apache.fop.render.ImageAdapter;
import org.apache.fop.render.RendererContext;
import org.apache.fop.render.ps.extensions.PSSetupCode;
import org.apache.fop.util.CharUtilities;
import org.w3c.dom.Document;
/**
* Renderer that renders to PostScript.
*
* 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.
*
* 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.).
*
* 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 Apache FOP Development Team
* @version $Id$
*/
public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAdapter {
/** The MIME type for PostScript */
public static final String MIME_TYPE = "application/postscript";
/** The application producing the PostScript */
private int currentPageNumber = 0;
private boolean enableComments = true;
private boolean autoRotateLandscape = false;
/** The PostScript generator used to output the PostScript */
protected PSGenerator gen;
private boolean ioTrouble = false;
private boolean inTextMode = false;
private boolean firstPageSequenceReceived = 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;
/**
* @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
*/
public void configure(Configuration cfg) throws ConfigurationException {
super.configure(cfg);
this.autoRotateLandscape = cfg.getChild("auto-rotate-landscape").getValueAsBoolean(false);
//Font configuration
List cfgFonts = FontSetup.buildFontListFromConfiguration(cfg);
if (this.fontList == null) {
this.fontList = cfgFonts;
} else {
this.fontList.addAll(cfgFonts);
}
}
/**
* 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) {
this.autoRotateLandscape = value;
}
/** @return true if the renderer is configured to rotate landscape pages */
public boolean isAutoRotateLandscape() {
return this.autoRotateLandscape;
}
/**
* @see org.apache.fop.render.Renderer#setUserAgent(FOUserAgent)
*/
public void setUserAgent(FOUserAgent agent) {
super.setUserAgent(agent);
}
/** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */
public Graphics2DAdapter getGraphics2DAdapter() {
return new PSGraphics2DAdapter(this);
}
/** @see org.apache.fop.render.Renderer#getImageAdapter() */
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) {
log.error("Error while writing to target file", ioe);
ioTrouble = true;
}
}
/**
* Write out a comment
* @param comment Comment to write
*/
protected void comment(String comment) {
if (this.enableComments) {
if (comment.startsWith("%")) {
writeln(comment);
} else {
writeln("%" + comment);
}
}
}
/**
* Make sure the cursor is in the right place.
*/
protected void movetoCurrPosition() {
moveTo(this.currentIPPosition, this.currentBPPosition);
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer#clip() */
protected void clip() {
writeln("clip newpath");
}
/**
* Clip an area.
* Write a clipping operation given coordinates in the current
* transform.
* @param x the x coordinate
* @param y the y coordinate
* @param width the width of the area
* @param height the height of the area
*/
protected void clipRect(float x, float y, float width, float height) {
try {
gen.defineRect(x, y, width, height);
clip();
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer#moveTo(float, float) */
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");
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer#lineTo(float, float) */
protected void lineTo(float x, float y) {
writeln(gen.formatDouble(x) + " " + gen.formatDouble(y) + " lineto");
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer#closePath() */
protected void closePath() {
writeln("cp");
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer */
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);
}
}
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer */
protected void updateColor(ColorType col, boolean fill) {
try {
useColor(col);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer */
protected void drawImage(String url, Rectangle2D pos) {
endTextObject();
url = ImageFactory.getURL(url);
ImageFactory fact = userAgent.getFactory().getImageFactory();
FopImage fopimage = fact.getImage(url, userAgent);
if (fopimage == null) {
return;
}
if (!fopimage.load(FopImage.DIMENSIONS)) {
return;
}
float x = (float)pos.getX() / 1000f;
x += currentIPPosition / 1000f;
float y = (float)pos.getY() / 1000f;
y += currentBPPosition / 1000f;
float w = (float)pos.getWidth() / 1000f;
float h = (float)pos.getHeight() / 1000f;
try {
String mime = fopimage.getMimeType();
if ("text/xml".equals(mime)) {
if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
return;
}
Document doc = ((XMLImage) fopimage).getDocument();
String ns = ((XMLImage) fopimage).getNameSpace();
renderDocument(doc, ns, pos);
} else if ("image/svg+xml".equals(mime)) {
if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
return;
}
Document doc = ((XMLImage) fopimage).getDocument();
String ns = ((XMLImage) fopimage).getNameSpace();
renderDocument(doc, ns, pos);
} else if (fopimage instanceof EPSImage) {
PSImageUtils.renderEPS((EPSImage)fopimage, x, y, w, h, gen);
} else {
PSImageUtils.renderBitmapImage(fopimage, x, y, w, h, gen);
}
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/** @see org.apache.fop.render.ImageAdapter */
public void paintImage(RenderedImage image, RendererContext context,
int x, int y, int width, int height) throws IOException {
float fx = (float)x / 1000f;
x += currentIPPosition / 1000f;
float fy = (float)y / 1000f;
y += currentBPPosition / 1000f;
float fw = (float)width / 1000f;
float fh = (float)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 {
//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);
}
}
/**
* Changes the currently used font.
* @param name name of the font
* @param size font size
*/
public void useFont(String name, int size) {
try {
gen.useFont(name, size / 1000f);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
private void useColor(ColorType col) throws IOException {
gen.useRGBColor(toColor(col));
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer#drawBackAndBorders(
* Area, float, float, float, float) */
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");
}
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer */
protected void drawBorderLine(float x1, float y1, float x2, float y2,
boolean horz, boolean startOrBefore, int style, ColorType col) {
try {
float w = x2 - x1;
float h = y2 - y1;
if ((w < 0) || (h < 0)) {
log.error("Negative extent received. Border won't be painted.");
return;
}
switch (style) {
case Constants.EN_DASHED:
useColor(col);
if (horz) {
float unit = Math.abs(2 * h);
int rep = (int)(w / unit);
if (rep % 2 == 0) {
rep++;
}
unit = w / rep;
gen.useDash("[" + unit + "] 0");
gen.useLineCap(0);
gen.useLineWidth(h);
float ym = y1 + (h / 2);
drawLine(x1, ym, x2, ym);
} else {
float unit = Math.abs(2 * w);
int rep = (int)(h / unit);
if (rep % 2 == 0) {
rep++;
}
unit = h / rep;
gen.useDash("[" + unit + "] 0");
gen.useLineCap(0);
gen.useLineWidth(w);
float xm = x1 + (w / 2);
drawLine(xm, y1, xm, y2);
}
break;
case Constants.EN_DOTTED:
useColor(col);
gen.useLineCap(1); //Rounded!
if (horz) {
float unit = Math.abs(2 * h);
int rep = (int)(w / unit);
if (rep % 2 == 0) {
rep++;
}
unit = w / rep;
gen.useDash("[0 " + unit + "] 0");
gen.useLineWidth(h);
float ym = y1 + (h / 2);
drawLine(x1, ym, x2, ym);
} else {
float unit = Math.abs(2 * w);
int rep = (int)(h / unit);
if (rep % 2 == 0) {
rep++;
}
unit = h / rep;
gen.useDash("[0 " + unit + "] 0");
gen.useLineWidth(w);
float xm = x1 + (w / 2);
drawLine(xm, y1, xm, y2);
}
break;
case Constants.EN_DOUBLE:
useColor(col);
gen.useDash(null);
if (horz) {
float h3 = h / 3;
gen.useLineWidth(h3);
float ym1 = y1 + (h3 / 2);
float ym2 = ym1 + h3 + h3;
drawLine(x1, ym1, x2, ym1);
drawLine(x1, ym2, x2, ym2);
} else {
float w3 = w / 3;
gen.useLineWidth(w3);
float xm1 = x1 + (w3 / 2);
float xm2 = xm1 + w3 + w3;
drawLine(xm1, y1, xm1, y2);
drawLine(xm2, y1, xm2, y2);
}
break;
case Constants.EN_GROOVE:
case Constants.EN_RIDGE:
float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
gen.useDash(null);
Color c = toColor(col);
if (horz) {
Color uppercol = lightenColor(c, -colFactor);
Color lowercol = lightenColor(c, colFactor);
float h3 = h / 3;
gen.useLineWidth(h3);
float ym1 = y1 + (h3 / 2);
gen.useRGBColor(uppercol);
drawLine(x1, ym1, x2, ym1);
gen.useRGBColor(c);
drawLine(x1, ym1 + h3, x2, ym1 + h3);
gen.useRGBColor(lowercol);
drawLine(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3);
} else {
Color leftcol = lightenColor(c, -colFactor);
Color rightcol = lightenColor(c, colFactor);
float w3 = w / 3;
gen.useLineWidth(w3);
float xm1 = x1 + (w3 / 2);
gen.useRGBColor(leftcol);
drawLine(xm1, y1, xm1, y2);
gen.useRGBColor(c);
drawLine(xm1 + w3, y1, xm1 + w3, y2);
gen.useRGBColor(rightcol);
drawLine(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2);
}
break;
case Constants.EN_INSET:
case Constants.EN_OUTSET:
colFactor = (style == EN_OUTSET ? 0.4f : -0.4f);
gen.useDash(null);
c = toColor(col);
if (horz) {
c = lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
gen.useLineWidth(h);
float ym1 = y1 + (h / 2);
gen.useRGBColor(c);
drawLine(x1, ym1, x2, ym1);
} else {
c = lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
gen.useLineWidth(w);
float xm1 = x1 + (w / 2);
gen.useRGBColor(c);
drawLine(xm1, y1, xm1, y2);
}
break;
case Constants.EN_HIDDEN:
break;
default:
useColor(col);
gen.useDash(null);
gen.useLineCap(0);
if (horz) {
gen.useLineWidth(h);
float ym = y1 + (h / 2);
drawLine(x1, ym, x2, ym);
} else {
gen.useLineWidth(w);
float xm = x1 + (w / 2);
drawLine(xm, y1, xm, y2);
}
}
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
}
/**
* @see org.apache.fop.render.Renderer#startRenderer(OutputStream)
*/
public void startRenderer(OutputStream outputStream)
throws IOException {
log.debug("rendering areas to PostScript");
//Setup for PostScript generation
this.gen = new PSGenerator(outputStream) {
/** Need to subclass PSGenerator to have better URI resolution */
public Source resolveURI(String uri) {
return userAgent.resolveURI(uri);
}
};
this.currentPageNumber = 0;
//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[] {PSGenerator.ATEND});
gen.writeDSCComment(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES,
new Object[] {PSGenerator.ATEND});
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()
}
/**
* @see org.apache.fop.render.Renderer#stopRenderer()
*/
public void stopRenderer() throws IOException {
//Notify resource usage for font which are not supplied
Map fonts = fontInfo.getUsedFonts();
Iterator e = fonts.keySet().iterator();
while (e.hasNext()) {
String key = (String)e.next();
//Typeface font = (Typeface)fonts.get(key);
PSResource res = (PSResource)this.fontResources.get(key);
boolean supplied = gen.isResourceSupplied(res);
if (!supplied) {
gen.notifyResourceUsage(res, true);
}
}
//Write trailer
gen.writeDSCComment(DSCConstants.TRAILER);
gen.writeDSCComment(DSCConstants.PAGES, new Integer(this.currentPageNumber));
gen.writeResources(false);
gen.writeDSCComment(DSCConstants.EOF);
gen.flush();
}
/** @see org.apache.fop.render.Renderer */
public void processOffDocumentItem(OffDocumentItem oDI) {
log.debug("Handling OffDocumentItem: " + oDI.getName());
if (oDI instanceof OffDocumentExtensionAttachment) {
ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment();
if (PSSetupCode.CATEGORY.equals(attachment.getCategory())) {
PSSetupCode setupCode = (PSSetupCode)attachment;
if (setupCodeList == null) {
setupCodeList = new java.util.ArrayList();
}
setupCodeList.add(setupCode);
}
}
super.processOffDocumentItem(oDI);
}
/** @see org.apache.fop.render.Renderer#startPageSequence(org.apache.fop.area.LineArea) */
public void startPageSequence(LineArea seqTitle) {
super.startPageSequence(seqTitle);
if (!firstPageSequenceReceived) {
//Do this only once, as soon as we have all the content for the Setup section!
try {
//Prolog
gen.writeDSCComment(DSCConstants.BEGIN_PROLOG);
PSProcSets.writeFOPStdProcSet(gen);
PSProcSets.writeFOPEPSProcSet(gen);
gen.writeDSCComment(DSCConstants.END_PROLOG);
//Setup
gen.writeDSCComment(DSCConstants.BEGIN_SETUP);
writeSetupCodeList(setupCodeList, "SetupCode");
this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo);
gen.writeln("FOPFonts begin");
gen.writeDSCComment(DSCConstants.END_SETUP);
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
firstPageSequenceReceived = true;
}
}
/**
* Formats and writes a List of PSSetupCode instances to the output stream.
* @param setupCodeList a List of PSSetupCode instances
* @param type the type of code section
*/
private void writeSetupCodeList(List setupCodeList, String type) throws IOException {
if (setupCodeList != null) {
Iterator i = setupCodeList.iterator();
while (i.hasNext()) {
PSSetupCode setupCode = (PSSetupCode)i.next();
gen.commentln("%FOPBegin" + type + ": ("
+ (setupCode.getName() != null ? setupCode.getName() : "")
+ ")");
LineNumberReader reader = new LineNumberReader(
new java.io.StringReader(setupCode.getContent()));
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.length() > 0) {
gen.writeln(line.trim());
}
}
gen.commentln("%FOPEnd" + type);
i.remove();
}
}
}
/**
* @see org.apache.fop.render.Renderer#renderPage(PageViewport)
*/
public void renderPage(PageViewport page)
throws IOException, FOPException {
log.debug("renderPage(): " + page);
this.currentPageNumber++;
gen.notifyStartNewPage();
gen.notifyResourceUsage(PSProcSets.STD_PROCSET, false);
gen.writeDSCComment(DSCConstants.PAGE, new Object[]
{page.getPageNumberString(),
new Integer(this.currentPageNumber)});
final Integer zero = new Integer(0);
final long pagewidth = Math.round(page.getViewArea().getWidth());
final long pageheight = Math.round(page.getViewArea().getHeight());
final double pspagewidth = pagewidth / 1000f;
final double pspageheight = pageheight / 1000f;
boolean rotate = false;
if (this.autoRotateLandscape && (pageheight < pagewidth)) {
rotate = true;
gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[]
{zero,
zero,
new Long(Math.round(pspageheight)),
new Long(Math.round(pspagewidth))});
gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[]
{zero,
zero,
new Double(pspageheight),
new Double(pspagewidth)});
gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Landscape");
} else {
gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[]
{zero,
zero,
new Long(Math.round(pspagewidth)),
new Long(Math.round(pspageheight))});
gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[]
{zero,
zero,
new Double(pspagewidth),
new Double(pspageheight)});
if (this.autoRotateLandscape) {
gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Portrait");
}
}
gen.writeDSCComment(DSCConstants.PAGE_RESOURCES,
new Object[] {PSGenerator.ATEND});
gen.commentln("%FOPSimplePageMaster: " + page.getSimplePageMasterName());
gen.writeDSCComment(DSCConstants.BEGIN_PAGE_SETUP);
//Handle PSSetupCode instances on simple-page-master
if (page.getExtensionAttachments() != null
&& page.getExtensionAttachments().size() > 0) {
List list = new java.util.ArrayList();
//Extract all PSSetupCode instances from the attachment list on the s-p-m
Iterator i = page.getExtensionAttachments().iterator();
while (i.hasNext()) {
ExtensionAttachment attachment = (ExtensionAttachment)i.next();
if (PSSetupCode.CATEGORY.equals(attachment.getCategory())) {
list.add(attachment);
}
}
writeSetupCodeList(list, "PageSetupCode");
}
if (rotate) {
gen.writeln(Math.round(pspageheight) + " 0 translate");
gen.writeln("90 rotate");
}
gen.writeln("<<");
gen.writeln("/PageSize ["
+ Math.round(pspagewidth) + " "
+ Math.round(pspageheight) + "]");
gen.writeln("/ImagingBBox null");
gen.writeln(">> setpagedevice");
concatMatrix(1, 0, 0, -1, 0, pageheight / 1000f);
gen.writeDSCComment(DSCConstants.END_PAGE_SETUP);
//Process page
super.renderPage(page);
writeln("showpage");
gen.writeDSCComment(DSCConstants.PAGE_TRAILER);
gen.writeResources(true);
gen.writeDSCComment(DSCConstants.END_PAGE);
}
/** @see org.apache.fop.render.AbstractRenderer */
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) {
writeln("ET");
restoreGraphicsState();
inTextMode = false;
}
}
/**
* @see org.apache.fop.render.AbstractRenderer#renderText(TextArea)
*/
public void renderText(TextArea area) {
renderInlineAreaBackAndBorders(area);
String fontname = getInternalFontNameForArea(area);
int fontsize = area.getTraitAsInteger(Trait.FONT_SIZE);
// This assumes that *all* CIDFonts use a /ToUnicode mapping
Typeface tf = (Typeface) fontInfo.getFonts().get(fontname);
//Determine position
int rx = currentIPPosition + area.getBorderAndPaddingWidthStart();
int bl = currentBPPosition + area.getOffset() + area.getBaselineOffset();
useFont(fontname, fontsize);
ColorType ct = (ColorType)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);
}
/**
* @see org.apache.fop.render.AbstractRenderer#renderWord(org.apache.fop.area.inline.WordArea)
*/
protected void renderWord(WordArea word) {
renderText((TextArea)word.getParentArea(), word.getWord(), word.getLetterAdjustArray());
super.renderWord(word);
}
/**
* @see org.apache.fop.render.AbstractRenderer#renderSpace(org.apache.fop.area.inline.SpaceArea)
*/
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 void renderText(AbstractTextArea area, String text, int[] letterAdjust) {
Font font = getFontFromArea(area);
Typeface tf = (Typeface) fontInfo.getFonts().get(font.getFontName());
int initialSize = text.length();
initialSize += initialSize / 2;
StringBuffer sb = new StringBuffer(initialSize);
int textLen = text.length();
if (letterAdjust == null
&& area.getTextLetterSpaceAdjust() == 0
&& area.getTextWordSpaceAdjust() == 0) {
sb.append("(");
for (int i = 0; i < textLen; i++) {
final char c = text.charAt(i);
final char mapped = tf.mapChar(c);
PSGenerator.escapeChar(mapped, sb);
}
sb.append(") t");
} else {
sb.append("(");
int[] offsets = new int[textLen];
for (int i = 0; i < textLen; i++) {
final char c = text.charAt(i);
final char mapped = tf.mapChar(c);
int wordSpace;
if (CharUtilities.isAdjustableSpace(mapped)) {
wordSpace = area.getTextWordSpaceAdjust();
} else {
wordSpace = 0;
}
int cw = tf.getWidth(mapped, font.getFontSize()) / 1000;
int ladj = (letterAdjust != null && i < textLen - 1 ? letterAdjust[i + 1] : 0);
int tls = (i < textLen - 1 ? area.getTextLetterSpaceAdjust() : 0);
offsets[i] = cw + ladj + tls + wordSpace;
PSGenerator.escapeChar(mapped, sb);
}
sb.append(")" + PSGenerator.LF + "[");
for (int i = 0; i < textLen; 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());
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer#breakOutOfStateStack() */
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;
}
}
/** @see org.apache.fop.render.AbstractPathOrientedRenderer */
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);
}
}
/**
* @see org.apache.fop.render.AbstractRenderer#startVParea(CTM, Rectangle2D)
*/
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);
}
/**
* @see org.apache.fop.render.AbstractRenderer#endVParea()
*/
protected void endVParea() {
endTextObject();
restoreGraphicsState();
}
/** @see org.apache.fop.render.AbstractRenderer */
protected void renderBlockViewport(BlockViewport bv, List children) {
comment("%FOPBeginBlockViewport: " + bv.toString());
super.renderBlockViewport(bv, children);
comment("%FOPEndBlockViewport");
}
/** @see org.apache.fop.render.AbstractRenderer */
protected void renderInlineParent(InlineParent ip) {
super.renderInlineParent(ip);
}
/**
* @see org.apache.fop.render.AbstractRenderer#renderLeader(org.apache.fop.area.inline.Leader)
*/
public void renderLeader(Leader area) {
renderInlineAreaBackAndBorders(area);
endTextObject();
saveGraphicsState();
int style = area.getRuleStyle();
float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
float starty = (currentBPPosition + area.getOffset()) / 1000f;
float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
+ area.getIPD()) / 1000f;
float ruleThickness = area.getRuleThickness() / 1000f;
ColorType col = (ColorType)area.getTrait(Trait.COLOR);
try {
switch (style) {
case EN_SOLID:
case EN_DASHED:
case EN_DOUBLE:
drawBorderLine(startx, starty, endx, starty + ruleThickness,
true, true, style, col);
break;
case EN_DOTTED:
clipRect(startx, starty, endx - startx, ruleThickness);
//This displaces the dots to the right by half a dot's width
//TODO There's room for improvement here
gen.concatMatrix(1, 0, 0, 1, ruleThickness / 2, 0);
drawBorderLine(startx, starty, endx, starty + ruleThickness,
true, true, style, col);
break;
case EN_GROOVE:
case EN_RIDGE:
float half = area.getRuleThickness() / 2000f;
gen.useRGBColor(lightenColor(toColor(col), 0.6f));
moveTo(startx, starty);
lineTo(endx, starty);
lineTo(endx, starty + 2 * half);
lineTo(startx, starty + 2 * half);
closePath();
gen.writeln(" fill newpath");
gen.useRGBColor(toColor(col));
if (style == EN_GROOVE) {
moveTo(startx, starty);
lineTo(endx, starty);
lineTo(endx, starty + half);
lineTo(startx + half, starty + half);
lineTo(startx, starty + 2 * half);
} else {
moveTo(endx, starty);
lineTo(endx, starty + 2 * half);
lineTo(startx, starty + 2 * half);
lineTo(startx, starty + half);
lineTo(endx - half, starty + half);
}
closePath();
gen.writeln(" fill newpath");
break;
default:
throw new UnsupportedOperationException("rule style not supported");
}
} catch (IOException ioe) {
handleIOTrouble(ioe);
}
restoreGraphicsState();
super.renderLeader(area);
}
/**
* @see org.apache.fop.render.AbstractRenderer#renderImage(Image, Rectangle2D)
*/
public void renderImage(Image image, Rectangle2D pos) {
drawImage(image.getURL(), pos);
}
/**
* @see org.apache.fop.render.AbstractRenderer#renderForeignObject(ForeignObject, Rectangle2D)
*/
public void renderForeignObject(ForeignObject fo, Rectangle2D pos) {
Document doc = fo.getDocument();
String ns = fo.getNameSpace();
renderDocument(doc, ns, pos);
}
/**
* Renders an XML document (SVG for example).
* @param doc DOM Document containing the XML document to be rendered
* @param ns Namespace for the XML document
* @param pos Position for the generated graphic/image
*/
public void renderDocument(Document doc, String ns, Rectangle2D pos) {
endTextObject();
RendererContext context;
context = new RendererContext(this, MIME_TYPE);
context.setUserAgent(userAgent);
context.setProperty(PSRendererContextConstants.PS_GENERATOR, this.gen);
context.setProperty(PSRendererContextConstants.PS_FONT_INFO, fontInfo);
context.setProperty(PSRendererContextConstants.WIDTH,
new Integer((int) pos.getWidth()));
context.setProperty(PSRendererContextConstants.HEIGHT,
new Integer((int) pos.getHeight()));
context.setProperty(PSRendererContextConstants.XPOS,
new Integer(currentIPPosition + (int) pos.getX()));
context.setProperty(PSRendererContextConstants.YPOS,
new Integer(currentBPPosition + (int) pos.getY()));
context.setProperty(PSRendererContextConstants.PAGE_VIEWPORT,
getCurrentPageViewport());
renderXML(context, doc, ns);
}
/** @see org.apache.fop.render.AbstractRenderer */
public String getMimeType() {
return MIME_TYPE;
}
}