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