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