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