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<redrawList.size();i++) {
GraphicsState gstate = (GraphicsState)redrawList.get(i);
pathGraphics.setTransform(initialTx);
((PathGraphics)pathGraphics).redrawRegion(
gstate.region,
gstate.sx,
gstate.sy,
gstate.theClip,
gstate.theTransform);
}
/* This is the banded-raster printing loop.
* It should be moved into its own method.
*/
} else {
BufferedImage band = cachedBand;
if (cachedBand == null ||
bandWidth != cachedBandWidth ||
bandHeight != cachedBandHeight) {
band = new BufferedImage(bandWidth, bandHeight,
BufferedImage.TYPE_3BYTE_BGR);
cachedBand = band;
cachedBandWidth = bandWidth;
cachedBandHeight = bandHeight;
}
Graphics2D bandGraphics = band.createGraphics();
Rectangle2D.Double clipArea =
new Rectangle2D.Double(0, 0, bandWidth, bandHeight);
initPrinterGraphics(bandGraphics, clipArea);
ProxyGraphics2D painterGraphics =
new ProxyGraphics2D(bandGraphics, this);
Graphics2D clearGraphics = band.createGraphics();
clearGraphics.setColor(Color.white);
/* We need the actual bits of the BufferedImage to send to
* the native Window's code. 'data' points to the actual
* pixels. Right now these are in ARGB format with 8 bits
* per component. We need to use a monochrome BufferedImage
* for monochrome printers when this is supported by
* BufferedImage. FIX
*/
ByteInterleavedRaster tile = (ByteInterleavedRaster)band.getRaster();
byte[] data = tile.getDataStorage();
/* Loop over the page moving our band down the page,
* calling the app to render the band, and then send the band
* to the printer.
*/
int deviceBottom = deviceTop + deviceAreaHeight;
/* device's printable x,y is really addressable origin
* we address relative to media origin so when we print a
* band we need to adjust for the different methods of
* addressing it.
*/
int deviceAddressableX = (int)getPhysicalPrintableX(paper);
int deviceAddressableY = (int)getPhysicalPrintableY(paper);
for (int bandTop = 0; bandTop <= deviceAreaHeight;
bandTop += bandHeight)
{
/* Put the band back into device space and
* erase the contents of the band.
*/
clearGraphics.fillRect(0, 0, bandWidth, bandHeight);
/* Put the band into the correct location on the
* page. Once the band is moved we translate the
* device transform so that the band will move down
* the page on the next iteration of the loop.
*/
bandGraphics.setTransform(uniformTransform);
bandGraphics.transform(deviceTransform);
deviceTransform.translate(0, -bandHeight);
/* Switch the band from device space to user,
* 72 dpi, space.
*/
bandGraphics.transform(scaleTransform);
bandGraphics.transform(new AffineTransform(page.getMatrix()));
Rectangle clip = bandGraphics.getClipBounds();
clip = pgAt.createTransformedShape(clip).getBounds();
if ((clip == null) || peekGraphics.hitsDrawingArea(clip) &&
(bandWidth > 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);
}
}