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