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