CPrinterJob.java revision 15999:4a8fee1f2953
1/*
2 * Copyright (c) 2011, 2015, 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    protected void finalize() {
560        synchronized (fNSPrintInfoLock) {
561            if (fNSPrintInfo != -1) {
562                dispose(fNSPrintInfo);
563            }
564            fNSPrintInfo = -1;
565        }
566    }
567
568    private native long createNSPrintInfo();
569    private native void dispose(long printInfo);
570
571    private long getNSPrintInfo() {
572        // This is called from the native side.
573        synchronized (fNSPrintInfoLock) {
574            if (fNSPrintInfo == -1) {
575                fNSPrintInfo = createNSPrintInfo();
576            }
577            return fNSPrintInfo;
578        }
579    }
580
581    private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException;
582
583    private PageFormat getPageFormat(int pageIndex) {
584        // This is called from the native side.
585        PageFormat page;
586        try {
587            page = getPageable().getPageFormat(pageIndex);
588        } catch (Exception e) {
589            return null;
590        }
591        return page;
592    }
593
594    private Printable getPrintable(int pageIndex) {
595        // This is called from the native side.
596        Printable painter;
597        try {
598            painter = getPageable().getPrintable(pageIndex);
599        } catch (Exception e) {
600            return null;
601        }
602        return painter;
603    }
604
605    private String getPrinterName(){
606        // This is called from the native side.
607        PrintService service = getPrintService();
608        if (service == null) return null;
609        return service.getName();
610    }
611
612    private void setPrinterServiceFromNative(String printerName) {
613        // This is called from the native side.
614        PrintService[] services = PrintServiceLookup.lookupPrintServices(DocFlavor.SERVICE_FORMATTED.PAGEABLE, null);
615
616        for (int i = 0; i < services.length; i++) {
617            PrintService service = services[i];
618
619            if (printerName.equals(service.getName())) {
620                try {
621                    setPrintService(service);
622                } catch (PrinterException e) {
623                    // ignored
624                }
625                return;
626            }
627        }
628    }
629
630    private Rectangle2D getPageFormatArea(PageFormat page) {
631        Rectangle2D.Double pageFormatArea =
632            new Rectangle2D.Double(page.getImageableX(),
633                    page.getImageableY(),
634                    page.getImageableWidth(),
635                    page.getImageableHeight());
636        return pageFormatArea;
637    }
638
639    private boolean cancelCheck() {
640        // This is called from the native side.
641
642        // This is used to avoid deadlock
643        // We would like to just call if isCancelled(),
644        // but that will block the AppKit thread against whomever is holding the synchronized lock
645        boolean cancelled = (performingPrinting && userCancelled);
646        if (cancelled) {
647            try {
648                LWCToolkit.invokeLater(new Runnable() { public void run() {
649                    try {
650                    cancelDoc();
651                    } catch (PrinterAbortException pae) {
652                        // no-op, let the native side handle it
653                    }
654                }}, null);
655            } catch (java.lang.reflect.InvocationTargetException ite) {}
656        }
657        return cancelled;
658    }
659
660    private PeekGraphics createFirstPassGraphics(PrinterJob printerJob, PageFormat page) {
661        // This is called from the native side.
662        BufferedImage bimg = new BufferedImage((int)Math.round(page.getWidth()), (int)Math.round(page.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE);
663        PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob);
664        Rectangle2D pageFormatArea = getPageFormatArea(page);
665        initPrinterGraphics(peekGraphics, pageFormatArea);
666        return peekGraphics;
667    }
668
669    private void printToPathGraphics(    final PeekGraphics graphics, // Always an actual PeekGraphics
670                                        final PrinterJob printerJob, // Always an actual CPrinterJob
671                                        final Printable painter, // Client class
672                                        final PageFormat page, // Client class
673                                        final int pageIndex,
674                                        final long context) throws PrinterException {
675        // This is called from the native side.
676        Runnable r = new Runnable() { public void run() {
677            try {
678                SurfaceData sd = CPrinterSurfaceData.createData(page, context); // Just stores page into an ivar
679                if (defaultFont == null) {
680                    defaultFont = new Font("Dialog", Font.PLAIN, 12);
681                }
682                Graphics2D delegate = new SunGraphics2D(sd, Color.black, Color.white, defaultFont);
683
684                Graphics2D pathGraphics = new CPrinterGraphics(delegate, printerJob); // Just stores delegate into an ivar
685                Rectangle2D pageFormatArea = getPageFormatArea(page);
686                initPrinterGraphics(pathGraphics, pageFormatArea);
687                painter.print(pathGraphics, page, pageIndex);
688                delegate.dispose();
689                delegate = null;
690        } catch (PrinterException pe) { throw new java.lang.reflect.UndeclaredThrowableException(pe); }
691        }};
692
693        if (onEventThread) {
694            try { EventQueue.invokeAndWait(r);
695            } catch (java.lang.reflect.InvocationTargetException ite) {
696                Throwable te = ite.getTargetException();
697                if (te instanceof PrinterException) throw (PrinterException)te;
698                else te.printStackTrace();
699            } catch (Exception e) { e.printStackTrace(); }
700        } else {
701            r.run();
702        }
703
704    }
705
706    // Returns either 1. an array of 3 object (PageFormat, Printable, PeekGraphics) or 2. null
707    private Object[] getPageformatPrintablePeekgraphics(final int pageIndex) {
708        final Object[] ret = new Object[3];
709        final PrinterJob printerJob = this;
710
711        Runnable r = new Runnable() { public void run() { synchronized(ret) {
712            try {
713                Pageable pageable = getPageable();
714                PageFormat pageFormat = pageable.getPageFormat(pageIndex);
715                if (pageFormat != null) {
716                    Printable printable = pageable.getPrintable(pageIndex);
717                    if (printable != null) {
718                        BufferedImage bimg =
719                              new BufferedImage(
720                                  (int)Math.round(pageFormat.getWidth()),
721                                  (int)Math.round(pageFormat.getHeight()),
722                                  BufferedImage.TYPE_INT_ARGB_PRE);
723                        PeekGraphics peekGraphics =
724                         createPeekGraphics(bimg.createGraphics(), printerJob);
725                        Rectangle2D pageFormatArea =
726                             getPageFormatArea(pageFormat);
727                        initPrinterGraphics(peekGraphics, pageFormatArea);
728
729                        // Do the assignment here!
730                        ret[0] = pageFormat;
731                        ret[1] = printable;
732                        ret[2] = peekGraphics;
733                    }
734                }
735            } catch (Exception e) {} // Original code bailed on any exception
736        }}};
737
738        if (onEventThread) {
739            try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
740        } else {
741            r.run();
742        }
743
744        synchronized(ret) {
745            if (ret[2] != null)
746                return ret;
747            return null;
748        }
749    }
750
751    private Rectangle2D printAndGetPageFormatArea(final Printable printable, final Graphics graphics, final PageFormat pageFormat, final int pageIndex) {
752        final Rectangle2D[] ret = new Rectangle2D[1];
753
754        Runnable r = new Runnable() { public void run() { synchronized(ret) {
755            try {
756                int pageResult = printable.print(graphics, pageFormat, pageIndex);
757                if (pageResult != Printable.NO_SUCH_PAGE) {
758                    ret[0] = getPageFormatArea(pageFormat);
759                }
760            } catch (Exception e) {} // Original code bailed on any exception
761        }}};
762
763        if (onEventThread) {
764            try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
765        } else {
766            r.run();
767        }
768
769        synchronized(ret) { return ret[0]; }
770    }
771
772    // upcall from native
773    private static void detachPrintLoop(final long target, final long arg) {
774        new Thread(null, () -> _safePrintLoop(target, arg),
775                   "PrintLoop", 0, false).start();
776    }
777    private static native void _safePrintLoop(long target, long arg);
778
779    @Override
780    protected void startPage(PageFormat arg0, Printable arg1, int arg2, boolean arg3) throws PrinterException {
781        // TODO Auto-generated method stub
782    }
783
784    @Override
785    protected MediaSize getMediaSize(Media media, PrintService service,
786            PageFormat page) {
787        if (media == null || !(media instanceof MediaSizeName)) {
788            return getDefaultMediaSize(page);
789        }
790        MediaSize size = MediaSize.getMediaSizeForName((MediaSizeName) media);
791        return size != null ? size : getDefaultMediaSize(page);
792    }
793
794    private MediaSize getDefaultMediaSize(PageFormat page){
795            final int inch = 72;
796            Paper paper = page.getPaper();
797            float width = (float) (paper.getWidth() / inch);
798            float height = (float) (paper.getHeight() / inch);
799            return new MediaSize(width, height, MediaSize.INCH);
800    }
801
802    @Override
803    protected MediaPrintableArea getDefaultPrintableArea(PageFormat page, double w, double h) {
804        final float dpi = 72.0f;
805        Paper paper = page.getPaper();
806        return new MediaPrintableArea(
807                (float) (paper.getImageableX() / dpi),
808                (float) (paper.getImageableY() / dpi),
809                (float) (paper.getImageableWidth() / dpi),
810                (float) (paper.getImageableHeight() / dpi),
811                MediaPrintableArea.INCH);
812    }
813}
814