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             (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 
WPathGraphics(Graphics2D graphics, PrinterJob printerJob, Printable painter, PageFormat pageFormat, int pageIndex, boolean canRedraw)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
create()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
draw(Shape s)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
drawString(String str, int x, int y)259     public void drawString(String str, int x, int y) {
260         drawString(str, (float) x, (float) y);
261     }
262 
263     @Override
drawString(String str, float x, float y)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
platformFontCount(Font font, String str)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 
isXP()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      */
strNeedsTextLayout(String str, Font font)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 
getAngle(Point2D.Double pt)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 
getAwScale(double scaleFactorX, double scaleFactorY)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
drawString(String str, float x, float y, Font font, FontRenderContext frc, float targetW)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
printGlyphVector(GlyphVector gv, float x, float y)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 
textOut(String str, Font font, PhysicalFont font2D, FontRenderContext frc, float deviceSize, int rotation, float awScale, double scaleFactorX, double scaleFactorY, float userx, float usery, float devx, float devy, float targetW)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       */
okGDIMetrics(String str, Font font, FontRenderContext frc, double scaleX)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()</code> methods for
918      * <code>WPathGraphics</code> are all decomposed
919      * into an invocation of <code>drawImageToPlatform</code>.
920      * The portion of the passed in image defined by
921      * <code>srcX, srcY, srcWidth, and srcHeight</code>
922      * is transformed by the supplied AffineTransform and
923      * drawn using GDI to the printer context.
924      *
925      * @param   img     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
drawImageToPlatform(Image image, AffineTransform xform, Color bgcolor, int srcX, int srcY, int srcWidth, int srcHeight, boolean handlingTransparency)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 (!handlingTransparency && hasTransparentPixels(img)) {
1096                     drawOpaque = false;
1097                     if (isBitmaskTransparency(img)) {
1098                         if (bgcolor == null) {
1099                             if (drawBitmaskImage(img, xform, bgcolor,
1100                                                  srcX, srcY,
1101                                                  srcWidth, srcHeight)) {
1102                                 // image drawn, just return.
1103                                 return true;
1104                             }
1105                         } else if (bgcolor.getTransparency()
1106                                    == Transparency.OPAQUE) {
1107                             drawOpaque = true;
1108                         }
1109                     }
1110                     if (!canDoRedraws()) {
1111                         drawOpaque = true;
1112                     }
1113                 } else {
1114                     // if there's no transparent pixels there's no need
1115                     // for a background colour. This can avoid edge artifacts
1116                     // in rotation cases.
1117                     bgcolor = null;
1118                 }
1119                 // if src region extends beyond the image, the "opaque" path
1120                 // may blit b/g colour (including white) where it shoudn't.
1121                 if ((srcX+srcWidth > img.getWidth(null) ||
1122                      srcY+srcHeight > img.getHeight(null))
1123                     && canDoRedraws()) {
1124                     drawOpaque = false;
1125                 }
1126                 if (drawOpaque == false) {
1127 
1128                     fullTransform.getMatrix(fullMatrix);
1129                     AffineTransform tx =
1130                         new AffineTransform(
1131                                             fullMatrix[0] / devScaleX,  //m00
1132                                             fullMatrix[1] / devScaleY,  //m10
1133                                             fullMatrix[2] / devScaleX,  //m01
1134                                             fullMatrix[3] / devScaleY,  //m11
1135                                             fullMatrix[4] / devScaleX,  //m02
1136                                             fullMatrix[5] / devScaleY); //m12
1137 
1138                     Rectangle2D.Float rect =
1139                         new Rectangle2D.Float(srcX, srcY, srcWidth, srcHeight);
1140 
1141                     Shape shape = fullTransform.createTransformedShape(rect);
1142                     // Region isn't user space because its potentially
1143                     // been rotated for landscape.
1144                     Rectangle2D region = shape.getBounds2D();
1145 
1146                     region.setRect(region.getX(), region.getY(),
1147                                    region.getWidth()+0.001,
1148                                    region.getHeight()+0.001);
1149 
1150                     // Try to limit the amount of memory used to 8Mb, so
1151                     // if at device resolution this exceeds a certain
1152                     // image size then scale down the region to fit in
1153                     // that memory, but never to less than 72 dpi.
1154 
1155                     int w = (int)region.getWidth();
1156                     int h = (int)region.getHeight();
1157                     int nbytes = w * h * 3;
1158                     int maxBytes = 8 * 1024 * 1024;
1159                     double origDpi = (devResX < devResY) ? devResX : devResY;
1160                     int dpi = (int)origDpi;
1161                     double scaleFactor = 1;
1162 
1163                     double maxSFX = w/(double)boundsWidth;
1164                     double maxSFY = h/(double)boundsHeight;
1165                     double maxSF = (maxSFX > maxSFY) ? maxSFY : maxSFX;
1166                     int minDpi = (int)(dpi/maxSF);
1167                     if (minDpi < DEFAULT_USER_RES) minDpi = DEFAULT_USER_RES;
1168 
1169                     while (nbytes > maxBytes && dpi > minDpi) {
1170                         scaleFactor *= 2;
1171                         dpi /= 2;
1172                         nbytes /= 4;
1173                     }
1174                     if (dpi < minDpi) {
1175                         scaleFactor = (origDpi / minDpi);
1176                     }
1177 
1178                     region.setRect(region.getX()/scaleFactor,
1179                                    region.getY()/scaleFactor,
1180                                    region.getWidth()/scaleFactor,
1181                                    region.getHeight()/scaleFactor);
1182 
1183                     /*
1184                      * We need to have the clip as part of the saved state,
1185                      * either directly, or all the components that are
1186                      * needed to reconstitute it (image source area,
1187                      * image transform and current graphics transform).
1188                      * The clip is described in user space, so we need to
1189                      * save the current graphics transform anyway so just
1190                      * save these two.
1191                      */
1192                     wPrinterJob.saveState(getTransform(), getClip(),
1193                                           region, scaleFactor, scaleFactor);
1194                     return true;
1195                 /* The image can be rendered directly by GDI so we
1196                  * copy it into a BufferedImage (this takes care of
1197                  * ColorSpace and BufferedImageOp issues) and then
1198                  * send that to GDI.
1199                  */
1200                 } else {
1201                     /* Create a buffered image big enough to hold the portion
1202                      * of the source image being printed.
1203                      * The image format will be 3BYTE_BGR for most cases
1204                      * except where we can represent the image as a 1, 4 or 8
1205                      * bits-per-pixel DIB.
1206                      */
1207                     int dibType = BufferedImage.TYPE_3BYTE_BGR;
1208                     IndexColorModel icm = null;
1209 
1210                     ColorModel cm = img.getColorModel();
1211                     int imgType = img.getType();
1212                     if (cm instanceof IndexColorModel &&
1213                         cm.getPixelSize() <= 8 &&
1214                         (imgType == BufferedImage.TYPE_BYTE_BINARY ||
1215                          imgType == BufferedImage.TYPE_BYTE_INDEXED)) {
1216                         icm = (IndexColorModel)cm;
1217                         dibType = imgType;
1218                         /* BYTE_BINARY may be 2 bpp which DIB can't handle.
1219                          * Convert this to 4bpp.
1220                          */
1221                         if (imgType == BufferedImage.TYPE_BYTE_BINARY &&
1222                             cm.getPixelSize() == 2) {
1223 
1224                             int[] rgbs = new int[16];
1225                             icm.getRGBs(rgbs);
1226                             boolean transparent =
1227                                 icm.getTransparency() != Transparency.OPAQUE;
1228                             int transpixel = icm.getTransparentPixel();
1229 
1230                             icm = new IndexColorModel(4, 16,
1231                                                       rgbs, 0,
1232                                                       transparent, transpixel,
1233                                                       DataBuffer.TYPE_BYTE);
1234                         }
1235                     }
1236 
1237                     int iw = (int)rotBounds.getWidth();
1238                     int ih = (int)rotBounds.getHeight();
1239                     BufferedImage deepImage = null;
1240                     /* If there is no special transform needed (this is a
1241                      * simple BLIT) and dibType == img.getType() and we
1242                      * didn't create a new IndexColorModel AND the whole of
1243                      * the source image is being drawn (GDI can't handle a
1244                      * portion of the original source image) then we
1245                      * don't need to create this intermediate image - GDI
1246                      * can access the data from the original image.
1247                      * Since a subimage can be created by calling
1248                      * BufferedImage.getSubImage() that condition needs to
1249                      * be accounted for too. This implies inspecting the
1250                      * data buffer. In the end too many cases are not able
1251                      * to take advantage of this option until we can teach
1252                      * the native code to properly navigate the data buffer.
1253                      * There was a concern that since in native code since we
1254                      * need to DWORD align and flip to a bottom up DIB that
1255                      * the "original" image may get perturbed by this.
1256                      * But in fact we always malloc new memory for the aligned
1257                      * copy so this isn't a problem.
1258                      * This points out that we allocate two temporaries copies
1259                      * of the image : one in Java and one in native. If
1260                      * we can be smarter about not allocating this one when
1261                      * not needed, that would seem like a good thing to do,
1262                      * even if in many cases the ColorModels don't match and
1263                      * its needed.
1264                      * Until all of this is resolved newImage is always true.
1265                      */
1266                     boolean newImage = true;
1267                     if (newImage) {
1268                         if (icm == null) {
1269                             deepImage = new BufferedImage(iw, ih, dibType);
1270                         } else {
1271                             deepImage = new BufferedImage(iw, ih, dibType,icm);
1272                         }
1273 
1274                         /* Setup a Graphics2D on to the BufferedImage so that
1275                          * the source image when copied, lands within the
1276                          * image buffer.
1277                          */
1278                         Graphics2D imageGraphics = deepImage.createGraphics();
1279                         imageGraphics.clipRect(0, 0,
1280                                                deepImage.getWidth(),
1281                                                deepImage.getHeight());
1282 
1283                         imageGraphics.translate(-rotBounds.getX(),
1284                                                 -rotBounds.getY());
1285                         imageGraphics.transform(rotTransform);
1286 
1287                         /* Fill the BufferedImage either with the caller
1288                          * supplied color, 'bgColor' or, if null, with white.
1289                          */
1290                         if (bgcolor == null) {
1291                             bgcolor = Color.white;
1292                         }
1293 
1294                         imageGraphics.drawImage(img,
1295                                                 srcX, srcY,
1296                                                 srcX + srcWidth,
1297                                                 srcY + srcHeight,
1298                                                 srcX, srcY,
1299                                                 srcX + srcWidth,
1300                                                 srcY + srcHeight,
1301                                                 bgcolor, null);
1302                         imageGraphics.dispose();
1303                     } else {
1304                         deepImage = img;
1305                     }
1306 
1307                     /* Scale the bounding rectangle by the scale transform.
1308                      * Because the scaling transform has only x and y
1309                      * scaling components it is equivalent to multiply
1310                      * the x components of the bounding rectangle by
1311                      * the x scaling factor and to multiply the y components
1312                      * by the y scaling factor.
1313                      */
1314                     Rectangle2D.Float scaledBounds
1315                             = new Rectangle2D.Float(
1316                                     (float) (rotBounds.getX() * scaleX),
1317                                     (float) (rotBounds.getY() * scaleY),
1318                                     (float) (rotBounds.getWidth() * scaleX),
1319                                     (float) (rotBounds.getHeight() * scaleY));
1320 
1321                     /* Pull the raster data from the buffered image
1322                      * and pass it along to GDI.
1323                      */
1324                     WritableRaster raster = deepImage.getRaster();
1325                     byte[] data;
1326                     if (raster instanceof ByteComponentRaster) {
1327                         data = ((ByteComponentRaster)raster).getDataStorage();
1328                     } else if (raster instanceof BytePackedRaster) {
1329                         data = ((BytePackedRaster)raster).getDataStorage();
1330                     } else {
1331                         return false;
1332                     }
1333 
1334                     int bitsPerPixel = 24;
1335                     SampleModel sm = deepImage.getSampleModel();
1336                     if (sm instanceof ComponentSampleModel) {
1337                         ComponentSampleModel csm = (ComponentSampleModel)sm;
1338                         bitsPerPixel = csm.getPixelStride() * 8;
1339                     } else if (sm instanceof MultiPixelPackedSampleModel) {
1340                         MultiPixelPackedSampleModel mppsm =
1341                             (MultiPixelPackedSampleModel)sm;
1342                         bitsPerPixel = mppsm.getPixelBitStride();
1343                     } else {
1344                         if (icm != null) {
1345                             int diw = deepImage.getWidth();
1346                             int dih = deepImage.getHeight();
1347                             if (diw > 0 && dih > 0) {
1348                                 bitsPerPixel = data.length*8/diw/dih;
1349                             }
1350                         }
1351                     }
1352 
1353                     /* Because the caller's image has been rotated
1354                      * and sheared into our BufferedImage and because
1355                      * we will be handing that BufferedImage directly to
1356                      * GDI, we need to set an additional clip. This clip
1357                      * makes sure that only parts of the BufferedImage
1358                      * that are also part of the caller's image are drawn.
1359                      */
1360                     Shape holdClip = getClip();
1361                     clip(xform.createTransformedShape(srcRect));
1362                     deviceClip(getClip().getPathIterator(getTransform()));
1363 
1364                     wPrinterJob.drawDIBImage
1365                         (data, scaledBounds.x, scaledBounds.y,
1366                          (float)Math.rint(scaledBounds.width+0.5),
1367                          (float)Math.rint(scaledBounds.height+0.5),
1368                          0f, 0f,
1369                          deepImage.getWidth(), deepImage.getHeight(),
1370                          bitsPerPixel, icm);
1371 
1372                     setClip(holdClip);
1373                 }
1374             }
1375         }
1376 
1377         return true;
1378     }
1379 
1380     /**
1381      * Have the printing application redraw everything that falls
1382      * within the page bounds defined by <code>region</code>.
1383      */
1384     @Override
redrawRegion(Rectangle2D region, double scaleX, double scaleY, Shape savedClip, AffineTransform savedTransform)1385     public void redrawRegion(Rectangle2D region, double scaleX, double scaleY,
1386                              Shape savedClip, AffineTransform savedTransform)
1387             throws PrinterException {
1388 
1389         WPrinterJob wPrinterJob = (WPrinterJob)getPrinterJob();
1390         Printable painter = getPrintable();
1391         PageFormat pageFormat = getPageFormat();
1392         int pageIndex = getPageIndex();
1393 
1394         /* Create a buffered image big enough to hold the portion
1395          * of the source image being printed.
1396          */
1397         BufferedImage deepImage = new BufferedImage(
1398                                         (int) region.getWidth(),
1399                                         (int) region.getHeight(),
1400                                         BufferedImage.TYPE_3BYTE_BGR);
1401 
1402         /* Get a graphics for the application to render into.
1403          * We initialize the buffer to white in order to
1404          * match the paper and then we shift the BufferedImage
1405          * so that it covers the area on the page where the
1406          * caller's Image will be drawn.
1407          */
1408         Graphics2D g = deepImage.createGraphics();
1409         ProxyGraphics2D proxy = new ProxyGraphics2D(g, wPrinterJob);
1410         proxy.setColor(Color.white);
1411         proxy.fillRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
1412         proxy.clipRect(0, 0, deepImage.getWidth(), deepImage.getHeight());
1413 
1414         proxy.translate(-region.getX(), -region.getY());
1415 
1416         /* Calculate the resolution of the source image.
1417          */
1418         float sourceResX = (float)(wPrinterJob.getXRes() / scaleX);
1419         float sourceResY = (float)(wPrinterJob.getYRes() / scaleY);
1420 
1421         /* The application expects to see user space at 72 dpi.
1422          * so change user space from image source resolution to
1423          *  72 dpi.
1424          */
1425         proxy.scale(sourceResX / DEFAULT_USER_RES,
1426                     sourceResY / DEFAULT_USER_RES);
1427 
1428         proxy.translate(
1429             -wPrinterJob.getPhysicalPrintableX(pageFormat.getPaper())
1430                / wPrinterJob.getXRes() * DEFAULT_USER_RES,
1431             -wPrinterJob.getPhysicalPrintableY(pageFormat.getPaper())
1432                / wPrinterJob.getYRes() * DEFAULT_USER_RES);
1433         /* NB User space now has to be at 72 dpi for this calc to be correct */
1434         proxy.transform(new AffineTransform(getPageFormat().getMatrix()));
1435         proxy.setPaint(Color.black);
1436 
1437         painter.print(proxy, pageFormat, pageIndex);
1438 
1439         g.dispose();
1440 
1441         /* We need to set the device clip using saved information.
1442          * savedClip intersects the user clip with a clip that restricts
1443          * the GDI rendered area of our BufferedImage to that which
1444          * may correspond to a rotate or shear.
1445          * The saved device transform is needed as the current transform
1446          * is not likely to be the same.
1447          */
1448         if (savedClip != null) {
1449             deviceClip(savedClip.getPathIterator(savedTransform));
1450         }
1451 
1452         /* Scale the bounding rectangle by the scale transform.
1453          * Because the scaling transform has only x and y
1454          * scaling components it is equivalent to multiplying
1455          * the x components of the bounding rectangle by
1456          * the x scaling factor and to multiplying the y components
1457          * by the y scaling factor.
1458          */
1459         Rectangle2D.Float scaledBounds
1460                 = new Rectangle2D.Float(
1461                         (float) (region.getX() * scaleX),
1462                         (float) (region.getY() * scaleY),
1463                         (float) (region.getWidth() * scaleX),
1464                         (float) (region.getHeight() * scaleY));
1465 
1466         /* Pull the raster data from the buffered image
1467          * and pass it along to GDI.
1468          */
1469        ByteComponentRaster tile
1470                 = (ByteComponentRaster)deepImage.getRaster();
1471 
1472         wPrinterJob.drawImage3ByteBGR(tile.getDataStorage(),
1473                     scaledBounds.x, scaledBounds.y,
1474                     scaledBounds.width,
1475                     scaledBounds.height,
1476                     0f, 0f,
1477                     deepImage.getWidth(), deepImage.getHeight());
1478 
1479     }
1480 
1481     /*
1482      * Fill the path defined by <code>pathIter</code>
1483      * with the specified color.
1484      * The path is provided in device coordinates.
1485      */
1486     @Override
deviceFill(PathIterator pathIter, Color color)1487     protected void deviceFill(PathIterator pathIter, Color color) {
1488 
1489         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1490 
1491         convertToWPath(pathIter);
1492         wPrinterJob.selectSolidBrush(color);
1493         wPrinterJob.fillPath();
1494     }
1495 
1496     /*
1497      * Set the printer device's clip to be the
1498      * path defined by <code>pathIter</code>
1499      * The path is provided in device coordinates.
1500      */
1501     @Override
deviceClip(PathIterator pathIter)1502     protected void deviceClip(PathIterator pathIter) {
1503 
1504         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1505 
1506         convertToWPath(pathIter);
1507         wPrinterJob.selectClipPath();
1508     }
1509 
1510     /**
1511      * Draw the bounding rectangle using transformed coordinates.
1512      */
1513      @Override
deviceFrameRect(int x, int y, int width, int height, Color color)1514      protected void deviceFrameRect(int x, int y, int width, int height,
1515                                      Color color) {
1516 
1517         AffineTransform deviceTransform = getTransform();
1518 
1519         /* check if rotated or sheared */
1520         int transformType = deviceTransform.getType();
1521         boolean usePath = ((transformType &
1522                            (AffineTransform.TYPE_GENERAL_ROTATION |
1523                             AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
1524 
1525         if (usePath) {
1526             draw(new Rectangle2D.Float(x, y, width, height));
1527             return;
1528         }
1529 
1530         Stroke stroke = getStroke();
1531 
1532         if (stroke instanceof BasicStroke) {
1533             BasicStroke lineStroke = (BasicStroke) stroke;
1534 
1535             int endCap = lineStroke.getEndCap();
1536             int lineJoin = lineStroke.getLineJoin();
1537 
1538 
1539             /* check for default style and try to optimize it by
1540              * calling the frameRect native function instead of using paths.
1541              */
1542             if ((endCap == BasicStroke.CAP_SQUARE) &&
1543                 (lineJoin == BasicStroke.JOIN_MITER) &&
1544                 (lineStroke.getMiterLimit() ==10.0f)) {
1545 
1546                 float lineWidth = lineStroke.getLineWidth();
1547                 Point2D.Float penSize = new Point2D.Float(lineWidth,
1548                                                           lineWidth);
1549 
1550                 deviceTransform.deltaTransform(penSize, penSize);
1551                 float deviceLineWidth = Math.min(Math.abs(penSize.x),
1552                                                  Math.abs(penSize.y));
1553 
1554                 /* transform upper left coordinate */
1555                 Point2D.Float ul_pos = new Point2D.Float(x, y);
1556                 deviceTransform.transform(ul_pos, ul_pos);
1557 
1558                 /* transform lower right coordinate */
1559                 Point2D.Float lr_pos = new Point2D.Float(x + width,
1560                                                          y + height);
1561                 deviceTransform.transform(lr_pos, lr_pos);
1562 
1563                 float w = (float) (lr_pos.getX() - ul_pos.getX());
1564                 float h = (float)(lr_pos.getY() - ul_pos.getY());
1565 
1566                 WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1567 
1568                 /* use selectStylePen, if supported */
1569                 if (wPrinterJob.selectStylePen(endCap, lineJoin,
1570                                            deviceLineWidth, color) == true)  {
1571                     wPrinterJob.frameRect((float)ul_pos.getX(),
1572                                           (float)ul_pos.getY(), w, h);
1573                 }
1574                 /* not supported, must be a Win 9x */
1575                 else {
1576 
1577                     double lowerRes = Math.min(wPrinterJob.getXRes(),
1578                                                wPrinterJob.getYRes());
1579 
1580                     if ((deviceLineWidth/lowerRes) < MAX_THINLINE_INCHES) {
1581                         /* use the default pen styles for thin pens. */
1582                         wPrinterJob.selectPen(deviceLineWidth, color);
1583                         wPrinterJob.frameRect((float)ul_pos.getX(),
1584                                               (float)ul_pos.getY(), w, h);
1585                     }
1586                     else {
1587                         draw(new Rectangle2D.Float(x, y, width, height));
1588                     }
1589                 }
1590             }
1591             else {
1592                 draw(new Rectangle2D.Float(x, y, width, height));
1593             }
1594         }
1595      }
1596 
1597 
1598      /*
1599       * Fill the rectangle with specified color and using Windows'
1600       * GDI fillRect function.
1601       * Boundaries are determined by the given coordinates.
1602       */
1603     @Override
deviceFillRect(int x, int y, int width, int height, Color color)1604     protected void deviceFillRect(int x, int y, int width, int height,
1605                                   Color color) {
1606         /*
1607          * Transform to device coordinates
1608          */
1609         AffineTransform deviceTransform = getTransform();
1610 
1611         /* check if rotated or sheared */
1612         int transformType = deviceTransform.getType();
1613         boolean usePath =  ((transformType &
1614                                (AffineTransform.TYPE_GENERAL_ROTATION |
1615                                 AffineTransform.TYPE_GENERAL_TRANSFORM)) != 0);
1616         if (usePath) {
1617             fill(new Rectangle2D.Float(x, y, width, height));
1618             return;
1619         }
1620 
1621         Point2D.Float tlc_pos = new Point2D.Float(x, y);
1622         deviceTransform.transform(tlc_pos, tlc_pos);
1623 
1624         Point2D.Float brc_pos = new Point2D.Float(x+width, y+height);
1625         deviceTransform.transform(brc_pos, brc_pos);
1626 
1627         float deviceWidth = (float) (brc_pos.getX() - tlc_pos.getX());
1628         float deviceHeight = (float)(brc_pos.getY() - tlc_pos.getY());
1629 
1630         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1631         wPrinterJob.fillRect((float)tlc_pos.getX(), (float)tlc_pos.getY(),
1632                              deviceWidth, deviceHeight, color);
1633     }
1634 
1635 
1636     /**
1637      * Draw a line using a pen created using the specified color
1638      * and current stroke properties.
1639      */
1640     @Override
deviceDrawLine(int xBegin, int yBegin, int xEnd, int yEnd, Color color)1641     protected void deviceDrawLine(int xBegin, int yBegin, int xEnd, int yEnd,
1642                                   Color color) {
1643         Stroke stroke = getStroke();
1644 
1645         if (stroke instanceof BasicStroke) {
1646             BasicStroke lineStroke = (BasicStroke) stroke;
1647 
1648             if (lineStroke.getDashArray() != null) {
1649                 draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd));
1650                 return;
1651             }
1652 
1653             float lineWidth = lineStroke.getLineWidth();
1654             Point2D.Float penSize = new Point2D.Float(lineWidth, lineWidth);
1655 
1656             AffineTransform deviceTransform = getTransform();
1657             deviceTransform.deltaTransform(penSize, penSize);
1658 
1659             float deviceLineWidth = Math.min(Math.abs(penSize.x),
1660                                              Math.abs(penSize.y));
1661 
1662             Point2D.Float begin_pos = new Point2D.Float(xBegin, yBegin);
1663             deviceTransform.transform(begin_pos, begin_pos);
1664 
1665             Point2D.Float end_pos = new Point2D.Float(xEnd, yEnd);
1666             deviceTransform.transform(end_pos, end_pos);
1667 
1668             int endCap = lineStroke.getEndCap();
1669             int lineJoin = lineStroke.getLineJoin();
1670 
1671             /* check if it's a one-pixel line */
1672             if ((end_pos.getX() == begin_pos.getX())
1673                 && (end_pos.getY() == begin_pos.getY())) {
1674 
1675                 /* endCap other than Round will not print!
1676                  * due to Windows GDI limitation, force it to CAP_ROUND
1677                  */
1678                 endCap = BasicStroke.CAP_ROUND;
1679             }
1680 
1681 
1682             WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1683 
1684             /* call native function that creates pen with style */
1685             if (wPrinterJob.selectStylePen(endCap, lineJoin,
1686                                            deviceLineWidth, color)) {
1687                 wPrinterJob.moveTo((float)begin_pos.getX(),
1688                                    (float)begin_pos.getY());
1689                 wPrinterJob.lineTo((float)end_pos.getX(),
1690                                    (float)end_pos.getY());
1691             }
1692             /* selectStylePen is not supported, must be Win 9X */
1693             else {
1694 
1695                 /* let's see if we can use a a default pen
1696                  *  if it's round end (Windows' default style)
1697                  *  or it's vertical/horizontal
1698                  *  or stroke is too thin.
1699                  */
1700                 double lowerRes = Math.min(wPrinterJob.getXRes(),
1701                                            wPrinterJob.getYRes());
1702 
1703                 if ((endCap == BasicStroke.CAP_ROUND) ||
1704                  (((xBegin == xEnd) || (yBegin == yEnd)) &&
1705                  (deviceLineWidth/lowerRes < MAX_THINLINE_INCHES))) {
1706 
1707                     wPrinterJob.selectPen(deviceLineWidth, color);
1708                     wPrinterJob.moveTo((float)begin_pos.getX(),
1709                                        (float)begin_pos.getY());
1710                     wPrinterJob.lineTo((float)end_pos.getX(),
1711                                        (float)end_pos.getY());
1712                 }
1713                 else {
1714                     draw(new Line2D.Float(xBegin, yBegin, xEnd, yEnd));
1715                 }
1716             }
1717         }
1718     }
1719 
1720 
1721     /**
1722      * Given a Java2D <code>PathIterator</code> instance,
1723      * this method translates that into a Window's path
1724      * in the printer device context.
1725      */
convertToWPath(PathIterator pathIter)1726     private void convertToWPath(PathIterator pathIter) {
1727 
1728         float[] segment = new float[6];
1729         int segmentType;
1730 
1731         WPrinterJob wPrinterJob = (WPrinterJob) getPrinterJob();
1732 
1733         /* Map the PathIterator's fill rule into the Window's
1734          * polygon fill rule.
1735          */
1736         int polyFillRule;
1737         if (pathIter.getWindingRule() == PathIterator.WIND_EVEN_ODD) {
1738             polyFillRule = WPrinterJob.POLYFILL_ALTERNATE;
1739         } else {
1740             polyFillRule = WPrinterJob.POLYFILL_WINDING;
1741         }
1742         wPrinterJob.setPolyFillMode(polyFillRule);
1743 
1744         wPrinterJob.beginPath();
1745 
1746         while (pathIter.isDone() == false) {
1747             segmentType = pathIter.currentSegment(segment);
1748 
1749             switch (segmentType) {
1750              case PathIterator.SEG_MOVETO:
1751                 wPrinterJob.moveTo(segment[0], segment[1]);
1752                 break;
1753 
1754              case PathIterator.SEG_LINETO:
1755                 wPrinterJob.lineTo(segment[0], segment[1]);
1756                 break;
1757 
1758             /* Convert the quad path to a bezier.
1759              */
1760              case PathIterator.SEG_QUADTO:
1761                 int lastX = wPrinterJob.getPenX();
1762                 int lastY = wPrinterJob.getPenY();
1763                 float c1x = lastX + (segment[0] - lastX) * 2 / 3;
1764                 float c1y = lastY + (segment[1] - lastY) * 2 / 3;
1765                 float c2x = segment[2] - (segment[2] - segment[0]) * 2/ 3;
1766                 float c2y = segment[3] - (segment[3] - segment[1]) * 2/ 3;
1767                 wPrinterJob.polyBezierTo(c1x, c1y,
1768                                          c2x, c2y,
1769                                          segment[2], segment[3]);
1770                 break;
1771 
1772              case PathIterator.SEG_CUBICTO:
1773                 wPrinterJob.polyBezierTo(segment[0], segment[1],
1774                                          segment[2], segment[3],
1775                                          segment[4], segment[5]);
1776                 break;
1777 
1778              case PathIterator.SEG_CLOSE:
1779                 wPrinterJob.closeFigure();
1780                 break;
1781             }
1782 
1783 
1784             pathIter.next();
1785         }
1786 
1787         wPrinterJob.endPath();
1788 
1789     }
1790 
1791 }
1792