1 /*
   2  * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import java.awt.Color;
  29 import java.awt.Font;
  30 import java.awt.Graphics;
  31 import java.awt.Graphics2D;
  32 import java.awt.Image;
  33 import java.awt.Shape;
  34 import java.awt.Transparency;
  35 
  36 import java.awt.font.FontRenderContext;
  37 import java.awt.font.TextLayout;
  38 
  39 import java.awt.geom.AffineTransform;
  40 import java.awt.geom.Area;
  41 import java.awt.geom.PathIterator;
  42 import java.awt.geom.Point2D;
  43 import java.awt.geom.Rectangle2D;
  44 import java.awt.geom.Line2D;
  45 
  46 import java.awt.image.BufferedImage;
  47 import sun.awt.image.ByteComponentRaster;
  48 
  49 import java.awt.print.PageFormat;
  50 import java.awt.print.Printable;
  51 import java.awt.print.PrinterException;
  52 import java.awt.print.PrinterJob;
  53 
  54 /**
  55  * This class converts paths into PostScript
  56  * by breaking all graphics into fills and
  57  * clips of paths.
  58  */
  59 
  60 class PSPathGraphics extends PathGraphics {
  61 
  62     /**
  63      * For a drawing application the initial user space
  64      * resolution is 72dpi.
  65      */
  66     private static final int DEFAULT_USER_RES = 72;
  67 
  68     PSPathGraphics(Graphics2D graphics, PrinterJob printerJob,
  69                    Printable painter, PageFormat pageFormat, int pageIndex,
  70                    boolean canRedraw) {
  71         super(graphics, printerJob, painter, pageFormat, pageIndex, canRedraw);
  72     }
  73 
  74     /**
  75      * Creates a new {@code Graphics} object that is
  76      * a copy of this {@code Graphics} object.
  77      * @return     a new graphics context that is a copy of
  78      *                       this graphics context.
  79      * @since      1.0
  80      */
  81     public Graphics create() {
  82 
  83         return new PSPathGraphics((Graphics2D) getDelegate().create(),
  84                                   getPrinterJob(),
  85                                   getPrintable(),
  86                                   getPageFormat(),
  87                                   getPageIndex(),
  88                                   canDoRedraws());
  89     }
  90 
  91 
  92     /**
  93      * Override the inherited implementation of fill
  94      * so that we can generate PostScript in user space
  95      * rather than device space.
  96      */
  97     public void fill(Shape s, Color color) {
  98         deviceFill(s.getPathIterator(new AffineTransform()), color);
  99     }
 100 
 101     /**
 102      * Draws the text given by the specified string, using this
 103      * graphics context's current font and color. The baseline of the
 104      * first character is at position (<i>x</i>,&nbsp;<i>y</i>) in this
 105      * graphics context's coordinate system.
 106      * @param       str      the string to be drawn.
 107      * @param       x        the <i>x</i> coordinate.
 108      * @param       y        the <i>y</i> coordinate.
 109      * @see         java.awt.Graphics#drawBytes
 110      * @see         java.awt.Graphics#drawChars
 111      * @since       1.0
 112      */
 113     public void drawString(String str, int x, int y) {
 114         drawString(str, (float) x, (float) y);
 115     }
 116 
 117     /**
 118      * Renders the text specified by the specified {@code String},
 119      * using the current {@code Font} and {@code Paint} attributes
 120      * in the {@code Graphics2D} context.
 121      * The baseline of the first character is at position
 122      * (<i>x</i>,&nbsp;<i>y</i>) in the User Space.
 123      * The rendering attributes applied include the {@code Clip},
 124      * {@code Transform}, {@code Paint}, {@code Font} and
 125      * {@code Composite} attributes. For characters in script systems
 126      * such as Hebrew and Arabic, the glyphs can be rendered from right to
 127      * left, in which case the coordinate supplied is the location of the
 128      * leftmost character on the baseline.
 129      * @param str the {@code String} to be rendered
 130      * @param x,&nbsp;y the coordinates where the {@code String}
 131      * should be rendered
 132      * @see #setPaint
 133      * @see java.awt.Graphics#setColor
 134      * @see java.awt.Graphics#setFont
 135      * @see #setTransform
 136      * @see #setComposite
 137      * @see #setClip
 138      */
 139      public void drawString(String str, float x, float y) {
 140          drawString(str, x, y, getFont(), getFontRenderContext(), 0f);
 141      }
 142 
 143 
 144     protected boolean canDrawStringToWidth() {
 145         return true;
 146     }
 147 
 148     protected int platformFontCount(Font font, String str) {
 149         PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
 150         return psPrinterJob.platformFontCount(font,  str);
 151     }
 152 
 153     protected void drawString(String str, float x, float y,
 154                               Font font, FontRenderContext frc, float w) {
 155         if (str.length() == 0) {
 156             return;
 157         }
 158 
 159         /* If the Font has layout attributes we need to delegate to TextLayout.
 160          * TextLayout renders text as GlyphVectors. We try to print those
 161          * using printer fonts - ie using Postscript text operators so
 162          * we may be reinvoked. In that case the "!printingGlyphVector" test
 163          * prevents us recursing and instead sends us into the body of the
 164          * method where we can safely ignore layout attributes as those
 165          * are already handled by TextLayout.
 166          */
 167         if (font.hasLayoutAttributes() && !printingGlyphVector) {
 168             TextLayout layout = new TextLayout(str, font, frc);
 169             layout.draw(this, x, y);
 170             return;
 171         }
 172 
 173         Font oldFont = getFont();
 174         if (!oldFont.equals(font)) {
 175             setFont(font);
 176         } else {
 177             oldFont = null;
 178         }
 179 
 180         boolean drawnWithPS = false;
 181 
 182         float translateX = 0f, translateY = 0f;
 183         boolean fontisTransformed = getFont().isTransformed();
 184 
 185         if (fontisTransformed) {
 186             AffineTransform fontTx = getFont().getTransform();
 187             int transformType = fontTx.getType();
 188             /* TYPE_TRANSLATION is a flag bit but we can do "==" here
 189              * because we want to detect when its just that bit set and
 190              *
 191              */
 192             if (transformType == AffineTransform.TYPE_TRANSLATION) {
 193                 translateX = (float)(fontTx.getTranslateX());
 194                 translateY = (float)(fontTx.getTranslateY());
 195                 if (Math.abs(translateX) < 0.00001) translateX = 0f;
 196                 if (Math.abs(translateY) < 0.00001) translateY = 0f;
 197                 fontisTransformed = false;
 198             }
 199         }
 200 
 201         boolean directToPS = !fontisTransformed;
 202 
 203         if (!PSPrinterJob.shapeTextProp && directToPS) {
 204 
 205             PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
 206             if (psPrinterJob.setFont(getFont())) {
 207 
 208                 /* Set the text color.
 209                  * We should not be in this shape printing path
 210                  * if the application is drawing with non-solid
 211                  * colors. We should be in the raster path. Because
 212                  * we are here in the shape path, the cast of the
 213                  * paint to a Color should be fine.
 214                  */
 215                 try {
 216                     psPrinterJob.setColor((Color)getPaint());
 217                 } catch (ClassCastException e) {
 218                     if (oldFont != null) {
 219                         setFont(oldFont);
 220                     }
 221                     throw new IllegalArgumentException(
 222                                                 "Expected a Color instance");
 223                 }
 224 
 225                 psPrinterJob.setTransform(getTransform());
 226                 psPrinterJob.setClip(getClip());
 227 
 228                 drawnWithPS = psPrinterJob.textOut(this, str,
 229                                                    x+translateX, y+translateY,
 230                                                    font, frc, w);
 231             }
 232         }
 233 
 234         /* The text could not be converted directly to PS text
 235          * calls so decompose the text into a shape.
 236          */
 237         if (drawnWithPS == false) {
 238             if (oldFont != null) {
 239                 setFont(oldFont);
 240                 oldFont = null;
 241             }
 242             super.drawString(str, x, y, font, frc, w);
 243         }
 244 
 245         if (oldFont != null) {
 246             setFont(oldFont);
 247         }
 248     }
 249 
 250     /**
 251      * The various {@code drawImage()} methods for
 252      * {@code WPathGraphics} are all decomposed
 253      * into an invocation of {@code drawImageToPlatform}.
 254      * The portion of the passed in image defined by
 255      * {@code srcX, srcY, srcWidth, and srcHeight}
 256      * is transformed by the supplied AffineTransform and
 257      * drawn using PS to the printer context.
 258      *
 259      * @param   image   The image to be drawn.
 260      *                  This method does nothing if {@code img} is null.
 261      * @param   xform   Used to transform the image before drawing.
 262      *                  This can be null.
 263      * @param   bgcolor This color is drawn where the image has transparent
 264      *                  pixels. If this parameter is null then the
 265      *                  pixels already in the destination should show
 266      *                  through.
 267      * @param   srcX    With srcY this defines the upper-left corner
 268      *                  of the portion of the image to be drawn.
 269      *
 270      * @param   srcY    With srcX this defines the upper-left corner
 271      *                  of the portion of the image to be drawn.
 272      * @param   srcWidth    The width of the portion of the image to
 273      *                      be drawn.
 274      * @param   srcHeight   The height of the portion of the image to
 275      *                      be drawn.
 276      * @param   handlingTransparency if being recursively called to
 277      *                    print opaque region of transparent image
 278      */
 279     protected boolean drawImageToPlatform(Image image, AffineTransform xform,
 280                                           Color bgcolor,
 281                                           int srcX, int srcY,
 282                                           int srcWidth, int srcHeight,
 283                                           boolean handlingTransparency) {
 284 
 285         BufferedImage img = getBufferedImage(image);
 286         if (img == null) {
 287             return true;
 288         }
 289 
 290         PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
 291 
 292         /* The full transform to be applied to the image is the
 293          * caller's transform concatenated on to the transform
 294          * from user space to device space. If the caller didn't
 295          * supply a transform then we just act as if they passed
 296          * in the identify transform.
 297          */
 298         AffineTransform fullTransform = getTransform();
 299         if (xform == null) {
 300             xform = new AffineTransform();
 301         }
 302         fullTransform.concatenate(xform);
 303 
 304         /* Split the full transform into a pair of
 305          * transforms. The first transform holds effects
 306          * such as rotation and shearing. The second transform
 307          * is setup to hold only the scaling effects.
 308          * These transforms are created such that a point,
 309          * p, in user space, when transformed by 'fullTransform'
 310          * lands in the same place as when it is transformed
 311          * by 'rotTransform' and then 'scaleTransform'.
 312          *
 313          * The entire image transformation is not in Java in order
 314          * to minimize the amount of memory needed in the VM. By
 315          * dividing the transform in two, we rotate and shear
 316          * the source image in its own space and only go to
 317          * the, usually, larger, device space when we ask
 318          * PostScript to perform the final scaling.
 319          */
 320         double[] fullMatrix = new double[6];
 321         fullTransform.getMatrix(fullMatrix);
 322 
 323         /* Calculate the amount of scaling in the x
 324          * and y directions. This scaling is computed by
 325          * transforming a unit vector along each axis
 326          * and computing the resulting magnitude.
 327          * The computed values 'scaleX' and 'scaleY'
 328          * represent the amount of scaling PS will be asked
 329          * to perform.
 330          * Clamp this to the device scale for better quality printing.
 331          */
 332         Point2D.Float unitVectorX = new Point2D.Float(1, 0);
 333         Point2D.Float unitVectorY = new Point2D.Float(0, 1);
 334         fullTransform.deltaTransform(unitVectorX, unitVectorX);
 335         fullTransform.deltaTransform(unitVectorY, unitVectorY);
 336 
 337         Point2D.Float origin = new Point2D.Float(0, 0);
 338         double scaleX = unitVectorX.distance(origin);
 339         double scaleY = unitVectorY.distance(origin);
 340 
 341         double devResX = psPrinterJob.getXRes();
 342         double devResY = psPrinterJob.getYRes();
 343         double devScaleX = devResX / DEFAULT_USER_RES;
 344         double devScaleY = devResY / DEFAULT_USER_RES;
 345 
 346         /* check if rotated or sheared */
 347         int transformType = fullTransform.getType();
 348         boolean clampScale = ((transformType &
 349                                (AffineTransform.TYPE_GENERAL_ROTATION |
 350                                 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
 351         if (clampScale) {
 352             if (scaleX > devScaleX) scaleX = devScaleX;
 353             if (scaleY > devScaleY) scaleY = devScaleY;
 354         }
 355 
 356         /* We do not need to draw anything if either scaling
 357          * factor is zero.
 358          */
 359         if (scaleX != 0 && scaleY != 0) {
 360 
 361             /* Here's the transformation we will do with Java2D,
 362             */
 363             AffineTransform rotTransform = new AffineTransform(
 364                                         fullMatrix[0] / scaleX,  //m00
 365                                         fullMatrix[1] / scaleY,  //m10
 366                                         fullMatrix[2] / scaleX,  //m01
 367                                         fullMatrix[3] / scaleY,  //m11
 368                                         fullMatrix[4] / scaleX,  //m02
 369                                         fullMatrix[5] / scaleY); //m12
 370 
 371             /* The scale transform is not used directly: we instead
 372              * directly multiply by scaleX and scaleY.
 373              *
 374              * Conceptually here is what the scaleTransform is:
 375              *
 376              * AffineTransform scaleTransform = new AffineTransform(
 377              *                      scaleX,                     //m00
 378              *                      0,                          //m10
 379              *                      0,                          //m01
 380              *                      scaleY,                     //m11
 381              *                      0,                          //m02
 382              *                      0);                         //m12
 383              */
 384 
 385             /* Convert the image source's rectangle into the rotated
 386              * and sheared space. Once there, we calculate a rectangle
 387              * that encloses the resulting shape. It is this rectangle
 388              * which defines the size of the BufferedImage we need to
 389              * create to hold the transformed image.
 390              */
 391             Rectangle2D.Float srcRect = new Rectangle2D.Float(srcX, srcY,
 392                                                               srcWidth,
 393                                                               srcHeight);
 394 
 395             Shape rotShape = rotTransform.createTransformedShape(srcRect);
 396             Rectangle2D rotBounds = rotShape.getBounds2D();
 397 
 398             /* add a fudge factor as some fp precision problems have
 399              * been observed which caused pixels to be rounded down and
 400              * out of the image.
 401              */
 402             rotBounds.setRect(rotBounds.getX(), rotBounds.getY(),
 403                               rotBounds.getWidth()+0.001,
 404                               rotBounds.getHeight()+0.001);
 405 
 406             int boundsWidth = (int) rotBounds.getWidth();
 407             int boundsHeight = (int) rotBounds.getHeight();
 408 
 409             if (boundsWidth > 0 && boundsHeight > 0) {
 410 
 411 
 412                 /* If the image has transparent or semi-transparent
 413                  * pixels then we'll have the application re-render
 414                  * the portion of the page covered by the image.
 415                  * This will be done in a later call to print using the
 416                  * saved graphics state.
 417                  * However several special cases can be handled otherwise:
 418                  * - bitmask transparency with a solid background colour
 419                  * - images which have transparency color models but no
 420                  * transparent pixels
 421                  * - images with bitmask transparency and an IndexColorModel
 422                  * (the common transparent GIF case) can be handled by
 423                  * rendering just the opaque pixels.
 424                  */
 425                 boolean drawOpaque = true;
 426                 if (!handlingTransparency && hasTransparentPixels(img)) {
 427                     drawOpaque = false;
 428                     if (isBitmaskTransparency(img)) {
 429                         if (bgcolor == null) {
 430                             if (drawBitmaskImage(img, xform, bgcolor,
 431                                                 srcX, srcY,
 432                                                  srcWidth, srcHeight)) {
 433                                 // image drawn, just return.
 434                                 return true;
 435                             }
 436                         } else if (bgcolor.getTransparency()
 437                                    == Transparency.OPAQUE) {
 438                             drawOpaque = true;
 439                         }
 440                     }
 441                     if (!canDoRedraws()) {
 442                         drawOpaque = true;
 443                     }
 444                 } else {
 445                     // if there's no transparent pixels there's no need
 446                     // for a background colour. This can avoid edge artifacts
 447                     // in rotation cases.
 448                     bgcolor = null;
 449                 }
 450                 // if src region extends beyond the image, the "opaque" path
 451                 // may blit b/g colour (including white) where it shoudn't.
 452                 if ((srcX+srcWidth > img.getWidth(null) ||
 453                      srcY+srcHeight > img.getHeight(null))
 454                     && canDoRedraws()) {
 455                     drawOpaque = false;
 456                 }
 457                 if (drawOpaque == false) {
 458 
 459                     fullTransform.getMatrix(fullMatrix);
 460                     AffineTransform tx =
 461                         new AffineTransform(
 462                                             fullMatrix[0] / devScaleX,  //m00
 463                                             fullMatrix[1] / devScaleY,  //m10
 464                                             fullMatrix[2] / devScaleX,  //m01
 465                                             fullMatrix[3] / devScaleY,  //m11
 466                                             fullMatrix[4] / devScaleX,  //m02
 467                                             fullMatrix[5] / devScaleY); //m12
 468 
 469                     Rectangle2D.Float rect =
 470                         new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight);
 471 
 472                     Shape shape = fullTransform.createTransformedShape(rect);
 473                     // Region isn't user space because its potentially
 474                     // been rotated for landscape.
 475                     Rectangle2D region = shape.getBounds2D();
 476 
 477                     region.setRect(region.getX(), region.getY(),
 478                                    region.getWidth()+0.001,
 479                                    region.getHeight()+0.001);
 480 
 481                     // Try to limit the amount of memory used to 8Mb, so
 482                     // if at device resolution this exceeds a certain
 483                     // image size then scale down the region to fit in
 484                     // that memory, but never to less than 72 dpi.
 485 
 486                     int w = (int)region.getWidth();
 487                     int h = (int)region.getHeight();
 488                     int nbytes = w * h * 3;
 489                     int maxBytes = 8 * 1024 * 1024;
 490                     double origDpi = (devResX < devResY) ? devResX : devResY;
 491                     int dpi = (int)origDpi;
 492                     double scaleFactor = 1;
 493 
 494                     double maxSFX = w/(double)boundsWidth;
 495                     double maxSFY = h/(double)boundsHeight;
 496                     double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX;
 497                     int minDpi = (int)(dpi/maxSF);
 498                     if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES;
 499 
 500                     while (nbytes > maxBytes && dpi > minDpi) {
 501                         scaleFactor *= 2;
 502                         dpi /= 2;
 503                         nbytes /= 4;
 504                     }
 505                     if (dpi < minDpi) {
 506                         scaleFactor = (origDpi / minDpi);
 507                     }
 508 
 509                     region.setRect(region.getX()/scaleFactor,
 510                                    region.getY()/scaleFactor,
 511                                    region.getWidth()/scaleFactor,
 512                                    region.getHeight()/scaleFactor);
 513 
 514                     /*
 515                      * We need to have the clip as part of the saved state,
 516                      * either directly, or all the components that are
 517                      * needed to reconstitute it (image source area,
 518                      * image transform and current graphics transform).
 519                      * The clip is described in user space, so we need to
 520                      * save the current graphics transform anyway so just
 521                      * save these two.
 522                      */
 523                     psPrinterJob.saveState(getTransform(), getClip(),
 524                                            region, scaleFactor, scaleFactor);
 525                     return true;
 526 
 527                 /* The image can be rendered directly by PS so we
 528                  * copy it into a BufferedImage (this takes care of
 529                  * ColorSpace and BufferedImageOp issues) and then
 530                  * send that to PS.
 531                  */
 532                 } else {
 533 
 534                     /* Create a buffered image big enough to hold the portion
 535                      * of the source image being printed.
 536                      */
 537                     BufferedImage deepImage = new BufferedImage(
 538                                                     (int) rotBounds.getWidth(),
 539                                                     (int) rotBounds.getHeight(),
 540                                                     BufferedImage.TYPE_3BYTE_BGR);
 541 
 542                     /* Setup a Graphics2D on to the BufferedImage so that the
 543                      * source image when copied, lands within the image buffer.
 544                      */
 545                     Graphics2D imageGraphics = deepImage.createGraphics();
 546                     imageGraphics.clipRect(0, 0,
 547                                            deepImage.getWidth(),
 548                                            deepImage.getHeight());
 549 
 550                     imageGraphics.translate(-rotBounds.getX(),
 551                                             -rotBounds.getY());
 552                     imageGraphics.transform(rotTransform);
 553 
 554                     /* Fill the BufferedImage either with the caller supplied
 555                      * color, 'bgColor' or, if null, with white.
 556                      */
 557                     if (bgcolor == null) {
 558                         bgcolor = Color.white;
 559                     }
 560 
 561                     /* REMIND: no need to use scaling here. */
 562                     imageGraphics.drawImage(img,
 563                                             srcX, srcY,
 564                                             srcX + srcWidth, srcY + srcHeight,
 565                                             srcX, srcY,
 566                                             srcX + srcWidth, srcY + srcHeight,
 567                                             bgcolor, null);
 568 
 569                     /* In PSPrinterJob images are printed in device space
 570                      * and therefore we need to set a device space clip.
 571                      * FIX: this is an overly tight coupling of these
 572                      * two classes.
 573                      * The temporary clip set needs to be an intersection
 574                      * with the previous user clip.
 575                      * REMIND: two xfms may lose accuracy in clip path.
 576                      */
 577                     Shape holdClip = getClip();
 578                     Shape oldClip =
 579                         getTransform().createTransformedShape(holdClip);
 580                     AffineTransform sat = AffineTransform.getScaleInstance(
 581                                                              scaleX, scaleY);
 582                     Shape imgClip = sat.createTransformedShape(rotShape);
 583                     Area imgArea = new Area(imgClip);
 584                     Area oldArea = new Area(oldClip);
 585                     imgArea.intersect(oldArea);
 586                     psPrinterJob.setClip(imgArea);
 587 
 588                     /* Scale the bounding rectangle by the scale transform.
 589                      * Because the scaling transform has only x and y
 590                      * scaling components it is equivalent to multiply
 591                      * the x components of the bounding rectangle by
 592                      * the x scaling factor and to multiply the y components
 593                      * by the y scaling factor.
 594                      */
 595                     Rectangle2D.Float scaledBounds
 596                             = new Rectangle2D.Float(
 597                                     (float) (rotBounds.getX() * scaleX),
 598                                     (float) (rotBounds.getY() * scaleY),
 599                                     (float) (rotBounds.getWidth() * scaleX),
 600                                     (float) (rotBounds.getHeight() * scaleY));
 601 
 602 
 603                     /* Pull the raster data from the buffered image
 604                      * and pass it along to PS.
 605                      */
 606                     ByteComponentRaster tile =
 607                                    (ByteComponentRaster)deepImage.getRaster();
 608 
 609                     psPrinterJob.drawImageBGR(tile.getDataStorage(),
 610                                 scaledBounds.x, scaledBounds.y,
 611                                 (float)Math.rint(scaledBounds.width+0.5),
 612                                 (float)Math.rint(scaledBounds.height+0.5),
 613                                 0f, 0f,
 614                                 deepImage.getWidth(), deepImage.getHeight(),
 615                                 deepImage.getWidth(), deepImage.getHeight());
 616 
 617                     /* Reset the device clip to match user clip */
 618                     psPrinterJob.setClip(
 619                                getTransform().createTransformedShape(holdClip));
 620 
 621 
 622                     imageGraphics.dispose();
 623                 }
 624 
 625             }
 626         }
 627 
 628         return true;
 629     }
 630 
 631     /** Redraw a rectanglular area using a proxy graphics
 632       * To do this we need to know the rectangular area to redraw and
 633       * the transform & clip in effect at the time of the original drawImage
 634       *
 635       */
 636 
 637     public void redrawRegion(Rectangle2D region, double scaleX, double scaleY,
 638                              Shape savedClip, AffineTransform savedTransform)
 639 
 640             throws PrinterException {
 641 
 642         PSPrinterJob psPrinterJob = (PSPrinterJob)getPrinterJob();
 643         Printable painter = getPrintable();
 644         PageFormat pageFormat = getPageFormat();
 645         int pageIndex = getPageIndex();
 646 
 647         /* Create a buffered image big enough to hold the portion
 648          * of the source image being printed.
 649          */
 650         BufferedImage deepImage = new BufferedImage(
 651                                         (int) region.getWidth(),
 652                                         (int) region.getHeight(),
 653                                         BufferedImage.TYPE_3BYTE_BGR);
 654 
 655         /* Get a graphics for the application to render into.
 656          * We initialize the buffer to white in order to
 657          * match the paper and then we shift the BufferedImage
 658          * so that it covers the area on the page where the
 659          * caller's Image will be drawn.
 660          */
 661         Graphics2D g = deepImage.createGraphics();
 662         ProxyGraphics2D proxy = new ProxyGraphics2D(g, psPrinterJob);
 663         proxy.setColor(Color.white);
 664         proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
 665         proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
 666 
 667         proxy.translate(-region.getX(), -region.getY());
 668 
 669         /* Calculate the resolution of the source image.
 670          */
 671         float sourceResX = (float)(psPrinterJob.getXRes() / scaleX);
 672         float sourceResY = (float)(psPrinterJob.getYRes() / scaleY);
 673 
 674         /* The application expects to see user space at 72 dpi.
 675          * so change user space from image source resolution to
 676          *  72 dpi.
 677          */
 678         proxy.scale(sourceResX / DEFAULT_USER_RES,
 679                     sourceResY / DEFAULT_USER_RES);
 680        proxy.translate(
 681             -psPrinterJob.getPhysicalPrintableX(pageFormat.getPaper())
 682                / psPrinterJob.getXRes() * DEFAULT_USER_RES,
 683             -psPrinterJob.getPhysicalPrintableY(pageFormat.getPaper())
 684                / psPrinterJob.getYRes() * DEFAULT_USER_RES);
 685        /* NB User space now has to be at 72 dpi for this calc to be correct */
 686         proxy.transform(new AffineTransform(getPageFormat().getMatrix()));
 687 
 688         proxy.setPaint(Color.black);
 689 
 690         painter.print(proxy, pageFormat, pageIndex);
 691 
 692         g.dispose();
 693 
 694         /* In PSPrinterJob images are printed in device space
 695          * and therefore we need to set a device space clip.
 696          */
 697         psPrinterJob.setClip(savedTransform.createTransformedShape(savedClip));
 698 
 699 
 700         /* Scale the bounding rectangle by the scale transform.
 701          * Because the scaling transform has only x and y
 702          * scaling components it is equivalent to multiply
 703          * the x components of the bounding rectangle by
 704          * the x scaling factor and to multiply the y components
 705          * by the y scaling factor.
 706          */
 707         Rectangle2D.Float scaledBounds
 708                 = new Rectangle2D.Float(
 709                         (float) (region.getX() * scaleX),
 710                         (float) (region.getY() * scaleY),
 711                         (float) (region.getWidth() * scaleX),
 712                         (float) (region.getHeight() * scaleY));
 713 
 714 
 715         /* Pull the raster data from the buffered image
 716          * and pass it along to PS.
 717          */
 718         ByteComponentRaster tile = (ByteComponentRaster)deepImage.getRaster();
 719 
 720         psPrinterJob.drawImageBGR(tile.getDataStorage(),
 721                             scaledBounds.x, scaledBounds.y,
 722                             scaledBounds.width,
 723                             scaledBounds.height,
 724                             0f, 0f,
 725                             deepImage.getWidth(), deepImage.getHeight(),
 726                             deepImage.getWidth(), deepImage.getHeight());
 727 
 728 
 729     }
 730 
 731 
 732     /*
 733      * Fill the path defined by {@code pathIter}
 734      * with the specified color.
 735      * The path is provided in current user space.
 736      */
 737     protected void deviceFill(PathIterator pathIter, Color color) {
 738 
 739         PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob();
 740         psPrinterJob.deviceFill(pathIter, color, getTransform(), getClip());
 741     }
 742 
 743     /*
 744      * Draw the bounding rectangle using path by calling draw()
 745      * function and passing a rectangle shape.
 746      */
 747     protected void deviceFrameRect(int x, int y, int width, int height,
 748                                    Color color) {
 749 
 750         draw(new Rectangle2D.Float(x, y, width, height));
 751     }
 752 
 753     /*
 754      * Draw a line using path by calling draw() function and passing
 755      * a line shape.
 756      */
 757     protected void deviceDrawLine(int xBegin, int yBegin,
 758                                   int xEnd, int yEnd, Color color) {
 759 
 760         draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd));
 761     }
 762 
 763     /*
 764      * Fill the rectangle with the specified color by calling fill().
 765      */
 766     protected void deviceFillRect(int x, int y, int width, int height,
 767                                   Color color) {
 768         fill(new Rectangle2D.Float(x, y, width, height));
 769     }
 770 
 771 
 772     /*
 773      * This method should not be invoked by PSPathGraphics.
 774      * FIX: Rework PathGraphics so that this method is
 775      * not an abstract method there.
 776      */
 777     protected void deviceClip(PathIterator pathIter) {
 778     }
 779 
 780 }