jdk/src/share/classes/sun/print/PSPathGraphics.java
author duke
Sat, 01 Dec 2007 00:00:00 +0000
changeset 2 90ce3da70b43
child 553 38a9503f374d
permissions -rw-r--r--
Initial load

/*
 * Copyright 1998-2006 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.print;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Transparency;

import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;

import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Line2D;

import java.awt.image.BufferedImage;
import sun.awt.image.ByteComponentRaster;

import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;

/**
 * This class converts paths into PostScript
 * by breaking all graphics into fills and
 * clips of paths.
 */

class PSPathGraphics extends PathGraphics {

    /**
     * For a drawing application the initial user space
     * resolution is 72dpi.
     */
    private static final int DEFAULT_USER_RES = 72;

    PSPathGraphics(Graphics2D graphics, PrinterJob printerJob,
                   Printable painter, PageFormat pageFormat, int pageIndex,
                   boolean canRedraw) {
        super(graphics, printerJob, painter, pageFormat, pageIndex, canRedraw);
    }

    /**
     * Creates a new <code>Graphics</code> object that is
     * a copy of this <code>Graphics</code> object.
     * @return     a new graphics context that is a copy of
     *                       this graphics context.
     * @since      JDK1.0
     */
    public Graphics create() {

        return new PSPathGraphics((Graphics2D) getDelegate().create(),
                                  getPrinterJob(),
                                  getPrintable(),
                                  getPageFormat(),
                                  getPageIndex(),
                                  canDoRedraws());
    }


    /**
     * Override the inherited implementation of fill
     * so that we can generate PostScript in user space
     * rather than device space.
     */
    public void fill(Shape s, Color color) {
        deviceFill(s.getPathIterator(new AffineTransform()), color);
    }

    /**
     * Draws the text given by the specified string, using this
     * graphics context's current font and color. The baseline of the
     * first character is at position (<i>x</i>,&nbsp;<i>y</i>) in this
     * graphics context's coordinate system.
     * @param       str      the string to be drawn.
     * @param       x        the <i>x</i> coordinate.
     * @param       y        the <i>y</i> coordinate.
     * @see         java.awt.Graphics#drawBytes
     * @see         java.awt.Graphics#drawChars
     * @since       JDK1.0
     */
    public void drawString(String str, int x, int y) {
        drawString(str, (float) x, (float) y);
    }

    /**
     * Renders the text specified by the specified <code>String</code>,
     * using the current <code>Font</code> and <code>Paint</code> attributes
     * in the <code>Graphics2D</code> context.
     * The baseline of the first character is at position
     * (<i>x</i>,&nbsp;<i>y</i>) in the User Space.
     * The rendering attributes applied include the <code>Clip</code>,
     * <code>Transform</code>, <code>Paint</code>, <code>Font</code> and
     * <code>Composite</code> attributes. For characters in script systems
     * such as Hebrew and Arabic, the glyphs can be rendered from right to
     * left, in which case the coordinate supplied is the location of the
     * leftmost character on the baseline.
     * @param s the <code>String</code> to be rendered
     * @param x,&nbsp;y the coordinates where the <code>String</code>
     * should be rendered
     * @see #setPaint
     * @see java.awt.Graphics#setColor
     * @see java.awt.Graphics#setFont
     * @see #setTransform
     * @see #setComposite
     * @see #setClip
     */
     public void drawString(String str, float x, float y) {
         drawString(str, x, y, getFont(), getFontRenderContext(), 0f);
     }


    protected boolean canDrawStringToWidth() {
        return true;
    }

    protected int platformFontCount(Font font, String str) {
        PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
        return psPrinterJob.platformFontCount(font,  str);
    }

