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