jdk/src/share/classes/sun/print/PSPrinterJob.java
changeset 2 90ce3da70b43
child 438 2ae294e4518c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/print/PSPrinterJob.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,2211 @@
+/*
+ * Copyright 1998-2007 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.print;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.GraphicsEnvironment;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.HeadlessException;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import java.awt.image.BufferedImage;
+
+import java.awt.font.FontRenderContext;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+
+import java.awt.image.BufferedImage;
+
+import java.awt.print.Pageable;
+import java.awt.print.PageFormat;
+import java.awt.print.Paper;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterIOException;
+import java.awt.print.PrinterJob;
+
+import javax.print.DocFlavor;
+import javax.print.PrintService;
+import javax.print.StreamPrintService;
+import javax.print.attribute.HashPrintRequestAttributeSet;
+import javax.print.attribute.PrintRequestAttributeSet;
+import javax.print.attribute.standard.Chromaticity;
+import javax.print.attribute.standard.Copies;
+import javax.print.attribute.standard.Destination;
+import javax.print.attribute.standard.DialogTypeSelection;
+import javax.print.attribute.standard.JobName;
+import javax.print.attribute.standard.Sides;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.CharConversionException;
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Properties;
+
+import sun.awt.CharsetString;
+import sun.awt.FontConfiguration;
+import sun.awt.FontDescriptor;
+import sun.awt.PlatformFont;
+import sun.awt.SunToolkit;
+
+import java.nio.charset.*;
+import java.nio.CharBuffer;
+import java.nio.ByteBuffer;
+
+//REMIND: Remove use of this class when IPPPrintService is moved to share directory.
+import java.lang.reflect.Method;
+
+/**
+ * A class which initiates and executes a PostScript printer job.
+ *
+ * @author Richard Blanchard
+ */
+public class PSPrinterJob extends RasterPrinterJob {
+
+ /* Class Constants */
+
+    /**
+     * Passed to the <code>setFillMode</code>
+     * method this value forces fills to be
+     * done using the even-odd fill rule.
+     */
+    protected static final int FILL_EVEN_ODD = 1;
+
+    /**
+     * Passed to the <code>setFillMode</code>
+     * method this value forces fills to be
+     * done using the non-zero winding rule.
+     */
+    protected static final int FILL_WINDING = 2;
+
+    /* PostScript has a 64K maximum on its strings.
+     */
+    private static final int MAX_PSSTR = (1024 * 64 - 1);
+
+    private static final int RED_MASK = 0x00ff0000;
+    private static final int GREEN_MASK = 0x0000ff00;
+    private static final int BLUE_MASK = 0x000000ff;
+
+    private static final int RED_SHIFT = 16;
+    private static final int GREEN_SHIFT = 8;
+    private static final int BLUE_SHIFT = 0;
+
+    private static final int LOWNIBBLE_MASK = 0x0000000f;
+    private static final int HINIBBLE_MASK =  0x000000f0;
+    private static final int HINIBBLE_SHIFT = 4;
+    private static final byte hexDigits[] = {
+        (byte)'0', (byte)'1', (byte)'2', (byte)'3',
+        (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+        (byte)'8', (byte)'9', (byte)'A', (byte)'B',
+        (byte)'C', (byte)'D', (byte)'E', (byte)'F'
+    };
+
+    private static final int PS_XRES = 300;
+    private static final int PS_YRES = 300;
+
+    private static final String ADOBE_PS_STR =  "%!PS-Adobe-3.0";
+    private static final String EOF_COMMENT =   "%%EOF";
+    private static final String PAGE_COMMENT =  "%%Page: ";
+
+    private static final String READIMAGEPROC = "/imStr 0 def /imageSrc " +
+        "{currentfile /ASCII85Decode filter /RunLengthDecode filter " +
+        " imStr readstring pop } def";
+
+    private static final String COPIES =        "/#copies exch def";
+    private static final String PAGE_SAVE =     "/pgSave save def";
+    private static final String PAGE_RESTORE =  "pgSave restore";
+    private static final String SHOWPAGE =      "showpage";
+    private static final String IMAGE_SAVE =    "/imSave save def";
+    private static final String IMAGE_STR =     " string /imStr exch def";
+    private static final String IMAGE_RESTORE = "imSave restore";
+
+    private static final String COORD_PREP =    " 0 exch translate "
+                                              + "1 -1 scale"
+                                              + "[72 " + PS_XRES + " div "
+                                              + "0 0 "
+                                              + "72 " + PS_YRES + " div "
+                                              + "0 0]concat";
+
+    private static final String SetFontName = "F";
+
+    private static final String DrawStringName = "S";
+
+    /**
+     * The PostScript invocation to fill a path using the
+     * even-odd rule. (eofill)
+     */
+    private static final String EVEN_ODD_FILL_STR = "EF";
+
+    /**
+     * The PostScript invocation to fill a path using the
+     * non-zero winding rule. (fill)
+     */
+    private static final String WINDING_FILL_STR = "WF";
+
+    /**
+     * The PostScript to set the clip to be the current path
+     * using the even odd rule. (eoclip)
+     */
+    private static final String EVEN_ODD_CLIP_STR = "EC";
+
+    /**
+     * The PostScript to set the clip to be the current path
+     * using the non-zero winding rule. (clip)
+     */
+    private static final String WINDING_CLIP_STR = "WC";
+
+    /**
+     * Expecting two numbers on the PostScript stack, this
+     * invocation moves the current pen position. (moveto)
+     */
+    private static final String MOVETO_STR = " M";
+    /**
+     * Expecting two numbers on the PostScript stack, this
+     * invocation draws a PS line from the current pen
+     * position to the point on the stack. (lineto)
+     */
+    private static final String LINETO_STR = " L";
+
+    /**
+     * This PostScript operator takes two control points
+     * and an ending point and using the current pen
+     * position as a starting point adds a bezier
+     * curve to the current path. (curveto)
+     */
+    private static final String CURVETO_STR = " C";
+
+    /**
+     * The PostScript to pop a state off of the printer's
+     * gstate stack. (grestore)
+     */
+    private static final String GRESTORE_STR = "R";
+    /**
+     * The PostScript to push a state on to the printer's
+     * gstate stack. (gsave)
+     */
+    private static final String GSAVE_STR = "G";
+
+    /**
+     * Make the current PostScript path an empty path. (newpath)
+     */
+    private static final String NEWPATH_STR = "N";
+
+    /**
+     * Close the current subpath by generating a line segment
+     * from the current position to the start of the subpath. (closepath)
+     */
+    private static final String CLOSEPATH_STR = "P";
+
+    /**
+     * Use the three numbers on top of the PS operator
+     * stack to set the rgb color. (setrgbcolor)
+     */
+    private static final String SETRGBCOLOR_STR = " SC";
+
+    /**
+     * Use the top number on the stack to set the printer's
+     * current gray value. (setgray)
+     */
+    private static final String SETGRAY_STR = " SG";
+
+ /* Instance Variables */
+
+   private int mDestType;
+
+   private String mDestination = "lp";
+
+   private boolean mNoJobSheet = false;
+
+   private String mOptions;
+
+   private Font mLastFont;
+
+   private Color mLastColor;
+
+   private Shape mLastClip;
+
+   private AffineTransform mLastTransform;
+
+   /* non-null if printing EPS for Java Plugin */
+   private EPSPrinter epsPrinter = null;
+
+   /**
+    * The metrics for the font currently set.
+    */
+   FontMetrics mCurMetrics;
+
+   /**
+    * The output stream to which the generated PostScript
+    * is written.
+    */
+   PrintStream mPSStream;
+
+   /* The temporary file to which we spool before sending to the printer  */
+
+   File spoolFile;
+
+   /**
+    * This string holds the PostScript operator to
+    * be used to fill a path. It can be changed
+    * by the <code>setFillMode</code> method.
+    */
+    private String mFillOpStr = WINDING_FILL_STR;
+
+   /**
+    * This string holds the PostScript operator to
+    * be used to clip to a path. It can be changed
+    * by the <code>setFillMode</code> method.
+    */
+    private String mClipOpStr = WINDING_CLIP_STR;
+
+   /**
+    * A stack that represents the PostScript gstate stack.
+    */
+   ArrayList mGStateStack = new ArrayList();
+
+   /**
+    * The x coordinate of the current pen position.
+    */
+   private float mPenX;
+
+   /**
+    * The y coordinate of the current pen position.
+    */
+   private float mPenY;
+
+   /**
+    * The x coordinate of the starting point of
+    * the current subpath.
+    */
+   private float mStartPathX;
+
+   /**
+    * The y coordinate of the starting point of
+    * the current subpath.
+    */
+   private float mStartPathY;
+
+   /**
+    * An optional mapping of fonts to PostScript names.
+    */
+   private static Properties mFontProps = null;
+
+    /* Class static initialiser block */
+    static {
+       //enable priviledges so initProps can access system properties,
+        // open the property file, etc.
+        java.security.AccessController.doPrivileged(
+                            new java.security.PrivilegedAction() {
+            public Object run() {
+                mFontProps = initProps();
+                return null;
+            }
+        });
+    }
+
+    /*
+     * Initialize PostScript font properties.
+     * Copied from PSPrintStream
+     */
+    private static Properties initProps() {
+        // search psfont.properties for fonts
+        // and create and initialize fontProps if it exist.
+
+        String jhome = System.getProperty("java.home");
+
+        if (jhome != null){
+            String ulocale = SunToolkit.getStartupLocale().getLanguage();
+            try {
+
+                File f = new File(jhome + File.separator +
+                                  "lib" + File.separator +
+                                  "psfontj2d.properties." + ulocale);
+
+                if (!f.canRead()){
+
+                    f = new File(jhome + File.separator +
+                                      "lib" + File.separator +
+                                      "psfont.properties." + ulocale);
+                    if (!f.canRead()){
+
+                        f = new File(jhome + File.separator + "lib" +
+                                     File.separator + "psfontj2d.properties");
+
+                        if (!f.canRead()){
+
+                            f = new File(jhome + File.separator + "lib" +
+                                         File.separator + "psfont.properties");
+
+                            if (!f.canRead()){
+                                return (Properties)null;
+                            }
+                        }
+                    }
+                }
+
+                // Load property file
+                InputStream in =
+                    new BufferedInputStream(new FileInputStream(f.getPath()));
+                Properties props = new Properties();
+                props.load(in);
+                in.close();
+                return props;
+            } catch (Exception e){
+                return (Properties)null;
+            }
+        }
+        return (Properties)null;
+    }
+
+ /* Constructors */
+
+    public PSPrinterJob()
+    {
+    }
+
+ /* Instance Methods */
+
+   /**
+     * Presents the user a dialog for changing properties of the
+     * print job interactively.
+     * @returns false if the user cancels the dialog and
+     *          true otherwise.
+     * @exception HeadlessException if GraphicsEnvironment.isHeadless()
+     * returns true.
+     * @see java.awt.GraphicsEnvironment#isHeadless
+     */
+    public boolean printDialog() throws HeadlessException {
+
+        if (GraphicsEnvironment.isHeadless()) {
+            throw new HeadlessException();
+        }
+
+        if (attributes == null) {
+            attributes = new HashPrintRequestAttributeSet();
+        }
+        attributes.add(new Copies(getCopies()));
+        attributes.add(new JobName(getJobName(), null));
+
+        boolean doPrint = false;
+        DialogTypeSelection dts =
+            (DialogTypeSelection)attributes.get(DialogTypeSelection.class);
+        if (dts == DialogTypeSelection.NATIVE) {
+            // Remove DialogTypeSelection.NATIVE to prevent infinite loop in
+            // RasterPrinterJob.
+            attributes.remove(DialogTypeSelection.class);
+            doPrint = printDialog(attributes);
+            // restore attribute
+            attributes.add(DialogTypeSelection.NATIVE);
+        } else {
+            doPrint = printDialog(attributes);
+        }
+
+        if (doPrint) {
+            JobName jobName = (JobName)attributes.get(JobName.class);
+            if (jobName != null) {
+                setJobName(jobName.getValue());
+            }
+            Copies copies = (Copies)attributes.get(Copies.class);
+            if (copies != null) {
+                setCopies(copies.getValue());
+            }
+
+            Destination dest = (Destination)attributes.get(Destination.class);
+
+            if (dest != null) {
+                try {
+                    mDestType = RasterPrinterJob.FILE;
+                    mDestination = (new File(dest.getURI())).getPath();
+                } catch (Exception e) {
+                    mDestination = "out.ps";
+                }
+            } else {
+                mDestType = RasterPrinterJob.PRINTER;
+                PrintService pServ = getPrintService();
+                if (pServ != null) {
+                    mDestination = pServ.getName();
+                }
+            }
+        }
+
+        return doPrint;
+    }
+
+    /**
+     * Invoked by the RasterPrinterJob super class
+     * this method is called to mark the start of a
+     * document.
+     */
+    protected void startDoc() throws PrinterException {
+
+        // A security check has been performed in the
+        // java.awt.print.printerJob.getPrinterJob method.
+        // We use an inner class to execute the privilged open operations.
+        // Note that we only open a file if it has been nominated by
+        // the end-user in a dialog that we ouselves put up.
+
+        OutputStream output;
+
+        if (epsPrinter == null) {
+            if (getPrintService() instanceof PSStreamPrintService) {
+                StreamPrintService sps = (StreamPrintService)getPrintService();
+                mDestType = RasterPrinterJob.STREAM;
+                if (sps.isDisposed()) {
+                    throw new PrinterException("service is disposed");
+                }
+                output = sps.getOutputStream();
+                if (output == null) {
+                    throw new PrinterException("Null output stream");
+                }
+            } else {
+                /* REMIND: This needs to be more maintainable */
+                mNoJobSheet = super.noJobSheet;
+                if (super.destinationAttr != null) {
+                    mDestType = RasterPrinterJob.FILE;
+                    mDestination = super.destinationAttr;
+                }
+                if (mDestType == RasterPrinterJob.FILE) {
+                    try {
+                        spoolFile = new File(mDestination);
+                        output =  new FileOutputStream(spoolFile);
+                    } catch (IOException ex) {
+                        throw new PrinterIOException(ex);
+                    }
+                } else {
+                    PrinterOpener po = new PrinterOpener();
+                    java.security.AccessController.doPrivileged(po);
+                    if (po.pex != null) {
+                        throw po.pex;
+                    }
+                    output = po.result;
+                }
+            }
+
+            mPSStream = new PrintStream(new BufferedOutputStream(output));
+            mPSStream.println(ADOBE_PS_STR);
+        }
+
+        mPSStream.println("%%BeginProlog");
+        mPSStream.println(READIMAGEPROC);
+        mPSStream.println("/BD {bind def} bind def");
+        mPSStream.println("/D {def} BD");
+        mPSStream.println("/C {curveto} BD");
+        mPSStream.println("/L {lineto} BD");
+        mPSStream.println("/M {moveto} BD");
+        mPSStream.println("/R {grestore} BD");
+        mPSStream.println("/G {gsave} BD");
+        mPSStream.println("/N {newpath} BD");
+        mPSStream.println("/P {closepath} BD");
+        mPSStream.println("/EC {eoclip} BD");
+        mPSStream.println("/WC {clip} BD");
+        mPSStream.println("/EF {eofill} BD");
+        mPSStream.println("/WF {fill} BD");
+        mPSStream.println("/SG {setgray} BD");
+        mPSStream.println("/SC {setrgbcolor} BD");
+        mPSStream.println("/ISOF {");
+        mPSStream.println("     dup findfont dup length 1 add dict begin {");
+        mPSStream.println("             1 index /FID eq {pop pop} {D} ifelse");
+        mPSStream.println("     } forall /Encoding ISOLatin1Encoding D");
+        mPSStream.println("     currentdict end definefont");
+        mPSStream.println("} BD");
+        mPSStream.println("/NZ {dup 1 lt {pop 1} if} BD");
+        /* The following procedure takes args: string, x, y, desiredWidth.
+         * It calculates using stringwidth the width of the string in the
+         * current font and subtracts it from the desiredWidth and divides
+         * this by stringLen-1. This gives us a per-glyph adjustment in
+         * the spacing needed (either +ve or -ve) to make the string
+         * print at the desiredWidth. The ashow procedure call takes this
+         * per-glyph adjustment as an argument. This is necessary for WYSIWYG
+         */
+        mPSStream.println("/"+DrawStringName +" {");
+        mPSStream.println("     moveto 1 index stringwidth pop NZ sub");
+        mPSStream.println("     1 index length 1 sub NZ div 0");
+        mPSStream.println("     3 2 roll ashow newpath} BD");
+        mPSStream.println("/FL [");
+        if (mFontProps == null){
+            mPSStream.println(" /Helvetica ISOF");
+            mPSStream.println(" /Helvetica-Bold ISOF");
+            mPSStream.println(" /Helvetica-Oblique ISOF");
+            mPSStream.println(" /Helvetica-BoldOblique ISOF");
+            mPSStream.println(" /Times-Roman ISOF");
+            mPSStream.println(" /Times-Bold ISOF");
+            mPSStream.println(" /Times-Italic ISOF");
+            mPSStream.println(" /Times-BoldItalic ISOF");
+            mPSStream.println(" /Courier ISOF");
+            mPSStream.println(" /Courier-Bold ISOF");
+            mPSStream.println(" /Courier-Oblique ISOF");
+            mPSStream.println(" /Courier-BoldOblique ISOF");
+        } else {
+            int cnt = Integer.parseInt(mFontProps.getProperty("font.num", "9"));
+            for (int i = 0; i < cnt; i++){
+                mPSStream.println("    /" + mFontProps.getProperty
+                           ("font." + String.valueOf(i), "Courier ISOF"));
+            }
+        }
+        mPSStream.println("] D");
+
+        mPSStream.println("/"+SetFontName +" {");
+        mPSStream.println("     FL exch get exch scalefont");
+        mPSStream.println("     [1 0 0 -1 0 0] makefont setfont} BD");
+
+        mPSStream.println("%%EndProlog");
+
+        mPSStream.println("%%BeginSetup");
+        if (epsPrinter == null) {
+            // Set Page Size using first page's format.
+            PageFormat pageFormat = getPageable().getPageFormat(0);
+            double paperHeight = pageFormat.getPaper().getHeight();
+            double paperWidth = pageFormat.getPaper().getWidth();
+
+            /* PostScript printers can always generate uncollated copies.
+             */
+            mPSStream.print("<< /PageSize [" +
+                                           paperWidth + " "+ paperHeight+"]");
+
+            final PrintService pservice = getPrintService();
+            Boolean isPS = (Boolean)java.security.AccessController.doPrivileged(
+                new java.security.PrivilegedAction() {
+                    public Object run() {
+                       try {
+                           Class psClass = Class.forName("sun.print.IPPPrintService");
+                           if (psClass.isInstance(pservice)) {
+                               Method isPSMethod = psClass.getMethod("isPostscript",
+                                                                     (Class[])null);
+                               return (Boolean)isPSMethod.invoke(pservice, (Object[])null);
+                           }
+                       } catch (Throwable t) {
+                       }
+                       return Boolean.TRUE;
+                    }
+                }
+            );
+            if (isPS) {
+                mPSStream.print(" /DeferredMediaSelection true");
+            }
+
+            mPSStream.print(" /ImagingBBox null /ManualFeed false");
+            mPSStream.print(isCollated() ? " /Collate true":"");
+            mPSStream.print(" /NumCopies " +getCopiesInt());
+
+            if (sidesAttr != Sides.ONE_SIDED) {
+                if (sidesAttr == Sides.TWO_SIDED_LONG_EDGE) {
+                    mPSStream.print(" /Duplex true ");
+                } else if (sidesAttr == Sides.TWO_SIDED_SHORT_EDGE) {
+                    mPSStream.print(" /Duplex true /Tumble true ");
+                }
+            }
+            mPSStream.println(" >> setpagedevice ");
+        }
+        mPSStream.println("%%EndSetup");
+    }
+
+    // Inner class to run "privileged" to open the printer output stream.
+
+    private class PrinterOpener implements java.security.PrivilegedAction {
+        PrinterException pex;
+        OutputStream result;
+
+        public Object run() {
+            try {
+
+                    /* Write to a temporary file which will be spooled to
+                     * the printer then deleted. In the case that the file
+                     * is not removed for some reason, request that it is
+                     * removed when the VM exits.
+                     */
+                    spoolFile = File.createTempFile("javaprint", ".ps", null);
+                    spoolFile.deleteOnExit();
+
+                result = new FileOutputStream(spoolFile);
+                return result;
+            } catch (IOException ex) {
+                // If there is an IOError we subvert it to a PrinterException.
+                pex = new PrinterIOException(ex);
+            }
+            return null;
+        }
+    }
+
+    // Inner class to run "privileged" to invoke the system print command
+
+    private class PrinterSpooler implements java.security.PrivilegedAction {
+        PrinterException pex;
+
+        public Object run() {
+            try {
+                /**
+                 * Spool to the printer.
+                 */
+                if (spoolFile == null || !spoolFile.exists()) {
+                   pex = new PrinterException("No spool file");
+                   return null;
+                }
+                String fileName = spoolFile.getAbsolutePath();
+                String execCmd[] = printExecCmd(mDestination, mOptions,
+                               mNoJobSheet, getJobNameInt(),
+                                                1, fileName);
+
+                Process process = Runtime.getRuntime().exec(execCmd);
+                process.waitFor();
+                spoolFile.delete();
+
+            } catch (IOException ex) {
+                pex = new PrinterIOException(ex);
+            } catch (InterruptedException ie) {
+                pex = new PrinterException(ie.toString());
+            }
+            return null;
+        }
+    }
+
+
+    /**
+     * Invoked if the application cancelled the printjob.
+     */
+    protected void abortDoc() {
+        if (mPSStream != null && mDestType != RasterPrinterJob.STREAM) {
+            mPSStream.close();
+        }
+        java.security.AccessController.doPrivileged(
+            new java.security.PrivilegedAction() {
+
+            public Object run() {
+               if (spoolFile != null && spoolFile.exists()) {
+                   spoolFile.delete();
+               }
+               return null;
+            }
+        });
+    }
+
+    /**
+     * Invoked by the RasterPrintJob super class
+     * this method is called after that last page
+     * has been imaged.
+     */
+    protected void endDoc() throws PrinterException {
+        if (mPSStream != null) {
+            mPSStream.println(EOF_COMMENT);
+            mPSStream.flush();
+            if (mDestType != RasterPrinterJob.STREAM) {
+                mPSStream.close();
+            }
+        }
+        if (mDestType == RasterPrinterJob.PRINTER) {
+            if (getPrintService() != null) {
+                mDestination = getPrintService().getName();
+            }
+            PrinterSpooler spooler = new PrinterSpooler();
+            java.security.AccessController.doPrivileged(spooler);
+            if (spooler.pex != null) {
+                throw spooler.pex;
+            }
+        }
+    }
+
+    /**
+     * The RasterPrintJob super class calls this method
+     * at the start of each page.
+     */
+    protected void startPage(PageFormat pageFormat, Printable painter,
+                             int index, boolean paperChanged)
+        throws PrinterException
+    {
+        double paperHeight = pageFormat.getPaper().getHeight();
+        double paperWidth = pageFormat.getPaper().getWidth();
+        int pageNumber = index + 1;
+
+        /* Place an initial gstate on to our gstate stack.
+         * It will have the default PostScript gstate
+         * attributes.
+         */
+        mGStateStack = new ArrayList();
+        mGStateStack.add(new GState());
+
+        mPSStream.println(PAGE_COMMENT + pageNumber + " " + pageNumber);
+
+        /* Check current page's pageFormat against the previous pageFormat,
+         */
+        if (index > 0 && paperChanged) {
+
+            mPSStream.print("<< /PageSize [" +
+                            paperWidth + " " + paperHeight + "]");
+
+            final PrintService pservice = getPrintService();
+            Boolean isPS =
+                (Boolean)java.security.AccessController.doPrivileged(
+
+                new java.security.PrivilegedAction() {
+                    public Object run() {
+                        try {
+                            Class psClass =
+                                Class.forName("sun.print.IPPPrintService");
+                            if (psClass.isInstance(pservice)) {
+                                Method isPSMethod =
+                                    psClass.getMethod("isPostscript",
+                                                      (Class[])null);
+                                return (Boolean)
+                                    isPSMethod.invoke(pservice,
+                                                      (Object[])null);
+                            }
+                        } catch (Throwable t) {
+                        }
+                        return Boolean.TRUE;
+                    }
+                    }
+                );
+
+            if (isPS) {
+                mPSStream.print(" /DeferredMediaSelection true");
+            }
+            mPSStream.println(" >> setpagedevice");
+        }
+        mPSStream.println(PAGE_SAVE);
+        mPSStream.println(paperHeight + COORD_PREP);
+    }
+
+    /**
+     * The RastePrintJob super class calls this method
+     * at the end of each page.
+     */
+    protected void endPage(PageFormat format, Printable painter,
+                           int index)
+        throws PrinterException
+    {
+        mPSStream.println(PAGE_RESTORE);
+        mPSStream.println(SHOWPAGE);
+    }
+
+   /**
+     * Convert the 24 bit BGR image buffer represented by
+     * <code>image</code> to PostScript. The image is drawn at
+     * <code>(destX, destY)</code> in device coordinates.
+     * The image is scaled into a square of size
+     * specified by <code>destWidth</code> and
+     * <code>destHeight</code>. The portion of the
+     * source image copied into that square is specified
+     * by <code>srcX</code>, <code>srcY</code>,
+     * <code>srcWidth</code>, and srcHeight.
+     */
+    protected void drawImageBGR(byte[] bgrData,
+                                   float destX, float destY,
+                                   float destWidth, float destHeight,
+                                   float srcX, float srcY,
+                                   float srcWidth, float srcHeight,
+                                   int srcBitMapWidth, int srcBitMapHeight) {
+
+        /* We draw images at device resolution so we probably need
+         * to change the current PostScript transform.
+         */
+        setTransform(new AffineTransform());
+        prepDrawing();
+
+        int intSrcWidth = (int) srcWidth;
+        int intSrcHeight = (int) srcHeight;
+
+        mPSStream.println(IMAGE_SAVE);
+
+        /* Create a PS string big enough to hold a row of pixels.
+         */
+        int psBytesPerRow = 3 * (int) intSrcWidth;
+        while (psBytesPerRow > MAX_PSSTR) {
+            psBytesPerRow /= 2;
+        }
+
+        mPSStream.println(psBytesPerRow + IMAGE_STR);
+
+        /* Scale and translate the unit image.
+         */
+        mPSStream.println("[" + destWidth + " 0 "
+                          + "0 " + destHeight
+                          + " " + destX + " " + destY
+                          +"]concat");
+
+        /* Color Image invocation.
+         */
+        mPSStream.println(intSrcWidth + " " + intSrcHeight + " " + 8 + "["
+                          + intSrcWidth + " 0 "
+                          + "0 " + intSrcHeight
+                          + " 0 " + 0 + "]"
+                          + "/imageSrc load false 3 colorimage");
+
+        /* Image data.
+         */
+        int index = 0;
+        byte[] rgbData = new byte[intSrcWidth * 3];
+
+        try {
+            /* Skip the parts of the image that are not part
+             * of the source rectangle.
+             */
+            index = (int) srcY * srcBitMapWidth;
+
+            for(int i = 0; i < intSrcHeight; i++) {
+
+                /* Skip the left part of the image that is not
+                 * part of the source rectangle.
+                 */
+                index += (int) srcX;
+
+                index = swapBGRtoRGB(bgrData, index, rgbData);
+                byte[] encodedData = rlEncode(rgbData);
+                byte[] asciiData = ascii85Encode(encodedData);
+                mPSStream.write(asciiData);
+                mPSStream.println("");
+            }
+
+            /*
+             * If there is an IOError we subvert it to a PrinterException.
+             * Fix: There has got to be a better way, maybe define
+             * a PrinterIOException and then throw that?
+             */
+        } catch (IOException e) {
+            //throw new PrinterException(e.toString());
+        }
+
+        mPSStream.println(IMAGE_RESTORE);
+    }
+
+    /**
+     * Prints the contents of the array of ints, 'data'
+     * to the current page. The band is placed at the
+     * location (x, y) in device coordinates on the
+     * page. The width and height of the band is
+     * specified by the caller. Currently the data
+     * is 24 bits per pixel in BGR format.
+     */
+    protected void printBand(byte[] bgrData, int x, int y,
+                             int width, int height)
+        throws PrinterException
+    {
+
+        mPSStream.println(IMAGE_SAVE);
+
+        /* Create a PS string big enough to hold a row of pixels.
+         */
+        int psBytesPerRow = 3 * width;
+        while (psBytesPerRow > MAX_PSSTR) {
+            psBytesPerRow /= 2;
+        }
+
+        mPSStream.println(psBytesPerRow + IMAGE_STR);
+
+        /* Scale and translate the unit image.
+         */
+        mPSStream.println("[" + width + " 0 "
+                          + "0 " + height
+                          + " " + x + " " + y
+                          +"]concat");
+
+        /* Color Image invocation.
+         */
+        mPSStream.println(width + " " + height + " " + 8 + "["
+                          + width + " 0 "
+                          + "0 " + -height
+                          + " 0 " + height + "]"
+                          + "/imageSrc load false 3 colorimage");
+
+        /* Image data.
+         */
+        int index = 0;
+        byte[] rgbData = new byte[width*3];
+
+        try {
+            for(int i = 0; i < height; i++) {
+                index = swapBGRtoRGB(bgrData, index, rgbData);
+                byte[] encodedData = rlEncode(rgbData);
+                byte[] asciiData = ascii85Encode(encodedData);
+                mPSStream.write(asciiData);
+                mPSStream.println("");
+            }
+
+        } catch (IOException e) {
+            throw new PrinterIOException(e);
+        }
+
+        mPSStream.println(IMAGE_RESTORE);
+    }
+
+    /**
+     * Examine the metrics captured by the
+     * <code>PeekGraphics</code> instance and
+     * if capable of directly converting this
+     * print job to the printer's control language
+     * or the native OS's graphics primitives, then
+     * return a <code>PSPathGraphics</code> to perform
+     * that conversion. If there is not an object
+     * capable of the conversion then return
+     * <code>null</code>. Returning <code>null</code>
+     * causes the print job to be rasterized.
+     */
+
+    protected Graphics2D createPathGraphics(PeekGraphics peekGraphics,
+                                            PrinterJob printerJob,
+                                            Printable painter,
+                                            PageFormat pageFormat,
+                                            int pageIndex) {
+
+        PSPathGraphics pathGraphics;
+        PeekMetrics metrics = peekGraphics.getMetrics();
+
+        /* If the application has drawn anything that
+         * out PathGraphics class can not handle then
+         * return a null PathGraphics.
+         */
+        if (forcePDL == false && (forceRaster == true
+                        || metrics.hasNonSolidColors()
+                        || metrics.hasCompositing())) {
+
+            pathGraphics = null;
+        } else {
+
+            BufferedImage bufferedImage = new BufferedImage(8, 8,
+                                            BufferedImage.TYPE_INT_RGB);
+            Graphics2D bufferedGraphics = bufferedImage.createGraphics();
+            boolean canRedraw = peekGraphics.getAWTDrawingOnly() == false;
+
+            pathGraphics =  new PSPathGraphics(bufferedGraphics, printerJob,
+                                               painter, pageFormat, pageIndex,
+                                               canRedraw);
+        }
+
+        return pathGraphics;
+    }
+
+    /**
+     * Intersect the gstate's current path with the
+     * current clip and make the result the new clip.
+     */
+    protected void selectClipPath() {
+
+        mPSStream.println(mClipOpStr);
+    }
+
+    protected void setClip(Shape clip) {
+
+        mLastClip = clip;
+    }
+
+    protected void setTransform(AffineTransform transform) {
+        mLastTransform = transform;
+    }
+
+    /**
+     * Set the current PostScript font.
+     * Taken from outFont in PSPrintStream.
+     */
+     protected boolean setFont(Font font) {
+        mLastFont = font;
+        return true;
+    }
+
+    /**
+     * Given an array of CharsetStrings that make up a run
+     * of text, this routine converts each CharsetString to
+     * an index into our PostScript font list. If one or more
+     * CharsetStrings can not be represented by a PostScript
+     * font, then this routine will return a null array.
+     */
+     private int[] getPSFontIndexArray(Font font, CharsetString[] charSet) {
+        int[] psFont = null;
+
+        if (mFontProps != null) {
+            psFont = new int[charSet.length];
+        }
+
+        for (int i = 0; i < charSet.length && psFont != null; i++){
+
+            /* Get the encoding of the run of text.
+             */
+            CharsetString cs = charSet[i];
+
+            CharsetEncoder fontCS = cs.fontDescriptor.encoder;
+            String charsetName = cs.fontDescriptor.getFontCharsetName();
+            /*
+             * sun.awt.Symbol perhaps should return "symbol" for encoding.
+             * Similarly X11Dingbats should return "dingbats"
+             * Forced to check for win32 & x/unix names for these converters.
+             */
+
+            if ("Symbol".equals(charsetName)) {
+                charsetName = "symbol";
+            } else if ("WingDings".equals(charsetName) ||
+                       "X11Dingbats".equals(charsetName)) {
+                charsetName = "dingbats";
+            } else {
+                charsetName = makeCharsetName(charsetName, cs.charsetChars);
+            }
+
+            int styleMask = font.getStyle() |
+                sun.font.FontManager.getFont2D(font).getStyle();
+
+            String style = FontConfiguration.getStyleString(styleMask);
+
+            /* First we map the font name through the properties file.
+             * This mapping provides alias names for fonts, for example,
+             * "timesroman" is mapped to "serif".
+             */
+            String fontName = font.getFamily().toLowerCase(Locale.ENGLISH);
+            fontName = fontName.replace(' ', '_');
+            String name = mFontProps.getProperty(fontName, "");
+
+            /* Now map the alias name, character set name, and style
+             * to a PostScript name.
+             */
+            String psName =
+                mFontProps.getProperty(name + "." + charsetName + "." + style,
+                                      null);
+
+            if (psName != null) {
+
+                /* Get the PostScript font index for the PostScript font.
+                 */
+                try {
+                    psFont[i] =
+                        Integer.parseInt(mFontProps.getProperty(psName));
+
+                /* If there is no PostScript font for this font name,
+                 * then we want to termintate the loop and the method
+                 * indicating our failure. Setting the array to null
+                 * is used to indicate these failures.
+                 */
+                } catch(NumberFormatException e){
+                    psFont = null;
+                }
+
+            /* There was no PostScript name for the font, character set,
+             * and style so give up.
+             */
+            } else {
+                psFont = null;
+            }
+        }
+
+         return psFont;
+     }
+
+
+    private static String escapeParens(String str) {
+        if (str.indexOf('(') == -1 && str.indexOf(')') == -1 ) {
+            return str;
+        } else {
+            int count = 0;
+            int pos = 0;
+            while ((pos = str.indexOf('(', pos)) != -1) {
+                count++;
+                pos++;
+            }
+            pos = 0;
+            while ((pos = str.indexOf(')', pos)) != -1) {
+                count++;
+                pos++;
+            }
+            char []inArr = str.toCharArray();
+            char []outArr = new char[inArr.length+count];
+            pos = 0;
+            for (int i=0;i<inArr.length;i++) {
+                if (inArr[i] == '(' || inArr[i] == ')') {
+                    outArr[pos++] = '\\';
+                }
+                outArr[pos++] = inArr[i];
+            }
+            return new String(outArr);
+
+        }
+    }
+
+    /* return of 0 means unsupported. Other return indicates the number
+     * of distinct PS fonts needed to draw this text. This saves us
+     * doing this processing one extra time.
+     */
+    protected int platformFontCount(Font font, String str) {
+        if (mFontProps == null) {
+            return 0;
+        }
+        CharsetString[] acs =
+            ((PlatformFont)(font.getPeer())).makeMultiCharsetString(str,false);
+        if (acs == null) {
+            /* AWT can't convert all chars so use 2D path */
+            return 0;
+        }
+        int[] psFonts = getPSFontIndexArray(font, acs);
+        return (psFonts == null) ? 0 : psFonts.length;
+    }
+
+     protected boolean textOut(Graphics g, String str, float x, float y,
+                               Font mLastFont, FontRenderContext frc,
+                               float width) {
+        boolean didText = true;
+
+        if (mFontProps == null) {
+            return false;
+        } else {
+            prepDrawing();
+
+            /* On-screen drawString renders most control chars as the missing
+             * glyph and have the non-zero advance of that glyph.
+             * Exceptions are \t, \n and \r which are considered zero-width.
+             * Postscript handles control chars mostly as a missing glyph.
+             * But we use 'ashow' specifying a width for the string which
+             * assumes zero-width for those three exceptions, and Postscript
+             * tries to squeeze the extra char in, with the result that the
+             * glyphs look compressed or even overlap.
+             * So exclude those control chars from the string sent to PS.
+             */
+            str = removeControlChars(str);
+            if (str.length() == 0) {
+                return true;
+            }
+            CharsetString[] acs =
+                ((PlatformFont)
+                 (mLastFont.getPeer())).makeMultiCharsetString(str, false);
+            if (acs == null) {
+                /* AWT can't convert all chars so use 2D path */
+                return false;
+            }
+            /* Get an array of indices into our PostScript name
+             * table. If all of the runs can not be converted
+             * to PostScript fonts then null is returned and
+             * we'll want to fall back to printing the text
+             * as shapes.
+             */
+            int[] psFonts = getPSFontIndexArray(mLastFont, acs);
+            if (psFonts != null) {
+
+                for (int i = 0; i < acs.length; i++){
+                    CharsetString cs = acs[i];
+                    CharsetEncoder fontCS = cs.fontDescriptor.encoder;
+
+                    StringBuffer nativeStr = new StringBuffer();
+                    byte[] strSeg = new byte[cs.length * 2];
+                    int len = 0;
+                    try {
+                        ByteBuffer bb = ByteBuffer.wrap(strSeg);
+                        fontCS.encode(CharBuffer.wrap(cs.charsetChars,
+                                                      cs.offset,
+                                                      cs.length),
+                                      bb, true);
+                        bb.flip();
+                        len = bb.limit();
+                    } catch(IllegalStateException xx){
+                        continue;
+                    } catch(CoderMalfunctionError xx){
+                        continue;
+                    }
+                    /* The width to fit to may either be specified,
+                     * or calculated. Specifying by the caller is only
+                     * valid if the text does not need to be decomposed
+                     * into multiple calls.
+                     */
+                    float desiredWidth;
+                    if (acs.length == 1 && width != 0f) {
+                        desiredWidth = width;
+                    } else {
+                        Rectangle2D r2d =
+                            mLastFont.getStringBounds(cs.charsetChars,
+                                                      cs.offset,
+                                                      cs.offset+cs.length,
+                                                      frc);
+                        desiredWidth = (float)r2d.getWidth();
+                    }
+                    /* unprintable chars had width of 0, causing a PS error
+                     */
+                    if (desiredWidth == 0) {
+                        return didText;
+                    }
+                    nativeStr.append('<');
+                    for (int j = 0; j < len; j++){
+                        byte b = strSeg[j];
+                        // to avoid encoding conversion with println()
+                        String hexS = Integer.toHexString(b);
+                        int length = hexS.length();
+                        if (length > 2) {
+                            hexS = hexS.substring(length - 2, length);
+                        } else if (length == 1) {
+                            hexS = "0" + hexS;
+                        } else if (length == 0) {
+                            hexS = "00";
+                        }
+                        nativeStr.append(hexS);
+                    }
+                    nativeStr.append('>');
+                    /* This comment costs too much in output file size */
+//                  mPSStream.println("% Font[" + mLastFont.getName() + ", " +
+//                             FontConfiguration.getStyleString(mLastFont.getStyle()) + ", "
+//                             + mLastFont.getSize2D() + "]");
+                    getGState().emitPSFont(psFonts[i], mLastFont.getSize2D());
+
+                    // out String
+                    mPSStream.println(nativeStr.toString() + " " +
+                                      desiredWidth + " " + x + " " + y + " " +
+                                      DrawStringName);
+                    x += desiredWidth;
+                }
+            } else {
+                didText = false;
+            }
+        }
+
+        return didText;
+     }
+    /**
+     * Set the current path rule to be either
+     * <code>FILL_EVEN_ODD</code> (using the
+     * even-odd file rule) or <code>FILL_WINDING</code>
+     * (using the non-zero winding rule.)
+     */
+    protected void setFillMode(int fillRule) {
+
+        switch (fillRule) {
+
+         case FILL_EVEN_ODD:
+            mFillOpStr = EVEN_ODD_FILL_STR;
+            mClipOpStr = EVEN_ODD_CLIP_STR;
+            break;
+
+         case FILL_WINDING:
+             mFillOpStr = WINDING_FILL_STR;
+             mClipOpStr = WINDING_CLIP_STR;
+             break;
+
+         default:
+             throw new IllegalArgumentException();
+        }
+
+    }
+
+    /**
+     * Set the printer's current color to be that
+     * defined by <code>color</code>
+     */
+    protected void setColor(Color color) {
+        mLastColor = color;
+    }
+
+    /**
+     * Fill the current path using the current fill mode
+     * and color.
+     */
+    protected void fillPath() {
+
+        mPSStream.println(mFillOpStr);
+    }
+
+    /**
+     * Called to mark the start of a new path.
+     */
+    protected void beginPath() {
+
+        prepDrawing();
+        mPSStream.println(NEWPATH_STR);
+
+        mPenX = 0;
+        mPenY = 0;
+    }
+
+    /**
+     * Close the current subpath by appending a straight
+     * line from the current point to the subpath's
+     * starting point.
+     */
+    protected void closeSubpath() {
+
+        mPSStream.println(CLOSEPATH_STR);
+
+        mPenX = mStartPathX;
+        mPenY = mStartPathY;
+    }
+
+
+    /**
+     * Generate PostScript to move the current pen
+     * position to <code>(x, y)</code>.
+     */
+    protected void moveTo(float x, float y) {
+
+        mPSStream.println(trunc(x) + " " + trunc(y) + MOVETO_STR);
+
+        /* moveto marks the start of a new subpath
+         * and we need to remember that starting
+         * position so that we know where the
+         * pen returns to with a close path.
+         */
+        mStartPathX = x;
+        mStartPathY = y;
+
+        mPenX = x;
+        mPenY = y;
+    }
+    /**
+     * Generate PostScript to draw a line from the
+     * current pen position to <code>(x, y)</code>.
+     */
+    protected void lineTo(float x, float y) {
+
+        mPSStream.println(trunc(x) + " " + trunc(y) + LINETO_STR);
+
+        mPenX = x;
+        mPenY = y;
+    }
+
+    /**
+     * Add to the current path a bezier curve formed
+     * by the current pen position and the method parameters
+     * which are two control points and an ending
+     * point.
+     */
+    protected void bezierTo(float control1x, float control1y,
+                                float control2x, float control2y,
+                                float endX, float endY) {
+
+//      mPSStream.println(control1x + " " + control1y
+//                        + " " + control2x + " " + control2y
+//                        + " " + endX + " " + endY
+//                        + CURVETO_STR);
+        mPSStream.println(trunc(control1x) + " " + trunc(control1y)
+                          + " " + trunc(control2x) + " " + trunc(control2y)
+                          + " " + trunc(endX) + " " + trunc(endY)
+                          + CURVETO_STR);
+
+
+        mPenX = endX;
+        mPenY = endY;
+    }
+
+    String trunc(float f) {
+        float af = Math.abs(f);
+        if (af >= 1f && af <=1000f) {
+            f = Math.round(f*1000)/1000f;
+        }
+        return Float.toString(f);
+    }
+
+    /**
+     * Return the x coordinate of the pen in the
+     * current path.
+     */
+    protected float getPenX() {
+
+        return mPenX;
+    }
+    /**
+     * Return the y coordinate of the pen in the
+     * current path.
+     */
+    protected float getPenY() {
+
+        return mPenY;
+    }
+
+    /**
+     * Return the x resolution of the coordinates
+     * to be rendered.
+     */
+    protected double getXRes() {
+        return PS_XRES;
+    }
+    /**
+     * Return the y resolution of the coordinates
+     * to be rendered.
+     */
+    protected double getYRes() {
+        return PS_YRES;
+    }
+
+    /**
+     * For PostScript the origin is in the upper-left of the
+     * paper not at the imageable area corner.
+     */
+    protected double getPhysicalPrintableX(Paper p) {
+        return 0;
+
+    }
+
+    /**
+     * For PostScript the origin is in the upper-left of the
+     * paper not at the imageable area corner.
+     */
+    protected double getPhysicalPrintableY(Paper p) {
+        return 0;
+    }
+
+    protected double getPhysicalPrintableWidth(Paper p) {
+        return p.getImageableWidth();
+    }
+
+    protected double getPhysicalPrintableHeight(Paper p) {
+        return p.getImageableHeight();
+    }
+
+    protected double getPhysicalPageWidth(Paper p) {
+        return p.getWidth();
+    }
+
+    protected double getPhysicalPageHeight(Paper p) {
+        return p.getHeight();
+    }
+
+   /**
+     * Returns how many times each page in the book
+     * should be consecutively printed by PrintJob.
+     * If the printer makes copies itself then this
+     * method should return 1.
+     */
+    protected int getNoncollatedCopies() {
+        return 1;
+    }
+
+    protected int getCollatedCopies() {
+        return 1;
+    }
+
+    private String[] printExecCmd(String printer, String options,
+                                  boolean noJobSheet,
+                                  String banner, int copies, String spoolFile) {
+        int PRINTER = 0x1;
+        int OPTIONS = 0x2;
+        int BANNER  = 0x4;
+        int COPIES  = 0x8;
+        int NOSHEET = 0x10;
+        int pFlags = 0;
+        String execCmd[];
+        int ncomps = 2; // minimum number of print args
+        int n = 0;
+
+        if (printer != null && !printer.equals("") && !printer.equals("lp")) {
+            pFlags |= PRINTER;
+            ncomps+=1;
+        }
+        if (options != null && !options.equals("")) {
+            pFlags |= OPTIONS;
+            ncomps+=1;
+        }
+        if (banner != null && !banner.equals("")) {
+            pFlags |= BANNER;
+            ncomps+=1;
+        }
+        if (copies > 1) {
+            pFlags |= COPIES;
+            ncomps+=1;
+        }
+        if (noJobSheet) {
+            pFlags |= NOSHEET;
+            ncomps+=1;
+        }
+       if (System.getProperty("os.name").equals("Linux")) {
+            execCmd = new String[ncomps];
+            execCmd[n++] = "/usr/bin/lpr";
+            if ((pFlags & PRINTER) != 0) {
+                execCmd[n++] = new String("-P" + printer);
+            }
+            if ((pFlags & BANNER) != 0) {
+                execCmd[n++] = new String("-J"  + banner);
+            }
+            if ((pFlags & COPIES) != 0) {
+                execCmd[n++] = new String("-#" + new Integer(copies).toString());
+            }
+            if ((pFlags & NOSHEET) != 0) {
+                execCmd[n++] = new String("-h");
+            }
+            if ((pFlags & OPTIONS) != 0) {
+                execCmd[n++] = new String(options);
+            }
+        } else {
+            ncomps+=1; //add 1 arg for lp
+            execCmd = new String[ncomps];
+            execCmd[n++] = "/usr/bin/lp";
+            execCmd[n++] = "-c";           // make a copy of the spool file
+            if ((pFlags & PRINTER) != 0) {
+                execCmd[n++] = new String("-d" + printer);
+            }
+            if ((pFlags & BANNER) != 0) {
+                execCmd[n++] = new String("-t"  + banner);
+            }
+            if ((pFlags & COPIES) != 0) {
+                execCmd[n++] = new String("-n" + new Integer(copies).toString());
+            }
+            if ((pFlags & NOSHEET) != 0) {
+                execCmd[n++] = new String("-o nobanner");
+            }
+            if ((pFlags & OPTIONS) != 0) {
+                execCmd[n++] = new String("-o" + options);
+            }
+        }
+        execCmd[n++] = spoolFile;
+        return execCmd;
+    }
+
+    private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) {
+        int destIndex = 0;
+        while(index < image.length-2 && destIndex < dest.length-2) {
+            dest[destIndex++] = image[index+2];
+            dest[destIndex++] = image[index+1];
+            dest[destIndex++] = image[index+0];
+            index+=3;
+        }
+        return index;
+    }
+
+    /*
+     * Currently CharToByteConverter.getCharacterEncoding() return values are
+     * not fixed yet. These are used as the part of the key of
+     * psfont.propeties. When those name are fixed this routine can
+     * be erased.
+     */
+    private String makeCharsetName(String name, char[] chs) {
+        if (name.equals("Cp1252") || name.equals("ISO8859_1")) {
+            return "latin1";
+        } else if (name.equals("UTF8")) {
+            // same as latin 1 if all chars < 256
+            for (int i=0; i < chs.length; i++) {
+                if (chs[i] > 255) {
+                    return name.toLowerCase();
+                }
+            }
+            return "latin1";
+        } else if (name.startsWith("ISO8859")) {
+            // same as latin 1 if all chars < 128
+            for (int i=0; i < chs.length; i++) {
+                if (chs[i] > 127) {
+                    return name.toLowerCase();
+                }
+            }
+            return "latin1";
+        } else {
+            return name.toLowerCase();
+        }
+    }
+
+    private void prepDrawing() {
+
+        /* Pop gstates until we can set the needed clip
+         * and transform or until we are at the outer most
+         * gstate.
+         */
+        while (isOuterGState() == false
+               && (getGState().canSetClip(mLastClip) == false
+                   || getGState().mTransform.equals(mLastTransform) == false)) {
+
+
+            grestore();
+        }
+
+        /* Set the color. This can push the color to the
+         * outer most gsave which is often a good thing.
+         */
+        getGState().emitPSColor(mLastColor);
+
+        /* We do not want to change the outermost
+         * transform or clip so if we are at the
+         * outer clip the generate a gsave.
+         */
+        if (isOuterGState()) {
+            gsave();
+            getGState().emitTransform(mLastTransform);
+            getGState().emitPSClip(mLastClip);
+        }
+
+        /* Set the font if we have been asked to. It is
+         * important that the font is set after the
+         * transform in order to get the font size
+         * correct.
+         */
+//      if (g != null) {
+//          getGState().emitPSFont(g, mLastFont);
+//      }
+
+    }
+
+    /**
+     * Return the GState that is currently on top
+     * of the GState stack. There should always be
+     * a GState on top of the stack. If there isn't
+     * then this method will throw an IndexOutOfBounds
+     * exception.
+     */
+    private GState getGState() {
+        int count = mGStateStack.size();
+        return (GState) mGStateStack.get(count - 1);
+    }
+
+    /**
+     * Emit a PostScript gsave command and add a
+     * new GState on to our stack which represents
+     * the printer's gstate stack.
+     */
+    private void gsave() {
+        GState oldGState = getGState();
+        mGStateStack.add(new GState(oldGState));
+        mPSStream.println(GSAVE_STR);
+    }
+
+    /**
+     * Emit a PostScript grestore command and remove
+     * a GState from our stack which represents the
+     * printer's gstate stack.
+     */
+    private void grestore() {
+        int count = mGStateStack.size();
+        mGStateStack.remove(count - 1);
+        mPSStream.println(GRESTORE_STR);
+    }
+
+    /**
+     * Return true if the current GState is the
+     * outermost GState and therefore should not
+     * be restored.
+     */
+    private boolean isOuterGState() {
+        return mGStateStack.size() == 1;
+    }
+
+    /**
+     * A stack of GStates is maintained to model the printer's
+     * gstate stack. Each GState holds information about
+     * the current graphics attributes.
+     */
+    private class GState{
+        Color mColor;
+        Shape mClip;
+        Font mFont;
+        AffineTransform mTransform;
+
+        GState() {
+            mColor = Color.black;
+            mClip = null;
+            mFont = null;
+            mTransform = new AffineTransform();
+        }
+
+        GState(GState copyGState) {
+            mColor = copyGState.mColor;
+            mClip = copyGState.mClip;
+            mFont = copyGState.mFont;
+            mTransform = copyGState.mTransform;
+        }
+
+        boolean canSetClip(Shape clip) {
+
+            return mClip == null || mClip.equals(clip);
+        }
+
+
+        void emitPSClip(Shape clip) {
+            if (clip != null
+                && (mClip == null || mClip.equals(clip) == false)) {
+                String saveFillOp = mFillOpStr;
+                String saveClipOp = mClipOpStr;
+                convertToPSPath(clip.getPathIterator(new AffineTransform()));
+                selectClipPath();
+                mClip = clip;
+                /* The clip is a shape and has reset the winding rule state */
+                mClipOpStr = saveFillOp;
+                mFillOpStr = saveFillOp;
+            }
+        }
+
+        void emitTransform(AffineTransform transform) {
+
+            if (transform != null && transform.equals(mTransform) == false) {
+                double[] matrix = new double[6];
+                transform.getMatrix(matrix);
+                mPSStream.println("[" + (float)matrix[0]
+                                  + " " + (float)matrix[1]
+                                  + " " + (float)matrix[2]
+                                  + " " + (float)matrix[3]
+                                  + " " + (float)matrix[4]
+                                  + " " + (float)matrix[5]
+                                  + "] concat");
+
+                mTransform = transform;
+            }
+        }
+
+        void emitPSColor(Color color) {
+            if (color != null && color.equals(mColor) == false) {
+                float[] rgb = color.getRGBColorComponents(null);
+
+                /* If the color is a gray value then use
+                 * setgray.
+                 */
+                if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {
+                    mPSStream.println(rgb[0] + SETGRAY_STR);
+
+                /* It's not gray so use setrgbcolor.
+                 */
+                } else {
+                    mPSStream.println(rgb[0] + " "
+                                      + rgb[1] + " "
+                                      + rgb[2] + " "
+                                      + SETRGBCOLOR_STR);
+                }
+
+                mColor = color;
+
+            }
+        }
+
+        void emitPSFont(int psFontIndex, float fontSize) {
+            mPSStream.println(fontSize + " " +
+                              psFontIndex + " " + SetFontName);
+        }
+    }
+
+       /**
+        * Given a Java2D <code>PathIterator</code> instance,
+        * this method translates that into a PostScript path..
+        */
+        void convertToPSPath(PathIterator pathIter) {
+
+            float[] segment = new float[6];
+            int segmentType;
+
+            /* Map the PathIterator's fill rule into the PostScript
+             * fill rule.
+             */
+            int fillRule;
+            if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
+                fillRule = FILL_EVEN_ODD;
+            } else {
+                fillRule = FILL_WINDING;
+            }
+
+            beginPath();
+
+            setFillMode(fillRule);
+
+            while (pathIter.isDone() == false) {
+                segmentType = pathIter.currentSegment(segment);
+
+                switch (segmentType) {
+                 case PathIterator.SEG_MOVETO:
+                    moveTo(segment[0], segment[1]);
+                    break;
+
+                 case PathIterator.SEG_LINETO:
+                    lineTo(segment[0], segment[1]);
+                    break;
+
+                /* Convert the quad path to a bezier.
+                 */
+                 case PathIterator.SEG_QUADTO:
+                    float lastX = getPenX();
+                    float lastY = getPenY();
+                    float c1x = lastX + (segment[0] - lastX) * 2 / 3;
+                    float c1y = lastY + (segment[1] - lastY) * 2 / 3;
+                    float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3;
+                    float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3;
+                    bezierTo(c1x, c1y,
+                             c2x, c2y,
+                             segment[2], segment[3]);
+                    break;
+
+                 case PathIterator.SEG_CUBICTO:
+                    bezierTo(segment[0], segment[1],
+                             segment[2], segment[3],
+                             segment[4], segment[5]);
+                    break;
+
+                 case PathIterator.SEG_CLOSE:
+                    closeSubpath();
+                    break;
+                }
+
+
+                pathIter.next();
+            }
+        }
+
+    /*
+     * Fill the path defined by <code>pathIter</code>
+     * with the specified color.
+     * The path is provided in current user space.
+     */
+    protected void deviceFill(PathIterator pathIter, Color color,
+                              AffineTransform tx, Shape clip) {
+
+        setTransform(tx);
+        setClip(clip);
+        setColor(color);
+        convertToPSPath(pathIter);
+        /* Specify the path to fill as the clip, this ensures that only
+         * pixels which are inside the path will be filled, which is
+         * what the Java 2D APIs specify
+         */
+        mPSStream.println(GSAVE_STR);
+        selectClipPath();
+        fillPath();
+        mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR);
+    }
+
+    /*
+     * Run length encode byte array in a form suitable for decoding
+     * by the PS Level 2 filter RunLengthDecode.
+     * Array data to encode is inArr. Encoded data is written to outArr
+     * outArr must be long enough to hold the encoded data but this
+     * can't be known ahead of time.
+     * A safe assumption is to use double the length of the input array.
+     * This is then copied into a new array of the correct length which
+     * is returned.
+     * Algorithm:
+     * Encoding is a lead byte followed by data bytes.
+     * Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow
+     * Lead byte of 129->255 indicates 257 - leadByte is the number of times
+     * the following byte is repeated in the source.
+     * 128 is a special lead byte indicating end of data (EOD) and is
+     * written as the final byte of the returned encoded data.
+     */
+     private byte[] rlEncode(byte[] inArr) {
+
+         int inIndex = 0;
+         int outIndex = 0;
+         int startIndex = 0;
+         int runLen = 0;
+         byte[] outArr = new byte[(inArr.length * 2) +2];
+         while (inIndex < inArr.length) {
+             if (runLen == 0) {
+                 startIndex = inIndex++;
+                 runLen=1;
+             }
+
+             while (runLen < 128 && inIndex < inArr.length &&
+                    inArr[inIndex] == inArr[startIndex]) {
+                 runLen++; // count run of same value
+                 inIndex++;
+             }
+
+             if (runLen > 1) {
+                 outArr[outIndex++] = (byte)(257 - runLen);
+                 outArr[outIndex++] = inArr[startIndex];
+                 runLen = 0;
+                 continue; // back to top of while loop.
+             }
+
+             // if reach here have a run of different values, or at the end.
+             while (runLen < 128 && inIndex < inArr.length &&
+                    inArr[inIndex] != inArr[inIndex-1]) {
+                 runLen++; // count run of different values
+                 inIndex++;
+             }
+             outArr[outIndex++] = (byte)(runLen - 1);
+             for (int i = startIndex; i < startIndex+runLen; i++) {
+                 outArr[outIndex++] = inArr[i];
+             }
+             runLen = 0;
+         }
+         outArr[outIndex++] = (byte)128;
+         byte[] encodedData = new byte[outIndex];
+         System.arraycopy(outArr, 0, encodedData, 0, outIndex);
+
+         return encodedData;
+     }
+
+    /* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter",
+     * "PS Language Reference Manual, 2nd edition: Section 3.13"
+     */
+    private byte[] ascii85Encode(byte[] inArr) {
+        byte[]  outArr = new byte[((inArr.length+4) * 5 / 4) + 2];
+        long p1 = 85;
+        long p2 = p1*p1;
+        long p3 = p1*p2;
+        long p4 = p1*p3;
+        byte pling = '!';
+
+        int i = 0;
+        int olen = 0;
+        long val, rem;
+
+        while (i+3 < inArr.length) {
+            val = ((long)((inArr[i++]&0xff))<<24) +
+                  ((long)((inArr[i++]&0xff))<<16) +
+                  ((long)((inArr[i++]&0xff))<< 8) +
+                  ((long)(inArr[i++]&0xff));
+            if (val == 0) {
+                outArr[olen++] = 'z';
+            } else {
+                rem = val;
+                outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4;
+                outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3;
+                outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2;
+                outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1;
+                outArr[olen++] = (byte)(rem + pling);
+            }
+        }
+        // input not a multiple of 4 bytes, write partial output.
+        if (i < inArr.length) {
+            int n = inArr.length - i; // n bytes remain to be written
+
+            val = 0;
+            while (i < inArr.length) {
+                val = (val << 8) + (inArr[i++]&0xff);
+            }
+
+            int append = 4 - n;
+            while (append-- > 0) {
+                val = val << 8;
+            }
+            byte []c = new byte[5];
+            rem = val;
+            c[0] = (byte)(rem / p4 + pling); rem = rem % p4;
+            c[1] = (byte)(rem / p3 + pling); rem = rem % p3;
+            c[2] = (byte)(rem / p2 + pling); rem = rem % p2;
+            c[3] = (byte)(rem / p1 + pling); rem = rem % p1;
+            c[4] = (byte)(rem + pling);
+
+            for (int b = 0; b < n+1 ; b++) {
+                outArr[olen++] = c[b];
+            }
+        }
+
+        // write EOD marker.
+        outArr[olen++]='~'; outArr[olen++]='>';
+
+        /* The original intention was to insert a newline after every 78 bytes.
+         * This was mainly intended for legibility but I decided against this
+         * partially because of the (small) amount of extra space, and
+         * partially because for line breaks either would have to hardwire
+         * ascii 10 (newline) or calculate space in bytes to allocate for
+         * the platform's newline byte sequence. Also need to be careful
+         * about where its inserted:
+         * Ascii 85 decoder ignores white space except for one special case:
+         * you must ensure you do not split the EOD marker across lines.
+         */
+        byte[] retArr = new byte[olen];
+        System.arraycopy(outArr, 0, retArr, 0, olen);
+        return retArr;
+
+    }
+
+    /**
+     * PluginPrinter generates EPSF wrapped with a header and trailer
+     * comment. This conforms to the new requirements of Mozilla 1.7
+     * and FireFox 1.5 and later. Earlier versions of these browsers
+     * did not support plugin printing in the general sense (not just Java).
+     * A notable limitation of these browsers is that they handle plugins
+     * which would span page boundaries by scaling plugin content to fit on a
+     * single page. This means white space is left at the bottom of the
+     * previous page and its impossible to print these cases as they appear on
+     * the web page. This is contrast to how the same browsers behave on
+     * Windows where it renders as on-screen.
+     * Cases where the content fits on a single page do work fine, and they
+     * are the majority of cases.
+     * The scaling that the browser specifies to make the plugin content fit
+     * when it is larger than a single page can hold is non-uniform. It
+     * scales the axis in which the content is too large just enough to
+     * ensure it fits. For content which is extremely long this could lead
+     * to noticeable distortion. However that is probably rare enough that
+     * its not worth compensating for that here, but we can revisit that if
+     * needed, and compensate by making the scale for the other axis the
+     * same.
+     */
+    public static class PluginPrinter implements Printable {
+
+        private EPSPrinter epsPrinter;
+        private Component applet;
+        private PrintStream stream;
+        private String epsTitle;
+        private int bx, by, bw, bh;
+        private int width, height;
+
+        /**
+         * This is called from the Java Plug-in to print an Applet's
+         * contents as EPS to a postscript stream provided by the browser.
+         * @param applet the applet component to print.
+         * @param stream the print stream provided by the plug-in
+         * @param x the x location of the applet panel in the browser window
+         * @param y the y location of the applet panel in the browser window
+         * @param w the width of the applet panel in the browser window
+         * @param h the width of the applet panel in the browser window
+         */
+        public PluginPrinter(Component applet,
+                             PrintStream stream,
+                             int x, int y, int w, int h) {
+
+            this.applet = applet;
+            this.epsTitle = "Java Plugin Applet";
+            this.stream = stream;
+            bx = x;
+            by = y;
+            bw = w;
+            bh = h;
+            width = applet.size().width;
+            height = applet.size().height;
+            epsPrinter = new EPSPrinter(this, epsTitle, stream,
+                                        0, 0, width, height);
+        }
+
+        public void printPluginPSHeader() {
+            stream.println("%%BeginDocument: JavaPluginApplet");
+        }
+
+        public void printPluginApplet() {
+            try {
+                epsPrinter.print();
+            } catch (PrinterException e) {
+            }
+        }
+
+        public void printPluginPSTrailer() {
+            stream.println("%%EndDocument: JavaPluginApplet");
+            stream.flush();
+        }
+
+        public void printAll() {
+            printPluginPSHeader();
+            printPluginApplet();
+            printPluginPSTrailer();
+        }
+
+        public int print(Graphics g, PageFormat pf, int pgIndex) {
+            if (pgIndex > 0) {
+                return Printable.NO_SUCH_PAGE;
+            } else {
+                // "aware" client code can detect that its been passed a
+                // PrinterGraphics and could theoretically print
+                // differently. I think this is more likely useful than
+                // a problem.
+                applet.printAll(g);
+                return Printable.PAGE_EXISTS;
+            }
+        }
+
+    }
+
+    /*
+     * This class can take an application-client supplied printable object
+     * and send the result to a stream.
+     * The application does not need to send any postscript to this stream
+     * unless it needs to specify a translation etc.
+     * It assumes that its importing application obeys all the conventions
+     * for importation of EPS. See Appendix H - Encapsulated Postscript File
+     * Format - of the Adobe Postscript Language Reference Manual, 2nd edition.
+     * This class could be used as the basis for exposing the ability to
+     * generate EPSF from 2D graphics as a StreamPrintService.
+     * In that case a MediaPrintableArea attribute could be used to
+     * communicate the bounding box.
+     */
+    public static class EPSPrinter implements Pageable {
+
+        private PageFormat pf;
+        private PSPrinterJob job;
+        private int llx, lly, urx, ury;
+        private Printable printable;
+        private PrintStream stream;
+        private String epsTitle;
+
+        public EPSPrinter(Printable printable, String title,
+                          PrintStream stream,
+                          int x, int y, int wid, int hgt) {
+
+            this.printable = printable;
+            this.epsTitle = title;
+            this.stream = stream;
+            llx = x;
+            lly = y;
+            urx = llx+wid;
+            ury = lly+hgt;
+            // construct a PageFormat with zero margins representing the
+            // exact bounds of the applet. ie construct a theoretical
+            // paper which happens to exactly match applet panel size.
+            Paper p = new Paper();
+            p.setSize((double)wid, (double)hgt);
+            p.setImageableArea(0.0,0.0, (double)wid, (double)hgt);
+            pf = new PageFormat();
+            pf.setPaper(p);
+        }
+
+        public void print() throws PrinterException {
+            stream.println("%!PS-Adobe-3.0 EPSF-3.0");
+            stream.println("%%BoundingBox: " +
+                           llx + " " + lly + " " + urx + " " + ury);
+            stream.println("%%Title: " + epsTitle);
+            stream.println("%%Creator: Java Printing");
+            stream.println("%%CreationDate: " + new java.util.Date());
+            stream.println("%%EndComments");
+            stream.println("/pluginSave save def");
+            stream.println("mark"); // for restoring stack state on return
+
+            job = new PSPrinterJob();
+            job.epsPrinter = this; // modifies the behaviour of PSPrinterJob
+            job.mPSStream = stream;
+            job.mDestType = RasterPrinterJob.STREAM; // prevents closure
+
+            job.startDoc();
+            try {
+                job.printPage(this, 0);
+            } catch (Throwable t) {
+                if (t instanceof PrinterException) {
+                    throw (PrinterException)t;
+                } else {
+                    throw new PrinterException(t.toString());
+                }
+            } finally {
+                stream.println("cleartomark"); // restore stack state
+                stream.println("pluginSave restore");
+                job.endDoc();
+            }
+            stream.flush();
+        }
+
+        public int getNumberOfPages() {
+            return 1;
+        }
+
+        public PageFormat getPageFormat(int pgIndex) {
+            if (pgIndex > 0) {
+                throw new IndexOutOfBoundsException("pgIndex");
+            } else {
+                return pf;
+            }
+        }
+
+        public Printable getPrintable(int pgIndex) {
+            if (pgIndex > 0) {
+                throw new IndexOutOfBoundsException("pgIndex");
+            } else {
+            return printable;
+            }
+        }
+
+    }
+}