    protected void drawString(String str, float x, float y,
                              Font font, FontRenderContext frc, float w) {
        if (str.length() == 0) {
            return;
        }

        /* If the Font has layout attributes we need to delegate to TextLayout.
         * TextLayout renders text as GlyphVectors. We try to print those
         * using printer fonts - ie using Postscript text operators so
         * we may be reinvoked. In that case the "!printingGlyphVector" test
         * prevents us recursing and instead sends us into the body of the
         * method where we can safely ignore layout attributes as those
         * are already handled by TextLayout.
         */
        if (font.hasLayoutAttributes() && !printingGlyphVector) {
            TextLayout layout = new TextLayout(str, font, frc);
            layout.draw(this, x, y);
            return;
        }

        Font oldFont = getFont();
        if (!oldFont.equals(font)) {
            setFont(font);
        } else {
            oldFont = null;
        }

        boolean drawnWithPS = false;

        float translateX = 0f, translateY = 0f;
        boolean fontisTransformed = getFont().isTransformed();

        if (fontisTransformed) {
            AffineTransform fontTx = getFont().getTransform();
            int transformType = fontTx.getType();
            /* TYPE_TRANSLATION is a flag bit but we can do "==" here
             * because we want to detect when its just that bit set and
             *
             */
            if (transformType == AffineTransform.TYPE_TRANSLATION) {
                translateX = (float)(fontTx.getTranslateX());
                translateY = (float)(fontTx.getTranslateY());
                if (Math.abs(translateX) < 0.00001) translateX = 0f;
                if (Math.abs(translateY) < 0.00001) translateY = 0f;
                fontisTransformed = false;
            }
        }

        boolean directToPS = !fontisTransformed;

        if (!PSPrinterJob.shapeTextProp && directToPS) {

            PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
            if (psPrinterJob.setFont(getFont())) {

                /* Set the text color.
                 * We should not be in this shape printing path
                 * if the application is drawing with non-solid
                 * colors. We should be in the raster path. Because
                 * we are here in the shape path, the cast of the
                 * paint to a Color should be fine.
                 */
                try {
                    psPrinterJob.setColor((Color)getPaint());
                } catch (ClassCastException e) {
                    if (oldFont != null) {
                        setFont(oldFont);
                    }
                    throw new IllegalArgumentException(
                                                "Expected a Color instance");
                }

                psPrinterJob.setTransform(getTransform());
                psPrinterJob.setClip(getClip());

                drawnWithPS = psPrinterJob.textOut(this, str,
                                                   x+translateX, y+translateY,
                                                   font, frc, w);
            }
        }

        /* The text could not be converted directly to PS text
         * calls so decompose the text into a shape.
         */
        if (drawnWithPS == false) {
            if (oldFont != null) {
                setFont(oldFont);
                oldFont = null;
            }
            super.drawString(str, x, y, font, frc, w);
        }

        if (oldFont != null) {
            setFont(oldFont);
        }
    }

