1 /* 2 * Copyright (c) 1998, 2014, 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.print; 27 28 import java.io.FilePermission; 29 30 import java.awt.Color; 31 import java.awt.Dialog; 32 import java.awt.Frame; 33 import java.awt.Graphics2D; 34 import java.awt.GraphicsConfiguration; 35 import java.awt.GraphicsEnvironment; 36 import java.awt.HeadlessException; 37 import java.awt.KeyboardFocusManager; 38 import java.awt.Rectangle; 39 import java.awt.Shape; 40 import java.awt.geom.AffineTransform; 41 import java.awt.geom.Point2D; 42 import java.awt.geom.Rectangle2D; 43 import java.awt.image.BufferedImage; 44 import java.awt.print.Book; 45 import java.awt.print.Pageable; 46 import java.awt.print.PageFormat; 47 import java.awt.print.Paper; 48 import java.awt.print.Printable; 49 import java.awt.print.PrinterAbortException; 50 import java.awt.print.PrinterException; 51 import java.awt.print.PrinterJob; 52 import java.awt.Window; 53 import java.io.File; 54 import java.io.IOException; 55 import java.util.ArrayList; 56 import java.util.Locale; 57 import sun.awt.image.ByteInterleavedRaster; 58 59 import javax.print.Doc; 60 import javax.print.DocFlavor; 61 import javax.print.DocPrintJob; 62 import javax.print.PrintException; 63 import javax.print.PrintService; 64 import javax.print.PrintServiceLookup; 65 import javax.print.ServiceUI; 66 import javax.print.StreamPrintService; 67 import javax.print.StreamPrintServiceFactory; 68 import javax.print.attribute.Attribute; 69 import javax.print.attribute.AttributeSet; 70 import javax.print.attribute.HashPrintRequestAttributeSet; 71 import javax.print.attribute.PrintRequestAttributeSet; 72 import javax.print.attribute.ResolutionSyntax; 73 import javax.print.attribute.Size2DSyntax; 74 import javax.print.attribute.standard.Copies; 75 import javax.print.attribute.standard.Destination; 76 import javax.print.attribute.standard.DialogTypeSelection; 77 import javax.print.attribute.standard.Fidelity; 78 import javax.print.attribute.standard.JobName; 79 import javax.print.attribute.standard.JobSheets; 80 import javax.print.attribute.standard.Media; 81 import javax.print.attribute.standard.MediaPrintableArea; 82 import javax.print.attribute.standard.MediaSize; 83 import javax.print.attribute.standard.MediaSizeName; 84 import javax.print.attribute.standard.OrientationRequested; 85 import javax.print.attribute.standard.PageRanges; 86 import javax.print.attribute.standard.PrinterResolution; 87 import javax.print.attribute.standard.PrinterState; 88 import javax.print.attribute.standard.PrinterStateReason; 89 import javax.print.attribute.standard.PrinterStateReasons; 90 import javax.print.attribute.standard.PrinterIsAcceptingJobs; 91 import javax.print.attribute.standard.RequestingUserName; 92 import javax.print.attribute.standard.SheetCollate; 93 import javax.print.attribute.standard.Sides; 94 95 import sun.print.PageableDoc; 96 import sun.print.ServiceDialog; 97 import sun.print.SunPrinterJobService; 98 import sun.print.SunPageSelection; 99 100 /** 101 * A class which rasterizes a printer job. 102 * 103 * @author Richard Blanchard 104 */ 105 public abstract class RasterPrinterJob extends PrinterJob { 106 107 /* Class Constants */ 108 109 /* Printer destination type. */ 110 protected static final int PRINTER = 0; 111 112 /* File destination type. */ 113 protected static final int FILE = 1; 114 115 /* Stream destination type. */ 116 protected static final int STREAM = 2; 117 118 /** 119 * Pageable MAX pages 120 */ 121 protected static final int MAX_UNKNOWN_PAGES = 9999; 122 123 protected static final int PD_ALLPAGES = 0x00000000; 124 protected static final int PD_SELECTION = 0x00000001; 125 protected static final int PD_PAGENUMS = 0x00000002; 126 protected static final int PD_NOSELECTION = 0x00000004; 127 128 /** 129 * Maximum amount of memory in bytes to use for the 130 * buffered image "band". 4Mb is a compromise between 131 * limiting the number of bands on hi-res printers and 132 * not using too much of the Java heap or causing paging 133 * on systems with little RAM. 134 */ 135 private static final int MAX_BAND_SIZE = (1024 * 1024 * 4); 136 137 /* Dots Per Inch */ 138 private static final float DPI = 72.0f; 139 140 /** 141 * Useful mainly for debugging, this system property 142 * can be used to force the printing code to print 143 * using a particular pipeline. The two currently 144 * supported values are FORCE_RASTER and FORCE_PDL. 145 */ 146 private static final String FORCE_PIPE_PROP = "sun.java2d.print.pipeline"; 147 148 /** 149 * When the system property FORCE_PIPE_PROP has this value 150 * then each page of a print job will be rendered through 151 * the raster pipeline. 152 */ 153 private static final String FORCE_RASTER = "raster"; 154 155 /** 156 * When the system property FORCE_PIPE_PROP has this value 157 * then each page of a print job will be rendered through 158 * the PDL pipeline. 159 */ 160 private static final String FORCE_PDL = "pdl"; 161 162 /** 163 * When the system property SHAPE_TEXT_PROP has this value 164 * then text is always rendered as a shape, and no attempt is made 165 * to match the font through GDI 166 */ 167 private static final String SHAPE_TEXT_PROP = "sun.java2d.print.shapetext"; 168 169 /** 170 * values obtained from System properties in static initialiser block 171 */ 172 public static boolean forcePDL = false; 173 public static boolean forceRaster = false; 174 public static boolean shapeTextProp = false; 175 176 static { 177 /* The system property FORCE_PIPE_PROP 178 * can be used to force the printing code to 179 * use a particular pipeline. Either the raster 180 * pipeline or the pdl pipeline can be forced. 181 */ 182 String forceStr = java.security.AccessController.doPrivileged( 183 new sun.security.action.GetPropertyAction(FORCE_PIPE_PROP)); 184 185 if (forceStr != null) { 186 if (forceStr.equalsIgnoreCase(FORCE_PDL)) { 187 forcePDL = true; 188 } else if (forceStr.equalsIgnoreCase(FORCE_RASTER)) { 189 forceRaster = true; 190 } 191 } 192 193 String shapeTextStr =java.security.AccessController.doPrivileged( 194 new sun.security.action.GetPropertyAction(SHAPE_TEXT_PROP)); 195 196 if (shapeTextStr != null) { 197 shapeTextProp = true; 198 } 199 } 200 201 /* Instance Variables */ 202 203 /** 204 * Used to minimize GC & reallocation of band when printing 205 */ 206 private int cachedBandWidth = 0; 207 private int cachedBandHeight = 0; 208 private BufferedImage cachedBand = null; 209 210 /** 211 * The number of book copies to be printed. 212 */ 213 private int mNumCopies = 1; 214 215 /** 216 * Collation effects the order of the pages printed 217 * when multiple copies are requested. For two copies 218 * of a three page document the page order is: 219 * mCollate true: 1, 2, 3, 1, 2, 3 220 * mCollate false: 1, 1, 2, 2, 3, 3 221 */ 222 private boolean mCollate = false; 223 224 /** 225 * The zero based indices of the first and last 226 * pages to be printed. If 'mFirstPage' is 227 * UNDEFINED_PAGE_NUM then the first page to 228 * be printed is page 0. If 'mLastPage' is 229 * UNDEFINED_PAGE_NUM then the last page to 230 * be printed is the last one in the book. 231 */ 232 private int mFirstPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; 233 private int mLastPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; 234 235 /** 236 * The previous print stream Paper 237 * Used to check if the paper size has changed such that the 238 * implementation needs to emit the new paper size information 239 * into the print stream. 240 * Since we do our own rotation, and the margins aren't relevant, 241 * Its strictly the dimensions of the paper that we will check. 242 */ 243 private Paper previousPaper; 244 245 /** 246 * The document to be printed. It is initialized to an 247 * empty (zero pages) book. 248 */ 249 // MacOSX - made protected so subclasses can reference it. 250 protected Pageable mDocument = new Book(); 251 252 /** 253 * The name of the job being printed. 254 */ 255 private String mDocName = "Java Printing"; 256 257 258 /** 259 * Printing cancellation flags 260 */ 261 // MacOSX - made protected so subclasses can reference it. 262 protected boolean performingPrinting = false; 263 // MacOSX - made protected so subclasses can reference it. 264 protected boolean userCancelled = false; 265 266 /** 267 * Print to file permission variables. 268 */ 269 private FilePermission printToFilePermission; 270 271 /** 272 * List of areas & the graphics state for redrawing 273 */ 274 private ArrayList<GraphicsState> redrawList = new ArrayList<>(); 275 276 277 /* variables representing values extracted from an attribute set. 278 * These take precedence over values set on a printer job 279 */ 280 private int copiesAttr; 281 private String jobNameAttr; 282 private String userNameAttr; 283 private PageRanges pageRangesAttr; 284 protected PrinterResolution printerResAttr; 285 protected Sides sidesAttr; 286 protected String destinationAttr; 287 protected boolean noJobSheet = false; 288 protected int mDestType = RasterPrinterJob.FILE; 289 protected String mDestination = ""; 290 protected boolean collateAttReq = false; 291 292 /** 293 * Device rotation flag, if it support 270, this is set to true; 294 */ 295 protected boolean landscapeRotates270 = false; 296 297 /** 298 * attributes used by no-args page and print dialog and print method to 299 * communicate state 300 */ 301 protected PrintRequestAttributeSet attributes = null; 302 303 /** 304 * Class to keep state information for redrawing areas 305 * "region" is an area at as a high a resolution as possible. 306 * The redrawing code needs to look at sx, sy to calculate the scale 307 * to device resolution. 308 */ 309 private class GraphicsState { 310 Rectangle2D region; // Area of page to repaint 311 Shape theClip; // image drawing clip. 312 AffineTransform theTransform; // to transform clip to dev coords. 313 double sx; // X scale from region to device resolution 314 double sy; // Y scale from region to device resolution 315 } 316 317 /** 318 * Service for this job 319 */ 320 protected PrintService myService; 321 322 /* Constructors */ 323 324 public RasterPrinterJob() 325 { 326 } 327 328 /* Abstract Methods */ 329 330 /** 331 * Returns the resolution in dots per inch across the width 332 * of the page. 333 */ 334 protected abstract double getXRes(); 335 336 /** 337 * Returns the resolution in dots per inch down the height 338 * of the page. 339 */ 340 protected abstract double getYRes(); 341 342 /** 343 * Must be obtained from the current printer. 344 * Value is in device pixels. 345 * Not adjusted for orientation of the paper. 346 */ 347 protected abstract double getPhysicalPrintableX(Paper p); 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 abstract double getPhysicalPrintableY(Paper p); 355 356 /** 357 * Must be obtained from the current printer. 358 * Value is in device pixels. 359 * Not adjusted for orientation of the paper. 360 */ 361 protected abstract double getPhysicalPrintableWidth(Paper p); 362 363 /** 364 * Must be obtained from the current printer. 365 * Value is in device pixels. 366 * Not adjusted for orientation of the paper. 367 */ 368 protected abstract double getPhysicalPrintableHeight(Paper p); 369 370 /** 371 * Must be obtained from the current printer. 372 * Value is in device pixels. 373 * Not adjusted for orientation of the paper. 374 */ 375 protected abstract double getPhysicalPageWidth(Paper p); 376 377 /** 378 * Must be obtained from the current printer. 379 * Value is in device pixels. 380 * Not adjusted for orientation of the paper. 381 */ 382 protected abstract double getPhysicalPageHeight(Paper p); 383 384 /** 385 * Begin a new page. 386 */ 387 protected abstract void startPage(PageFormat format, Printable painter, 388 int index, boolean paperChanged) 389 throws PrinterException; 390 391 /** 392 * End a page. 393 */ 394 protected abstract void endPage(PageFormat format, Printable painter, 395 int index) 396 throws PrinterException; 397 398 /** 399 * Prints the contents of the array of ints, 'data' 400 * to the current page. The band is placed at the 401 * location (x, y) in device coordinates on the 402 * page. The width and height of the band is 403 * specified by the caller. 404 */ 405 protected abstract void printBand(byte[] data, int x, int y, 406 int width, int height) 407 throws PrinterException; 408 409 /* Instance Methods */ 410 411 /** 412 * save graphics state of a PathGraphics for later redrawing 413 * of part of page represented by the region in that state 414 */ 415 416 public void saveState(AffineTransform at, Shape clip, 417 Rectangle2D region, double sx, double sy) { 418 GraphicsState gstate = new GraphicsState(); 419 gstate.theTransform = at; 420 gstate.theClip = clip; 421 gstate.region = region; 422 gstate.sx = sx; 423 gstate.sy = sy; 424 redrawList.add(gstate); 425 } 426 427 428 /* 429 * A convenience method which returns the default service 430 * for 2D {@code PrinterJob}s. 431 * May return null if there is no suitable default (although there 432 * may still be 2D services available). 433 * @return default 2D print service, or null. 434 * @since 1.4 435 */ 436 protected static PrintService lookupDefaultPrintService() { 437 PrintService service = PrintServiceLookup.lookupDefaultPrintService(); 438 439 /* Pageable implies Printable so checking both isn't strictly needed */ 440 if (service != null && 441 service.isDocFlavorSupported( 442 DocFlavor.SERVICE_FORMATTED.PAGEABLE) && 443 service.isDocFlavorSupported( 444 DocFlavor.SERVICE_FORMATTED.PRINTABLE)) { 445 return service; 446 } else { 447 PrintService []services = 448 PrintServiceLookup.lookupPrintServices( 449 DocFlavor.SERVICE_FORMATTED.PAGEABLE, null); 450 if (services.length > 0) { 451 return services[0]; 452 } 453 } 454 return null; 455 } 456 457 /** 458 * Returns the service (printer) for this printer job. 459 * Implementations of this class which do not support print services 460 * may return null; 461 * @return the service for this printer job. 462 * 463 */ 464 public PrintService getPrintService() { 465 if (myService == null) { 466 PrintService svc = PrintServiceLookup.lookupDefaultPrintService(); 467 if (svc != null && 468 svc.isDocFlavorSupported( 469 DocFlavor.SERVICE_FORMATTED.PAGEABLE)) { 470 try { 471 setPrintService(svc); 472 myService = svc; 473 } catch (PrinterException e) { 474 } 475 } 476 if (myService == null) { 477 PrintService[] svcs = PrintServiceLookup.lookupPrintServices( 478 DocFlavor.SERVICE_FORMATTED.PAGEABLE, null); 479 if (svcs.length > 0) { 480 try { 481 setPrintService(svcs[0]); 482 myService = svcs[0]; 483 } catch (PrinterException e) { 484 } 485 } 486 } 487 } 488 return myService; 489 } 490 491 /** 492 * Associate this PrinterJob with a new PrintService. 493 * 494 * Throws {@code PrinterException} if the specified service 495 * cannot support the {@code Pageable} and 496 * {@code Printable} interfaces necessary to support 2D printing. 497 * @param service print service which supports 2D printing. 498 * 499 * @throws PrinterException if the specified service does not support 500 * 2D printing or no longer available. 501 */ 502 public void setPrintService(PrintService service) 503 throws PrinterException { 504 if (service == null) { 505 throw new PrinterException("Service cannot be null"); 506 } else if (!(service instanceof StreamPrintService) && 507 service.getName() == null) { 508 throw new PrinterException("Null PrintService name."); 509 } else { 510 // Check the list of services. This service may have been 511 // deleted already 512 PrinterState prnState = service.getAttribute(PrinterState.class); 513 if (prnState == PrinterState.STOPPED) { 514 PrinterStateReasons prnStateReasons = 515 service.getAttribute(PrinterStateReasons.class); 516 if ((prnStateReasons != null) && 517 (prnStateReasons.containsKey(PrinterStateReason.SHUTDOWN))) 518 { 519 throw new PrinterException("PrintService is no longer available."); 520 } 521 } 522 523 524 if (service.isDocFlavorSupported( 525 DocFlavor.SERVICE_FORMATTED.PAGEABLE) && 526 service.isDocFlavorSupported( 527 DocFlavor.SERVICE_FORMATTED.PRINTABLE)) { 528 myService = service; 529 } else { 530 throw new PrinterException("Not a 2D print service: " + service); 531 } 532 } 533 } 534 535 private PageFormat attributeToPageFormat(PrintService service, 536 PrintRequestAttributeSet attSet) { 537 PageFormat page = defaultPage(); 538 539 if (service == null) { 540 return page; 541 } 542 543 OrientationRequested orient = (OrientationRequested) 544 attSet.get(OrientationRequested.class); 545 if (orient == null) { 546 orient = (OrientationRequested) 547 service.getDefaultAttributeValue(OrientationRequested.class); 548 } 549 if (orient == OrientationRequested.REVERSE_LANDSCAPE) { 550 page.setOrientation(PageFormat.REVERSE_LANDSCAPE); 551 } else if (orient == OrientationRequested.LANDSCAPE) { 552 page.setOrientation(PageFormat.LANDSCAPE); 553 } else { 554 page.setOrientation(PageFormat.PORTRAIT); 555 } 556 557 Media media = (Media)attSet.get(Media.class); 558 MediaSize size = getMediaSize(media, service, page); 559 560 Paper paper = new Paper(); 561 float dim[] = size.getSize(1); //units == 1 to avoid FP error 562 double w = Math.rint((dim[0]*72.0)/Size2DSyntax.INCH); 563 double h = Math.rint((dim[1]*72.0)/Size2DSyntax.INCH); 564 paper.setSize(w, h); 565 MediaPrintableArea area = 566 (MediaPrintableArea) 567 attSet.get(MediaPrintableArea.class); 568 if (area == null) { 569 area = getDefaultPrintableArea(page, w, h); 570 } 571 572 double ix, iw, iy, ih; 573 // Should pass in same unit as updatePageAttributes 574 // to avoid rounding off errors. 575 ix = Math.rint( 576 area.getX(MediaPrintableArea.INCH) * DPI); 577 iy = Math.rint( 578 area.getY(MediaPrintableArea.INCH) * DPI); 579 iw = Math.rint( 580 area.getWidth(MediaPrintableArea.INCH) * DPI); 581 ih = Math.rint( 582 area.getHeight(MediaPrintableArea.INCH) * DPI); 583 paper.setImageableArea(ix, iy, iw, ih); 584 page.setPaper(paper); 585 return page; 586 } 587 protected MediaSize getMediaSize(Media media, PrintService service, 588 PageFormat page) { 589 if (media == null) { 590 media = (Media)service.getDefaultAttributeValue(Media.class); 591 } 592 if (!(media instanceof MediaSizeName)) { 593 media = MediaSizeName.NA_LETTER; 594 } 595 MediaSize size = MediaSize.getMediaSizeForName((MediaSizeName) media); 596 return size != null ? size : MediaSize.NA.LETTER; 597 } 598 599 protected MediaPrintableArea getDefaultPrintableArea(PageFormat page, 600 double w, double h) { 601 double ix, iw, iy, ih; 602 if (w >= 72.0 * 6.0) { 603 ix = 72.0; 604 iw = w - 2 * 72.0; 605 } else { 606 ix = w / 6.0; 607 iw = w * 0.75; 608 } 609 if (h >= 72.0 * 6.0) { 610 iy = 72.0; 611 ih = h - 2 * 72.0; 612 } else { 613 iy = h / 6.0; 614 ih = h * 0.75; 615 } 616 617 return new MediaPrintableArea((float) (ix / DPI), (float) (iy / DPI), 618 (float) (iw / DPI), (float) (ih / DPI), MediaPrintableArea.INCH); 619 } 620 621 protected void updatePageAttributes(PrintService service, 622 PageFormat page) { 623 if (this.attributes == null) { 624 this.attributes = new HashPrintRequestAttributeSet(); 625 } 626 627 updateAttributesWithPageFormat(service, page, this.attributes); 628 } 629 630 protected void updateAttributesWithPageFormat(PrintService service, 631 PageFormat page, 632 PrintRequestAttributeSet pageAttributes) { 633 if (service == null || page == null || pageAttributes == null) { 634 return; 635 } 636 637 float x = (float)Math.rint( 638 (page.getPaper().getWidth()*Size2DSyntax.INCH)/ 639 (72.0))/(float)Size2DSyntax.INCH; 640 float y = (float)Math.rint( 641 (page.getPaper().getHeight()*Size2DSyntax.INCH)/ 642 (72.0))/(float)Size2DSyntax.INCH; 643 644 // We should limit the list where we search the matching 645 // media, this will prevent mapping to wrong media ex. Ledger 646 // can be mapped to B. Especially useful when creating 647 // custom MediaSize. 648 Media[] mediaList = (Media[])service.getSupportedAttributeValues( 649 Media.class, null, null); 650 Media media = null; 651 try { 652 media = CustomMediaSizeName.findMedia(mediaList, x, y, 653 Size2DSyntax.INCH); 654 } catch (IllegalArgumentException iae) { 655 } 656 if ((media == null) || 657 !(service.isAttributeValueSupported(media, null, null))) { 658 media = (Media)service.getDefaultAttributeValue(Media.class); 659 } 660 661 OrientationRequested orient; 662 switch (page.getOrientation()) { 663 case PageFormat.LANDSCAPE : 664 orient = OrientationRequested.LANDSCAPE; 665 break; 666 case PageFormat.REVERSE_LANDSCAPE: 667 orient = OrientationRequested.REVERSE_LANDSCAPE; 668 break; 669 default: 670 orient = OrientationRequested.PORTRAIT; 671 } 672 673 if (media != null) { 674 pageAttributes.add(media); 675 } 676 pageAttributes.add(orient); 677 678 float ix = (float)(page.getPaper().getImageableX()/DPI); 679 float iw = (float)(page.getPaper().getImageableWidth()/DPI); 680 float iy = (float)(page.getPaper().getImageableY()/DPI); 681 float ih = (float)(page.getPaper().getImageableHeight()/DPI); 682 683 if (ix < 0) ix = 0; if (iy < 0) iy = 0; 684 if (iw <= 0) iw = (float)(page.getPaper().getWidth()/DPI) - (ix*2); 685 686 // If iw is still negative, it means ix is too large to print 687 // anything inside printable area if we have to leave the same margin 688 // in the right side of paper so we go back to default mpa values 689 if (iw < 0) iw = 0; 690 691 if (ih <= 0) ih = (float)(page.getPaper().getHeight()/DPI) - (iy*2); 692 693 // If ih is still negative, it means iy is too large to print 694 // anything inside printable area if we have to leave the same margin 695 // in the bottom side of paper so we go back to default mpa values 696 if (ih < 0) ih = 0; 697 try { 698 pageAttributes.add(new MediaPrintableArea(ix, iy, iw, ih, 699 MediaPrintableArea.INCH)); 700 } catch (IllegalArgumentException iae) { 701 } 702 } 703 704 /** 705 * Display a dialog to the user allowing the modification of a 706 * PageFormat instance. 707 * The {@code page} argument is used to initialize controls 708 * in the page setup dialog. 709 * If the user cancels the dialog, then the method returns the 710 * original {@code page} object unmodified. 711 * If the user okays the dialog then the method returns a new 712 * PageFormat object with the indicated changes. 713 * In either case the original {@code page} object will 714 * not be modified. 715 * @param page the default PageFormat presented to the user 716 * for modification 717 * @return the original {@code page} object if the dialog 718 * is cancelled, or a new PageFormat object containing 719 * the format indicated by the user if the dialog is 720 * acknowledged 721 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 722 * returns true. 723 * @see java.awt.GraphicsEnvironment#isHeadless 724 * @since 1.2 725 */ 726 public PageFormat pageDialog(PageFormat page) 727 throws HeadlessException { 728 if (GraphicsEnvironment.isHeadless()) { 729 throw new HeadlessException(); 730 } 731 732 final GraphicsConfiguration gc = 733 GraphicsEnvironment.getLocalGraphicsEnvironment(). 734 getDefaultScreenDevice().getDefaultConfiguration(); 735 736 PrintService service = java.security.AccessController.doPrivileged( 737 new java.security.PrivilegedAction<PrintService>() { 738 public PrintService run() { 739 PrintService service = getPrintService(); 740 if (service == null) { 741 ServiceDialog.showNoPrintService(gc); 742 return null; 743 } 744 return service; 745 } 746 }); 747 748 if (service == null) { 749 return page; 750 } 751 updatePageAttributes(service, page); 752 753 PageFormat newPage = null; 754 DialogTypeSelection dts = 755 (DialogTypeSelection)attributes.get(DialogTypeSelection.class); 756 if (dts == DialogTypeSelection.NATIVE) { 757 // Remove DialogTypeSelection.NATIVE to prevent infinite loop in 758 // RasterPrinterJob. 759 attributes.remove(DialogTypeSelection.class); 760 newPage = pageDialog(attributes); 761 // restore attribute 762 attributes.add(DialogTypeSelection.NATIVE); 763 } else { 764 newPage = pageDialog(attributes); 765 } 766 767 if (newPage == null) { 768 return page; 769 } else { 770 return newPage; 771 } 772 } 773 774 /** 775 * return a PageFormat corresponding to the updated attributes, 776 * or null if the user cancelled the dialog. 777 */ 778 @SuppressWarnings("deprecation") 779 public PageFormat pageDialog(final PrintRequestAttributeSet attributes) 780 throws HeadlessException { 781 if (GraphicsEnvironment.isHeadless()) { 782 throw new HeadlessException(); 783 } 784 785 DialogTypeSelection dlg = 786 (DialogTypeSelection)attributes.get(DialogTypeSelection.class); 787 788 // Check for native, note that default dialog is COMMON. 789 if (dlg == DialogTypeSelection.NATIVE) { 790 PrintService pservice = getPrintService(); 791 PageFormat pageFrmAttrib = attributeToPageFormat(pservice, 792 attributes); 793 PageFormat page = pageDialog(pageFrmAttrib); 794 795 // If user cancels the dialog, pageDialog() will return the original 796 // page object and as per spec, we should return null in that case. 797 if (page == pageFrmAttrib) { 798 return null; 799 } 800 updateAttributesWithPageFormat(pservice, page, attributes); 801 return page; 802 } 803 804 GraphicsConfiguration grCfg = null; 805 Window w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); 806 if (w != null) { 807 grCfg = w.getGraphicsConfiguration(); 808 } else { 809 grCfg = GraphicsEnvironment.getLocalGraphicsEnvironment(). 810 getDefaultScreenDevice().getDefaultConfiguration(); 811 } 812 final GraphicsConfiguration gc = grCfg; 813 814 PrintService service = java.security.AccessController.doPrivileged( 815 new java.security.PrivilegedAction<PrintService>() { 816 public PrintService run() { 817 PrintService service = getPrintService(); 818 if (service == null) { 819 ServiceDialog.showNoPrintService(gc); 820 return null; 821 } 822 return service; 823 } 824 }); 825 826 if (service == null) { 827 return null; 828 } 829 830 // we position the dialog a little beyond the upper-left corner of the window 831 // which is consistent with the NATIVE page dialog 832 Rectangle gcBounds = gc.getBounds(); 833 int x = gcBounds.x+50; 834 int y = gcBounds.y+50; 835 ServiceDialog pageDialog; 836 if (w instanceof Frame) { 837 pageDialog = new ServiceDialog(gc, x, y, service, 838 DocFlavor.SERVICE_FORMATTED.PAGEABLE, 839 attributes, (Frame)w); 840 } else { 841 pageDialog = new ServiceDialog(gc, x, y, service, 842 DocFlavor.SERVICE_FORMATTED.PAGEABLE, 843 attributes, (Dialog)w); 844 } 845 Rectangle dlgBounds = pageDialog.getBounds(); 846 847 // if portion of dialog is not within the gc boundary 848 if (!gcBounds.contains(dlgBounds)) { 849 // check if dialog exceed window bounds at left or bottom 850 // Then position the dialog by moving it by the amount it exceeds 851 // the window bounds 852 // If it results in dialog moving beyond the window bounds at top/left 853 // then position it at window top/left 854 if (dlgBounds.x + dlgBounds.width > gcBounds.x + gcBounds.width) { 855 if ((gcBounds.x + gcBounds.width - dlgBounds.width) > gcBounds.x) { 856 x = (gcBounds.x + gcBounds.width) - dlgBounds.width; 857 } else { 858 x = gcBounds.x; 859 } 860 } 861 if (dlgBounds.y + dlgBounds.height > gcBounds.y + gcBounds.height) { 862 if ((gcBounds.y + gcBounds.height - dlgBounds.height) > gcBounds.y) { 863 y = (gcBounds.y + gcBounds.height) - dlgBounds.height; 864 } else { 865 y = gcBounds.y; 866 } 867 } 868 pageDialog.setBounds(x, y, dlgBounds.width, dlgBounds.height); 869 } 870 pageDialog.show(); 871 872 if (pageDialog.getStatus() == ServiceDialog.APPROVE) { 873 PrintRequestAttributeSet newas = 874 pageDialog.getAttributes(); 875 Class<?> amCategory = SunAlternateMedia.class; 876 877 if (attributes.containsKey(amCategory) && 878 !newas.containsKey(amCategory)) { 879 attributes.remove(amCategory); 880 } 881 attributes.addAll(newas); 882 return attributeToPageFormat(service, attributes); 883 } else { 884 return null; 885 } 886 } 887 888 protected PageFormat getPageFormatFromAttributes() { 889 if (attributes == null || attributes.isEmpty()) { 890 return null; 891 } 892 return attributeToPageFormat(getPrintService(), this.attributes); 893 } 894 895 896 /** 897 * Presents the user a dialog for changing properties of the 898 * print job interactively. 899 * The services browsable here are determined by the type of 900 * service currently installed. 901 * If the application installed a StreamPrintService on this 902 * PrinterJob, only the available StreamPrintService (factories) are 903 * browsable. 904 * 905 * @param attributes to store changed properties. 906 * @return false if the user cancels the dialog and true otherwise. 907 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 908 * returns true. 909 * @see java.awt.GraphicsEnvironment#isHeadless 910 */ 911 public boolean printDialog(final PrintRequestAttributeSet attributes) 912 throws HeadlessException { 913 if (GraphicsEnvironment.isHeadless()) { 914 throw new HeadlessException(); 915 } 916 917 DialogTypeSelection dlg = 918 (DialogTypeSelection)attributes.get(DialogTypeSelection.class); 919 920 // Check for native, note that default dialog is COMMON. 921 if (dlg == DialogTypeSelection.NATIVE) { 922 this.attributes = attributes; 923 try { 924 debug_println("calling setAttributes in printDialog"); 925 setAttributes(attributes); 926 927 } catch (PrinterException e) { 928 929 } 930 931 boolean ret = printDialog(); 932 this.attributes = attributes; 933 return ret; 934 935 } 936 937 /* A security check has already been performed in the 938 * java.awt.print.printerJob.getPrinterJob method. 939 * So by the time we get here, it is OK for the current thread 940 * to print either to a file (from a Dialog we control!) or 941 * to a chosen printer. 942 * 943 * We raise privilege when we put up the dialog, to avoid 944 * the "warning applet window" banner. 945 */ 946 GraphicsConfiguration grCfg = null; 947 Window w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); 948 if (w != null) { 949 grCfg = w.getGraphicsConfiguration(); 950 /* Add DialogOwner attribute to set the owner of this print dialog 951 * only if it is not set already 952 * (it might be set in java.awt.PrintJob.printDialog) 953 */ 954 if (attributes.get(DialogOwner.class) == null) { 955 if (w instanceof Frame) { 956 attributes.add(new DialogOwner((Frame)w)); 957 } else if (w instanceof Dialog) { 958 attributes.add(new DialogOwner((Dialog)w)); 959 } 960 } 961 } else { 962 grCfg = GraphicsEnvironment.getLocalGraphicsEnvironment(). 963 getDefaultScreenDevice().getDefaultConfiguration(); 964 } 965 final GraphicsConfiguration gc = grCfg; 966 967 PrintService service = java.security.AccessController.doPrivileged( 968 new java.security.PrivilegedAction<PrintService>() { 969 public PrintService run() { 970 PrintService service = getPrintService(); 971 if (service == null) { 972 ServiceDialog.showNoPrintService(gc); 973 return null; 974 } 975 return service; 976 } 977 }); 978 979 if (service == null) { 980 return false; 981 } 982 983 PrintService[] services; 984 StreamPrintServiceFactory[] spsFactories = null; 985 if (service instanceof StreamPrintService) { 986 spsFactories = lookupStreamPrintServices(null); 987 services = new StreamPrintService[spsFactories.length]; 988 for (int i=0; i<spsFactories.length; i++) { 989 services[i] = spsFactories[i].getPrintService(null); 990 } 991 } else { 992 services = java.security.AccessController.doPrivileged( 993 new java.security.PrivilegedAction<PrintService[]>() { 994 public PrintService[] run() { 995 PrintService[] services = PrinterJob.lookupPrintServices(); 996 return services; 997 } 998 }); 999 1000 if ((services == null) || (services.length == 0)) { 1001 /* 1002 * No services but default PrintService exists? 1003 * Create services using defaultService. 1004 */ 1005 services = new PrintService[1]; 1006 services[0] = service; 1007 } 1008 } 1009 1010 // we position the dialog a little beyond the upper-left corner of the window 1011 // which is consistent with the NATIVE print dialog 1012 int x = 50; 1013 int y = 50; 1014 PrintService newService; 1015 // temporarily add an attribute pointing back to this job. 1016 PrinterJobWrapper jobWrapper = new PrinterJobWrapper(this); 1017 attributes.add(jobWrapper); 1018 try { 1019 newService = 1020 ServiceUI.printDialog(gc, x, y, 1021 services, service, 1022 DocFlavor.SERVICE_FORMATTED.PAGEABLE, 1023 attributes); 1024 } catch (IllegalArgumentException iae) { 1025 newService = ServiceUI.printDialog(gc, x, y, 1026 services, services[0], 1027 DocFlavor.SERVICE_FORMATTED.PAGEABLE, 1028 attributes); 1029 } 1030 attributes.remove(PrinterJobWrapper.class); 1031 1032 if (newService == null) { 1033 return false; 1034 } 1035 1036 if (!service.equals(newService)) { 1037 try { 1038 setPrintService(newService); 1039 } catch (PrinterException e) { 1040 /* 1041 * The only time it would throw an exception is when 1042 * newService is no longer available but we should still 1043 * select this printer. 1044 */ 1045 myService = newService; 1046 } 1047 } 1048 return true; 1049 } 1050 1051 /** 1052 * Presents the user a dialog for changing properties of the 1053 * print job interactively. 1054 * @return false if the user cancels the dialog and 1055 * true otherwise. 1056 * @exception HeadlessException if GraphicsEnvironment.isHeadless() 1057 * returns true. 1058 * @see java.awt.GraphicsEnvironment#isHeadless 1059 */ 1060 public boolean printDialog() throws HeadlessException { 1061 1062 if (GraphicsEnvironment.isHeadless()) { 1063 throw new HeadlessException(); 1064 } 1065 1066 PrintRequestAttributeSet attributes = 1067 new HashPrintRequestAttributeSet(); 1068 attributes.add(new Copies(getCopies())); 1069 attributes.add(new JobName(getJobName(), null)); 1070 boolean doPrint = printDialog(attributes); 1071 if (doPrint) { 1072 JobName jobName = (JobName)attributes.get(JobName.class); 1073 if (jobName != null) { 1074 setJobName(jobName.getValue()); 1075 } 1076 Copies copies = (Copies)attributes.get(Copies.class); 1077 if (copies != null) { 1078 setCopies(copies.getValue()); 1079 } 1080 1081 Destination dest = (Destination)attributes.get(Destination.class); 1082 1083 if (dest != null) { 1084 try { 1085 mDestType = RasterPrinterJob.FILE; 1086 mDestination = (new File(dest.getURI())).getPath(); 1087 } catch (Exception e) { 1088 mDestination = "out.prn"; 1089 PrintService ps = getPrintService(); 1090 if (ps != null) { 1091 Destination defaultDest = (Destination)ps. 1092 getDefaultAttributeValue(Destination.class); 1093 if (defaultDest != null) { 1094 mDestination = (new File(defaultDest.getURI())).getPath(); 1095 } 1096 } 1097 } 1098 } else { 1099 mDestType = RasterPrinterJob.PRINTER; 1100 PrintService ps = getPrintService(); 1101 if (ps != null) { 1102 mDestination = ps.getName(); 1103 } 1104 } 1105 } 1106 1107 return doPrint; 1108 } 1109 1110 /** 1111 * The pages in the document to be printed by this PrinterJob 1112 * are drawn by the Printable object 'painter'. The PageFormat 1113 * for each page is the default page format. 1114 * @param painter Called to render each page of the document. 1115 */ 1116 public void setPrintable(Printable painter) { 1117 setPageable(new OpenBook(defaultPage(new PageFormat()), painter)); 1118 } 1119 1120 /** 1121 * The pages in the document to be printed by this PrinterJob 1122 * are drawn by the Printable object 'painter'. The PageFormat 1123 * of each page is 'format'. 1124 * @param painter Called to render each page of the document. 1125 * @param format The size and orientation of each page to 1126 * be printed. 1127 */ 1128 public void setPrintable(Printable painter, PageFormat format) { 1129 setPageable(new OpenBook(format, painter)); 1130 updatePageAttributes(getPrintService(), format); 1131 } 1132 1133 /** 1134 * The pages in the document to be printed are held by the 1135 * Pageable instance 'document'. 'document' will be queried 1136 * for the number of pages as well as the PageFormat and 1137 * Printable for each page. 1138 * @param document The document to be printed. It may not be null. 1139 * @exception NullPointerException the Pageable passed in was null. 1140 * @see PageFormat 1141 * @see Printable 1142 */ 1143 public void setPageable(Pageable document) throws NullPointerException { 1144 if (document != null) { 1145 mDocument = document; 1146 1147 } else { 1148 throw new NullPointerException(); 1149 } 1150 } 1151 1152 protected void initPrinter() { 1153 return; 1154 } 1155 1156 protected boolean isSupportedValue(Attribute attrval, 1157 PrintRequestAttributeSet attrset) { 1158 PrintService ps = getPrintService(); 1159 return 1160 (attrval != null && ps != null && 1161 ps.isAttributeValueSupported(attrval, 1162 DocFlavor.SERVICE_FORMATTED.PAGEABLE, 1163 attrset)); 1164 } 1165 1166 /** 1167 * Set the device resolution. 1168 * Overridden and used only by the postscript code. 1169 * Windows code pulls the information from the attribute set itself. 1170 */ 1171 protected void setXYRes(double x, double y) { 1172 } 1173 1174 /* subclasses may need to pull extra information out of the attribute set 1175 * They can override this method & call super.setAttributes() 1176 */ 1177 protected void setAttributes(PrintRequestAttributeSet attributes) 1178 throws PrinterException { 1179 /* reset all values to defaults */ 1180 setCollated(false); 1181 sidesAttr = null; 1182 printerResAttr = null; 1183 pageRangesAttr = null; 1184 copiesAttr = 0; 1185 jobNameAttr = null; 1186 userNameAttr = null; 1187 destinationAttr = null; 1188 collateAttReq = false; 1189 1190 PrintService service = getPrintService(); 1191 if (attributes == null || service == null) { 1192 return; 1193 } 1194 1195 boolean fidelity = false; 1196 Fidelity attrFidelity = (Fidelity)attributes.get(Fidelity.class); 1197 if (attrFidelity != null && attrFidelity == Fidelity.FIDELITY_TRUE) { 1198 fidelity = true; 1199 } 1200 1201 if (fidelity == true) { 1202 AttributeSet unsupported = 1203 service.getUnsupportedAttributes( 1204 DocFlavor.SERVICE_FORMATTED.PAGEABLE, 1205 attributes); 1206 if (unsupported != null) { 1207 throw new PrinterException("Fidelity cannot be satisfied"); 1208 } 1209 } 1210 1211 /* 1212 * Since we have verified supported values if fidelity is true, 1213 * we can either ignore unsupported values, or substitute a 1214 * reasonable alternative 1215 */ 1216 1217 SheetCollate collateAttr = 1218 (SheetCollate)attributes.get(SheetCollate.class); 1219 if (isSupportedValue(collateAttr, attributes)) { 1220 setCollated(collateAttr == SheetCollate.COLLATED); 1221 } 1222 1223 sidesAttr = (Sides)attributes.get(Sides.class); 1224 if (!isSupportedValue(sidesAttr, attributes)) { 1225 sidesAttr = Sides.ONE_SIDED; 1226 } 1227 1228 printerResAttr = (PrinterResolution)attributes.get(PrinterResolution.class); 1229 if (service.isAttributeCategorySupported(PrinterResolution.class)) { 1230 if (!isSupportedValue(printerResAttr, attributes)) { 1231 printerResAttr = (PrinterResolution) 1232 service.getDefaultAttributeValue(PrinterResolution.class); 1233 } 1234 double xr = 1235 printerResAttr.getCrossFeedResolution(ResolutionSyntax.DPI); 1236 double yr = printerResAttr.getFeedResolution(ResolutionSyntax.DPI); 1237 setXYRes(xr, yr); 1238 } 1239 1240 pageRangesAttr = (PageRanges)attributes.get(PageRanges.class); 1241 if (!isSupportedValue(pageRangesAttr, attributes)) { 1242 pageRangesAttr = null; 1243 setPageRange(-1, -1); 1244 } else { 1245 if ((SunPageSelection)attributes.get(SunPageSelection.class) 1246 == SunPageSelection.RANGE) { 1247 // get to, from, min, max page ranges 1248 int[][] range = pageRangesAttr.getMembers(); 1249 // setPageRanges uses 0-based indexing so we subtract 1 1250 setPageRange(range[0][0] - 1, range[0][1] - 1); 1251 } else { 1252 setPageRange(-1, - 1); 1253 } 1254 } 1255 1256 Copies copies = (Copies)attributes.get(Copies.class); 1257 if (isSupportedValue(copies, attributes) || 1258 (!fidelity && copies != null)) { 1259 copiesAttr = copies.getValue(); 1260 setCopies(copiesAttr); 1261 } else { 1262 copiesAttr = getCopies(); 1263 } 1264 1265 Destination destination = 1266 (Destination)attributes.get(Destination.class); 1267 1268 if (isSupportedValue(destination, attributes)) { 1269 try { 1270 // Old code (new File(destination.getURI())).getPath() 1271 // would generate a "URI is not hierarchical" IAE 1272 // for "file:out.prn" so we use getSchemeSpecificPart instead 1273 destinationAttr = "" + new File(destination.getURI(). 1274 getSchemeSpecificPart()); 1275 } catch (Exception e) { // paranoid exception 1276 Destination defaultDest = (Destination)service. 1277 getDefaultAttributeValue(Destination.class); 1278 if (defaultDest != null) { 1279 destinationAttr = "" + new File(defaultDest.getURI(). 1280 getSchemeSpecificPart()); 1281 } 1282 } 1283 } 1284 1285 JobSheets jobSheets = (JobSheets)attributes.get(JobSheets.class); 1286 if (jobSheets != null) { 1287 noJobSheet = jobSheets == JobSheets.NONE; 1288 } else { 1289 JobSheets js = (JobSheets)getPrintService(). 1290 getDefaultAttributeValue(JobSheets.class); 1291 if (js != null && js.equals(JobSheets.NONE)) { 1292 noJobSheet = true; 1293 } 1294 } 1295 1296 JobName jobName = (JobName)attributes.get(JobName.class); 1297 if (isSupportedValue(jobName, attributes) || 1298 (!fidelity && jobName != null)) { 1299 jobNameAttr = jobName.getValue(); 1300 setJobName(jobNameAttr); 1301 } else { 1302 jobNameAttr = getJobName(); 1303 } 1304 1305 RequestingUserName userName = 1306 (RequestingUserName)attributes.get(RequestingUserName.class); 1307 if (isSupportedValue(userName, attributes) || 1308 (!fidelity && userName != null)) { 1309 userNameAttr = userName.getValue(); 1310 } else { 1311 try { 1312 userNameAttr = getUserName(); 1313 } catch (SecurityException e) { 1314 userNameAttr = ""; 1315 } 1316 } 1317 1318 /* OpenBook is used internally only when app uses Printable. 1319 * This is the case when we use the values from the attribute set. 1320 */ 1321 Media media = (Media)attributes.get(Media.class); 1322 OrientationRequested orientReq = 1323 (OrientationRequested)attributes.get(OrientationRequested.class); 1324 MediaPrintableArea mpa = 1325 (MediaPrintableArea)attributes.get(MediaPrintableArea.class); 1326 1327 if ((orientReq != null || media != null || mpa != null) && 1328 getPageable() instanceof OpenBook) { 1329 1330 /* We could almost(!) use PrinterJob.getPageFormat() except 1331 * here we need to start with the PageFormat from the OpenBook : 1332 */ 1333 Pageable pageable = getPageable(); 1334 Printable printable = pageable.getPrintable(0); 1335 PageFormat pf = (PageFormat)pageable.getPageFormat(0).clone(); 1336 Paper paper = pf.getPaper(); 1337 1338 /* If there's a media but no media printable area, we can try 1339 * to retrieve the default value for mpa and use that. 1340 */ 1341 if (mpa == null && media != null && 1342 service. 1343 isAttributeCategorySupported(MediaPrintableArea.class)) { 1344 Object mpaVals = service. 1345 getSupportedAttributeValues(MediaPrintableArea.class, 1346 null, attributes); 1347 if (mpaVals instanceof MediaPrintableArea[] && 1348 ((MediaPrintableArea[])mpaVals).length > 0) { 1349 mpa = ((MediaPrintableArea[])mpaVals)[0]; 1350 } 1351 } 1352 1353 if (isSupportedValue(orientReq, attributes) || 1354 (!fidelity && orientReq != null)) { 1355 int orient; 1356 if (orientReq.equals(OrientationRequested.REVERSE_LANDSCAPE)) { 1357 orient = PageFormat.REVERSE_LANDSCAPE; 1358 } else if (orientReq.equals(OrientationRequested.LANDSCAPE)) { 1359 orient = PageFormat.LANDSCAPE; 1360 } else { 1361 orient = PageFormat.PORTRAIT; 1362 } 1363 pf.setOrientation(orient); 1364 } 1365 1366 if (isSupportedValue(media, attributes) || 1367 (!fidelity && media != null)) { 1368 if (media instanceof MediaSizeName) { 1369 MediaSizeName msn = (MediaSizeName)media; 1370 MediaSize msz = MediaSize.getMediaSizeForName(msn); 1371 if (msz != null) { 1372 float paperWid = msz.getX(MediaSize.INCH) * 72.0f; 1373 float paperHgt = msz.getY(MediaSize.INCH) * 72.0f; 1374 paper.setSize(paperWid, paperHgt); 1375 if (mpa == null) { 1376 paper.setImageableArea(72.0, 72.0, 1377 paperWid-144.0, 1378 paperHgt-144.0); 1379 } 1380 } 1381 } 1382 } 1383 1384 if (isSupportedValue(mpa, attributes) || 1385 (!fidelity && mpa != null)) { 1386 float [] printableArea = 1387 mpa.getPrintableArea(MediaPrintableArea.INCH); 1388 for (int i=0; i < printableArea.length; i++) { 1389 printableArea[i] = printableArea[i]*72.0f; 1390 } 1391 paper.setImageableArea(printableArea[0], printableArea[1], 1392 printableArea[2], printableArea[3]); 1393 } 1394 1395 pf.setPaper(paper); 1396 pf = validatePage(pf); 1397 setPrintable(printable, pf); 1398 } else { 1399 // for AWT where pageable is not an instance of OpenBook, 1400 // we need to save paper info 1401 this.attributes = attributes; 1402 } 1403 1404 } 1405 1406 /* 1407 * Services we don't recognize as built-in services can't be 1408 * implemented as subclasses of PrinterJob, therefore we create 1409 * a DocPrintJob from their service and pass a Doc representing 1410 * the application's printjob 1411 */ 1412 // MacOSX - made protected so subclasses can reference it. 1413 protected void spoolToService(PrintService psvc, 1414 PrintRequestAttributeSet attributes) 1415 throws PrinterException { 1416 1417 if (psvc == null) { 1418 throw new PrinterException("No print service found."); 1419 } 1420 1421 DocPrintJob job = psvc.createPrintJob(); 1422 Doc doc = new PageableDoc(getPageable()); 1423 if (attributes == null) { 1424 attributes = new HashPrintRequestAttributeSet(); 1425 attributes.add(new Copies(getCopies())); 1426 attributes.add(new JobName(getJobName(), null)); 1427 } 1428 try { 1429 job.print(doc, attributes); 1430 } catch (PrintException e) { 1431 throw new PrinterException(e.toString()); 1432 } 1433 } 1434 1435 /** 1436 * Prints a set of pages. 1437 * @exception java.awt.print.PrinterException an error in the print system 1438 * caused the job to be aborted 1439 * @see java.awt.print.Book 1440 * @see java.awt.print.Pageable 1441 * @see java.awt.print.Printable 1442 */ 1443 public void print() throws PrinterException { 1444 print(attributes); 1445 } 1446 1447 public static boolean debugPrint = false; 1448 protected void debug_println(String str) { 1449 if (debugPrint) { 1450 System.out.println("RasterPrinterJob "+str+" "+this); 1451 } 1452 } 1453 1454 public void print(PrintRequestAttributeSet attributes) 1455 throws PrinterException { 1456 1457 /* 1458 * In the future PrinterJob will probably always dispatch 1459 * the print job to the PrintService. 1460 * This is how third party 2D Print Services will be invoked 1461 * when applications use the PrinterJob API. 1462 * However the JRE's concrete PrinterJob implementations have 1463 * not yet been re-worked to be implemented as standalone 1464 * services, and are implemented only as subclasses of PrinterJob. 1465 * So here we dispatch only those services we do not recognize 1466 * as implemented through platform subclasses of PrinterJob 1467 * (and this class). 1468 */ 1469 PrintService psvc = getPrintService(); 1470 debug_println("psvc = "+psvc); 1471 if (psvc == null) { 1472 throw new PrinterException("No print service found."); 1473 } 1474 1475 // Check the list of services. This service may have been 1476 // deleted already 1477 PrinterState prnState = psvc.getAttribute(PrinterState.class); 1478 if (prnState == PrinterState.STOPPED) { 1479 PrinterStateReasons prnStateReasons = 1480 psvc.getAttribute(PrinterStateReasons.class); 1481 if ((prnStateReasons != null) && 1482 (prnStateReasons.containsKey(PrinterStateReason.SHUTDOWN))) 1483 { 1484 throw new PrinterException("PrintService is no longer available."); 1485 } 1486 } 1487 1488 if ((psvc.getAttribute(PrinterIsAcceptingJobs.class)) == 1489 PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) { 1490 throw new PrinterException("Printer is not accepting job."); 1491 } 1492 1493 if ((psvc instanceof SunPrinterJobService) && 1494 ((SunPrinterJobService)psvc).usesClass(getClass())) { 1495 setAttributes(attributes); 1496 // throw exception for invalid destination 1497 if (destinationAttr != null) { 1498 validateDestination(destinationAttr); 1499 } 1500 } else { 1501 spoolToService(psvc, attributes); 1502 return; 1503 } 1504 /* We need to make sure that the collation and copies 1505 * settings are initialised */ 1506 initPrinter(); 1507 1508 int numCollatedCopies = getCollatedCopies(); 1509 int numNonCollatedCopies = getNoncollatedCopies(); 1510 debug_println("getCollatedCopies() "+numCollatedCopies 1511 + " getNoncollatedCopies() "+ numNonCollatedCopies); 1512 1513 /* Get the range of pages we are to print. If the 1514 * last page to print is unknown, then we print to 1515 * the end of the document. Note that firstPage 1516 * and lastPage are 0 based page indices. 1517 */ 1518 int numPages = mDocument.getNumberOfPages(); 1519 if (numPages == 0) { 1520 return; 1521 } 1522 1523 int firstPage = getFirstPage(); 1524 int lastPage = getLastPage(); 1525 if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES){ 1526 int totalPages = mDocument.getNumberOfPages(); 1527 if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) { 1528 lastPage = mDocument.getNumberOfPages() - 1; 1529 } 1530 } 1531 1532 try { 1533 synchronized (this) { 1534 performingPrinting = true; 1535 userCancelled = false; 1536 } 1537 1538 startDoc(); 1539 if (isCancelled()) { 1540 cancelDoc(); 1541 } 1542 1543 // PageRanges can be set even if RANGE is not selected 1544 // so we need to check if it is selected. 1545 boolean rangeIsSelected = true; 1546 if (attributes != null) { 1547 SunPageSelection pages = 1548 (SunPageSelection)attributes.get(SunPageSelection.class); 1549 if ((pages != null) && (pages != SunPageSelection.RANGE)) { 1550 rangeIsSelected = false; 1551 } 1552 } 1553 1554 1555 debug_println("after startDoc rangeSelected? "+rangeIsSelected 1556 + " numNonCollatedCopies "+ numNonCollatedCopies); 1557 1558 1559 /* Three nested loops iterate over the document. The outer loop 1560 * counts the number of collated copies while the inner loop 1561 * counts the number of nonCollated copies. Normally, one of 1562 * these two loops will only execute once; that is we will 1563 * either print collated copies or noncollated copies. The 1564 * middle loop iterates over the pages. 1565 * If a PageRanges attribute is used, it constrains the pages 1566 * that are imaged. If a platform subclass (though a user dialog) 1567 * requests a page range via setPageRange(). it too can 1568 * constrain the page ranges that are imaged. 1569 * It is expected that only one of these will be used in a 1570 * job but both should be able to co-exist. 1571 */ 1572 for(int collated = 0; collated < numCollatedCopies; collated++) { 1573 for(int i = firstPage, pageResult = Printable.PAGE_EXISTS; 1574 (i <= lastPage || 1575 lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) 1576 && pageResult == Printable.PAGE_EXISTS; 1577 i++) 1578 { 1579 1580 if ((pageRangesAttr != null) && rangeIsSelected ){ 1581 int nexti = pageRangesAttr.next(i); 1582 if (nexti == -1) { 1583 break; 1584 } else if (nexti != i+1) { 1585 continue; 1586 } 1587 } 1588 1589 for(int nonCollated = 0; 1590 nonCollated < numNonCollatedCopies 1591 && pageResult == Printable.PAGE_EXISTS; 1592 nonCollated++) 1593 { 1594 if (isCancelled()) { 1595 cancelDoc(); 1596 } 1597 debug_println("printPage "+i); 1598 pageResult = printPage(mDocument, i); 1599 1600 } 1601 } 1602 } 1603 1604 if (isCancelled()) { 1605 cancelDoc(); 1606 } 1607 1608 } finally { 1609 // reset previousPaper in case this job is invoked again. 1610 previousPaper = null; 1611 synchronized (this) { 1612 if (performingPrinting) { 1613 endDoc(); 1614 } 1615 performingPrinting = false; 1616 notify(); 1617 } 1618 } 1619 } 1620 1621 protected void validateDestination(String dest) throws PrinterException { 1622 if (dest == null) { 1623 return; 1624 } 1625 // dest is null for Destination(new URI("")) 1626 // because isAttributeValueSupported returns false in setAttributes 1627 1628 // Destination(new URI(" ")) throws URISyntaxException 1629 File f = new File(dest); 1630 try { 1631 // check if this is a new file and if filename chars are valid 1632 if (f.createNewFile()) { 1633 f.delete(); 1634 } 1635 } catch (IOException ioe) { 1636 throw new PrinterException("Cannot write to file:"+ 1637 dest); 1638 } catch (SecurityException se) { 1639 //There is already file read/write access so at this point 1640 // only delete access is denied. Just ignore it because in 1641 // most cases the file created in createNewFile gets overwritten 1642 // anyway. 1643 } 1644 1645 File pFile = f.getParentFile(); 1646 if ((f.exists() && 1647 (!f.isFile() || !f.canWrite())) || 1648 ((pFile != null) && 1649 (!pFile.exists() || (pFile.exists() && !pFile.canWrite())))) { 1650 if (f.exists()) { 1651 f.delete(); 1652 } 1653 throw new PrinterException("Cannot write to file:"+ 1654 dest); 1655 } 1656 } 1657 1658 /** 1659 * updates a Paper object to reflect the current printer's selected 1660 * paper size and imageable area for that paper size. 1661 * Default implementation copies settings from the original, applies 1662 * applies some validity checks, changes them only if they are 1663 * clearly unreasonable, then sets them into the new Paper. 1664 * Subclasses are expected to override this method to make more 1665 * informed decisons. 1666 */ 1667 protected void validatePaper(Paper origPaper, Paper newPaper) { 1668 if (origPaper == null || newPaper == null) { 1669 return; 1670 } else { 1671 double wid = origPaper.getWidth(); 1672 double hgt = origPaper.getHeight(); 1673 double ix = origPaper.getImageableX(); 1674 double iy = origPaper.getImageableY(); 1675 double iw = origPaper.getImageableWidth(); 1676 double ih = origPaper.getImageableHeight(); 1677 1678 /* Assume any +ve values are legal. Overall paper dimensions 1679 * take precedence. Make sure imageable area fits on the paper. 1680 */ 1681 Paper defaultPaper = new Paper(); 1682 wid = ((wid > 0.0) ? wid : defaultPaper.getWidth()); 1683 hgt = ((hgt > 0.0) ? hgt : defaultPaper.getHeight()); 1684 ix = ((ix > 0.0) ? ix : defaultPaper.getImageableX()); 1685 iy = ((iy > 0.0) ? iy : defaultPaper.getImageableY()); 1686 iw = ((iw > 0.0) ? iw : defaultPaper.getImageableWidth()); 1687 ih = ((ih > 0.0) ? ih : defaultPaper.getImageableHeight()); 1688 /* full width/height is not likely to be imageable, but since we 1689 * don't know the limits we have to allow it 1690 */ 1691 if (iw > wid) { 1692 iw = wid; 1693 } 1694 if (ih > hgt) { 1695 ih = hgt; 1696 } 1697 if ((ix + iw) > wid) { 1698 ix = wid - iw; 1699 } 1700 if ((iy + ih) > hgt) { 1701 iy = hgt - ih; 1702 } 1703 newPaper.setSize(wid, hgt); 1704 newPaper.setImageableArea(ix, iy, iw, ih); 1705 } 1706 } 1707 1708 /** 1709 * The passed in PageFormat will be copied and altered to describe 1710 * the default page size and orientation of the PrinterJob's 1711 * current printer. 1712 * Platform subclasses which can access the actual default paper size 1713 * for a printer may override this method. 1714 */ 1715 public PageFormat defaultPage(PageFormat page) { 1716 PageFormat newPage = (PageFormat)page.clone(); 1717 newPage.setOrientation(PageFormat.PORTRAIT); 1718 Paper newPaper = new Paper(); 1719 double ptsPerInch = 72.0; 1720 double w, h; 1721 Media media = null; 1722 1723 PrintService service = getPrintService(); 1724 if (service != null) { 1725 MediaSize size; 1726 media = 1727 (Media)service.getDefaultAttributeValue(Media.class); 1728 1729 if (media instanceof MediaSizeName && 1730 ((size = MediaSize.getMediaSizeForName((MediaSizeName)media)) != 1731 null)) { 1732 w = size.getX(MediaSize.INCH) * ptsPerInch; 1733 h = size.getY(MediaSize.INCH) * ptsPerInch; 1734 newPaper.setSize(w, h); 1735 newPaper.setImageableArea(ptsPerInch, ptsPerInch, 1736 w - 2.0*ptsPerInch, 1737 h - 2.0*ptsPerInch); 1738 newPage.setPaper(newPaper); 1739 return newPage; 1740 1741 } 1742 } 1743 1744 /* Default to A4 paper outside North America. 1745 */ 1746 String defaultCountry = Locale.getDefault().getCountry(); 1747 if (!Locale.getDefault().equals(Locale.ENGLISH) && // ie "C" 1748 defaultCountry != null && 1749 !defaultCountry.equals(Locale.US.getCountry()) && 1750 !defaultCountry.equals(Locale.CANADA.getCountry())) { 1751 1752 double mmPerInch = 25.4; 1753 w = Math.rint((210.0*ptsPerInch)/mmPerInch); 1754 h = Math.rint((297.0*ptsPerInch)/mmPerInch); 1755 newPaper.setSize(w, h); 1756 newPaper.setImageableArea(ptsPerInch, ptsPerInch, 1757 w - 2.0*ptsPerInch, 1758 h - 2.0*ptsPerInch); 1759 } 1760 1761 newPage.setPaper(newPaper); 1762 1763 return newPage; 1764 } 1765 1766 /** 1767 * The passed in PageFormat is cloned and altered to be usable on 1768 * the PrinterJob's current printer. 1769 */ 1770 public PageFormat validatePage(PageFormat page) { 1771 PageFormat newPage = (PageFormat)page.clone(); 1772 Paper newPaper = new Paper(); 1773 validatePaper(newPage.getPaper(), newPaper); 1774 newPage.setPaper(newPaper); 1775 1776 return newPage; 1777 } 1778 1779 /** 1780 * Set the number of copies to be printed. 1781 */ 1782 public void setCopies(int copies) { 1783 mNumCopies = copies; 1784 } 1785 1786 /** 1787 * Get the number of copies to be printed. 1788 */ 1789 public int getCopies() { 1790 return mNumCopies; 1791 } 1792 1793 /* Used when executing a print job where an attribute set may 1794 * over ride API values. 1795 */ 1796 protected int getCopiesInt() { 1797 return (copiesAttr > 0) ? copiesAttr : getCopies(); 1798 } 1799 1800 /** 1801 * Get the name of the printing user. 1802 * The caller must have security permission to read system properties. 1803 */ 1804 public String getUserName() { 1805 return System.getProperty("user.name"); 1806 } 1807 1808 /* Used when executing a print job where an attribute set may 1809 * over ride API values. 1810 */ 1811 protected String getUserNameInt() { 1812 if (userNameAttr != null) { 1813 return userNameAttr; 1814 } else { 1815 try { 1816 return getUserName(); 1817 } catch (SecurityException e) { 1818 return ""; 1819 } 1820 } 1821 } 1822 1823 /** 1824 * Set the name of the document to be printed. 1825 * The document name can not be null. 1826 */ 1827 public void setJobName(String jobName) { 1828 if (jobName != null) { 1829 mDocName = jobName; 1830 } else { 1831 throw new NullPointerException(); 1832 } 1833 } 1834 1835 /** 1836 * Get the name of the document to be printed. 1837 */ 1838 public String getJobName() { 1839 return mDocName; 1840 } 1841 1842 /* Used when executing a print job where an attribute set may 1843 * over ride API values. 1844 */ 1845 protected String getJobNameInt() { 1846 return (jobNameAttr != null) ? jobNameAttr : getJobName(); 1847 } 1848 1849 /** 1850 * Set the range of pages from a Book to be printed. 1851 * Both 'firstPage' and 'lastPage' are zero based 1852 * page indices. If either parameter is less than 1853 * zero then the page range is set to be from the 1854 * first page to the last. 1855 */ 1856 protected void setPageRange(int firstPage, int lastPage) { 1857 if(firstPage >= 0 && lastPage >= 0) { 1858 mFirstPage = firstPage; 1859 mLastPage = lastPage; 1860 if(mLastPage < mFirstPage) mLastPage = mFirstPage; 1861 } else { 1862 mFirstPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; 1863 mLastPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; 1864 } 1865 } 1866 1867 /** 1868 * Return the zero based index of the first page to 1869 * be printed in this job. 1870 */ 1871 protected int getFirstPage() { 1872 return mFirstPage == Book.UNKNOWN_NUMBER_OF_PAGES ? 0 : mFirstPage; 1873 } 1874 1875 /** 1876 * Return the zero based index of the last page to 1877 * be printed in this job. 1878 */ 1879 protected int getLastPage() { 1880 return mLastPage; 1881 } 1882 1883 /** 1884 * Set whether copies should be collated or not. 1885 * Two collated copies of a three page document 1886 * print in this order: 1, 2, 3, 1, 2, 3 while 1887 * uncollated copies print in this order: 1888 * 1, 1, 2, 2, 3, 3. 1889 * This is set when request is using an attribute set. 1890 */ 1891 protected void setCollated(boolean collate) { 1892 mCollate = collate; 1893 collateAttReq = true; 1894 } 1895 1896 /** 1897 * Return true if collated copies will be printed as determined 1898 * in an attribute set. 1899 */ 1900 protected boolean isCollated() { 1901 return mCollate; 1902 } 1903 1904 protected final int getSelectAttrib() { 1905 if (attributes != null) { 1906 SunPageSelection pages = 1907 (SunPageSelection)attributes.get(SunPageSelection.class); 1908 if (pages == SunPageSelection.RANGE) { 1909 return PD_PAGENUMS; 1910 } else if (pages == SunPageSelection.SELECTION) { 1911 return PD_SELECTION; 1912 } else if (pages == SunPageSelection.ALL) { 1913 return PD_ALLPAGES; 1914 } 1915 } 1916 return PD_NOSELECTION; 1917 } 1918 1919 //returns 1-based index for "From" page 1920 protected final int getFromPageAttrib() { 1921 if (attributes != null) { 1922 PageRanges pageRangesAttr = 1923 (PageRanges)attributes.get(PageRanges.class); 1924 if (pageRangesAttr != null) { 1925 int[][] range = pageRangesAttr.getMembers(); 1926 return range[0][0]; 1927 } 1928 } 1929 return getMinPageAttrib(); 1930 } 1931 1932 //returns 1-based index for "To" page 1933 protected final int getToPageAttrib() { 1934 if (attributes != null) { 1935 PageRanges pageRangesAttr = 1936 (PageRanges)attributes.get(PageRanges.class); 1937 if (pageRangesAttr != null) { 1938 int[][] range = pageRangesAttr.getMembers(); 1939 return range[range.length-1][1]; 1940 } 1941 } 1942 return getMaxPageAttrib(); 1943 } 1944 1945 protected final int getMinPageAttrib() { 1946 if (attributes != null) { 1947 SunMinMaxPage s = 1948 (SunMinMaxPage)attributes.get(SunMinMaxPage.class); 1949 if (s != null) { 1950 return s.getMin(); 1951 } 1952 } 1953 return 1; 1954 } 1955 1956 protected final int getMaxPageAttrib() { 1957 if (attributes != null) { 1958 SunMinMaxPage s = 1959 (SunMinMaxPage)attributes.get(SunMinMaxPage.class); 1960 if (s != null) { 1961 return s.getMax(); 1962 } 1963 } 1964 1965 Pageable pageable = getPageable(); 1966 if (pageable != null) { 1967 int numPages = pageable.getNumberOfPages(); 1968 if (numPages <= Pageable.UNKNOWN_NUMBER_OF_PAGES) { 1969 numPages = MAX_UNKNOWN_PAGES; 1970 } 1971 return ((numPages == 0) ? 1 : numPages); 1972 } 1973 1974 return Integer.MAX_VALUE; 1975 } 1976 /** 1977 * Called by the print() method at the start of 1978 * a print job. 1979 */ 1980 protected abstract void startDoc() throws PrinterException; 1981 1982 /** 1983 * Called by the print() method at the end of 1984 * a print job. 1985 */ 1986 protected abstract void endDoc() throws PrinterException; 1987 1988 /* Called by cancelDoc */ 1989 protected abstract void abortDoc(); 1990 1991 // MacOSX - made protected so subclasses can reference it. 1992 protected void cancelDoc() throws PrinterAbortException { 1993 abortDoc(); 1994 synchronized (this) { 1995 userCancelled = false; 1996 performingPrinting = false; 1997 notify(); 1998 } 1999 throw new PrinterAbortException(); 2000 } 2001 2002 /** 2003 * Returns how many times the entire book should 2004 * be printed by the PrintJob. If the printer 2005 * itself supports collation then this method 2006 * should return 1 indicating that the entire 2007 * book need only be printed once and the copies 2008 * will be collated and made in the printer. 2009 */ 2010 protected int getCollatedCopies() { 2011 return isCollated() ? getCopiesInt() : 1; 2012 } 2013 2014 /** 2015 * Returns how many times each page in the book 2016 * should be consecutively printed by PrintJob. 2017 * If the printer makes copies itself then this 2018 * method should return 1. 2019 */ 2020 protected int getNoncollatedCopies() { 2021 return isCollated() ? 1 : getCopiesInt(); 2022 } 2023 2024 2025 /* The printer graphics config is cached on the job, so that it can 2026 * be created once, and updated only as needed (for now only to change 2027 * the bounds if when using a Pageable the page sizes changes). 2028 */ 2029 2030 private int deviceWidth, deviceHeight; 2031 private AffineTransform defaultDeviceTransform; 2032 private PrinterGraphicsConfig pgConfig; 2033 2034 synchronized void setGraphicsConfigInfo(AffineTransform at, 2035 double pw, double ph) { 2036 Point2D.Double pt = new Point2D.Double(pw, ph); 2037 at.transform(pt, pt); 2038 2039 if (pgConfig == null || 2040 defaultDeviceTransform == null || 2041 !at.equals(defaultDeviceTransform) || 2042 deviceWidth != (int)pt.getX() || 2043 deviceHeight != (int)pt.getY()) { 2044 2045 deviceWidth = (int)pt.getX(); 2046 deviceHeight = (int)pt.getY(); 2047 defaultDeviceTransform = at; 2048 pgConfig = null; 2049 } 2050 } 2051 2052 synchronized PrinterGraphicsConfig getPrinterGraphicsConfig() { 2053 if (pgConfig != null) { 2054 return pgConfig; 2055 } 2056 String deviceID = "Printer Device"; 2057 PrintService service = getPrintService(); 2058 if (service != null) { 2059 deviceID = service.toString(); 2060 } 2061 pgConfig = new PrinterGraphicsConfig(deviceID, 2062 defaultDeviceTransform, 2063 deviceWidth, deviceHeight); 2064 return pgConfig; 2065 } 2066 2067 /** 2068 * Print a page from the provided document. 2069 * @return int Printable.PAGE_EXISTS if the page existed and was drawn and 2070 * Printable.NO_SUCH_PAGE if the page did not exist. 2071 * @see java.awt.print.Printable 2072 */ 2073 protected int printPage(Pageable document, int pageIndex) 2074 throws PrinterException 2075 { 2076 PageFormat page; 2077 PageFormat origPage; 2078 Printable painter; 2079 try { 2080 origPage = document.getPageFormat(pageIndex); 2081 page = (PageFormat)origPage.clone(); 2082 painter = document.getPrintable(pageIndex); 2083 } catch (Exception e) { 2084 PrinterException pe = 2085 new PrinterException("Error getting page or printable.[ " + 2086 e +" ]"); 2087 pe.initCause(e); 2088 throw pe; 2089 } 2090 2091 /* Get the imageable area from Paper instead of PageFormat 2092 * because we do not want it adjusted by the page orientation. 2093 */ 2094 Paper paper = page.getPaper(); 2095 // if non-portrait and 270 degree landscape rotation 2096 if (page.getOrientation() != PageFormat.PORTRAIT && 2097 landscapeRotates270) { 2098 2099 double left = paper.getImageableX(); 2100 double top = paper.getImageableY(); 2101 double width = paper.getImageableWidth(); 2102 double height = paper.getImageableHeight(); 2103 paper.setImageableArea(paper.getWidth()-left-width, 2104 paper.getHeight()-top-height, 2105 width, height); 2106 page.setPaper(paper); 2107 if (page.getOrientation() == PageFormat.LANDSCAPE) { 2108 page.setOrientation(PageFormat.REVERSE_LANDSCAPE); 2109 } else { 2110 page.setOrientation(PageFormat.LANDSCAPE); 2111 } 2112 } 2113 2114 double xScale = getXRes() / 72.0; 2115 double yScale = getYRes() / 72.0; 2116 2117 /* The deviceArea is the imageable area in the printer's 2118 * resolution. 2119 */ 2120 Rectangle2D deviceArea = 2121 new Rectangle2D.Double(paper.getImageableX() * xScale, 2122 paper.getImageableY() * yScale, 2123 paper.getImageableWidth() * xScale, 2124 paper.getImageableHeight() * yScale); 2125 2126 /* Build and hold on to a uniform transform so that 2127 * we can get back to device space at the beginning 2128 * of each band. 2129 */ 2130 AffineTransform uniformTransform = new AffineTransform(); 2131 2132 /* The scale transform is used to switch from the 2133 * device space to the user's 72 dpi space. 2134 */ 2135 AffineTransform scaleTransform = new AffineTransform(); 2136 scaleTransform.scale(xScale, yScale); 2137 2138 /* bandwidth is multiple of 4 as the data is used in a win32 DIB and 2139 * some drivers behave badly if scanlines aren't multiples of 4 bytes. 2140 */ 2141 int bandWidth = (int) deviceArea.getWidth(); 2142 if (bandWidth % 4 != 0) { 2143 bandWidth += (4 - (bandWidth % 4)); 2144 } 2145 if (bandWidth <= 0) { 2146 throw new PrinterException("Paper's imageable width is too small."); 2147 } 2148 2149 int deviceAreaHeight = (int)deviceArea.getHeight(); 2150 if (deviceAreaHeight <= 0) { 2151 throw new PrinterException("Paper's imageable height is too small."); 2152 } 2153 2154 /* Figure out the number of lines that will fit into 2155 * our maximum band size. The hard coded 3 reflects the 2156 * fact that we can only create 24 bit per pixel 3 byte BGR 2157 * BufferedImages. FIX. 2158 */ 2159 int bandHeight = (MAX_BAND_SIZE / bandWidth / 3); 2160 2161 int deviceLeft = (int)Math.rint(paper.getImageableX() * xScale); 2162 int deviceTop = (int)Math.rint(paper.getImageableY() * yScale); 2163 2164 /* The device transform is used to move the band down 2165 * the page using translates. Normally this is all it 2166 * would do, but since, when printing, the Window's 2167 * DIB format wants the last line to be first (lowest) in 2168 * memory, the deviceTransform moves the origin to the 2169 * bottom of the band and flips the origin. This way the 2170 * app prints upside down into the band which is the DIB 2171 * format. 2172 */ 2173 AffineTransform deviceTransform = new AffineTransform(); 2174 deviceTransform.translate(-deviceLeft, deviceTop); 2175 deviceTransform.translate(0, bandHeight); 2176 deviceTransform.scale(1, -1); 2177 2178 /* Create a BufferedImage to hold the band. We set the clip 2179 * of the band to be tight around the bits so that the 2180 * application can use it to figure what part of the 2181 * page needs to be drawn. The clip is never altered in 2182 * this method, but we do translate the band's coordinate 2183 * system so that the app will see the clip moving down the 2184 * page though it s always around the same set of pixels. 2185 */ 2186 BufferedImage pBand = new BufferedImage(1, 1, 2187 BufferedImage.TYPE_3BYTE_BGR); 2188 2189 /* Have the app draw into a PeekGraphics object so we can 2190 * learn something about the needs of the print job. 2191 */ 2192 2193 PeekGraphics peekGraphics = createPeekGraphics(pBand.createGraphics(), 2194 this); 2195 2196 Rectangle2D.Double pageFormatArea = 2197 new Rectangle2D.Double(page.getImageableX(), 2198 page.getImageableY(), 2199 page.getImageableWidth(), 2200 page.getImageableHeight()); 2201 peekGraphics.transform(scaleTransform); 2202 peekGraphics.translate(-getPhysicalPrintableX(paper) / xScale, 2203 -getPhysicalPrintableY(paper) / yScale); 2204 peekGraphics.transform(new AffineTransform(page.getMatrix())); 2205 initPrinterGraphics(peekGraphics, pageFormatArea); 2206 AffineTransform pgAt = peekGraphics.getTransform(); 2207 2208 /* Update the information used to return a GraphicsConfiguration 2209 * for this printer device. It needs to be updated per page as 2210 * not all pages in a job may be the same size (different bounds) 2211 * The transform is the scaling transform as this corresponds to 2212 * the default transform for the device. The width and height are 2213 * those of the paper, not the page format, as we want to describe 2214 * the bounds of the device in its natural coordinate system of 2215 * device coordinate whereas a page format may be in a rotated context. 2216 */ 2217 setGraphicsConfigInfo(scaleTransform, 2218 paper.getWidth(), paper.getHeight()); 2219 int pageResult = painter.print(peekGraphics, origPage, pageIndex); 2220 debug_println("pageResult "+pageResult); 2221 if (pageResult == Printable.PAGE_EXISTS) { 2222 debug_println("startPage "+pageIndex); 2223 2224 /* We need to check if the paper size is changed. 2225 * Note that it is not sufficient to ask for the pageformat 2226 * of "pageIndex-1", since PageRanges mean that pages can be 2227 * skipped. So we have to look at the actual last paper size used. 2228 */ 2229 Paper thisPaper = page.getPaper(); 2230 boolean paperChanged = 2231 previousPaper == null || 2232 thisPaper.getWidth() != previousPaper.getWidth() || 2233 thisPaper.getHeight() != previousPaper.getHeight(); 2234 previousPaper = thisPaper; 2235 2236 startPage(page, painter, pageIndex, paperChanged); 2237 Graphics2D pathGraphics = createPathGraphics(peekGraphics, this, 2238 painter, page, 2239 pageIndex); 2240 2241 /* If we can convert the page directly to the 2242 * underlying graphics system then we do not 2243 * need to rasterize. We also may not need to 2244 * create the 'band' if all the pages can take 2245 * this path. 2246 */ 2247 if (pathGraphics != null) { 2248 pathGraphics.transform(scaleTransform); 2249 // user (0,0) should be origin of page, not imageable area 2250 pathGraphics.translate(-getPhysicalPrintableX(paper) / xScale, 2251 -getPhysicalPrintableY(paper) / yScale); 2252 pathGraphics.transform(new AffineTransform(page.getMatrix())); 2253 initPrinterGraphics(pathGraphics, pageFormatArea); 2254 2255 redrawList.clear(); 2256 2257 AffineTransform initialTx = pathGraphics.getTransform(); 2258 2259 painter.print(pathGraphics, origPage, pageIndex); 2260 2261 for (int i=0;i<redrawList.size();i++) { 2262 GraphicsState gstate = redrawList.get(i); 2263 pathGraphics.setTransform(initialTx); 2264 ((PathGraphics)pathGraphics).redrawRegion( 2265 gstate.region, 2266 gstate.sx, 2267 gstate.sy, 2268 gstate.theClip, 2269 gstate.theTransform); 2270 } 2271 2272 /* This is the banded-raster printing loop. 2273 * It should be moved into its own method. 2274 */ 2275 } else { 2276 BufferedImage band = cachedBand; 2277 if (cachedBand == null || 2278 bandWidth != cachedBandWidth || 2279 bandHeight != cachedBandHeight) { 2280 band = new BufferedImage(bandWidth, bandHeight, 2281 BufferedImage.TYPE_3BYTE_BGR); 2282 cachedBand = band; 2283 cachedBandWidth = bandWidth; 2284 cachedBandHeight = bandHeight; 2285 } 2286 Graphics2D bandGraphics = band.createGraphics(); 2287 2288 Rectangle2D.Double clipArea = 2289 new Rectangle2D.Double(0, 0, bandWidth, bandHeight); 2290 2291 initPrinterGraphics(bandGraphics, clipArea); 2292 2293 ProxyGraphics2D painterGraphics = 2294 new ProxyGraphics2D(bandGraphics, this); 2295 2296 Graphics2D clearGraphics = band.createGraphics(); 2297 clearGraphics.setColor(Color.white); 2298 2299 /* We need the actual bits of the BufferedImage to send to 2300 * the native Window's code. 'data' points to the actual 2301 * pixels. Right now these are in ARGB format with 8 bits 2302 * per component. We need to use a monochrome BufferedImage 2303 * for monochrome printers when this is supported by 2304 * BufferedImage. FIX 2305 */ 2306 ByteInterleavedRaster tile = (ByteInterleavedRaster)band.getRaster(); 2307 byte[] data = tile.getDataStorage(); 2308 2309 /* Loop over the page moving our band down the page, 2310 * calling the app to render the band, and then send the band 2311 * to the printer. 2312 */ 2313 int deviceBottom = deviceTop + deviceAreaHeight; 2314 2315 /* device's printable x,y is really addressable origin 2316 * we address relative to media origin so when we print a 2317 * band we need to adjust for the different methods of 2318 * addressing it. 2319 */ 2320 int deviceAddressableX = (int)getPhysicalPrintableX(paper); 2321 int deviceAddressableY = (int)getPhysicalPrintableY(paper); 2322 2323 for (int bandTop = 0; bandTop <= deviceAreaHeight; 2324 bandTop += bandHeight) 2325 { 2326 2327 /* Put the band back into device space and 2328 * erase the contents of the band. 2329 */ 2330 clearGraphics.fillRect(0, 0, bandWidth, bandHeight); 2331 2332 /* Put the band into the correct location on the 2333 * page. Once the band is moved we translate the 2334 * device transform so that the band will move down 2335 * the page on the next iteration of the loop. 2336 */ 2337 bandGraphics.setTransform(uniformTransform); 2338 bandGraphics.transform(deviceTransform); 2339 deviceTransform.translate(0, -bandHeight); 2340 2341 /* Switch the band from device space to user, 2342 * 72 dpi, space. 2343 */ 2344 bandGraphics.transform(scaleTransform); 2345 bandGraphics.transform(new AffineTransform(page.getMatrix())); 2346 2347 Rectangle clip = bandGraphics.getClipBounds(); 2348 clip = pgAt.createTransformedShape(clip).getBounds(); 2349 2350 if ((clip == null) || peekGraphics.hitsDrawingArea(clip) && 2351 (bandWidth > 0 && bandHeight > 0)) { 2352 2353 /* if the client has specified an imageable X or Y 2354 * which is off than the physically addressable 2355 * area of the page, then we need to adjust for that 2356 * here so that we pass only non -ve band coordinates 2357 * We also need to translate by the adjusted amount 2358 * so that printing appears in the correct place. 2359 */ 2360 int bandX = deviceLeft - deviceAddressableX; 2361 if (bandX < 0) { 2362 bandGraphics.translate(bandX/xScale,0); 2363 bandX = 0; 2364 } 2365 int bandY = deviceTop + bandTop - deviceAddressableY; 2366 if (bandY < 0) { 2367 bandGraphics.translate(0,bandY/yScale); 2368 bandY = 0; 2369 } 2370 /* Have the app's painter image into the band 2371 * and then send the band to the printer. 2372 */ 2373 painterGraphics.setDelegate((Graphics2D) bandGraphics.create()); 2374 painter.print(painterGraphics, origPage, pageIndex); 2375 painterGraphics.dispose(); 2376 printBand(data, bandX, bandY, bandWidth, bandHeight); 2377 } 2378 } 2379 2380 clearGraphics.dispose(); 2381 bandGraphics.dispose(); 2382 2383 } 2384 debug_println("calling endPage "+pageIndex); 2385 endPage(page, painter, pageIndex); 2386 } 2387 2388 return pageResult; 2389 } 2390 2391 /** 2392 * If a print job is in progress, print() has been 2393 * called but has not returned, then this signals 2394 * that the job should be cancelled and the next 2395 * chance. If there is no print job in progress then 2396 * this call does nothing. 2397 */ 2398 public void cancel() { 2399 synchronized (this) { 2400 if (performingPrinting) { 2401 userCancelled = true; 2402 } 2403 notify(); 2404 } 2405 } 2406 2407 /** 2408 * Returns true is a print job is ongoing but will 2409 * be cancelled and the next opportunity. false is 2410 * returned otherwise. 2411 */ 2412 public boolean isCancelled() { 2413 2414 boolean cancelled = false; 2415 2416 synchronized (this) { 2417 cancelled = (performingPrinting && userCancelled); 2418 notify(); 2419 } 2420 2421 return cancelled; 2422 } 2423 2424 /** 2425 * Return the Pageable describing the pages to be printed. 2426 */ 2427 protected Pageable getPageable() { 2428 return mDocument; 2429 } 2430 2431 /** 2432 * Examine the metrics captured by the 2433 * {@code PeekGraphics} instance and 2434 * if capable of directly converting this 2435 * print job to the printer's control language 2436 * or the native OS's graphics primitives, then 2437 * return a {@code PathGraphics} to perform 2438 * that conversion. If there is not an object 2439 * capable of the conversion then return 2440 * {@code null}. Returning {@code null} 2441 * causes the print job to be rasterized. 2442 */ 2443 protected Graphics2D createPathGraphics(PeekGraphics graphics, 2444 PrinterJob printerJob, 2445 Printable painter, 2446 PageFormat pageFormat, 2447 int pageIndex) { 2448 2449 return null; 2450 } 2451 2452 /** 2453 * Create and return an object that will 2454 * gather and hold metrics about the print 2455 * job. This method is passed a {@code Graphics2D} 2456 * object that can be used as a proxy for the 2457 * object gathering the print job matrics. The 2458 * method is also supplied with the instance 2459 * controlling the print job, {@code printerJob}. 2460 */ 2461 protected PeekGraphics createPeekGraphics(Graphics2D graphics, 2462 PrinterJob printerJob) { 2463 2464 return new PeekGraphics(graphics, printerJob); 2465 } 2466 2467 /** 2468 * Configure the passed in Graphics2D so that 2469 * is contains the defined initial settings 2470 * for a print job. These settings are: 2471 * color: black. 2472 * clip: <as passed in> 2473 */ 2474 // MacOSX - made protected so subclasses can reference it. 2475 protected void initPrinterGraphics(Graphics2D g, Rectangle2D clip) { 2476 2477 g.setClip(clip); 2478 g.setPaint(Color.black); 2479 } 2480 2481 2482 /** 2483 * User dialogs should disable "File" buttons if this returns false. 2484 * 2485 */ 2486 public boolean checkAllowedToPrintToFile() { 2487 try { 2488 throwPrintToFile(); 2489 return true; 2490 } catch (SecurityException e) { 2491 return false; 2492 } 2493 } 2494 2495 /** 2496 * Break this out as it may be useful when we allow API to 2497 * specify printing to a file. In that case its probably right 2498 * to throw a SecurityException if the permission is not granted 2499 */ 2500 private void throwPrintToFile() { 2501 SecurityManager security = System.getSecurityManager(); 2502 if (security != null) { 2503 if (printToFilePermission == null) { 2504 printToFilePermission = 2505 new FilePermission("<<ALL FILES>>", "read,write"); 2506 } 2507 security.checkPermission(printToFilePermission); 2508 } 2509 } 2510 2511 /* On-screen drawString renders most control chars as the missing glyph 2512 * and have the non-zero advance of that glyph. 2513 * Exceptions are \t, \n and \r which are considered zero-width. 2514 * This is a utility method used by subclasses to remove them so we 2515 * don't have to worry about platform or font specific handling of them. 2516 */ 2517 protected String removeControlChars(String s) { 2518 char[] in_chars = s.toCharArray(); 2519 int len = in_chars.length; 2520 char[] out_chars = new char[len]; 2521 int pos = 0; 2522 2523 for (int i = 0; i < len; i++) { 2524 char c = in_chars[i]; 2525 if (c > '\r' || c < '\t' || c == '\u000b' || c == '\u000c') { 2526 out_chars[pos++] = c; 2527 } 2528 } 2529 if (pos == len) { 2530 return s; // no need to make a new String. 2531 } else { 2532 return new String(out_chars, 0, pos); 2533 } 2534 } 2535 }