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 (!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 }