    /**
     * The various <code>drawImage()</code> methods for
     * <code>WPathGraphics</code> are all decomposed
     * into an invocation of <code>drawImageToPlatform</code>.
     * The portion of the passed in image defined by
     * <code>srcX, srcY, srcWidth, and srcHeight</code>
     * is transformed by the supplied AffineTransform and
     * drawn using PS to the printer context.
     *
     * @param   img     The image to be drawn.
     *                  This method does nothing if <code>img</code> is null.
     * @param   xform   Used to tranform the image before drawing.
     *                  This can be null.
     * @param   bgcolor This color is drawn where the image has transparent
     *                  pixels. If this parameter is null then the
     *                  pixels already in the destination should show
     *                  through.
     * @param   srcX    With srcY this defines the upper-left corner
     *                  of the portion of the image to be drawn.
     *
     * @param   srcY    With srcX this defines the upper-left corner
     *                  of the portion of the image to be drawn.
     * @param   srcWidth    The width of the portion of the image to
     *                      be drawn.
     * @param   srcHeight   The height of the portion of the image to
     *                      be drawn.
     * @param   handlingTransparency if being recursively called to
     *                    print opaque region of transparent image
     */
    protected boolean drawImageToPlatform(Image image, AffineTransform xform,
                                          Color bgcolor,
                                          int srcX, int srcY,
                                          int srcWidth, int srcHeight,
                                          boolean handlingTransparency) {

        BufferedImage img = getBufferedImage(image);
        if (img == null) {
            return true;
        }

        PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();

        /* The full transform to be applied to the image is the
         * caller's transform concatenated on to the transform
         * from user space to device space. If the caller didn't
         * supply a transform then we just act as if they passed
         * in the identify transform.
         */
        AffineTransform fullTransform = getTransform();
        if (xform == null) {
            xform = new AffineTransform();
        }
        fullTransform.concatenate(xform);

        /* Split the full transform into a pair of
         * transforms. The first transform holds effects
         * such as rotation and shearing. The second transform
         * is setup to hold only the scaling effects.
         * These transforms are created such that a point,
         * p, in user space, when transformed by 'fullTransform'
         * lands in the same place as when it is transformed
         * by 'rotTransform' and then 'scaleTransform'.
         *
         * The entire image transformation is not in Java in order
         * to minimize the amount of memory needed in the VM. By
         * dividing the transform in two, we rotate and shear
         * the source image in its own space and only go to
         * the, usually, larger, device space when we ask
         * PostScript to perform the final scaling.
         */
        double[] fullMatrix = new double[6];
        fullTransform.getMatrix(fullMatrix);

        /* Calculate the amount of scaling in the x
         * and y directions. This scaling is computed by
         * transforming a unit vector along each axis
         * and computing the resulting magnitude.
         * The computed values 'scaleX' and 'scaleY'
         * represent the amount of scaling PS will be asked
         * to perform.
         * Clamp this to the device scale for better quality printing.
         */
        Point2D.Float unitVectorX = new Point2D.Float(1, 0);
        Point2D.Float unitVectorY = new Point2D.Float(0, 1);
        fullTransform.deltaTransform(unitVectorX, unitVectorX);
        fullTransform.deltaTransform(unitVectorY, unitVectorY);

        Point2D.Float origin = new Point2D.Float(0, 0);
        double scaleX = unitVectorX.distance(origin);
        double scaleY = unitVectorY.distance(origin);

        double devResX = psPrinterJob.getXRes();
        double devResY = psPrinterJob.getYRes();
        double devScaleX = devResX / DEFAULT_USER_RES;
        double devScaleY = devResY / DEFAULT_USER_RES;

        if (scaleX > devScaleX) scaleX = devScaleX;
        if (scaleY > devScaleY) scaleY = devScaleY;

        /* We do not need to draw anything if either scaling
         * factor is zero.
         */
        if (scaleX != 0 && scaleY != 0) {

            /* Here's the transformation we will do with Java2D,
            */
            AffineTransform rotTransform = new AffineTransform(
                                        fullMatrix[0] / scaleX,  //m00
                                        fullMatrix[1] / scaleY,  //m10
                                        fullMatrix[2] / scaleX,  //m01
                                        fullMatrix[3] / scaleY,  //m11
                                        fullMatrix[4] / scaleX,  //m02
                                        fullMatrix[5] / scaleY); //m12

            /* The scale transform is not used directly: we instead
             * directly multiply by scaleX and scaleY.
             *
             * Conceptually here is what the scaleTransform is:
             *
             * AffineTransform scaleTransform = new AffineTransform(
             *                      scaleX,                     //m00
             *                      0,                          //m10
             *                      0,                          //m01
             *                      scaleY,                     //m11
             *                      0,                          //m02
             *                      0);                         //m12
             */

            /* Convert the image source's rectangle into the rotated
             * and sheared space. Once there, we calculate a rectangle
             * that encloses the resulting shape. It is this rectangle
             * which defines the size of the BufferedImage we need to
             * create to hold the transformed image.
             */
            Rectangle2D.Float srcRect = new Rectangle2D.Float(srcX, srcY,
                                                              srcWidth,
                                                              srcHeight);

            Shape rotShape = rotTransform.createTransformedShape(srcRect);
            Rectangle2D rotBounds = rotShape.getBounds2D();

            /* add a fudge factor as some fp precision problems have
             * been observed which caused pixels to be rounded down and
             * out of the image.
             */
            rotBounds.setRect(rotBounds.getX(), rotBounds.getY(),
                              rotBounds.getWidth()+0.001,
                              rotBounds.getHeight()+0.001);

            int boundsWidth = (int) rotBounds.getWidth();
            int boundsHeight = (int) rotBounds.getHeight();

            if (boundsWidth > 0 && boundsHeight > 0) {


                /* If the image has transparent or semi-transparent
                 * pixels then we'll have the application re-render
                 * the portion of the page covered by the image.
                 * This will be done in a later call to print using the
                 * saved graphics state.
                 * However several special cases can be handled otherwise:
                 * - bitmask transparency with a solid background colour
                 * - images which have transparency color models but no
                 * transparent pixels
                 * - images with bitmask transparency and an IndexColorModel
                 * (the common transparent GIF case) can be handled by
                 * rendering just the opaque pixels.
                 */
                boolean drawOpaque = true;
                if (!handlingTransparency && hasTransparentPixels(img)) {
                    drawOpaque = false;
                    if (isBitmaskTransparency(img)) {
                        if (bgcolor == null) {
                            if (drawBitmaskImage(img, xform, bgcolor,
                                                srcX, srcY,
                                                 srcWidth, srcHeight)) {
                                // image drawn, just return.
                                return true;
                            }
                        } else if (bgcolor.getTransparency()
                                   == Transparency.OPAQUE) {
                            drawOpaque = true;
                        }
                    }
                    if (!canDoRedraws()) {
                        drawOpaque = true;
                    }
                } else {
                    // if there's no transparent pixels there's no need
                    // for a background colour. This can avoid edge artifacts
                    // in rotation cases.
                    bgcolor = null;
                }
                // if src region extends beyond the image, the "opaque" path
                // may blit b/g colour (including white) where it shoudn't.
                if ((srcX+srcWidth > img.getWidth(null) ||
                     srcY+srcHeight > img.getHeight(null))
                    && canDoRedraws()) {
                    drawOpaque = false;
                }
                if (drawOpaque == false) {

                    fullTransform.getMatrix(fullMatrix);
                    AffineTransform tx =
                        new AffineTransform(
                                            fullMatrix[0] / devScaleX,  //m00
                                            fullMatrix[1] / devScaleY,  //m10
                                            fullMatrix[2] / devScaleX,  //m01
                                            fullMatrix[3] / devScaleY,  //m11
                                            fullMatrix[4] / devScaleX,  //m02
                                            fullMatrix[5] / devScaleY); //m12

                    Rectangle2D.Float rect =
                        new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight);

                    Shape shape = fullTransform.createTransformedShape(rect);
                    // Region isn't user space because its potentially
                    // been rotated for landscape.
                    Rectangle2D region = shape.getBounds2D();

                    region.setRect(region.getX(), region.getY(),
                                   region.getWidth()+0.001,
                                   region.getHeight()+0.001);

                    // Try to limit the amount of memory used to 8Mb, so
                    // if at device resolution this exceeds a certain
                    // image size then scale down the region to fit in
                    // that memory, but never to less than 72 dpi.

                    int w = (int)region.getWidth();
                    int h = (int)region.getHeight();
                    int nbytes = w * h * 3;
                    int maxBytes = 8 * 1024 * 1024;
                    double origDpi = (devResX < devResY) ? devResX : devResY;
                    int dpi = (int)origDpi;
                    double scaleFactor = 1;

                    double maxSFX = w/(double)boundsWidth;
                    double maxSFY = h/(double)boundsHeight;
                    double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX;
                    int minDpi = (int)(dpi/maxSF);
                    if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES;

                    while (nbytes > maxBytes && dpi > minDpi) {
                        scaleFactor *= 2;
                        dpi /= 2;
                        nbytes /= 4;
                    }
                    if (dpi < minDpi) {
                        scaleFactor = (origDpi / minDpi);
                    }

                    region.setRect(region.getX()/scaleFactor,
                                   region.getY()/scaleFactor,
                                   region.getWidth()/scaleFactor,
                                   region.getHeight()/scaleFactor);

                    /*
                     * We need to have the clip as part of the saved state,
                     * either directly, or all the components that are
                     * needed to reconstitute it (image source area,
                     * image transform and current graphics transform).
                     * The clip is described in user space, so we need to
                     * save the current graphics transform anyway so just
                     * save these two.
                     */
                    psPrinterJob.saveState(getTransform(), getClip(),
                                           region, scaleFactor, scaleFactor);
                    return true;

                /* The image can be rendered directly by PS so we
                 * copy it into a BufferedImage (this takes care of
                 * ColorSpace and BufferedImageOp issues) and then
                 * send that to PS.
                 */
                } else {

                    /* Create a buffered image big enough to hold the portion
                     * of the source image being printed.
                     */
                    BufferedImage deepImage = new BufferedImage(
                                                    (int) rotBounds.getWidth(),
                                                    (int) rotBounds.getHeight(),
                                                    BufferedImage.TYPE_3BYTE_BGR);

                    /* Setup a Graphics2D on to the BufferedImage so that the
                     * source image when copied, lands within the image buffer.
                     */
                    Graphics2D imageGraphics = deepImage.createGraphics();
                    imageGraphics.clipRect(0, 0,
                                           deepImage.getWidth(),
                                           deepImage.getHeight());

                    imageGraphics.translate(-rotBounds.getX(),
                                            -rotBounds.getY());
                    imageGraphics.transform(rotTransform);

                    /* Fill the BufferedImage either with the caller supplied
                     * color, 'bgColor' or, if null, with white.
                     */
                    if (bgcolor == null) {
                        bgcolor = Color.white;
                    }

                    /* REMIND: no need to use scaling here. */
                    imageGraphics.drawImage(img,
                                            srcX, srcY,
                                            srcX + srcWidth, srcY + srcHeight,
                                            srcX, srcY,
                                            srcX + srcWidth, srcY + srcHeight,
                                            bgcolor, null);

                    /* In PSPrinterJob images are printed in device space
                     * and therefore we need to set a device space clip.
                     * FIX: this is an overly tight coupling of these
                     * two classes.
                     * The temporary clip set needs to be an intersection
                     * with the previous user clip.
                     * REMIND: two xfms may lose accuracy in clip path.
                     */
                    Shape holdClip = getClip();
                    Shape oldClip =
                        getTransform().createTransformedShape(holdClip);
                    AffineTransform sat = AffineTransform.getScaleInstance(
                                                             scaleX, scaleY);
                    Shape imgClip = sat.createTransformedShape(rotShape);
                    Area imgArea = new Area(imgClip);
                    Area oldArea = new Area(oldClip);
                    imgArea.intersect(oldArea);
                    psPrinterJob.setClip(imgArea);

                    /* Scale the bounding rectangle by the scale transform.
                     * Because the scaling transform has only x and y
                     * scaling components it is equivalent to multiply
                     * the x components of the bounding rectangle by
                     * the x scaling factor and to multiply the y components
                     * by the y scaling factor.
                     */
                    Rectangle2D.Float scaledBounds
                            = new Rectangle2D.Float(
                                    (float) (rotBounds.getX() * scaleX),
                                    (float) (rotBounds.getY() * scaleY),
                                    (float) (rotBounds.getWidth() * scaleX),
                                    (float) (rotBounds.getHeight() * scaleY));


                    /* Pull the raster data from the buffered image
                     * and pass it along to PS.
                     */
                    ByteComponentRaster tile =
                                   (ByteComponentRaster)deepImage.getRaster();

                    psPrinterJob.drawImageBGR(tile.getDataStorage(),
                                scaledBounds.x, scaledBounds.y,
                                (float)Math.rint(scaledBounds.width+0.5),
                                (float)Math.rint(scaledBounds.height+0.5),
                                0f, 0f,
                                deepImage.getWidth(), deepImage.getHeight(),
                                deepImage.getWidth(), deepImage.getHeight());

                    /* Reset the device clip to match user clip */
                    psPrinterJob.setClip(
                               getTransform().createTransformedShape(holdClip));


                    imageGraphics.dispose();
                }

            }
        }

