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