1 /*
   2  * Copyright 1998-2008 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import java.awt.Color;
  29 import java.awt.Component;
  30 import java.awt.Font;
  31 import java.awt.FontMetrics;
  32 import java.awt.GraphicsEnvironment;
  33 import java.awt.Graphics;
  34 import java.awt.Graphics2D;
  35 import java.awt.HeadlessException;
  36 import java.awt.Rectangle;
  37 import java.awt.Shape;
  38 
  39 import java.awt.image.BufferedImage;
  40 
  41 import java.awt.font.FontRenderContext;
  42 
  43 import java.awt.geom.AffineTransform;
  44 import java.awt.geom.PathIterator;
  45 import java.awt.geom.Rectangle2D;
  46 
  47 import java.awt.image.BufferedImage;
  48 
  49 import java.awt.print.Pageable;
  50 import java.awt.print.PageFormat;
  51 import java.awt.print.Paper;
  52 import java.awt.print.Printable;
  53 import java.awt.print.PrinterException;
  54 import java.awt.print.PrinterIOException;
  55 import java.awt.print.PrinterJob;
  56 
  57 import javax.print.DocFlavor;
  58 import javax.print.PrintService;
  59 import javax.print.StreamPrintService;
  60 import javax.print.attribute.HashPrintRequestAttributeSet;
  61 import javax.print.attribute.PrintRequestAttributeSet;
  62 import javax.print.attribute.standard.Chromaticity;
  63 import javax.print.attribute.standard.Copies;
  64 import javax.print.attribute.standard.Destination;
  65 import javax.print.attribute.standard.DialogTypeSelection;
  66 import javax.print.attribute.standard.JobName;
  67 import javax.print.attribute.standard.Sides;
  68 
  69 import java.io.BufferedInputStream;
  70 import java.io.BufferedOutputStream;
  71 import java.io.CharConversionException;
  72 import java.io.File;
  73 import java.io.InputStream;
  74 import java.io.IOException;
  75 import java.io.FileInputStream;
  76 import java.io.FileOutputStream;
  77 import java.io.OutputStream;
  78 import java.io.PrintStream;
  79 
  80 import java.util.ArrayList;
  81 import java.util.Enumeration;
  82 import java.util.Locale;
  83 import java.util.Properties;
  84 
  85 import sun.awt.CharsetString;
  86 import sun.awt.FontConfiguration;
  87 import sun.awt.FontDescriptor;
  88 import sun.awt.PlatformFont;
  89 import sun.awt.SunToolkit;
  90 
  91 import java.nio.charset.*;
  92 import java.nio.CharBuffer;
  93 import java.nio.ByteBuffer;
  94 
  95 //REMIND: Remove use of this class when IPPPrintService is moved to share directory.
  96 import java.lang.reflect.Method;
  97 
  98 /**
  99  * A class which initiates and executes a PostScript printer job.
 100  *
 101  * @author Richard Blanchard
 102  */
 103 public class PSPrinterJob extends RasterPrinterJob {
 104 
 105  /* Class Constants */
 106 
 107     /**
 108      * Passed to the <code>setFillMode</code>
 109      * method this value forces fills to be
 110      * done using the even-odd fill rule.
 111      */
 112     protected static final int FILL_EVEN_ODD = 1;
 113 
 114     /**
 115      * Passed to the <code>setFillMode</code>
 116      * method this value forces fills to be
 117      * done using the non-zero winding rule.
 118      */
 119     protected static final int FILL_WINDING = 2;
 120 
 121     /* PostScript has a 64K maximum on its strings.
 122      */
 123     private static final int MAX_PSSTR = (1024 * 64 - 1);
 124 
 125     private static final int RED_MASK = 0x00ff0000;
 126     private static final int GREEN_MASK = 0x0000ff00;
 127     private static final int BLUE_MASK = 0x000000ff;
 128 
 129     private static final int RED_SHIFT = 16;
 130     private static final int GREEN_SHIFT = 8;
 131     private static final int BLUE_SHIFT = 0;
 132 
 133     private static final int LOWNIBBLE_MASK = 0x0000000f;
 134     private static final int HINIBBLE_MASK =  0x000000f0;
 135     private static final int HINIBBLE_SHIFT = 4;
 136     private static final byte hexDigits[] = {
 137         (byte)'0', (byte)'1', (byte)'2', (byte)'3',
 138         (byte)'4', (byte)'5', (byte)'6', (byte)'7',
 139         (byte)'8', (byte)'9', (byte)'A', (byte)'B',
 140         (byte)'C', (byte)'D', (byte)'E', (byte)'F'
 141     };
 142 
 143     private static final int PS_XRES = 300;
 144     private static final int PS_YRES = 300;
 145 
 146     private static final String ADOBE_PS_STR =  "%!PS-Adobe-3.0";
 147     private static final String EOF_COMMENT =   "%%EOF";
 148     private static final String PAGE_COMMENT =  "%%Page: ";
 149 
 150     private static final String READIMAGEPROC = "/imStr 0 def /imageSrc " +
 151         "{currentfile /ASCII85Decode filter /RunLengthDecode filter " +
 152         " imStr readstring pop } def";
 153 
 154     private static final String COPIES =        "/#copies exch def";
 155     private static final String PAGE_SAVE =     "/pgSave save def";
 156     private static final String PAGE_RESTORE =  "pgSave restore";
 157     private static final String SHOWPAGE =      "showpage";
 158     private static final String IMAGE_SAVE =    "/imSave save def";
 159     private static final String IMAGE_STR =     " string /imStr exch def";
 160     private static final String IMAGE_RESTORE = "imSave restore";
 161 
 162     private static final String COORD_PREP =    " 0 exch translate "
 163                                               + "1 -1 scale"
 164                                               + "[72 " + PS_XRES + " div "
 165                                               + "0 0 "
 166                                               + "72 " + PS_YRES + " div "
 167                                               + "0 0]concat";
 168 
 169     private static final String SetFontName = "F";
 170 
 171     private static final String DrawStringName = "S";
 172 
 173     /**
 174      * The PostScript invocation to fill a path using the
 175      * even-odd rule. (eofill)
 176      */
 177     private static final String EVEN_ODD_FILL_STR = "EF";
 178 
 179     /**
 180      * The PostScript invocation to fill a path using the
 181      * non-zero winding rule. (fill)
 182      */
 183     private static final String WINDING_FILL_STR = "WF";
 184 
 185     /**
 186      * The PostScript to set the clip to be the current path
 187      * using the even odd rule. (eoclip)
 188      */
 189     private static final String EVEN_ODD_CLIP_STR = "EC";
 190 
 191     /**
 192      * The PostScript to set the clip to be the current path
 193      * using the non-zero winding rule. (clip)
 194      */
 195     private static final String WINDING_CLIP_STR = "WC";
 196 
 197     /**
 198      * Expecting two numbers on the PostScript stack, this
 199      * invocation moves the current pen position. (moveto)
 200      */
 201     private static final String MOVETO_STR = " M";
 202     /**
 203      * Expecting two numbers on the PostScript stack, this
 204      * invocation draws a PS line from the current pen
 205      * position to the point on the stack. (lineto)
 206      */
 207     private static final String LINETO_STR = " L";
 208 
 209     /**
 210      * This PostScript operator takes two control points
 211      * and an ending point and using the current pen
 212      * position as a starting point adds a bezier
 213      * curve to the current path. (curveto)
 214      */
 215     private static final String CURVETO_STR = " C";
 216 
 217     /**
 218      * The PostScript to pop a state off of the printer's
 219      * gstate stack. (grestore)
 220      */
 221     private static final String GRESTORE_STR = "R";
 222     /**
 223      * The PostScript to push a state on to the printer's
 224      * gstate stack. (gsave)
 225      */
 226     private static final String GSAVE_STR = "G";
 227 
 228     /**
 229      * Make the current PostScript path an empty path. (newpath)
 230      */
 231     private static final String NEWPATH_STR = "N";
 232 
 233     /**
 234      * Close the current subpath by generating a line segment
 235      * from the current position to the start of the subpath. (closepath)
 236      */
 237     private static final String CLOSEPATH_STR = "P";
 238 
 239     /**
 240      * Use the three numbers on top of the PS operator
 241      * stack to set the rgb color. (setrgbcolor)
 242      */
 243     private static final String SETRGBCOLOR_STR = " SC";
 244 
 245     /**
 246      * Use the top number on the stack to set the printer's
 247      * current gray value. (setgray)
 248      */
 249     private static final String SETGRAY_STR = " SG";
 250 
 251  /* Instance Variables */
 252 
 253    private int mDestType;
 254 
 255    private String mDestination = "lp";
 256 
 257    private boolean mNoJobSheet = false;
 258 
 259    private String mOptions;
 260 
 261    private Font mLastFont;
 262 
 263    private Color mLastColor;
 264 
 265    private Shape mLastClip;
 266 
 267    private AffineTransform mLastTransform;
 268 
 269    /* non-null if printing EPS for Java Plugin */
 270    private EPSPrinter epsPrinter = null;
 271 
 272    /**
 273     * The metrics for the font currently set.
 274     */
 275    FontMetrics mCurMetrics;
 276 
 277    /**
 278     * The output stream to which the generated PostScript
 279     * is written.
 280     */
 281    PrintStream mPSStream;
 282 
 283    /* The temporary file to which we spool before sending to the printer  */
 284 
 285    File spoolFile;
 286 
 287    /**
 288     * This string holds the PostScript operator to
 289     * be used to fill a path. It can be changed
 290     * by the <code>setFillMode</code> method.
 291     */
 292     private String mFillOpStr = WINDING_FILL_STR;
 293 
 294    /**
 295     * This string holds the PostScript operator to
 296     * be used to clip to a path. It can be changed
 297     * by the <code>setFillMode</code> method.
 298     */
 299     private String mClipOpStr = WINDING_CLIP_STR;
 300 
 301    /**
 302     * A stack that represents the PostScript gstate stack.
 303     */
 304    ArrayList mGStateStack = new ArrayList();
 305 
 306    /**
 307     * The x coordinate of the current pen position.
 308     */
 309    private float mPenX;
 310 
 311    /**
 312     * The y coordinate of the current pen position.
 313     */
 314    private float mPenY;
 315 
 316    /**
 317     * The x coordinate of the starting point of
 318     * the current subpath.
 319     */
 320    private float mStartPathX;
 321 
 322    /**
 323     * The y coordinate of the starting point of
 324     * the current subpath.
 325     */
 326    private float mStartPathY;
 327 
 328    /**
 329     * An optional mapping of fonts to PostScript names.
 330     */
 331    private static Properties mFontProps = null;
 332 
 333     /* Class static initialiser block */
 334     static {
 335        //enable priviledges so initProps can access system properties,
 336         // open the property file, etc.
 337         java.security.AccessController.doPrivileged(
 338                             new java.security.PrivilegedAction() {
 339             public Object run() {
 340                 mFontProps = initProps();
 341                 return null;
 342             }
 343         });
 344     }
 345 
 346     /*
 347      * Initialize PostScript font properties.
 348      * Copied from PSPrintStream
 349      */
 350     private static Properties initProps() {
 351         // search psfont.properties for fonts
 352         // and create and initialize fontProps if it exist.
 353 
 354         String jhome = System.getProperty("java.home");
 355 
 356         if (jhome != null){
 357             String ulocale = SunToolkit.getStartupLocale().getLanguage();
 358             try {
 359 
 360                 File f = new File(jhome + File.separator +
 361                                   "lib" + File.separator +
 362                                   "psfontj2d.properties." + ulocale);
 363 
 364                 if (!f.canRead()){
 365 
 366                     f = new File(jhome + File.separator +
 367                                       "lib" + File.separator +
 368                                       "psfont.properties." + ulocale);
 369                     if (!f.canRead()){
 370 
 371                         f = new File(jhome + File.separator + "lib" +
 372                                      File.separator + "psfontj2d.properties");
 373 
 374                         if (!f.canRead()){
 375 
 376                             f = new File(jhome + File.separator + "lib" +
 377                                          File.separator + "psfont.properties");
 378 
 379                             if (!f.canRead()){
 380                                 return (Properties)null;
 381                             }
 382                         }
 383                     }
 384                 }
 385 
 386                 // Load property file
 387                 InputStream in =
 388                     new BufferedInputStream(new FileInputStream(f.getPath()));
 389                 Properties props = new Properties();
 390                 props.load(in);
 391                 in.close();
 392                 return props;
 393             } catch (Exception e){
 394                 return (Properties)null;
 395             }
 396         }
 397         return (Properties)null;
 398     }
 399 
 400  /* Constructors */
 401 
 402     public PSPrinterJob()
 403     {
 404     }
 405 
 406  /* Instance Methods */
 407 
 408    /**
 409      * Presents the user a dialog for changing properties of the
 410      * print job interactively.
 411      * @returns false if the user cancels the dialog and
 412      *          true otherwise.
 413      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
 414      * returns true.
 415      * @see java.awt.GraphicsEnvironment#isHeadless
 416      */
 417     public boolean printDialog() throws HeadlessException {
 418 
 419         if (GraphicsEnvironment.isHeadless()) {
 420             throw new HeadlessException();
 421         }
 422 
 423         if (attributes == null) {
 424             attributes = new HashPrintRequestAttributeSet();
 425         }
 426         attributes.add(new Copies(getCopies()));
 427         attributes.add(new JobName(getJobName(), null));
 428 
 429         boolean doPrint = false;
 430         DialogTypeSelection dts =
 431             (DialogTypeSelection)attributes.get(DialogTypeSelection.class);
 432         if (dts == DialogTypeSelection.NATIVE) {
 433             // Remove DialogTypeSelection.NATIVE to prevent infinite loop in
 434             // RasterPrinterJob.
 435             attributes.remove(DialogTypeSelection.class);
 436             doPrint = printDialog(attributes);
 437             // restore attribute
 438             attributes.add(DialogTypeSelection.NATIVE);
 439         } else {
 440             doPrint = printDialog(attributes);
 441         }
 442 
 443         if (doPrint) {
 444             JobName jobName = (JobName)attributes.get(JobName.class);
 445             if (jobName != null) {
 446                 setJobName(jobName.getValue());
 447             }
 448             Copies copies = (Copies)attributes.get(Copies.class);
 449             if (copies != null) {
 450                 setCopies(copies.getValue());
 451             }
 452 
 453             Destination dest = (Destination)attributes.get(Destination.class);
 454 
 455             if (dest != null) {
 456                 try {
 457                     mDestType = RasterPrinterJob.FILE;
 458                     mDestination = (new File(dest.getURI())).getPath();
 459                 } catch (Exception e) {
 460                     mDestination = "out.ps";
 461                 }
 462             } else {
 463                 mDestType = RasterPrinterJob.PRINTER;
 464                 PrintService pServ = getPrintService();
 465                 if (pServ != null) {
 466                     mDestination = pServ.getName();
 467                 }
 468             }
 469         }
 470 
 471         return doPrint;
 472     }
 473 
 474     /**
 475      * Invoked by the RasterPrinterJob super class
 476      * this method is called to mark the start of a
 477      * document.
 478      */
 479     protected void startDoc() throws PrinterException {
 480 
 481         // A security check has been performed in the
 482         // java.awt.print.printerJob.getPrinterJob method.
 483         // We use an inner class to execute the privilged open operations.
 484         // Note that we only open a file if it has been nominated by
 485         // the end-user in a dialog that we ouselves put up.
 486 
 487         OutputStream output;
 488 
 489         if (epsPrinter == null) {
 490             if (getPrintService() instanceof PSStreamPrintService) {
 491                 StreamPrintService sps = (StreamPrintService)getPrintService();
 492                 mDestType = RasterPrinterJob.STREAM;
 493                 if (sps.isDisposed()) {
 494                     throw new PrinterException("service is disposed");
 495                 }
 496                 output = sps.getOutputStream();
 497                 if (output == null) {
 498                     throw new PrinterException("Null output stream");
 499                 }
 500             } else {
 501                 /* REMIND: This needs to be more maintainable */
 502                 mNoJobSheet = super.noJobSheet;
 503                 if (super.destinationAttr != null) {
 504                     mDestType = RasterPrinterJob.FILE;
 505                     mDestination = super.destinationAttr;
 506                 }
 507                 if (mDestType == RasterPrinterJob.FILE) {
 508                     try {
 509                         spoolFile = new File(mDestination);
 510                         output =  new FileOutputStream(spoolFile);
 511                     } catch (IOException ex) {
 512                         throw new PrinterIOException(ex);
 513                     }
 514                 } else {
 515                     PrinterOpener po = new PrinterOpener();
 516                     java.security.AccessController.doPrivileged(po);
 517                     if (po.pex != null) {
 518                         throw po.pex;
 519                     }
 520                     output = po.result;
 521                 }
 522             }
 523 
 524             mPSStream = new PrintStream(new BufferedOutputStream(output));
 525             mPSStream.println(ADOBE_PS_STR);
 526         }
 527 
 528         mPSStream.println("%%BeginProlog");
 529         mPSStream.println(READIMAGEPROC);
 530         mPSStream.println("/BD {bind def} bind def");
 531         mPSStream.println("/D {def} BD");
 532         mPSStream.println("/C {curveto} BD");
 533         mPSStream.println("/L {lineto} BD");
 534         mPSStream.println("/M {moveto} BD");
 535         mPSStream.println("/R {grestore} BD");
 536         mPSStream.println("/G {gsave} BD");
 537         mPSStream.println("/N {newpath} BD");
 538         mPSStream.println("/P {closepath} BD");
 539         mPSStream.println("/EC {eoclip} BD");
 540         mPSStream.println("/WC {clip} BD");
 541         mPSStream.println("/EF {eofill} BD");
 542         mPSStream.println("/WF {fill} BD");
 543         mPSStream.println("/SG {setgray} BD");
 544         mPSStream.println("/SC {setrgbcolor} BD");
 545         mPSStream.println("/ISOF {");
 546         mPSStream.println("     dup findfont dup length 1 add dict begin {");
 547         mPSStream.println("             1 index /FID eq {pop pop} {D} ifelse");
 548         mPSStream.println("     } forall /Encoding ISOLatin1Encoding D");
 549         mPSStream.println("     currentdict end definefont");
 550         mPSStream.println("} BD");
 551         mPSStream.println("/NZ {dup 1 lt {pop 1} if} BD");
 552         /* The following procedure takes args: string, x, y, desiredWidth.
 553          * It calculates using stringwidth the width of the string in the
 554          * current font and subtracts it from the desiredWidth and divides
 555          * this by stringLen-1. This gives us a per-glyph adjustment in
 556          * the spacing needed (either +ve or -ve) to make the string
 557          * print at the desiredWidth. The ashow procedure call takes this
 558          * per-glyph adjustment as an argument. This is necessary for WYSIWYG
 559          */
 560         mPSStream.println("/"+DrawStringName +" {");
 561         mPSStream.println("     moveto 1 index stringwidth pop NZ sub");
 562         mPSStream.println("     1 index length 1 sub NZ div 0");
 563         mPSStream.println("     3 2 roll ashow newpath} BD");
 564         mPSStream.println("/FL [");
 565         if (mFontProps == null){
 566             mPSStream.println(" /Helvetica ISOF");
 567             mPSStream.println(" /Helvetica-Bold ISOF");
 568             mPSStream.println(" /Helvetica-Oblique ISOF");
 569             mPSStream.println(" /Helvetica-BoldOblique ISOF");
 570             mPSStream.println(" /Times-Roman ISOF");
 571             mPSStream.println(" /Times-Bold ISOF");
 572             mPSStream.println(" /Times-Italic ISOF");
 573             mPSStream.println(" /Times-BoldItalic ISOF");
 574             mPSStream.println(" /Courier ISOF");
 575             mPSStream.println(" /Courier-Bold ISOF");
 576             mPSStream.println(" /Courier-Oblique ISOF");
 577             mPSStream.println(" /Courier-BoldOblique ISOF");
 578         } else {
 579             int cnt = Integer.parseInt(mFontProps.getProperty("font.num", "9"));
 580             for (int i = 0; i < cnt; i++){
 581                 mPSStream.println("    /" + mFontProps.getProperty
 582                            ("font." + String.valueOf(i), "Courier ISOF"));
 583             }
 584         }
 585         mPSStream.println("] D");
 586 
 587         mPSStream.println("/"+SetFontName +" {");
 588         mPSStream.println("     FL exch get exch scalefont");
 589         mPSStream.println("     [1 0 0 -1 0 0] makefont setfont} BD");
 590 
 591         mPSStream.println("%%EndProlog");
 592 
 593         mPSStream.println("%%BeginSetup");
 594         if (epsPrinter == null) {
 595             // Set Page Size using first page's format.
 596             PageFormat pageFormat = getPageable().getPageFormat(0);
 597             double paperHeight = pageFormat.getPaper().getHeight();
 598             double paperWidth = pageFormat.getPaper().getWidth();
 599 
 600             /* PostScript printers can always generate uncollated copies.
 601              */
 602             mPSStream.print("<< /PageSize [" +
 603                                            paperWidth + " "+ paperHeight+"]");
 604 
 605             final PrintService pservice = getPrintService();
 606             Boolean isPS = (Boolean)java.security.AccessController.doPrivileged(
 607                 new java.security.PrivilegedAction() {
 608                     public Object run() {
 609                        try {
 610                            Class psClass = Class.forName("sun.print.IPPPrintService");
 611                            if (psClass.isInstance(pservice)) {
 612                                Method isPSMethod = psClass.getMethod("isPostscript",
 613                                                                      (Class[])null);
 614                                return (Boolean)isPSMethod.invoke(pservice, (Object[])null);
 615                            }
 616                        } catch (Throwable t) {
 617                        }
 618                        return Boolean.TRUE;
 619                     }
 620                 }
 621             );
 622             if (isPS) {
 623                 mPSStream.print(" /DeferredMediaSelection true");
 624             }
 625 
 626             mPSStream.print(" /ImagingBBox null /ManualFeed false");
 627             mPSStream.print(isCollated() ? " /Collate true":"");
 628             mPSStream.print(" /NumCopies " +getCopiesInt());
 629 
 630             if (sidesAttr != Sides.ONE_SIDED) {
 631                 if (sidesAttr == Sides.TWO_SIDED_LONG_EDGE) {
 632                     mPSStream.print(" /Duplex true ");
 633                 } else if (sidesAttr == Sides.TWO_SIDED_SHORT_EDGE) {
 634                     mPSStream.print(" /Duplex true /Tumble true ");
 635                 }
 636             }
 637             mPSStream.println(" >> setpagedevice ");
 638         }
 639         mPSStream.println("%%EndSetup");
 640     }
 641 
 642     // Inner class to run "privileged" to open the printer output stream.
 643 
 644     private class PrinterOpener implements java.security.PrivilegedAction {
 645         PrinterException pex;
 646         OutputStream result;
 647 
 648         public Object run() {
 649             try {
 650 
 651                     /* Write to a temporary file which will be spooled to
 652                      * the printer then deleted. In the case that the file
 653                      * is not removed for some reason, request that it is
 654                      * removed when the VM exits.
 655                      */
 656                     spoolFile = File.createTempFile("javaprint", ".ps", null);
 657                     spoolFile.deleteOnExit();
 658 
 659                 result = new FileOutputStream(spoolFile);
 660                 return result;
 661             } catch (IOException ex) {
 662                 // If there is an IOError we subvert it to a PrinterException.
 663                 pex = new PrinterIOException(ex);
 664             }
 665             return null;
 666         }
 667     }
 668 
 669     // Inner class to run "privileged" to invoke the system print command
 670 
 671     private class PrinterSpooler implements java.security.PrivilegedAction {
 672         PrinterException pex;
 673 
 674         public Object run() {
 675             try {
 676                 /**
 677                  * Spool to the printer.
 678                  */
 679                 if (spoolFile == null || !spoolFile.exists()) {
 680                    pex = new PrinterException("No spool file");
 681                    return null;
 682                 }
 683                 String fileName = spoolFile.getAbsolutePath();
 684                 String execCmd[] = printExecCmd(mDestination, mOptions,
 685                                mNoJobSheet, getJobNameInt(),
 686                                                 1, fileName);
 687 
 688                 Process process = Runtime.getRuntime().exec(execCmd);
 689                 process.waitFor();
 690                 spoolFile.delete();
 691 
 692             } catch (IOException ex) {
 693                 pex = new PrinterIOException(ex);
 694             } catch (InterruptedException ie) {
 695                 pex = new PrinterException(ie.toString());
 696             }
 697             return null;
 698         }
 699     }
 700 
 701 
 702     /**
 703      * Invoked if the application cancelled the printjob.
 704      */
 705     protected void abortDoc() {
 706         if (mPSStream != null && mDestType != RasterPrinterJob.STREAM) {
 707             mPSStream.close();
 708         }
 709         java.security.AccessController.doPrivileged(
 710             new java.security.PrivilegedAction() {
 711 
 712             public Object run() {
 713                if (spoolFile != null && spoolFile.exists()) {
 714                    spoolFile.delete();
 715                }
 716                return null;
 717             }
 718         });
 719     }
 720 
 721     /**
 722      * Invoked by the RasterPrintJob super class
 723      * this method is called after that last page
 724      * has been imaged.
 725      */
 726     protected void endDoc() throws PrinterException {
 727         if (mPSStream != null) {
 728             mPSStream.println(EOF_COMMENT);
 729             mPSStream.flush();
 730             if (mDestType != RasterPrinterJob.STREAM) {
 731                 mPSStream.close();
 732             }
 733         }
 734         if (mDestType == RasterPrinterJob.PRINTER) {
 735             if (getPrintService() != null) {
 736                 mDestination = getPrintService().getName();
 737             }
 738             PrinterSpooler spooler = new PrinterSpooler();
 739             java.security.AccessController.doPrivileged(spooler);
 740             if (spooler.pex != null) {
 741                 throw spooler.pex;
 742             }
 743         }
 744     }
 745 
 746     /**
 747      * The RasterPrintJob super class calls this method
 748      * at the start of each page.
 749      */
 750     protected void startPage(PageFormat pageFormat, Printable painter,
 751                              int index, boolean paperChanged)
 752         throws PrinterException
 753     {
 754         double paperHeight = pageFormat.getPaper().getHeight();
 755         double paperWidth = pageFormat.getPaper().getWidth();
 756         int pageNumber = index + 1;
 757 
 758         /* Place an initial gstate on to our gstate stack.
 759          * It will have the default PostScript gstate
 760          * attributes.
 761          */
 762         mGStateStack = new ArrayList();
 763         mGStateStack.add(new GState());
 764 
 765         mPSStream.println(PAGE_COMMENT + pageNumber + " " + pageNumber);
 766 
 767         /* Check current page's pageFormat against the previous pageFormat,
 768          */
 769         if (index > 0 && paperChanged) {
 770 
 771             mPSStream.print("<< /PageSize [" +
 772                             paperWidth + " " + paperHeight + "]");
 773 
 774             final PrintService pservice = getPrintService();
 775             Boolean isPS =
 776                 (Boolean)java.security.AccessController.doPrivileged(
 777 
 778                 new java.security.PrivilegedAction() {
 779                     public Object run() {
 780                         try {
 781                             Class psClass =
 782                                 Class.forName("sun.print.IPPPrintService");
 783                             if (psClass.isInstance(pservice)) {
 784                                 Method isPSMethod =
 785                                     psClass.getMethod("isPostscript",
 786                                                       (Class[])null);
 787                                 return (Boolean)
 788                                     isPSMethod.invoke(pservice,
 789                                                       (Object[])null);
 790                             }
 791                         } catch (Throwable t) {
 792                         }
 793                         return Boolean.TRUE;
 794                     }
 795                     }
 796                 );
 797 
 798             if (isPS) {
 799                 mPSStream.print(" /DeferredMediaSelection true");
 800             }
 801             mPSStream.println(" >> setpagedevice");
 802         }
 803         mPSStream.println(PAGE_SAVE);
 804         mPSStream.println(paperHeight + COORD_PREP);
 805     }
 806 
 807     /**
 808      * The RastePrintJob super class calls this method
 809      * at the end of each page.
 810      */
 811     protected void endPage(PageFormat format, Printable painter,
 812                            int index)
 813         throws PrinterException
 814     {
 815         mPSStream.println(PAGE_RESTORE);
 816         mPSStream.println(SHOWPAGE);
 817     }
 818 
 819    /**
 820      * Convert the 24 bit BGR image buffer represented by
 821      * <code>image</code> to PostScript. The image is drawn at
 822      * <code>(destX, destY)</code> in device coordinates.
 823      * The image is scaled into a square of size
 824      * specified by <code>destWidth</code> and
 825      * <code>destHeight</code>. The portion of the
 826      * source image copied into that square is specified
 827      * by <code>srcX</code>, <code>srcY</code>,
 828      * <code>srcWidth</code>, and srcHeight.
 829      */
 830     protected void drawImageBGR(byte[] bgrData,
 831                                    float destX, float destY,
 832                                    float destWidth, float destHeight,
 833                                    float srcX, float srcY,
 834                                    float srcWidth, float srcHeight,
 835                                    int srcBitMapWidth, int srcBitMapHeight) {
 836 
 837         /* We draw images at device resolution so we probably need
 838          * to change the current PostScript transform.
 839          */
 840         setTransform(new AffineTransform());
 841         prepDrawing();
 842 
 843         int intSrcWidth = (int) srcWidth;
 844         int intSrcHeight = (int) srcHeight;
 845 
 846         mPSStream.println(IMAGE_SAVE);
 847 
 848         /* Create a PS string big enough to hold a row of pixels.
 849          */
 850         int psBytesPerRow = 3 * (int) intSrcWidth;
 851         while (psBytesPerRow > MAX_PSSTR) {
 852             psBytesPerRow /= 2;
 853         }
 854 
 855         mPSStream.println(psBytesPerRow + IMAGE_STR);
 856 
 857         /* Scale and translate the unit image.
 858          */
 859         mPSStream.println("[" + destWidth + " 0 "
 860                           + "0 " + destHeight
 861                           + " " + destX + " " + destY
 862                           +"]concat");
 863 
 864         /* Color Image invocation.
 865          */
 866         mPSStream.println(intSrcWidth + " " + intSrcHeight + " " + 8 + "["
 867                           + intSrcWidth + " 0 "
 868                           + "0 " + intSrcHeight
 869                           + " 0 " + 0 + "]"
 870                           + "/imageSrc load false 3 colorimage");
 871 
 872         /* Image data.
 873          */
 874         int index = 0;
 875         byte[] rgbData = new byte[intSrcWidth * 3];
 876 
 877         try {
 878             /* Skip the parts of the image that are not part
 879              * of the source rectangle.
 880              */
 881             index = (int) srcY * srcBitMapWidth;
 882 
 883             for(int i = 0; i < intSrcHeight; i++) {
 884 
 885                 /* Skip the left part of the image that is not
 886                  * part of the source rectangle.
 887                  */
 888                 index += (int) srcX;
 889 
 890                 index = swapBGRtoRGB(bgrData, index, rgbData);
 891                 byte[] encodedData = rlEncode(rgbData);
 892                 byte[] asciiData = ascii85Encode(encodedData);
 893                 mPSStream.write(asciiData);
 894                 mPSStream.println("");
 895             }
 896 
 897             /*
 898              * If there is an IOError we subvert it to a PrinterException.
 899              * Fix: There has got to be a better way, maybe define
 900              * a PrinterIOException and then throw that?
 901              */
 902         } catch (IOException e) {
 903             //throw new PrinterException(e.toString());
 904         }
 905 
 906         mPSStream.println(IMAGE_RESTORE);
 907     }
 908 
 909     /**
 910      * Prints the contents of the array of ints, 'data'
 911      * to the current page. The band is placed at the
 912      * location (x, y) in device coordinates on the
 913      * page. The width and height of the band is
 914      * specified by the caller. Currently the data
 915      * is 24 bits per pixel in BGR format.
 916      */
 917     protected void printBand(byte[] bgrData, int x, int y,
 918                              int width, int height)
 919         throws PrinterException
 920     {
 921 
 922         mPSStream.println(IMAGE_SAVE);
 923 
 924         /* Create a PS string big enough to hold a row of pixels.
 925          */
 926         int psBytesPerRow = 3 * width;
 927         while (psBytesPerRow > MAX_PSSTR) {
 928             psBytesPerRow /= 2;
 929         }
 930 
 931         mPSStream.println(psBytesPerRow + IMAGE_STR);
 932 
 933         /* Scale and translate the unit image.
 934          */
 935         mPSStream.println("[" + width + " 0 "
 936                           + "0 " + height
 937                           + " " + x + " " + y
 938                           +"]concat");
 939 
 940         /* Color Image invocation.
 941          */
 942         mPSStream.println(width + " " + height + " " + 8 + "["
 943                           + width + " 0 "
 944                           + "0 " + -height
 945                           + " 0 " + height + "]"
 946                           + "/imageSrc load false 3 colorimage");
 947 
 948         /* Image data.
 949          */
 950         int index = 0;
 951         byte[] rgbData = new byte[width*3];
 952 
 953         try {
 954             for(int i = 0; i < height; i++) {
 955                 index = swapBGRtoRGB(bgrData, index, rgbData);
 956                 byte[] encodedData = rlEncode(rgbData);
 957                 byte[] asciiData = ascii85Encode(encodedData);
 958                 mPSStream.write(asciiData);
 959                 mPSStream.println("");
 960             }
 961 
 962         } catch (IOException e) {
 963             throw new PrinterIOException(e);
 964         }
 965 
 966         mPSStream.println(IMAGE_RESTORE);
 967     }
 968 
 969     /**
 970      * Examine the metrics captured by the
 971      * <code>PeekGraphics</code> instance and
 972      * if capable of directly converting this
 973      * print job to the printer's control language
 974      * or the native OS's graphics primitives, then
 975      * return a <code>PSPathGraphics</code> to perform
 976      * that conversion. If there is not an object
 977      * capable of the conversion then return
 978      * <code>null</code>. Returning <code>null</code>
 979      * causes the print job to be rasterized.
 980      */
 981 
 982     protected Graphics2D createPathGraphics(PeekGraphics peekGraphics,
 983                                             PrinterJob printerJob,
 984                                             Printable painter,
 985                                             PageFormat pageFormat,
 986                                             int pageIndex) {
 987 
 988         PSPathGraphics pathGraphics;
 989         PeekMetrics metrics = peekGraphics.getMetrics();
 990 
 991         /* If the application has drawn anything that
 992          * out PathGraphics class can not handle then
 993          * return a null PathGraphics.
 994          */
 995         if (forcePDL == false && (forceRaster == true
 996                         || metrics.hasNonSolidColors()
 997                         || metrics.hasCompositing())) {
 998 
 999             pathGraphics = null;
1000         } else {
1001 
1002             BufferedImage bufferedImage = new BufferedImage(8, 8,
1003                                             BufferedImage.TYPE_INT_RGB);
1004             Graphics2D bufferedGraphics = bufferedImage.createGraphics();
1005             boolean canRedraw = peekGraphics.getAWTDrawingOnly() == false;
1006 
1007             pathGraphics =  new PSPathGraphics(bufferedGraphics, printerJob,
1008                                                painter, pageFormat, pageIndex,
1009                                                canRedraw);
1010         }
1011 
1012         return pathGraphics;
1013     }
1014 
1015     /**
1016      * Intersect the gstate's current path with the
1017      * current clip and make the result the new clip.
1018      */
1019     protected void selectClipPath() {
1020 
1021         mPSStream.println(mClipOpStr);
1022     }
1023 
1024     protected void setClip(Shape clip) {
1025 
1026         mLastClip = clip;
1027     }
1028 
1029     protected void setTransform(AffineTransform transform) {
1030         mLastTransform = transform;
1031     }
1032 
1033     /**
1034      * Set the current PostScript font.
1035      * Taken from outFont in PSPrintStream.
1036      */
1037      protected boolean setFont(Font font) {
1038         mLastFont = font;
1039         return true;
1040     }
1041 
1042     /**
1043      * Given an array of CharsetStrings that make up a run
1044      * of text, this routine converts each CharsetString to
1045      * an index into our PostScript font list. If one or more
1046      * CharsetStrings can not be represented by a PostScript
1047      * font, then this routine will return a null array.
1048      */
1049      private int[] getPSFontIndexArray(Font font, CharsetString[] charSet) {
1050         int[] psFont = null;
1051 
1052         if (mFontProps != null) {
1053             psFont = new int[charSet.length];
1054         }
1055 
1056         for (int i = 0; i < charSet.length && psFont != null; i++){
1057 
1058             /* Get the encoding of the run of text.
1059              */
1060             CharsetString cs = charSet[i];
1061 
1062             CharsetEncoder fontCS = cs.fontDescriptor.encoder;
1063             String charsetName = cs.fontDescriptor.getFontCharsetName();
1064             /*
1065              * sun.awt.Symbol perhaps should return "symbol" for encoding.
1066              * Similarly X11Dingbats should return "dingbats"
1067              * Forced to check for win32 & x/unix names for these converters.
1068              */
1069 
1070             if ("Symbol".equals(charsetName)) {
1071                 charsetName = "symbol";
1072             } else if ("WingDings".equals(charsetName) ||
1073                        "X11Dingbats".equals(charsetName)) {
1074                 charsetName = "dingbats";
1075             } else {
1076                 charsetName = makeCharsetName(charsetName, cs.charsetChars);
1077             }
1078 
1079             int styleMask = font.getStyle() |
1080                 sun.font.FontManager.getFont2D(font).getStyle();
1081 
1082             String style = FontConfiguration.getStyleString(styleMask);
1083 
1084             /* First we map the font name through the properties file.
1085              * This mapping provides alias names for fonts, for example,
1086              * "timesroman" is mapped to "serif".
1087              */
1088             String fontName = font.getFamily().toLowerCase(Locale.ENGLISH);
1089             fontName = fontName.replace(' ', '_');
1090             String name = mFontProps.getProperty(fontName, "");
1091 
1092             /* Now map the alias name, character set name, and style
1093              * to a PostScript name.
1094              */
1095             String psName =
1096                 mFontProps.getProperty(name + "." + charsetName + "." + style,
1097                                       null);
1098 
1099             if (psName != null) {
1100 
1101                 /* Get the PostScript font index for the PostScript font.
1102                  */
1103                 try {
1104                     psFont[i] =
1105                         Integer.parseInt(mFontProps.getProperty(psName));
1106 
1107                 /* If there is no PostScript font for this font name,
1108                  * then we want to termintate the loop and the method
1109                  * indicating our failure. Setting the array to null
1110                  * is used to indicate these failures.
1111                  */
1112                 } catch(NumberFormatException e){
1113                     psFont = null;
1114                 }
1115 
1116             /* There was no PostScript name for the font, character set,
1117              * and style so give up.
1118              */
1119             } else {
1120                 psFont = null;
1121             }
1122         }
1123 
1124          return psFont;
1125      }
1126 
1127 
1128     private static String escapeParens(String str) {
1129         if (str.indexOf('(') == -1 && str.indexOf(')') == -1 ) {
1130             return str;
1131         } else {
1132             int count = 0;
1133             int pos = 0;
1134             while ((pos = str.indexOf('(', pos)) != -1) {
1135                 count++;
1136                 pos++;
1137             }
1138             pos = 0;
1139             while ((pos = str.indexOf(')', pos)) != -1) {
1140                 count++;
1141                 pos++;
1142             }
1143             char []inArr = str.toCharArray();
1144             char []outArr = new char[inArr.length+count];
1145             pos = 0;
1146             for (int i=0;i<inArr.length;i++) {
1147                 if (inArr[i] == '(' || inArr[i] == ')') {
1148                     outArr[pos++] = '\\';
1149                 }
1150                 outArr[pos++] = inArr[i];
1151             }
1152             return new String(outArr);
1153 
1154         }
1155     }
1156 
1157     /* return of 0 means unsupported. Other return indicates the number
1158      * of distinct PS fonts needed to draw this text. This saves us
1159      * doing this processing one extra time.
1160      */
1161     protected int platformFontCount(Font font, String str) {
1162         if (mFontProps == null) {
1163             return 0;
1164         }
1165         CharsetString[] acs =
1166             ((PlatformFont)(font.getPeer())).makeMultiCharsetString(str,false);
1167         if (acs == null) {
1168             /* AWT can't convert all chars so use 2D path */
1169             return 0;
1170         }
1171         int[] psFonts = getPSFontIndexArray(font, acs);
1172         return (psFonts == null) ? 0 : psFonts.length;
1173     }
1174 
1175      protected boolean textOut(Graphics g, String str, float x, float y,
1176                                Font mLastFont, FontRenderContext frc,
1177                                float width) {
1178         boolean didText = true;
1179 
1180         if (mFontProps == null) {
1181             return false;
1182         } else {
1183             prepDrawing();
1184 
1185             /* On-screen drawString renders most control chars as the missing
1186              * glyph and have the non-zero advance of that glyph.
1187              * Exceptions are \t, \n and \r which are considered zero-width.
1188              * Postscript handles control chars mostly as a missing glyph.
1189              * But we use 'ashow' specifying a width for the string which
1190              * assumes zero-width for those three exceptions, and Postscript
1191              * tries to squeeze the extra char in, with the result that the
1192              * glyphs look compressed or even overlap.
1193              * So exclude those control chars from the string sent to PS.
1194              */
1195             str = removeControlChars(str);
1196             if (str.length() == 0) {
1197                 return true;
1198             }
1199             CharsetString[] acs =
1200                 ((PlatformFont)
1201                  (mLastFont.getPeer())).makeMultiCharsetString(str, false);
1202             if (acs == null) {
1203                 /* AWT can't convert all chars so use 2D path */
1204                 return false;
1205             }
1206             /* Get an array of indices into our PostScript name
1207              * table. If all of the runs can not be converted
1208              * to PostScript fonts then null is returned and
1209              * we'll want to fall back to printing the text
1210              * as shapes.
1211              */
1212             int[] psFonts = getPSFontIndexArray(mLastFont, acs);
1213             if (psFonts != null) {
1214 
1215                 for (int i = 0; i < acs.length; i++){
1216                     CharsetString cs = acs[i];
1217                     CharsetEncoder fontCS = cs.fontDescriptor.encoder;
1218 
1219                     StringBuffer nativeStr = new StringBuffer();
1220                     byte[] strSeg = new byte[cs.length * 2];
1221                     int len = 0;
1222                     try {
1223                         ByteBuffer bb = ByteBuffer.wrap(strSeg);
1224                         fontCS.encode(CharBuffer.wrap(cs.charsetChars,
1225                                                       cs.offset,
1226                                                       cs.length),
1227                                       bb, true);
1228                         bb.flip();
1229                         len = bb.limit();
1230                     } catch(IllegalStateException xx){
1231                         continue;
1232                     } catch(CoderMalfunctionError xx){
1233                         continue;
1234                     }
1235                     /* The width to fit to may either be specified,
1236                      * or calculated. Specifying by the caller is only
1237                      * valid if the text does not need to be decomposed
1238                      * into multiple calls.
1239                      */
1240                     float desiredWidth;
1241                     if (acs.length == 1 && width != 0f) {
1242                         desiredWidth = width;
1243                     } else {
1244                         Rectangle2D r2d =
1245                             mLastFont.getStringBounds(cs.charsetChars,
1246                                                       cs.offset,
1247                                                       cs.offset+cs.length,
1248                                                       frc);
1249                         desiredWidth = (float)r2d.getWidth();
1250                     }
1251                     /* unprintable chars had width of 0, causing a PS error
1252                      */
1253                     if (desiredWidth == 0) {
1254                         return didText;
1255                     }
1256                     nativeStr.append('<');
1257                     for (int j = 0; j < len; j++){
1258                         byte b = strSeg[j];
1259                         // to avoid encoding conversion with println()
1260                         String hexS = Integer.toHexString(b);
1261                         int length = hexS.length();
1262                         if (length > 2) {
1263                             hexS = hexS.substring(length - 2, length);
1264                         } else if (length == 1) {
1265                             hexS = "0" + hexS;
1266                         } else if (length == 0) {
1267                             hexS = "00";
1268                         }
1269                         nativeStr.append(hexS);
1270                     }
1271                     nativeStr.append('>');
1272                     /* This comment costs too much in output file size */
1273 //                  mPSStream.println("% Font[" + mLastFont.getName() + ", " +
1274 //                             FontConfiguration.getStyleString(mLastFont.getStyle()) + ", "
1275 //                             + mLastFont.getSize2D() + "]");
1276                     getGState().emitPSFont(psFonts[i], mLastFont.getSize2D());
1277 
1278                     // out String
1279                     mPSStream.println(nativeStr.toString() + " " +
1280                                       desiredWidth + " " + x + " " + y + " " +
1281                                       DrawStringName);
1282                     x += desiredWidth;
1283                 }
1284             } else {
1285                 didText = false;
1286             }
1287         }
1288 
1289         return didText;
1290      }
1291     /**
1292      * Set the current path rule to be either
1293      * <code>FILL_EVEN_ODD</code> (using the
1294      * even-odd file rule) or <code>FILL_WINDING</code>
1295      * (using the non-zero winding rule.)
1296      */
1297     protected void setFillMode(int fillRule) {
1298 
1299         switch (fillRule) {
1300 
1301          case FILL_EVEN_ODD:
1302             mFillOpStr = EVEN_ODD_FILL_STR;
1303             mClipOpStr = EVEN_ODD_CLIP_STR;
1304             break;
1305 
1306          case FILL_WINDING:
1307              mFillOpStr = WINDING_FILL_STR;
1308              mClipOpStr = WINDING_CLIP_STR;
1309              break;
1310 
1311          default:
1312              throw new IllegalArgumentException();
1313         }
1314 
1315     }
1316 
1317     /**
1318      * Set the printer's current color to be that
1319      * defined by <code>color</code>
1320      */
1321     protected void setColor(Color color) {
1322         mLastColor = color;
1323     }
1324 
1325     /**
1326      * Fill the current path using the current fill mode
1327      * and color.
1328      */
1329     protected void fillPath() {
1330 
1331         mPSStream.println(mFillOpStr);
1332     }
1333 
1334     /**
1335      * Called to mark the start of a new path.
1336      */
1337     protected void beginPath() {
1338 
1339         prepDrawing();
1340         mPSStream.println(NEWPATH_STR);
1341 
1342         mPenX = 0;
1343         mPenY = 0;
1344     }
1345 
1346     /**
1347      * Close the current subpath by appending a straight
1348      * line from the current point to the subpath's
1349      * starting point.
1350      */
1351     protected void closeSubpath() {
1352 
1353         mPSStream.println(CLOSEPATH_STR);
1354 
1355         mPenX = mStartPathX;
1356         mPenY = mStartPathY;
1357     }
1358 
1359 
1360     /**
1361      * Generate PostScript to move the current pen
1362      * position to <code>(x, y)</code>.
1363      */
1364     protected void moveTo(float x, float y) {
1365 
1366         mPSStream.println(trunc(x) + " " + trunc(y) + MOVETO_STR);
1367 
1368         /* moveto marks the start of a new subpath
1369          * and we need to remember that starting
1370          * position so that we know where the
1371          * pen returns to with a close path.
1372          */
1373         mStartPathX = x;
1374         mStartPathY = y;
1375 
1376         mPenX = x;
1377         mPenY = y;
1378     }
1379     /**
1380      * Generate PostScript to draw a line from the
1381      * current pen position to <code>(x, y)</code>.
1382      */
1383     protected void lineTo(float x, float y) {
1384 
1385         mPSStream.println(trunc(x) + " " + trunc(y) + LINETO_STR);
1386 
1387         mPenX = x;
1388         mPenY = y;
1389     }
1390 
1391     /**
1392      * Add to the current path a bezier curve formed
1393      * by the current pen position and the method parameters
1394      * which are two control points and an ending
1395      * point.
1396      */
1397     protected void bezierTo(float control1x, float control1y,
1398                                 float control2x, float control2y,
1399                                 float endX, float endY) {
1400 
1401 //      mPSStream.println(control1x + " " + control1y
1402 //                        + " " + control2x + " " + control2y
1403 //                        + " " + endX + " " + endY
1404 //                        + CURVETO_STR);
1405         mPSStream.println(trunc(control1x) + " " + trunc(control1y)
1406                           + " " + trunc(control2x) + " " + trunc(control2y)
1407                           + " " + trunc(endX) + " " + trunc(endY)
1408                           + CURVETO_STR);
1409 
1410 
1411         mPenX = endX;
1412         mPenY = endY;
1413     }
1414 
1415     String trunc(float f) {
1416         float af = Math.abs(f);
1417         if (af >= 1f && af <=1000f) {
1418             f = Math.round(f*1000)/1000f;
1419         }
1420         return Float.toString(f);
1421     }
1422 
1423     /**
1424      * Return the x coordinate of the pen in the
1425      * current path.
1426      */
1427     protected float getPenX() {
1428 
1429         return mPenX;
1430     }
1431     /**
1432      * Return the y coordinate of the pen in the
1433      * current path.
1434      */
1435     protected float getPenY() {
1436 
1437         return mPenY;
1438     }
1439 
1440     /**
1441      * Return the x resolution of the coordinates
1442      * to be rendered.
1443      */
1444     protected double getXRes() {
1445         return PS_XRES;
1446     }
1447     /**
1448      * Return the y resolution of the coordinates
1449      * to be rendered.
1450      */
1451     protected double getYRes() {
1452         return PS_YRES;
1453     }
1454 
1455     /**
1456      * For PostScript the origin is in the upper-left of the
1457      * paper not at the imageable area corner.
1458      */
1459     protected double getPhysicalPrintableX(Paper p) {
1460         return 0;
1461 
1462     }
1463 
1464     /**
1465      * For PostScript the origin is in the upper-left of the
1466      * paper not at the imageable area corner.
1467      */
1468     protected double getPhysicalPrintableY(Paper p) {
1469         return 0;
1470     }
1471 
1472     protected double getPhysicalPrintableWidth(Paper p) {
1473         return p.getImageableWidth();
1474     }
1475 
1476     protected double getPhysicalPrintableHeight(Paper p) {
1477         return p.getImageableHeight();
1478     }
1479 
1480     protected double getPhysicalPageWidth(Paper p) {
1481         return p.getWidth();
1482     }
1483 
1484     protected double getPhysicalPageHeight(Paper p) {
1485         return p.getHeight();
1486     }
1487 
1488    /**
1489      * Returns how many times each page in the book
1490      * should be consecutively printed by PrintJob.
1491      * If the printer makes copies itself then this
1492      * method should return 1.
1493      */
1494     protected int getNoncollatedCopies() {
1495         return 1;
1496     }
1497 
1498     protected int getCollatedCopies() {
1499         return 1;
1500     }
1501 
1502     private String[] printExecCmd(String printer, String options,
1503                                   boolean noJobSheet,
1504                                   String banner, int copies, String spoolFile) {
1505         int PRINTER = 0x1;
1506         int OPTIONS = 0x2;
1507         int BANNER  = 0x4;
1508         int COPIES  = 0x8;
1509         int NOSHEET = 0x10;
1510         int pFlags = 0;
1511         String execCmd[];
1512         int ncomps = 2; // minimum number of print args
1513         int n = 0;
1514 
1515         if (printer != null && !printer.equals("") && !printer.equals("lp")) {
1516             pFlags |= PRINTER;
1517             ncomps+=1;
1518         }
1519         if (options != null && !options.equals("")) {
1520             pFlags |= OPTIONS;
1521             ncomps+=1;
1522         }
1523         if (banner != null && !banner.equals("")) {
1524             pFlags |= BANNER;
1525             ncomps+=1;
1526         }
1527         if (copies > 1) {
1528             pFlags |= COPIES;
1529             ncomps+=1;
1530         }
1531         if (noJobSheet) {
1532             pFlags |= NOSHEET;
1533             ncomps+=1;
1534         }
1535        if (System.getProperty("os.name").equals("Linux")) {
1536             execCmd = new String[ncomps];
1537             execCmd[n++] = "/usr/bin/lpr";
1538             if ((pFlags & PRINTER) != 0) {
1539                 execCmd[n++] = "-P" + printer;
1540             }
1541             if ((pFlags & BANNER) != 0) {
1542                 execCmd[n++] = "-J"  + banner;
1543             }
1544             if ((pFlags & COPIES) != 0) {
1545                 execCmd[n++] = "-#" + copies;
1546             }
1547             if ((pFlags & NOSHEET) != 0) {
1548                 execCmd[n++] = "-h";
1549             }
1550             if ((pFlags & OPTIONS) != 0) {
1551                 execCmd[n++] = new String(options);
1552             }
1553         } else {
1554             ncomps+=1; //add 1 arg for lp
1555             execCmd = new String[ncomps];
1556             execCmd[n++] = "/usr/bin/lp";
1557             execCmd[n++] = "-c";           // make a copy of the spool file
1558             if ((pFlags & PRINTER) != 0) {
1559                 execCmd[n++] = "-d" + printer;
1560             }
1561             if ((pFlags & BANNER) != 0) {
1562                 execCmd[n++] = "-t"  + banner;
1563             }
1564             if ((pFlags & COPIES) != 0) {
1565                 execCmd[n++] = "-n" + copies;
1566             }
1567             if ((pFlags & NOSHEET) != 0) {
1568                 execCmd[n++] = "-o nobanner";
1569             }
1570             if ((pFlags & OPTIONS) != 0) {
1571                 execCmd[n++] = "-o" + options;
1572             }
1573         }
1574         execCmd[n++] = spoolFile;
1575         return execCmd;
1576     }
1577 
1578     private static int swapBGRtoRGB(byte[] image, int index, byte[] dest) {
1579         int destIndex = 0;
1580         while(index < image.length-2 && destIndex < dest.length-2) {
1581             dest[destIndex++] = image[index+2];
1582             dest[destIndex++] = image[index+1];
1583             dest[destIndex++] = image[index+0];
1584             index+=3;
1585         }
1586         return index;
1587     }
1588 
1589     /*
1590      * Currently CharToByteConverter.getCharacterEncoding() return values are
1591      * not fixed yet. These are used as the part of the key of
1592      * psfont.propeties. When those name are fixed this routine can
1593      * be erased.
1594      */
1595     private String makeCharsetName(String name, char[] chs) {
1596         if (name.equals("Cp1252") || name.equals("ISO8859_1")) {
1597             return "latin1";
1598         } else if (name.equals("UTF8")) {
1599             // same as latin 1 if all chars < 256
1600             for (int i=0; i < chs.length; i++) {
1601                 if (chs[i] > 255) {
1602                     return name.toLowerCase();
1603                 }
1604             }
1605             return "latin1";
1606         } else if (name.startsWith("ISO8859")) {
1607             // same as latin 1 if all chars < 128
1608             for (int i=0; i < chs.length; i++) {
1609                 if (chs[i] > 127) {
1610                     return name.toLowerCase();
1611                 }
1612             }
1613             return "latin1";
1614         } else {
1615             return name.toLowerCase();
1616         }
1617     }
1618 
1619     private void prepDrawing() {
1620 
1621         /* Pop gstates until we can set the needed clip
1622          * and transform or until we are at the outer most
1623          * gstate.
1624          */
1625         while (isOuterGState() == false
1626                && (getGState().canSetClip(mLastClip) == false
1627                    || getGState().mTransform.equals(mLastTransform) == false)) {
1628 
1629 
1630             grestore();
1631         }
1632 
1633         /* Set the color. This can push the color to the
1634          * outer most gsave which is often a good thing.
1635          */
1636         getGState().emitPSColor(mLastColor);
1637 
1638         /* We do not want to change the outermost
1639          * transform or clip so if we are at the
1640          * outer clip the generate a gsave.
1641          */
1642         if (isOuterGState()) {
1643             gsave();
1644             getGState().emitTransform(mLastTransform);
1645             getGState().emitPSClip(mLastClip);
1646         }
1647 
1648         /* Set the font if we have been asked to. It is
1649          * important that the font is set after the
1650          * transform in order to get the font size
1651          * correct.
1652          */
1653 //      if (g != null) {
1654 //          getGState().emitPSFont(g, mLastFont);
1655 //      }
1656 
1657     }
1658 
1659     /**
1660      * Return the GState that is currently on top
1661      * of the GState stack. There should always be
1662      * a GState on top of the stack. If there isn't
1663      * then this method will throw an IndexOutOfBounds
1664      * exception.
1665      */
1666     private GState getGState() {
1667         int count = mGStateStack.size();
1668         return (GState) mGStateStack.get(count - 1);
1669     }
1670 
1671     /**
1672      * Emit a PostScript gsave command and add a
1673      * new GState on to our stack which represents
1674      * the printer's gstate stack.
1675      */
1676     private void gsave() {
1677         GState oldGState = getGState();
1678         mGStateStack.add(new GState(oldGState));
1679         mPSStream.println(GSAVE_STR);
1680     }
1681 
1682     /**
1683      * Emit a PostScript grestore command and remove
1684      * a GState from our stack which represents the
1685      * printer's gstate stack.
1686      */
1687     private void grestore() {
1688         int count = mGStateStack.size();
1689         mGStateStack.remove(count - 1);
1690         mPSStream.println(GRESTORE_STR);
1691     }
1692 
1693     /**
1694      * Return true if the current GState is the
1695      * outermost GState and therefore should not
1696      * be restored.
1697      */
1698     private boolean isOuterGState() {
1699         return mGStateStack.size() == 1;
1700     }
1701 
1702     /**
1703      * A stack of GStates is maintained to model the printer's
1704      * gstate stack. Each GState holds information about
1705      * the current graphics attributes.
1706      */
1707     private class GState{
1708         Color mColor;
1709         Shape mClip;
1710         Font mFont;
1711         AffineTransform mTransform;
1712 
1713         GState() {
1714             mColor = Color.black;
1715             mClip = null;
1716             mFont = null;
1717             mTransform = new AffineTransform();
1718         }
1719 
1720         GState(GState copyGState) {
1721             mColor = copyGState.mColor;
1722             mClip = copyGState.mClip;
1723             mFont = copyGState.mFont;
1724             mTransform = copyGState.mTransform;
1725         }
1726 
1727         boolean canSetClip(Shape clip) {
1728 
1729             return mClip == null || mClip.equals(clip);
1730         }
1731 
1732 
1733         void emitPSClip(Shape clip) {
1734             if (clip != null
1735                 && (mClip == null || mClip.equals(clip) == false)) {
1736                 String saveFillOp = mFillOpStr;
1737                 String saveClipOp = mClipOpStr;
1738                 convertToPSPath(clip.getPathIterator(new AffineTransform()));
1739                 selectClipPath();
1740                 mClip = clip;
1741                 /* The clip is a shape and has reset the winding rule state */
1742                 mClipOpStr = saveFillOp;
1743                 mFillOpStr = saveFillOp;
1744             }
1745         }
1746 
1747         void emitTransform(AffineTransform transform) {
1748 
1749             if (transform != null && transform.equals(mTransform) == false) {
1750                 double[] matrix = new double[6];
1751                 transform.getMatrix(matrix);
1752                 mPSStream.println("[" + (float)matrix[0]
1753                                   + " " + (float)matrix[1]
1754                                   + " " + (float)matrix[2]
1755                                   + " " + (float)matrix[3]
1756                                   + " " + (float)matrix[4]
1757                                   + " " + (float)matrix[5]
1758                                   + "] concat");
1759 
1760                 mTransform = transform;
1761             }
1762         }
1763 
1764         void emitPSColor(Color color) {
1765             if (color != null && color.equals(mColor) == false) {
1766                 float[] rgb = color.getRGBColorComponents(null);
1767 
1768                 /* If the color is a gray value then use
1769                  * setgray.
1770                  */
1771                 if (rgb[0] == rgb[1] && rgb[1] == rgb[2]) {
1772                     mPSStream.println(rgb[0] + SETGRAY_STR);
1773 
1774                 /* It's not gray so use setrgbcolor.
1775                  */
1776                 } else {
1777                     mPSStream.println(rgb[0] + " "
1778                                       + rgb[1] + " "
1779                                       + rgb[2] + " "
1780                                       + SETRGBCOLOR_STR);
1781                 }
1782 
1783                 mColor = color;
1784 
1785             }
1786         }
1787 
1788         void emitPSFont(int psFontIndex, float fontSize) {
1789             mPSStream.println(fontSize + " " +
1790                               psFontIndex + " " + SetFontName);
1791         }
1792     }
1793 
1794        /**
1795         * Given a Java2D <code>PathIterator</code> instance,
1796         * this method translates that into a PostScript path..
1797         */
1798         void convertToPSPath(PathIterator pathIter) {
1799 
1800             float[] segment = new float[6];
1801             int segmentType;
1802 
1803             /* Map the PathIterator's fill rule into the PostScript
1804              * fill rule.
1805              */
1806             int fillRule;
1807             if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
1808                 fillRule = FILL_EVEN_ODD;
1809             } else {
1810                 fillRule = FILL_WINDING;
1811             }
1812 
1813             beginPath();
1814 
1815             setFillMode(fillRule);
1816 
1817             while (pathIter.isDone() == false) {
1818                 segmentType = pathIter.currentSegment(segment);
1819 
1820                 switch (segmentType) {
1821                  case PathIterator.SEG_MOVETO:
1822                     moveTo(segment[0], segment[1]);
1823                     break;
1824 
1825                  case PathIterator.SEG_LINETO:
1826                     lineTo(segment[0], segment[1]);
1827                     break;
1828 
1829                 /* Convert the quad path to a bezier.
1830                  */
1831                  case PathIterator.SEG_QUADTO:
1832                     float lastX = getPenX();
1833                     float lastY = getPenY();
1834                     float c1x = lastX + (segment[0] - lastX) * 2 / 3;
1835                     float c1y = lastY + (segment[1] - lastY) * 2 / 3;
1836                     float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3;
1837                     float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3;
1838                     bezierTo(c1x, c1y,
1839                              c2x, c2y,
1840                              segment[2], segment[3]);
1841                     break;
1842 
1843                  case PathIterator.SEG_CUBICTO:
1844                     bezierTo(segment[0], segment[1],
1845                              segment[2], segment[3],
1846                              segment[4], segment[5]);
1847                     break;
1848 
1849                  case PathIterator.SEG_CLOSE:
1850                     closeSubpath();
1851                     break;
1852                 }
1853 
1854 
1855                 pathIter.next();
1856             }
1857         }
1858 
1859     /*
1860      * Fill the path defined by <code>pathIter</code>
1861      * with the specified color.
1862      * The path is provided in current user space.
1863      */
1864     protected void deviceFill(PathIterator pathIter, Color color,
1865                               AffineTransform tx, Shape clip) {
1866 
1867         setTransform(tx);
1868         setClip(clip);
1869         setColor(color);
1870         convertToPSPath(pathIter);
1871         /* Specify the path to fill as the clip, this ensures that only
1872          * pixels which are inside the path will be filled, which is
1873          * what the Java 2D APIs specify
1874          */
1875         mPSStream.println(GSAVE_STR);
1876         selectClipPath();
1877         fillPath();
1878         mPSStream.println(GRESTORE_STR + " " + NEWPATH_STR);
1879     }
1880 
1881     /*
1882      * Run length encode byte array in a form suitable for decoding
1883      * by the PS Level 2 filter RunLengthDecode.
1884      * Array data to encode is inArr. Encoded data is written to outArr
1885      * outArr must be long enough to hold the encoded data but this
1886      * can't be known ahead of time.
1887      * A safe assumption is to use double the length of the input array.
1888      * This is then copied into a new array of the correct length which
1889      * is returned.
1890      * Algorithm:
1891      * Encoding is a lead byte followed by data bytes.
1892      * Lead byte of 0->127 indicates leadByte + 1 distinct bytes follow
1893      * Lead byte of 129->255 indicates 257 - leadByte is the number of times
1894      * the following byte is repeated in the source.
1895      * 128 is a special lead byte indicating end of data (EOD) and is
1896      * written as the final byte of the returned encoded data.
1897      */
1898      private byte[] rlEncode(byte[] inArr) {
1899 
1900          int inIndex = 0;
1901          int outIndex = 0;
1902          int startIndex = 0;
1903          int runLen = 0;
1904          byte[] outArr = new byte[(inArr.length * 2) +2];
1905          while (inIndex < inArr.length) {
1906              if (runLen == 0) {
1907                  startIndex = inIndex++;
1908                  runLen=1;
1909              }
1910 
1911              while (runLen < 128 && inIndex < inArr.length &&
1912                     inArr[inIndex] == inArr[startIndex]) {
1913                  runLen++; // count run of same value
1914                  inIndex++;
1915              }
1916 
1917              if (runLen > 1) {
1918                  outArr[outIndex++] = (byte)(257 - runLen);
1919                  outArr[outIndex++] = inArr[startIndex];
1920                  runLen = 0;
1921                  continue; // back to top of while loop.
1922              }
1923 
1924              // if reach here have a run of different values, or at the end.
1925              while (runLen < 128 && inIndex < inArr.length &&
1926                     inArr[inIndex] != inArr[inIndex-1]) {
1927                  runLen++; // count run of different values
1928                  inIndex++;
1929              }
1930              outArr[outIndex++] = (byte)(runLen - 1);
1931              for (int i = startIndex; i < startIndex+runLen; i++) {
1932                  outArr[outIndex++] = inArr[i];
1933              }
1934              runLen = 0;
1935          }
1936          outArr[outIndex++] = (byte)128;
1937          byte[] encodedData = new byte[outIndex];
1938          System.arraycopy(outArr, 0, encodedData, 0, outIndex);
1939 
1940          return encodedData;
1941      }
1942 
1943     /* written acc. to Adobe Spec. "Filtered Files: ASCIIEncode Filter",
1944      * "PS Language Reference Manual, 2nd edition: Section 3.13"
1945      */
1946     private byte[] ascii85Encode(byte[] inArr) {
1947         byte[]  outArr = new byte[((inArr.length+4) * 5 / 4) + 2];
1948         long p1 = 85;
1949         long p2 = p1*p1;
1950         long p3 = p1*p2;
1951         long p4 = p1*p3;
1952         byte pling = '!';
1953 
1954         int i = 0;
1955         int olen = 0;
1956         long val, rem;
1957 
1958         while (i+3 < inArr.length) {
1959             val = ((long)((inArr[i++]&0xff))<<24) +
1960                   ((long)((inArr[i++]&0xff))<<16) +
1961                   ((long)((inArr[i++]&0xff))<< 8) +
1962                   ((long)(inArr[i++]&0xff));
1963             if (val == 0) {
1964                 outArr[olen++] = 'z';
1965             } else {
1966                 rem = val;
1967                 outArr[olen++] = (byte)(rem / p4 + pling); rem = rem % p4;
1968                 outArr[olen++] = (byte)(rem / p3 + pling); rem = rem % p3;
1969                 outArr[olen++] = (byte)(rem / p2 + pling); rem = rem % p2;
1970                 outArr[olen++] = (byte)(rem / p1 + pling); rem = rem % p1;
1971                 outArr[olen++] = (byte)(rem + pling);
1972             }
1973         }
1974         // input not a multiple of 4 bytes, write partial output.
1975         if (i < inArr.length) {
1976             int n = inArr.length - i; // n bytes remain to be written
1977 
1978             val = 0;
1979             while (i < inArr.length) {
1980                 val = (val << 8) + (inArr[i++]&0xff);
1981             }
1982 
1983             int append = 4 - n;
1984             while (append-- > 0) {
1985                 val = val << 8;
1986             }
1987             byte []c = new byte[5];
1988             rem = val;
1989             c[0] = (byte)(rem / p4 + pling); rem = rem % p4;
1990             c[1] = (byte)(rem / p3 + pling); rem = rem % p3;
1991             c[2] = (byte)(rem / p2 + pling); rem = rem % p2;
1992             c[3] = (byte)(rem / p1 + pling); rem = rem % p1;
1993             c[4] = (byte)(rem + pling);
1994 
1995             for (int b = 0; b < n+1 ; b++) {
1996                 outArr[olen++] = c[b];
1997             }
1998         }
1999 
2000         // write EOD marker.
2001         outArr[olen++]='~'; outArr[olen++]='>';
2002 
2003         /* The original intention was to insert a newline after every 78 bytes.
2004          * This was mainly intended for legibility but I decided against this
2005          * partially because of the (small) amount of extra space, and
2006          * partially because for line breaks either would have to hardwire
2007          * ascii 10 (newline) or calculate space in bytes to allocate for
2008          * the platform's newline byte sequence. Also need to be careful
2009          * about where its inserted:
2010          * Ascii 85 decoder ignores white space except for one special case:
2011          * you must ensure you do not split the EOD marker across lines.
2012          */
2013         byte[] retArr = new byte[olen];
2014         System.arraycopy(outArr, 0, retArr, 0, olen);
2015         return retArr;
2016 
2017     }
2018 
2019     /**
2020      * PluginPrinter generates EPSF wrapped with a header and trailer
2021      * comment. This conforms to the new requirements of Mozilla 1.7
2022      * and FireFox 1.5 and later. Earlier versions of these browsers
2023      * did not support plugin printing in the general sense (not just Java).
2024      * A notable limitation of these browsers is that they handle plugins
2025      * which would span page boundaries by scaling plugin content to fit on a
2026      * single page. This means white space is left at the bottom of the
2027      * previous page and its impossible to print these cases as they appear on
2028      * the web page. This is contrast to how the same browsers behave on
2029      * Windows where it renders as on-screen.
2030      * Cases where the content fits on a single page do work fine, and they
2031      * are the majority of cases.
2032      * The scaling that the browser specifies to make the plugin content fit
2033      * when it is larger than a single page can hold is non-uniform. It
2034      * scales the axis in which the content is too large just enough to
2035      * ensure it fits. For content which is extremely long this could lead
2036      * to noticeable distortion. However that is probably rare enough that
2037      * its not worth compensating for that here, but we can revisit that if
2038      * needed, and compensate by making the scale for the other axis the
2039      * same.
2040      */
2041     public static class PluginPrinter implements Printable {
2042 
2043         private EPSPrinter epsPrinter;
2044         private Component applet;
2045         private PrintStream stream;
2046         private String epsTitle;
2047         private int bx, by, bw, bh;
2048         private int width, height;
2049 
2050         /**
2051          * This is called from the Java Plug-in to print an Applet's
2052          * contents as EPS to a postscript stream provided by the browser.
2053          * @param applet the applet component to print.
2054          * @param stream the print stream provided by the plug-in
2055          * @param x the x location of the applet panel in the browser window
2056          * @param y the y location of the applet panel in the browser window
2057          * @param w the width of the applet panel in the browser window
2058          * @param h the width of the applet panel in the browser window
2059          */
2060         public PluginPrinter(Component applet,
2061                              PrintStream stream,
2062                              int x, int y, int w, int h) {
2063 
2064             this.applet = applet;
2065             this.epsTitle = "Java Plugin Applet";
2066             this.stream = stream;
2067             bx = x;
2068             by = y;
2069             bw = w;
2070             bh = h;
2071             width = applet.size().width;
2072             height = applet.size().height;
2073             epsPrinter = new EPSPrinter(this, epsTitle, stream,
2074                                         0, 0, width, height);
2075         }
2076 
2077         public void printPluginPSHeader() {
2078             stream.println("%%BeginDocument: JavaPluginApplet");
2079         }
2080 
2081         public void printPluginApplet() {
2082             try {
2083                 epsPrinter.print();
2084             } catch (PrinterException e) {
2085             }
2086         }
2087 
2088         public void printPluginPSTrailer() {
2089             stream.println("%%EndDocument: JavaPluginApplet");
2090             stream.flush();
2091         }
2092 
2093         public void printAll() {
2094             printPluginPSHeader();
2095             printPluginApplet();
2096             printPluginPSTrailer();
2097         }
2098 
2099         public int print(Graphics g, PageFormat pf, int pgIndex) {
2100             if (pgIndex > 0) {
2101                 return Printable.NO_SUCH_PAGE;
2102             } else {
2103                 // "aware" client code can detect that its been passed a
2104                 // PrinterGraphics and could theoretically print
2105                 // differently. I think this is more likely useful than
2106                 // a problem.
2107                 applet.printAll(g);
2108                 return Printable.PAGE_EXISTS;
2109             }
2110         }
2111 
2112     }
2113 
2114     /*
2115      * This class can take an application-client supplied printable object
2116      * and send the result to a stream.
2117      * The application does not need to send any postscript to this stream
2118      * unless it needs to specify a translation etc.
2119      * It assumes that its importing application obeys all the conventions
2120      * for importation of EPS. See Appendix H - Encapsulated Postscript File
2121      * Format - of the Adobe Postscript Language Reference Manual, 2nd edition.
2122      * This class could be used as the basis for exposing the ability to
2123      * generate EPSF from 2D graphics as a StreamPrintService.
2124      * In that case a MediaPrintableArea attribute could be used to
2125      * communicate the bounding box.
2126      */
2127     public static class EPSPrinter implements Pageable {
2128 
2129         private PageFormat pf;
2130         private PSPrinterJob job;
2131         private int llx, lly, urx, ury;
2132         private Printable printable;
2133         private PrintStream stream;
2134         private String epsTitle;
2135 
2136         public EPSPrinter(Printable printable, String title,
2137                           PrintStream stream,
2138                           int x, int y, int wid, int hgt) {
2139 
2140             this.printable = printable;
2141             this.epsTitle = title;
2142             this.stream = stream;
2143             llx = x;
2144             lly = y;
2145             urx = llx+wid;
2146             ury = lly+hgt;
2147             // construct a PageFormat with zero margins representing the
2148             // exact bounds of the applet. ie construct a theoretical
2149             // paper which happens to exactly match applet panel size.
2150             Paper p = new Paper();
2151             p.setSize((double)wid, (double)hgt);
2152             p.setImageableArea(0.0,0.0, (double)wid, (double)hgt);
2153             pf = new PageFormat();
2154             pf.setPaper(p);
2155         }
2156 
2157         public void print() throws PrinterException {
2158             stream.println("%!PS-Adobe-3.0 EPSF-3.0");
2159             stream.println("%%BoundingBox: " +
2160                            llx + " " + lly + " " + urx + " " + ury);
2161             stream.println("%%Title: " + epsTitle);
2162             stream.println("%%Creator: Java Printing");
2163             stream.println("%%CreationDate: " + new java.util.Date());
2164             stream.println("%%EndComments");
2165             stream.println("/pluginSave save def");
2166             stream.println("mark"); // for restoring stack state on return
2167 
2168             job = new PSPrinterJob();
2169             job.epsPrinter = this; // modifies the behaviour of PSPrinterJob
2170             job.mPSStream = stream;
2171             job.mDestType = RasterPrinterJob.STREAM; // prevents closure
2172 
2173             job.startDoc();
2174             try {
2175                 job.printPage(this, 0);
2176             } catch (Throwable t) {
2177                 if (t instanceof PrinterException) {
2178                     throw (PrinterException)t;
2179                 } else {
2180                     throw new PrinterException(t.toString());
2181                 }
2182             } finally {
2183                 stream.println("cleartomark"); // restore stack state
2184                 stream.println("pluginSave restore");
2185                 job.endDoc();
2186             }
2187             stream.flush();
2188         }
2189 
2190         public int getNumberOfPages() {
2191             return 1;
2192         }
2193 
2194         public PageFormat getPageFormat(int pgIndex) {
2195             if (pgIndex > 0) {
2196                 throw new IndexOutOfBoundsException("pgIndex");
2197             } else {
2198                 return pf;
2199             }
2200         }
2201 
2202         public Printable getPrintable(int pgIndex) {
2203             if (pgIndex > 0) {
2204                 throw new IndexOutOfBoundsException("pgIndex");
2205             } else {
2206             return printable;
2207             }
2208         }
2209 
2210     }
2211 }