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