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