        return true;
    }

    /** Redraw a rectanglular area using a proxy graphics
      * To do this we need to know the rectangular area to redraw and
      * the transform & clip in effect at the time of the original drawImage
      *
      */

    public void redrawRegion(Rectangle2D region, double scaleX, double scaleY,
                             Shape savedClip, AffineTransform savedTransform)

            throws PrinterException {

        PSPrinterJob psPrinterJob = (PSPrinterJob)getPrinterJob();
        Printable painter = getPrintable();
        PageFormat pageFormat = getPageFormat();
        int pageIndex = getPageIndex();

        /* Create a buffered image big enough to hold the portion
         * of the source image being printed.
         */
        BufferedImage deepImage = new BufferedImage(
                                        (int) region.getWidth(),
                                        (int) region.getHeight(),
                                        BufferedImage.TYPE_3BYTE_BGR);

        /* Get a graphics for the application to render into.
         * We initialize the buffer to white in order to
         * match the paper and then we shift the BufferedImage
         * so that it covers the area on the page where the
         * caller's Image will be drawn.
         */
        Graphics2D g = deepImage.createGraphics();
        ProxyGraphics2D proxy = new ProxyGraphics2D(g, psPrinterJob);
        proxy.setColor(Color.white);
        proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
        proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight());

        proxy.translate(-region.getX(), -region.getY());

        /* Calculate the resolution of the source image.
         */
        float sourceResX = (float)(psPrinterJob.getXRes() / scaleX);
        float sourceResY = (float)(psPrinterJob.getYRes() / scaleY);

        /* The application expects to see user space at 72 dpi.
         * so change user space from image source resolution to
         *  72 dpi.
         */
        proxy.scale(sourceResX / DEFAULT_USER_RES,
                    sourceResY / DEFAULT_USER_RES);
       proxy.translate(
            -psPrinterJob.getPhysicalPrintableX(pageFormat.getPaper())
               / psPrinterJob.getXRes() * DEFAULT_USER_RES,
            -psPrinterJob.getPhysicalPrintableY(pageFormat.getPaper())
               / psPrinterJob.getYRes() * DEFAULT_USER_RES);
       /* NB User space now has to be at 72 dpi for this calc to be correct */
        proxy.transform(new AffineTransform(getPageFormat().getMatrix()));

        proxy.setPaint(Color.black);

        painter.print(proxy, pageFormat, pageIndex);

        g.dispose();

        /* In PSPrinterJob images are printed in device space
         * and therefore we need to set a device space clip.
         */
        psPrinterJob.setClip(savedTransform.createTransformedShape(savedClip));


        /* Scale the bounding rectangle by the scale transform.
         * Because the scaling transform has only x and y
         * scaling components it is equivalent to multiply
         * the x components of the bounding rectangle by
         * the x scaling factor and to multiply the y components
         * by the y scaling factor.
         */
        Rectangle2D.Float scaledBounds
                = new Rectangle2D.Float(
                        (float) (region.getX() * scaleX),
                        (float) (region.getY() * scaleY),
                        (float) (region.getWidth() * scaleX),
                        (float) (region.getHeight() * scaleY));


        /* Pull the raster data from the buffered image
         * and pass it along to PS.
         */
        ByteComponentRaster tile = (ByteComponentRaster)deepImage.getRaster();

        psPrinterJob.drawImageBGR(tile.getDataStorage(),
                            scaledBounds.x, scaledBounds.y,
                            scaledBounds.width,
                            scaledBounds.height,
                            0f, 0f,
                            deepImage.getWidth(), deepImage.getHeight(),
                            deepImage.getWidth(), deepImage.getHeight());


    }


    /*
     * Fill the path defined by <code>pathIter</code>
     * with the specified color.
     * The path is provided in current user space.
     */
    protected void deviceFill(PathIterator pathIter, Color color) {

        PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
        psPrinterJob.deviceFill(pathIter, color, getTransform(), getClip());
    }

    /*
     * Draw the bounding rectangle using path by calling draw()
     * function and passing a rectangle shape.
     */
    protected void deviceFrameRect(int x, int y, int width, int height,
                                   Color color) {

        draw(new Rectangle2D.Float(x, y, width, height));
    }

    /*
     * Draw a line using path by calling draw() function and passing
     * a line shape.
     */
    protected void deviceDrawLine(int xBegin, int yBegin,
                                  int xEnd, int yEnd, Color color) {

        draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd));
    }

    /*
     * Fill the rectangle with the specified color by calling fill().
     */
    protected void deviceFillRect(int x, int y, int width, int height,
                                  Color color) {
        fill(new Rectangle2D.Float(x, y, width, height));
    }


    /*
     * This method should not be invoked by PSPathGraphics.
     * FIX: Rework PathGraphics so that this method is
     * not an abstract method there.
     */
    protected void deviceClip(PathIterator pathIter) {
    }

}