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