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