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 }