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