1 /*
2  * Copyright (c) 2011, 2020, 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.lwawt.macosx;
27 
28 
29 import java.awt.*;
30 import java.awt.geom.Rectangle2D;
31 import java.awt.image.BufferedImage;
32 import java.awt.print.*;
33 import java.net.URI;
34 import java.security.AccessController;
35 import java.security.PrivilegedAction;
36 
37 import javax.print.*;
38 import javax.print.attribute.PrintRequestAttributeSet;
39 import javax.print.attribute.HashPrintRequestAttributeSet;
40 import javax.print.attribute.standard.Copies;
41 import javax.print.attribute.standard.Destination;
42 import javax.print.attribute.standard.Media;
43 import javax.print.attribute.standard.MediaPrintableArea;
44 import javax.print.attribute.standard.MediaSize;
45 import javax.print.attribute.standard.MediaSizeName;
46 import javax.print.attribute.standard.PageRanges;
47 import javax.print.attribute.Attribute;
48 
49 import sun.java2d.*;
50 import sun.print.*;
51 
52 public final class CPrinterJob extends RasterPrinterJob {
53     // NOTE: This uses RasterPrinterJob as a base, but it doesn't use
54     // all of the RasterPrinterJob functions. RasterPrinterJob will
55     // break down printing to pieces that aren't necessary under MacOSX
56     // printing, such as controlling the # of copies and collating. These
57     // are handled by the native printing. RasterPrinterJob is kept for
58     // future compatibility and the state keeping that it handles.
59 
60     private static String sShouldNotReachHere = "Should not reach here.";
61 
62     private volatile SecondaryLoop printingLoop;
63 
64     private boolean noDefaultPrinter = false;
65 
66     private static Font defaultFont;
67 
68     private String tray = null;
69 
70     // This is the NSPrintInfo for this PrinterJob. Protect multi thread
71     //  access to it. It is used by the pageDialog, jobDialog, and printLoop.
72     //  This way the state of these items is shared across these calls.
73     //  PageFormat data is passed in and set on the fNSPrintInfo on a per call
74     //  basis.
75     private long fNSPrintInfo = -1;
76     private Object fNSPrintInfoLock = new Object();
77 
78     static {
79         // AWT has to be initialized for the native code to function correctly.
Toolkit.getDefaultToolkit()80         Toolkit.getDefaultToolkit();
81     }
82 
83     /**
84      * Presents a dialog to the user for changing the properties of
85      * the print job.
86      * This method will display a native dialog if a native print
87      * service is selected, and user choice of printers will be restricted
88      * to these native print services.
89      * To present the cross platform print dialog for all services,
90      * including native ones instead use
91      * {@code printDialog(PrintRequestAttributeSet)}.
92      * <p>
93      * PrinterJob implementations which can use PrintService's will update
94      * the PrintService for this PrinterJob to reflect the new service
95      * selected by the user.
96      * @return {@code true} if the user does not cancel the dialog;
97      * {@code false} otherwise.
98      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
99      * returns true.
100      * @see java.awt.GraphicsEnvironment#isHeadless
101      */
102     @Override
printDialog()103     public boolean printDialog() throws HeadlessException {
104         if (GraphicsEnvironment.isHeadless()) {
105             throw new HeadlessException();
106         }
107 
108         if (noDefaultPrinter) {
109             return false;
110         }
111 
112         if (attributes == null) {
113             attributes = new HashPrintRequestAttributeSet();
114         }
115 
116         if (getPrintService() instanceof StreamPrintService) {
117             return super.printDialog(attributes);
118         }
119 
120         return jobSetup(getPageable(), checkAllowedToPrintToFile());
121     }
122 
123     /**
124      * Displays a dialog that allows modification of a
125      * {@code PageFormat} instance.
126      * The {@code page} argument is used to initialize controls
127      * in the page setup dialog.
128      * If the user cancels the dialog then this method returns the
129      * original {@code page} object unmodified.
130      * If the user okays the dialog then this method returns a new
131      * {@code PageFormat} object with the indicated changes.
132      * In either case, the original {@code page} object is
133      * not modified.
134      * @param page the default {@code PageFormat} presented to the
135      *            user for modification
136      * @return    the original {@code page} object if the dialog
137      *            is cancelled; a new {@code PageFormat} object
138      *          containing the format indicated by the user if the
139      *          dialog is acknowledged.
140      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
141      * returns true.
142      * @see java.awt.GraphicsEnvironment#isHeadless
143      * @since     1.2
144      */
145     @Override
pageDialog(PageFormat page)146     public PageFormat pageDialog(PageFormat page) throws HeadlessException {
147         if (GraphicsEnvironment.isHeadless()) {
148             throw new HeadlessException();
149         }
150 
151         if (noDefaultPrinter) {
152             return page;
153         }
154 
155         if (getPrintService() instanceof StreamPrintService) {
156             return super.pageDialog(page);
157         }
158 
159         PageFormat pageClone = (PageFormat) page.clone();
160         boolean doIt = pageSetup(pageClone, null);
161         return doIt ? pageClone : page;
162     }
163 
164     /**
165      * Clones the {@code PageFormat} argument and alters the
166      * clone to describe a default page size and orientation.
167      * @param page the {@code PageFormat} to be cloned and altered
168      * @return clone of {@code page}, altered to describe a default
169      *                      {@code PageFormat}.
170      */
171     @Override
defaultPage(PageFormat page)172     public PageFormat defaultPage(PageFormat page) {
173         PageFormat newPage = (PageFormat)page.clone();
174         getDefaultPage(newPage);
175         return newPage;
176     }
177 
178     @Override
setAttributes(PrintRequestAttributeSet attributes)179     protected void setAttributes(PrintRequestAttributeSet attributes) throws PrinterException {
180         super.setAttributes(attributes);
181 
182         if (attributes == null) {
183             return;
184         }
185         Attribute attr = attributes.get(Media.class);
186         if (attr instanceof CustomMediaTray) {
187             CustomMediaTray customTray = (CustomMediaTray) attr;
188             tray = customTray.getChoiceName();
189         }
190 
191         PageRanges pageRangesAttr =  (PageRanges)attributes.get(PageRanges.class);
192         if (isSupportedValue(pageRangesAttr, attributes)) {
193             SunPageSelection rangeSelect = (SunPageSelection)attributes.get(SunPageSelection.class);
194             // If rangeSelect is not null, we are using AWT's print dialog that has
195             // All, Selection, and Range radio buttons
196             if (rangeSelect == null || rangeSelect == SunPageSelection.RANGE) {
197                 int[][] range = pageRangesAttr.getMembers();
198                 // setPageRange will set firstPage and lastPage as called in getFirstPage
199                 // and getLastPage
200                 setPageRange(range[0][0] - 1, range[0][1] - 1);
201             } else {
202                 // if rangeSelect is SunPageSelection.ALL
203                 // then setPageRange appropriately
204                 setPageRange(-1, -1);
205             }
206         }
207     }
208 
setPageRangeAttribute(int from, int to, boolean isRangeSet)209     private void setPageRangeAttribute(int from, int to, boolean isRangeSet) {
210         if (attributes != null) {
211             // since native Print use zero-based page indices,
212             // we need to store in 1-based format in attributes set
213             // but setPageRange again uses zero-based indices so it should be
214             // 1 less than pageRanges attribute
215             if (isRangeSet) {
216                 attributes.add(new PageRanges(from+1, to+1));
217                 attributes.add(SunPageSelection.RANGE);
218                 setPageRange(from, to);
219             } else {
220                 attributes.add(SunPageSelection.ALL);
221             }
222         }
223     }
224 
setCopiesAttribute(int copies)225     private void setCopiesAttribute(int copies) {
226         if (attributes != null) {
227             attributes.add(new Copies(copies));
228             super.setCopies(copies);
229         }
230     }
231 
232     volatile boolean onEventThread;
233 
234     @Override
cancelDoc()235     protected void cancelDoc() throws PrinterAbortException {
236         super.cancelDoc();
237         if (printingLoop != null) {
238             printingLoop.exit();
239         }
240     }
241 
completePrintLoop()242     private void completePrintLoop() {
243         Runnable r = new Runnable() { public void run() {
244             synchronized(this) {
245                 performingPrinting = false;
246             }
247             if (printingLoop != null) {
248                 printingLoop.exit();
249             }
250         }};
251 
252         if (onEventThread) {
253             try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
254         } else {
255             r.run();
256         }
257     }
258 
259     boolean isPrintToFile = false;
setPrintToFile(boolean printToFile)260     private void setPrintToFile(boolean printToFile) {
261         isPrintToFile = printToFile;
262     }
263 
setDestinationFile(String dest)264     private void setDestinationFile(String dest) {
265         if (attributes != null && dest != null) {
266             try {
267                URI destURI = new URI(dest);
268                attributes.add(new Destination(destURI));
269                destinationAttr = "" + destURI.getSchemeSpecificPart();
270             } catch (Exception e) {
271             }
272         }
273     }
274 
getDestinationFile()275     private String getDestinationFile() {
276         return destinationAttr;
277     }
278 
279     @Override
print(PrintRequestAttributeSet attributes)280     public void print(PrintRequestAttributeSet attributes) throws PrinterException {
281         // NOTE: Some of this code is copied from RasterPrinterJob.
282 
283         // this code uses javax.print APIs
284         // this will make it print directly to the printer
285         // this will not work if the user clicks on the "Preview" button
286         // However if the printer is a StreamPrintService, its the right path.
287         PrintService psvc = getPrintService();
288 
289         if (psvc == null && !isPrintToFile) {
290             throw new PrinterException("No print service found.");
291         }
292 
293         if (psvc instanceof StreamPrintService) {
294             spoolToService(psvc, attributes);
295             return;
296         }
297 
298 
299         setAttributes(attributes);
300         // throw exception for invalid destination
301         if (destinationAttr != null) {
302             validateDestination(destinationAttr);
303         }
304 
305         /* Get the range of pages we are to print. If the
306          * last page to print is unknown, then we print to
307          * the end of the document. Note that firstPage
308          * and lastPage are 0 based page indices.
309          */
310 
311         int firstPage = getFirstPage();
312         int lastPage = getLastPage();
313         if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) {
314             int totalPages = mDocument.getNumberOfPages();
315             if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) {
316                 lastPage = mDocument.getNumberOfPages() - 1;
317             }
318         }
319 
320         try {
321             synchronized (this) {
322                 performingPrinting = true;
323                 userCancelled = false;
324             }
325 
326             //Add support for PageRange
327             PageRanges pr = (attributes == null) ?  null
328                                                  : (PageRanges)attributes.get(PageRanges.class);
329             int[][] prMembers = (pr == null) ? new int[0][0] : pr.getMembers();
330             int loopi = 0;
331             do {
332                 if (EventQueue.isDispatchThread()) {
333                     // This is an AWT EventQueue, and this print rendering loop needs to block it.
334 
335                     onEventThread = true;
336 
337                     printingLoop = AccessController.doPrivileged(new PrivilegedAction<SecondaryLoop>() {
338                         @Override
339                         public SecondaryLoop run() {
340                             return Toolkit.getDefaultToolkit()
341                                     .getSystemEventQueue()
342                                     .createSecondaryLoop();
343                         }
344                     });
345 
346                     try {
347                         // Fire off the print rendering loop on the AppKit thread, and don't have
348                         //  it wait and block this thread.
349                         if (printLoop(false, firstPage, lastPage)) {
350                             // Start a secondary loop on EDT until printing operation is finished or cancelled
351                             printingLoop.enter();
352                         }
353                     } catch (Exception e) {
354                         e.printStackTrace();
355                     }
356               } else {
357                     // Fire off the print rendering loop on the AppKit, and block this thread
358                     //  until it is done.
359                     // But don't actually block... we need to come back here!
360                     onEventThread = false;
361 
362                     try {
363                         printLoop(true, firstPage, lastPage);
364                     } catch (Exception e) {
365                         e.printStackTrace();
366                     }
367                 }
368                 if (++loopi < prMembers.length) {
369                      firstPage = prMembers[loopi][0]-1;
370                      lastPage = prMembers[loopi][1] -1;
371                 }
372             }  while (loopi < prMembers.length);
373         } finally {
374             synchronized (this) {
375                 // NOTE: Native code shouldn't allow exceptions out while
376                 // printing. They should cancel the print loop.
377                 performingPrinting = false;
378                 notify();
379             }
380             if (printingLoop != null) {
381                 printingLoop.exit();
382             }
383         }
384 
385         // Normalize the collated, # copies, numPages, first/last pages. Need to
386         //  make note of pageRangesAttr.
387 
388         // Set up NSPrintInfo with the java settings (PageFormat & Paper).
389 
390         // Create an NSView for printing. Have knowsPageRange return YES, and give the correct
391         //  range, or MAX? if unknown. Have rectForPage do a peekGraphics check before returning
392         //  the rectangle. Have drawRect do the real render of the page. Have printJobTitle do
393         //  the right thing.
394 
395         // Call NSPrintOperation, it will call NSView.drawRect: for each page.
396 
397         // NSView.drawRect: will create a CPrinterGraphics with the current CGContextRef, and then
398         //  pass this Graphics onto the Printable with the appropriate PageFormat and index.
399 
400         // Need to be able to cancel the NSPrintOperation (using code from RasterPrinterJob, be
401         //  sure to initialize userCancelled and performingPrinting member variables).
402 
403         // Extensions available from AppKit: Print to PDF or EPS file!
404     }
405 
406     /**
407      * Returns the resolution in dots per inch across the width
408      * of the page.
409      */
410     @Override
getXRes()411     protected double getXRes() {
412         // NOTE: This is not used in the CPrinterJob code path.
413         return 0;
414     }
415 
416     /**
417      * Returns the resolution in dots per inch down the height
418      * of the page.
419      */
420     @Override
getYRes()421     protected double getYRes() {
422         // NOTE: This is not used in the CPrinterJob code path.
423         return 0;
424     }
425 
426     /**
427      * Must be obtained from the current printer.
428      * Value is in device pixels.
429      * Not adjusted for orientation of the paper.
430      */
431     @Override
getPhysicalPrintableX(Paper p)432     protected double getPhysicalPrintableX(Paper p) {
433         // NOTE: This is not used in the CPrinterJob code path.
434         return 0;
435     }
436 
437     /**
438      * Must be obtained from the current printer.
439      * Value is in device pixels.
440      * Not adjusted for orientation of the paper.
441      */
442     @Override
getPhysicalPrintableY(Paper p)443     protected double getPhysicalPrintableY(Paper p) {
444         // NOTE: This is not used in the CPrinterJob code path.
445         return 0;
446     }
447 
448     /**
449      * Must be obtained from the current printer.
450      * Value is in device pixels.
451      * Not adjusted for orientation of the paper.
452      */
453     @Override
getPhysicalPrintableWidth(Paper p)454     protected double getPhysicalPrintableWidth(Paper p) {
455         // NOTE: This is not used in the CPrinterJob code path.
456         return 0;
457     }
458 
459     /**
460      * Must be obtained from the current printer.
461      * Value is in device pixels.
462      * Not adjusted for orientation of the paper.
463      */
464     @Override
getPhysicalPrintableHeight(Paper p)465     protected double getPhysicalPrintableHeight(Paper p) {
466         // NOTE: This is not used in the CPrinterJob code path.
467         return 0;
468     }
469 
470     /**
471      * Must be obtained from the current printer.
472      * Value is in device pixels.
473      * Not adjusted for orientation of the paper.
474      */
475     @Override
getPhysicalPageWidth(Paper p)476     protected double getPhysicalPageWidth(Paper p) {
477         // NOTE: This is not used in the CPrinterJob code path.
478         return 0;
479     }
480 
481     /**
482      * Must be obtained from the current printer.
483      * Value is in device pixels.
484      * Not adjusted for orientation of the paper.
485      */
486     @Override
getPhysicalPageHeight(Paper p)487     protected double getPhysicalPageHeight(Paper p) {
488         // NOTE: This is not used in the CPrinterJob code path.
489         return 0;
490     }
491 
492     /**
493      * Begin a new page. This call's Window's
494      * StartPage routine.
495      */
startPage(PageFormat format, Printable painter, int index)496     protected void startPage(PageFormat format, Printable painter, int index) throws PrinterException {
497         // NOTE: This is not used in the CPrinterJob code path.
498         throw new PrinterException(sShouldNotReachHere);
499     }
500 
501     /**
502      * End a page.
503      */
504     @Override
endPage(PageFormat format, Printable painter, int index)505     protected void endPage(PageFormat format, Printable painter, int index) throws PrinterException {
506         // NOTE: This is not used in the CPrinterJob code path.
507         throw new PrinterException(sShouldNotReachHere);
508     }
509 
510     /**
511      * Prints the contents of the array of ints, 'data'
512      * to the current page. The band is placed at the
513      * location (x, y) in device coordinates on the
514      * page. The width and height of the band is
515      * specified by the caller.
516      */
517     @Override
printBand(byte[] data, int x, int y, int width, int height)518     protected void printBand(byte[] data, int x, int y, int width, int height) throws PrinterException {
519         // NOTE: This is not used in the CPrinterJob code path.
520         throw new PrinterException(sShouldNotReachHere);
521     }
522 
523     /**
524      * Called by the print() method at the start of
525      * a print job.
526      */
527     @Override
startDoc()528     protected void startDoc() throws PrinterException {
529         // NOTE: This is not used in the CPrinterJob code path.
530         throw new PrinterException(sShouldNotReachHere);
531     }
532 
533     /**
534      * Called by the print() method at the end of
535      * a print job.
536      */
537     @Override
endDoc()538     protected void endDoc() throws PrinterException {
539         // NOTE: This is not used in the CPrinterJob code path.
540         throw new PrinterException(sShouldNotReachHere);
541     }
542 
543     /* Called by cancelDoc */
544     @Override
abortDoc()545     protected native void abortDoc();
546 
547     /**
548      * Displays the page setup dialog placing the user's
549      * settings into 'page'.
550      */
pageSetup(PageFormat page, Printable painter)551     public boolean pageSetup(PageFormat page, Printable painter) {
552         CPrinterDialog printerDialog = new CPrinterPageDialog(null, this, page, painter);
553         printerDialog.setVisible(true);
554         boolean result = printerDialog.getRetVal();
555         printerDialog.dispose();
556         return result;
557     }
558 
559     /**
560      * Displays the print dialog and records the user's settings
561      * into this object. Return false if the user cancels the
562      * dialog.
563      * If the dialog is to use a set of attributes, useAttributes is true.
564      */
jobSetup(Pageable doc, boolean allowPrintToFile)565     private boolean jobSetup(Pageable doc, boolean allowPrintToFile) {
566         CPrinterDialog printerDialog = new CPrinterJobDialog(null, this, doc, allowPrintToFile);
567         printerDialog.setVisible(true);
568         boolean result = printerDialog.getRetVal();
569         printerDialog.dispose();
570         return result;
571     }
572 
573     /**
574      * Alters the orientation and Paper to match defaults obtained
575      * from a printer.
576      */
getDefaultPage(PageFormat page)577     private native void getDefaultPage(PageFormat page);
578 
579     /**
580      * validate the paper size against the current printer.
581      */
582     @Override
validatePaper(Paper origPaper, Paper newPaper )583     protected native void validatePaper(Paper origPaper, Paper newPaper );
584 
585     // The following methods are CPrinterJob specific.
586 
587     @Override
588     @SuppressWarnings("deprecation")
finalize()589     protected void finalize() {
590         synchronized (fNSPrintInfoLock) {
591             if (fNSPrintInfo != -1) {
592                 dispose(fNSPrintInfo);
593             }
594             fNSPrintInfo = -1;
595         }
596     }
597 
createNSPrintInfo()598     private native long createNSPrintInfo();
dispose(long printInfo)599     private native void dispose(long printInfo);
600 
getNSPrintInfo()601     private long getNSPrintInfo() {
602         // This is called from the native side.
603         synchronized (fNSPrintInfoLock) {
604             if (fNSPrintInfo == -1) {
605                 fNSPrintInfo = createNSPrintInfo();
606             }
607             return fNSPrintInfo;
608         }
609     }
610 
printLoop(boolean waitUntilDone, int firstPage, int lastPage)611     private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException;
612 
getPageFormat(int pageIndex)613     private PageFormat getPageFormat(int pageIndex) {
614         // This is called from the native side.
615         PageFormat page;
616         try {
617             page = getPageable().getPageFormat(pageIndex);
618         } catch (Exception e) {
619             return null;
620         }
621         return page;
622     }
623 
getPrintable(int pageIndex)624     private Printable getPrintable(int pageIndex) {
625         // This is called from the native side.
626         Printable painter;
627         try {
628             painter = getPageable().getPrintable(pageIndex);
629         } catch (Exception e) {
630             return null;
631         }
632         return painter;
633     }
634 
getPrinterName()635     private String getPrinterName(){
636         // This is called from the native side.
637         PrintService service = getPrintService();
638         if (service == null) return null;
639         return service.getName();
640     }
641 
getPrinterTray()642     private String getPrinterTray() {
643         return tray;
644     }
645 
setPrinterServiceFromNative(String printerName)646     private void setPrinterServiceFromNative(String printerName) {
647         // This is called from the native side.
648         PrintService[] services = PrintServiceLookup.lookupPrintServices(DocFlavor.SERVICE_FORMATTED.PAGEABLE, null);
649 
650         for (int i = 0; i < services.length; i++) {
651             PrintService service = services[i];
652 
653             if (printerName.equals(service.getName())) {
654                 try {
655                     setPrintService(service);
656                 } catch (PrinterException e) {
657                     // ignored
658                 }
659                 return;
660             }
661         }
662     }
663 
getPageFormatArea(PageFormat page)664     private Rectangle2D getPageFormatArea(PageFormat page) {
665         Rectangle2D.Double pageFormatArea =
666             new Rectangle2D.Double(page.getImageableX(),
667                     page.getImageableY(),
668                     page.getImageableWidth(),
669                     page.getImageableHeight());
670         return pageFormatArea;
671     }
672 
cancelCheck()673     private boolean cancelCheck() {
674         // This is called from the native side.
675 
676         // This is used to avoid deadlock
677         // We would like to just call if isCancelled(),
678         // but that will block the AppKit thread against whomever is holding the synchronized lock
679         boolean cancelled = (performingPrinting && userCancelled);
680         if (cancelled) {
681             try {
682                 LWCToolkit.invokeLater(new Runnable() { public void run() {
683                     try {
684                     cancelDoc();
685                     } catch (PrinterAbortException pae) {
686                         // no-op, let the native side handle it
687                     }
688                 }}, null);
689             } catch (java.lang.reflect.InvocationTargetException ite) {}
690         }
691         return cancelled;
692     }
693 
createFirstPassGraphics(PrinterJob printerJob, PageFormat page)694     private PeekGraphics createFirstPassGraphics(PrinterJob printerJob, PageFormat page) {
695         // This is called from the native side.
696         BufferedImage bimg = new BufferedImage((int)Math.round(page.getWidth()), (int)Math.round(page.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE);
697         PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob);
698         Rectangle2D pageFormatArea = getPageFormatArea(page);
699         initPrinterGraphics(peekGraphics, pageFormatArea);
700         return peekGraphics;
701     }
702 
printToPathGraphics( final PeekGraphics graphics, final PrinterJob printerJob, final Printable painter, final PageFormat page, final int pageIndex, final long context)703     private void printToPathGraphics(    final PeekGraphics graphics, // Always an actual PeekGraphics
704                                         final PrinterJob printerJob, // Always an actual CPrinterJob
705                                         final Printable painter, // Client class
706                                         final PageFormat page, // Client class
707                                         final int pageIndex,
708                                         final long context) throws PrinterException {
709         // This is called from the native side.
710         Runnable r = new Runnable() { public void run() {
711             try {
712                 SurfaceData sd = CPrinterSurfaceData.createData(page, context); // Just stores page into an ivar
713                 if (defaultFont == null) {
714                     defaultFont = new Font("Dialog", Font.PLAIN, 12);
715                 }
716                 Graphics2D delegate = new SunGraphics2D(sd, Color.black, Color.white, defaultFont);
717 
718                 Graphics2D pathGraphics = new CPrinterGraphics(delegate, printerJob); // Just stores delegate into an ivar
719                 Rectangle2D pageFormatArea = getPageFormatArea(page);
720                 initPrinterGraphics(pathGraphics, pageFormatArea);
721                 painter.print(pathGraphics, page, pageIndex);
722                 delegate.dispose();
723                 delegate = null;
724         } catch (PrinterException pe) { throw new java.lang.reflect.UndeclaredThrowableException(pe); }
725         }};
726 
727         if (onEventThread) {
728             try { EventQueue.invokeAndWait(r);
729             } catch (java.lang.reflect.InvocationTargetException ite) {
730                 Throwable te = ite.getTargetException();
731                 if (te instanceof PrinterException) throw (PrinterException)te;
732                 else te.printStackTrace();
733             } catch (Exception e) { e.printStackTrace(); }
734         } else {
735             r.run();
736         }
737 
738     }
739 
740     // Returns either 1. an array of 3 object (PageFormat, Printable, PeekGraphics) or 2. null
getPageformatPrintablePeekgraphics(final int pageIndex)741     private Object[] getPageformatPrintablePeekgraphics(final int pageIndex) {
742         final Object[] ret = new Object[3];
743         final PrinterJob printerJob = this;
744 
745         Runnable r = new Runnable() { public void run() { synchronized(ret) {
746             try {
747                 Pageable pageable = getPageable();
748                 PageFormat pageFormat = pageable.getPageFormat(pageIndex);
749                 if (pageFormat != null) {
750                     Printable printable = pageable.getPrintable(pageIndex);
751                     if (printable != null) {
752                         BufferedImage bimg =
753                               new BufferedImage(
754                                   (int)Math.round(pageFormat.getWidth()),
755                                   (int)Math.round(pageFormat.getHeight()),
756                                   BufferedImage.TYPE_INT_ARGB_PRE);
757                         PeekGraphics peekGraphics =
758                          createPeekGraphics(bimg.createGraphics(), printerJob);
759                         Rectangle2D pageFormatArea =
760                              getPageFormatArea(pageFormat);
761                         initPrinterGraphics(peekGraphics, pageFormatArea);
762 
763                         // Do the assignment here!
764                         ret[0] = pageFormat;
765                         ret[1] = printable;
766                         ret[2] = peekGraphics;
767                     }
768                 }
769             } catch (Exception e) {} // Original code bailed on any exception
770         }}};
771 
772         if (onEventThread) {
773             try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
774         } else {
775             r.run();
776         }
777 
778         synchronized(ret) {
779             if (ret[2] != null)
780                 return ret;
781             return null;
782         }
783     }
784 
printAndGetPageFormatArea(final Printable printable, final Graphics graphics, final PageFormat pageFormat, final int pageIndex)785     private Rectangle2D printAndGetPageFormatArea(final Printable printable, final Graphics graphics, final PageFormat pageFormat, final int pageIndex) {
786         final Rectangle2D[] ret = new Rectangle2D[1];
787 
788         Runnable r = new Runnable() { public void run() { synchronized(ret) {
789             try {
790                 int pageResult = printable.print(graphics, pageFormat, pageIndex);
791                 if (pageResult != Printable.NO_SUCH_PAGE) {
792                     ret[0] = getPageFormatArea(pageFormat);
793                 }
794             } catch (Exception e) {} // Original code bailed on any exception
795         }}};
796 
797         if (onEventThread) {
798             try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
799         } else {
800             r.run();
801         }
802 
803         synchronized(ret) { return ret[0]; }
804     }
805 
806     // upcall from native
detachPrintLoop(final long target, final long arg)807     private static void detachPrintLoop(final long target, final long arg) {
808         new Thread(null, () -> _safePrintLoop(target, arg),
809                    "PrintLoop", 0, false).start();
810     }
_safePrintLoop(long target, long arg)811     private static native void _safePrintLoop(long target, long arg);
812 
813     @Override
startPage(PageFormat arg0, Printable arg1, int arg2, boolean arg3)814     protected void startPage(PageFormat arg0, Printable arg1, int arg2, boolean arg3) throws PrinterException {
815         // TODO Auto-generated method stub
816     }
817 
818     @Override
getMediaSize(Media media, PrintService service, PageFormat page)819     protected MediaSize getMediaSize(Media media, PrintService service,
820             PageFormat page) {
821         if (media == null || !(media instanceof MediaSizeName)) {
822             return getDefaultMediaSize(page);
823         }
824         MediaSize size = MediaSize.getMediaSizeForName((MediaSizeName) media);
825         return size != null ? size : getDefaultMediaSize(page);
826     }
827 
getDefaultMediaSize(PageFormat page)828     private MediaSize getDefaultMediaSize(PageFormat page){
829             final int inch = 72;
830             Paper paper = page.getPaper();
831             float width = (float) (paper.getWidth() / inch);
832             float height = (float) (paper.getHeight() / inch);
833             return new MediaSize(width, height, MediaSize.INCH);
834     }
835 
836     @Override
getDefaultPrintableArea(PageFormat page, double w, double h)837     protected MediaPrintableArea getDefaultPrintableArea(PageFormat page, double w, double h) {
838         final float dpi = 72.0f;
839         Paper paper = page.getPaper();
840         return new MediaPrintableArea(
841                 (float) (paper.getImageableX() / dpi),
842                 (float) (paper.getImageableY() / dpi),
843                 (float) (paper.getImageableWidth() / dpi),
844                 (float) (paper.getImageableHeight() / dpi),
845                 MediaPrintableArea.INCH);
846     }
847 }
848