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