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