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