/*
* Copyright (c) 1998, 2012, 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;
/**
* Pageable MAX pages
*/
private static final int MAX_UNKNOWN_PAGES = 9999;
private static final int PD_ALLPAGES = 0x00000000;
private static final int PD_SELECTION = 0x00000001;
private static final int PD_PAGENUMS = 0x00000002;
private static final int PD_NOSELECTION = 0x00000004;
/**
* 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 PrinterJob
s.
* 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);
}
}
}
private PageFormat attributeToPageFormat(PrintService service,
PrintRequestAttributeSet attSet) {
PageFormat page = defaultPage();
if (service == null) {
return page;
}
OrientationRequested orient = (OrientationRequested)
attSet.get(OrientationRequested.class);
if (orient == null) {
orient = (OrientationRequested)
service.getDefaultAttributeValue(OrientationRequested.class);
}
if (orient == OrientationRequested.REVERSE_LANDSCAPE) {
page.setOrientation(PageFormat.REVERSE_LANDSCAPE);
} else if (orient == OrientationRequested.LANDSCAPE) {
page.setOrientation(PageFormat.LANDSCAPE);
} else {
page.setOrientation(PageFormat.PORTRAIT);
}
Media media = (Media)attSet.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)
attSet.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;
}
protected void updatePageAttributes(PrintService service,
PageFormat page) {
if (this.attributes == null) {
this.attributes = new HashPrintRequestAttributeSet();
}
updateAttributesWithPageFormat(service, page, this.attributes);
}
protected void updateAttributesWithPageFormat(PrintService service,
PageFormat page,
PrintRequestAttributeSet pageAttributes) {
if (service == null || page == null || pageAttributes == 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 (media != null) {
pageAttributes.add(media);
}
pageAttributes.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 {
pageAttributes.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();
}
DialogTypeSelection dlg =
(DialogTypeSelection)attributes.get(DialogTypeSelection.class);
// Check for native, note that default dialog is COMMON.
if (dlg == DialogTypeSelection.NATIVE) {
PrintService pservice = getPrintService();
PageFormat page = pageDialog(attributeToPageFormat(pservice,
attributes));
updateAttributesWithPageFormat(pservice, page, attributes);
return page;
}
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);
return attributeToPageFormat(service, attributes);
} else {
return null;
}
}
protected PageFormat getPageFormatFromAttributes() {
if (attributes == null) {
return null;
}
return attributeToPageFormat(getPrintService(), this.attributes);
}
/**
* 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) {
validateDestination(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();
}
}
}
protected void validateDestination(String dest) throws PrinterException {
if (dest == null) {
return;
}
// dest is null for Destination(new URI(""))
// because isAttributeValueSupported returns false in setAttributes
// Destination(new URI(" ")) throws URISyntaxException
File f = new File(dest);
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:"+
dest);
} 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:"+
dest);
}
}
/**
* 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;
}
private final int getSelectAttrib() {
if (attributes != null) {
SunPageSelection pages =
(SunPageSelection)attributes.get(SunPageSelection.class);
if (pages == SunPageSelection.RANGE) {
return PD_PAGENUMS;
} else if (pages == SunPageSelection.SELECTION) {
return PD_SELECTION;
} else if (pages == SunPageSelection.ALL) {
return PD_ALLPAGES;
}
}
return PD_NOSELECTION;
}
//returns 1-based index for "From" page
private final int getFromPageAttrib() {
if (attributes != null) {
PageRanges pageRangesAttr =
(PageRanges)attributes.get(PageRanges.class);
if (pageRangesAttr != null) {
int[][] range = pageRangesAttr.getMembers();
return range[0][0];
}
}
return getMinPageAttrib();
}
//returns 1-based index for "To" page
private final int getToPageAttrib() {
if (attributes != null) {
PageRanges pageRangesAttr =
(PageRanges)attributes.get(PageRanges.class);
if (pageRangesAttr != null) {
int[][] range = pageRangesAttr.getMembers();
return range[range.length-1][1];
}
}
return getMaxPageAttrib();
}
private final int getMinPageAttrib() {
if (attributes != null) {
SunMinMaxPage s =
(SunMinMaxPage)attributes.get(SunMinMaxPage.class);
if (s != null) {
return s.getMin();
}
}
return 1;
}
private final int getMaxPageAttrib() {
if (attributes != null) {
SunMinMaxPage s =
(SunMinMaxPage)attributes.get(SunMinMaxPage.class);
if (s != null) {
return s.getMax();
}
}
Pageable pageable = getPageable();
if (pageable != null) {
int numPages = pageable.getNumberOfPages();
if (numPages <= Pageable.UNKNOWN_NUMBER_OF_PAGES) {
numPages = MAX_UNKNOWN_PAGES;
}
return ((numPages == 0) ? 1 : numPages);
}
return Integer.MAX_VALUE;
}
/**
* 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);
}
}
}