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