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