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