]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
New addition: postscript renderer
authorKeiron Liddle <keiron@apache.org>
Fri, 22 Jun 2001 09:08:50 +0000 (09:08 +0000)
committerKeiron Liddle <keiron@apache.org>
Fri, 22 Jun 2001 09:08:50 +0000 (09:08 +0000)
Submitted by: Jeremias Maerki <jeremias.maerki@outline.ch>

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@194310 13f79535-47bb-0310-9956-ffa450edef68

src/org/apache/fop/render/ps/ASCII85EncodeFilter.java [new file with mode: 0644]
src/org/apache/fop/render/ps/ASCIIHexEncodeFilter.java [new file with mode: 0644]
src/org/apache/fop/render/ps/Filter.java [new file with mode: 0644]
src/org/apache/fop/render/ps/FilterThread.java [new file with mode: 0644]
src/org/apache/fop/render/ps/FlateEncodeFilter.java [new file with mode: 0644]
src/org/apache/fop/render/ps/PSRenderer.java [new file with mode: 0644]
src/org/apache/fop/render/ps/PSStream.java [new file with mode: 0644]

diff --git a/src/org/apache/fop/render/ps/ASCII85EncodeFilter.java b/src/org/apache/fop/render/ps/ASCII85EncodeFilter.java
new file mode 100644 (file)
index 0000000..1ba0f61
--- /dev/null
@@ -0,0 +1,149 @@
+/* $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;
+
+import java.io.*;
+
+public class ASCII85EncodeFilter implements Filter {
+
+    private static final char ASCII85_ZERO = 'z';
+    private static final char ASCII85_START = '!';
+    private static final char ASCII85_EOL = '\n';
+    private static final String ASCII85_EOD = "~>";
+    private static final String ENCODING = "US-ASCII";
+
+    private static final long base85_4 = 85;
+    private static final long base85_3 = base85_4 * base85_4;
+    private static final long base85_2 = base85_3 * base85_4;
+    private static final long base85_1 = base85_2 * base85_4;
+
+    protected ASCII85EncodeFilter() {
+    }
+
+    public long write(OutputStream out, byte[] buf, int len,
+                      long bw) throws IOException {
+        //Assumption: len<80
+        int line = (int)(bw % 80) + len;
+        if (line >= 80) {
+            int first = len - (line - 80);
+            out.write(buf, 0, first);
+            out.write(ASCII85_EOL);
+            out.write(buf, first, len - first);
+        } else {
+            out.write(buf, 0, len);
+        }
+        return bw + len;
+    }
+
+    public void doFilter(InputStream in,
+                         OutputStream out) throws IOException {
+        int total = 0;
+        int diff = 0;
+        long bw = 0;
+
+        // first encode the majority of the data
+        // each 4 byte group becomes a 5 byte group
+        byte[] data = new byte[4];
+        int bytes_read;
+        while ((bytes_read = in.read(data)) == data.length) {
+            long val = ((data[0] << 24) & 0xff000000L)// note: must have the L at the
+                       + ((data[1] << 16) & 0xff0000L)// end, otherwise you get into
+                       + ((data[2] << 8) & 0xff00L)// weird signed value problems
+                       + (data[3] & 0xffL); // cause we're using a full 32 bits
+            byte[] conv = convertWord(val);
+
+            bw = write(out, conv, conv.length, bw);
+        }
+
+        // now take care of the trailing few bytes.
+        // with n leftover bytes, we append 0 bytes to make a full group of 4
+        // then convert like normal (except not applying the special zero rule)
+        // and write out the first n+1 bytes from the result
+        if ((bytes_read < data.length) && (bytes_read >= 0)) {
+            int n = data.length - bytes_read;
+            byte[] lastdata = new byte[4];
+            int i = 0;
+            for (int j = 0; j < 4; j++) {
+                if (j < n) {
+                    lastdata[j] = data[i++];
+                } else {
+                    lastdata[j] = 0;
+                }
+            }
+
+            long val = ((lastdata[0] << 24) & 0xff000000L) +
+                       ((lastdata[1] << 16) & 0xff0000L) +
+                       ((lastdata[2] << 8) & 0xff00L) + (lastdata[3] & 0xffL);
+
+            byte[] conv;
+            // special rule for handling zeros at the end
+            if (val != 0) {
+                conv = convertWord(val);
+            } else {
+                conv = new byte[5];
+                for (int j = 0; j < 5; j++) {
+                    conv[j] = (byte)'!';
+                }
+            }
+            // assert n+1 <= 5
+            bw = write(out, conv, n + 1, bw);
+            // System.out.println("ASCII85 end of data was "+n+" bytes long");
+
+        }
+        // finally write the two character end of data marker
+        byte[] EOD = ASCII85_EOD.getBytes();
+        bw = write(out, EOD, EOD.length, bw);
+    }
+
+    /**
+     * This converts a 32 bit value (4 bytes) into 5 bytes using base 85.
+     * each byte in the result starts with zero at the '!' character so
+     * the resulting base85 number fits into printable ascii chars
+     *
+     * @param word the 32 bit unsigned (hence the long datatype) word
+     * @return 5 bytes (or a single byte of the 'z' character for word
+     * values of 0)
+     */
+    private byte[] convertWord(long word) {
+        word = word & 0xffffffff;
+        if (word < 0) {
+            word = -word;
+        }
+
+        if (word == 0) {
+            byte[] result = { (byte) ASCII85_ZERO };
+            return result;
+        } else {
+            byte c1 = (byte)((word / base85_1) & 0xFF);
+            byte c2 = (byte)(((word - (c1 * base85_1)) / base85_2) & 0xFF);
+            byte c3 = (byte)( ((word - (c1 * base85_1) - (c2 * base85_2)) /
+                               base85_3) & 0xFF);
+            byte c4 = (byte)( ((word - (c1 * base85_1) - (c2 * base85_2) -
+                                (c3 * base85_3)) / base85_4) & 0xFF);
+            byte c5 = (byte)( ((word - (c1 * base85_1) - (c2 * base85_2) -
+                                (c3 * base85_3) - (c4 * base85_4))) & 0xFF);
+
+            byte[] ret = {(byte)(c1 + ASCII85_START),
+                          (byte)(c2 + ASCII85_START), (byte)(c3 + ASCII85_START),
+                          (byte)(c4 + ASCII85_START), (byte)(c5 + ASCII85_START)};
+
+            for (int i = 0; i < ret.length; i++) {
+                if (ret[i] < 33 || ret[i] > 117) {
+                    System.out.println("Illegal char value "+
+                                       new Integer(ret[i]));
+                }
+            }
+            return ret;
+        }
+    }
+
+
+    public static InputStream filter(InputStream in) throws IOException {
+        ASCII85EncodeFilter myfilter = new ASCII85EncodeFilter();
+        return FilterThread.filter(in, myfilter);
+    }
+}
diff --git a/src/org/apache/fop/render/ps/ASCIIHexEncodeFilter.java b/src/org/apache/fop/render/ps/ASCIIHexEncodeFilter.java
new file mode 100644 (file)
index 0000000..ab447ae
--- /dev/null
@@ -0,0 +1,68 @@
+/* $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;
+
+import java.io.*;
+
+public class ASCIIHexEncodeFilter implements Filter {
+
+    private static final char ASCIIHEX_EOL = '\n';
+    private static final String ASCIIHEX_EOD = ">";
+    private static final String ENCODING = "US-ASCII";
+
+    protected ASCIIHexEncodeFilter() {
+    }
+
+    public long write(OutputStream out, byte[] buf, int len,
+                      long bw) throws IOException {
+        boolean last = false;
+        int pos = 0;
+        int rest = len;
+        while (rest > 0) {
+            int restofline = 80 - (int)((bw + pos) % 80);
+            if (rest < restofline) {
+                //last line
+                restofline = rest;
+                last = true;
+            }
+            if (restofline > 0) {
+                out.write(buf, pos, restofline);
+                pos += restofline;
+                if (!last)
+                    out.write(ASCIIHEX_EOL);
+            }
+            rest = len - pos;
+        }
+        return bw + len;
+    }
+
+    public void doFilter(InputStream in,
+                         OutputStream out) throws IOException {
+        long bw = 0;
+        byte[] buf = new byte[2048];
+        int bytes_read;
+        StringBuffer sb = new StringBuffer(2048 * 2);
+        while ((bytes_read = in.read(buf)) != -1) {
+            sb.setLength(0);
+            for (int i = 0; i < bytes_read; i++) {
+                int val = (int)(buf[i] & 0xFF);
+                if (val < 16)
+                    sb.append("0");
+                sb.append(Integer.toHexString(val));
+            }
+            bw = write(out, sb.toString().getBytes(ENCODING),
+                       bytes_read * 2, bw);
+        }
+        byte[] eod = ASCIIHEX_EOD.getBytes(ENCODING);
+        bw = write(out, eod, eod.length, bw);
+    }
+
+    public static InputStream filter(InputStream in) throws IOException {
+        ASCIIHexEncodeFilter myfilter = new ASCIIHexEncodeFilter();
+        return FilterThread.filter(in, myfilter);
+    }
+}
diff --git a/src/org/apache/fop/render/ps/Filter.java b/src/org/apache/fop/render/ps/Filter.java
new file mode 100644 (file)
index 0000000..bf5b17a
--- /dev/null
@@ -0,0 +1,18 @@
+/* $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;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+public interface Filter {
+
+    public void doFilter(InputStream in,
+                         OutputStream out) throws IOException;
+}
+
diff --git a/src/org/apache/fop/render/ps/FilterThread.java b/src/org/apache/fop/render/ps/FilterThread.java
new file mode 100644 (file)
index 0000000..e69f802
--- /dev/null
@@ -0,0 +1,45 @@
+/* $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;
+
+import java.io.*;
+
+public class FilterThread extends Thread {
+
+    private Filter filter;
+    private InputStream in;
+    private OutputStream out;
+
+    private FilterThread(Filter filter, InputStream in, OutputStream out) {
+        this.filter = filter;
+        this.in = in;
+        this.out = out;
+    }
+
+    public void run() {
+        try {
+            try {
+                this.filter.doFilter(in, out);
+                this.out.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        finally { this.filter = null;
+                  this.in = null;
+                  this.out = null;
+                } }
+
+    public static InputStream filter(InputStream in,
+                                     Filter filter) throws IOException {
+        PipedInputStream pin = new PipedInputStream();
+        PipedOutputStream pout = new PipedOutputStream(pin);
+        FilterThread thread = new FilterThread(filter, in, pout);
+        thread.start();
+        return pin;
+    }
+}
diff --git a/src/org/apache/fop/render/ps/FlateEncodeFilter.java b/src/org/apache/fop/render/ps/FlateEncodeFilter.java
new file mode 100644 (file)
index 0000000..1e93f38
--- /dev/null
@@ -0,0 +1,41 @@
+/* $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;
+
+import java.io.*;
+import java.util.zip.DeflaterOutputStream;
+
+public class FlateEncodeFilter implements Filter {
+
+    protected FlateEncodeFilter() {
+    }
+
+    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;
+    }
+
+    public void doFilter(InputStream in,
+                         OutputStream out) throws IOException {
+        DeflaterOutputStream dout = new DeflaterOutputStream(out);
+        copyStream(in, dout, 2048);
+        //dout.flush();
+        dout.close();
+    }
+
+    public static InputStream filter(InputStream in) throws IOException {
+        FlateEncodeFilter myfilter = new FlateEncodeFilter();
+        return FilterThread.filter(in, myfilter);
+    }
+}
diff --git a/src/org/apache/fop/render/ps/PSRenderer.java b/src/org/apache/fop/render/ps/PSRenderer.java
new file mode 100644 (file)
index 0000000..1f080ac
--- /dev/null
@@ -0,0 +1,856 @@
+/* $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;
+        }
+    }
+
+}
diff --git a/src/org/apache/fop/render/ps/PSStream.java b/src/org/apache/fop/render/ps/PSStream.java
new file mode 100644 (file)
index 0000000..6f0fb0e
--- /dev/null
@@ -0,0 +1,24 @@
+/* $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;
+
+import java.io.*;
+
+public class PSStream extends FilterOutputStream {
+
+    public PSStream(OutputStream out) {
+        super(out);
+    }
+
+    public void write(String cmd) throws IOException {
+        if (cmd.length() > 255)
+            throw new RuntimeException("PostScript command exceeded limit of 255 characters");
+        write(cmd.getBytes("US-ASCII"));
+        write('\n');
+    }
+
+}