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