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