1 /*
   2  * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt.windows;
  27 
  28 import java.awt.BasicStroke;
  29 import java.awt.Color;
  30 import java.awt.Font;
  31 import java.awt.Graphics;
  32 import java.awt.Graphics2D;
  33 import java.awt.Image;
  34 import java.awt.Shape;
  35 import java.awt.Stroke;
  36 import java.awt.Transparency;
  37 
  38 import java.awt.font.FontRenderContext;
  39 import java.awt.font.GlyphVector;
  40 import java.awt.font.TextLayout;
  41 
  42 import java.awt.geom.AffineTransform;
  43 import java.awt.geom.NoninvertibleTransformException;
  44 import java.awt.geom.PathIterator;
  45 import java.awt.geom.Point2D;
  46 import java.awt.geom.Rectangle2D;
  47 import java.awt.geom.Line2D;
  48 
  49 import java.awt.image.BufferedImage;
  50 import java.awt.image.ColorModel;
  51 import java.awt.image.DataBuffer;
  52 import java.awt.image.IndexColorModel;
  53 import java.awt.image.WritableRaster;
  54 import java.awt.image.ComponentSampleModel;
  55 import java.awt.image.MultiPixelPackedSampleModel;
  56 import java.awt.image.SampleModel;
  57 
  58 import sun.awt.image.ByteComponentRaster;
  59 import sun.awt.image.BytePackedRaster;
  60 import java.awt.print.PageFormat;
  61 import java.awt.print.Printable;
  62 import java.awt.print.PrinterException;
  63 import java.awt.print.PrinterJob;
  64 
  65 import java.util.Arrays;
  66 
  67 import sun.font.CharToGlyphMapper;
  68 import sun.font.CompositeFont;
  69 import sun.font.Font2D;
  70 import sun.font.FontUtilities;
  71 import sun.font.PhysicalFont;
  72 import sun.font.TrueTypeFont;
  73 
  74 import sun.print.PathGraphics;
  75 import sun.print.ProxyGraphics2D;
  76 
  77 final class WPathGraphics extends PathGraphics {
  78 
  79     /**
  80      * For a drawing application the initial user space
  81      * resolution is 72dpi.
  82      */
  83     private static final int DEFAULT_USER_RES = 72;
  84 
  85     private static final float MIN_DEVICE_LINEWIDTH = 1.2f;
  86     private static final float MAX_THINLINE_INCHES = 0.014f;
  87 
  88     /* Note that preferGDITextLayout implies useGDITextLayout.
  89      * "prefer" is used to override cases where would otherwise
  90      * choose not to use it. Note that non-layout factors may
  91      * still mean that GDI cannot be used.
  92      */
  93     private static boolean useGDITextLayout = true;
  94     private static boolean preferGDITextLayout = false;
  95     static {
  96         String textLayoutStr =
  97             java.security.AccessController.doPrivileged(
  98                    new sun.security.action.GetPropertyAction(
  99                          "sun.java2d.print.enableGDITextLayout"));
 100 
 101         if (textLayoutStr != null) {
 102             useGDITextLayout = Boolean.getBoolean(textLayoutStr);
 103             if (!useGDITextLayout) {
 104                 if (textLayoutStr.equalsIgnoreCase("prefer")) {
 105                     useGDITextLayout = true;
 106                     preferGDITextLayout = true;
 107                 }
 108             }
 109         }
 110     }
 111 
 112     WPathGraphics(Graphics2D graphics, PrinterJob printerJob,
 113                   Printable painter, PageFormat pageFormat, int pageIndex,
 114                   boolean canRedraw) {
 115         super(graphics, printerJob, painter, pageFormat, pageIndex, canRedraw);
 116     }
 117 
 118     /**
 119      * Creates a new <code>Graphics</code> object that is
 120      * a copy of this <code>Graphics</code> object.
 121      * @return     a new graphics context that is a copy of
 122      *                       this graphics context.
 123      * @since      1.0
 124      */
 125     @Override
 126     public Graphics create() {
 127 
 128         return new WPathGraphics((Graphics2D) getDelegate().create(),
 129                                  getPrinterJob(),
 130                                  getPrintable(),
 131                                  getPageFormat(),
 132                                  getPageIndex(),
 133                                  canDoRedraws());
 134     }
 135 
 136     /**
 137      * Strokes the outline of a Shape using the settings of the current
 138      * graphics state.  The rendering attributes applied include the
 139      * clip, transform, paint or color, composite and stroke attributes.
 140      * @param s The shape to be drawn.
 141      * @see #setStroke
 142      * @see #setPaint
 143      * @see java.awt.Graphics#setColor
 144      * @see #transform
 145      * @see #setTransform
 146      * @see #clip
 147      * @see #setClip
 148      * @see #setComposite
 149      */
 150     @Override
 151     public void draw(Shape s) {
 152 
 153         Stroke stroke = getStroke();
 154 
 155         /* If the line being drawn is thinner than can be
 156          * rendered, then change the line width, stroke
 157          * the shape, and then set the line width back.
 158          * We can only do this for BasicStroke's.
 159          */
 160         if (stroke instanceof BasicStroke) {
 161             BasicStroke lineStroke;
 162             BasicStroke minLineStroke = null;
 163             float deviceLineWidth;
 164             float lineWidth;
 165             AffineTransform deviceTransform;
 166             Point2D.Float penSize;
 167 
 168             /* Get the requested line width in user space.
 169              */
 170             lineStroke = (BasicStroke) stroke;
 171             lineWidth = lineStroke.getLineWidth();
 172             penSize = new Point2D.Float(lineWidth, lineWidth);
 173 
 174             /* Compute the line width in device coordinates.
 175              * Work on a point in case there is asymetric scaling
 176              * between user and device space.
 177              * Take the absolute value in case there is negative
 178              * scaling in effect.
 179              */
 180             deviceTransform = getTransform();
 181             deviceTransform.deltaTransform(penSize, penSize);
 182             deviceLineWidth = Math.min(Math.abs(penSize.x),
 183                                        Math.abs(penSize.y));
 184 
 185             /* If the requested line is too thin then map our
 186              * minimum line width back to user space and set
 187              * a new BasicStroke.
 188              */
 189             if (deviceLineWidth < MIN_DEVICE_LINEWIDTH) {
 190 
 191                 Point2D.Float minPenSize = new Point2D.Float(
 192                                                 MIN_DEVICE_LINEWIDTH,
 193                                                 MIN_DEVICE_LINEWIDTH);
 194 
 195                 try {
 196                     AffineTransform inverse;
 197                     float minLineWidth;
 198 
 199                     /* Convert the minimum line width from device
 200                      * space to user space.
 201                      */
 202                     inverse = deviceTransform.createInverse();
 203                     inverse.deltaTransform(minPenSize, minPenSize);
 204 
 205                     minLineWidth = Math.max(Math.abs(minPenSize.x),
 206                                             Math.abs(minPenSize.y));
 207 
 208                     /* Use all of the parameters from the current
 209                      * stroke but change the line width to our
 210                      * calculated minimum.
 211                      */
 212                     minLineStroke = new BasicStroke(minLineWidth,
 213                                                     lineStroke.getEndCap(),
 214                                                     lineStroke.getLineJoin(),
 215                                                     lineStroke.getMiterLimit(),
 216                                                     lineStroke.getDashArray(),
 217                                                     lineStroke.getDashPhase());
 218                     setStroke(minLineStroke);
 219 
 220                 } catch (NoninvertibleTransformException e) {
 221                     /* If we can't invert the matrix there is something
 222                      * very wrong so don't worry about the minor matter
 223                      * of a minimum line width.
 224                      */
 225                 }
 226             }
 227 
 228             super.draw(s);
 229 
 230             /* If we changed the stroke, put back the old
 231              * stroke in order to maintain a minimum line
 232              * width.
 233              */
 234             if (minLineStroke != null) {
 235                 setStroke(lineStroke);
 236             }
 237 
 238         /* The stroke in effect was not a BasicStroke so we
 239          * will not try to enforce a minimum line width.
 240          */
 241         } else {
 242             super.draw(s);
 243         }
 244     }
 245 
 246     /**
 247      * Draws the text given by the specified string, using this
 248      * graphics context's current font and color. The baseline of the
 249      * first character is at position (<i>x</i>,&nbsp;<i>y</i>) in this
 250      * graphics context's coordinate system.
 251      * @param       str      the string to be drawn.
 252      * @param       x        the <i>x</i> coordinate.
 253      * @param       y        the <i>y</i> coordinate.
 254      * @see         java.awt.Graphics#drawBytes
 255      * @see         java.awt.Graphics#drawChars
 256      * @since       1.0
 257      */
 258     @Override
 259     public void drawString(String str, int x, int y) {
 260         drawString(str, (float) x, (float) y);
 261     }
 262 
 263     @Override
 264      public void drawString(String str, float x, float y) {
 265          drawString(str, x, y, getFont(), getFontRenderContext(), 0f);
 266      }
 267 
 268     /* A return value of 0 would mean font not available to GDI, or the
 269      * it can't be used for this string.
 270      * A return of 1 means it is suitable, including for composites.
 271      * We check that the transform in effect is doable with GDI, and that
 272      * this is a composite font AWT can handle, or a physical font GDI
 273      * can handle directly. Its possible that some strings may ultimately
 274      * fail the more stringent tests in drawString but this is rare and
 275      * also that method will always succeed, as if the font isn't available
 276      * it will use outlines via a superclass call. Also it is only called for
 277      * the default render context (as canDrawStringToWidth() will return
 278      * false. That is why it ignores the frc and width arguments.
 279      */
 280     @Override
 281     protected int platformFontCount(Font font, String str) {
 282 
 283         AffineTransform deviceTransform = getTransform();
 284         AffineTransform fontTransform = new AffineTransform(deviceTransform);
 285         fontTransform.concatenate(getFont().getTransform());
 286         int transformType = fontTransform.getType();
 287 
 288         /* Test if GDI can handle the transform */
 289         boolean directToGDI = ((transformType !=
 290                                AffineTransform.TYPE_GENERAL_TRANSFORM)
 291                                && ((transformType & AffineTransform.TYPE_FLIP)
 292                                    == 0));
 293 
 294         if (!directToGDI) {
 295             return 0;
 296         }
 297 
 298         /* Since all windows fonts are available, and the JRE fonts
 299          * are also registered. Only the Font.createFont() case is presently
 300          * unknown to GDI. Those can be registered too, although that
 301          * code does not exist yet, it can be added too, so we should not
 302          * fail that case. Just do a quick check whether its a TrueTypeFont
 303          * - ie not a Type1 font etc, and let drawString() resolve the rest.
 304          */
 305         Font2D font2D = FontUtilities.getFont2D(font);
 306         if (font2D instanceof CompositeFont ||
 307             font2D instanceof TrueTypeFont) {
 308             return 1;
 309         } else {
 310             return 0;
 311         }
 312     }
 313 
 314     private static boolean isXP() {
 315         String osVersion = System.getProperty("os.version");
 316         if (osVersion != null) {
 317             Float version = Float.valueOf(osVersion);
 318             return (version.floatValue() >= 5.1f);
 319         } else {
 320             return false;
 321         }
 322     }
 323 
 324     /* In case GDI doesn't handle shaping or BIDI consistently with
 325      * 2D's TextLayout, we can detect these cases and redelegate up to
 326      * be drawn via TextLayout, which in is rendered as runs of
 327      * GlyphVectors, to which we can assign positions for each glyph.
 328      */
 329     private boolean strNeedsTextLayout(String str, Font font) {
 330         char[] chars = str.toCharArray();
 331         boolean isComplex = FontUtilities.isComplexText(chars, 0, chars.length);
 332         if (!isComplex) {
 333             return false;
 334         } else if (!useGDITextLayout) {
 335             return true;
 336         } else {
 337             if (preferGDITextLayout ||
 338                 (isXP() && FontUtilities.textLayoutIsCompatible(font))) {
 339                 return false;
 340             } else {
 341                 return true;
 342             }
 343         }
 344     }
 345 
 346     private int getAngle(Point2D.Double pt) {
 347         /* Get the rotation in 1/10'ths degree (as needed by Windows)
 348          * so that GDI can draw the text rotated.
 349          * This calculation is only valid for a uniform scale, no shearing.
 350          */
 351         double angle = Math.toDegrees(Math.atan2(pt.y, pt.x));
 352         if (angle < 0.0) {
 353             angle+= 360.0;
 354         }
 355         /* Windows specifies the rotation anti-clockwise from the x-axis
 356          * of the device, 2D specifies +ve rotation towards the y-axis
 357          * Since the 2D y-axis runs from top-to-bottom, windows angle of
 358          * rotation here is opposite than 2D's, so the rotation needed
 359          * needs to be recalculated in the opposite direction.
 360          */
 361         if (angle != 0.0) {
 362             angle = 360.0 - angle;
 363         }
 364         return (int)Math.round(angle * 10.0);
 365     }
 366 
 367     private float getAwScale(double scaleFactorX, double scaleFactorY) {
 368 
 369         float awScale = (float)(scaleFactorX/scaleFactorY);
 370         /* don't let rounding errors be interpreted as non-uniform scale */
 371         if (awScale > 0.999f && awScale < 1.001f) {
 372             awScale = 1.0f;
 373         }
 374         return awScale;
 375     }
 376 
 377     /**
 378      * Renders the text specified by the specified <code>String</code>,
 379      * using the current <code>Font</code> and <code>Paint</code> attributes
 380      * in the <code>Graphics2D</code> context.
 381      * The baseline of the first character is at position
 382      * (<i>x</i>,&nbsp;<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,&nbsp;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     @Override
 900     protected boolean drawImageToPlatform(Image image, AffineTransform xform,
 901                                           Color bgcolor,
 902                                           int srcX, int srcY,
 903                                           int srcWidth, int srcHeight,
 904                                           boolean handlingTransparency) {
 905 
 906         BufferedImage img = getBufferedImage(image);
 907         if (img == null) {
 908             return true;
 909         }
 910 
 911         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
 912 
 913         /* The full transform to be applied to the image is the
 914          * caller's transform concatenated on to the transform
 915          * from user space to device space. If the caller didn't
 916          * supply a transform then we just act as if they passed
 917          * in the identify transform.
 918          */
 919         AffineTransform fullTransform = getTransform();
 920         if (xform == null) {
 921             xform = new AffineTransform();
 922         }
 923         fullTransform.concatenate(xform);
 924 
 925         /* Split the full transform into a pair of
 926          * transforms. The first transform holds effects
 927          * that GDI (under Win95) can not perform such
 928          * as rotation and shearing. The second transform
 929          * is setup to hold only the scaling effects.
 930          * These transforms are created such that a point,
 931          * p, in user space, when transformed by 'fullTransform'
 932          * lands in the same place as when it is transformed
 933          * by 'rotTransform' and then 'scaleTransform'.
 934          *
 935          * The entire image transformation is not in Java in order
 936          * to minimize the amount of memory needed in the VM. By
 937          * dividing the transform in two, we rotate and shear
 938          * the source image in its own space and only go to
 939          * the, usually, larger, device space when we ask
 940          * GDI to perform the final scaling.
 941          * Clamp this to the device scale for better quality printing.
 942          */
 943         double[] fullMatrix = new double[6];
 944         fullTransform.getMatrix(fullMatrix);
 945 
 946         /* Calculate the amount of scaling in the x
 947          * and y directions. This scaling is computed by
 948          * transforming a unit vector along each axis
 949          * and computing the resulting magnitude.
 950          * The computed values 'scaleX' and 'scaleY'
 951          * represent the amount of scaling GDI will be asked
 952          * to perform.
 953          */
 954         Point2D.Float unitVectorX = new Point2D.Float(1, 0);
 955         Point2D.Float unitVectorY = new Point2D.Float(0, 1);
 956         fullTransform.deltaTransform(unitVectorX, unitVectorX);
 957         fullTransform.deltaTransform(unitVectorY, unitVectorY);
 958 
 959         Point2D.Float origin = new Point2D.Float(0, 0);
 960         double scaleX = unitVectorX.distance(origin);
 961         double scaleY = unitVectorY.distance(origin);
 962 
 963         double devResX = wPrinterJob.getXRes();
 964         double devResY = wPrinterJob.getYRes();
 965         double devScaleX = devResX / DEFAULT_USER_RES;
 966         double devScaleY = devResY / DEFAULT_USER_RES;
 967 
 968         /* check if rotated or sheared */
 969         int transformType = fullTransform.getType();
 970         boolean clampScale = ((transformType &
 971                                (AffineTransform.TYPE_GENERAL_ROTATION |
 972                                 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
 973         if (clampScale) {
 974             if (scaleX > devScaleX) scaleX = devScaleX;
 975             if (scaleY > devScaleY) scaleY = devScaleY;
 976         }
 977 
 978         /* We do not need to draw anything if either scaling
 979          * factor is zero.
 980          */
 981         if (scaleX != 0 && scaleY != 0) {
 982 
 983             /* Here's the transformation we will do with Java2D,
 984             */
 985             AffineTransform rotTransform = new AffineTransform(
 986                                         fullMatrix[0] / scaleX,  //m00
 987                                         fullMatrix[1] / scaleY,  //m10
 988                                         fullMatrix[2] / scaleX,  //m01
 989                                         fullMatrix[3] / scaleY,  //m11
 990                                         fullMatrix[4] / scaleX,  //m02
 991                                         fullMatrix[5] / scaleY); //m12
 992 
 993             /* The scale transform is not used directly: we instead
 994              * directly multiply by scaleX and scaleY.
 995              *
 996              * Conceptually here is what the scaleTransform is:
 997              *
 998              * AffineTransform scaleTransform = new AffineTransform(
 999              *                      scaleX,                     //m00
1000              *                      0,                          //m10
1001              *                      0,                          //m01
1002              *                      scaleY,                     //m11
1003              *                      0,                          //m02
1004              *                      0);                         //m12
1005              */
1006 
1007             /* Convert the image source's rectangle into the rotated
1008              * and sheared space. Once there, we calculate a rectangle
1009              * that encloses the resulting shape. It is this rectangle
1010              * which defines the size of the BufferedImage we need to
1011              * create to hold the transformed image.
1012              */
1013             Rectangle2D.Float srcRect = new Rectangle2D.Float(srcX, srcY,
1014                                                               srcWidth,
1015                                                               srcHeight);
1016 
1017             Shape rotShape = rotTransform.createTransformedShape(srcRect);
1018             Rectangle2D rotBounds = rotShape.getBounds2D();
1019 
1020             /* add a fudge factor as some fp precision problems have
1021              * been observed which caused pixels to be rounded down and
1022              * out of the image.
1023              */
1024             rotBounds.setRect(rotBounds.getX(), rotBounds.getY(),
1025                               rotBounds.getWidth()+0.001,
1026                               rotBounds.getHeight()+0.001);
1027 
1028             int boundsWidth = (int) rotBounds.getWidth();
1029             int boundsHeight = (int) rotBounds.getHeight();
1030 
1031             if (boundsWidth > 0 && boundsHeight > 0) {
1032 
1033                 /* If the image has transparent or semi-transparent
1034                  * pixels then we'll have the application re-render
1035                  * the portion of the page covered by the image.
1036                  * The BufferedImage will be at the image's resolution
1037                  * to avoid wasting memory. By re-rendering this portion
1038                  * of a page all compositing is done by Java2D into
1039                  * the BufferedImage and then that image is copied to
1040                  * GDI.
1041                  * However several special cases can be handled otherwise:
1042                  * - bitmask transparency with a solid background colour
1043                  * - images which have transparency color models but no
1044                  * transparent pixels
1045                  * - images with bitmask transparency and an IndexColorModel
1046                  * (the common transparent GIF case) can be handled by
1047                  * rendering just the opaque pixels.
1048                  */
1049                 boolean drawOpaque = true;
1050                 if (!handlingTransparency && hasTransparentPixels(img)) {
1051                     drawOpaque = false;
1052                     if (isBitmaskTransparency(img)) {
1053                         if (bgcolor == null) {
1054                             if (drawBitmaskImage(img, xform, bgcolor,
1055                                                  srcX, srcY,
1056                                                  srcWidth, srcHeight)) {
1057                                 // image drawn, just return.
1058                                 return true;
1059                             }
1060                         } else if (bgcolor.getTransparency()
1061                                    == Transparency.OPAQUE) {
1062                             drawOpaque = true;
1063                         }
1064                     }
1065                     if (!canDoRedraws()) {
1066                         drawOpaque = true;
1067                     }
1068                 } else {
1069                     // if there's no transparent pixels there's no need
1070                     // for a background colour. This can avoid edge artifacts
1071                     // in rotation cases.
1072                     bgcolor = null;
1073                 }
1074                 // if src region extends beyond the image, the "opaque" path
1075                 // may blit b/g colour (including white) where it shoudn't.
1076                 if ((srcX+srcWidth > img.getWidth(null) ||
1077                      srcY+srcHeight > img.getHeight(null))
1078                     && canDoRedraws()) {
1079                     drawOpaque = false;
1080                 }
1081                 if (drawOpaque == false) {
1082 
1083                     fullTransform.getMatrix(fullMatrix);
1084                     AffineTransform tx =
1085                         new AffineTransform(
1086                                             fullMatrix[0] / devScaleX,  //m00
1087                                             fullMatrix[1] / devScaleY,  //m10
1088                                             fullMatrix[2] / devScaleX,  //m01
1089                                             fullMatrix[3] / devScaleY,  //m11
1090                                             fullMatrix[4] / devScaleX,  //m02
1091                                             fullMatrix[5] / devScaleY); //m12
1092 
1093                     Rectangle2D.Float rect =
1094                         new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight);
1095 
1096                     Shape shape = fullTransform.createTransformedShape(rect);
1097                     // Region isn't user space because its potentially
1098                     // been rotated for landscape.
1099                     Rectangle2D region = shape.getBounds2D();
1100 
1101                     region.setRect(region.getX(), region.getY(),
1102                                    region.getWidth()+0.001,
1103                                    region.getHeight()+0.001);
1104 
1105                     // Try to limit the amount of memory used to 8Mb, so
1106                     // if at device resolution this exceeds a certain
1107                     // image size then scale down the region to fit in
1108                     // that memory, but never to less than 72 dpi.
1109 
1110                     int w = (int)region.getWidth();
1111                     int h = (int)region.getHeight();
1112                     int nbytes = w * h * 3;
1113                     int maxBytes = 8 * 1024 * 1024;
1114                     double origDpi = (devResX < devResY) ? devResX : devResY;
1115                     int dpi = (int)origDpi;
1116                     double scaleFactor = 1;
1117 
1118                     double maxSFX = w/(double)boundsWidth;
1119                     double maxSFY = h/(double)boundsHeight;
1120                     double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX;
1121                     int minDpi = (int)(dpi/maxSF);
1122                     if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES;
1123 
1124                     while (nbytes > maxBytes && dpi > minDpi) {
1125                         scaleFactor *= 2;
1126                         dpi /= 2;
1127                         nbytes /= 4;
1128                     }
1129                     if (dpi < minDpi) {
1130                         scaleFactor = (origDpi / minDpi);
1131                     }
1132 
1133                     region.setRect(region.getX()/scaleFactor,
1134                                    region.getY()/scaleFactor,
1135                                    region.getWidth()/scaleFactor,
1136                                    region.getHeight()/scaleFactor);
1137 
1138                     /*
1139                      * We need to have the clip as part of the saved state,
1140                      * either directly, or all the components that are
1141                      * needed to reconstitute it (image source area,
1142                      * image transform and current graphics transform).
1143                      * The clip is described in user space, so we need to
1144                      * save the current graphics transform anyway so just
1145                      * save these two.
1146                      */
1147                     wPrinterJob.saveState(getTransform(), getClip(),
1148                                           region, scaleFactor, scaleFactor);
1149                     return true;
1150                 /* The image can be rendered directly by GDI so we
1151                  * copy it into a BufferedImage (this takes care of
1152                  * ColorSpace and BufferedImageOp issues) and then
1153                  * send that to GDI.
1154                  */
1155                 } else {
1156                     /* Create a buffered image big enough to hold the portion
1157                      * of the source image being printed.
1158                      * The image format will be 3BYTE_BGR for most cases
1159                      * except where we can represent the image as a 1, 4 or 8
1160                      * bits-per-pixel DIB.
1161                      */
1162                     int dibType = BufferedImage.TYPE_3BYTE_BGR;
1163                     IndexColorModel icm = null;
1164 
1165                     ColorModel cm = img.getColorModel();
1166                     int imgType = img.getType();
1167                     if (cm instanceof IndexColorModel &&
1168                         cm.getPixelSize() <= 8 &&
1169                         (imgType == BufferedImage.TYPE_BYTE_BINARY ||
1170                          imgType == BufferedImage.TYPE_BYTE_INDEXED)) {
1171                         icm = (IndexColorModel)cm;
1172                         dibType = imgType;
1173                         /* BYTE_BINARY may be 2 bpp which DIB can't handle.
1174                          * Convert this to 4bpp.
1175                          */
1176                         if (imgType == BufferedImage.TYPE_BYTE_BINARY &&
1177                             cm.getPixelSize() == 2) {
1178 
1179                             int[] rgbs = new int[16];
1180                             icm.getRGBs(rgbs);
1181                             boolean transparent =
1182                                 icm.getTransparency() != Transparency.OPAQUE;
1183                             int transpixel = icm.getTransparentPixel();
1184 
1185                             icm = new IndexColorModel(4, 16,
1186                                                       rgbs, 0,
1187                                                       transparent, transpixel,
1188                                                       DataBuffer.TYPE_BYTE);
1189                         }
1190                     }
1191 
1192                     int iw = (int)rotBounds.getWidth();
1193                     int ih = (int)rotBounds.getHeight();
1194                     BufferedImage deepImage = null;
1195                     /* If there is no special transform needed (this is a
1196                      * simple BLIT) and dibType == img.getType() and we
1197                      * didn't create a new IndexColorModel AND the whole of
1198                      * the source image is being drawn (GDI can't handle a
1199                      * portion of the original source image) then we
1200                      * don't need to create this intermediate image - GDI
1201                      * can access the data from the original image.
1202                      * Since a subimage can be created by calling
1203                      * BufferedImage.getSubImage() that condition needs to
1204                      * be accounted for too. This implies inspecting the
1205                      * data buffer. In the end too many cases are not able
1206                      * to take advantage of this option until we can teach
1207                      * the native code to properly navigate the data buffer.
1208                      * There was a concern that since in native code since we
1209                      * need to DWORD align and flip to a bottom up DIB that
1210                      * the "original" image may get perturbed by this.
1211                      * But in fact we always malloc new memory for the aligned
1212                      * copy so this isn't a problem.
1213                      * This points out that we allocate two temporaries copies
1214                      * of the image : one in Java and one in native. If
1215                      * we can be smarter about not allocating this one when
1216                      * not needed, that would seem like a good thing to do,
1217                      * even if in many cases the ColorModels don't match and
1218                      * its needed.
1219                      * Until all of this is resolved newImage is always true.
1220                      */
1221                     boolean newImage = true;
1222                     if (newImage) {
1223                         if (icm == null) {
1224                             deepImage = new BufferedImage(iw, ih, dibType);
1225                         } else {
1226                             deepImage = new BufferedImage(iw, ih, dibType,icm);
1227                         }
1228 
1229                         /* Setup a Graphics2D on to the BufferedImage so that
1230                          * the source image when copied, lands within the
1231                          * image buffer.
1232                          */
1233                         Graphics2D imageGraphics = deepImage.createGraphics();
1234                         imageGraphics.clipRect(0, 0,
1235                                                deepImage.getWidth(),
1236                                                deepImage.getHeight());
1237 
1238                         imageGraphics.translate(-rotBounds.getX(),
1239                                                 -rotBounds.getY());
1240                         imageGraphics.transform(rotTransform);
1241 
1242                         /* Fill the BufferedImage either with the caller
1243                          * supplied color, 'bgColor' or, if null, with white.
1244                          */
1245                         if (bgcolor == null) {
1246                             bgcolor = Color.white;
1247                         }
1248 
1249                         imageGraphics.drawImage(img,
1250                                                 srcX, srcY,
1251                                                 srcX + srcWidth,
1252                                                 srcY + srcHeight,
1253                                                 srcX, srcY,
1254                                                 srcX + srcWidth,
1255                                                 srcY + srcHeight,
1256                                                 bgcolor, null);
1257                         imageGraphics.dispose();
1258                     } else {
1259                         deepImage = img;
1260                     }
1261 
1262                     /* Scale the bounding rectangle by the scale transform.
1263                      * Because the scaling transform has only x and y
1264                      * scaling components it is equivalent to multiply
1265                      * the x components of the bounding rectangle by
1266                      * the x scaling factor and to multiply the y components
1267                      * by the y scaling factor.
1268                      */
1269                     Rectangle2D.Float scaledBounds
1270                             = new Rectangle2D.Float(
1271                                     (float) (rotBounds.getX() * scaleX),
1272                                     (float) (rotBounds.getY() * scaleY),
1273                                     (float) (rotBounds.getWidth() * scaleX),
1274                                     (float) (rotBounds.getHeight() * scaleY));
1275 
1276                     /* Pull the raster data from the buffered image
1277                      * and pass it along to GDI.
1278                      */
1279                     WritableRaster raster = deepImage.getRaster();
1280                     byte[] data;
1281                     if (raster instanceof ByteComponentRaster) {
1282                         data = ((ByteComponentRaster)raster).getDataStorage();
1283                     } else if (raster instanceof BytePackedRaster) {
1284                         data = ((BytePackedRaster)raster).getDataStorage();
1285                     } else {
1286                         return false;
1287                     }
1288 
1289                     int bitsPerPixel = 24;
1290                     SampleModel sm = deepImage.getSampleModel();
1291                     if (sm instanceof ComponentSampleModel) {
1292                         ComponentSampleModel csm = (ComponentSampleModel)sm;
1293                         bitsPerPixel = csm.getPixelStride() * 8;
1294                     } else if (sm instanceof MultiPixelPackedSampleModel) {
1295                         MultiPixelPackedSampleModel mppsm =
1296                             (MultiPixelPackedSampleModel)sm;
1297                         bitsPerPixel = mppsm.getPixelBitStride();
1298                     } else {
1299                         if (icm != null) {
1300                             int diw = deepImage.getWidth();
1301                             int dih = deepImage.getHeight();
1302                             if (diw > 0 && dih > 0) {
1303                                 bitsPerPixel = data.length*8/diw/dih;
1304                             }
1305                         }
1306                     }
1307 
1308                     /* Because the caller's image has been rotated
1309                      * and sheared into our BufferedImage and because
1310                      * we will be handing that BufferedImage directly to
1311                      * GDI, we need to set an additional clip. This clip
1312                      * makes sure that only parts of the BufferedImage
1313                      * that are also part of the caller's image are drawn.
1314                      */
1315                     Shape holdClip = getClip();
1316                     clip(xform.createTransformedShape(srcRect));
1317                     deviceClip(getClip().getPathIterator(getTransform()));
1318 
1319                     wPrinterJob.drawDIBImage
1320                         (data, scaledBounds.x, scaledBounds.y,
1321                          (float)Math.rint(scaledBounds.width+0.5),
1322                          (float)Math.rint(scaledBounds.height+0.5),
1323                          0f, 0f,
1324                          deepImage.getWidth(), deepImage.getHeight(),
1325                          bitsPerPixel, icm);
1326 
1327                     setClip(holdClip);
1328                 }
1329             }
1330         }
1331 
1332         return true;
1333     }
1334 
1335     /**
1336      * Have the printing application redraw everything that falls
1337      * within the page bounds defined by <code>region</code>.
1338      */
1339     @Override
1340     public void redrawRegion(Rectangle2D region, double scaleX, double scaleY,
1341                              Shape savedClip, AffineTransform savedTransform)
1342             throws PrinterException {
1343 
1344         WPrinterJob wPrinterJob = (WPrinterJob)getPrinterJob();
1345         Printable painter = getPrintable();
1346         PageFormat pageFormat = getPageFormat();
1347         int pageIndex = getPageIndex();
1348 
1349         /* Create a buffered image big enough to hold the portion
1350          * of the source image being printed.
1351          */
1352         BufferedImage deepImage = new BufferedImage(
1353                                         (int) region.getWidth(),
1354                                         (int) region.getHeight(),
1355                                         BufferedImage.TYPE_3BYTE_BGR);
1356 
1357         /* Get a graphics for the application to render into.
1358          * We initialize the buffer to white in order to
1359          * match the paper and then we shift the BufferedImage
1360          * so that it covers the area on the page where the
1361          * caller's Image will be drawn.
1362          */
1363         Graphics2D g = deepImage.createGraphics();
1364         ProxyGraphics2D proxy = new ProxyGraphics2D(g, wPrinterJob);
1365         proxy.setColor(Color.white);
1366         proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
1367         proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
1368 
1369         proxy.translate(-region.getX(), -region.getY());
1370 
1371         /* Calculate the resolution of the source image.
1372          */
1373         float sourceResX = (float)(wPrinterJob.getXRes() / scaleX);
1374         float sourceResY = (float)(wPrinterJob.getYRes() / scaleY);
1375 
1376         /* The application expects to see user space at 72 dpi.
1377          * so change user space from image source resolution to
1378          *  72 dpi.
1379          */
1380         proxy.scale(sourceResX / DEFAULT_USER_RES,
1381                     sourceResY / DEFAULT_USER_RES);
1382 
1383         proxy.translate(
1384             -wPrinterJob.getPhysicalPrintableX(pageFormat.getPaper())
1385                / wPrinterJob.getXRes() * DEFAULT_USER_RES,
1386             -wPrinterJob.getPhysicalPrintableY(pageFormat.getPaper())
1387                / wPrinterJob.getYRes() * DEFAULT_USER_RES);
1388         /* NB User space now has to be at 72 dpi for this calc to be correct */
1389         proxy.transform(new AffineTransform(getPageFormat().getMatrix()));
1390         proxy.setPaint(Color.black);
1391 
1392         painter.print(proxy, pageFormat, pageIndex);
1393 
1394         g.dispose();
1395 
1396         /* We need to set the device clip using saved information.
1397          * savedClip intersects the user clip with a clip that restricts
1398          * the GDI rendered area of our BufferedImage to that which
1399          * may correspond to a rotate or shear.
1400          * The saved device transform is needed as the current transform
1401          * is not likely to be the same.
1402          */
1403         if (savedClip != null) {
1404             deviceClip(savedClip.getPathIterator(savedTransform));
1405         }
1406 
1407         /* Scale the bounding rectangle by the scale transform.
1408          * Because the scaling transform has only x and y
1409          * scaling components it is equivalent to multiplying
1410          * the x components of the bounding rectangle by
1411          * the x scaling factor and to multiplying the y components
1412          * by the y scaling factor.
1413          */
1414         Rectangle2D.Float scaledBounds
1415                 = new Rectangle2D.Float(
1416                         (float) (region.getX() * scaleX),
1417                         (float) (region.getY() * scaleY),
1418                         (float) (region.getWidth() * scaleX),
1419                         (float) (region.getHeight() * scaleY));
1420 
1421         /* Pull the raster data from the buffered image
1422          * and pass it along to GDI.
1423          */
1424        ByteComponentRaster tile
1425                 = (ByteComponentRaster)deepImage.getRaster();
1426 
1427         wPrinterJob.drawImage3ByteBGR(tile.getDataStorage(),
1428                     scaledBounds.x, scaledBounds.y,
1429                     scaledBounds.width,
1430                     scaledBounds.height,
1431                     0f, 0f,
1432                     deepImage.getWidth(), deepImage.getHeight());
1433 
1434     }
1435 
1436     /*
1437      * Fill the path defined by <code>pathIter</code>
1438      * with the specified color.
1439      * The path is provided in device coordinates.
1440      */
1441     @Override
1442     protected void deviceFill(PathIterator pathIter, Color color) {
1443 
1444         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1445 
1446         convertToWPath(pathIter);
1447         wPrinterJob.selectSolidBrush(color);
1448         wPrinterJob.fillPath();
1449     }
1450 
1451     /*
1452      * Set the printer device's clip to be the
1453      * path defined by <code>pathIter</code>
1454      * The path is provided in device coordinates.
1455      */
1456     @Override
1457     protected void deviceClip(PathIterator pathIter) {
1458 
1459         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1460 
1461         convertToWPath(pathIter);
1462         wPrinterJob.selectClipPath();
1463     }
1464 
1465     /**
1466      * Draw the bounding rectangle using transformed coordinates.
1467      */
1468      @Override
1469      protected void deviceFrameRect(int x, int y, int width, int height,
1470                                      Color color) {
1471 
1472         AffineTransform deviceTransform = getTransform();
1473 
1474         /* check if rotated or sheared */
1475         int transformType = deviceTransform.getType();
1476         boolean usePath = ((transformType &
1477                            (AffineTransform.TYPE_GENERAL_ROTATION |
1478                             AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
1479 
1480         if (usePath) {
1481             draw(new Rectangle2D.Float(x, y, width, height));
1482             return;
1483         }
1484 
1485         Stroke stroke = getStroke();
1486 
1487         if (stroke instanceof BasicStroke) {
1488             BasicStroke lineStroke = (BasicStroke) stroke;
1489 
1490             int endCap = lineStroke.getEndCap();
1491             int lineJoin = lineStroke.getLineJoin();
1492 
1493 
1494             /* check for default style and try to optimize it by
1495              * calling the frameRect native function instead of using paths.
1496              */
1497             if ((endCap == BasicStroke.CAP_SQUARE) &&
1498                 (lineJoin == BasicStroke.JOIN_MITER) &&
1499                 (lineStroke.getMiterLimit() ==10.0f)) {
1500 
1501                 float lineWidth = lineStroke.getLineWidth();
1502                 Point2D.Float penSize = new Point2D.Float(lineWidth,
1503                                                           lineWidth);
1504 
1505                 deviceTransform.deltaTransform(penSize, penSize);
1506                 float deviceLineWidth = Math.min(Math.abs(penSize.x),
1507                                                  Math.abs(penSize.y));
1508 
1509                 /* transform upper left coordinate */
1510                 Point2D.Float ul_pos = new Point2D.Float(x, y);
1511                 deviceTransform.transform(ul_pos, ul_pos);
1512 
1513                 /* transform lower right coordinate */
1514                 Point2D.Float lr_pos = new Point2D.Float(x + width,
1515                                                          y + height);
1516                 deviceTransform.transform(lr_pos, lr_pos);
1517 
1518                 float w = (float) (lr_pos.getX() - ul_pos.getX());
1519                 float h = (float)(lr_pos.getY() - ul_pos.getY());
1520 
1521                 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1522 
1523                 /* use selectStylePen, if supported */
1524                 if (wPrinterJob.selectStylePen(endCap, lineJoin,
1525                                            deviceLineWidth, color) == true)  {
1526                     wPrinterJob.frameRect((float)ul_pos.getX(),
1527                                           (float)ul_pos.getY(), w, h);
1528                 }
1529                 /* not supported, must be a Win 9x */
1530                 else {
1531 
1532                     double lowerRes = Math.min(wPrinterJob.getXRes(),
1533                                                wPrinterJob.getYRes());
1534 
1535                     if ((deviceLineWidth/lowerRes) < MAX_THINLINE_INCHES) {
1536                         /* use the default pen styles for thin pens. */
1537                         wPrinterJob.selectPen(deviceLineWidth, color);
1538                         wPrinterJob.frameRect((float)ul_pos.getX(),
1539                                               (float)ul_pos.getY(), w, h);
1540                     }
1541                     else {
1542                         draw(new Rectangle2D.Float(x, y, width, height));
1543                     }
1544                 }
1545             }
1546             else {
1547                 draw(new Rectangle2D.Float(x, y, width, height));
1548             }
1549         }
1550      }
1551 
1552 
1553      /*
1554       * Fill the rectangle with specified color and using Windows'
1555       * GDI fillRect function.
1556       * Boundaries are determined by the given coordinates.
1557       */
1558     @Override
1559     protected void deviceFillRect(int x, int y, int width, int height,
1560                                   Color color) {
1561         /*
1562          * Transform to device coordinates
1563          */
1564         AffineTransform deviceTransform = getTransform();
1565 
1566         /* check if rotated or sheared */
1567         int transformType = deviceTransform.getType();
1568         boolean usePath =  ((transformType &
1569                                (AffineTransform.TYPE_GENERAL_ROTATION |
1570                                 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
1571         if (usePath) {
1572             fill(new Rectangle2D.Float(x, y, width, height));
1573             return;
1574         }
1575 
1576         Point2D.Float tlc_pos = new Point2D.Float(x, y);
1577         deviceTransform.transform(tlc_pos, tlc_pos);
1578 
1579         Point2D.Float brc_pos = new Point2D.Float(x+width, y+height);
1580         deviceTransform.transform(brc_pos, brc_pos);
1581 
1582         float deviceWidth = (float) (brc_pos.getX() - tlc_pos.getX());
1583         float deviceHeight = (float)(brc_pos.getY() - tlc_pos.getY());
1584 
1585         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1586         wPrinterJob.fillRect((float)tlc_pos.getX(), (float)tlc_pos.getY(),
1587                              deviceWidth, deviceHeight, color);
1588     }
1589 
1590 
1591     /**
1592      * Draw a line using a pen created using the specified color
1593      * and current stroke properties.
1594      */
1595     @Override
1596     protected void deviceDrawLine(int xBegin, int yBegin, int xEnd, int yEnd,
1597                                   Color color) {
1598         Stroke stroke = getStroke();
1599 
1600         if (stroke instanceof BasicStroke) {
1601             BasicStroke lineStroke = (BasicStroke) stroke;
1602 
1603             if (lineStroke.getDashArray() != null) {
1604                 draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd));
1605                 return;
1606             }
1607 
1608             float lineWidth = lineStroke.getLineWidth();
1609             Point2D.Float penSize = new Point2D.Float(lineWidth, lineWidth);
1610 
1611             AffineTransform deviceTransform = getTransform();
1612             deviceTransform.deltaTransform(penSize, penSize);
1613 
1614             float deviceLineWidth = Math.min(Math.abs(penSize.x),
1615                                              Math.abs(penSize.y));
1616 
1617             Point2D.Float begin_pos = new Point2D.Float(xBegin, yBegin);
1618             deviceTransform.transform(begin_pos, begin_pos);
1619 
1620             Point2D.Float end_pos = new Point2D.Float(xEnd, yEnd);
1621             deviceTransform.transform(end_pos, end_pos);
1622 
1623             int endCap = lineStroke.getEndCap();
1624             int lineJoin = lineStroke.getLineJoin();
1625 
1626             /* check if it's a one-pixel line */
1627             if ((end_pos.getX() == begin_pos.getX())
1628                 && (end_pos.getY() == begin_pos.getY())) {
1629 
1630                 /* endCap other than Round will not print!
1631                  * due to Windows GDI limitation, force it to CAP_ROUND
1632                  */
1633                 endCap = BasicStroke.CAP_ROUND;
1634             }
1635 
1636 
1637             WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1638 
1639             /* call native function that creates pen with style */
1640             if (wPrinterJob.selectStylePen(endCap, lineJoin,
1641                                            deviceLineWidth, color)) {
1642                 wPrinterJob.moveTo((float)begin_pos.getX(),
1643                                    (float)begin_pos.getY());
1644                 wPrinterJob.lineTo((float)end_pos.getX(),
1645                                    (float)end_pos.getY());
1646             }
1647             /* selectStylePen is not supported, must be Win 9X */
1648             else {
1649 
1650                 /* let's see if we can use a a default pen
1651                  *  if it's round end (Windows' default style)
1652                  *  or it's vertical/horizontal
1653                  *  or stroke is too thin.
1654                  */
1655                 double lowerRes = Math.min(wPrinterJob.getXRes(),
1656                                            wPrinterJob.getYRes());
1657 
1658                 if ((endCap == BasicStroke.CAP_ROUND) ||
1659                  (((xBegin == xEnd) || (yBegin == yEnd)) &&
1660                  (deviceLineWidth/lowerRes < MAX_THINLINE_INCHES))) {
1661 
1662                     wPrinterJob.selectPen(deviceLineWidth, color);
1663                     wPrinterJob.moveTo((float)begin_pos.getX(),
1664                                        (float)begin_pos.getY());
1665                     wPrinterJob.lineTo((float)end_pos.getX(),
1666                                        (float)end_pos.getY());
1667                 }
1668                 else {
1669                     draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd));
1670                 }
1671             }
1672         }
1673     }
1674 
1675 
1676     /**
1677      * Given a Java2D <code>PathIterator</code> instance,
1678      * this method translates that into a Window's path
1679      * in the printer device context.
1680      */
1681     private void convertToWPath(PathIterator pathIter) {
1682 
1683         float[] segment = new float[6];
1684         int segmentType;
1685 
1686         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1687 
1688         /* Map the PathIterator's fill rule into the Window's
1689          * polygon fill rule.
1690          */
1691         int polyFillRule;
1692         if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
1693             polyFillRule = WPrinterJob.POLYFILL_ALTERNATE;
1694         } else {
1695             polyFillRule = WPrinterJob.POLYFILL_WINDING;
1696         }
1697         wPrinterJob.setPolyFillMode(polyFillRule);
1698 
1699         wPrinterJob.beginPath();
1700 
1701         while (pathIter.isDone() == false) {
1702             segmentType = pathIter.currentSegment(segment);
1703 
1704             switch (segmentType) {
1705              case PathIterator.SEG_MOVETO:
1706                 wPrinterJob.moveTo(segment[0], segment[1]);
1707                 break;
1708 
1709              case PathIterator.SEG_LINETO:
1710                 wPrinterJob.lineTo(segment[0], segment[1]);
1711                 break;
1712 
1713             /* Convert the quad path to a bezier.
1714              */
1715              case PathIterator.SEG_QUADTO:
1716                 int lastX = wPrinterJob.getPenX();
1717                 int lastY = wPrinterJob.getPenY();
1718                 float c1x = lastX + (segment[0] - lastX) * 2 / 3;
1719                 float c1y = lastY + (segment[1] - lastY) * 2 / 3;
1720                 float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3;
1721                 float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3;
1722                 wPrinterJob.polyBezierTo(c1x, c1y,
1723                                          c2x, c2y,
1724                                          segment[2], segment[3]);
1725                 break;
1726 
1727              case PathIterator.SEG_CUBICTO:
1728                 wPrinterJob.polyBezierTo(segment[0], segment[1],
1729                                          segment[2], segment[3],
1730                                          segment[4], segment[5]);
1731                 break;
1732 
1733              case PathIterator.SEG_CLOSE:
1734                 wPrinterJob.closeFigure();
1735                 break;
1736             }
1737 
1738 
1739             pathIter.next();
1740         }
1741 
1742         wPrinterJob.endPath();
1743 
1744     }
1745 
1746 }