1 /* 2 * Copyright (c) 1998, 2014, 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.awt.windows; 27 28 import java.awt.BasicStroke; 29 import java.awt.Color; 30 import java.awt.Font; 31 import java.awt.Graphics; 32 import java.awt.Graphics2D; 33 import java.awt.Image; 34 import java.awt.Shape; 35 import java.awt.Stroke; 36 import java.awt.Transparency; 37 38 import java.awt.font.FontRenderContext; 39 import java.awt.font.GlyphVector; 40 import java.awt.font.TextLayout; 41 42 import java.awt.geom.AffineTransform; 43 import java.awt.geom.NoninvertibleTransformException; 44 import java.awt.geom.PathIterator; 45 import java.awt.geom.Point2D; 46 import java.awt.geom.Rectangle2D; 47 import java.awt.geom.Line2D; 48 49 import java.awt.image.BufferedImage; 50 import java.awt.image.ColorModel; 51 import java.awt.image.DataBuffer; 52 import java.awt.image.IndexColorModel; 53 import java.awt.image.WritableRaster; 54 import java.awt.image.ComponentSampleModel; 55 import java.awt.image.MultiPixelPackedSampleModel; 56 import java.awt.image.SampleModel; 57 58 import sun.awt.image.ByteComponentRaster; 59 import sun.awt.image.BytePackedRaster; 60 import java.awt.print.PageFormat; 61 import java.awt.print.Printable; 62 import java.awt.print.PrinterException; 63 import java.awt.print.PrinterJob; 64 65 import java.util.Arrays; 66 67 import sun.font.CharToGlyphMapper; 68 import sun.font.CompositeFont; 69 import sun.font.Font2D; 70 import sun.font.FontUtilities; 71 import sun.font.PhysicalFont; 72 import sun.font.TrueTypeFont; 73 74 import sun.print.PathGraphics; 75 import sun.print.ProxyGraphics2D; 76 77 final class WPathGraphics extends PathGraphics { 78 79 /** 80 * For a drawing application the initial user space 81 * resolution is 72dpi. 82 */ 83 private static final int DEFAULT_USER_RES = 72; 84 85 private static final float MIN_DEVICE_LINEWIDTH = 1.2f; 86 private static final float MAX_THINLINE_INCHES = 0.014f; 87 88 /* Note that preferGDITextLayout implies useGDITextLayout. 89 * "prefer" is used to override cases where would otherwise 90 * choose not to use it. Note that non-layout factors may 91 * still mean that GDI cannot be used. 92 */ 93 private static boolean useGDITextLayout = true; 94 private static boolean preferGDITextLayout = false; 95 static { 96 String textLayoutStr = 97 java.security.AccessController.doPrivileged( 98 new sun.security.action.GetPropertyAction( 99 "sun.java2d.print.enableGDITextLayout")); 100 101 if (textLayoutStr != null) { 102 useGDITextLayout = Boolean.getBoolean(textLayoutStr); 103 if (!useGDITextLayout) { 104 if (textLayoutStr.equalsIgnoreCase("prefer")) { 105 useGDITextLayout = true; 106 preferGDITextLayout = true; 107 } 108 } 109 } 110 } 111 112 WPathGraphics(Graphics2D graphics, PrinterJob printerJob, 113 Printable painter, PageFormat pageFormat, int pageIndex, 114 boolean canRedraw) { 115 super(graphics, printerJob, painter, pageFormat, pageIndex, canRedraw); 116 } 117 118 /** 119 * Creates a new {@code Graphics} object that is 120 * a copy of this {@code Graphics} object. 121 * @return a new graphics context that is a copy of 122 * this graphics context. 123 * @since 1.0 124 */ 125 @Override 126 public Graphics create() { 127 128 return new WPathGraphics((Graphics2D) getDelegate().create(), 129 getPrinterJob(), 130 getPrintable(), 131 getPageFormat(), 132 getPageIndex(), 133 canDoRedraws()); 134 } 135 136 /** 137 * Strokes the outline of a Shape using the settings of the current 138 * graphics state. The rendering attributes applied include the 139 * clip, transform, paint or color, composite and stroke attributes. 140 * @param s The shape to be drawn. 141 * @see #setStroke 142 * @see #setPaint 143 * @see java.awt.Graphics#setColor 144 * @see #transform 145 * @see #setTransform 146 * @see #clip 147 * @see #setClip 148 * @see #setComposite 149 */ 150 @Override 151 public void draw(Shape s) { 152 153 Stroke stroke = getStroke(); 154 155 /* If the line being drawn is thinner than can be 156 * rendered, then change the line width, stroke 157 * the shape, and then set the line width back. 158 * We can only do this for BasicStroke's. 159 */ 160 if (stroke instanceof BasicStroke) { 161 BasicStroke lineStroke; 162 BasicStroke minLineStroke = null; 163 float deviceLineWidth; 164 float lineWidth; 165 AffineTransform deviceTransform; 166 Point2D.Float penSize; 167 168 /* Get the requested line width in user space. 169 */ 170 lineStroke = (BasicStroke) stroke; 171 lineWidth = lineStroke.getLineWidth(); 172 penSize = new Point2D.Float(lineWidth, lineWidth); 173 174 /* Compute the line width in device coordinates. 175 * Work on a point in case there is asymetric scaling 176 * between user and device space. 177 * Take the absolute value in case there is negative 178 * scaling in effect. 179 */ 180 deviceTransform = getTransform(); 181 deviceTransform.deltaTransform(penSize, penSize); 182 deviceLineWidth = Math.min(Math.abs(penSize.x), 183 Math.abs(penSize.y)); 184 185 /* If the requested line is too thin then map our 186 * minimum line width back to user space and set 187 * a new BasicStroke. 188 */ 189 if (deviceLineWidth < MIN_DEVICE_LINEWIDTH) { 190 191 Point2D.Float minPenSize = new Point2D.Float( 192 MIN_DEVICE_LINEWIDTH, 193 MIN_DEVICE_LINEWIDTH); 194 195 try { 196 AffineTransform inverse; 197 float minLineWidth; 198 199 /* Convert the minimum line width from device 200 * space to user space. 201 */ 202 inverse = deviceTransform.createInverse(); 203 inverse.deltaTransform(minPenSize, minPenSize); 204 205 minLineWidth = Math.max(Math.abs(minPenSize.x), 206 Math.abs(minPenSize.y)); 207 208 /* Use all of the parameters from the current 209 * stroke but change the line width to our 210 * calculated minimum. 211 */ 212 minLineStroke = new BasicStroke(minLineWidth, 213 lineStroke.getEndCap(), 214 lineStroke.getLineJoin(), 215 lineStroke.getMiterLimit(), 216 lineStroke.getDashArray(), 217 lineStroke.getDashPhase()); 218 setStroke(minLineStroke); 219 220 } catch (NoninvertibleTransformException e) { 221 /* If we can't invert the matrix there is something 222 * very wrong so don't worry about the minor matter 223 * of a minimum line width. 224 */ 225 } 226 } 227 228 super.draw(s); 229 230 /* If we changed the stroke, put back the old 231 * stroke in order to maintain a minimum line 232 * width. 233 */ 234 if (minLineStroke != null) { 235 setStroke(lineStroke); 236 } 237 238 /* The stroke in effect was not a BasicStroke so we 239 * will not try to enforce a minimum line width. 240 */ 241 } else { 242 super.draw(s); 243 } 244 } 245 246 /** 247 * Draws the text given by the specified string, using this 248 * graphics context's current font and color. The baseline of the 249 * first character is at position (<i>x</i>, <i>y</i>) in this 250 * graphics context's coordinate system. 251 * @param str the string to be drawn. 252 * @param x the <i>x</i> coordinate. 253 * @param y the <i>y</i> coordinate. 254 * @see java.awt.Graphics#drawBytes 255 * @see java.awt.Graphics#drawChars 256 * @since 1.0 257 */ 258 @Override 259 public void drawString(String str, int x, int y) { 260 drawString(str, (float) x, (float) y); 261 } 262 263 @Override 264 public void drawString(String str, float x, float y) { 265 drawString(str, x, y, getFont(), getFontRenderContext(), 0f); 266 } 267 268 /* A return value of 0 would mean font not available to GDI, or the 269 * it can't be used for this string. 270 * A return of 1 means it is suitable, including for composites. 271 * We check that the transform in effect is doable with GDI, and that 272 * this is a composite font AWT can handle, or a physical font GDI 273 * can handle directly. Its possible that some strings may ultimately 274 * fail the more stringent tests in drawString but this is rare and 275 * also that method will always succeed, as if the font isn't available 276 * it will use outlines via a superclass call. Also it is only called for 277 * the default render context (as canDrawStringToWidth() will return 278 * false. That is why it ignores the frc and width arguments. 279 */ 280 @Override 281 protected int platformFontCount(Font font, String str) { 282 283 AffineTransform deviceTransform = getTransform(); 284 AffineTransform fontTransform = new AffineTransform(deviceTransform); 285 fontTransform.concatenate(getFont().getTransform()); 286 int transformType = fontTransform.getType(); 287 288 /* Test if GDI can handle the transform */ 289 boolean directToGDI = ((transformType != 290 AffineTransform.TYPE_GENERAL_TRANSFORM) 291 && ((transformType & AffineTransform.TYPE_FLIP) 292 == 0)); 293 294 if (!directToGDI) { 295 return 0; 296 } 297 298 /* Since all windows fonts are available, and the JRE fonts 299 * are also registered. Only the Font.createFont() case is presently 300 * unknown to GDI. Those can be registered too, although that 301 * code does not exist yet, it can be added too, so we should not 302 * fail that case. Just do a quick check whether its a TrueTypeFont 303 * - ie not a Type1 font etc, and let drawString() resolve the rest. 304 */ 305 Font2D font2D = FontUtilities.getFont2D(font); 306 if (font2D instanceof CompositeFont || 307 font2D instanceof TrueTypeFont) { 308 return 1; 309 } else { 310 return 0; 311 } 312 } 313 314 private static boolean isXP() { 315 String osVersion = System.getProperty("os.version"); 316 if (osVersion != null) { 317 Float version = Float.valueOf(osVersion); 318 return (version.floatValue() >= 5.1f); 319 } else { 320 return false; 321 } 322 } 323 324 /* In case GDI doesn't handle shaping or BIDI consistently with 325 * 2D's TextLayout, we can detect these cases and redelegate up to 326 * be drawn via TextLayout, which in is rendered as runs of 327 * GlyphVectors, to which we can assign positions for each glyph. 328 */ 329 private boolean strNeedsTextLayout(String str, Font font) { 330 char[] chars = str.toCharArray(); 331 boolean isComplex = FontUtilities.isComplexText(chars, 0, chars.length); 332 if (!isComplex) { 333 return false; 334 } else if (!useGDITextLayout) { 335 return true; 336 } else { 337 if (preferGDITextLayout || 338 (isXP() && FontUtilities.textLayoutIsCompatible(font))) { 339 return false; 340 } else { 341 return true; 342 } 343 } 344 } 345 346 private int getAngle(Point2D.Double pt) { 347 /* Get the rotation in 1/10'ths degree (as needed by Windows) 348 * so that GDI can draw the text rotated. 349 * This calculation is only valid for a uniform scale, no shearing. 350 */ 351 double angle = Math.toDegrees(Math.atan2(pt.y, pt.x)); 352 if (angle < 0.0) { 353 angle+= 360.0; 354 } 355 /* Windows specifies the rotation anti-clockwise from the x-axis 356 * of the device, 2D specifies +ve rotation towards the y-axis 357 * Since the 2D y-axis runs from top-to-bottom, windows angle of 358 * rotation here is opposite than 2D's, so the rotation needed 359 * needs to be recalculated in the opposite direction. 360 */ 361 if (angle != 0.0) { 362 angle = 360.0 - angle; 363 } 364 return (int)Math.round(angle * 10.0); 365 } 366 367 private float getAwScale(double scaleFactorX, double scaleFactorY) { 368 369 float awScale = (float)(scaleFactorX/scaleFactorY); 370 /* don't let rounding errors be interpreted as non-uniform scale */ 371 if (awScale > 0.999f && awScale < 1.001f) { 372 awScale = 1.0f; 373 } 374 return awScale; 375 } 376 377 /** 378 * Renders the text specified by the specified {@code String}, 379 * using the current {@code Font} and {@code Paint} attributes 380 * in the {@code Graphics2D} context. 381 * The baseline of the first character is at position 382 * (<i>x</i>, <i>y</i>) in the User Space. 383 * The rendering attributes applied include the {@code Clip}, 384 * {@code Transform}, {@code Paint}, {@code Font} and 385 * {@code Composite} attributes. For characters in script systems 386 * such as Hebrew and Arabic, the glyphs can be rendered from right to 387 * left, in which case the coordinate supplied is the location of the 388 * leftmost character on the baseline. 389 * @param str the {@code String} to be rendered 390 * @param x, y the coordinates where the {@code String} 391 * should be rendered 392 * @see #setPaint 393 * @see java.awt.Graphics#setColor 394 * @see java.awt.Graphics#setFont 395 * @see #setTransform 396 * @see #setComposite 397 * @see #setClip 398 */ 399 @Override 400 public void drawString(String str, float x, float y, 401 Font font, FontRenderContext frc, float targetW) { 402 if (str.length() == 0) { 403 return; 404 } 405 406 if (WPrinterJob.shapeTextProp) { 407 super.drawString(str, x, y, font, frc, targetW); 408 return; 409 } 410 411 /* If the Font has layout attributes we need to delegate to TextLayout. 412 * TextLayout renders text as GlyphVectors. We try to print those 413 * using printer fonts - ie using Postscript text operators so 414 * we may be reinvoked. In that case the "!printingGlyphVector" test 415 * prevents us recursing and instead sends us into the body of the 416 * method where we can safely ignore layout attributes as those 417 * are already handled by TextLayout. 418 * Similarly if layout is needed based on the text, then we 419 * delegate to TextLayout if possible, or failing that we delegate 420 * upwards to filled shapes. 421 */ 422 boolean layoutNeeded = strNeedsTextLayout(str, font); 423 if ((font.hasLayoutAttributes() || layoutNeeded) 424 && !printingGlyphVector) { 425 TextLayout layout = new TextLayout(str, font, frc); 426 layout.draw(this, x, y); 427 return; 428 } else if (layoutNeeded) { 429 super.drawString(str, x, y, font, frc, targetW); 430 return; 431 } 432 433 AffineTransform deviceTransform = getTransform(); 434 AffineTransform fontTransform = new AffineTransform(deviceTransform); 435 fontTransform.concatenate(font.getTransform()); 436 int transformType = fontTransform.getType(); 437 438 /* Use GDI for the text if the graphics transform is something 439 * for which we can obtain a suitable GDI font. 440 * A flip or shearing transform on the graphics or a transform 441 * on the font force us to decompose the text into a shape. 442 */ 443 boolean directToGDI = ((transformType != 444 AffineTransform.TYPE_GENERAL_TRANSFORM) 445 && ((transformType & AffineTransform.TYPE_FLIP) 446 == 0)); 447 448 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 449 try { 450 wPrinterJob.setTextColor((Color)getPaint()); 451 } catch (ClassCastException e) { // peek should detect such paints. 452 directToGDI = false; 453 } 454 455 if (!directToGDI) { 456 super.drawString(str, x, y, font, frc, targetW); 457 return; 458 } 459 460 /* Now we have checked everything is OK to go through GDI as text 461 * with the exception of testing GDI can find and use the font. That 462 * is handled in the textOut() call. 463 */ 464 465 /* Compute the starting position of the string in 466 * device space. 467 */ 468 Point2D.Float userpos = new Point2D.Float(x, y); 469 Point2D.Float devpos = new Point2D.Float(); 470 471 /* Already have the translate from the deviceTransform, 472 * but the font may have a translation component too. 473 */ 474 if (font.isTransformed()) { 475 AffineTransform fontTx = font.getTransform(); 476 float translateX = (float)(fontTx.getTranslateX()); 477 float translateY = (float)(fontTx.getTranslateY()); 478 if (Math.abs(translateX) < 0.00001) translateX = 0f; 479 if (Math.abs(translateY) < 0.00001) translateY = 0f; 480 userpos.x += translateX; userpos.y += translateY; 481 } 482 deviceTransform.transform(userpos, devpos); 483 484 if (getClip() != null) { 485 deviceClip(getClip().getPathIterator(deviceTransform)); 486 } 487 488 /* Get the font size in device coordinates. 489 * The size needed is the font height scaled to device space. 490 * Although we have already tested that there is no shear, 491 * there may be a non-uniform scale, so the width of the font 492 * does not scale equally with the height. That is handled 493 * by specifying an 'average width' scale to GDI. 494 */ 495 float fontSize = font.getSize2D(); 496 497 double devResX = wPrinterJob.getXRes(); 498 double devResY = wPrinterJob.getYRes(); 499 500 double fontDevScaleY = devResY / DEFAULT_USER_RES; 501 502 int orient = getPageFormat().getOrientation(); 503 if (orient == PageFormat.LANDSCAPE || 504 orient == PageFormat.REVERSE_LANDSCAPE) 505 { 506 double tmp = devResX; 507 devResX = devResY; 508 devResY = tmp; 509 } 510 511 double devScaleX = devResX / DEFAULT_USER_RES; 512 double devScaleY = devResY / DEFAULT_USER_RES; 513 fontTransform.scale(1.0/devScaleX, 1.0/devScaleY); 514 515 Point2D.Double pty = new Point2D.Double(0.0, 1.0); 516 fontTransform.deltaTransform(pty, pty); 517 double scaleFactorY = Math.sqrt(pty.x*pty.x+pty.y*pty.y); 518 float scaledFontSizeY = (float)(fontSize * scaleFactorY * fontDevScaleY); 519 520 Point2D.Double ptx = new Point2D.Double(1.0, 0.0); 521 fontTransform.deltaTransform(ptx, ptx); 522 double scaleFactorX = Math.sqrt(ptx.x*ptx.x+ptx.y*ptx.y); 523 524 float awScale = getAwScale(scaleFactorX, scaleFactorY); 525 int iangle = getAngle(ptx); 526 527 ptx = new Point2D.Double(1.0, 0.0); 528 deviceTransform.deltaTransform(ptx, ptx); 529 double advanceScaleX = Math.sqrt(ptx.x*ptx.x+ptx.y*ptx.y); 530 pty = new Point2D.Double(0.0, 1.0); 531 deviceTransform.deltaTransform(pty, pty); 532 double advanceScaleY = Math.sqrt(pty.x*pty.x+pty.y*pty.y); 533 534 Font2D font2D = FontUtilities.getFont2D(font); 535 if (font2D instanceof TrueTypeFont) { 536 textOut(str, font, (TrueTypeFont)font2D, frc, 537 scaledFontSizeY, iangle, awScale, 538 advanceScaleX, advanceScaleY, 539 x, y, devpos.x, devpos.y, targetW); 540 } else if (font2D instanceof CompositeFont) { 541 /* Composite fonts are made up of multiple fonts and each 542 * substring that uses a particular component font needs to 543 * be separately sent to GDI. 544 * This works for standard composite fonts, alternate ones, 545 * Fonts that are a physical font backed by a standard composite, 546 * and with fallback fonts. 547 */ 548 CompositeFont compFont = (CompositeFont)font2D; 549 float userx = x, usery = y; 550 float devx = devpos.x, devy = devpos.y; 551 char[] chars = str.toCharArray(); 552 int len = chars.length; 553 int[] glyphs = new int[len]; 554 compFont.getMapper().charsToGlyphs(len, chars, glyphs); 555 556 int startChar = 0, endChar = 0, slot = 0; 557 while (endChar < len) { 558 559 startChar = endChar; 560 slot = glyphs[startChar] >>> 24; 561 562 while (endChar < len && ((glyphs[endChar] >>> 24) == slot)) { 563 endChar++; 564 } 565 String substr = new String(chars, startChar,endChar-startChar); 566 PhysicalFont slotFont = compFont.getSlotFont(slot); 567 textOut(substr, font, slotFont, frc, 568 scaledFontSizeY, iangle, awScale, 569 advanceScaleX, advanceScaleY, 570 userx, usery, devx, devy, 0f); 571 Rectangle2D bds = font.getStringBounds(substr, frc); 572 float xAdvance = (float)bds.getWidth(); 573 userx += xAdvance; 574 userpos.x += xAdvance; 575 deviceTransform.transform(userpos, devpos); 576 devx = devpos.x; 577 devy = devpos.y; 578 } 579 } else { 580 super.drawString(str, x, y, font, frc, targetW); 581 } 582 } 583 584 /** return true if the Graphics instance can directly print 585 * this glyphvector 586 */ 587 @Override 588 protected boolean printGlyphVector(GlyphVector gv, float x, float y) { 589 /* We don't want to try to handle per-glyph transforms. GDI can't 590 * handle per-glyph rotations, etc. There's no way to express it 591 * in a single call, so just bail for this uncommon case. 592 */ 593 if ((gv.getLayoutFlags() & GlyphVector.FLAG_HAS_TRANSFORMS) != 0) { 594 return false; 595 } 596 597 if (gv.getNumGlyphs() == 0) { 598 return true; // nothing to do. 599 } 600 601 AffineTransform deviceTransform = getTransform(); 602 AffineTransform fontTransform = new AffineTransform(deviceTransform); 603 Font font = gv.getFont(); 604 fontTransform.concatenate(font.getTransform()); 605 int transformType = fontTransform.getType(); 606 607 /* Use GDI for the text if the graphics transform is something 608 * for which we can obtain a suitable GDI font. 609 * A flip or shearing transform on the graphics or a transform 610 * on the font force us to decompose the text into a shape. 611 */ 612 boolean directToGDI = 613 ((transformType != AffineTransform.TYPE_GENERAL_TRANSFORM) && 614 ((transformType & AffineTransform.TYPE_FLIP) == 0)); 615 616 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 617 try { 618 wPrinterJob.setTextColor((Color)getPaint()); 619 } catch (ClassCastException e) { // peek should detect such paints. 620 directToGDI = false; 621 } 622 623 if (WPrinterJob.shapeTextProp || !directToGDI) { 624 return false; 625 } 626 /* Compute the starting position of the string in 627 * device space. 628 */ 629 Point2D.Float userpos = new Point2D.Float(x, y); 630 /* Add the position of the first glyph - its not always 0,0 */ 631 Point2D g0pos = gv.getGlyphPosition(0); 632 userpos.x += (float)g0pos.getX(); 633 userpos.y += (float)g0pos.getY(); 634 Point2D.Float devpos = new Point2D.Float(); 635 636 /* Already have the translate from the deviceTransform, 637 * but the font may have a translation component too. 638 */ 639 if (font.isTransformed()) { 640 AffineTransform fontTx = font.getTransform(); 641 float translateX = (float)(fontTx.getTranslateX()); 642 float translateY = (float)(fontTx.getTranslateY()); 643 if (Math.abs(translateX) < 0.00001) translateX = 0f; 644 if (Math.abs(translateY) < 0.00001) translateY = 0f; 645 userpos.x += translateX; userpos.y += translateY; 646 } 647 deviceTransform.transform(userpos, devpos); 648 649 if (getClip() != null) { 650 deviceClip(getClip().getPathIterator(deviceTransform)); 651 } 652 653 /* Get the font size in device coordinates. 654 * The size needed is the font height scaled to device space. 655 * Although we have already tested that there is no shear, 656 * there may be a non-uniform scale, so the width of the font 657 * does not scale equally with the height. That is handled 658 * by specifying an 'average width' scale to GDI. 659 */ 660 float fontSize = font.getSize2D(); 661 662 double devResX = wPrinterJob.getXRes(); 663 double devResY = wPrinterJob.getYRes(); 664 665 double fontDevScaleY = devResY / DEFAULT_USER_RES; 666 667 int orient = getPageFormat().getOrientation(); 668 if (orient == PageFormat.LANDSCAPE || 669 orient == PageFormat.REVERSE_LANDSCAPE) 670 { 671 double tmp = devResX; 672 devResX = devResY; 673 devResY = tmp; 674 } 675 676 double devScaleX = devResX / DEFAULT_USER_RES; 677 double devScaleY = devResY / DEFAULT_USER_RES; 678 fontTransform.scale(1.0/devScaleX, 1.0/devScaleY); 679 680 Point2D.Double pty = new Point2D.Double(0.0, 1.0); 681 fontTransform.deltaTransform(pty, pty); 682 double scaleFactorY = Math.sqrt(pty.x*pty.x+pty.y*pty.y); 683 float scaledFontSizeY = (float)(fontSize * scaleFactorY * fontDevScaleY); 684 685 Point2D.Double ptx = new Point2D.Double(1.0, 0.0); 686 fontTransform.deltaTransform(ptx, ptx); 687 double scaleFactorX = Math.sqrt(ptx.x*ptx.x+ptx.y*ptx.y); 688 689 float awScale = getAwScale(scaleFactorX, scaleFactorY); 690 int iangle = getAngle(ptx); 691 692 ptx = new Point2D.Double(1.0, 0.0); 693 deviceTransform.deltaTransform(ptx, ptx); 694 double advanceScaleX = Math.sqrt(ptx.x*ptx.x+ptx.y*ptx.y); 695 pty = new Point2D.Double(0.0, 1.0); 696 deviceTransform.deltaTransform(pty, pty); 697 double advanceScaleY = Math.sqrt(pty.x*pty.x+pty.y*pty.y); 698 699 int numGlyphs = gv.getNumGlyphs(); 700 int[] glyphCodes = gv.getGlyphCodes(0, numGlyphs, null); 701 float[] glyphPos = gv.getGlyphPositions(0, numGlyphs, null); 702 703 /* layout replaces glyphs which have been combined away 704 * with 0xfffe or 0xffff. These are supposed to be invisible 705 * and we need to handle this here as GDI will interpret it 706 * as a missing glyph. We'll do it here by compacting the 707 * glyph codes array, but we have to do it in conjunction with 708 * compacting the positions/advances arrays too AND updating 709 * the number of glyphs .. 710 * Note that since the slot number for composites is in the 711 * significant byte we need to mask out that for comparison of 712 * the invisible glyph. 713 */ 714 int invisibleGlyphCnt = 0; 715 for (int gc=0; gc<numGlyphs; gc++) { 716 if ((glyphCodes[gc] & 0xffff) >= 717 CharToGlyphMapper.INVISIBLE_GLYPHS) { 718 invisibleGlyphCnt++; 719 } 720 } 721 if (invisibleGlyphCnt > 0) { 722 int visibleGlyphCnt = numGlyphs - invisibleGlyphCnt; 723 int[] visibleGlyphCodes = new int[visibleGlyphCnt]; 724 float[] visiblePositions = new float[visibleGlyphCnt*2]; 725 int index = 0; 726 for (int i=0; i<numGlyphs; i++) { 727 if ((glyphCodes[i] & 0xffff) 728 < CharToGlyphMapper.INVISIBLE_GLYPHS) { 729 visibleGlyphCodes[index] = glyphCodes[i]; 730 visiblePositions[index*2] = glyphPos[i*2]; 731 visiblePositions[index*2+1] = glyphPos[i*2+1]; 732 index++; 733 } 734 } 735 numGlyphs = visibleGlyphCnt; 736 glyphCodes = visibleGlyphCodes; 737 glyphPos = visiblePositions; 738 } 739 740 /* To get GDI to rotate glyphs we need to specify the angle 741 * of rotation to GDI when creating the HFONT. This implicitly 742 * also rotates the baseline, and this adjusts the X & Y advances 743 * of the glyphs accordingly. 744 * When we specify the advances, they are in device space, so 745 * we don't want any further interpretation applied by GDI, but 746 * since as noted the advances are interpreted in the HFONT's 747 * coordinate space, our advances would be rotated again. 748 * We don't have any way to tell GDI to rotate only the glyphs and 749 * not the advances, so we need to account for this in the advances 750 * we supply, by supplying unrotated advances. 751 * Note that "iangle" is in the opposite direction to 2D's normal 752 * direction of rotation, so this rotation inverts the 753 * rotation element of the deviceTransform. 754 */ 755 AffineTransform advanceTransform = 756 AffineTransform.getScaleInstance(advanceScaleX, advanceScaleY); 757 float[] glyphAdvPos = new float[glyphPos.length]; 758 759 advanceTransform.transform(glyphPos, 0, //source 760 glyphAdvPos, 0, //destination 761 glyphPos.length/2); //num points 762 763 Font2D font2D = FontUtilities.getFont2D(font); 764 if (font2D instanceof TrueTypeFont) { 765 String family = font2D.getFamilyName(null); 766 int style = font.getStyle() | font2D.getStyle(); 767 if (!wPrinterJob.setFont(family, scaledFontSizeY, style, 768 iangle, awScale)) { 769 return false; 770 } 771 wPrinterJob.glyphsOut(glyphCodes, devpos.x, devpos.y, glyphAdvPos); 772 773 } else if (font2D instanceof CompositeFont) { 774 /* Composite fonts are made up of multiple fonts and each 775 * substring that uses a particular component font needs to 776 * be separately sent to GDI. 777 * This works for standard composite fonts, alternate ones, 778 * Fonts that are a physical font backed by a standard composite, 779 * and with fallback fonts. 780 */ 781 CompositeFont compFont = (CompositeFont)font2D; 782 float userx = x, usery = y; 783 float devx = devpos.x, devy = devpos.y; 784 785 int start = 0, end = 0, slot = 0; 786 while (end < numGlyphs) { 787 788 start = end; 789 slot = glyphCodes[start] >>> 24; 790 791 while (end < numGlyphs && ((glyphCodes[end] >>> 24) == slot)) { 792 end++; 793 } 794 /* If we can't get the font, bail to outlines. 795 * But we should always be able to get all fonts for 796 * Composites, so this is unlikely, so any overstriking 797 * if only one slot is unavailable is not worth worrying 798 * about. 799 */ 800 PhysicalFont slotFont = compFont.getSlotFont(slot); 801 if (!(slotFont instanceof TrueTypeFont)) { 802 return false; 803 } 804 String family = slotFont.getFamilyName(null); 805 int style = font.getStyle() | slotFont.getStyle(); 806 if (!wPrinterJob.setFont(family, scaledFontSizeY, style, 807 iangle, awScale)) { 808 return false; 809 } 810 811 int[] glyphs = Arrays.copyOfRange(glyphCodes, start, end); 812 float[] posns = Arrays.copyOfRange(glyphAdvPos, 813 start*2, end*2); 814 if (start != 0) { 815 Point2D.Float p = 816 new Point2D.Float(x+glyphPos[start*2], 817 y+glyphPos[start*2+1]); 818 deviceTransform.transform(p, p); 819 devx = p.x; 820 devy = p.y; 821 } 822 wPrinterJob.glyphsOut(glyphs, devx, devy, posns); 823 } 824 } else { 825 return false; 826 } 827 return true; 828 } 829 830 private void textOut(String str, 831 Font font, PhysicalFont font2D, 832 FontRenderContext frc, 833 float deviceSize, int rotation, float awScale, 834 double scaleFactorX, double scaleFactorY, 835 float userx, float usery, 836 float devx, float devy, float targetW) { 837 838 String family = font2D.getFamilyName(null); 839 int style = font.getStyle() | font2D.getStyle(); 840 WPrinterJob wPrinterJob = (WPrinterJob)getPrinterJob(); 841 boolean setFont = wPrinterJob.setFont(family, deviceSize, style, 842 rotation, awScale); 843 if (!setFont) { 844 super.drawString(str, userx, usery, font, frc, targetW); 845 return; 846 } 847 848 float[] glyphPos = null; 849 if (!okGDIMetrics(str, font, frc, scaleFactorX)) { 850 /* If there is a 1:1 char->glyph mapping then char positions 851 * are the same as glyph positions and we can tell GDI 852 * where to place the glyphs. 853 * On drawing we remove control chars so these need to be 854 * removed now so the string and positions are the same length. 855 * For other cases we need to pass glyph codes to GDI. 856 */ 857 str = wPrinterJob.removeControlChars(str); 858 char[] chars = str.toCharArray(); 859 int len = chars.length; 860 GlyphVector gv = null; 861 if (!FontUtilities.isComplexText(chars, 0, len)) { 862 gv = font.createGlyphVector(frc, str); 863 } 864 if (gv == null) { 865 super.drawString(str, userx, usery, font, frc, targetW); 866 return; 867 } 868 glyphPos = gv.getGlyphPositions(0, len, null); 869 Point2D gvAdvPt = gv.getGlyphPosition(gv.getNumGlyphs()); 870 871 /* GDI advances must not include device space rotation. 872 * See earlier comment in printGlyphVector() for details. 873 */ 874 AffineTransform advanceTransform = 875 AffineTransform.getScaleInstance(scaleFactorX, scaleFactorY); 876 float[] glyphAdvPos = new float[glyphPos.length]; 877 878 advanceTransform.transform(glyphPos, 0, //source 879 glyphAdvPos, 0, //destination 880 glyphPos.length/2); //num points 881 glyphPos = glyphAdvPos; 882 } 883 wPrinterJob.textOut(str, devx, devy, glyphPos); 884 } 885 886 /* If 2D and GDI agree on the advance of the string we do not 887 * need to explicitly assign glyph positions. 888 * If we are to use the GDI advance, require it to agree with 889 * JDK to a precision of <= 1.0% - ie 1 pixel in 100 890 * discrepancy after rounding the 2D advance to the 891 * nearest pixel and is greater than one pixel in total. 892 * ie strings < 100 pixels in length will be OK so long 893 * as they differ by only 1 pixel even though that is > 1% 894 * The bounds from 2D are in user space so need to 895 * be scaled to device space for comparison with GDI. 896 * scaleX is the scale from user space to device space needed for this. 897 */ 898 private boolean okGDIMetrics(String str, Font font, 899 FontRenderContext frc, double scaleX) { 900 901 Rectangle2D bds = font.getStringBounds(str, frc); 902 double jdkAdvance = bds.getWidth(); 903 jdkAdvance = Math.round(jdkAdvance*scaleX); 904 int gdiAdvance = ((WPrinterJob)getPrinterJob()).getGDIAdvance(str); 905 if (jdkAdvance > 0 && gdiAdvance > 0) { 906 double diff = Math.abs(gdiAdvance-jdkAdvance); 907 double ratio = gdiAdvance/jdkAdvance; 908 if (ratio < 1) { 909 ratio = 1/ratio; 910 } 911 return diff <= 1 || ratio < 1.01; 912 } 913 return true; 914 } 915 916 /** 917 * The various {@code drawImage()} methods for 918 * {@code WPathGraphics} are all decomposed 919 * into an invocation of {@code drawImageToPlatform}. 920 * The portion of the passed in image defined by 921 * {@code srcX, srcY, srcWidth, and srcHeight} 922 * is transformed by the supplied AffineTransform and 923 * drawn using GDI to the printer context. 924 * 925 * @param image The image to be drawn. 926 * @param xform Used to transform the image before drawing. 927 * This can be null. 928 * @param bgcolor This color is drawn where the image has transparent 929 * pixels. If this parameter is null then the 930 * pixels already in the destination should show 931 * through. 932 * @param srcX With srcY this defines the upper-left corner 933 * of the portion of the image to be drawn. 934 * 935 * @param srcY With srcX this defines the upper-left corner 936 * of the portion of the image to be drawn. 937 * @param srcWidth The width of the portion of the image to 938 * be drawn. 939 * @param srcHeight The height of the portion of the image to 940 * be drawn. 941 * @param handlingTransparency if being recursively called to 942 * print opaque region of transparent image 943 */ 944 @Override 945 protected boolean drawImageToPlatform(Image image, AffineTransform xform, 946 Color bgcolor, 947 int srcX, int srcY, 948 int srcWidth, int srcHeight, 949 boolean handlingTransparency) { 950 951 BufferedImage img = getBufferedImage(image); 952 if (img == null) { 953 return true; 954 } 955 956 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 957 958 /* The full transform to be applied to the image is the 959 * caller's transform concatenated on to the transform 960 * from user space to device space. If the caller didn't 961 * supply a transform then we just act as if they passed 962 * in the identify transform. 963 */ 964 AffineTransform fullTransform = getTransform(); 965 if (xform == null) { 966 xform = new AffineTransform(); 967 } 968 fullTransform.concatenate(xform); 969 970 /* Split the full transform into a pair of 971 * transforms. The first transform holds effects 972 * that GDI (under Win95) can not perform such 973 * as rotation and shearing. The second transform 974 * is setup to hold only the scaling effects. 975 * These transforms are created such that a point, 976 * p, in user space, when transformed by 'fullTransform' 977 * lands in the same place as when it is transformed 978 * by 'rotTransform' and then 'scaleTransform'. 979 * 980 * The entire image transformation is not in Java in order 981 * to minimize the amount of memory needed in the VM. By 982 * dividing the transform in two, we rotate and shear 983 * the source image in its own space and only go to 984 * the, usually, larger, device space when we ask 985 * GDI to perform the final scaling. 986 * Clamp this to the device scale for better quality printing. 987 */ 988 double[] fullMatrix = new double[6]; 989 fullTransform.getMatrix(fullMatrix); 990 991 /* Calculate the amount of scaling in the x 992 * and y directions. This scaling is computed by 993 * transforming a unit vector along each axis 994 * and computing the resulting magnitude. 995 * The computed values 'scaleX' and 'scaleY' 996 * represent the amount of scaling GDI will be asked 997 * to perform. 998 */ 999 Point2D.Float unitVectorX = new Point2D.Float(1, 0); 1000 Point2D.Float unitVectorY = new Point2D.Float(0, 1); 1001 fullTransform.deltaTransform(unitVectorX, unitVectorX); 1002 fullTransform.deltaTransform(unitVectorY, unitVectorY); 1003 1004 Point2D.Float origin = new Point2D.Float(0, 0); 1005 double scaleX = unitVectorX.distance(origin); 1006 double scaleY = unitVectorY.distance(origin); 1007 1008 double devResX = wPrinterJob.getXRes(); 1009 double devResY = wPrinterJob.getYRes(); 1010 double devScaleX = devResX / DEFAULT_USER_RES; 1011 double devScaleY = devResY / DEFAULT_USER_RES; 1012 1013 /* check if rotated or sheared */ 1014 int transformType = fullTransform.getType(); 1015 boolean clampScale = ((transformType & 1016 (AffineTransform.TYPE_GENERAL_ROTATION | 1017 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 1018 if (clampScale) { 1019 if (scaleX > devScaleX) scaleX = devScaleX; 1020 if (scaleY > devScaleY) scaleY = devScaleY; 1021 } 1022 1023 /* We do not need to draw anything if either scaling 1024 * factor is zero. 1025 */ 1026 if (scaleX != 0 && scaleY != 0) { 1027 1028 /* Here's the transformation we will do with Java2D, 1029 */ 1030 AffineTransform rotTransform = new AffineTransform( 1031 fullMatrix[0] / scaleX, //m00 1032 fullMatrix[1] / scaleY, //m10 1033 fullMatrix[2] / scaleX, //m01 1034 fullMatrix[3] / scaleY, //m11 1035 fullMatrix[4] / scaleX, //m02 1036 fullMatrix[5] / scaleY); //m12 1037 1038 /* The scale transform is not used directly: we instead 1039 * directly multiply by scaleX and scaleY. 1040 * 1041 * Conceptually here is what the scaleTransform is: 1042 * 1043 * AffineTransform scaleTransform = new AffineTransform( 1044 * scaleX, //m00 1045 * 0, //m10 1046 * 0, //m01 1047 * scaleY, //m11 1048 * 0, //m02 1049 * 0); //m12 1050 */ 1051 1052 /* Convert the image source's rectangle into the rotated 1053 * and sheared space. Once there, we calculate a rectangle 1054 * that encloses the resulting shape. It is this rectangle 1055 * which defines the size of the BufferedImage we need to 1056 * create to hold the transformed image. 1057 */ 1058 Rectangle2D.Float srcRect = new Rectangle2D.Float(srcX, srcY, 1059 srcWidth, 1060 srcHeight); 1061 1062 Shape rotShape = rotTransform.createTransformedShape(srcRect); 1063 Rectangle2D rotBounds = rotShape.getBounds2D(); 1064 1065 /* add a fudge factor as some fp precision problems have 1066 * been observed which caused pixels to be rounded down and 1067 * out of the image. 1068 */ 1069 rotBounds.setRect(rotBounds.getX(), rotBounds.getY(), 1070 rotBounds.getWidth()+0.001, 1071 rotBounds.getHeight()+0.001); 1072 1073 int boundsWidth = (int) rotBounds.getWidth(); 1074 int boundsHeight = (int) rotBounds.getHeight(); 1075 1076 if (boundsWidth > 0 && boundsHeight > 0) { 1077 1078 /* If the image has transparent or semi-transparent 1079 * pixels then we'll have the application re-render 1080 * the portion of the page covered by the image. 1081 * The BufferedImage will be at the image's resolution 1082 * to avoid wasting memory. By re-rendering this portion 1083 * of a page all compositing is done by Java2D into 1084 * the BufferedImage and then that image is copied to 1085 * GDI. 1086 * However several special cases can be handled otherwise: 1087 * - bitmask transparency with a solid background colour 1088 * - images which have transparency color models but no 1089 * transparent pixels 1090 * - images with bitmask transparency and an IndexColorModel 1091 * (the common transparent GIF case) can be handled by 1092 * rendering just the opaque pixels. 1093 */ 1094 boolean drawOpaque = true; 1095 if (isCompositing(getComposite())) { 1096 drawOpaque = false; 1097 } else if (!handlingTransparency && hasTransparentPixels(img)) { 1098 drawOpaque = false; 1099 if (isBitmaskTransparency(img)) { 1100 if (bgcolor == null) { 1101 if (drawBitmaskImage(img, xform, bgcolor, 1102 srcX, srcY, 1103 srcWidth, srcHeight)) { 1104 // image drawn, just return. 1105 return true; 1106 } 1107 } else if (bgcolor.getTransparency() 1108 == Transparency.OPAQUE) { 1109 drawOpaque = true; 1110 } 1111 } 1112 if (!canDoRedraws()) { 1113 drawOpaque = true; 1114 } 1115 } else { 1116 // if there's no transparent pixels there's no need 1117 // for a background colour. This can avoid edge artifacts 1118 // in rotation cases. 1119 bgcolor = null; 1120 } 1121 // if src region extends beyond the image, the "opaque" path 1122 // may blit b/g colour (including white) where it shoudn't. 1123 if ((srcX+srcWidth > img.getWidth(null) || 1124 srcY+srcHeight > img.getHeight(null)) 1125 && canDoRedraws()) { 1126 drawOpaque = false; 1127 } 1128 if (drawOpaque == false) { 1129 1130 fullTransform.getMatrix(fullMatrix); 1131 AffineTransform tx = 1132 new AffineTransform( 1133 fullMatrix[0] / devScaleX, //m00 1134 fullMatrix[1] / devScaleY, //m10 1135 fullMatrix[2] / devScaleX, //m01 1136 fullMatrix[3] / devScaleY, //m11 1137 fullMatrix[4] / devScaleX, //m02 1138 fullMatrix[5] / devScaleY); //m12 1139 1140 Rectangle2D.Float rect = 1141 new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight); 1142 1143 Shape shape = fullTransform.createTransformedShape(rect); 1144 // Region isn't user space because its potentially 1145 // been rotated for landscape. 1146 Rectangle2D region = shape.getBounds2D(); 1147 1148 region.setRect(region.getX(), region.getY(), 1149 region.getWidth()+0.001, 1150 region.getHeight()+0.001); 1151 1152 // Try to limit the amount of memory used to 8Mb, so 1153 // if at device resolution this exceeds a certain 1154 // image size then scale down the region to fit in 1155 // that memory, but never to less than 72 dpi. 1156 1157 int w = (int)region.getWidth(); 1158 int h = (int)region.getHeight(); 1159 int nbytes = w * h * 3; 1160 int maxBytes = 8 * 1024 * 1024; 1161 double origDpi = (devResX < devResY) ? devResX : devResY; 1162 int dpi = (int)origDpi; 1163 double scaleFactor = 1; 1164 1165 double maxSFX = w/(double)boundsWidth; 1166 double maxSFY = h/(double)boundsHeight; 1167 double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX; 1168 int minDpi = (int)(dpi/maxSF); 1169 if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES; 1170 1171 while (nbytes > maxBytes && dpi > minDpi) { 1172 scaleFactor *= 2; 1173 dpi /= 2; 1174 nbytes /= 4; 1175 } 1176 if (dpi < minDpi) { 1177 scaleFactor = (origDpi / minDpi); 1178 } 1179 1180 region.setRect(region.getX()/scaleFactor, 1181 region.getY()/scaleFactor, 1182 region.getWidth()/scaleFactor, 1183 region.getHeight()/scaleFactor); 1184 1185 /* 1186 * We need to have the clip as part of the saved state, 1187 * either directly, or all the components that are 1188 * needed to reconstitute it (image source area, 1189 * image transform and current graphics transform). 1190 * The clip is described in user space, so we need to 1191 * save the current graphics transform anyway so just 1192 * save these two. 1193 */ 1194 wPrinterJob.saveState(getTransform(), getClip(), 1195 region, scaleFactor, scaleFactor); 1196 return true; 1197 /* The image can be rendered directly by GDI so we 1198 * copy it into a BufferedImage (this takes care of 1199 * ColorSpace and BufferedImageOp issues) and then 1200 * send that to GDI. 1201 */ 1202 } else { 1203 /* Create a buffered image big enough to hold the portion 1204 * of the source image being printed. 1205 * The image format will be 3BYTE_BGR for most cases 1206 * except where we can represent the image as a 1, 4 or 8 1207 * bits-per-pixel DIB. 1208 */ 1209 int dibType = BufferedImage.TYPE_3BYTE_BGR; 1210 IndexColorModel icm = null; 1211 1212 ColorModel cm = img.getColorModel(); 1213 int imgType = img.getType(); 1214 if (cm instanceof IndexColorModel && 1215 cm.getPixelSize() <= 8 && 1216 (imgType == BufferedImage.TYPE_BYTE_BINARY || 1217 imgType == BufferedImage.TYPE_BYTE_INDEXED)) { 1218 icm = (IndexColorModel)cm; 1219 dibType = imgType; 1220 /* BYTE_BINARY may be 2 bpp which DIB can't handle. 1221 * Convert this to 4bpp. 1222 */ 1223 if (imgType == BufferedImage.TYPE_BYTE_BINARY && 1224 cm.getPixelSize() == 2) { 1225 1226 int[] rgbs = new int[16]; 1227 icm.getRGBs(rgbs); 1228 boolean transparent = 1229 icm.getTransparency() != Transparency.OPAQUE; 1230 int transpixel = icm.getTransparentPixel(); 1231 1232 icm = new IndexColorModel(4, 16, 1233 rgbs, 0, 1234 transparent, transpixel, 1235 DataBuffer.TYPE_BYTE); 1236 } 1237 } 1238 1239 int iw = (int)rotBounds.getWidth(); 1240 int ih = (int)rotBounds.getHeight(); 1241 BufferedImage deepImage = null; 1242 /* If there is no special transform needed (this is a 1243 * simple BLIT) and dibType == img.getType() and we 1244 * didn't create a new IndexColorModel AND the whole of 1245 * the source image is being drawn (GDI can't handle a 1246 * portion of the original source image) then we 1247 * don't need to create this intermediate image - GDI 1248 * can access the data from the original image. 1249 * Since a subimage can be created by calling 1250 * BufferedImage.getSubImage() that condition needs to 1251 * be accounted for too. This implies inspecting the 1252 * data buffer. In the end too many cases are not able 1253 * to take advantage of this option until we can teach 1254 * the native code to properly navigate the data buffer. 1255 * There was a concern that since in native code since we 1256 * need to DWORD align and flip to a bottom up DIB that 1257 * the "original" image may get perturbed by this. 1258 * But in fact we always malloc new memory for the aligned 1259 * copy so this isn't a problem. 1260 * This points out that we allocate two temporaries copies 1261 * of the image : one in Java and one in native. If 1262 * we can be smarter about not allocating this one when 1263 * not needed, that would seem like a good thing to do, 1264 * even if in many cases the ColorModels don't match and 1265 * its needed. 1266 * Until all of this is resolved newImage is always true. 1267 */ 1268 boolean newImage = true; 1269 if (newImage) { 1270 if (icm == null) { 1271 deepImage = new BufferedImage(iw, ih, dibType); 1272 } else { 1273 deepImage = new BufferedImage(iw, ih, dibType,icm); 1274 } 1275 1276 /* Setup a Graphics2D on to the BufferedImage so that 1277 * the source image when copied, lands within the 1278 * image buffer. 1279 */ 1280 Graphics2D imageGraphics = deepImage.createGraphics(); 1281 imageGraphics.clipRect(0, 0, 1282 deepImage.getWidth(), 1283 deepImage.getHeight()); 1284 1285 imageGraphics.translate(-rotBounds.getX(), 1286 -rotBounds.getY()); 1287 imageGraphics.transform(rotTransform); 1288 1289 /* Fill the BufferedImage either with the caller 1290 * supplied color, 'bgColor' or, if null, with white. 1291 */ 1292 if (bgcolor == null) { 1293 bgcolor = Color.white; 1294 } 1295 1296 imageGraphics.drawImage(img, 1297 srcX, srcY, 1298 srcX + srcWidth, 1299 srcY + srcHeight, 1300 srcX, srcY, 1301 srcX + srcWidth, 1302 srcY + srcHeight, 1303 bgcolor, null); 1304 imageGraphics.dispose(); 1305 } else { 1306 deepImage = img; 1307 } 1308 1309 /* Scale the bounding rectangle by the scale transform. 1310 * Because the scaling transform has only x and y 1311 * scaling components it is equivalent to multiply 1312 * the x components of the bounding rectangle by 1313 * the x scaling factor and to multiply the y components 1314 * by the y scaling factor. 1315 */ 1316 Rectangle2D.Float scaledBounds 1317 = new Rectangle2D.Float( 1318 (float) (rotBounds.getX() * scaleX), 1319 (float) (rotBounds.getY() * scaleY), 1320 (float) (rotBounds.getWidth() * scaleX), 1321 (float) (rotBounds.getHeight() * scaleY)); 1322 1323 /* Pull the raster data from the buffered image 1324 * and pass it along to GDI. 1325 */ 1326 WritableRaster raster = deepImage.getRaster(); 1327 byte[] data; 1328 if (raster instanceof ByteComponentRaster) { 1329 data = ((ByteComponentRaster)raster).getDataStorage(); 1330 } else if (raster instanceof BytePackedRaster) { 1331 data = ((BytePackedRaster)raster).getDataStorage(); 1332 } else { 1333 return false; 1334 } 1335 1336 int bitsPerPixel = 24; 1337 SampleModel sm = deepImage.getSampleModel(); 1338 if (sm instanceof ComponentSampleModel) { 1339 ComponentSampleModel csm = (ComponentSampleModel)sm; 1340 bitsPerPixel = csm.getPixelStride() * 8; 1341 } else if (sm instanceof MultiPixelPackedSampleModel) { 1342 MultiPixelPackedSampleModel mppsm = 1343 (MultiPixelPackedSampleModel)sm; 1344 bitsPerPixel = mppsm.getPixelBitStride(); 1345 } else { 1346 if (icm != null) { 1347 int diw = deepImage.getWidth(); 1348 int dih = deepImage.getHeight(); 1349 if (diw > 0 && dih > 0) { 1350 bitsPerPixel = data.length*8/diw/dih; 1351 } 1352 } 1353 } 1354 1355 /* Because the caller's image has been rotated 1356 * and sheared into our BufferedImage and because 1357 * we will be handing that BufferedImage directly to 1358 * GDI, we need to set an additional clip. This clip 1359 * makes sure that only parts of the BufferedImage 1360 * that are also part of the caller's image are drawn. 1361 */ 1362 Shape holdClip = getClip(); 1363 clip(xform.createTransformedShape(srcRect)); 1364 deviceClip(getClip().getPathIterator(getTransform())); 1365 1366 wPrinterJob.drawDIBImage 1367 (data, scaledBounds.x, scaledBounds.y, 1368 (float)Math.rint(scaledBounds.width+0.5), 1369 (float)Math.rint(scaledBounds.height+0.5), 1370 0f, 0f, 1371 deepImage.getWidth(), deepImage.getHeight(), 1372 bitsPerPixel, icm); 1373 1374 setClip(holdClip); 1375 } 1376 } 1377 } 1378 1379 return true; 1380 } 1381 1382 /** 1383 * Have the printing application redraw everything that falls 1384 * within the page bounds defined by {@code region}. 1385 */ 1386 @Override 1387 public void redrawRegion(Rectangle2D region, double scaleX, double scaleY, 1388 Shape savedClip, AffineTransform savedTransform) 1389 throws PrinterException { 1390 1391 WPrinterJob wPrinterJob = (WPrinterJob)getPrinterJob(); 1392 Printable painter = getPrintable(); 1393 PageFormat pageFormat = getPageFormat(); 1394 int pageIndex = getPageIndex(); 1395 1396 /* Create a buffered image big enough to hold the portion 1397 * of the source image being printed. 1398 */ 1399 BufferedImage deepImage = new BufferedImage( 1400 (int) region.getWidth(), 1401 (int) region.getHeight(), 1402 BufferedImage.TYPE_3BYTE_BGR); 1403 1404 /* Get a graphics for the application to render into. 1405 * We initialize the buffer to white in order to 1406 * match the paper and then we shift the BufferedImage 1407 * so that it covers the area on the page where the 1408 * caller's Image will be drawn. 1409 */ 1410 Graphics2D g = deepImage.createGraphics(); 1411 ProxyGraphics2D proxy = new ProxyGraphics2D(g, wPrinterJob); 1412 proxy.setColor(Color.white); 1413 proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight()); 1414 proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight()); 1415 1416 proxy.translate(-region.getX(), -region.getY()); 1417 1418 /* Calculate the resolution of the source image. 1419 */ 1420 float sourceResX = (float)(wPrinterJob.getXRes() / scaleX); 1421 float sourceResY = (float)(wPrinterJob.getYRes() / scaleY); 1422 1423 /* The application expects to see user space at 72 dpi. 1424 * so change user space from image source resolution to 1425 * 72 dpi. 1426 */ 1427 proxy.scale(sourceResX / DEFAULT_USER_RES, 1428 sourceResY / DEFAULT_USER_RES); 1429 1430 proxy.translate( 1431 -wPrinterJob.getPhysicalPrintableX(pageFormat.getPaper()) 1432 / wPrinterJob.getXRes() * DEFAULT_USER_RES, 1433 -wPrinterJob.getPhysicalPrintableY(pageFormat.getPaper()) 1434 / wPrinterJob.getYRes() * DEFAULT_USER_RES); 1435 /* NB User space now has to be at 72 dpi for this calc to be correct */ 1436 proxy.transform(new AffineTransform(getPageFormat().getMatrix())); 1437 proxy.setPaint(Color.black); 1438 1439 painter.print(proxy, pageFormat, pageIndex); 1440 1441 g.dispose(); 1442 1443 /* We need to set the device clip using saved information. 1444 * savedClip intersects the user clip with a clip that restricts 1445 * the GDI rendered area of our BufferedImage to that which 1446 * may correspond to a rotate or shear. 1447 * The saved device transform is needed as the current transform 1448 * is not likely to be the same. 1449 */ 1450 if (savedClip != null) { 1451 deviceClip(savedClip.getPathIterator(savedTransform)); 1452 } 1453 1454 /* Scale the bounding rectangle by the scale transform. 1455 * Because the scaling transform has only x and y 1456 * scaling components it is equivalent to multiplying 1457 * the x components of the bounding rectangle by 1458 * the x scaling factor and to multiplying the y components 1459 * by the y scaling factor. 1460 */ 1461 Rectangle2D.Float scaledBounds 1462 = new Rectangle2D.Float( 1463 (float) (region.getX() * scaleX), 1464 (float) (region.getY() * scaleY), 1465 (float) (region.getWidth() * scaleX), 1466 (float) (region.getHeight() * scaleY)); 1467 1468 /* Pull the raster data from the buffered image 1469 * and pass it along to GDI. 1470 */ 1471 ByteComponentRaster tile 1472 = (ByteComponentRaster)deepImage.getRaster(); 1473 1474 wPrinterJob.drawImage3ByteBGR(tile.getDataStorage(), 1475 scaledBounds.x, scaledBounds.y, 1476 scaledBounds.width, 1477 scaledBounds.height, 1478 0f, 0f, 1479 deepImage.getWidth(), deepImage.getHeight()); 1480 1481 } 1482 1483 /* 1484 * Fill the path defined by {@code pathIter} 1485 * with the specified color. 1486 * The path is provided in device coordinates. 1487 */ 1488 @Override 1489 protected void deviceFill(PathIterator pathIter, Color color) { 1490 1491 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1492 1493 convertToWPath(pathIter); 1494 wPrinterJob.selectSolidBrush(color); 1495 wPrinterJob.fillPath(); 1496 } 1497 1498 /* 1499 * Set the printer device's clip to be the 1500 * path defined by {@code pathIter} 1501 * The path is provided in device coordinates. 1502 */ 1503 @Override 1504 protected void deviceClip(PathIterator pathIter) { 1505 1506 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1507 1508 convertToWPath(pathIter); 1509 wPrinterJob.selectClipPath(); 1510 } 1511 1512 /** 1513 * Draw the bounding rectangle using transformed coordinates. 1514 */ 1515 @Override 1516 protected void deviceFrameRect(int x, int y, int width, int height, 1517 Color color) { 1518 1519 AffineTransform deviceTransform = getTransform(); 1520 1521 /* check if rotated or sheared */ 1522 int transformType = deviceTransform.getType(); 1523 boolean usePath = ((transformType & 1524 (AffineTransform.TYPE_GENERAL_ROTATION | 1525 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 1526 1527 if (usePath) { 1528 draw(new Rectangle2D.Float(x, y, width, height)); 1529 return; 1530 } 1531 1532 Stroke stroke = getStroke(); 1533 1534 if (stroke instanceof BasicStroke) { 1535 BasicStroke lineStroke = (BasicStroke) stroke; 1536 1537 int endCap = lineStroke.getEndCap(); 1538 int lineJoin = lineStroke.getLineJoin(); 1539 1540 1541 /* check for default style and try to optimize it by 1542 * calling the frameRect native function instead of using paths. 1543 */ 1544 if ((endCap == BasicStroke.CAP_SQUARE) && 1545 (lineJoin == BasicStroke.JOIN_MITER) && 1546 (lineStroke.getMiterLimit() ==10.0f)) { 1547 1548 float lineWidth = lineStroke.getLineWidth(); 1549 Point2D.Float penSize = new Point2D.Float(lineWidth, 1550 lineWidth); 1551 1552 deviceTransform.deltaTransform(penSize, penSize); 1553 float deviceLineWidth = Math.min(Math.abs(penSize.x), 1554 Math.abs(penSize.y)); 1555 1556 /* transform upper left coordinate */ 1557 Point2D.Float ul_pos = new Point2D.Float(x, y); 1558 deviceTransform.transform(ul_pos, ul_pos); 1559 1560 /* transform lower right coordinate */ 1561 Point2D.Float lr_pos = new Point2D.Float(x + width, 1562 y + height); 1563 deviceTransform.transform(lr_pos, lr_pos); 1564 1565 float w = (float) (lr_pos.getX() - ul_pos.getX()); 1566 float h = (float)(lr_pos.getY() - ul_pos.getY()); 1567 1568 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1569 1570 /* use selectStylePen, if supported */ 1571 if (wPrinterJob.selectStylePen(endCap, lineJoin, 1572 deviceLineWidth, color) == true) { 1573 wPrinterJob.frameRect((float)ul_pos.getX(), 1574 (float)ul_pos.getY(), w, h); 1575 } 1576 /* not supported, must be a Win 9x */ 1577 else { 1578 1579 double lowerRes = Math.min(wPrinterJob.getXRes(), 1580 wPrinterJob.getYRes()); 1581 1582 if ((deviceLineWidth/lowerRes) < MAX_THINLINE_INCHES) { 1583 /* use the default pen styles for thin pens. */ 1584 wPrinterJob.selectPen(deviceLineWidth, color); 1585 wPrinterJob.frameRect((float)ul_pos.getX(), 1586 (float)ul_pos.getY(), w, h); 1587 } 1588 else { 1589 draw(new Rectangle2D.Float(x, y, width, height)); 1590 } 1591 } 1592 } 1593 else { 1594 draw(new Rectangle2D.Float(x, y, width, height)); 1595 } 1596 } 1597 } 1598 1599 1600 /* 1601 * Fill the rectangle with specified color and using Windows' 1602 * GDI fillRect function. 1603 * Boundaries are determined by the given coordinates. 1604 */ 1605 @Override 1606 protected void deviceFillRect(int x, int y, int width, int height, 1607 Color color) { 1608 /* 1609 * Transform to device coordinates 1610 */ 1611 AffineTransform deviceTransform = getTransform(); 1612 1613 /* check if rotated or sheared */ 1614 int transformType = deviceTransform.getType(); 1615 boolean usePath = ((transformType & 1616 (AffineTransform.TYPE_GENERAL_ROTATION | 1617 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 1618 if (usePath) { 1619 fill(new Rectangle2D.Float(x, y, width, height)); 1620 return; 1621 } 1622 1623 Point2D.Float tlc_pos = new Point2D.Float(x, y); 1624 deviceTransform.transform(tlc_pos, tlc_pos); 1625 1626 Point2D.Float brc_pos = new Point2D.Float(x+width, y+height); 1627 deviceTransform.transform(brc_pos, brc_pos); 1628 1629 float deviceWidth = (float) (brc_pos.getX() - tlc_pos.getX()); 1630 float deviceHeight = (float)(brc_pos.getY() - tlc_pos.getY()); 1631 1632 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1633 wPrinterJob.fillRect((float)tlc_pos.getX(), (float)tlc_pos.getY(), 1634 deviceWidth, deviceHeight, color); 1635 } 1636 1637 1638 /** 1639 * Draw a line using a pen created using the specified color 1640 * and current stroke properties. 1641 */ 1642 @Override 1643 protected void deviceDrawLine(int xBegin, int yBegin, int xEnd, int yEnd, 1644 Color color) { 1645 Stroke stroke = getStroke(); 1646 1647 if (stroke instanceof BasicStroke) { 1648 BasicStroke lineStroke = (BasicStroke) stroke; 1649 1650 if (lineStroke.getDashArray() != null) { 1651 draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd)); 1652 return; 1653 } 1654 1655 float lineWidth = lineStroke.getLineWidth(); 1656 Point2D.Float penSize = new Point2D.Float(lineWidth, lineWidth); 1657 1658 AffineTransform deviceTransform = getTransform(); 1659 deviceTransform.deltaTransform(penSize, penSize); 1660 1661 float deviceLineWidth = Math.min(Math.abs(penSize.x), 1662 Math.abs(penSize.y)); 1663 1664 Point2D.Float begin_pos = new Point2D.Float(xBegin, yBegin); 1665 deviceTransform.transform(begin_pos, begin_pos); 1666 1667 Point2D.Float end_pos = new Point2D.Float(xEnd, yEnd); 1668 deviceTransform.transform(end_pos, end_pos); 1669 1670 int endCap = lineStroke.getEndCap(); 1671 int lineJoin = lineStroke.getLineJoin(); 1672 1673 /* check if it's a one-pixel line */ 1674 if ((end_pos.getX() == begin_pos.getX()) 1675 && (end_pos.getY() == begin_pos.getY())) { 1676 1677 /* endCap other than Round will not print! 1678 * due to Windows GDI limitation, force it to CAP_ROUND 1679 */ 1680 endCap = BasicStroke.CAP_ROUND; 1681 } 1682 1683 1684 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1685 1686 /* call native function that creates pen with style */ 1687 if (wPrinterJob.selectStylePen(endCap, lineJoin, 1688 deviceLineWidth, color)) { 1689 wPrinterJob.moveTo((float)begin_pos.getX(), 1690 (float)begin_pos.getY()); 1691 wPrinterJob.lineTo((float)end_pos.getX(), 1692 (float)end_pos.getY()); 1693 } 1694 /* selectStylePen is not supported, must be Win 9X */ 1695 else { 1696 1697 /* let's see if we can use a a default pen 1698 * if it's round end (Windows' default style) 1699 * or it's vertical/horizontal 1700 * or stroke is too thin. 1701 */ 1702 double lowerRes = Math.min(wPrinterJob.getXRes(), 1703 wPrinterJob.getYRes()); 1704 1705 if ((endCap == BasicStroke.CAP_ROUND) || 1706 (((xBegin == xEnd) || (yBegin == yEnd)) && 1707 (deviceLineWidth/lowerRes < MAX_THINLINE_INCHES))) { 1708 1709 wPrinterJob.selectPen(deviceLineWidth, color); 1710 wPrinterJob.moveTo((float)begin_pos.getX(), 1711 (float)begin_pos.getY()); 1712 wPrinterJob.lineTo((float)end_pos.getX(), 1713 (float)end_pos.getY()); 1714 } 1715 else { 1716 draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd)); 1717 } 1718 } 1719 } 1720 } 1721 1722 1723 /** 1724 * Given a Java2D {@code PathIterator} instance, 1725 * this method translates that into a Window's path 1726 * in the printer device context. 1727 */ 1728 private void convertToWPath(PathIterator pathIter) { 1729 1730 float[] segment = new float[6]; 1731 int segmentType; 1732 1733 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1734 1735 /* Map the PathIterator's fill rule into the Window's 1736 * polygon fill rule. 1737 */ 1738 int polyFillRule; 1739 if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) { 1740 polyFillRule = WPrinterJob.POLYFILL_ALTERNATE; 1741 } else { 1742 polyFillRule = WPrinterJob.POLYFILL_WINDING; 1743 } 1744 wPrinterJob.setPolyFillMode(polyFillRule); 1745 1746 wPrinterJob.beginPath(); 1747 1748 while (pathIter.isDone() == false) { 1749 segmentType = pathIter.currentSegment(segment); 1750 1751 switch (segmentType) { 1752 case PathIterator.SEG_MOVETO: 1753 wPrinterJob.moveTo(segment[0], segment[1]); 1754 break; 1755 1756 case PathIterator.SEG_LINETO: 1757 wPrinterJob.lineTo(segment[0], segment[1]); 1758 break; 1759 1760 /* Convert the quad path to a bezier. 1761 */ 1762 case PathIterator.SEG_QUADTO: 1763 int lastX = wPrinterJob.getPenX(); 1764 int lastY = wPrinterJob.getPenY(); 1765 float c1x = lastX + (segment[0] - lastX) * 2 / 3; 1766 float c1y = lastY + (segment[1] - lastY) * 2 / 3; 1767 float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3; 1768 float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3; 1769 wPrinterJob.polyBezierTo(c1x, c1y, 1770 c2x, c2y, 1771 segment[2], segment[3]); 1772 break; 1773 1774 case PathIterator.SEG_CUBICTO: 1775 wPrinterJob.polyBezierTo(segment[0], segment[1], 1776 segment[2], segment[3], 1777 segment[4], segment[5]); 1778 break; 1779 1780 case PathIterator.SEG_CLOSE: 1781 wPrinterJob.closeFigure(); 1782 break; 1783 } 1784 1785 1786 pathIter.next(); 1787 } 1788 1789 wPrinterJob.endPath(); 1790 1791 } 1792 1793 }