--- /dev/null
+/* $Id$
+ * Copyright (C) 2001 The Apache Software Foundation. All rights reserved.
+ * For details on use and redistribution please refer to the
+ * LICENSE file included with these sources.
+ */
+
+package org.apache.fop.render.ps;
+
+// FOP
+import org.apache.fop.messaging.MessageHandler;
+import org.apache.fop.svg.SVGArea;
+import org.apache.fop.render.Renderer;
+import org.apache.fop.image.ImageArea;
+import org.apache.fop.image.FopImage;
+import org.apache.fop.image.FopImageException;
+import org.apache.fop.layout.*;
+import org.apache.fop.layout.inline.*;
+import org.apache.fop.datatypes.*;
+import org.apache.fop.fo.properties.*;
+import org.apache.fop.render.pdf.Font;
+
+// SVG
+import org.w3c.dom.svg.SVGSVGElement;
+import org.w3c.dom.svg.SVGDocument;
+
+// Java
+import java.io.*;
+import java.util.*;
+
+/**
+ * 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 I did everything right). 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>
+ * TODO: Character size/spacing, SVG Transcoder for Batik, configuration, move
+ * to PrintRenderer, maybe improve filters (I'm not very proud of them), add a
+ * RunLengthEncode filter (useful for Level 2 Postscript), Improve
+ * DocumentProcessColors stuff (probably needs to be configurable, then maybe
+ * add a color to grayscale conversion for bitmaps to make output smaller (See
+ * PCLRenderer), font embedding, support different character encodings, try to
+ * implement image transparency, positioning of images is wrong etc.
+ *
+ * @author Jeremias Märki
+ */
+public class PSRenderer implements Renderer {
+
+ /** the application producing the PostScript */
+ protected String producer;
+
+ int imagecount = 0; //DEBUG
+
+ private boolean enableComments = true;
+
+ /** the stream used to output the PostScript */
+ protected PSStream out;
+ private boolean ioTrouble = false;
+
+ private String currentFontName;
+ private int currentFontSize;
+ private int pageHeight;
+ private int pageWidth;
+ private int currentXPosition = 0;
+ private int currentYPosition = 0;
+ private int currentAreaContainerXPosition = 0;
+ private float currRed;
+ private float currGreen;
+ private float currBlue;
+
+ protected Hashtable options;
+
+
+ /**
+ * set the document's producer
+ *
+ * @param producer string indicating application producing the PostScript
+ */
+ public void setProducer(String producer) {
+ this.producer = producer;
+ }
+
+
+ /** set up renderer options */
+ public void setOptions(Hashtable options) {
+ this.options = options;
+ }
+
+
+ /**
+ * render the areas into PostScript
+ *
+ * @param areaTree the laid-out area tree
+ * @param stream the OutputStream to give the PostScript to
+ */
+ public void render(AreaTree areaTree,
+ OutputStream stream) throws IOException {
+ MessageHandler.logln("rendering areas to PostScript");
+ this.out = new PSStream(stream);
+ write("%!PS-Adobe-3.0");
+ write("%%Creator: "+this.producer);
+ write("%%Pages: "+areaTree.getPages().size());
+ write("%%DocumentProcessColors: Black");
+ write("%%DocumentSuppliedResources: procset FOPFonts");
+ write("%%EndComments");
+ write("%%BeginDefaults");
+ write("%%EndDefaults");
+ write("%%BeginProlog");
+ write("%%EndProlog");
+ write("%%BeginSetup");
+ writeFontDict(areaTree.getFontInfo());
+ write("%%EndSetup");
+ write("FOPFonts begin");
+
+ comment("% --- AreaTree begin");
+ Enumeration e = areaTree.getPages().elements();
+ while (e.hasMoreElements()) {
+ this.renderPage((Page) e.nextElement());
+ }
+ comment("% --- AreaTree end");
+ write("%%Trailer");
+ write("%%EOF");
+ this.out.flush();
+ MessageHandler.logln("written out PostScript");
+ }
+
+
+ /**
+ * write out a command
+ */
+ protected void write(String cmd) {
+ try {
+ out.write(cmd);
+ } catch (IOException e) {
+ if (!ioTrouble)
+ e.printStackTrace();
+ ioTrouble = true;
+ }
+ }
+
+
+ /**
+ * write out a comment
+ */
+ protected void comment(String comment) {
+ if (this.enableComments)
+ write(comment);
+ }
+
+
+ protected void writeFontDict(FontInfo fontInfo) {
+ write("%%BeginResource: procset FOPFonts");
+ write("%%Title: Font setup (shortcuts) for this file");
+ write("/FOPFonts 100 dict dup begin");
+ write("/bd{bind def}bind def");
+ write("/ld{load def}bd");
+ write("/M/moveto ld");
+ write("/RM/rmoveto ld");
+ write("/t/show ld");
+
+ write("/ux 0.0 def");
+ write("/uy 0.0 def");
+ //write("/cf /Helvetica def");
+ //write("/cs 12000 def");
+
+ //<font> <size> F
+ write("/F {");
+ write(" /Tp exch def");
+ //write(" currentdict exch get");
+ write(" /Tf exch def");
+ write(" Tf findfont Tp scalefont setfont");
+ write(" /cf Tf def /cs Tp def /cw ( ) stringwidth pop def");
+ write("} bd");
+
+ write("/ULS {currentpoint /uy exch def /ux exch def} bd");
+ write("/ULE {");
+ write(" /Tcx currentpoint pop def");
+ write(" gsave");
+ write(" newpath");
+ write(" cf findfont cs scalefont dup");
+ write(" /FontMatrix get 0 get /Ts exch def /FontInfo get dup");
+ write(" /UnderlinePosition get Ts mul /To exch def");
+ write(" /UnderlineThickness get Ts mul /Tt exch def");
+ write(" ux uy To add moveto Tcx uy To add lineto");
+ write(" Tt setlinewidth stroke");
+ write(" grestore");
+ write("} bd");
+
+ write("/OLE {");
+ write(" /Tcx currentpoint pop def");
+ write(" gsave");
+ write(" newpath");
+ write(" cf findfont cs scalefont dup");
+ write(" /FontMatrix get 0 get /Ts exch def /FontInfo get dup");
+ write(" /UnderlinePosition get Ts mul /To exch def");
+ write(" /UnderlineThickness get Ts mul /Tt exch def");
+ write(" ux uy To add cs add moveto Tcx uy To add cs add lineto");
+ write(" Tt setlinewidth stroke");
+ write(" grestore");
+ write("} bd");
+
+ write("/SOE {");
+ write(" /Tcx currentpoint pop def");
+ write(" gsave");
+ write(" newpath");
+ write(" cf findfont cs scalefont dup");
+ write(" /FontMatrix get 0 get /Ts exch def /FontInfo get dup");
+ write(" /UnderlinePosition get Ts mul /To exch def");
+ write(" /UnderlineThickness get Ts mul /Tt exch def");
+ write(" ux uy To add cs 10 mul 26 idiv add moveto Tcx uy To add cs 10 mul 26 idiv add lineto");
+ write(" Tt setlinewidth stroke");
+ write(" grestore");
+ write("} bd");
+
+
+
+ //write("/gfF1{/Helvetica findfont} bd");
+ //write("/gfF3{/Helvetica-Bold findfont} bd");
+ Hashtable fonts = fontInfo.getFonts();
+ Enumeration enum = fonts.keys();
+ while (enum.hasMoreElements()) {
+ String key = (String) enum.nextElement();
+ Font fm = (Font) fonts.get(key);
+ write("/"+key + " /"+fm.fontName() + " def");
+ }
+ write("end def");
+ write("%%EndResource");
+ enum = fonts.keys();
+ while (enum.hasMoreElements()) {
+ String key = (String) enum.nextElement();
+ Font fm = (Font) fonts.get(key);
+ write("/"+fm.fontName() + " findfont");
+ write("dup length dict begin");
+ write(" {1 index /FID ne {def} {pop pop} ifelse} forall");
+ write(" /Encoding ISOLatin1Encoding def");
+ write(" currentdict");
+ write("end");
+ write("/"+fm.fontName() + " exch definefont pop");
+ }
+ }
+
+ protected void movetoCurrPosition() {
+ write(this.currentXPosition + " "+this.currentYPosition + " M");
+ }
+
+ /**
+ * set up the font info
+ *
+ * @param fontInfo the font info object to set up
+ */
+ public void setupFontInfo(FontInfo fontInfo) {
+ /* use PDF's font setup to get PDF metrics */
+ org.apache.fop.render.pdf.FontSetup.setup(fontInfo);
+ }
+
+ /**
+ * render an area container to PostScript
+ *
+ * @param area the area container to render
+ */
+ public void renderAreaContainer(AreaContainer area) {
+ int saveY = this.currentYPosition;
+ int saveX = this.currentAreaContainerXPosition;
+ if (area.getPosition() == Position.ABSOLUTE) {
+ // Y position is computed assuming positive Y axis, adjust for negative postscript one
+ this.currentYPosition =
+ area.getYPosition() - 2 * area.getPaddingTop() -
+ 2 * area.getBorderTopWidth();
+ this.currentAreaContainerXPosition = area.getXPosition();
+ } else if (area.getPosition() == Position.RELATIVE) {
+ this.currentYPosition -= area.getYPosition();
+ this.currentAreaContainerXPosition += area.getXPosition();
+ } else if (area.getPosition() == Position.STATIC) {
+ this.currentYPosition -=
+ area.getPaddingTop() + area.getBorderTopWidth();
+ this.currentAreaContainerXPosition +=
+ area.getPaddingLeft() + area.getBorderLeftWidth();
+ }
+
+ this.currentXPosition = this.currentAreaContainerXPosition;
+
+ //comment("% --- AreaContainer begin");
+ doFrame(area);
+ Enumeration e = area.getChildren().elements();
+ while (e.hasMoreElements()) {
+ Box b = (Box) e.nextElement();
+ b.render(this);
+ }
+ //comment("% --- AreaContainer end");
+
+ if (area.getPosition() != Position.STATIC) {
+ this.currentYPosition = saveY;
+ this.currentAreaContainerXPosition = saveX;
+ } else {
+ this.currentYPosition -= area.getHeight();
+ }
+ }
+
+ /**
+ * render a body area container to PostScript
+ *
+ * @param area the body area container to render
+ */
+ public void renderBodyAreaContainer(BodyAreaContainer area) {
+ int saveY = this.currentYPosition;
+ int saveX = this.currentAreaContainerXPosition;
+
+ if (area.getPosition() == Position.ABSOLUTE) {
+ // Y position is computed assuming positive Y axis, adjust for negative postscript one
+ this.currentYPosition = area.getYPosition();
+ this.currentAreaContainerXPosition = area.getXPosition();
+ } else if (area.getPosition() == Position.RELATIVE) {
+ this.currentYPosition -= area.getYPosition();
+ this.currentAreaContainerXPosition += area.getXPosition();
+ }
+
+ this.currentXPosition = this.currentAreaContainerXPosition;
+ int w, h;
+ int rx = this.currentAreaContainerXPosition;
+ w = area.getContentWidth();
+ h = area.getContentHeight();
+ int ry = this.currentYPosition;
+
+ //comment("% --- BodyAreaContainer begin");
+ doFrame(area);
+ //movetoCurrPosition();
+
+ Enumeration e = area.getChildren().elements();
+ while (e.hasMoreElements()) {
+ Box b = (Box) e.nextElement();
+ b.render(this);
+ }
+ //comment("% --- BodyAreaContainer end");
+
+ if (area.getPosition() != Position.STATIC) {
+ this.currentYPosition = saveY;
+ this.currentAreaContainerXPosition = saveX;
+ } else {
+ this.currentYPosition -= area.getHeight();
+ }
+ }
+
+ /**
+ * render a span area to PostScript
+ *
+ * @param area the span area to render
+ */
+ public void renderSpanArea(SpanArea area) {
+ //comment("% --- SpanArea begin");
+ Enumeration e = area.getChildren().elements();
+ while (e.hasMoreElements()) {
+ Box b = (Box) e.nextElement();
+ b.render(this);
+ }
+ //comment("% --- SpanArea end");
+ }
+
+ /**
+ * render a block area to PostScript
+ *
+ * @param area the block area to render
+ */
+ public void renderBlockArea(BlockArea area) {
+ //comment("% --- BlockArea begin");
+ doFrame(area);
+ Enumeration e = area.getChildren().elements();
+ while (e.hasMoreElements()) {
+ Box b = (Box) e.nextElement();
+ b.render(this);
+ }
+ //comment("% --- BlockArea end");
+ }
+
+ /**
+ * render a display space to PostScript
+ *
+ * @param space the space to render
+ */
+ public void renderDisplaySpace(DisplaySpace space) {
+ //write("% --- DisplaySpace size="+space.getSize());
+ this.currentYPosition -= space.getSize();
+ movetoCurrPosition();
+ }
+
+ /** render a foreign object area */
+ public void renderForeignObjectArea(ForeignObjectArea area) {
+ // if necessary need to scale and align the content
+ area.getObject().render(this);
+ }
+
+ /**
+ * render an SVG area to PostScript
+ *
+ * @param area the area to render
+ */
+ public void renderSVGArea(SVGArea area) {
+ int x = this.currentXPosition;
+ int y = this.currentYPosition;
+ SVGSVGElement svg =
+ ((SVGDocument) area.getSVGDocument()).getRootElement();
+ int w = (int)(svg.getWidth().getBaseVal().getValue() * 1000);
+ int h = (int)(svg.getHeight().getBaseVal().getValue() * 1000);
+
+ comment("% --- SVG Area");
+ /**@todo Implement SVG */
+ comment("% --- SVG Area end");
+ movetoCurrPosition();
+ }
+
+ public void renderBitmap(FopImage img, int x, int y, int w, int h) {
+ try {
+ boolean iscolor = img.getColorSpace().getColorSpace() !=
+ ColorSpace.DEVICE_GRAY;
+ byte[] imgmap = img.getBitmaps();
+
+ write("gsave");
+ write("/DeviceRGB setcolorspace");
+ write(x + " "+(y - h) + " translate");
+ write(w + " "+h + " scale");
+ write("<<");
+ write(" /ImageType 1");
+ write(" /Width "+img.getWidth());
+ write(" /Height "+img.getHeight());
+ write(" /BitsPerComponent 8");
+ if (iscolor) {
+ write(" /Decode [0 1 0 1 0 1]");
+ } else {
+ write(" /Decode [0 1]");
+ }
+ //Setup scanning for left-to-right and top-to-bottom
+ write(" /ImageMatrix ["+img.getWidth() + " 0 0 -"+
+ img.getHeight() + " 0 "+img.getHeight() + "]");
+ write(" /DataSource currentfile /ASCII85Decode filter /FlateDecode filter");
+ //write(" /DataSource currentfile /ASCIIHexDecode filter /FlateDecode filter");
+ //write(" /DataSource currentfile /ASCII85Decode filter /RunLengthDecode filter");
+ //write(" /DataSource currentfile /ASCIIHexDecode filter /RunLengthDecode filter");
+ //write(" /DataSource currentfile /ASCIIHexDecode filter");
+ //write(" /DataSource currentfile /ASCII85Decode filter");
+ //write(" /DataSource currentfile /RunLengthDecode filter");
+ write(">>");
+ write("image");
+
+ /*
+ for (int y=0; y<img.getHeight(); y++) {
+ int indx = y * img.getWidth();
+ if (iscolor) indx*= 3;
+ for (int x=0; x<img.getWidth(); x++) {
+ if (iscolor) {
+ writeASCIIHex(imgmap[indx++] & 0xFF);
+ writeASCIIHex(imgmap[indx++] & 0xFF);
+ writeASCIIHex(imgmap[indx++] & 0xFF);
+ } else {
+ writeASCIIHex(imgmap[indx++] & 0xFF);
+ }
+ }
+ }*/
+ try {
+ //imgmap[0] = 1;
+ InputStream bain = new ByteArrayInputStream(imgmap);
+ InputStream in;
+ in = bain;
+ in = FlateEncodeFilter.filter(in);
+ //in = RunLengthEncodeFilter.filter(in);
+ //in = ASCIIHexEncodeFilter.filter(in);
+ in = ASCII85EncodeFilter.filter(in);
+ copyStream(in, this.out);
+ } catch (IOException e) {
+ if (!ioTrouble)
+ e.printStackTrace();
+ ioTrouble = true;
+ }
+
+ write("");
+ write("grestore");
+ }
+ catch (FopImageException e) {
+ e.printStackTrace();
+ MessageHandler.errorln(
+ "PSRenderer.renderImageArea(): Error rendering bitmap (" +
+ e.toString() + ")");
+ }
+ }
+
+ /**
+ * render an image area to PostScript
+ *
+ * @param area the area to render
+ */
+ public void renderImageArea(ImageArea area) {
+ int x = this.currentAreaContainerXPosition + area.getXOffset();
+ int y = this.currentYPosition;
+ int w = area.getContentWidth();
+ int h = area.getHeight();
+ this.currentYPosition -= area.getHeight();
+
+ imagecount++;
+ //if (imagecount!=4) return;
+
+ comment("% --- ImageArea");
+ renderBitmap(area.getImage(), x, y, w, h);
+ comment("% --- ImageArea end");
+ }
+
+ private long copyStream(InputStream in, OutputStream out,
+ int bufferSize) throws IOException {
+ long bytes_total = 0;
+ byte[] buf = new byte[bufferSize];
+ int bytes_read;
+ while ((bytes_read = in.read(buf)) != -1) {
+ bytes_total += bytes_read;
+ out.write(buf, 0, bytes_read);
+ }
+ return bytes_total;
+ }
+
+
+ private long copyStream(InputStream in,
+ OutputStream out) throws IOException {
+ return copyStream(in, out, 4096);
+ }
+
+ /**
+ * render an inline area to PostScript
+ *
+ * @param area the area to render
+ */
+ public void renderWordArea(WordArea area) {
+ FontState fs = area.getFontState();
+ String fontWeight = fs.getFontWeight();
+ StringBuffer sb = new StringBuffer();
+ String s = area.getText();
+ int l = s.length();
+ for (int i = 0; i < l; i++) {
+ char ch = s.charAt(i);
+ char mch = fs.mapChar(ch);
+ if (mch > 127) {
+ sb = sb.append("\\"+Integer.toOctalString(mch));
+ } else {
+ String escape = "\\()[]{}";
+ if (escape.indexOf(mch) >= 0) {
+ sb.append("\\");
+ }
+ sb = sb.append(mch);
+ }
+ }
+ //System.out.println("["+s+"] --> ["+sb.toString()+"]");
+
+ //comment("% --- InlineArea font-weight="+fontWeight+": " + sb.toString());
+ useFont(fs.getFontName(), fs.getFontSize());
+ useColor(area.getRed(), area.getGreen(), area.getBlue());
+ if (area.getUnderlined() || area.getLineThrough() ||
+ area.getOverlined())
+ write("ULS");
+ write("("+sb.toString() + ") t");
+ if (area.getUnderlined())
+ write("ULE");
+ if (area.getLineThrough())
+ write("SOE");
+ if (area.getOverlined())
+ write("OLE");
+ this.currentXPosition += area.getContentWidth();
+ }
+
+ public void useFont(String name, int size) {
+ if ((currentFontName != name) || (currentFontSize != size)) {
+ write(name + " "+size + " F");
+ currentFontName = name;
+ currentFontSize = size;
+ }
+ }
+
+ /**
+ * render an inline space to PostScript
+ *
+ * @param space the space to render
+ */
+ public void renderInlineSpace(InlineSpace space) {
+ //write("% --- InlineSpace size="+space.getSize());
+ this.currentXPosition += space.getSize();
+ if (space.getUnderlined() || space.getLineThrough() ||
+ space.getOverlined())
+ write("ULS");
+ write(space.getSize() + " 0 RM");
+ if (space.getUnderlined())
+ write("ULE");
+ if (space.getLineThrough())
+ write("SOE");
+ if (space.getOverlined())
+ write("OLE");
+ }
+
+ /**
+ * render a line area to PostScript
+ *
+ * @param area the area to render
+ */
+ public void renderLineArea(LineArea area) {
+ int rx = this.currentAreaContainerXPosition + area.getStartIndent();
+ int ry = this.currentYPosition;
+ int w = area.getContentWidth();
+ int h = area.getHeight();
+
+ this.currentYPosition -= area.getPlacementOffset();
+ this.currentXPosition = rx;
+
+ int bl = this.currentYPosition;
+ movetoCurrPosition();
+
+ String fontWeight = area.getFontState().getFontWeight();
+ //comment("% --- LineArea begin font-weight="+fontWeight);
+ Enumeration e = area.getChildren().elements();
+ while (e.hasMoreElements()) {
+ Box b = (Box) e.nextElement();
+ this.currentYPosition = ry - area.getPlacementOffset();
+ b.render(this);
+ }
+ //comment("% --- LineArea end");
+
+ this.currentYPosition = ry - h;
+ this.currentXPosition = rx;
+ }
+
+ /**
+ * render a page to PostScript
+ *
+ * @param page the page to render
+ */
+ public void renderPage(Page page) {
+ BodyAreaContainer body;
+ AreaContainer before, after;
+ write("%%Page: "+page.getNumber() + " "+page.getNumber());
+ write("%%BeginPageSetup");
+ write("FOPFonts begin");
+ write("0.001 0.001 scale");
+ write("%%EndPageSetup");
+ body = page.getBody();
+ before = page.getBefore();
+ after = page.getAfter();
+ if (before != null) {
+ renderAreaContainer(before);
+ }
+ renderBodyAreaContainer(body);
+ if (after != null) {
+ renderAreaContainer(after);
+ }
+ write("showpage");
+ write("%%PageTrailer");
+ write("%%EndPage");
+ }
+
+ /**
+ * render a leader area to PostScript
+ *
+ * @param area the area to render
+ */
+ public void renderLeaderArea(LeaderArea area) {
+ int rx = this.currentXPosition;
+ int ry = this.currentYPosition;
+ int w = area.getContentWidth();
+ int th = area.getRuleThickness();
+ int th2 = th / 2;
+ int th3 = th / 3;
+ int th4 = th / 4;
+
+ switch (area.getLeaderPattern()) {
+ case LeaderPattern.SPACE:
+ //NOP
+ break;
+ case LeaderPattern.RULE:
+ if (area.getRuleStyle() == RuleStyle.NONE)
+ break;
+ useColor(area.getRed(), area.getGreen(), area.getBlue());
+ write("gsave");
+ write("0 setlinecap");
+ switch (area.getRuleStyle()) {
+ case RuleStyle.DOTTED:
+ write("newpath");
+ write("[1000 3000] 0 setdash");
+ write(th + " setlinewidth");
+ write(rx + " "+ry + " M");
+ write(w + " 0 rlineto");
+ useColor(area.getRed(), area.getGreen(),
+ area.getBlue());
+ write("stroke");
+ break;
+ case RuleStyle.DASHED:
+ write("newpath");
+ write("[3000 3000] 0 setdash");
+ write(th + " setlinewidth");
+ write(rx + " "+ry + " M");
+ write(w + " 0 rlineto");
+ useColor(area.getRed(), area.getGreen(),
+ area.getBlue());
+ write("stroke");
+ break;
+ case RuleStyle.SOLID:
+ write("newpath");
+ write(th + " setlinewidth");
+ write(rx + " "+ry + " M");
+ write(w + " 0 rlineto");
+ useColor(area.getRed(), area.getGreen(),
+ area.getBlue());
+ write("stroke");
+ break;
+ case RuleStyle.DOUBLE:
+ write("newpath");
+ write(th3 + " setlinewidth");
+ write(rx + " "+(ry - th3) + " M");
+ write(w + " 0 rlineto");
+ write(rx + " "+(ry + th3) + " M");
+ write(w + " 0 rlineto");
+ useColor(area.getRed(), area.getGreen(),
+ area.getBlue());
+ write("stroke");
+ break;
+ case RuleStyle.GROOVE:
+ write(th2 + " setlinewidth");
+ write("newpath");
+ write(rx + " "+(ry - th4) + " M");
+ write(w + " 0 rlineto");
+ useColor(area.getRed(), area.getGreen(),
+ area.getBlue());
+ write("stroke");
+ write("newpath");
+ write(rx + " "+(ry + th4) + " M");
+ write(w + " 0 rlineto");
+ useColor(1, 1, 1); //white
+ write("stroke");
+ break;
+ case RuleStyle.RIDGE:
+ write(th2 + " setlinewidth");
+ write("newpath");
+ write(rx + " "+(ry - th4) + " M");
+ write(w + " 0 rlineto");
+ useColor(1, 1, 1); //white
+ write("stroke");
+ write("newpath");
+ write(rx + " "+(ry + th4) + " M");
+ write(w + " 0 rlineto");
+ useColor(area.getRed(), area.getGreen(),
+ area.getBlue());
+ write("stroke");
+ break;
+ }
+ write("grestore");
+ break;
+ case LeaderPattern.DOTS:
+ comment("% --- Leader dots NYI");
+ MessageHandler.errorln("Leader dots: Not yet implemented");
+ break;
+ case LeaderPattern.USECONTENT:
+ comment("% --- Leader use-content NYI");
+ MessageHandler.errorln("Leader use-content: Not yet implemented");
+ break;
+ }
+ this.currentXPosition += area.getContentWidth();
+ write(area.getContentWidth() + " 0 RM");
+ }
+
+ private void doFrame(Area area) {
+ int w, h;
+ int rx = this.currentAreaContainerXPosition;
+ w = area.getContentWidth();
+ BorderAndPadding bap = area.getBorderAndPadding();
+
+ if (area instanceof BlockArea)
+ rx += ((BlockArea) area).getStartIndent();
+
+ h = area.getContentHeight();
+ int ry = this.currentYPosition;
+
+ rx = rx - area.getPaddingLeft();
+ ry = ry + area.getPaddingTop();
+ w = w + area.getPaddingLeft() + area.getPaddingRight();
+ h = h + area.getPaddingTop() + area.getPaddingBottom();
+
+ rx = rx - area.getBorderLeftWidth();
+ ry = ry + area.getBorderTopWidth();
+ w = w + area.getBorderLeftWidth() + area.getBorderRightWidth();
+ h = h + area.getBorderTopWidth() + area.getBorderBottomWidth();
+
+ //Create a textrect with these dimensions.
+ //The y co-ordinate is measured +ve downwards so subtract page-height
+
+ ColorType bg = area.getBackgroundColor();
+ if ((bg != null) && (bg.alpha() == 0)) {
+ write("newpath");
+ write(rx + " "+ry + " M");
+ write(w + " 0 rlineto");
+ write("0 "+(-h) + " rlineto");
+ write((-w) + " 0 rlineto");
+ write("0 "+h + " rlineto");
+ write("closepath");
+ useColor(bg);
+ write("fill");
+ }
+
+
+ if (area.getBorderTopWidth() != 0) {
+ write("newpath");
+ write(rx + " "+ry + " M");
+ write(w + " 0 rlineto");
+ write(area.getBorderTopWidth() + " setlinewidth");
+ write("0 setlinecap");
+ useColor(bap.getBorderColor(BorderAndPadding.TOP));
+ write("stroke");
+ }
+ if (area.getBorderLeftWidth() != 0) {
+ write("newpath");
+ write(rx + " "+ry + " M");
+ write("0 "+(-h) + " rlineto");
+ write(area.getBorderLeftWidth() + " setlinewidth");
+ write("0 setlinecap");
+ useColor(bap.getBorderColor(BorderAndPadding.LEFT));
+ write("stroke");
+ }
+ if (area.getBorderRightWidth() != 0) {
+ write("newpath");
+ write((rx + w) + " "+ry + " M");
+ write("0 "+(-h) + " rlineto");
+ write(area.getBorderRightWidth() + " setlinewidth");
+ write("0 setlinecap");
+ useColor(bap.getBorderColor(BorderAndPadding.RIGHT));
+ write("stroke");
+ }
+ if (area.getBorderBottomWidth() != 0) {
+ write("newpath");
+ write(rx + " "+(ry - h) + " M");
+ write(w + " 0 rlineto");
+ write(area.getBorderBottomWidth() + " setlinewidth");
+ write("0 setlinecap");
+ useColor(bap.getBorderColor(BorderAndPadding.BOTTOM));
+ write("stroke");
+ }
+ }
+
+ private void useColor(ColorType col) {
+ useColor(col.red(), col.green(), col.blue());
+ }
+
+ private void useColor(float red, float green, float blue) {
+ if ((red != currRed) || (green != currGreen) ||
+ (blue != currBlue)) {
+ write(red + " "+green + " "+blue + " setrgbcolor");
+ currRed = red;
+ currGreen = green;
+ currBlue = blue;
+ }
+ }
+
+}