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 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 JDK1.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 JDK1.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 s 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 img 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 protected boolean drawImageToPlatform(Image image, AffineTransform xform, 900 Color bgcolor, 901 int srcX, int srcY, 902 int srcWidth, int srcHeight, 903 boolean handlingTransparency) { 904 905 BufferedImage img = getBufferedImage(image); 906 if (img == null) { 907 return true; 908 } 909 910 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 911 912 /* The full transform to be applied to the image is the 913 * caller's transform concatenated on to the transform 914 * from user space to device space. If the caller didn't 915 * supply a transform then we just act as if they passed 916 * in the identify transform. 917 */ 918 AffineTransform fullTransform = getTransform(); 919 if (xform == null) { 920 xform = new AffineTransform(); 921 } 922 fullTransform.concatenate(xform); 923 924 /* Split the full transform into a pair of 925 * transforms. The first transform holds effects 926 * that GDI (under Win95) can not perform such 927 * as rotation and shearing. The second transform 928 * is setup to hold only the scaling effects. 929 * These transforms are created such that a point, 930 * p, in user space, when transformed by 'fullTransform' 931 * lands in the same place as when it is transformed 932 * by 'rotTransform' and then 'scaleTransform'. 933 * 934 * The entire image transformation is not in Java in order 935 * to minimize the amount of memory needed in the VM. By 936 * dividing the transform in two, we rotate and shear 937 * the source image in its own space and only go to 938 * the, usually, larger, device space when we ask 939 * GDI to perform the final scaling. 940 * Clamp this to the device scale for better quality printing. 941 */ 942 double[] fullMatrix = new double[6]; 943 fullTransform.getMatrix(fullMatrix); 944 945 /* Calculate the amount of scaling in the x 946 * and y directions. This scaling is computed by 947 * transforming a unit vector along each axis 948 * and computing the resulting magnitude. 949 * The computed values 'scaleX' and 'scaleY' 950 * represent the amount of scaling GDI will be asked 951 * to perform. 952 */ 953 Point2D.Float unitVectorX = new Point2D.Float(1, 0); 954 Point2D.Float unitVectorY = new Point2D.Float(0, 1); 955 fullTransform.deltaTransform(unitVectorX, unitVectorX); 956 fullTransform.deltaTransform(unitVectorY, unitVectorY); 957 958 Point2D.Float origin = new Point2D.Float(0, 0); 959 double scaleX = unitVectorX.distance(origin); 960 double scaleY = unitVectorY.distance(origin); 961 962 double devResX = wPrinterJob.getXRes(); 963 double devResY = wPrinterJob.getYRes(); 964 double devScaleX = devResX / DEFAULT_USER_RES; 965 double devScaleY = devResY / DEFAULT_USER_RES; 966 967 /* check if rotated or sheared */ 968 int transformType = fullTransform.getType(); 969 boolean clampScale = ((transformType & 970 (AffineTransform.TYPE_GENERAL_ROTATION | 971 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 972 if (clampScale) { 973 if (scaleX > devScaleX) scaleX = devScaleX; 974 if (scaleY > devScaleY) scaleY = devScaleY; 975 } 976 977 /* We do not need to draw anything if either scaling 978 * factor is zero. 979 */ 980 if (scaleX != 0 && scaleY != 0) { 981 982 /* Here's the transformation we will do with Java2D, 983 */ 984 AffineTransform rotTransform = new AffineTransform( 985 fullMatrix[0] / scaleX, //m00 986 fullMatrix[1] / scaleY, //m10 987 fullMatrix[2] / scaleX, //m01 988 fullMatrix[3] / scaleY, //m11 989 fullMatrix[4] / scaleX, //m02 990 fullMatrix[5] / scaleY); //m12 991 992 /* The scale transform is not used directly: we instead 993 * directly multiply by scaleX and scaleY. 994 * 995 * Conceptually here is what the scaleTransform is: 996 * 997 * AffineTransform scaleTransform = new AffineTransform( 998 * scaleX, //m00 999 * 0, //m10 1000 * 0, //m01 1001 * scaleY, //m11 1002 * 0, //m02 1003 * 0); //m12 1004 */ 1005 1006 /* Convert the image source's rectangle into the rotated 1007 * and sheared space. Once there, we calculate a rectangle 1008 * that encloses the resulting shape. It is this rectangle 1009 * which defines the size of the BufferedImage we need to 1010 * create to hold the transformed image. 1011 */ 1012 Rectangle2D.Float srcRect = new Rectangle2D.Float(srcX, srcY, 1013 srcWidth, 1014 srcHeight); 1015 1016 Shape rotShape = rotTransform.createTransformedShape(srcRect); 1017 Rectangle2D rotBounds = rotShape.getBounds2D(); 1018 1019 /* add a fudge factor as some fp precision problems have 1020 * been observed which caused pixels to be rounded down and 1021 * out of the image. 1022 */ 1023 rotBounds.setRect(rotBounds.getX(), rotBounds.getY(), 1024 rotBounds.getWidth()+0.001, 1025 rotBounds.getHeight()+0.001); 1026 1027 int boundsWidth = (int) rotBounds.getWidth(); 1028 int boundsHeight = (int) rotBounds.getHeight(); 1029 1030 if (boundsWidth > 0 && boundsHeight > 0) { 1031 1032 /* If the image has transparent or semi-transparent 1033 * pixels then we'll have the application re-render 1034 * the portion of the page covered by the image. 1035 * The BufferedImage will be at the image's resolution 1036 * to avoid wasting memory. By re-rendering this portion 1037 * of a page all compositing is done by Java2D into 1038 * the BufferedImage and then that image is copied to 1039 * GDI. 1040 * However several special cases can be handled otherwise: 1041 * - bitmask transparency with a solid background colour 1042 * - images which have transparency color models but no 1043 * transparent pixels 1044 * - images with bitmask transparency and an IndexColorModel 1045 * (the common transparent GIF case) can be handled by 1046 * rendering just the opaque pixels. 1047 */ 1048 boolean drawOpaque = true; 1049 if (!handlingTransparency && hasTransparentPixels(img)) { 1050 drawOpaque = false; 1051 if (isBitmaskTransparency(img)) { 1052 if (bgcolor == null) { 1053 if (drawBitmaskImage(img, xform, bgcolor, 1054 srcX, srcY, 1055 srcWidth, srcHeight)) { 1056 // image drawn, just return. 1057 return true; 1058 } 1059 } else if (bgcolor.getTransparency() 1060 == Transparency.OPAQUE) { 1061 drawOpaque = true; 1062 } 1063 } 1064 if (!canDoRedraws()) { 1065 drawOpaque = true; 1066 } 1067 } else { 1068 // if there's no transparent pixels there's no need 1069 // for a background colour. This can avoid edge artifacts 1070 // in rotation cases. 1071 bgcolor = null; 1072 } 1073 // if src region extends beyond the image, the "opaque" path 1074 // may blit b/g colour (including white) where it shoudn't. 1075 if ((srcX+srcWidth > img.getWidth(null) || 1076 srcY+srcHeight > img.getHeight(null)) 1077 && canDoRedraws()) { 1078 drawOpaque = false; 1079 } 1080 if (drawOpaque == false) { 1081 1082 fullTransform.getMatrix(fullMatrix); 1083 AffineTransform tx = 1084 new AffineTransform( 1085 fullMatrix[0] / devScaleX, //m00 1086 fullMatrix[1] / devScaleY, //m10 1087 fullMatrix[2] / devScaleX, //m01 1088 fullMatrix[3] / devScaleY, //m11 1089 fullMatrix[4] / devScaleX, //m02 1090 fullMatrix[5] / devScaleY); //m12 1091 1092 Rectangle2D.Float rect = 1093 new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight); 1094 1095 Shape shape = fullTransform.createTransformedShape(rect); 1096 // Region isn't user space because its potentially 1097 // been rotated for landscape. 1098 Rectangle2D region = shape.getBounds2D(); 1099 1100 region.setRect(region.getX(), region.getY(), 1101 region.getWidth()+0.001, 1102 region.getHeight()+0.001); 1103 1104 // Try to limit the amount of memory used to 8Mb, so 1105 // if at device resolution this exceeds a certain 1106 // image size then scale down the region to fit in 1107 // that memory, but never to less than 72 dpi. 1108 1109 int w = (int)region.getWidth(); 1110 int h = (int)region.getHeight(); 1111 int nbytes = w * h * 3; 1112 int maxBytes = 8 * 1024 * 1024; 1113 double origDpi = (devResX < devResY) ? devResX : devResY; 1114 int dpi = (int)origDpi; 1115 double scaleFactor = 1; 1116 1117 double maxSFX = w/(double)boundsWidth; 1118 double maxSFY = h/(double)boundsHeight; 1119 double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX; 1120 int minDpi = (int)(dpi/maxSF); 1121 if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES; 1122 1123 while (nbytes > maxBytes && dpi > minDpi) { 1124 scaleFactor *= 2; 1125 dpi /= 2; 1126 nbytes /= 4; 1127 } 1128 if (dpi < minDpi) { 1129 scaleFactor = (origDpi / minDpi); 1130 } 1131 1132 region.setRect(region.getX()/scaleFactor, 1133 region.getY()/scaleFactor, 1134 region.getWidth()/scaleFactor, 1135 region.getHeight()/scaleFactor); 1136 1137 /* 1138 * We need to have the clip as part of the saved state, 1139 * either directly, or all the components that are 1140 * needed to reconstitute it (image source area, 1141 * image transform and current graphics transform). 1142 * The clip is described in user space, so we need to 1143 * save the current graphics transform anyway so just 1144 * save these two. 1145 */ 1146 wPrinterJob.saveState(getTransform(), getClip(), 1147 region, scaleFactor, scaleFactor); 1148 return true; 1149 /* The image can be rendered directly by GDI so we 1150 * copy it into a BufferedImage (this takes care of 1151 * ColorSpace and BufferedImageOp issues) and then 1152 * send that to GDI. 1153 */ 1154 } else { 1155 /* Create a buffered image big enough to hold the portion 1156 * of the source image being printed. 1157 * The image format will be 3BYTE_BGR for most cases 1158 * except where we can represent the image as a 1, 4 or 8 1159 * bits-per-pixel DIB. 1160 */ 1161 int dibType = BufferedImage.TYPE_3BYTE_BGR; 1162 IndexColorModel icm = null; 1163 1164 ColorModel cm = img.getColorModel(); 1165 int imgType = img.getType(); 1166 if (cm instanceof IndexColorModel && 1167 cm.getPixelSize() <= 8 && 1168 (imgType == BufferedImage.TYPE_BYTE_BINARY || 1169 imgType == BufferedImage.TYPE_BYTE_INDEXED)) { 1170 icm = (IndexColorModel)cm; 1171 dibType = imgType; 1172 /* BYTE_BINARY may be 2 bpp which DIB can't handle. 1173 * Convert this to 4bpp. 1174 */ 1175 if (imgType == BufferedImage.TYPE_BYTE_BINARY && 1176 cm.getPixelSize() == 2) { 1177 1178 int[] rgbs = new int[16]; 1179 icm.getRGBs(rgbs); 1180 boolean transparent = 1181 icm.getTransparency() != Transparency.OPAQUE; 1182 int transpixel = icm.getTransparentPixel(); 1183 1184 icm = new IndexColorModel(4, 16, 1185 rgbs, 0, 1186 transparent, transpixel, 1187 DataBuffer.TYPE_BYTE); 1188 } 1189 } 1190 1191 int iw = (int)rotBounds.getWidth(); 1192 int ih = (int)rotBounds.getHeight(); 1193 BufferedImage deepImage = null; 1194 /* If there is no special transform needed (this is a 1195 * simple BLIT) and dibType == img.getType() and we 1196 * didn't create a new IndexColorModel AND the whole of 1197 * the source image is being drawn (GDI can't handle a 1198 * portion of the original source image) then we 1199 * don't need to create this intermediate image - GDI 1200 * can access the data from the original image. 1201 * Since a subimage can be created by calling 1202 * BufferedImage.getSubImage() that condition needs to 1203 * be accounted for too. This implies inspecting the 1204 * data buffer. In the end too many cases are not able 1205 * to take advantage of this option until we can teach 1206 * the native code to properly navigate the data buffer. 1207 * There was a concern that since in native code since we 1208 * need to DWORD align and flip to a bottom up DIB that 1209 * the "original" image may get perturbed by this. 1210 * But in fact we always malloc new memory for the aligned 1211 * copy so this isn't a problem. 1212 * This points out that we allocate two temporaries copies 1213 * of the image : one in Java and one in native. If 1214 * we can be smarter about not allocating this one when 1215 * not needed, that would seem like a good thing to do, 1216 * even if in many cases the ColorModels don't match and 1217 * its needed. 1218 * Until all of this is resolved newImage is always true. 1219 */ 1220 boolean newImage = true; 1221 if (newImage) { 1222 if (icm == null) { 1223 deepImage = new BufferedImage(iw, ih, dibType); 1224 } else { 1225 deepImage = new BufferedImage(iw, ih, dibType,icm); 1226 } 1227 1228 /* Setup a Graphics2D on to the BufferedImage so that 1229 * the source image when copied, lands within the 1230 * image buffer. 1231 */ 1232 Graphics2D imageGraphics = deepImage.createGraphics(); 1233 imageGraphics.clipRect(0, 0, 1234 deepImage.getWidth(), 1235 deepImage.getHeight()); 1236 1237 imageGraphics.translate(-rotBounds.getX(), 1238 -rotBounds.getY()); 1239 imageGraphics.transform(rotTransform); 1240 1241 /* Fill the BufferedImage either with the caller 1242 * supplied color, 'bgColor' or, if null, with white. 1243 */ 1244 if (bgcolor == null) { 1245 bgcolor = Color.white; 1246 } 1247 1248 imageGraphics.drawImage(img, 1249 srcX, srcY, 1250 srcX + srcWidth, 1251 srcY + srcHeight, 1252 srcX, srcY, 1253 srcX + srcWidth, 1254 srcY + srcHeight, 1255 bgcolor, null); 1256 imageGraphics.dispose(); 1257 } else { 1258 deepImage = img; 1259 } 1260 1261 /* Scale the bounding rectangle by the scale transform. 1262 * Because the scaling transform has only x and y 1263 * scaling components it is equivalent to multiply 1264 * the x components of the bounding rectangle by 1265 * the x scaling factor and to multiply the y components 1266 * by the y scaling factor. 1267 */ 1268 Rectangle2D.Float scaledBounds 1269 = new Rectangle2D.Float( 1270 (float) (rotBounds.getX() * scaleX), 1271 (float) (rotBounds.getY() * scaleY), 1272 (float) (rotBounds.getWidth() * scaleX), 1273 (float) (rotBounds.getHeight() * scaleY)); 1274 1275 /* Pull the raster data from the buffered image 1276 * and pass it along to GDI. 1277 */ 1278 WritableRaster raster = deepImage.getRaster(); 1279 byte[] data; 1280 if (raster instanceof ByteComponentRaster) { 1281 data = ((ByteComponentRaster)raster).getDataStorage(); 1282 } else if (raster instanceof BytePackedRaster) { 1283 data = ((BytePackedRaster)raster).getDataStorage(); 1284 } else { 1285 return false; 1286 } 1287 1288 int bitsPerPixel = 24; 1289 SampleModel sm = deepImage.getSampleModel(); 1290 if (sm instanceof ComponentSampleModel) { 1291 ComponentSampleModel csm = (ComponentSampleModel)sm; 1292 bitsPerPixel = csm.getPixelStride() * 8; 1293 } else if (sm instanceof MultiPixelPackedSampleModel) { 1294 MultiPixelPackedSampleModel mppsm = 1295 (MultiPixelPackedSampleModel)sm; 1296 bitsPerPixel = mppsm.getPixelBitStride(); 1297 } else { 1298 if (icm != null) { 1299 int diw = deepImage.getWidth(); 1300 int dih = deepImage.getHeight(); 1301 if (diw > 0 && dih > 0) { 1302 bitsPerPixel = data.length*8/diw/dih; 1303 } 1304 } 1305 } 1306 1307 /* Because the caller's image has been rotated 1308 * and sheared into our BufferedImage and because 1309 * we will be handing that BufferedImage directly to 1310 * GDI, we need to set an additional clip. This clip 1311 * makes sure that only parts of the BufferedImage 1312 * that are also part of the caller's image are drawn. 1313 */ 1314 Shape holdClip = getClip(); 1315 clip(xform.createTransformedShape(srcRect)); 1316 deviceClip(getClip().getPathIterator(getTransform())); 1317 1318 wPrinterJob.drawDIBImage 1319 (data, scaledBounds.x, scaledBounds.y, 1320 (float)Math.rint(scaledBounds.width+0.5), 1321 (float)Math.rint(scaledBounds.height+0.5), 1322 0f, 0f, 1323 deepImage.getWidth(), deepImage.getHeight(), 1324 bitsPerPixel, icm); 1325 1326 setClip(holdClip); 1327 } 1328 } 1329 } 1330 1331 return true; 1332 } 1333 1334 /** 1335 * Have the printing application redraw everything that falls 1336 * within the page bounds defined by <code>region</code>. 1337 */ 1338 public void redrawRegion(Rectangle2D region, double scaleX, double scaleY, 1339 Shape savedClip, AffineTransform savedTransform) 1340 throws PrinterException { 1341 1342 WPrinterJob wPrinterJob = (WPrinterJob)getPrinterJob(); 1343 Printable painter = getPrintable(); 1344 PageFormat pageFormat = getPageFormat(); 1345 int pageIndex = getPageIndex(); 1346 1347 /* Create a buffered image big enough to hold the portion 1348 * of the source image being printed. 1349 */ 1350 BufferedImage deepImage = new BufferedImage( 1351 (int) region.getWidth(), 1352 (int) region.getHeight(), 1353 BufferedImage.TYPE_3BYTE_BGR); 1354 1355 /* Get a graphics for the application to render into. 1356 * We initialize the buffer to white in order to 1357 * match the paper and then we shift the BufferedImage 1358 * so that it covers the area on the page where the 1359 * caller's Image will be drawn. 1360 */ 1361 Graphics2D g = deepImage.createGraphics(); 1362 ProxyGraphics2D proxy = new ProxyGraphics2D(g, wPrinterJob); 1363 proxy.setColor(Color.white); 1364 proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight()); 1365 proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight()); 1366 1367 proxy.translate(-region.getX(), -region.getY()); 1368 1369 /* Calculate the resolution of the source image. 1370 */ 1371 float sourceResX = (float)(wPrinterJob.getXRes() / scaleX); 1372 float sourceResY = (float)(wPrinterJob.getYRes() / scaleY); 1373 1374 /* The application expects to see user space at 72 dpi. 1375 * so change user space from image source resolution to 1376 * 72 dpi. 1377 */ 1378 proxy.scale(sourceResX / DEFAULT_USER_RES, 1379 sourceResY / DEFAULT_USER_RES); 1380 1381 proxy.translate( 1382 -wPrinterJob.getPhysicalPrintableX(pageFormat.getPaper()) 1383 / wPrinterJob.getXRes() * DEFAULT_USER_RES, 1384 -wPrinterJob.getPhysicalPrintableY(pageFormat.getPaper()) 1385 / wPrinterJob.getYRes() * DEFAULT_USER_RES); 1386 /* NB User space now has to be at 72 dpi for this calc to be correct */ 1387 proxy.transform(new AffineTransform(getPageFormat().getMatrix())); 1388 proxy.setPaint(Color.black); 1389 1390 painter.print(proxy, pageFormat, pageIndex); 1391 1392 g.dispose(); 1393 1394 /* We need to set the device clip using saved information. 1395 * savedClip intersects the user clip with a clip that restricts 1396 * the GDI rendered area of our BufferedImage to that which 1397 * may correspond to a rotate or shear. 1398 * The saved device transform is needed as the current transform 1399 * is not likely to be the same. 1400 */ 1401 deviceClip(savedClip.getPathIterator(savedTransform)); 1402 1403 /* Scale the bounding rectangle by the scale transform. 1404 * Because the scaling transform has only x and y 1405 * scaling components it is equivalent to multiplying 1406 * the x components of the bounding rectangle by 1407 * the x scaling factor and to multiplying the y components 1408 * by the y scaling factor. 1409 */ 1410 Rectangle2D.Float scaledBounds 1411 = new Rectangle2D.Float( 1412 (float) (region.getX() * scaleX), 1413 (float) (region.getY() * scaleY), 1414 (float) (region.getWidth() * scaleX), 1415 (float) (region.getHeight() * scaleY)); 1416 1417 /* Pull the raster data from the buffered image 1418 * and pass it along to GDI. 1419 */ 1420 ByteComponentRaster tile 1421 = (ByteComponentRaster)deepImage.getRaster(); 1422 1423 wPrinterJob.drawImage3ByteBGR(tile.getDataStorage(), 1424 scaledBounds.x, scaledBounds.y, 1425 scaledBounds.width, 1426 scaledBounds.height, 1427 0f, 0f, 1428 deepImage.getWidth(), deepImage.getHeight()); 1429 1430 } 1431 1432 /* 1433 * Fill the path defined by <code>pathIter</code> 1434 * with the specified color. 1435 * The path is provided in device coordinates. 1436 */ 1437 protected void deviceFill(PathIterator pathIter, Color color) { 1438 1439 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1440 1441 convertToWPath(pathIter); 1442 wPrinterJob.selectSolidBrush(color); 1443 wPrinterJob.fillPath(); 1444 } 1445 1446 /* 1447 * Set the printer device's clip to be the 1448 * path defined by <code>pathIter</code> 1449 * The path is provided in device coordinates. 1450 */ 1451 protected void deviceClip(PathIterator pathIter) { 1452 1453 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1454 1455 convertToWPath(pathIter); 1456 wPrinterJob.selectClipPath(); 1457 } 1458 1459 /** 1460 * Draw the bounding rectangle using transformed coordinates. 1461 */ 1462 protected void deviceFrameRect(int x, int y, int width, int height, 1463 Color color) { 1464 1465 AffineTransform deviceTransform = getTransform(); 1466 1467 /* check if rotated or sheared */ 1468 int transformType = deviceTransform.getType(); 1469 boolean usePath = ((transformType & 1470 (AffineTransform.TYPE_GENERAL_ROTATION | 1471 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 1472 1473 if (usePath) { 1474 draw(new Rectangle2D.Float(x, y, width, height)); 1475 return; 1476 } 1477 1478 Stroke stroke = getStroke(); 1479 1480 if (stroke instanceof BasicStroke) { 1481 BasicStroke lineStroke = (BasicStroke) stroke; 1482 1483 int endCap = lineStroke.getEndCap(); 1484 int lineJoin = lineStroke.getLineJoin(); 1485 1486 1487 /* check for default style and try to optimize it by 1488 * calling the frameRect native function instead of using paths. 1489 */ 1490 if ((endCap == BasicStroke.CAP_SQUARE) && 1491 (lineJoin == BasicStroke.JOIN_MITER) && 1492 (lineStroke.getMiterLimit() ==10.0f)) { 1493 1494 float lineWidth = lineStroke.getLineWidth(); 1495 Point2D.Float penSize = new Point2D.Float(lineWidth, 1496 lineWidth); 1497 1498 deviceTransform.deltaTransform(penSize, penSize); 1499 float deviceLineWidth = Math.min(Math.abs(penSize.x), 1500 Math.abs(penSize.y)); 1501 1502 /* transform upper left coordinate */ 1503 Point2D.Float ul_pos = new Point2D.Float(x, y); 1504 deviceTransform.transform(ul_pos, ul_pos); 1505 1506 /* transform lower right coordinate */ 1507 Point2D.Float lr_pos = new Point2D.Float(x + width, 1508 y + height); 1509 deviceTransform.transform(lr_pos, lr_pos); 1510 1511 float w = (float) (lr_pos.getX() - ul_pos.getX()); 1512 float h = (float)(lr_pos.getY() - ul_pos.getY()); 1513 1514 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1515 1516 /* use selectStylePen, if supported */ 1517 if (wPrinterJob.selectStylePen(endCap, lineJoin, 1518 deviceLineWidth, color) == true) { 1519 wPrinterJob.frameRect((float)ul_pos.getX(), 1520 (float)ul_pos.getY(), w, h); 1521 } 1522 /* not supported, must be a Win 9x */ 1523 else { 1524 1525 double lowerRes = Math.min(wPrinterJob.getXRes(), 1526 wPrinterJob.getYRes()); 1527 1528 if ((deviceLineWidth/lowerRes) < MAX_THINLINE_INCHES) { 1529 /* use the default pen styles for thin pens. */ 1530 wPrinterJob.selectPen(deviceLineWidth, color); 1531 wPrinterJob.frameRect((float)ul_pos.getX(), 1532 (float)ul_pos.getY(), w, h); 1533 } 1534 else { 1535 draw(new Rectangle2D.Float(x, y, width, height)); 1536 } 1537 } 1538 } 1539 else { 1540 draw(new Rectangle2D.Float(x, y, width, height)); 1541 } 1542 } 1543 } 1544 1545 1546 /* 1547 * Fill the rectangle with specified color and using Windows' 1548 * GDI fillRect function. 1549 * Boundaries are determined by the given coordinates. 1550 */ 1551 protected void deviceFillRect(int x, int y, int width, int height, 1552 Color color) { 1553 /* 1554 * Transform to device coordinates 1555 */ 1556 AffineTransform deviceTransform = getTransform(); 1557 1558 /* check if rotated or sheared */ 1559 int transformType = deviceTransform.getType(); 1560 boolean usePath = ((transformType & 1561 (AffineTransform.TYPE_GENERAL_ROTATION | 1562 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0); 1563 if (usePath) { 1564 fill(new Rectangle2D.Float(x, y, width, height)); 1565 return; 1566 } 1567 1568 Point2D.Float tlc_pos = new Point2D.Float(x, y); 1569 deviceTransform.transform(tlc_pos, tlc_pos); 1570 1571 Point2D.Float brc_pos = new Point2D.Float(x+width, y+height); 1572 deviceTransform.transform(brc_pos, brc_pos); 1573 1574 float deviceWidth = (float) (brc_pos.getX() - tlc_pos.getX()); 1575 float deviceHeight = (float)(brc_pos.getY() - tlc_pos.getY()); 1576 1577 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1578 wPrinterJob.fillRect((float)tlc_pos.getX(), (float)tlc_pos.getY(), 1579 deviceWidth, deviceHeight, color); 1580 } 1581 1582 1583 /** 1584 * Draw a line using a pen created using the specified color 1585 * and current stroke properties. 1586 */ 1587 protected void deviceDrawLine(int xBegin, int yBegin, int xEnd, int yEnd, 1588 Color color) { 1589 Stroke stroke = getStroke(); 1590 1591 if (stroke instanceof BasicStroke) { 1592 BasicStroke lineStroke = (BasicStroke) stroke; 1593 1594 if (lineStroke.getDashArray() != null) { 1595 draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd)); 1596 return; 1597 } 1598 1599 float lineWidth = lineStroke.getLineWidth(); 1600 Point2D.Float penSize = new Point2D.Float(lineWidth, lineWidth); 1601 1602 AffineTransform deviceTransform = getTransform(); 1603 deviceTransform.deltaTransform(penSize, penSize); 1604 1605 float deviceLineWidth = Math.min(Math.abs(penSize.x), 1606 Math.abs(penSize.y)); 1607 1608 Point2D.Float begin_pos = new Point2D.Float(xBegin, yBegin); 1609 deviceTransform.transform(begin_pos, begin_pos); 1610 1611 Point2D.Float end_pos = new Point2D.Float(xEnd, yEnd); 1612 deviceTransform.transform(end_pos, end_pos); 1613 1614 int endCap = lineStroke.getEndCap(); 1615 int lineJoin = lineStroke.getLineJoin(); 1616 1617 /* check if it's a one-pixel line */ 1618 if ((end_pos.getX() == begin_pos.getX()) 1619 && (end_pos.getY() == begin_pos.getY())) { 1620 1621 /* endCap other than Round will not print! 1622 * due to Windows GDI limitation, force it to CAP_ROUND 1623 */ 1624 endCap = BasicStroke.CAP_ROUND; 1625 } 1626 1627 1628 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1629 1630 /* call native function that creates pen with style */ 1631 if (wPrinterJob.selectStylePen(endCap, lineJoin, 1632 deviceLineWidth, color)) { 1633 wPrinterJob.moveTo((float)begin_pos.getX(), 1634 (float)begin_pos.getY()); 1635 wPrinterJob.lineTo((float)end_pos.getX(), 1636 (float)end_pos.getY()); 1637 } 1638 /* selectStylePen is not supported, must be Win 9X */ 1639 else { 1640 1641 /* let's see if we can use a a default pen 1642 * if it's round end (Windows' default style) 1643 * or it's vertical/horizontal 1644 * or stroke is too thin. 1645 */ 1646 double lowerRes = Math.min(wPrinterJob.getXRes(), 1647 wPrinterJob.getYRes()); 1648 1649 if ((endCap == BasicStroke.CAP_ROUND) || 1650 (((xBegin == xEnd) || (yBegin == yEnd)) && 1651 (deviceLineWidth/lowerRes < MAX_THINLINE_INCHES))) { 1652 1653 wPrinterJob.selectPen(deviceLineWidth, color); 1654 wPrinterJob.moveTo((float)begin_pos.getX(), 1655 (float)begin_pos.getY()); 1656 wPrinterJob.lineTo((float)end_pos.getX(), 1657 (float)end_pos.getY()); 1658 } 1659 else { 1660 draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd)); 1661 } 1662 } 1663 } 1664 } 1665 1666 1667 /** 1668 * Given a Java2D <code>PathIterator</code> instance, 1669 * this method translates that into a Window's path 1670 * in the printer device context. 1671 */ 1672 private void convertToWPath(PathIterator pathIter) { 1673 1674 float[] segment = new float[6]; 1675 int segmentType; 1676 1677 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob(); 1678 1679 /* Map the PathIterator's fill rule into the Window's 1680 * polygon fill rule. 1681 */ 1682 int polyFillRule; 1683 if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) { 1684 polyFillRule = WPrinterJob.POLYFILL_ALTERNATE; 1685 } else { 1686 polyFillRule = WPrinterJob.POLYFILL_WINDING; 1687 } 1688 wPrinterJob.setPolyFillMode(polyFillRule); 1689 1690 wPrinterJob.beginPath(); 1691 1692 while (pathIter.isDone() == false) { 1693 segmentType = pathIter.currentSegment(segment); 1694 1695 switch (segmentType) { 1696 case PathIterator.SEG_MOVETO: 1697 wPrinterJob.moveTo(segment[0], segment[1]); 1698 break; 1699 1700 case PathIterator.SEG_LINETO: 1701 wPrinterJob.lineTo(segment[0], segment[1]); 1702 break; 1703 1704 /* Convert the quad path to a bezier. 1705 */ 1706 case PathIterator.SEG_QUADTO: 1707 int lastX = wPrinterJob.getPenX(); 1708 int lastY = wPrinterJob.getPenY(); 1709 float c1x = lastX + (segment[0] - lastX) * 2 / 3; 1710 float c1y = lastY + (segment[1] - lastY) * 2 / 3; 1711 float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3; 1712 float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3; 1713 wPrinterJob.polyBezierTo(c1x, c1y, 1714 c2x, c2y, 1715 segment[2], segment[3]); 1716 break; 1717 1718 case PathIterator.SEG_CUBICTO: 1719 wPrinterJob.polyBezierTo(segment[0], segment[1], 1720 segment[2], segment[3], 1721 segment[4], segment[5]); 1722 break; 1723 1724 case PathIterator.SEG_CLOSE: 1725 wPrinterJob.closeFigure(); 1726 break; 1727 } 1728 1729 1730 pathIter.next(); 1731 } 1732 1733 wPrinterJob.endPath(); 1734 1735 } 1736 1737 }