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