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