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