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 }