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