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>, <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>, <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, 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 (isCompositing(getComposite())) { 427 drawOpaque = false; 428 } else if (!handlingTransparency && hasTransparentPixels(img)) { 429 drawOpaque = false; 430 if (isBitmaskTransparency(img)) { 431 if (bgcolor == null) { 432 if (drawBitmaskImage(img, xform, bgcolor, 433 srcX, srcY, 434 srcWidth, srcHeight)) { 435 // image drawn, just return. 436 return true; 437 } 438 } else if (bgcolor.getTransparency() 439 == Transparency.OPAQUE) { 440 drawOpaque = true; 441 } 442 } 443 if (!canDoRedraws()) { 444 drawOpaque = true; 445 } 446 } else { 447 // if there's no transparent pixels there's no need 448 // for a background colour. This can avoid edge artifacts 449 // in rotation cases. 450 bgcolor = null; 451 } 452 // if src region extends beyond the image, the "opaque" path 453 // may blit b/g colour (including white) where it shoudn't. 454 if ((srcX+srcWidth > img.getWidth(null) || 455 srcY+srcHeight > img.getHeight(null)) 456 && canDoRedraws()) { 457 drawOpaque = false; 458 } 459 if (drawOpaque == false) { 460 461 fullTransform.getMatrix(fullMatrix); 462 AffineTransform tx = 463 new AffineTransform( 464 fullMatrix[0] / devScaleX, //m00 465 fullMatrix[1] / devScaleY, //m10 466 fullMatrix[2] / devScaleX, //m01 467 fullMatrix[3] / devScaleY, //m11 468 fullMatrix[4] / devScaleX, //m02 469 fullMatrix[5] / devScaleY); //m12 470 471 Rectangle2D.Float rect = 472 new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight); 473 474 Shape shape = fullTransform.createTransformedShape(rect); 475 // Region isn't user space because its potentially 476 // been rotated for landscape. 477 Rectangle2D region = shape.getBounds2D(); 478 479 region.setRect(region.getX(), region.getY(), 480 region.getWidth()+0.001, 481 region.getHeight()+0.001); 482 483 // Try to limit the amount of memory used to 8Mb, so 484 // if at device resolution this exceeds a certain 485 // image size then scale down the region to fit in 486 // that memory, but never to less than 72 dpi. 487 488 int w = (int)region.getWidth(); 489 int h = (int)region.getHeight(); 490 int nbytes = w * h * 3; 491 int maxBytes = 8 * 1024 * 1024; 492 double origDpi = (devResX < devResY) ? devResX : devResY; 493 int dpi = (int)origDpi; 494 double scaleFactor = 1; 495 496 double maxSFX = w/(double)boundsWidth; 497 double maxSFY = h/(double)boundsHeight; 498 double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX; 499 int minDpi = (int)(dpi/maxSF); 500 if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES; 501 502 while (nbytes > maxBytes && dpi > minDpi) { 503 scaleFactor *= 2; 504 dpi /= 2; 505 nbytes /= 4; 506 } 507 if (dpi < minDpi) { 508 scaleFactor = (origDpi / minDpi); 509 } 510 511 region.setRect(region.getX()/scaleFactor, 512 region.getY()/scaleFactor, 513 region.getWidth()/scaleFactor, 514 region.getHeight()/scaleFactor); 515 516 /* 517 * We need to have the clip as part of the saved state, 518 * either directly, or all the components that are 519 * needed to reconstitute it (image source area, 520 * image transform and current graphics transform). 521 * The clip is described in user space, so we need to 522 * save the current graphics transform anyway so just 523 * save these two. 524 */ 525 psPrinterJob.saveState(getTransform(), getClip(), 526 region, scaleFactor, scaleFactor); 527 return true; 528 529 /* The image can be rendered directly by PS so we 530 * copy it into a BufferedImage (this takes care of 531 * ColorSpace and BufferedImageOp issues) and then 532 * send that to PS. 533 */ 534 } else { 535 536 /* Create a buffered image big enough to hold the portion 537 * of the source image being printed. 538 */ 539 BufferedImage deepImage = new BufferedImage( 540 (int) rotBounds.getWidth(), 541 (int) rotBounds.getHeight(), 542 BufferedImage.TYPE_3BYTE_BGR); 543 544 /* Setup a Graphics2D on to the BufferedImage so that the 545 * source image when copied, lands within the image buffer. 546 */ 547 Graphics2D imageGraphics = deepImage.createGraphics(); 548 imageGraphics.clipRect(0, 0, 549 deepImage.getWidth(), 550 deepImage.getHeight()); 551 552 imageGraphics.translate(-rotBounds.getX(), 553 -rotBounds.getY()); 554 imageGraphics.transform(rotTransform); 555 556 /* Fill the BufferedImage either with the caller supplied 557 * color, 'bgColor' or, if null, with white. 558 */ 559 if (bgcolor == null) { 560 bgcolor = Color.white; 561 } 562 563 /* REMIND: no need to use scaling here. */ 564 imageGraphics.drawImage(img, 565 srcX, srcY, 566 srcX + srcWidth, srcY + srcHeight, 567 srcX, srcY, 568 srcX + srcWidth, srcY + srcHeight, 569 bgcolor, null); 570 571 /* In PSPrinterJob images are printed in device space 572 * and therefore we need to set a device space clip. 573 * FIX: this is an overly tight coupling of these 574 * two classes. 575 * The temporary clip set needs to be an intersection 576 * with the previous user clip. 577 * REMIND: two xfms may lose accuracy in clip path. 578 */ 579 Shape holdClip = getClip(); 580 Shape oldClip = 581 getTransform().createTransformedShape(holdClip); 582 AffineTransform sat = AffineTransform.getScaleInstance( 583 scaleX, scaleY); 584 Shape imgClip = sat.createTransformedShape(rotShape); 585 Area imgArea = new Area(imgClip); 586 Area oldArea = new Area(oldClip); 587 imgArea.intersect(oldArea); 588 psPrinterJob.setClip(imgArea); 589 590 /* Scale the bounding rectangle by the scale transform. 591 * Because the scaling transform has only x and y 592 * scaling components it is equivalent to multiply 593 * the x components of the bounding rectangle by 594 * the x scaling factor and to multiply the y components 595 * by the y scaling factor. 596 */ 597 Rectangle2D.Float scaledBounds 598 = new Rectangle2D.Float( 599 (float) (rotBounds.getX() * scaleX), 600 (float) (rotBounds.getY() * scaleY), 601 (float) (rotBounds.getWidth() * scaleX), 602 (float) (rotBounds.getHeight() * scaleY)); 603 604 605 /* Pull the raster data from the buffered image 606 * and pass it along to PS. 607 */ 608 ByteComponentRaster tile = 609 (ByteComponentRaster)deepImage.getRaster(); 610 611 psPrinterJob.drawImageBGR(tile.getDataStorage(), 612 scaledBounds.x, scaledBounds.y, 613 (float)Math.rint(scaledBounds.width+0.5), 614 (float)Math.rint(scaledBounds.height+0.5), 615 0f, 0f, 616 deepImage.getWidth(), deepImage.getHeight(), 617 deepImage.getWidth(), deepImage.getHeight()); 618 619 /* Reset the device clip to match user clip */ 620 psPrinterJob.setClip( 621 getTransform().createTransformedShape(holdClip)); 622 623 624 imageGraphics.dispose(); 625 } 626 627 } 628 } 629 630 return true; 631 } 632 633 /** Redraw a rectanglular area using a proxy graphics 634 * To do this we need to know the rectangular area to redraw and 635 * the transform & clip in effect at the time of the original drawImage 636 * 637 */ 638 639 public void redrawRegion(Rectangle2D region, double scaleX, double scaleY, 640 Shape savedClip, AffineTransform savedTransform) 641 642 throws PrinterException { 643 644 PSPrinterJob psPrinterJob = (PSPrinterJob)getPrinterJob(); 645 Printable painter = getPrintable(); 646 PageFormat pageFormat = getPageFormat(); 647 int pageIndex = getPageIndex(); 648 649 /* Create a buffered image big enough to hold the portion 650 * of the source image being printed. 651 */ 652 BufferedImage deepImage = new BufferedImage( 653 (int) region.getWidth(), 654 (int) region.getHeight(), 655 BufferedImage.TYPE_3BYTE_BGR); 656 657 /* Get a graphics for the application to render into. 658 * We initialize the buffer to white in order to 659 * match the paper and then we shift the BufferedImage 660 * so that it covers the area on the page where the 661 * caller's Image will be drawn. 662 */ 663 Graphics2D g = deepImage.createGraphics(); 664 ProxyGraphics2D proxy = new ProxyGraphics2D(g, psPrinterJob); 665 proxy.setColor(Color.white); 666 proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight()); 667 proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight()); 668 669 proxy.translate(-region.getX(), -region.getY()); 670 671 /* Calculate the resolution of the source image. 672 */ 673 float sourceResX = (float)(psPrinterJob.getXRes() / scaleX); 674 float sourceResY = (float)(psPrinterJob.getYRes() / scaleY); 675 676 /* The application expects to see user space at 72 dpi. 677 * so change user space from image source resolution to 678 * 72 dpi. 679 */ 680 proxy.scale(sourceResX / DEFAULT_USER_RES, 681 sourceResY / DEFAULT_USER_RES); 682 proxy.translate( 683 -psPrinterJob.getPhysicalPrintableX(pageFormat.getPaper()) 684 / psPrinterJob.getXRes() * DEFAULT_USER_RES, 685 -psPrinterJob.getPhysicalPrintableY(pageFormat.getPaper()) 686 / psPrinterJob.getYRes() * DEFAULT_USER_RES); 687 /* NB User space now has to be at 72 dpi for this calc to be correct */ 688 proxy.transform(new AffineTransform(getPageFormat().getMatrix())); 689 690 proxy.setPaint(Color.black); 691 692 painter.print(proxy, pageFormat, pageIndex); 693 694 g.dispose(); 695 696 /* In PSPrinterJob images are printed in device space 697 * and therefore we need to set a device space clip. 698 */ 699 psPrinterJob.setClip(savedTransform.createTransformedShape(savedClip)); 700 701 702 /* Scale the bounding rectangle by the scale transform. 703 * Because the scaling transform has only x and y 704 * scaling components it is equivalent to multiply 705 * the x components of the bounding rectangle by 706 * the x scaling factor and to multiply the y components 707 * by the y scaling factor. 708 */ 709 Rectangle2D.Float scaledBounds 710 = new Rectangle2D.Float( 711 (float) (region.getX() * scaleX), 712 (float) (region.getY() * scaleY), 713 (float) (region.getWidth() * scaleX), 714 (float) (region.getHeight() * scaleY)); 715 716 717 /* Pull the raster data from the buffered image 718 * and pass it along to PS. 719 */ 720 ByteComponentRaster tile = (ByteComponentRaster)deepImage.getRaster(); 721 722 psPrinterJob.drawImageBGR(tile.getDataStorage(), 723 scaledBounds.x, scaledBounds.y, 724 scaledBounds.width, 725 scaledBounds.height, 726 0f, 0f, 727 deepImage.getWidth(), deepImage.getHeight(), 728 deepImage.getWidth(), deepImage.getHeight()); 729 730 731 } 732 733 734 /* 735 * Fill the path defined by {@code pathIter} 736 * with the specified color. 737 * The path is provided in current user space. 738 */ 739 protected void deviceFill(PathIterator pathIter, Color color) { 740 741 PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob(); 742 psPrinterJob.deviceFill(pathIter, color, getTransform(), getClip()); 743 } 744 745 /* 746 * Draw the bounding rectangle using path by calling draw() 747 * function and passing a rectangle shape. 748 */ 749 protected void deviceFrameRect(int x, int y, int width, int height, 750 Color color) { 751 752 draw(new Rectangle2D.Float(x, y, width, height)); 753 } 754 755 /* 756 * Draw a line using path by calling draw() function and passing 757 * a line shape. 758 */ 759 protected void deviceDrawLine(int xBegin, int yBegin, 760 int xEnd, int yEnd, Color color) { 761 762 draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd)); 763 } 764 765 /* 766 * Fill the rectangle with the specified color by calling fill(). 767 */ 768 protected void deviceFillRect(int x, int y, int width, int height, 769 Color color) { 770 fill(new Rectangle2D.Float(x, y, width, height)); 771 } 772 773 774 /* 775 * This method should not be invoked by PSPathGraphics. 776 * FIX: Rework PathGraphics so that this method is 777 * not an abstract method there. 778 */ 779 protected void deviceClip(PathIterator pathIter) { 780 } 781 782 }