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