/* * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.print; import java.io.FilePermission; import java.awt.Color; import java.awt.Dialog; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.KeyboardFocusManager; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.print.Book; import java.awt.print.Pageable; import java.awt.print.PageFormat; import java.awt.print.Paper; import java.awt.print.Printable; import java.awt.print.PrinterAbortException; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.awt.Window; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Locale; import sun.awt.image.ByteInterleavedRaster; import javax.print.Doc; import javax.print.DocFlavor; import javax.print.DocPrintJob; import javax.print.PrintException; import javax.print.PrintService; import javax.print.PrintServiceLookup; import javax.print.ServiceUI; import javax.print.StreamPrintService; import javax.print.StreamPrintServiceFactory; import javax.print.attribute.Attribute; import javax.print.attribute.AttributeSet; import javax.print.attribute.HashPrintRequestAttributeSet; import javax.print.attribute.PrintRequestAttributeSet; import javax.print.attribute.Size2DSyntax; import javax.print.attribute.standard.Chromaticity; import javax.print.attribute.standard.Copies; import javax.print.attribute.standard.Destination; import javax.print.attribute.standard.DialogTypeSelection; import javax.print.attribute.standard.Fidelity; import javax.print.attribute.standard.JobName; import javax.print.attribute.standard.JobSheets; import javax.print.attribute.standard.Media; import javax.print.attribute.standard.MediaPrintableArea; import javax.print.attribute.standard.MediaSize; import javax.print.attribute.standard.MediaSizeName; import javax.print.attribute.standard.OrientationRequested; import javax.print.attribute.standard.PageRanges; import javax.print.attribute.standard.PrinterState; import javax.print.attribute.standard.PrinterStateReason; import javax.print.attribute.standard.PrinterStateReasons; import javax.print.attribute.standard.PrinterIsAcceptingJobs; import javax.print.attribute.standard.RequestingUserName; import javax.print.attribute.standard.SheetCollate; import javax.print.attribute.standard.Sides; import sun.print.PageableDoc; import sun.print.ServiceDialog; import sun.print.SunPrinterJobService; import sun.print.SunPageSelection; /** * A class which rasterizes a printer job. * * @author Richard Blanchard */ public abstract class RasterPrinterJob extends PrinterJob { /* Class Constants */ /* Printer destination type. */ protected static final int PRINTER = 0; /* File destination type. */ protected static final int FILE = 1; /* Stream destination type. */ protected static final int STREAM = 2; /** * Maximum amount of memory in bytes to use for the * buffered image "band". 4Mb is a compromise between * limiting the number of bands on hi-res printers and * not using too much of the Java heap or causing paging * on systems with little RAM. */ private static final int MAX_BAND_SIZE = (1024 * 1024 * 4); /* Dots Per Inch */ private static final float DPI = 72.0f; /** * Useful mainly for debugging, this system property * can be used to force the printing code to print * using a particular pipeline. The two currently * supported values are FORCE_RASTER and FORCE_PDL. */ private static final String FORCE_PIPE_PROP = "sun.java2d.print.pipeline"; /** * When the system property FORCE_PIPE_PROP has this value * then each page of a print job will be rendered through * the raster pipeline. */ private static final String FORCE_RASTER = "raster"; /** * When the system property FORCE_PIPE_PROP has this value * then each page of a print job will be rendered through * the PDL pipeline. */ private static final String FORCE_PDL = "pdl"; /** * When the system property SHAPE_TEXT_PROP has this value * then text is always rendered as a shape, and no attempt is made * to match the font through GDI */ private static final String SHAPE_TEXT_PROP = "sun.java2d.print.shapetext"; /** * values obtained from System properties in static initialiser block */ public static boolean forcePDL = false; public static boolean forceRaster = false; public static boolean shapeTextProp = false; static { /* The system property FORCE_PIPE_PROP * can be used to force the printing code to * use a particular pipeline. Either the raster * pipeline or the pdl pipeline can be forced. */ String forceStr = (String)java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction(FORCE_PIPE_PROP)); if (forceStr != null) { if (forceStr.equalsIgnoreCase(FORCE_PDL)) { forcePDL = true; } else if (forceStr.equalsIgnoreCase(FORCE_RASTER)) { forceRaster = true; } } String shapeTextStr = (String)java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction(SHAPE_TEXT_PROP)); if (shapeTextStr != null) { shapeTextProp = true; } } /* Instance Variables */ /** * Used to minimise GC & reallocation of band when printing */ private int cachedBandWidth = 0; private int cachedBandHeight = 0; private BufferedImage cachedBand = null; /** * The number of book copies to be printed. */ private int mNumCopies = 1; /** * Collation effects the order of the pages printed * when multiple copies are requested. For two copies * of a three page document the page order is: * mCollate true: 1, 2, 3, 1, 2, 3 * mCollate false: 1, 1, 2, 2, 3, 3 */ private boolean mCollate = false; /** * The zero based indices of the first and last * pages to be printed. If 'mFirstPage' is * UNDEFINED_PAGE_NUM then the first page to * be printed is page 0. If 'mLastPage' is * UNDEFINED_PAGE_NUM then the last page to * be printed is the last one in the book. */ private int mFirstPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; private int mLastPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; /** * The previous print stream Paper * Used to check if the paper size has changed such that the * implementation needs to emit the new paper size information * into the print stream. * Since we do our own rotation, and the margins aren't relevant, * Its strictly the dimensions of the paper that we will check. */ private Paper previousPaper; /** * The document to be printed. It is initialized to an * empty (zero pages) book. */ // MacOSX - made protected so subclasses can reference it. protected Pageable mDocument = new Book(); /** * The name of the job being printed. */ private String mDocName = "Java Printing"; /** * Printing cancellation flags */ // MacOSX - made protected so subclasses can reference it. protected boolean performingPrinting = false; // MacOSX - made protected so subclasses can reference it. protected boolean userCancelled = false; /** * Print to file permission variables. */ private FilePermission printToFilePermission; /** * List of areas & the graphics state for redrawing */ private ArrayList redrawList = new ArrayList(); /* variables representing values extracted from an attribute set. * These take precedence over values set on a printer job */ private int copiesAttr; private String jobNameAttr; private String userNameAttr; private PageRanges pageRangesAttr; protected Sides sidesAttr; protected String destinationAttr; protected boolean noJobSheet = false; protected int mDestType = RasterPrinterJob.FILE; protected String mDestination = ""; protected boolean collateAttReq = false; /** * Device rotation flag, if it support 270, this is set to true; */ protected boolean landscapeRotates270 = false; /** * attributes used by no-args page and print dialog and print method to * communicate state */ protected PrintRequestAttributeSet attributes = null; /** * Class to keep state information for redrawing areas * "region" is an area at as a high a resolution as possible. * The redrawing code needs to look at sx, sy to calculate the scale * to device resolution. */ private class GraphicsState { Rectangle2D region; // Area of page to repaint Shape theClip; // image drawing clip. AffineTransform theTransform; // to transform clip to dev coords. double sx; // X scale from region to device resolution double sy; // Y scale from region to device resolution } /** * Service for this job */ protected PrintService myService; /* Constructors */ public RasterPrinterJob() { } /* Abstract Methods */ /** * Returns the resolution in dots per inch across the width * of the page. */ abstract protected double getXRes(); /** * Returns the resolution in dots per inch down the height * of the page. */ abstract protected double getYRes(); /** * Must be obtained from the current printer. * Value is in device pixels. * Not adjusted for orientation of the paper. */ abstract protected double getPhysicalPrintableX(Paper p); /** * Must be obtained from the current printer. * Value is in device pixels. * Not adjusted for orientation of the paper. */ abstract protected double getPhysicalPrintableY(Paper p); /** * Must be obtained from the current printer. * Value is in device pixels. * Not adjusted for orientation of the paper. */ abstract protected double getPhysicalPrintableWidth(Paper p); /** * Must be obtained from the current printer. * Value is in device pixels. * Not adjusted for orientation of the paper. */ abstract protected double getPhysicalPrintableHeight(Paper p); /** * Must be obtained from the current printer. * Value is in device pixels. * Not adjusted for orientation of the paper. */ abstract protected double getPhysicalPageWidth(Paper p); /** * Must be obtained from the current printer. * Value is in device pixels. * Not adjusted for orientation of the paper. */ abstract protected double getPhysicalPageHeight(Paper p); /** * Begin a new page. */ abstract protected void startPage(PageFormat format, Printable painter, int index, boolean paperChanged) throws PrinterException; /** * End a page. */ abstract protected void endPage(PageFormat format, Printable painter, int index) throws PrinterException; /** * Prints the contents of the array of ints, 'data' * to the current page. The band is placed at the * location (x, y) in device coordinates on the * page. The width and height of the band is * specified by the caller. */ abstract protected void printBand(byte[] data, int x, int y, int width, int height) throws PrinterException; /* Instance Methods */ /** * save graphics state of a PathGraphics for later redrawing * of part of page represented by the region in that state */ public void saveState(AffineTransform at, Shape clip, Rectangle2D region, double sx, double sy) { GraphicsState gstate = new GraphicsState(); gstate.theTransform = at; gstate.theClip = clip; gstate.region = region; gstate.sx = sx; gstate.sy = sy; redrawList.add(gstate); } /* * A convenience method which returns the default service * for 2D PrinterJobs. * May return null if there is no suitable default (although there * may still be 2D services available). * @return default 2D print service, or null. * @since 1.4 */ protected static PrintService lookupDefaultPrintService() { PrintService service = PrintServiceLookup.lookupDefaultPrintService(); /* Pageable implies Printable so checking both isn't strictly needed */ if (service != null && service.isDocFlavorSupported( DocFlavor.SERVICE_FORMATTED.PAGEABLE) && service.isDocFlavorSupported( DocFlavor.SERVICE_FORMATTED.PRINTABLE)) { return service; } else { PrintService []services = PrintServiceLookup.lookupPrintServices( DocFlavor.SERVICE_FORMATTED.PAGEABLE, null); if (services.length > 0) { return services[0]; } } return null; } /** * Returns the service (printer) for this printer job. * Implementations of this class which do not support print services * may return null; * @return the service for this printer job. * */ public PrintService getPrintService() { if (myService == null) { PrintService svc = PrintServiceLookup.lookupDefaultPrintService(); if (svc != null && svc.isDocFlavorSupported( DocFlavor.SERVICE_FORMATTED.PAGEABLE)) { try { setPrintService(svc); myService = svc; } catch (PrinterException e) { } } if (myService == null) { PrintService[] svcs = PrintServiceLookup.lookupPrintServices( DocFlavor.SERVICE_FORMATTED.PAGEABLE, null); if (svcs.length > 0) { try { setPrintService(svcs[0]); myService = svcs[0]; } catch (PrinterException e) { } } } } return myService; } /** * Associate this PrinterJob with a new PrintService. * * Throws PrinterException if the specified service * cannot support the Pageable and * Printable interfaces necessary to support 2D printing. * @param a print service which supports 2D printing. * * @throws PrinterException if the specified service does not support * 2D printing or no longer available. */ public void setPrintService(PrintService service) throws PrinterException { if (service == null) { throw new PrinterException("Service cannot be null"); } else if (!(service instanceof StreamPrintService) && service.getName() == null) { throw new PrinterException("Null PrintService name."); } else { // Check the list of services. This service may have been // deleted already PrinterState prnState = (PrinterState)service.getAttribute( PrinterState.class); if (prnState == PrinterState.STOPPED) { PrinterStateReasons prnStateReasons = (PrinterStateReasons)service.getAttribute( PrinterStateReasons.class); if ((prnStateReasons != null) && (prnStateReasons.containsKey(PrinterStateReason.SHUTDOWN))) { throw new PrinterException("PrintService is no longer available."); } } if (service.isDocFlavorSupported( DocFlavor.SERVICE_FORMATTED.PAGEABLE) && service.isDocFlavorSupported( DocFlavor.SERVICE_FORMATTED.PRINTABLE)) { myService = service; } else { throw new PrinterException("Not a 2D print service: " + service); } } } protected void updatePageAttributes(PrintService service, PageFormat page) { if (service == null || page == null) { return; } float x = (float)Math.rint( (page.getPaper().getWidth()*Size2DSyntax.INCH)/ (72.0))/(float)Size2DSyntax.INCH; float y = (float)Math.rint( (page.getPaper().getHeight()*Size2DSyntax.INCH)/ (72.0))/(float)Size2DSyntax.INCH; // We should limit the list where we search the matching // media, this will prevent mapping to wrong media ex. Ledger // can be mapped to B. Especially useful when creating // custom MediaSize. Media[] mediaList = (Media[])service.getSupportedAttributeValues( Media.class, null, null); Media media = null; try { media = CustomMediaSizeName.findMedia(mediaList, x, y, Size2DSyntax.INCH); } catch (IllegalArgumentException iae) { } if ((media == null) || !(service.isAttributeValueSupported(media, null, null))) { media = (Media)service.getDefaultAttributeValue(Media.class); } OrientationRequested orient; switch (page.getOrientation()) { case PageFormat.LANDSCAPE : orient = OrientationRequested.LANDSCAPE; break; case PageFormat.REVERSE_LANDSCAPE: orient = OrientationRequested.REVERSE_LANDSCAPE; break; default: orient = OrientationRequested.PORTRAIT; } if (attributes == null) { attributes = new HashPrintRequestAttributeSet(); } if (media != null) { attributes.add(media); } attributes.add(orient); float ix = (float)(page.getPaper().getImageableX()/DPI); float iw = (float)(page.getPaper().getImageableWidth()/DPI); float iy = (float)(page.getPaper().getImageableY()/DPI); float ih = (float)(page.getPaper().getImageableHeight()/DPI); if (ix < 0) ix = 0f; if (iy < 0) iy = 0f; try { attributes.add(new MediaPrintableArea(ix, iy, iw, ih, MediaPrintableArea.INCH)); } catch (IllegalArgumentException iae) { } } /** * Display a dialog to the user allowing the modification of a * PageFormat instance. * The page argument is used to initialize controls * in the page setup dialog. * If the user cancels the dialog, then the method returns the * original page object unmodified. * If the user okays the dialog then the method returns a new * PageFormat object with the indicated changes. * In either case the original page object will * not be modified. * @param page the default PageFormat presented to the user * for modification * @return the original page object if the dialog * is cancelled, or a new PageFormat object containing * the format indicated by the user if the dialog is * acknowledged * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless * @since 1.2 */ public PageFormat pageDialog(PageFormat page) throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { throw new HeadlessException(); } final GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment(). getDefaultScreenDevice().getDefaultConfiguration(); PrintService service = (PrintService)java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { PrintService service = getPrintService(); if (service == null) { ServiceDialog.showNoPrintService(gc); return null; } return service; } }); if (service == null) { return page; } updatePageAttributes(service, page); PageFormat newPage = pageDialog(attributes); if (newPage == null) { return page; } else { return newPage; } } /** * return a PageFormat corresponding to the updated attributes, * or null if the user cancelled the dialog. */ public PageFormat pageDialog(final PrintRequestAttributeSet attributes) throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { throw new HeadlessException(); } final GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment(). getDefaultScreenDevice().getDefaultConfiguration(); Rectangle bounds = gc.getBounds(); int x = bounds.x+bounds.width/3; int y = bounds.y+bounds.height/3; PrintService service = (PrintService)java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { PrintService service = getPrintService(); if (service == null) { ServiceDialog.showNoPrintService(gc); return null; } return service; } }); if (service == null) { return null; } ServiceDialog pageDialog = new ServiceDialog(gc, x, y, service, DocFlavor.SERVICE_FORMATTED.PAGEABLE, attributes, (Frame)null); pageDialog.show(); if (pageDialog.getStatus() == ServiceDialog.APPROVE) { PrintRequestAttributeSet newas = pageDialog.getAttributes(); Class amCategory = SunAlternateMedia.class; if (attributes.containsKey(amCategory) && !newas.containsKey(amCategory)) { attributes.remove(amCategory); } attributes.addAll(newas); PageFormat page = defaultPage(); OrientationRequested orient = (OrientationRequested) attributes.get(OrientationRequested.class); int pfOrient = PageFormat.PORTRAIT; if (orient != null) { if (orient == OrientationRequested.REVERSE_LANDSCAPE) { pfOrient = PageFormat.REVERSE_LANDSCAPE; } else if (orient == OrientationRequested.LANDSCAPE) { pfOrient = PageFormat.LANDSCAPE; } } page.setOrientation(pfOrient); Media media = (Media)attributes.get(Media.class); if (media == null) { media = (Media)service.getDefaultAttributeValue(Media.class); } if (!(media instanceof MediaSizeName)) { media = MediaSizeName.NA_LETTER; } MediaSize size = MediaSize.getMediaSizeForName((MediaSizeName)media); if (size == null) { size = MediaSize.NA.LETTER; } Paper paper = new Paper(); float dim[] = size.getSize(1); //units == 1 to avoid FP error double w = Math.rint((dim[0]*72.0)/Size2DSyntax.INCH); double h = Math.rint((dim[1]*72.0)/Size2DSyntax.INCH); paper.setSize(w, h); MediaPrintableArea area = (MediaPrintableArea) attributes.get(MediaPrintableArea.class); double ix, iw, iy, ih; if (area != null) { // Should pass in same unit as updatePageAttributes // to avoid rounding off errors. ix = Math.rint( area.getX(MediaPrintableArea.INCH) * DPI); iy = Math.rint( area.getY(MediaPrintableArea.INCH) * DPI); iw = Math.rint( area.getWidth(MediaPrintableArea.INCH) * DPI); ih = Math.rint( area.getHeight(MediaPrintableArea.INCH) * DPI); } else { if (w >= 72.0 * 6.0) { ix = 72.0; iw = w - 2 * 72.0; } else { ix = w / 6.0; iw = w * 0.75; } if (h >= 72.0 * 6.0) { iy = 72.0; ih = h - 2 * 72.0; } else { iy = h / 6.0; ih = h * 0.75; } } paper.setImageableArea(ix, iy, iw, ih); page.setPaper(paper); return page; } else { return null; } } /** * Presents the user a dialog for changing properties of the * print job interactively. * The services browsable here are determined by the type of * service currently installed. * If the application installed a StreamPrintService on this * PrinterJob, only the available StreamPrintService (factories) are * browsable. * * @param attributes to store changed properties. * @return false if the user cancels the dialog and true otherwise. * @exception HeadlessException if GraphicsEnvironment.isHeadless() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless */ public boolean printDialog(final PrintRequestAttributeSet attributes) throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { throw new HeadlessException(); } DialogTypeSelection dlg = (DialogTypeSelection)attributes.get(DialogTypeSelection.class); // Check for native, note that default dialog is COMMON. if (dlg == DialogTypeSelection.NATIVE) { this.attributes = attributes; try { debug_println("calling setAttributes in printDialog"); setAttributes(attributes); } catch (PrinterException e) { } boolean ret = printDialog(); this.attributes = attributes; return ret; } /* A security check has already been performed in the * java.awt.print.printerJob.getPrinterJob method. * So by the time we get here, it is OK for the current thread * to print either to a file (from a Dialog we control!) or * to a chosen printer. * * We raise privilege when we put up the dialog, to avoid * the "warning applet window" banner. */ final GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment(). getDefaultScreenDevice().getDefaultConfiguration(); PrintService service = (PrintService)java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { PrintService service = getPrintService(); if (service == null) { ServiceDialog.showNoPrintService(gc); return null; } return service; } }); if (service == null) { return false; } PrintService[] services; StreamPrintServiceFactory[] spsFactories = null; if (service instanceof StreamPrintService) { spsFactories = lookupStreamPrintServices(null); services = new StreamPrintService[spsFactories.length]; for (int i=0; i 0) { mpa = ((MediaPrintableArea[])mpaVals)[0]; } } if (isSupportedValue(orientReq, attributes) || (!fidelity && orientReq != null)) { int orient; if (orientReq.equals(OrientationRequested.REVERSE_LANDSCAPE)) { orient = PageFormat.REVERSE_LANDSCAPE; } else if (orientReq.equals(OrientationRequested.LANDSCAPE)) { orient = PageFormat.LANDSCAPE; } else { orient = PageFormat.PORTRAIT; } pf.setOrientation(orient); } if (isSupportedValue(media, attributes) || (!fidelity && media != null)) { if (media instanceof MediaSizeName) { MediaSizeName msn = (MediaSizeName)media; MediaSize msz = MediaSize.getMediaSizeForName(msn); if (msz != null) { float paperWid = msz.getX(MediaSize.INCH) * 72.0f; float paperHgt = msz.getY(MediaSize.INCH) * 72.0f; paper.setSize(paperWid, paperHgt); if (mpa == null) { paper.setImageableArea(72.0, 72.0, paperWid-144.0, paperHgt-144.0); } } } } if (isSupportedValue(mpa, attributes) || (!fidelity && mpa != null)) { float [] printableArea = mpa.getPrintableArea(MediaPrintableArea.INCH); for (int i=0; i < printableArea.length; i++) { printableArea[i] = printableArea[i]*72.0f; } paper.setImageableArea(printableArea[0], printableArea[1], printableArea[2], printableArea[3]); } pf.setPaper(paper); pf = validatePage(pf); setPrintable(printable, pf); } else { // for AWT where pageable is not an instance of OpenBook, // we need to save paper info this.attributes = attributes; } } /* * Services we don't recognize as built-in services can't be * implemented as subclasses of PrinterJob, therefore we create * a DocPrintJob from their service and pass a Doc representing * the application's printjob */ // MacOSX - made protected so subclasses can reference it. protected void spoolToService(PrintService psvc, PrintRequestAttributeSet attributes) throws PrinterException { if (psvc == null) { throw new PrinterException("No print service found."); } DocPrintJob job = psvc.createPrintJob(); Doc doc = new PageableDoc(getPageable()); if (attributes == null) { attributes = new HashPrintRequestAttributeSet(); } try { job.print(doc, attributes); } catch (PrintException e) { throw new PrinterException(e.toString()); } } /** * Prints a set of pages. * @exception java.awt.print.PrinterException an error in the print system * caused the job to be aborted * @see java.awt.print.Book * @see java.awt.print.Pageable * @see java.awt.print.Printable */ public void print() throws PrinterException { print(attributes); } public static boolean debugPrint = false; protected void debug_println(String str) { if (debugPrint) { System.out.println("RasterPrinterJob "+str+" "+this); } } public void print(PrintRequestAttributeSet attributes) throws PrinterException { /* * In the future PrinterJob will probably always dispatch * the print job to the PrintService. * This is how third party 2D Print Services will be invoked * when applications use the PrinterJob API. * However the JRE's concrete PrinterJob implementations have * not yet been re-worked to be implemented as standalone * services, and are implemented only as subclasses of PrinterJob. * So here we dispatch only those services we do not recognize * as implemented through platform subclasses of PrinterJob * (and this class). */ PrintService psvc = getPrintService(); debug_println("psvc = "+psvc); if (psvc == null) { throw new PrinterException("No print service found."); } // Check the list of services. This service may have been // deleted already PrinterState prnState = (PrinterState)psvc.getAttribute( PrinterState.class); if (prnState == PrinterState.STOPPED) { PrinterStateReasons prnStateReasons = (PrinterStateReasons)psvc.getAttribute( PrinterStateReasons.class); if ((prnStateReasons != null) && (prnStateReasons.containsKey(PrinterStateReason.SHUTDOWN))) { throw new PrinterException("PrintService is no longer available."); } } if ((PrinterIsAcceptingJobs)(psvc.getAttribute( PrinterIsAcceptingJobs.class)) == PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) { throw new PrinterException("Printer is not accepting job."); } if ((psvc instanceof SunPrinterJobService) && ((SunPrinterJobService)psvc).usesClass(getClass())) { setAttributes(attributes); // throw exception for invalid destination if (destinationAttr != null) { // destinationAttr is null for Destination(new URI("")) // because isAttributeValueSupported returns false in setAttributes // Destination(new URI(" ")) throws URISyntaxException File f = new File(destinationAttr); try { // check if this is a new file and if filename chars are valid if (f.createNewFile()) { f.delete(); } } catch (IOException ioe) { throw new PrinterException("Cannot write to file:"+ destinationAttr); } catch (SecurityException se) { //There is already file read/write access so at this point // only delete access is denied. Just ignore it because in // most cases the file created in createNewFile gets overwritten // anyway. } File pFile = f.getParentFile(); if ((f.exists() && (!f.isFile() || !f.canWrite())) || ((pFile != null) && (!pFile.exists() || (pFile.exists() && !pFile.canWrite())))) { throw new PrinterException("Cannot write to file:"+ destinationAttr); } } } else { spoolToService(psvc, attributes); return; } /* We need to make sure that the collation and copies * settings are initialised */ initPrinter(); int numCollatedCopies = getCollatedCopies(); int numNonCollatedCopies = getNoncollatedCopies(); debug_println("getCollatedCopies() "+numCollatedCopies + " getNoncollatedCopies() "+ numNonCollatedCopies); /* Get the range of pages we are to print. If the * last page to print is unknown, then we print to * the end of the document. Note that firstPage * and lastPage are 0 based page indices. */ int numPages = mDocument.getNumberOfPages(); if (numPages == 0) { return; } int firstPage = getFirstPage(); int lastPage = getLastPage(); if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES){ int totalPages = mDocument.getNumberOfPages(); if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) { lastPage = mDocument.getNumberOfPages() - 1; } } try { synchronized (this) { performingPrinting = true; userCancelled = false; } startDoc(); if (isCancelled()) { cancelDoc(); } // PageRanges can be set even if RANGE is not selected // so we need to check if it is selected. boolean rangeIsSelected = true; if (attributes != null) { SunPageSelection pages = (SunPageSelection)attributes.get(SunPageSelection.class); if ((pages != null) && (pages != SunPageSelection.RANGE)) { rangeIsSelected = false; } } debug_println("after startDoc rangeSelected? "+rangeIsSelected + " numNonCollatedCopies "+ numNonCollatedCopies); /* Three nested loops iterate over the document. The outer loop * counts the number of collated copies while the inner loop * counts the number of nonCollated copies. Normally, one of * these two loops will only execute once; that is we will * either print collated copies or noncollated copies. The * middle loop iterates over the pages. * If a PageRanges attribute is used, it constrains the pages * that are imaged. If a platform subclass (though a user dialog) * requests a page range via setPageRange(). it too can * constrain the page ranges that are imaged. * It is expected that only one of these will be used in a * job but both should be able to co-exist. */ for(int collated = 0; collated < numCollatedCopies; collated++) { for(int i = firstPage, pageResult = Printable.PAGE_EXISTS; (i <= lastPage || lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) && pageResult == Printable.PAGE_EXISTS; i++) { if ((pageRangesAttr != null) && rangeIsSelected ){ int nexti = pageRangesAttr.next(i); if (nexti == -1) { break; } else if (nexti != i+1) { continue; } } for(int nonCollated = 0; nonCollated < numNonCollatedCopies && pageResult == Printable.PAGE_EXISTS; nonCollated++) { if (isCancelled()) { cancelDoc(); } debug_println("printPage "+i); pageResult = printPage(mDocument, i); } } } if (isCancelled()) { cancelDoc(); } } finally { // reset previousPaper in case this job is invoked again. previousPaper = null; synchronized (this) { if (performingPrinting) { endDoc(); } performingPrinting = false; notify(); } } } /** * updates a Paper object to reflect the current printer's selected * paper size and imageable area for that paper size. * Default implementation copies settings from the original, applies * applies some validity checks, changes them only if they are * clearly unreasonable, then sets them into the new Paper. * Subclasses are expected to override this method to make more * informed decisons. */ protected void validatePaper(Paper origPaper, Paper newPaper) { if (origPaper == null || newPaper == null) { return; } else { double wid = origPaper.getWidth(); double hgt = origPaper.getHeight(); double ix = origPaper.getImageableX(); double iy = origPaper.getImageableY(); double iw = origPaper.getImageableWidth(); double ih = origPaper.getImageableHeight(); /* Assume any +ve values are legal. Overall paper dimensions * take precedence. Make sure imageable area fits on the paper. */ Paper defaultPaper = new Paper(); wid = ((wid > 0.0) ? wid : defaultPaper.getWidth()); hgt = ((hgt > 0.0) ? hgt : defaultPaper.getHeight()); ix = ((ix > 0.0) ? ix : defaultPaper.getImageableX()); iy = ((iy > 0.0) ? iy : defaultPaper.getImageableY()); iw = ((iw > 0.0) ? iw : defaultPaper.getImageableWidth()); ih = ((ih > 0.0) ? ih : defaultPaper.getImageableHeight()); /* full width/height is not likely to be imageable, but since we * don't know the limits we have to allow it */ if (iw > wid) { iw = wid; } if (ih > hgt) { ih = hgt; } if ((ix + iw) > wid) { ix = wid - iw; } if ((iy + ih) > hgt) { iy = hgt - ih; } newPaper.setSize(wid, hgt); newPaper.setImageableArea(ix, iy, iw, ih); } } /** * The passed in PageFormat will be copied and altered to describe * the default page size and orientation of the PrinterJob's * current printer. * Platform subclasses which can access the actual default paper size * for a printer may override this method. */ public PageFormat defaultPage(PageFormat page) { PageFormat newPage = (PageFormat)page.clone(); newPage.setOrientation(PageFormat.PORTRAIT); Paper newPaper = new Paper(); double ptsPerInch = 72.0; double w, h; Media media = null; PrintService service = getPrintService(); if (service != null) { MediaSize size; media = (Media)service.getDefaultAttributeValue(Media.class); if (media instanceof MediaSizeName && ((size = MediaSize.getMediaSizeForName((MediaSizeName)media)) != null)) { w = size.getX(MediaSize.INCH) * ptsPerInch; h = size.getY(MediaSize.INCH) * ptsPerInch; newPaper.setSize(w, h); newPaper.setImageableArea(ptsPerInch, ptsPerInch, w - 2.0*ptsPerInch, h - 2.0*ptsPerInch); newPage.setPaper(newPaper); return newPage; } } /* Default to A4 paper outside North America. */ String defaultCountry = Locale.getDefault().getCountry(); if (!Locale.getDefault().equals(Locale.ENGLISH) && // ie "C" defaultCountry != null && !defaultCountry.equals(Locale.US.getCountry()) && !defaultCountry.equals(Locale.CANADA.getCountry())) { double mmPerInch = 25.4; w = Math.rint((210.0*ptsPerInch)/mmPerInch); h = Math.rint((297.0*ptsPerInch)/mmPerInch); newPaper.setSize(w, h); newPaper.setImageableArea(ptsPerInch, ptsPerInch, w - 2.0*ptsPerInch, h - 2.0*ptsPerInch); } newPage.setPaper(newPaper); return newPage; } /** * The passed in PageFormat is cloned and altered to be usable on * the PrinterJob's current printer. */ public PageFormat validatePage(PageFormat page) { PageFormat newPage = (PageFormat)page.clone(); Paper newPaper = new Paper(); validatePaper(newPage.getPaper(), newPaper); newPage.setPaper(newPaper); return newPage; } /** * Set the number of copies to be printed. */ public void setCopies(int copies) { mNumCopies = copies; } /** * Get the number of copies to be printed. */ public int getCopies() { return mNumCopies; } /* Used when executing a print job where an attribute set may * over ride API values. */ protected int getCopiesInt() { return (copiesAttr > 0) ? copiesAttr : getCopies(); } /** * Get the name of the printing user. * The caller must have security permission to read system properties. */ public String getUserName() { return System.getProperty("user.name"); } /* Used when executing a print job where an attribute set may * over ride API values. */ protected String getUserNameInt() { if (userNameAttr != null) { return userNameAttr; } else { try { return getUserName(); } catch (SecurityException e) { return ""; } } } /** * Set the name of the document to be printed. * The document name can not be null. */ public void setJobName(String jobName) { if (jobName != null) { mDocName = jobName; } else { throw new NullPointerException(); } } /** * Get the name of the document to be printed. */ public String getJobName() { return mDocName; } /* Used when executing a print job where an attribute set may * over ride API values. */ protected String getJobNameInt() { return (jobNameAttr != null) ? jobNameAttr : getJobName(); } /** * Set the range of pages from a Book to be printed. * Both 'firstPage' and 'lastPage' are zero based * page indices. If either parameter is less than * zero then the page range is set to be from the * first page to the last. */ protected void setPageRange(int firstPage, int lastPage) { if(firstPage >= 0 && lastPage >= 0) { mFirstPage = firstPage; mLastPage = lastPage; if(mLastPage < mFirstPage) mLastPage = mFirstPage; } else { mFirstPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; mLastPage = Pageable.UNKNOWN_NUMBER_OF_PAGES; } } /** * Return the zero based index of the first page to * be printed in this job. */ protected int getFirstPage() { return mFirstPage == Book.UNKNOWN_NUMBER_OF_PAGES ? 0 : mFirstPage; } /** * Return the zero based index of the last page to * be printed in this job. */ protected int getLastPage() { return mLastPage; } /** * Set whether copies should be collated or not. * Two collated copies of a three page document * print in this order: 1, 2, 3, 1, 2, 3 while * uncollated copies print in this order: * 1, 1, 2, 2, 3, 3. * This is set when request is using an attribute set. */ protected void setCollated(boolean collate) { mCollate = collate; collateAttReq = true; } /** * Return true if collated copies will be printed as determined * in an attribute set. */ protected boolean isCollated() { return mCollate; } /** * Called by the print() method at the start of * a print job. */ protected abstract void startDoc() throws PrinterException; /** * Called by the print() method at the end of * a print job. */ protected abstract void endDoc() throws PrinterException; /* Called by cancelDoc */ protected abstract void abortDoc(); // MacOSX - made protected so subclasses can reference it. protected void cancelDoc() throws PrinterAbortException { abortDoc(); synchronized (this) { userCancelled = false; performingPrinting = false; notify(); } throw new PrinterAbortException(); } /** * Returns how many times the entire book should * be printed by the PrintJob. If the printer * itself supports collation then this method * should return 1 indicating that the entire * book need only be printed once and the copies * will be collated and made in the printer. */ protected int getCollatedCopies() { return isCollated() ? getCopiesInt() : 1; } /** * Returns how many times each page in the book * should be consecutively printed by PrintJob. * If the printer makes copies itself then this * method should return 1. */ protected int getNoncollatedCopies() { return isCollated() ? 1 : getCopiesInt(); } /* The printer graphics config is cached on the job, so that it can * be created once, and updated only as needed (for now only to change * the bounds if when using a Pageable the page sizes changes). */ private int deviceWidth, deviceHeight; private AffineTransform defaultDeviceTransform; private PrinterGraphicsConfig pgConfig; synchronized void setGraphicsConfigInfo(AffineTransform at, double pw, double ph) { Point2D.Double pt = new Point2D.Double(pw, ph); at.transform(pt, pt); if (pgConfig == null || defaultDeviceTransform == null || !at.equals(defaultDeviceTransform) || deviceWidth != (int)pt.getX() || deviceHeight != (int)pt.getY()) { deviceWidth = (int)pt.getX(); deviceHeight = (int)pt.getY(); defaultDeviceTransform = at; pgConfig = null; } } synchronized PrinterGraphicsConfig getPrinterGraphicsConfig() { if (pgConfig != null) { return pgConfig; } String deviceID = "Printer Device"; PrintService service = getPrintService(); if (service != null) { deviceID = service.toString(); } pgConfig = new PrinterGraphicsConfig(deviceID, defaultDeviceTransform, deviceWidth, deviceHeight); return pgConfig; } /** * Print a page from the provided document. * @return int Printable.PAGE_EXISTS if the page existed and was drawn and * Printable.NO_SUCH_PAGE if the page did not exist. * @see java.awt.print.Printable */ protected int printPage(Pageable document, int pageIndex) throws PrinterException { PageFormat page; PageFormat origPage; Printable painter; try { origPage = document.getPageFormat(pageIndex); page = (PageFormat)origPage.clone(); painter = document.getPrintable(pageIndex); } catch (Exception e) { PrinterException pe = new PrinterException("Error getting page or printable.[ " + e +" ]"); pe.initCause(e); throw pe; } /* Get the imageable area from Paper instead of PageFormat * because we do not want it adjusted by the page orientation. */ Paper paper = page.getPaper(); // if non-portrait and 270 degree landscape rotation if (page.getOrientation() != PageFormat.PORTRAIT && landscapeRotates270) { double left = paper.getImageableX(); double top = paper.getImageableY(); double width = paper.getImageableWidth(); double height = paper.getImageableHeight(); paper.setImageableArea(paper.getWidth()-left-width, paper.getHeight()-top-height, width, height); page.setPaper(paper); if (page.getOrientation() == PageFormat.LANDSCAPE) { page.setOrientation(PageFormat.REVERSE_LANDSCAPE); } else { page.setOrientation(PageFormat.LANDSCAPE); } } double xScale = getXRes() / 72.0; double yScale = getYRes() / 72.0; /* The deviceArea is the imageable area in the printer's * resolution. */ Rectangle2D deviceArea = new Rectangle2D.Double(paper.getImageableX() * xScale, paper.getImageableY() * yScale, paper.getImageableWidth() * xScale, paper.getImageableHeight() * yScale); /* Build and hold on to a uniform transform so that * we can get back to device space at the beginning * of each band. */ AffineTransform uniformTransform = new AffineTransform(); /* The scale transform is used to switch from the * device space to the user's 72 dpi space. */ AffineTransform scaleTransform = new AffineTransform(); scaleTransform.scale(xScale, yScale); /* bandwidth is multiple of 4 as the data is used in a win32 DIB and * some drivers behave badly if scanlines aren't multiples of 4 bytes. */ int bandWidth = (int) deviceArea.getWidth(); if (bandWidth % 4 != 0) { bandWidth += (4 - (bandWidth % 4)); } if (bandWidth <= 0) { throw new PrinterException("Paper's imageable width is too small."); } int deviceAreaHeight = (int)deviceArea.getHeight(); if (deviceAreaHeight <= 0) { throw new PrinterException("Paper's imageable height is too small."); } /* Figure out the number of lines that will fit into * our maximum band size. The hard coded 3 reflects the * fact that we can only create 24 bit per pixel 3 byte BGR * BufferedImages. FIX. */ int bandHeight = (int)(MAX_BAND_SIZE / bandWidth / 3); int deviceLeft = (int)Math.rint(paper.getImageableX() * xScale); int deviceTop = (int)Math.rint(paper.getImageableY() * yScale); /* The device transform is used to move the band down * the page using translates. Normally this is all it * would do, but since, when printing, the Window's * DIB format wants the last line to be first (lowest) in * memory, the deviceTransform moves the origin to the * bottom of the band and flips the origin. This way the * app prints upside down into the band which is the DIB * format. */ AffineTransform deviceTransform = new AffineTransform(); deviceTransform.translate(-deviceLeft, deviceTop); deviceTransform.translate(0, bandHeight); deviceTransform.scale(1, -1); /* Create a BufferedImage to hold the band. We set the clip * of the band to be tight around the bits so that the * application can use it to figure what part of the * page needs to be drawn. The clip is never altered in * this method, but we do translate the band's coordinate * system so that the app will see the clip moving down the * page though it s always around the same set of pixels. */ BufferedImage pBand = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR); /* Have the app draw into a PeekGraphics object so we can * learn something about the needs of the print job. */ PeekGraphics peekGraphics = createPeekGraphics(pBand.createGraphics(), this); Rectangle2D.Double pageFormatArea = new Rectangle2D.Double(page.getImageableX(), page.getImageableY(), page.getImageableWidth(), page.getImageableHeight()); peekGraphics.transform(scaleTransform); peekGraphics.translate(-getPhysicalPrintableX(paper) / xScale, -getPhysicalPrintableY(paper) / yScale); peekGraphics.transform(new AffineTransform(page.getMatrix())); initPrinterGraphics(peekGraphics, pageFormatArea); AffineTransform pgAt = peekGraphics.getTransform(); /* Update the information used to return a GraphicsConfiguration * for this printer device. It needs to be updated per page as * not all pages in a job may be the same size (different bounds) * The transform is the scaling transform as this corresponds to * the default transform for the device. The width and height are * those of the paper, not the page format, as we want to describe * the bounds of the device in its natural coordinate system of * device coordinate whereas a page format may be in a rotated context. */ setGraphicsConfigInfo(scaleTransform, paper.getWidth(), paper.getHeight()); int pageResult = painter.print(peekGraphics, origPage, pageIndex); debug_println("pageResult "+pageResult); if (pageResult == Printable.PAGE_EXISTS) { debug_println("startPage "+pageIndex); /* We need to check if the paper size is changed. * Note that it is not sufficient to ask for the pageformat * of "pageIndex-1", since PageRanges mean that pages can be * skipped. So we have to look at the actual last paper size used. */ Paper thisPaper = page.getPaper(); boolean paperChanged = previousPaper == null || thisPaper.getWidth() != previousPaper.getWidth() || thisPaper.getHeight() != previousPaper.getHeight(); previousPaper = thisPaper; startPage(page, painter, pageIndex, paperChanged); Graphics2D pathGraphics = createPathGraphics(peekGraphics, this, painter, page, pageIndex); /* If we can convert the page directly to the * underlying graphics system then we do not * need to rasterize. We also may not need to * create the 'band' if all the pages can take * this path. */ if (pathGraphics != null) { pathGraphics.transform(scaleTransform); // user (0,0) should be origin of page, not imageable area pathGraphics.translate(-getPhysicalPrintableX(paper) / xScale, -getPhysicalPrintableY(paper) / yScale); pathGraphics.transform(new AffineTransform(page.getMatrix())); initPrinterGraphics(pathGraphics, pageFormatArea); redrawList.clear(); AffineTransform initialTx = pathGraphics.getTransform(); painter.print(pathGraphics, origPage, pageIndex); for (int i=0;i 0 && bandHeight > 0)) { /* if the client has specified an imageable X or Y * which is off than the physically addressable * area of the page, then we need to adjust for that * here so that we pass only non -ve band coordinates * We also need to translate by the adjusted amount * so that printing appears in the correct place. */ int bandX = deviceLeft - deviceAddressableX; if (bandX < 0) { bandGraphics.translate(bandX/xScale,0); bandX = 0; } int bandY = deviceTop + bandTop - deviceAddressableY; if (bandY < 0) { bandGraphics.translate(0,bandY/yScale); bandY = 0; } /* Have the app's painter image into the band * and then send the band to the printer. */ painterGraphics.setDelegate((Graphics2D) bandGraphics.create()); painter.print(painterGraphics, origPage, pageIndex); painterGraphics.dispose(); printBand(data, bandX, bandY, bandWidth, bandHeight); } } clearGraphics.dispose(); bandGraphics.dispose(); } debug_println("calling endPage "+pageIndex); endPage(page, painter, pageIndex); } return pageResult; } /** * If a print job is in progress, print() has been * called but has not returned, then this signals * that the job should be cancelled and the next * chance. If there is no print job in progress then * this call does nothing. */ public void cancel() { synchronized (this) { if (performingPrinting) { userCancelled = true; } notify(); } } /** * Returns true is a print job is ongoing but will * be cancelled and the next opportunity. false is * returned otherwise. */ public boolean isCancelled() { boolean cancelled = false; synchronized (this) { cancelled = (performingPrinting && userCancelled); notify(); } return cancelled; } /** * Return the Pageable describing the pages to be printed. */ protected Pageable getPageable() { return mDocument; } /** * Examine the metrics captured by the * PeekGraphics instance and * if capable of directly converting this * print job to the printer's control language * or the native OS's graphics primitives, then * return a PathGraphics to perform * that conversion. If there is not an object * capable of the conversion then return * null. Returning null * causes the print job to be rasterized. */ protected Graphics2D createPathGraphics(PeekGraphics graphics, PrinterJob printerJob, Printable painter, PageFormat pageFormat, int pageIndex) { return null; } /** * Create and return an object that will * gather and hold metrics about the print * job. This method is passed a Graphics2D * object that can be used as a proxy for the * object gathering the print job matrics. The * method is also supplied with the instance * controlling the print job, printerJob. */ protected PeekGraphics createPeekGraphics(Graphics2D graphics, PrinterJob printerJob) { return new PeekGraphics(graphics, printerJob); } /** * Configure the passed in Graphics2D so that * is contains the defined initial settings * for a print job. These settings are: * color: black. * clip: */ // MacOSX - made protected so subclasses can reference it. protected void initPrinterGraphics(Graphics2D g, Rectangle2D clip) { g.setClip(clip); g.setPaint(Color.black); } /** * User dialogs should disable "File" buttons if this returns false. * */ public boolean checkAllowedToPrintToFile() { try { throwPrintToFile(); return true; } catch (SecurityException e) { return false; } } /** * Break this out as it may be useful when we allow API to * specify printing to a file. In that case its probably right * to throw a SecurityException if the permission is not granted */ private void throwPrintToFile() { SecurityManager security = System.getSecurityManager(); if (security != null) { if (printToFilePermission == null) { printToFilePermission = new FilePermission("<>", "read,write"); } security.checkPermission(printToFilePermission); } } /* On-screen drawString renders most control chars as the missing glyph * and have the non-zero advance of that glyph. * Exceptions are \t, \n and \r which are considered zero-width. * This is a utility method used by subclasses to remove them so we * don't have to worry about platform or font specific handling of them. */ protected String removeControlChars(String s) { char[] in_chars = s.toCharArray(); int len = in_chars.length; char[] out_chars = new char[len]; int pos = 0; for (int i = 0; i < len; i++) { char c = in_chars[i]; if (c > '\r' || c < '\t' || c == '\u000b' || c == '\u000c') { out_chars[pos++] = c; } } if (pos == len) { return s; // no need to make a new String. } else { return new String(out_chars, 0, pos); } } }