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