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