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