1 /*
   2  * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.java2d;
  27 
  28 import java.awt.Graphics;
  29 import java.awt.Graphics2D;
  30 import java.awt.RenderingHints;
  31 import java.awt.RenderingHints.Key;
  32 import java.awt.geom.Area;
  33 import java.awt.geom.AffineTransform;
  34 import java.awt.geom.NoninvertibleTransformException;
  35 import java.awt.AlphaComposite;
  36 import java.awt.BasicStroke;
  37 import java.awt.image.BufferedImage;
  38 import java.awt.image.BufferedImageOp;
  39 import java.awt.image.RenderedImage;
  40 import java.awt.image.renderable.RenderableImage;
  41 import java.awt.image.renderable.RenderContext;
  42 import java.awt.image.AffineTransformOp;
  43 import java.awt.image.Raster;
  44 import java.awt.image.WritableRaster;
  45 import java.awt.Image;
  46 import java.awt.Composite;
  47 import java.awt.Color;
  48 import java.awt.image.ColorModel;
  49 import java.awt.GraphicsConfiguration;
  50 import java.awt.Paint;
  51 import java.awt.GradientPaint;
  52 import java.awt.LinearGradientPaint;
  53 import java.awt.RadialGradientPaint;
  54 import java.awt.TexturePaint;
  55 import java.awt.geom.Rectangle2D;
  56 import java.awt.geom.PathIterator;
  57 import java.awt.geom.GeneralPath;
  58 import java.awt.Shape;
  59 import java.awt.Stroke;
  60 import java.awt.FontMetrics;
  61 import java.awt.Rectangle;
  62 import java.text.AttributedCharacterIterator;
  63 import java.awt.Font;
  64 import java.awt.image.ImageObserver;
  65 import java.awt.Transparency;
  66 import java.awt.font.GlyphVector;
  67 import java.awt.font.TextLayout;
  68 import sun.font.FontDesignMetrics;
  69 import sun.java2d.pipe.PixelDrawPipe;
  70 import sun.java2d.pipe.PixelFillPipe;
  71 import sun.java2d.pipe.ShapeDrawPipe;
  72 import sun.java2d.pipe.ValidatePipe;
  73 import sun.java2d.pipe.ShapeSpanIterator;
  74 import sun.java2d.pipe.Region;
  75 import sun.java2d.pipe.TextPipe;
  76 import sun.java2d.pipe.DrawImagePipe;
  77 import sun.java2d.pipe.LoopPipe;
  78 import sun.java2d.loops.FontInfo;
  79 import sun.java2d.loops.RenderLoops;
  80 import sun.java2d.loops.CompositeType;
  81 import sun.java2d.loops.SurfaceType;
  82 import sun.java2d.loops.Blit;
  83 import sun.java2d.loops.MaskFill;
  84 import sun.font.FontManager;
  85 import java.awt.font.FontRenderContext;
  86 import sun.java2d.loops.XORComposite;
  87 import sun.awt.ConstrainableGraphics;
  88 import sun.awt.SunHints;
  89 import java.util.Map;
  90 import java.util.Iterator;
  91 import sun.java2d.DestSurfaceProvider;
  92 import sun.misc.PerformanceLogger;
  93 
  94 /**
  95  * This is a the master Graphics2D superclass for all of the Sun
  96  * Graphics implementations.  This class relies on subclasses to
  97  * manage the various device information, but provides an overall
  98  * general framework for performing all of the requests in the
  99  * Graphics and Graphics2D APIs.
 100  *
 101  * @author Jim Graham
 102  */
 103 public final class SunGraphics2D
 104     extends Graphics2D
 105     implements ConstrainableGraphics, Cloneable, DestSurfaceProvider
 106 {
 107     /*
 108      * Attribute States
 109      */
 110     /* Paint */
 111     public static final int PAINT_CUSTOM       = 6; /* Any other Paint object */
 112     public static final int PAINT_TEXTURE      = 5; /* Tiled Image */
 113     public static final int PAINT_RAD_GRADIENT = 4; /* Color RadialGradient */
 114     public static final int PAINT_LIN_GRADIENT = 3; /* Color LinearGradient */
 115     public static final int PAINT_GRADIENT     = 2; /* Color Gradient */
 116     public static final int PAINT_ALPHACOLOR   = 1; /* Non-opaque Color */
 117     public static final int PAINT_OPAQUECOLOR  = 0; /* Opaque Color */
 118 
 119     /* Composite*/
 120     public static final int COMP_CUSTOM = 3;/* Custom Composite */
 121     public static final int COMP_XOR    = 2;/* XOR Mode Composite */
 122     public static final int COMP_ALPHA  = 1;/* AlphaComposite */
 123     public static final int COMP_ISCOPY = 0;/* simple stores into destination,
 124                                              * i.e. Src, SrcOverNoEa, and other
 125                                              * alpha modes which replace
 126                                              * the destination.
 127                                              */
 128 
 129     /* Stroke */
 130     public static final int STROKE_CUSTOM = 3; /* custom Stroke */
 131     public static final int STROKE_WIDE   = 2; /* BasicStroke */
 132     public static final int STROKE_THINDASHED   = 1; /* BasicStroke */
 133     public static final int STROKE_THIN   = 0; /* BasicStroke */
 134 
 135     /* Transform */
 136     public static final int TRANSFORM_GENERIC = 4; /* any 3x2 */
 137     public static final int TRANSFORM_TRANSLATESCALE = 3; /* scale XY */
 138     public static final int TRANSFORM_ANY_TRANSLATE = 2; /* non-int translate */
 139     public static final int TRANSFORM_INT_TRANSLATE = 1; /* int translate */
 140     public static final int TRANSFORM_ISIDENT = 0; /* Identity */
 141 
 142     /* Clipping */
 143     public static final int CLIP_SHAPE       = 2; /* arbitrary clip */
 144     public static final int CLIP_RECTANGULAR = 1; /* rectangular clip */
 145     public static final int CLIP_DEVICE      = 0; /* no clipping set */
 146 
 147     /* The following fields are used when the current Paint is a Color. */
 148     public int eargb;  // ARGB value with ExtraAlpha baked in
 149     public int pixel;  // pixel value for eargb
 150 
 151     public SurfaceData surfaceData;
 152 
 153     public PixelDrawPipe drawpipe;
 154     public PixelFillPipe fillpipe;
 155     public DrawImagePipe imagepipe;
 156     public ShapeDrawPipe shapepipe;
 157     public TextPipe textpipe;
 158     public MaskFill alphafill;
 159 
 160     public RenderLoops loops;
 161 
 162     public CompositeType imageComp;     /* Image Transparency checked on fly */
 163 
 164     public int paintState;
 165     public int compositeState;
 166     public int strokeState;
 167     public int transformState;
 168     public int clipState;
 169 
 170     public Color foregroundColor;
 171     public Color backgroundColor;
 172 
 173     public AffineTransform transform;
 174     public int transX;
 175     public int transY;
 176 
 177     protected static final Stroke defaultStroke = new BasicStroke();
 178     protected static final Composite defaultComposite = AlphaComposite.SrcOver;
 179     private static final Font defaultFont =
 180         new Font(Font.DIALOG, Font.PLAIN, 12);
 181 
 182     public Paint paint;
 183     public Stroke stroke;
 184     public Composite composite;
 185     protected Font font;
 186     protected FontMetrics fontMetrics;
 187 
 188     public int renderHint;
 189     public int antialiasHint;
 190     public int textAntialiasHint;
 191     private int fractionalMetricsHint;
 192 
 193     /* A gamma adjustment to the colour used in lcd text blitting */
 194     public int lcdTextContrast;
 195     private static int lcdTextContrastDefaultValue = 140;
 196 
 197     private int interpolationHint;      // raw value of rendering Hint
 198     public int strokeHint;
 199 
 200     public int interpolationType;       // algorithm choice based on
 201                                         // interpolation and render Hints
 202 
 203     public RenderingHints hints;
 204 
 205     public Region constrainClip;                // lightweight bounds
 206     public int constrainX;
 207     public int constrainY;
 208 
 209     public Region clipRegion;
 210     public Shape usrClip;
 211     protected Region devClip;           // Actual physical drawable
 212 
 213     // cached state for text rendering
 214     private boolean validFontInfo;
 215     private FontInfo fontInfo;
 216     private FontInfo glyphVectorFontInfo;
 217     private FontRenderContext glyphVectorFRC;
 218 
 219     private final static int slowTextTransformMask =
 220                             AffineTransform.TYPE_GENERAL_TRANSFORM
 221                         |   AffineTransform.TYPE_MASK_ROTATION
 222                         |   AffineTransform.TYPE_FLIP;
 223 
 224     static {
 225         if (PerformanceLogger.loggingEnabled()) {
 226             PerformanceLogger.setTime("SunGraphics2D static initialization");
 227         }
 228     }
 229 
 230     public SunGraphics2D(SurfaceData sd, Color fg, Color bg, Font f) {
 231         surfaceData = sd;
 232         foregroundColor = fg;
 233         backgroundColor = bg;
 234 
 235         transform = new AffineTransform();
 236         stroke = defaultStroke;
 237         composite = defaultComposite;
 238         paint = foregroundColor;
 239 
 240         imageComp = CompositeType.SrcOverNoEa;
 241 
 242         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
 243         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
 244         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
 245         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 246         lcdTextContrast = lcdTextContrastDefaultValue;
 247         interpolationHint = -1;
 248         strokeHint = SunHints.INTVAL_STROKE_DEFAULT;
 249 
 250         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
 251 
 252         validateColor();
 253 
 254         font = f;
 255         if (font == null) {
 256             font = defaultFont;
 257         }
 258 
 259         loops = sd.getRenderLoops(this);
 260         setDevClip(sd.getBounds());
 261         invalidatePipe();
 262     }
 263 
 264     protected Object clone() {
 265         try {
 266             SunGraphics2D g = (SunGraphics2D) super.clone();
 267             g.transform = new AffineTransform(this.transform);
 268             if (hints != null) {
 269                 g.hints = (RenderingHints) this.hints.clone();
 270             }
 271             /* FontInfos are re-used, so must be cloned too, if they
 272              * are valid, and be nulled out if invalid.
 273              * The implied trade-off is that there is more to be gained
 274              * from re-using these objects than is lost by having to
 275              * clone them when the SG2D is cloned.
 276              */
 277             if (this.fontInfo != null) {
 278                 if (this.validFontInfo) {
 279                     g.fontInfo = (FontInfo)this.fontInfo.clone();
 280                 } else {
 281                     g.fontInfo = null;
 282                 }
 283             }
 284             if (this.glyphVectorFontInfo != null) {
 285                 g.glyphVectorFontInfo =
 286                     (FontInfo)this.glyphVectorFontInfo.clone();
 287                 g.glyphVectorFRC = this.glyphVectorFRC;
 288             }
 289             //g.invalidatePipe();
 290             return g;
 291         } catch (CloneNotSupportedException e) {
 292         }
 293         return null;
 294     }
 295 
 296     /**
 297      * Create a new SunGraphics2D based on this one.
 298      */
 299     public Graphics create() {
 300         return (Graphics) clone();
 301     }
 302 
 303     public void setDevClip(int x, int y, int w, int h) {
 304         Region c = constrainClip;
 305         if (c == null) {
 306             devClip = Region.getInstanceXYWH(x, y, w, h);
 307         } else {
 308             devClip = c.getIntersectionXYWH(x, y, w, h);
 309         }
 310         validateCompClip();
 311     }
 312 
 313     public void setDevClip(Rectangle r) {
 314         setDevClip(r.x, r.y, r.width, r.height);
 315     }
 316 
 317     /**
 318      * Constrain rendering for lightweight objects.
 319      *
 320      * REMIND: This method will back off to the "workaround"
 321      * of using translate and clipRect if the Graphics
 322      * to be constrained has a complex transform.  The
 323      * drawback of the workaround is that the resulting
 324      * clip and device origin cannot be "enforced".
 325      *
 326      * @exception IllegalStateException If the Graphics
 327      * to be constrained has a complex transform.
 328      */
 329     public void constrain(int x, int y, int w, int h) {
 330         if ((x|y) != 0) {
 331             translate(x, y);
 332         }
 333         if (transformState >= TRANSFORM_TRANSLATESCALE) {
 334             clipRect(0, 0, w, h);
 335             return;
 336         }
 337         x = constrainX = transX;
 338         y = constrainY = transY;
 339         w = Region.dimAdd(x, w);
 340         h = Region.dimAdd(y, h);
 341         Region c = constrainClip;
 342         if (c == null) {
 343             c = Region.getInstanceXYXY(x, y, w, h);
 344         } else {
 345             c = c.getIntersectionXYXY(x, y, w, h);
 346             if (c == constrainClip) {
 347                 // Common case to ignore
 348                 return;
 349             }
 350         }
 351         constrainClip = c;
 352         if (!devClip.isInsideQuickCheck(c)) {
 353             devClip = devClip.getIntersection(c);
 354             validateCompClip();
 355         }
 356     }
 357 
 358     protected static ValidatePipe invalidpipe = new ValidatePipe();
 359 
 360     /*
 361      * Invalidate the pipeline
 362      */
 363     protected void invalidatePipe() {
 364         drawpipe = invalidpipe;
 365         fillpipe = invalidpipe;
 366         shapepipe = invalidpipe;
 367         textpipe = invalidpipe;
 368         imagepipe = invalidpipe;
 369     }
 370 
 371     public void validatePipe() {
 372         surfaceData.validatePipe(this);
 373     }
 374 
 375     /*
 376      * Intersect two Shapes by the simplest method, attempting to produce
 377      * a simplified result.
 378      * The boolean arguments keep1 and keep2 specify whether or not
 379      * the first or second shapes can be modified during the operation
 380      * or whether that shape must be "kept" unmodified.
 381      */
 382     Shape intersectShapes(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 383         if (s1 instanceof Rectangle && s2 instanceof Rectangle) {
 384             return ((Rectangle) s1).intersection((Rectangle) s2);
 385         }
 386         if (s1 instanceof Rectangle2D) {
 387             return intersectRectShape((Rectangle2D) s1, s2, keep1, keep2);
 388         } else if (s2 instanceof Rectangle2D) {
 389             return intersectRectShape((Rectangle2D) s2, s1, keep2, keep1);
 390         }
 391         return intersectByArea(s1, s2, keep1, keep2);
 392     }
 393 
 394     /*
 395      * Intersect a Rectangle with a Shape by the simplest method,
 396      * attempting to produce a simplified result.
 397      * The boolean arguments keep1 and keep2 specify whether or not
 398      * the first or second shapes can be modified during the operation
 399      * or whether that shape must be "kept" unmodified.
 400      */
 401     Shape intersectRectShape(Rectangle2D r, Shape s,
 402                              boolean keep1, boolean keep2) {
 403         if (s instanceof Rectangle2D) {
 404             Rectangle2D r2 = (Rectangle2D) s;
 405             Rectangle2D outrect;
 406             if (!keep1) {
 407                 outrect = r;
 408             } else if (!keep2) {
 409                 outrect = r2;
 410             } else {
 411                 outrect = new Rectangle2D.Float();
 412             }
 413             double x1 = Math.max(r.getX(), r2.getX());
 414             double x2 = Math.min(r.getX()  + r.getWidth(),
 415                                  r2.getX() + r2.getWidth());
 416             double y1 = Math.max(r.getY(), r2.getY());
 417             double y2 = Math.min(r.getY()  + r.getHeight(),
 418                                  r2.getY() + r2.getHeight());
 419 
 420             if (((x2 - x1) < 0) || ((y2 - y1) < 0))
 421                 // Width or height is negative. No intersection.
 422                 outrect.setFrameFromDiagonal(0, 0, 0, 0);
 423             else
 424                 outrect.setFrameFromDiagonal(x1, y1, x2, y2);
 425             return outrect;
 426         }
 427         if (r.contains(s.getBounds2D())) {
 428             if (keep2) {
 429                 s = cloneShape(s);
 430             }
 431             return s;
 432         }
 433         return intersectByArea(r, s, keep1, keep2);
 434     }
 435 
 436     protected static Shape cloneShape(Shape s) {
 437         return new GeneralPath(s);
 438     }
 439 
 440     /*
 441      * Intersect two Shapes using the Area class.  Presumably other
 442      * attempts at simpler intersection methods proved fruitless.
 443      * The boolean arguments keep1 and keep2 specify whether or not
 444      * the first or second shapes can be modified during the operation
 445      * or whether that shape must be "kept" unmodified.
 446      * @see #intersectShapes
 447      * @see #intersectRectShape
 448      */
 449     Shape intersectByArea(Shape s1, Shape s2, boolean keep1, boolean keep2) {
 450         Area a1, a2;
 451 
 452         // First see if we can find an overwriteable source shape
 453         // to use as our destination area to avoid duplication.
 454         if (!keep1 && (s1 instanceof Area)) {
 455             a1 = (Area) s1;
 456         } else if (!keep2 && (s2 instanceof Area)) {
 457             a1 = (Area) s2;
 458             s2 = s1;
 459         } else {
 460             a1 = new Area(s1);
 461         }
 462 
 463         if (s2 instanceof Area) {
 464             a2 = (Area) s2;
 465         } else {
 466             a2 = new Area(s2);
 467         }
 468 
 469         a1.intersect(a2);
 470         if (a1.isRectangular()) {
 471             return a1.getBounds();
 472         }
 473 
 474         return a1;
 475     }
 476 
 477     /*
 478      * Intersect usrClip bounds and device bounds to determine the composite
 479      * rendering boundaries.
 480      */
 481     public Region getCompClip() {
 482         if (!surfaceData.isValid()) {
 483             // revalidateAll() implicitly recalculcates the composite clip
 484             revalidateAll();
 485         }
 486 
 487         return clipRegion;
 488     }
 489 
 490     public Font getFont() {
 491         if (font == null) {
 492             font = defaultFont;
 493         }
 494         return font;
 495     }
 496 
 497     private static final double[] IDENT_MATRIX = {1, 0, 0, 1};
 498     private static final AffineTransform IDENT_ATX =
 499         new AffineTransform();
 500 
 501     private static final int MINALLOCATED = 8;
 502     private static final int TEXTARRSIZE = 17;
 503     private static double[][] textTxArr = new double[TEXTARRSIZE][];
 504     private static AffineTransform[] textAtArr =
 505         new AffineTransform[TEXTARRSIZE];
 506 
 507     static {
 508         for (int i=MINALLOCATED;i<TEXTARRSIZE;i++) {
 509           textTxArr[i] = new double [] {i, 0, 0, i};
 510           textAtArr[i] = new AffineTransform( textTxArr[i]);
 511         }
 512     }
 513 
 514     // cached state for various draw[String,Char,Byte] optimizations
 515     public FontInfo checkFontInfo(FontInfo info, Font font,
 516                                   FontRenderContext frc) {
 517         /* Do not create a FontInfo object as part of construction of an
 518          * SG2D as its possible it may never be needed - ie if no text
 519          * is drawn using this SG2D.
 520          */
 521         if (info == null) {
 522             info = new FontInfo();
 523         }
 524 
 525         float ptSize = font.getSize2D();
 526         int txFontType;
 527         AffineTransform devAt, textAt=null;
 528         if (font.isTransformed()) {
 529             textAt = font.getTransform();
 530             textAt.scale(ptSize, ptSize);
 531             txFontType = textAt.getType();
 532             info.originX = (float)textAt.getTranslateX();
 533             info.originY = (float)textAt.getTranslateY();
 534             textAt.translate(-info.originX, -info.originY);
 535             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 536                 transform.getMatrix(info.devTx = new double[4]);
 537                 devAt = new AffineTransform(info.devTx);
 538                 textAt.preConcatenate(devAt);
 539             } else {
 540                 info.devTx = IDENT_MATRIX;
 541                 devAt = IDENT_ATX;
 542             }
 543             textAt.getMatrix(info.glyphTx = new double[4]);
 544             double shearx = textAt.getShearX();
 545             double scaley = textAt.getScaleY();
 546             if (shearx != 0) {
 547                 scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 548             }
 549             info.pixelHeight = (int)(Math.abs(scaley)+0.5);
 550         } else {
 551             txFontType = AffineTransform.TYPE_IDENTITY;
 552             info.originX = info.originY = 0;
 553             if (transformState >= TRANSFORM_TRANSLATESCALE) {
 554                 transform.getMatrix(info.devTx = new double[4]);
 555                 devAt = new AffineTransform(info.devTx);
 556                 info.glyphTx = new double[4];
 557                 for (int i = 0; i < 4; i++) {
 558                     info.glyphTx[i] = info.devTx[i] * ptSize;
 559                 }
 560                 textAt = new AffineTransform(info.glyphTx);
 561                 double shearx = transform.getShearX();
 562                 double scaley = transform.getScaleY();
 563                 if (shearx != 0) {
 564                     scaley = Math.sqrt(shearx * shearx + scaley * scaley);
 565                 }
 566                 info.pixelHeight = (int)(Math.abs(scaley * ptSize)+0.5);
 567             } else {
 568                 /* If the double represents a common integral, we
 569                  * may have pre-allocated objects.
 570                  * A "sparse" array be seems to be as fast as a switch
 571                  * even for 3 or 4 pt sizes, and is more flexible.
 572                  * This should perform comparably in single-threaded
 573                  * rendering to the old code which synchronized on the
 574                  * class and scale better on MP systems.
 575                  */
 576                 int pszInt = (int)ptSize;
 577                 if (ptSize == pszInt &&
 578                     pszInt >= MINALLOCATED && pszInt < TEXTARRSIZE) {
 579                     info.glyphTx = textTxArr[pszInt];
 580                     textAt = textAtArr[pszInt];
 581                     info.pixelHeight = pszInt;
 582                 } else {
 583                     info.pixelHeight = (int)(ptSize+0.5);
 584                 }
 585                 if (textAt == null) {
 586                     info.glyphTx = new double[] {ptSize, 0, 0, ptSize};
 587                     textAt = new AffineTransform(info.glyphTx);
 588                 }
 589 
 590                 info.devTx = IDENT_MATRIX;
 591                 devAt = IDENT_ATX;
 592             }
 593         }
 594 
 595         info.font2D = FontManager.getFont2D(font);
 596 
 597         int fmhint = fractionalMetricsHint;
 598         if (fmhint == SunHints.INTVAL_FRACTIONALMETRICS_DEFAULT) {
 599             fmhint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
 600         }
 601         info.lcdSubPixPos = false; // conditionally set true in LCD mode.
 602 
 603         /* The text anti-aliasing hints that are set by the client need
 604          * to be interpreted for the current state and stored in the
 605          * FontInfo.aahint which is what will actually be used and
 606          * will be one of OFF, ON, LCD_HRGB or LCD_VRGB.
 607          * This is what pipe selection code should typically refer to, not
 608          * textAntialiasHint. This means we are now evaluating the meaning
 609          * of "default" here. Any pipe that really cares about that will
 610          * also need to consult that variable.
 611          * Otherwise these are being used only as args to getStrike,
 612          * and are encapsulated in that object which is part of the
 613          * FontInfo, so we do not need to store them directly as fields
 614          * in the FontInfo object.
 615          * That could change if FontInfo's were more selectively
 616          * revalidated when graphics state changed. Presently this
 617          * method re-evaluates all fields in the fontInfo.
 618          * The strike doesn't need to know the RGB subpixel order. Just
 619          * if its H or V orientation, so if an LCD option is specified we
 620          * always pass in the RGB hint to the strike.
 621          * frc is non-null only if this is a GlyphVector. For reasons
 622          * which are probably a historical mistake the AA hint in a GV
 623          * is honoured when we render, overriding the Graphics setting.
 624          */
 625         int aahint;
 626         if (frc == null) {
 627             aahint = textAntialiasHint;
 628         } else {
 629             aahint = ((SunHints.Value)frc.getAntiAliasingHint()).getIndex();
 630         }
 631         if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT) {
 632             if (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
 633                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 634             } else {
 635                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 636             }
 637         } else {
 638             /* If we are in checkFontInfo because a rendering hint has been
 639              * set then all pipes are revalidated. But we can also
 640              * be here because setFont() has been called when the 'gasp'
 641              * hint is set, as then the font size determines the text pipe.
 642              * See comments in SunGraphics2d.setFont(Font).
 643              */
 644             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP) {
 645                 if (info.font2D.useAAForPtSize(info.pixelHeight)) {
 646                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 647                 } else {
 648                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_OFF;
 649                 }
 650             } else if (aahint >= SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB) {
 651                 /* loops for default rendering modes are installed in the SG2D
 652                  * constructor. If there are none this will be null.
 653                  * Not all compositing modes update the render loops, so
 654                  * we also test that this is a mode we know should support
 655                  * this. One minor issue is that the loops aren't necessarily
 656                  * installed for a new rendering mode until after this
 657                  * method is called during pipeline validation. So it is
 658                  * theoretically possible that it was set to null for a
 659                  * compositing mode, the composite is then set back to Src,
 660                  * but the loop is still null when this is called and AA=ON
 661                  * is installed instead of an LCD mode.
 662                  * However this is done in the right order in SurfaceData.java
 663                  * so this is not likely to be a problem - but not
 664                  * guaranteed.
 665                  */
 666                 if (
 667                     !surfaceData.canRenderLCDText(this)
 668 //                    loops.drawGlyphListLCDLoop == null ||
 669 //                    compositeState > COMP_ISCOPY ||
 670 //                    paintState > PAINT_ALPHACOLOR
 671                       ) {
 672                     aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
 673                 } else {
 674                     info.lcdRGBOrder = true;
 675                     /* Collapse these into just HRGB or VRGB.
 676                      * Pipe selection code needs only to test for these two.
 677                      * Since these both select the same pipe anyway its
 678                      * tempting to collapse into one value. But they are
 679                      * different strikes (glyph caches) so the distinction
 680                      * needs to be made for that purpose.
 681                      */
 682                     if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HBGR) {
 683                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 684                         info.lcdRGBOrder = false;
 685                     } else if
 686                         (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VBGR) {
 687                         aahint = SunHints.INTVAL_TEXT_ANTIALIAS_LCD_VRGB;
 688                         info.lcdRGBOrder = false;
 689                     }
 690                     /* Support subpixel positioning only for the case in
 691                      * which the horizontal resolution is increased
 692                      */
 693                     info.lcdSubPixPos =
 694                         fmhint == SunHints.INTVAL_FRACTIONALMETRICS_ON &&
 695                         aahint == SunHints.INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
 696                 }
 697             }
 698         }
 699         info.aaHint = aahint;
 700         info.fontStrike = info.font2D.getStrike(font, devAt, textAt,
 701                                                 aahint, fmhint);
 702         return info;
 703     }
 704 
 705     public static boolean isRotated(double [] mtx) {
 706         if ((mtx[0] == mtx[3]) &&
 707             (mtx[1] == 0.0) &&
 708             (mtx[2] == 0.0) &&
 709             (mtx[0] > 0.0))
 710         {
 711             return false;
 712         }
 713 
 714         return true;
 715     }
 716 
 717     public void setFont(Font font) {
 718         /* replacing the reference equality test font != this.font with
 719          * !font.equals(this.font) did not yield any measurable difference
 720          * in testing, but there may be yet to be identified cases where it
 721          * is beneficial.
 722          */
 723         if (font != null && font!=this.font/*!font.equals(this.font)*/) {
 724             /* In the GASP AA case the textpipe depends on the glyph size
 725              * as determined by graphics and font transforms as well as the
 726              * font size, and information in the font. But we may invalidate
 727              * the pipe only to find that it made no difference.
 728              * Deferring pipe invalidation to checkFontInfo won't work because
 729              * when called we may already be rendering to the wrong pipe.
 730              * So, if the font is transformed, or the graphics has more than
 731              * a simple scale, we'll take that as enough of a hint to
 732              * revalidate everything. But if they aren't we will
 733              * use the font's point size to query the gasp table and see if
 734              * what it says matches what's currently being used, in which
 735              * case there's no need to invalidate the textpipe.
 736              * This should be sufficient for all typical uses cases.
 737              */
 738             if (textAntialiasHint == SunHints.INTVAL_TEXT_ANTIALIAS_GASP &&
 739                 textpipe != invalidpipe &&
 740                 (transformState > TRANSFORM_ANY_TRANSLATE ||
 741                  font.isTransformed() ||
 742                  fontInfo == null || // Precaution, if true shouldn't get here
 743                  (fontInfo.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) !=
 744                  FontManager.getFont2D(font).useAAForPtSize(font.getSize()))) {
 745                 textpipe = invalidpipe;
 746             }
 747             this.font = font;
 748             this.fontMetrics = null;
 749             this.validFontInfo = false;
 750         }
 751     }
 752 
 753     public FontInfo getFontInfo() {
 754         if (!validFontInfo) {
 755             this.fontInfo = checkFontInfo(this.fontInfo, font, null);
 756             validFontInfo = true;
 757         }
 758         return this.fontInfo;
 759     }
 760 
 761     /* Used by drawGlyphVector which specifies its own font. */
 762     public FontInfo getGVFontInfo(Font font, FontRenderContext frc) {
 763         if (glyphVectorFontInfo != null &&
 764             glyphVectorFontInfo.font == font &&
 765             glyphVectorFRC == frc) {
 766             return glyphVectorFontInfo;
 767         } else {
 768             glyphVectorFRC = frc;
 769             return glyphVectorFontInfo =
 770                 checkFontInfo(glyphVectorFontInfo, font, frc);
 771         }
 772     }
 773 
 774     public FontMetrics getFontMetrics() {
 775         if (this.fontMetrics != null) {
 776             return this.fontMetrics;
 777         }
 778         /* NB the constructor and the setter disallow "font" being null */
 779         return this.fontMetrics =
 780            FontDesignMetrics.getMetrics(font, getFontRenderContext());
 781     }
 782 
 783     public FontMetrics getFontMetrics(Font font) {
 784         if ((this.fontMetrics != null) && (font == this.font)) {
 785             return this.fontMetrics;
 786         }
 787         FontMetrics fm =
 788           FontDesignMetrics.getMetrics(font, getFontRenderContext());
 789 
 790         if (this.font == font) {
 791             this.fontMetrics = fm;
 792         }
 793         return fm;
 794     }
 795 
 796     /**
 797      * Checks to see if a Path intersects the specified Rectangle in device
 798      * space.  The rendering attributes taken into account include the
 799      * clip, transform, and stroke attributes.
 800      * @param rect The area in device space to check for a hit.
 801      * @param p The path to check for a hit.
 802      * @param onStroke Flag to choose between testing the stroked or
 803      * the filled path.
 804      * @return True if there is a hit, false otherwise.
 805      * @see #setStroke
 806      * @see #fillPath
 807      * @see #drawPath
 808      * @see #transform
 809      * @see #setTransform
 810      * @see #clip
 811      * @see #setClip
 812      */
 813     public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
 814         if (onStroke) {
 815             s = stroke.createStrokedShape(s);
 816         }
 817 
 818         s = transformShape(s);
 819         if ((constrainX|constrainY) != 0) {
 820             rect = new Rectangle(rect);
 821             rect.translate(constrainX, constrainY);
 822         }
 823 
 824         return s.intersects(rect);
 825     }
 826 
 827     /**
 828      * Return the ColorModel associated with this Graphics2D.
 829      */
 830     public ColorModel getDeviceColorModel() {
 831         return surfaceData.getColorModel();
 832     }
 833 
 834     /**
 835      * Return the device configuration associated with this Graphics2D.
 836      */
 837     public GraphicsConfiguration getDeviceConfiguration() {
 838         return surfaceData.getDeviceConfiguration();
 839     }
 840 
 841     /**
 842      * Return the SurfaceData object assigned to manage the destination
 843      * drawable surface of this Graphics2D.
 844      */
 845     public final SurfaceData getSurfaceData() {
 846         return surfaceData;
 847     }
 848 
 849     /**
 850      * Sets the Composite in the current graphics state. Composite is used
 851      * in all drawing methods such as drawImage, drawString, drawPath,
 852      * and fillPath.  It specifies how new pixels are to be combined with
 853      * the existing pixels on the graphics device in the rendering process.
 854      * @param comp The Composite object to be used for drawing.
 855      * @see java.awt.Graphics#setXORMode
 856      * @see java.awt.Graphics#setPaintMode
 857      * @see AlphaComposite
 858      */
 859     public void setComposite(Composite comp) {
 860         if (composite == comp) {
 861             return;
 862         }
 863         int newCompState;
 864         CompositeType newCompType;
 865         if (comp instanceof AlphaComposite) {
 866             AlphaComposite alphacomp = (AlphaComposite) comp;
 867             newCompType = CompositeType.forAlphaComposite(alphacomp);
 868             if (newCompType == CompositeType.SrcOverNoEa) {
 869                 if (paintState == PAINT_OPAQUECOLOR ||
 870                     (paintState > PAINT_ALPHACOLOR &&
 871                      paint.getTransparency() == Transparency.OPAQUE))
 872                 {
 873                     newCompState = COMP_ISCOPY;
 874                 } else {
 875                     newCompState = COMP_ALPHA;
 876                 }
 877             } else if (newCompType == CompositeType.SrcNoEa ||
 878                        newCompType == CompositeType.Src ||
 879                        newCompType == CompositeType.Clear)
 880             {
 881                 newCompState = COMP_ISCOPY;
 882             } else if (surfaceData.getTransparency() == Transparency.OPAQUE &&
 883                        newCompType == CompositeType.SrcIn)
 884             {
 885                 newCompState = COMP_ISCOPY;
 886             } else {
 887                 newCompState = COMP_ALPHA;
 888             }
 889         } else if (comp instanceof XORComposite) {
 890             newCompState = COMP_XOR;
 891             newCompType = CompositeType.Xor;
 892         } else if (comp == null) {
 893             throw new IllegalArgumentException("null Composite");
 894         } else {
 895             surfaceData.checkCustomComposite();
 896             newCompState = COMP_CUSTOM;
 897             newCompType = CompositeType.General;
 898         }
 899         if (compositeState != newCompState ||
 900             imageComp != newCompType)
 901         {
 902             compositeState = newCompState;
 903             imageComp = newCompType;
 904             invalidatePipe();
 905             validFontInfo = false;
 906         }
 907         composite = comp;
 908         if (paintState <= PAINT_ALPHACOLOR) {
 909             validateColor();
 910         }
 911     }
 912 
 913     /**
 914      * Sets the Paint in the current graphics state.
 915      * @param paint The Paint object to be used to generate color in
 916      * the rendering process.
 917      * @see java.awt.Graphics#setColor
 918      * @see GradientPaint
 919      * @see TexturePaint
 920      */
 921     public void setPaint(Paint paint) {
 922         if (paint instanceof Color) {
 923             setColor((Color) paint);
 924             return;
 925         }
 926         if (paint == null || this.paint == paint) {
 927             return;
 928         }
 929         this.paint = paint;
 930         if (imageComp == CompositeType.SrcOverNoEa) {
 931             // special case where compState depends on opacity of paint
 932             if (paint.getTransparency() == Transparency.OPAQUE) {
 933                 if (compositeState != COMP_ISCOPY) {
 934                     compositeState = COMP_ISCOPY;
 935                 }
 936             } else {
 937                 if (compositeState == COMP_ISCOPY) {
 938                     compositeState = COMP_ALPHA;
 939                 }
 940             }
 941         }
 942         Class paintClass = paint.getClass();
 943         if (paintClass == GradientPaint.class) {
 944             paintState = PAINT_GRADIENT;
 945         } else if (paintClass == LinearGradientPaint.class) {
 946             paintState = PAINT_LIN_GRADIENT;
 947         } else if (paintClass == RadialGradientPaint.class) {
 948             paintState = PAINT_RAD_GRADIENT;
 949         } else if (paintClass == TexturePaint.class) {
 950             paintState = PAINT_TEXTURE;
 951         } else {
 952             paintState = PAINT_CUSTOM;
 953         }
 954         validFontInfo = false;
 955         invalidatePipe();
 956     }
 957 
 958     static final int NON_UNIFORM_SCALE_MASK =
 959         (AffineTransform.TYPE_GENERAL_TRANSFORM |
 960          AffineTransform.TYPE_GENERAL_SCALE);
 961     public static final double MinPenSizeAA =
 962         sun.java2d.pipe.RenderingEngine.getInstance().getMinimumAAPenSize();
 963     public static final double MinPenSizeAASquared =
 964         (MinPenSizeAA * MinPenSizeAA);
 965     // Since inaccuracies in the trig package can cause us to
 966     // calculated a rotated pen width of just slightly greater
 967     // than 1.0, we add a fudge factor to our comparison value
 968     // here so that we do not misclassify single width lines as
 969     // wide lines under certain rotations.
 970     public static final double MinPenSizeSquared = 1.000000001;
 971 
 972     private void validateBasicStroke(BasicStroke bs) {
 973         boolean aa = (antialiasHint == SunHints.INTVAL_ANTIALIAS_ON);
 974         if (transformState < TRANSFORM_TRANSLATESCALE) {
 975             if (aa) {
 976                 if (bs.getLineWidth() <= MinPenSizeAA) {
 977                     if (bs.getDashArray() == null) {
 978                         strokeState = STROKE_THIN;
 979                     } else {
 980                         strokeState = STROKE_THINDASHED;
 981                     }
 982                 } else {
 983                     strokeState = STROKE_WIDE;
 984                 }
 985             } else {
 986                 if (bs == defaultStroke) {
 987                     strokeState = STROKE_THIN;
 988                 } else if (bs.getLineWidth() <= 1.0f) {
 989                     if (bs.getDashArray() == null) {
 990                         strokeState = STROKE_THIN;
 991                     } else {
 992                         strokeState = STROKE_THINDASHED;
 993                     }
 994                 } else {
 995                     strokeState = STROKE_WIDE;
 996                 }
 997             }
 998         } else {
 999             double widthsquared;
1000             if ((transform.getType() & NON_UNIFORM_SCALE_MASK) == 0) {
1001                 /* sqrt omitted, compare to squared limits below. */
1002                 widthsquared = Math.abs(transform.getDeterminant());
1003             } else {
1004                 /* First calculate the "maximum scale" of this transform. */
1005                 double A = transform.getScaleX();       // m00
1006                 double C = transform.getShearX();       // m01
1007                 double B = transform.getShearY();       // m10
1008                 double D = transform.getScaleY();       // m11
1009 
1010                 /*
1011                  * Given a 2 x 2 affine matrix [ A B ] such that
1012                  *                             [ C D ]
1013                  * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
1014                  * find the maximum magnitude (norm) of the vector v'
1015                  * with the constraint (x^2 + y^2 = 1).
1016                  * The equation to maximize is
1017                  *     |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
1018                  * or  |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
1019                  * Since sqrt is monotonic we can maximize |v'|^2
1020                  * instead and plug in the substitution y = sqrt(1 - x^2).
1021                  * Trigonometric equalities can then be used to get
1022                  * rid of most of the sqrt terms.
1023                  */
1024                 double EA = A*A + B*B;          // x^2 coefficient
1025                 double EB = 2*(A*C + B*D);      // xy coefficient
1026                 double EC = C*C + D*D;          // y^2 coefficient
1027 
1028                 /*
1029                  * There is a lot of calculus omitted here.
1030                  *
1031                  * Conceptually, in the interests of understanding the
1032                  * terms that the calculus produced we can consider
1033                  * that EA and EC end up providing the lengths along
1034                  * the major axes and the hypot term ends up being an
1035                  * adjustment for the additional length along the off-axis
1036                  * angle of rotated or sheared ellipses as well as an
1037                  * adjustment for the fact that the equation below
1038                  * averages the two major axis lengths.  (Notice that
1039                  * the hypot term contains a part which resolves to the
1040                  * difference of these two axis lengths in the absence
1041                  * of rotation.)
1042                  *
1043                  * In the calculus, the ratio of the EB and (EA-EC) terms
1044                  * ends up being the tangent of 2*theta where theta is
1045                  * the angle that the long axis of the ellipse makes
1046                  * with the horizontal axis.  Thus, this equation is
1047                  * calculating the length of the hypotenuse of a triangle
1048                  * along that axis.
1049                  */
1050                 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
1051 
1052                 /* sqrt omitted, compare to squared limits below. */
1053                 widthsquared = ((EA + EC + hypot)/2.0);
1054             }
1055             if (bs != defaultStroke) {
1056                 widthsquared *= bs.getLineWidth() * bs.getLineWidth();
1057             }
1058             if (widthsquared <=
1059                 (aa ? MinPenSizeAASquared : MinPenSizeSquared))
1060             {
1061                 if (bs.getDashArray() == null) {
1062                     strokeState = STROKE_THIN;
1063                 } else {
1064                     strokeState = STROKE_THINDASHED;
1065                 }
1066             } else {
1067                 strokeState = STROKE_WIDE;
1068             }
1069         }
1070     }
1071 
1072     /*
1073      * Sets the Stroke in the current graphics state.
1074      * @param s The Stroke object to be used to stroke a Path in
1075      * the rendering process.
1076      * @see BasicStroke
1077      */
1078     public void setStroke(Stroke s) {
1079         if (s == null) {
1080             throw new IllegalArgumentException("null Stroke");
1081         }
1082         int saveStrokeState = strokeState;
1083         stroke = s;
1084         if (s instanceof BasicStroke) {
1085             validateBasicStroke((BasicStroke) s);
1086         } else {
1087             strokeState = STROKE_CUSTOM;
1088         }
1089         if (strokeState != saveStrokeState) {
1090             invalidatePipe();
1091         }
1092     }
1093 
1094     /**
1095      * Sets the preferences for the rendering algorithms.
1096      * Hint categories include controls for rendering quality and
1097      * overall time/quality trade-off in the rendering process.
1098      * @param hintKey The key of hint to be set. The strings are
1099      * defined in the RenderingHints class.
1100      * @param hintValue The value indicating preferences for the specified
1101      * hint category. These strings are defined in the RenderingHints
1102      * class.
1103      * @see RenderingHints
1104      */
1105     public void setRenderingHint(Key hintKey, Object hintValue) {
1106         // If we recognize the key, we must recognize the value
1107         //     otherwise throw an IllegalArgumentException
1108         //     and do not change the Hints object
1109         // If we do not recognize the key, just pass it through
1110         //     to the Hints object untouched
1111         if (!hintKey.isCompatibleValue(hintValue)) {
1112             throw new IllegalArgumentException
1113                 (hintValue+" is not compatible with "+hintKey);
1114         }
1115         if (hintKey instanceof SunHints.Key) {
1116             boolean stateChanged;
1117             boolean textStateChanged = false;
1118             boolean recognized = true;
1119             SunHints.Key sunKey = (SunHints.Key) hintKey;
1120             int newHint;
1121             if (sunKey == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST) {
1122                 newHint = ((Integer)hintValue).intValue();
1123             } else {
1124                 newHint = ((SunHints.Value) hintValue).getIndex();
1125             }
1126             switch (sunKey.getIndex()) {
1127             case SunHints.INTKEY_RENDERING:
1128                 stateChanged = (renderHint != newHint);
1129                 if (stateChanged) {
1130                     renderHint = newHint;
1131                     if (interpolationHint == -1) {
1132                         interpolationType =
1133                             (newHint == SunHints.INTVAL_RENDER_QUALITY
1134                              ? AffineTransformOp.TYPE_BILINEAR
1135                              : AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
1136                     }
1137                 }
1138                 break;
1139             case SunHints.INTKEY_ANTIALIASING:
1140                 stateChanged = (antialiasHint != newHint);
1141                 antialiasHint = newHint;
1142                 if (stateChanged) {
1143                     textStateChanged =
1144                         (textAntialiasHint ==
1145                          SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT);
1146                     if (strokeState != STROKE_CUSTOM) {
1147                         validateBasicStroke((BasicStroke) stroke);
1148                     }
1149                 }
1150                 break;
1151             case SunHints.INTKEY_TEXT_ANTIALIASING:
1152                 stateChanged = (textAntialiasHint != newHint);
1153                 textStateChanged = stateChanged;
1154                 textAntialiasHint = newHint;
1155                 break;
1156             case SunHints.INTKEY_FRACTIONALMETRICS:
1157                 stateChanged = (fractionalMetricsHint != newHint);
1158                 textStateChanged = stateChanged;
1159                 fractionalMetricsHint = newHint;
1160                 break;
1161             case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1162                 stateChanged = false;
1163                 /* Already have validated it is an int 100 <= newHint <= 250 */
1164                 lcdTextContrast = newHint;
1165                 break;
1166             case SunHints.INTKEY_INTERPOLATION:
1167                 interpolationHint = newHint;
1168                 switch (newHint) {
1169                 case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1170                     newHint = AffineTransformOp.TYPE_BICUBIC;
1171                     break;
1172                 case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1173                     newHint = AffineTransformOp.TYPE_BILINEAR;
1174                     break;
1175                 default:
1176                 case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1177                     newHint = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1178                     break;
1179                 }
1180                 stateChanged = (interpolationType != newHint);
1181                 interpolationType = newHint;
1182                 break;
1183             case SunHints.INTKEY_STROKE_CONTROL:
1184                 stateChanged = (strokeHint != newHint);
1185                 strokeHint = newHint;
1186                 break;
1187             default:
1188                 recognized = false;
1189                 stateChanged = false;
1190                 break;
1191             }
1192             if (recognized) {
1193                 if (stateChanged) {
1194                     invalidatePipe();
1195                     if (textStateChanged) {
1196                         fontMetrics = null;
1197                         this.cachedFRC = null;
1198                         validFontInfo = false;
1199                         this.glyphVectorFontInfo = null;
1200                     }
1201                 }
1202                 if (hints != null) {
1203                     hints.put(hintKey, hintValue);
1204                 }
1205                 return;
1206             }
1207         }
1208         // Nothing we recognize so none of "our state" has changed
1209         if (hints == null) {
1210             hints = makeHints(null);
1211         }
1212         hints.put(hintKey, hintValue);
1213     }
1214 
1215 
1216     /**
1217      * Returns the preferences for the rendering algorithms.
1218      * @param hintCategory The category of hint to be set. The strings
1219      * are defined in the RenderingHints class.
1220      * @return The preferences for rendering algorithms. The strings
1221      * are defined in the RenderingHints class.
1222      * @see RenderingHints
1223      */
1224     public Object getRenderingHint(Key hintKey) {
1225         if (hints != null) {
1226             return hints.get(hintKey);
1227         }
1228         if (!(hintKey instanceof SunHints.Key)) {
1229             return null;
1230         }
1231         int keyindex = ((SunHints.Key)hintKey).getIndex();
1232         switch (keyindex) {
1233         case SunHints.INTKEY_RENDERING:
1234             return SunHints.Value.get(SunHints.INTKEY_RENDERING,
1235                                       renderHint);
1236         case SunHints.INTKEY_ANTIALIASING:
1237             return SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1238                                       antialiasHint);
1239         case SunHints.INTKEY_TEXT_ANTIALIASING:
1240             return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1241                                       textAntialiasHint);
1242         case SunHints.INTKEY_FRACTIONALMETRICS:
1243             return SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1244                                       fractionalMetricsHint);
1245         case SunHints.INTKEY_AATEXT_LCD_CONTRAST:
1246             return new Integer(lcdTextContrast);
1247         case SunHints.INTKEY_INTERPOLATION:
1248             switch (interpolationHint) {
1249             case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1250                 return SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1251             case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1252                 return SunHints.VALUE_INTERPOLATION_BILINEAR;
1253             case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1254                 return SunHints.VALUE_INTERPOLATION_BICUBIC;
1255             }
1256             return null;
1257         case SunHints.INTKEY_STROKE_CONTROL:
1258             return SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1259                                       strokeHint);
1260         }
1261         return null;
1262     }
1263 
1264     /**
1265      * Sets the preferences for the rendering algorithms.
1266      * Hint categories include controls for rendering quality and
1267      * overall time/quality trade-off in the rendering process.
1268      * @param hints The rendering hints to be set
1269      * @see RenderingHints
1270      */
1271     public void setRenderingHints(Map<?,?> hints) {
1272         this.hints = null;
1273         renderHint = SunHints.INTVAL_RENDER_DEFAULT;
1274         antialiasHint = SunHints.INTVAL_ANTIALIAS_OFF;
1275         textAntialiasHint = SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT;
1276         fractionalMetricsHint = SunHints.INTVAL_FRACTIONALMETRICS_OFF;
1277         lcdTextContrast = lcdTextContrastDefaultValue;
1278         interpolationHint = -1;
1279         interpolationType = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
1280         boolean customHintPresent = false;
1281         Iterator iter = hints.keySet().iterator();
1282         while (iter.hasNext()) {
1283             Object key = iter.next();
1284             if (key == SunHints.KEY_RENDERING ||
1285                 key == SunHints.KEY_ANTIALIASING ||
1286                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1287                 key == SunHints.KEY_FRACTIONALMETRICS ||
1288                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1289                 key == SunHints.KEY_STROKE_CONTROL ||
1290                 key == SunHints.KEY_INTERPOLATION)
1291             {
1292                 setRenderingHint((Key) key, hints.get(key));
1293             } else {
1294                 customHintPresent = true;
1295             }
1296         }
1297         if (customHintPresent) {
1298             this.hints = makeHints(hints);
1299         }
1300         invalidatePipe();
1301     }
1302 
1303     /**
1304      * Adds a number of preferences for the rendering algorithms.
1305      * Hint categories include controls for rendering quality and
1306      * overall time/quality trade-off in the rendering process.
1307      * @param hints The rendering hints to be set
1308      * @see RenderingHints
1309      */
1310     public void addRenderingHints(Map<?,?> hints) {
1311         boolean customHintPresent = false;
1312         Iterator iter = hints.keySet().iterator();
1313         while (iter.hasNext()) {
1314             Object key = iter.next();
1315             if (key == SunHints.KEY_RENDERING ||
1316                 key == SunHints.KEY_ANTIALIASING ||
1317                 key == SunHints.KEY_TEXT_ANTIALIASING ||
1318                 key == SunHints.KEY_FRACTIONALMETRICS ||
1319                 key == SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST ||
1320                 key == SunHints.KEY_STROKE_CONTROL ||
1321                 key == SunHints.KEY_INTERPOLATION)
1322             {
1323                 setRenderingHint((Key) key, hints.get(key));
1324             } else {
1325                 customHintPresent = true;
1326             }
1327         }
1328         if (customHintPresent) {
1329             if (this.hints == null) {
1330                 this.hints = makeHints(hints);
1331             } else {
1332                 this.hints.putAll(hints);
1333             }
1334         }
1335     }
1336 
1337     /**
1338      * Gets the preferences for the rendering algorithms.
1339      * Hint categories include controls for rendering quality and
1340      * overall time/quality trade-off in the rendering process.
1341      * @see RenderingHints
1342      */
1343     public RenderingHints getRenderingHints() {
1344         if (hints == null) {
1345             return makeHints(null);
1346         } else {
1347             return (RenderingHints) hints.clone();
1348         }
1349     }
1350 
1351     RenderingHints makeHints(Map hints) {
1352         RenderingHints model = new RenderingHints(hints);
1353         model.put(SunHints.KEY_RENDERING,
1354                   SunHints.Value.get(SunHints.INTKEY_RENDERING,
1355                                      renderHint));
1356         model.put(SunHints.KEY_ANTIALIASING,
1357                   SunHints.Value.get(SunHints.INTKEY_ANTIALIASING,
1358                                      antialiasHint));
1359         model.put(SunHints.KEY_TEXT_ANTIALIASING,
1360                   SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
1361                                      textAntialiasHint));
1362         model.put(SunHints.KEY_FRACTIONALMETRICS,
1363                   SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
1364                                      fractionalMetricsHint));
1365         model.put(SunHints.KEY_TEXT_ANTIALIAS_LCD_CONTRAST,
1366                   Integer.valueOf(lcdTextContrast));
1367         Object value;
1368         switch (interpolationHint) {
1369         case SunHints.INTVAL_INTERPOLATION_NEAREST_NEIGHBOR:
1370             value = SunHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1371             break;
1372         case SunHints.INTVAL_INTERPOLATION_BILINEAR:
1373             value = SunHints.VALUE_INTERPOLATION_BILINEAR;
1374             break;
1375         case SunHints.INTVAL_INTERPOLATION_BICUBIC:
1376             value = SunHints.VALUE_INTERPOLATION_BICUBIC;
1377             break;
1378         default:
1379             value = null;
1380             break;
1381         }
1382         if (value != null) {
1383             model.put(SunHints.KEY_INTERPOLATION, value);
1384         }
1385         model.put(SunHints.KEY_STROKE_CONTROL,
1386                   SunHints.Value.get(SunHints.INTKEY_STROKE_CONTROL,
1387                                      strokeHint));
1388         return model;
1389     }
1390 
1391     /**
1392      * Concatenates the current transform of this Graphics2D with a
1393      * translation transformation.
1394      * This is equivalent to calling transform(T), where T is an
1395      * AffineTransform represented by the following matrix:
1396      * <pre>
1397      *          [   1    0    tx  ]
1398      *          [   0    1    ty  ]
1399      *          [   0    0    1   ]
1400      * </pre>
1401      */
1402     public void translate(double tx, double ty) {
1403         transform.translate(tx, ty);
1404         invalidateTransform();
1405     }
1406 
1407     /**
1408      * Concatenates the current transform of this Graphics2D with a
1409      * rotation transformation.
1410      * This is equivalent to calling transform(R), where R is an
1411      * AffineTransform represented by the following matrix:
1412      * <pre>
1413      *          [   cos(theta)    -sin(theta)    0   ]
1414      *          [   sin(theta)     cos(theta)    0   ]
1415      *          [       0              0         1   ]
1416      * </pre>
1417      * Rotating with a positive angle theta rotates points on the positive
1418      * x axis toward the positive y axis.
1419      * @param theta The angle of rotation in radians.
1420      */
1421     public void rotate(double theta) {
1422         transform.rotate(theta);
1423         invalidateTransform();
1424     }
1425 
1426     /**
1427      * Concatenates the current transform of this Graphics2D with a
1428      * translated rotation transformation.
1429      * This is equivalent to the following sequence of calls:
1430      * <pre>
1431      *          translate(x, y);
1432      *          rotate(theta);
1433      *          translate(-x, -y);
1434      * </pre>
1435      * Rotating with a positive angle theta rotates points on the positive
1436      * x axis toward the positive y axis.
1437      * @param theta The angle of rotation in radians.
1438      * @param x The x coordinate of the origin of the rotation
1439      * @param y The x coordinate of the origin of the rotation
1440      */
1441     public void rotate(double theta, double x, double y) {
1442         transform.rotate(theta, x, y);
1443         invalidateTransform();
1444     }
1445 
1446     /**
1447      * Concatenates the current transform of this Graphics2D with a
1448      * scaling transformation.
1449      * This is equivalent to calling transform(S), where S is an
1450      * AffineTransform represented by the following matrix:
1451      * <pre>
1452      *          [   sx   0    0   ]
1453      *          [   0    sy   0   ]
1454      *          [   0    0    1   ]
1455      * </pre>
1456      */
1457     public void scale(double sx, double sy) {
1458         transform.scale(sx, sy);
1459         invalidateTransform();
1460     }
1461 
1462     /**
1463      * Concatenates the current transform of this Graphics2D with a
1464      * shearing transformation.
1465      * This is equivalent to calling transform(SH), where SH is an
1466      * AffineTransform represented by the following matrix:
1467      * <pre>
1468      *          [   1   shx   0   ]
1469      *          [  shy   1    0   ]
1470      *          [   0    0    1   ]
1471      * </pre>
1472      * @param shx The factor by which coordinates are shifted towards the
1473      * positive X axis direction according to their Y coordinate
1474      * @param shy The factor by which coordinates are shifted towards the
1475      * positive Y axis direction according to their X coordinate
1476      */
1477     public void shear(double shx, double shy) {
1478         transform.shear(shx, shy);
1479         invalidateTransform();
1480     }
1481 
1482     /**
1483      * Composes a Transform object with the transform in this
1484      * Graphics2D according to the rule last-specified-first-applied.
1485      * If the currrent transform is Cx, the result of composition
1486      * with Tx is a new transform Cx'.  Cx' becomes the current
1487      * transform for this Graphics2D.
1488      * Transforming a point p by the updated transform Cx' is
1489      * equivalent to first transforming p by Tx and then transforming
1490      * the result by the original transform Cx.  In other words,
1491      * Cx'(p) = Cx(Tx(p)).
1492      * A copy of the Tx is made, if necessary, so further
1493      * modifications to Tx do not affect rendering.
1494      * @param Tx The Transform object to be composed with the current
1495      * transform.
1496      * @see #setTransform
1497      * @see AffineTransform
1498      */
1499     public void transform(AffineTransform xform) {
1500         this.transform.concatenate(xform);
1501         invalidateTransform();
1502     }
1503 
1504     /**
1505      * Translate
1506      */
1507     public void translate(int x, int y) {
1508         transform.translate(x, y);
1509         if (transformState <= TRANSFORM_INT_TRANSLATE) {
1510             transX += x;
1511             transY += y;
1512             transformState = (((transX | transY) == 0) ?
1513                               TRANSFORM_ISIDENT : TRANSFORM_INT_TRANSLATE);
1514         } else {
1515             invalidateTransform();
1516         }
1517     }
1518 
1519     /**
1520      * Sets the Transform in the current graphics state.
1521      * @param Tx The Transform object to be used in the rendering process.
1522      * @see #transform
1523      * @see TransformChain
1524      * @see AffineTransform
1525      */
1526     public void setTransform(AffineTransform Tx) {
1527         if ((constrainX|constrainY) == 0) {
1528             transform.setTransform(Tx);
1529         } else {
1530             transform.setToTranslation(constrainX, constrainY);
1531             transform.concatenate(Tx);
1532         }
1533         invalidateTransform();
1534     }
1535 
1536     protected void invalidateTransform() {
1537         int type = transform.getType();
1538         int origTransformState = transformState;
1539         if (type == AffineTransform.TYPE_IDENTITY) {
1540             transformState = TRANSFORM_ISIDENT;
1541             transX = transY = 0;
1542         } else if (type == AffineTransform.TYPE_TRANSLATION) {
1543             double dtx = transform.getTranslateX();
1544             double dty = transform.getTranslateY();
1545             transX = (int) Math.floor(dtx + 0.5);
1546             transY = (int) Math.floor(dty + 0.5);
1547             if (dtx == transX && dty == transY) {
1548                 transformState = TRANSFORM_INT_TRANSLATE;
1549             } else {
1550                 transformState = TRANSFORM_ANY_TRANSLATE;
1551             }
1552         } else if ((type & (AffineTransform.TYPE_FLIP |
1553                             AffineTransform.TYPE_MASK_ROTATION |
1554                             AffineTransform.TYPE_GENERAL_TRANSFORM)) == 0)
1555         {
1556             transformState = TRANSFORM_TRANSLATESCALE;
1557             transX = transY = 0;
1558         } else {
1559             transformState = TRANSFORM_GENERIC;
1560             transX = transY = 0;
1561         }
1562 
1563         if (transformState >= TRANSFORM_TRANSLATESCALE ||
1564             origTransformState >= TRANSFORM_TRANSLATESCALE)
1565         {
1566             /* Its only in this case that the previous or current transform
1567              * was more than a translate that font info is invalidated
1568              */
1569             cachedFRC = null;
1570             this.validFontInfo = false;
1571             this.fontMetrics = null;
1572             this.glyphVectorFontInfo = null;
1573 
1574             if (transformState != origTransformState) {
1575                 invalidatePipe();
1576             }
1577         }
1578         if (strokeState != STROKE_CUSTOM) {
1579             validateBasicStroke((BasicStroke) stroke);
1580         }
1581     }
1582 
1583     /**
1584      * Returns the current Transform in the Graphics2D state.
1585      * @see #transform
1586      * @see #setTransform
1587      */
1588     public AffineTransform getTransform() {
1589         if ((constrainX|constrainY) == 0) {
1590             return new AffineTransform(transform);
1591         }
1592         AffineTransform tx =
1593             AffineTransform.getTranslateInstance(-constrainX, -constrainY);
1594         tx.concatenate(transform);
1595         return tx;
1596     }
1597 
1598     /**
1599      * Returns the current Transform ignoring the "constrain"
1600      * rectangle.
1601      */
1602     public AffineTransform cloneTransform() {
1603         return new AffineTransform(transform);
1604     }
1605 
1606     /**
1607      * Returns the current Paint in the Graphics2D state.
1608      * @see #setPaint
1609      * @see java.awt.Graphics#setColor
1610      */
1611     public Paint getPaint() {
1612         return paint;
1613     }
1614 
1615     /**
1616      * Returns the current Composite in the Graphics2D state.
1617      * @see #setComposite
1618      */
1619     public Composite getComposite() {
1620         return composite;
1621     }
1622 
1623     public Color getColor() {
1624         return foregroundColor;
1625     }
1626 
1627     /*
1628      * Validate the eargb and pixel fields against the current color.
1629      *
1630      * The eargb field must take into account the extraAlpha
1631      * value of an AlphaComposite.  It may also take into account
1632      * the Fsrc Porter-Duff blending function if such a function is
1633      * a constant (see handling of Clear mode below).  For instance,
1634      * by factoring in the (Fsrc == 0) state of the Clear mode we can
1635      * use a SrcNoEa loop just as easily as a general Alpha loop
1636      * since the math will be the same in both cases.
1637      *
1638      * The pixel field will always be the best pixel data choice for
1639      * the final result of all calculations applied to the eargb field.
1640      *
1641      * Note that this method is only necessary under the following
1642      * conditions:
1643      *     (paintState <= PAINT_ALPHA_COLOR &&
1644      *      compositeState <= COMP_CUSTOM)
1645      * though nothing bad will happen if it is run in other states.
1646      */
1647     final void validateColor() {
1648         int eargb;
1649         if (imageComp == CompositeType.Clear) {
1650             eargb = 0;
1651         } else {
1652             eargb = foregroundColor.getRGB();
1653             if (compositeState <= COMP_ALPHA &&
1654                 imageComp != CompositeType.SrcNoEa &&
1655                 imageComp != CompositeType.SrcOverNoEa)
1656             {
1657                 AlphaComposite alphacomp = (AlphaComposite) composite;
1658                 int a = Math.round(alphacomp.getAlpha() * (eargb >>> 24));
1659                 eargb = (eargb & 0x00ffffff) | (a << 24);
1660             }
1661         }
1662         this.eargb = eargb;
1663         this.pixel = surfaceData.pixelFor(eargb);
1664     }
1665 
1666     public void setColor(Color color) {
1667         if (color == null || color == paint) {
1668             return;
1669         }
1670         this.paint = foregroundColor = color;
1671         validateColor();
1672         if ((eargb >> 24) == -1) {
1673             if (paintState == PAINT_OPAQUECOLOR) {
1674                 return;
1675             }
1676             paintState = PAINT_OPAQUECOLOR;
1677             if (imageComp == CompositeType.SrcOverNoEa) {
1678                 // special case where compState depends on opacity of paint
1679                 compositeState = COMP_ISCOPY;
1680             }
1681         } else {
1682             if (paintState == PAINT_ALPHACOLOR) {
1683                 return;
1684             }
1685             paintState = PAINT_ALPHACOLOR;
1686             if (imageComp == CompositeType.SrcOverNoEa) {
1687                 // special case where compState depends on opacity of paint
1688                 compositeState = COMP_ALPHA;
1689             }
1690         }
1691         validFontInfo = false;
1692         invalidatePipe();
1693     }
1694 
1695     /**
1696      * Sets the background color in this context used for clearing a region.
1697      * When Graphics2D is constructed for a component, the backgroung color is
1698      * inherited from the component. Setting the background color in the
1699      * Graphics2D context only affects the subsequent clearRect() calls and
1700      * not the background color of the component. To change the background
1701      * of the component, use appropriate methods of the component.
1702      * @param color The background color that should be used in
1703      * subsequent calls to clearRect().
1704      * @see getBackground
1705      * @see Graphics.clearRect()
1706      */
1707     public void setBackground(Color color) {
1708         backgroundColor = color;
1709     }
1710 
1711     /**
1712      * Returns the background color used for clearing a region.
1713      * @see setBackground
1714      */
1715     public Color getBackground() {
1716         return backgroundColor;
1717     }
1718 
1719     /**
1720      * Returns the current Stroke in the Graphics2D state.
1721      * @see setStroke
1722      */
1723     public Stroke getStroke() {
1724         return stroke;
1725     }
1726 
1727     public Rectangle getClipBounds() {
1728         Rectangle r;
1729         if (clipState == CLIP_DEVICE) {
1730             r = null;
1731         } else if (transformState <= TRANSFORM_INT_TRANSLATE) {
1732             if (usrClip instanceof Rectangle) {
1733                 r = new Rectangle((Rectangle) usrClip);
1734             } else {
1735                 r = usrClip.getBounds();
1736             }
1737             r.translate(-transX, -transY);
1738         } else {
1739             r = getClip().getBounds();
1740         }
1741         return r;
1742     }
1743 
1744     public Rectangle getClipBounds(Rectangle r) {
1745         if (clipState != CLIP_DEVICE) {
1746             if (transformState <= TRANSFORM_INT_TRANSLATE) {
1747                 if (usrClip instanceof Rectangle) {
1748                     r.setBounds((Rectangle) usrClip);
1749                 } else {
1750                     r.setBounds(usrClip.getBounds());
1751                 }
1752                 r.translate(-transX, -transY);
1753             } else {
1754                 r.setBounds(getClip().getBounds());
1755             }
1756         } else if (r == null) {
1757             throw new NullPointerException("null rectangle parameter");
1758         }
1759         return r;
1760     }
1761 
1762     public boolean hitClip(int x, int y, int width, int height) {
1763         if (width <= 0 || height <= 0) {
1764             return false;
1765         }
1766         if (transformState > TRANSFORM_INT_TRANSLATE) {
1767             // Note: Technically the most accurate test would be to
1768             // raster scan the parallelogram of the transformed rectangle
1769             // and do a span for span hit test against the clip, but for
1770             // speed we approximate the test with a bounding box of the
1771             // transformed rectangle.  The cost of rasterizing the
1772             // transformed rectangle is probably high enough that it is
1773             // not worth doing so to save the caller from having to call
1774             // a rendering method where we will end up discovering the
1775             // same answer in about the same amount of time anyway.
1776             // This logic breaks down if this hit test is being performed
1777             // on the bounds of a group of shapes in which case it might
1778             // be beneficial to be a little more accurate to avoid lots
1779             // of subsequent rendering calls.  In either case, this relaxed
1780             // test should not be significantly less accurate than the
1781             // optimal test for most transforms and so the conservative
1782             // answer should not cause too much extra work.
1783 
1784             double d[] = {
1785                 x, y,
1786                 x+width, y,
1787                 x, y+height,
1788                 x+width, y+height
1789             };
1790             transform.transform(d, 0, d, 0, 4);
1791             x = (int) Math.floor(Math.min(Math.min(d[0], d[2]),
1792                                           Math.min(d[4], d[6])));
1793             y = (int) Math.floor(Math.min(Math.min(d[1], d[3]),
1794                                           Math.min(d[5], d[7])));
1795             width = (int) Math.ceil(Math.max(Math.max(d[0], d[2]),
1796                                              Math.max(d[4], d[6])));
1797             height = (int) Math.ceil(Math.max(Math.max(d[1], d[3]),
1798                                               Math.max(d[5], d[7])));
1799         } else {
1800             x += transX;
1801             y += transY;
1802             width += x;
1803             height += y;
1804         }
1805         if (!getCompClip().intersectsQuickCheckXYXY(x, y, width, height)) {
1806             return false;
1807         }
1808         // REMIND: We could go one step further here and examine the
1809         // non-rectangular clip shape more closely if there is one.
1810         // Since the clip has already been rasterized, the performance
1811         // penalty of doing the scan is probably still within the bounds
1812         // of a good tradeoff between speed and quality of the answer.
1813         return true;
1814     }
1815 
1816     protected void validateCompClip() {
1817         int origClipState = clipState;
1818         if (usrClip == null) {
1819             clipState = CLIP_DEVICE;
1820             clipRegion = devClip;
1821         } else if (usrClip instanceof Rectangle2D) {
1822             clipState = CLIP_RECTANGULAR;
1823             if (usrClip instanceof Rectangle) {
1824                 clipRegion = devClip.getIntersection((Rectangle)usrClip);
1825             } else {
1826                 clipRegion = devClip.getIntersection(usrClip.getBounds());
1827             }
1828         } else {
1829             PathIterator cpi = usrClip.getPathIterator(null);
1830             int box[] = new int[4];
1831             ShapeSpanIterator sr = LoopPipe.getFillSSI(this);
1832             try {
1833                 sr.setOutputArea(devClip);
1834                 sr.appendPath(cpi);
1835                 sr.getPathBox(box);
1836                 Region r = Region.getInstance(box);
1837                 r.appendSpans(sr);
1838                 clipRegion = r;
1839                 clipState =
1840                     r.isRectangular() ? CLIP_RECTANGULAR : CLIP_SHAPE;
1841             } finally {
1842                 sr.dispose();
1843             }
1844         }
1845         if (origClipState != clipState &&
1846             (clipState == CLIP_SHAPE || origClipState == CLIP_SHAPE))
1847         {
1848             validFontInfo = false;
1849             invalidatePipe();
1850         }
1851     }
1852 
1853     static final int NON_RECTILINEAR_TRANSFORM_MASK =
1854         (AffineTransform.TYPE_GENERAL_TRANSFORM |
1855          AffineTransform.TYPE_GENERAL_ROTATION);
1856 
1857     protected Shape transformShape(Shape s) {
1858         if (s == null) {
1859             return null;
1860         }
1861         if (transformState > TRANSFORM_INT_TRANSLATE) {
1862             return transformShape(transform, s);
1863         } else {
1864             return transformShape(transX, transY, s);
1865         }
1866     }
1867 
1868     public Shape untransformShape(Shape s) {
1869         if (s == null) {
1870             return null;
1871         }
1872         if (transformState > TRANSFORM_INT_TRANSLATE) {
1873             try {
1874                 return transformShape(transform.createInverse(), s);
1875             } catch (NoninvertibleTransformException e) {
1876                 return null;
1877             }
1878         } else {
1879             return transformShape(-transX, -transY, s);
1880         }
1881     }
1882 
1883     protected static Shape transformShape(int tx, int ty, Shape s) {
1884         if (s == null) {
1885             return null;
1886         }
1887 
1888         if (s instanceof Rectangle) {
1889             Rectangle r = s.getBounds();
1890             r.translate(tx, ty);
1891             return r;
1892         }
1893         if (s instanceof Rectangle2D) {
1894             Rectangle2D rect = (Rectangle2D) s;
1895             return new Rectangle2D.Double(rect.getX() + tx,
1896                                           rect.getY() + ty,
1897                                           rect.getWidth(),
1898                                           rect.getHeight());
1899         }
1900 
1901         if (tx == 0 && ty == 0) {
1902             return cloneShape(s);
1903         }
1904 
1905         AffineTransform mat = AffineTransform.getTranslateInstance(tx, ty);
1906         return mat.createTransformedShape(s);
1907     }
1908 
1909     protected static Shape transformShape(AffineTransform tx, Shape clip) {
1910         if (clip == null) {
1911             return null;
1912         }
1913 
1914         if (clip instanceof Rectangle2D &&
1915             (tx.getType() & NON_RECTILINEAR_TRANSFORM_MASK) == 0)
1916         {
1917             Rectangle2D rect = (Rectangle2D) clip;
1918             double matrix[] = new double[4];
1919             matrix[0] = rect.getX();
1920             matrix[1] = rect.getY();
1921             matrix[2] = matrix[0] + rect.getWidth();
1922             matrix[3] = matrix[1] + rect.getHeight();
1923             tx.transform(matrix, 0, matrix, 0, 2);
1924             rect = new Rectangle2D.Float();
1925             rect.setFrameFromDiagonal(matrix[0], matrix[1],
1926                                       matrix[2], matrix[3]);
1927             return rect;
1928         }
1929 
1930         if (tx.isIdentity()) {
1931             return cloneShape(clip);
1932         }
1933 
1934         return tx.createTransformedShape(clip);
1935     }
1936 
1937     public void clipRect(int x, int y, int w, int h) {
1938         clip(new Rectangle(x, y, w, h));
1939     }
1940 
1941     public void setClip(int x, int y, int w, int h) {
1942         setClip(new Rectangle(x, y, w, h));
1943     }
1944 
1945     public Shape getClip() {
1946         return untransformShape(usrClip);
1947     }
1948 
1949     public void setClip(Shape sh) {
1950         usrClip = transformShape(sh);
1951         validateCompClip();
1952     }
1953 
1954     /**
1955      * Intersects the current clip with the specified Path and sets the
1956      * current clip to the resulting intersection. The clip is transformed
1957      * with the current transform in the Graphics2D state before being
1958      * intersected with the current clip. This method is used to make the
1959      * current clip smaller. To make the clip larger, use any setClip method.
1960      * @param p The Path to be intersected with the current clip.
1961      */
1962     public void clip(Shape s) {
1963         s = transformShape(s);
1964         if (usrClip != null) {
1965             s = intersectShapes(usrClip, s, true, true);
1966         }
1967         usrClip = s;
1968         validateCompClip();
1969     }
1970 
1971     public void setPaintMode() {
1972         setComposite(AlphaComposite.SrcOver);
1973     }
1974 
1975     public void setXORMode(Color c) {
1976         if (c == null) {
1977             throw new IllegalArgumentException("null XORColor");
1978         }
1979         setComposite(new XORComposite(c, surfaceData));
1980     }
1981 
1982     Blit lastCAblit;
1983     Composite lastCAcomp;
1984 
1985     public void copyArea(int x, int y, int w, int h, int dx, int dy) {
1986         try {
1987             doCopyArea(x, y, w, h, dx, dy);
1988         } catch (InvalidPipeException e) {
1989             revalidateAll();
1990             try {
1991                 doCopyArea(x, y, w, h, dx, dy);
1992             } catch (InvalidPipeException e2) {
1993                 // Still catching the exception; we are not yet ready to
1994                 // validate the surfaceData correctly.  Fail for now and
1995                 // try again next time around.
1996             }
1997         } finally {
1998             surfaceData.markDirty();
1999         }
2000     }
2001 
2002     private void doCopyArea(int x, int y, int w, int h, int dx, int dy) {
2003         if (w <= 0 || h <= 0) {
2004             return;
2005         }
2006         SurfaceData theData = surfaceData;
2007         if (theData.copyArea(this, x, y, w, h, dx, dy)) {
2008             return;
2009         }
2010         if (transformState >= TRANSFORM_TRANSLATESCALE) {
2011             throw new InternalError("transformed copyArea not implemented yet");
2012         }
2013         // REMIND: This method does not deal with missing data from the
2014         // source object (i.e. it does not send exposure events...)
2015 
2016         Region clip = getCompClip();
2017 
2018         Composite comp = composite;
2019         if (lastCAcomp != comp) {
2020             SurfaceType dsttype = theData.getSurfaceType();
2021             CompositeType comptype = imageComp;
2022             if (CompositeType.SrcOverNoEa.equals(comptype) &&
2023                 theData.getTransparency() == Transparency.OPAQUE)
2024             {
2025                 comptype = CompositeType.SrcNoEa;
2026             }
2027             lastCAblit = Blit.locate(dsttype, comptype, dsttype);
2028             lastCAcomp = comp;
2029         }
2030 
2031         x += transX;
2032         y += transY;
2033 
2034         Blit ob = lastCAblit;
2035         if (dy == 0 && dx > 0 && dx < w) {
2036             while (w > 0) {
2037                 int partW = Math.min(w, dx);
2038                 w -= partW;
2039                 int sx = x + w;
2040                 ob.Blit(theData, theData, comp, clip,
2041                         sx, y, sx+dx, y+dy, partW, h);
2042             }
2043             return;
2044         }
2045         if (dy > 0 && dy < h && dx > -w && dx < w) {
2046             while (h > 0) {
2047                 int partH = Math.min(h, dy);
2048                 h -= partH;
2049                 int sy = y + h;
2050                 ob.Blit(theData, theData, comp, clip,
2051                         x, sy, x+dx, sy+dy, w, partH);
2052             }
2053             return;
2054         }
2055         ob.Blit(theData, theData, comp, clip, x, y, x+dx, y+dy, w, h);
2056     }
2057 
2058     /*
2059     public void XcopyArea(int x, int y, int w, int h, int dx, int dy) {
2060         Rectangle rect = new Rectangle(x, y, w, h);
2061         rect = transformBounds(rect, transform);
2062         Point2D    point = new Point2D.Float(dx, dy);
2063         Point2D    root  = new Point2D.Float(0, 0);
2064         point = transform.transform(point, point);
2065         root  = transform.transform(root, root);
2066         int fdx = (int)(point.getX()-root.getX());
2067         int fdy = (int)(point.getY()-root.getY());
2068 
2069         Rectangle r = getCompBounds().intersection(rect.getBounds());
2070 
2071         if (r.isEmpty()) {
2072             return;
2073         }
2074 
2075         // Begin Rasterizer for Clip Shape
2076         boolean skipClip = true;
2077         byte[] clipAlpha = null;
2078 
2079         if (clipState == CLIP_SHAPE) {
2080 
2081             int box[] = new int[4];
2082 
2083             clipRegion.getBounds(box);
2084             Rectangle devR = new Rectangle(box[0], box[1],
2085                                            box[2] - box[0],
2086                                            box[3] - box[1]);
2087             if (!devR.isEmpty()) {
2088                 OutputManager mgr = getOutputManager();
2089                 RegionIterator ri = clipRegion.getIterator();
2090                 while (ri.nextYRange(box)) {
2091                     int spany = box[1];
2092                     int spanh = box[3] - spany;
2093                     while (ri.nextXBand(box)) {
2094                         int spanx = box[0];
2095                         int spanw = box[2] - spanx;
2096                         mgr.copyArea(this, null,
2097                                      spanw, 0,
2098                                      spanx, spany,
2099                                      spanw, spanh,
2100                                      fdx, fdy,
2101                                      null);
2102                     }
2103                 }
2104             }
2105             return;
2106         }
2107         // End Rasterizer for Clip Shape
2108 
2109         getOutputManager().copyArea(this, null,
2110                                     r.width, 0,
2111                                     r.x, r.y, r.width,
2112                                     r.height, fdx, fdy,
2113                                     null);
2114     }
2115     */
2116 
2117     public void drawLine(int x1, int y1, int x2, int y2) {
2118         try {
2119             drawpipe.drawLine(this, x1, y1, x2, y2);
2120         } catch (InvalidPipeException e) {
2121             revalidateAll();
2122             try {
2123                 drawpipe.drawLine(this, x1, y1, x2, y2);
2124             } catch (InvalidPipeException e2) {
2125                 // Still catching the exception; we are not yet ready to
2126                 // validate the surfaceData correctly.  Fail for now and
2127                 // try again next time around.
2128             }
2129         } finally {
2130             surfaceData.markDirty();
2131         }
2132     }
2133 
2134     public void drawRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2135         try {
2136             drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2137         } catch (InvalidPipeException e) {
2138             revalidateAll();
2139             try {
2140                 drawpipe.drawRoundRect(this, x, y, w, h, arcW, arcH);
2141             } catch (InvalidPipeException e2) {
2142                 // Still catching the exception; we are not yet ready to
2143                 // validate the surfaceData correctly.  Fail for now and
2144                 // try again next time around.
2145             }
2146         } finally {
2147             surfaceData.markDirty();
2148         }
2149     }
2150 
2151     public void fillRoundRect(int x, int y, int w, int h, int arcW, int arcH) {
2152         try {
2153             fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2154         } catch (InvalidPipeException e) {
2155             revalidateAll();
2156             try {
2157                 fillpipe.fillRoundRect(this, x, y, w, h, arcW, arcH);
2158             } catch (InvalidPipeException e2) {
2159                 // Still catching the exception; we are not yet ready to
2160                 // validate the surfaceData correctly.  Fail for now and
2161                 // try again next time around.
2162             }
2163         } finally {
2164             surfaceData.markDirty();
2165         }
2166     }
2167 
2168     public void drawOval(int x, int y, int w, int h) {
2169         try {
2170             drawpipe.drawOval(this, x, y, w, h);
2171         } catch (InvalidPipeException e) {
2172             revalidateAll();
2173             try {
2174                 drawpipe.drawOval(this, x, y, w, h);
2175             } catch (InvalidPipeException e2) {
2176                 // Still catching the exception; we are not yet ready to
2177                 // validate the surfaceData correctly.  Fail for now and
2178                 // try again next time around.
2179             }
2180         } finally {
2181             surfaceData.markDirty();
2182         }
2183     }
2184 
2185     public void fillOval(int x, int y, int w, int h) {
2186         try {
2187             fillpipe.fillOval(this, x, y, w, h);
2188         } catch (InvalidPipeException e) {
2189             revalidateAll();
2190             try {
2191                 fillpipe.fillOval(this, x, y, w, h);
2192             } catch (InvalidPipeException e2) {
2193                 // Still catching the exception; we are not yet ready to
2194                 // validate the surfaceData correctly.  Fail for now and
2195                 // try again next time around.
2196             }
2197         } finally {
2198             surfaceData.markDirty();
2199         }
2200     }
2201 
2202     public void drawArc(int x, int y, int w, int h,
2203                         int startAngl, int arcAngl) {
2204         try {
2205             drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2206         } catch (InvalidPipeException e) {
2207             revalidateAll();
2208             try {
2209                 drawpipe.drawArc(this, x, y, w, h, startAngl, arcAngl);
2210             } catch (InvalidPipeException e2) {
2211                 // Still catching the exception; we are not yet ready to
2212                 // validate the surfaceData correctly.  Fail for now and
2213                 // try again next time around.
2214             }
2215         } finally {
2216             surfaceData.markDirty();
2217         }
2218     }
2219 
2220     public void fillArc(int x, int y, int w, int h,
2221                         int startAngl, int arcAngl) {
2222         try {
2223             fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2224         } catch (InvalidPipeException e) {
2225             revalidateAll();
2226             try {
2227                 fillpipe.fillArc(this, x, y, w, h, startAngl, arcAngl);
2228             } catch (InvalidPipeException e2) {
2229                 // Still catching the exception; we are not yet ready to
2230                 // validate the surfaceData correctly.  Fail for now and
2231                 // try again next time around.
2232             }
2233         } finally {
2234             surfaceData.markDirty();
2235         }
2236     }
2237 
2238     public void drawPolyline(int xPoints[], int yPoints[], int nPoints) {
2239         try {
2240             drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2241         } catch (InvalidPipeException e) {
2242             revalidateAll();
2243             try {
2244                 drawpipe.drawPolyline(this, xPoints, yPoints, nPoints);
2245             } catch (InvalidPipeException e2) {
2246                 // Still catching the exception; we are not yet ready to
2247                 // validate the surfaceData correctly.  Fail for now and
2248                 // try again next time around.
2249             }
2250         } finally {
2251             surfaceData.markDirty();
2252         }
2253     }
2254 
2255     public void drawPolygon(int xPoints[], int yPoints[], int nPoints) {
2256         try {
2257             drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2258         } catch (InvalidPipeException e) {
2259             revalidateAll();
2260             try {
2261                 drawpipe.drawPolygon(this, xPoints, yPoints, nPoints);
2262             } catch (InvalidPipeException e2) {
2263                 // Still catching the exception; we are not yet ready to
2264                 // validate the surfaceData correctly.  Fail for now and
2265                 // try again next time around.
2266             }
2267         } finally {
2268             surfaceData.markDirty();
2269         }
2270     }
2271 
2272     public void fillPolygon(int xPoints[], int yPoints[], int nPoints) {
2273         try {
2274             fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2275         } catch (InvalidPipeException e) {
2276             revalidateAll();
2277             try {
2278                 fillpipe.fillPolygon(this, xPoints, yPoints, nPoints);
2279             } catch (InvalidPipeException e2) {
2280                 // Still catching the exception; we are not yet ready to
2281                 // validate the surfaceData correctly.  Fail for now and
2282                 // try again next time around.
2283             }
2284         } finally {
2285             surfaceData.markDirty();
2286         }
2287     }
2288 
2289     public void drawRect (int x, int y, int w, int h) {
2290         try {
2291             drawpipe.drawRect(this, x, y, w, h);
2292         } catch (InvalidPipeException e) {
2293             revalidateAll();
2294             try {
2295                 drawpipe.drawRect(this, x, y, w, h);
2296             } catch (InvalidPipeException e2) {
2297                 // Still catching the exception; we are not yet ready to
2298                 // validate the surfaceData correctly.  Fail for now and
2299                 // try again next time around.
2300             }
2301         } finally {
2302             surfaceData.markDirty();
2303         }
2304     }
2305 
2306     public void fillRect (int x, int y, int w, int h) {
2307         try {
2308             fillpipe.fillRect(this, x, y, w, h);
2309         } catch (InvalidPipeException e) {
2310             revalidateAll();
2311             try {
2312                 fillpipe.fillRect(this, x, y, w, h);
2313             } catch (InvalidPipeException e2) {
2314                 // Still catching the exception; we are not yet ready to
2315                 // validate the surfaceData correctly.  Fail for now and
2316                 // try again next time around.
2317             }
2318         } finally {
2319             surfaceData.markDirty();
2320         }
2321     }
2322 
2323     private void revalidateAll() {
2324         try {
2325             // REMIND: This locking needs to be done around the
2326             // caller of this method so that the pipe stays valid
2327             // long enough to call the new primitive.
2328             // REMIND: No locking yet in screen SurfaceData objects!
2329             // surfaceData.lock();
2330             surfaceData = surfaceData.getReplacement();
2331             if (surfaceData == null) {
2332                 surfaceData = NullSurfaceData.theInstance;
2333             }
2334 
2335             // this will recalculate the composite clip
2336             setDevClip(surfaceData.getBounds());
2337 
2338             if (paintState <= PAINT_ALPHACOLOR) {
2339                 validateColor();
2340             }
2341             if (composite instanceof XORComposite) {
2342                 Color c = ((XORComposite) composite).getXorColor();
2343                 setComposite(new XORComposite(c, surfaceData));
2344             }
2345             validatePipe();
2346         } finally {
2347             // REMIND: No locking yet in screen SurfaceData objects!
2348             // surfaceData.unlock();
2349         }
2350     }
2351 
2352     public void clearRect(int x, int y, int w, int h) {
2353         // REMIND: has some "interesting" consequences if threads are
2354         // not synchronized
2355         Composite c = composite;
2356         Paint p = paint;
2357         setComposite(AlphaComposite.Src);
2358         setColor(getBackground());
2359         validatePipe();
2360         fillRect(x, y, w, h);
2361         setPaint(p);
2362         setComposite(c);
2363     }
2364 
2365     /**
2366      * Strokes the outline of a Path using the settings of the current
2367      * graphics state.  The rendering attributes applied include the
2368      * clip, transform, paint or color, composite and stroke attributes.
2369      * @param p The path to be drawn.
2370      * @see #setStroke
2371      * @see #setPaint
2372      * @see java.awt.Graphics#setColor
2373      * @see #transform
2374      * @see #setTransform
2375      * @see #clip
2376      * @see #setClip
2377      * @see #setComposite
2378      */
2379     public void draw(Shape s) {
2380         try {
2381             shapepipe.draw(this, s);
2382         } catch (InvalidPipeException e) {
2383             revalidateAll();
2384             try {
2385                 shapepipe.draw(this, s);
2386             } catch (InvalidPipeException e2) {
2387                 // Still catching the exception; we are not yet ready to
2388                 // validate the surfaceData correctly.  Fail for now and
2389                 // try again next time around.
2390             }
2391         } finally {
2392             surfaceData.markDirty();
2393         }
2394     }
2395 
2396 
2397     /**
2398      * Fills the interior of a Path using the settings of the current
2399      * graphics state. The rendering attributes applied include the
2400      * clip, transform, paint or color, and composite.
2401      * @see #setPaint
2402      * @see java.awt.Graphics#setColor
2403      * @see #transform
2404      * @see #setTransform
2405      * @see #setComposite
2406      * @see #clip
2407      * @see #setClip
2408      */
2409     public void fill(Shape s) {
2410         try {
2411             shapepipe.fill(this, s);
2412         } catch (InvalidPipeException e) {
2413             revalidateAll();
2414             try {
2415                 shapepipe.fill(this, s);
2416             } catch (InvalidPipeException e2) {
2417                 // Still catching the exception; we are not yet ready to
2418                 // validate the surfaceData correctly.  Fail for now and
2419                 // try again next time around.
2420             }
2421         } finally {
2422             surfaceData.markDirty();
2423         }
2424     }
2425 
2426     /**
2427      * Returns true if the given AffineTransform is an integer
2428      * translation.
2429      */
2430     private static boolean isIntegerTranslation(AffineTransform xform) {
2431         if (xform.isIdentity()) {
2432             return true;
2433         }
2434         if (xform.getType() == AffineTransform.TYPE_TRANSLATION) {
2435             double tx = xform.getTranslateX();
2436             double ty = xform.getTranslateY();
2437             return (tx == (int)tx && ty == (int)ty);
2438         }
2439         return false;
2440     }
2441 
2442     /**
2443      * Returns the index of the tile corresponding to the supplied position
2444      * given the tile grid offset and size along the same axis.
2445      */
2446     private static int getTileIndex(int p, int tileGridOffset, int tileSize) {
2447         p -= tileGridOffset;
2448         if (p < 0) {
2449             p += 1 - tileSize;          // force round to -infinity (ceiling)
2450         }
2451         return p/tileSize;
2452     }
2453 
2454     /**
2455      * Returns a rectangle in image coordinates that may be required
2456      * in order to draw the given image into the given clipping region
2457      * through a pair of AffineTransforms.  In addition, horizontal and
2458      * vertical padding factors for antialising and interpolation may
2459      * be used.
2460      */
2461     private static Rectangle getImageRegion(RenderedImage img,
2462                                             Region compClip,
2463                                             AffineTransform transform,
2464                                             AffineTransform xform,
2465                                             int padX, int padY) {
2466         Rectangle imageRect =
2467             new Rectangle(img.getMinX(), img.getMinY(),
2468                           img.getWidth(), img.getHeight());
2469 
2470         Rectangle result = null;
2471         try {
2472             double p[] = new double[8];
2473             p[0] = p[2] = compClip.getLoX();
2474             p[4] = p[6] = compClip.getHiX();
2475             p[1] = p[5] = compClip.getLoY();
2476             p[3] = p[7] = compClip.getHiY();
2477 
2478             // Inverse transform the output bounding rect
2479             transform.inverseTransform(p, 0, p, 0, 4);
2480             xform.inverseTransform(p, 0, p, 0, 4);
2481 
2482             // Determine a bounding box for the inverse transformed region
2483             double x0,x1,y0,y1;
2484             x0 = x1 = p[0];
2485             y0 = y1 = p[1];
2486 
2487             for (int i = 2; i < 8; ) {
2488                 double pt = p[i++];
2489                 if (pt < x0)  {
2490                     x0 = pt;
2491                 } else if (pt > x1) {
2492                     x1 = pt;
2493                 }
2494                 pt = p[i++];
2495                 if (pt < y0)  {
2496                     y0 = pt;
2497                 } else if (pt > y1) {
2498                     y1 = pt;
2499                 }
2500             }
2501 
2502             // This is padding for anti-aliasing and such.  It may
2503             // be more than is needed.
2504             int x = (int)x0 - padX;
2505             int w = (int)(x1 - x0 + 2*padX);
2506             int y = (int)y0 - padY;
2507             int h = (int)(y1 - y0 + 2*padY);
2508 
2509             Rectangle clipRect = new Rectangle(x,y,w,h);
2510             result = clipRect.intersection(imageRect);
2511         } catch (NoninvertibleTransformException nte) {
2512             // Worst case bounds are the bounds of the image.
2513             result = imageRect;
2514         }
2515 
2516         return result;
2517     }
2518 
2519     /**
2520      * Draws an image, applying a transform from image space into user space
2521      * before drawing.
2522      * The transformation from user space into device space is done with
2523      * the current transform in the Graphics2D.
2524      * The given transformation is applied to the image before the
2525      * transform attribute in the Graphics2D state is applied.
2526      * The rendering attributes applied include the clip, transform,
2527      * and composite attributes. Note that the result is
2528      * undefined, if the given transform is noninvertible.
2529      * @param img The image to be drawn. Does nothing if img is null.
2530      * @param xform The transformation from image space into user space.
2531      * @see #transform
2532      * @see #setTransform
2533      * @see #setComposite
2534      * @see #clip
2535      * @see #setClip
2536      */
2537     public void drawRenderedImage(RenderedImage img,
2538                                   AffineTransform xform) {
2539 
2540         if (img == null) {
2541             return;
2542         }
2543 
2544         // BufferedImage case: use a simple drawImage call
2545         if (img instanceof BufferedImage) {
2546             BufferedImage bufImg = (BufferedImage)img;
2547             drawImage(bufImg,xform,null);
2548             return;
2549         }
2550 
2551         // transformState tracks the state of transform and
2552         // transX, transY contain the integer casts of the
2553         // translation factors
2554         boolean isIntegerTranslate =
2555             (transformState <= TRANSFORM_INT_TRANSLATE) &&
2556             isIntegerTranslation(xform);
2557 
2558         // Include padding for interpolation/antialiasing if necessary
2559         int pad = isIntegerTranslate ? 0 : 3;
2560 
2561         // Determine the region of the image that may contribute to
2562         // the clipped drawing area
2563         Rectangle region = getImageRegion(img,
2564                                           getCompClip(),
2565                                           transform,
2566                                           xform,
2567                                           pad, pad);
2568         if (region.width <= 0 || region.height <= 0) {
2569             return;
2570         }
2571 
2572         // Attempt to optimize integer translation of tiled images.
2573         // Although theoretically we are O.K. if the concatenation of
2574         // the user transform and the device transform is an integer
2575         // translation, we'll play it safe and only optimize the case
2576         // where both are integer translations.
2577         if (isIntegerTranslate) {
2578             // Use optimized code
2579             // Note that drawTranslatedRenderedImage calls copyImage
2580             // which takes the user space to device space transform into
2581             // account, but we need to provide the image space to user space
2582             // translations.
2583 
2584             drawTranslatedRenderedImage(img, region,
2585                                         (int) xform.getTranslateX(),
2586                                         (int) xform.getTranslateY());
2587             return;
2588         }
2589 
2590         // General case: cobble the necessary region into a single Raster
2591         Raster raster = img.getData(region);
2592 
2593         // Make a new Raster with the same contents as raster
2594         // but starting at (0, 0).  This raster is thus in the same
2595         // coordinate system as the SampleModel of the original raster.
2596         WritableRaster wRaster =
2597               Raster.createWritableRaster(raster.getSampleModel(),
2598                                           raster.getDataBuffer(),
2599                                           null);
2600 
2601         // If the original raster was in a different coordinate
2602         // system than its SampleModel, we need to perform an
2603         // additional translation in order to get the (minX, minY)
2604         // pixel of raster to be pixel (0, 0) of wRaster.  We also
2605         // have to have the correct width and height.
2606         int minX = raster.getMinX();
2607         int minY = raster.getMinY();
2608         int width = raster.getWidth();
2609         int height = raster.getHeight();
2610         int px = minX - raster.getSampleModelTranslateX();
2611         int py = minY - raster.getSampleModelTranslateY();
2612         if (px != 0 || py != 0 || width != wRaster.getWidth() ||
2613             height != wRaster.getHeight()) {
2614             wRaster =
2615                 wRaster.createWritableChild(px,
2616                                             py,
2617                                             width,
2618                                             height,
2619                                             0, 0,
2620                                             null);
2621         }
2622 
2623         // Now we have a BufferedImage starting at (0, 0)
2624         // with the same contents that started at (minX, minY)
2625         // in raster.  So we must draw the BufferedImage with a
2626         // translation of (minX, minY).
2627         AffineTransform transXform = (AffineTransform)xform.clone();
2628         transXform.translate(minX, minY);
2629 
2630         ColorModel cm = img.getColorModel();
2631         BufferedImage bufImg = new BufferedImage(cm,
2632                                                  wRaster,
2633                                                  cm.isAlphaPremultiplied(),
2634                                                  null);
2635         drawImage(bufImg, transXform, null);
2636     }
2637 
2638     /**
2639      * Intersects <code>destRect</code> with <code>clip</code> and
2640      * overwrites <code>destRect</code> with the result.
2641      * Returns false if the intersection was empty, true otherwise.
2642      */
2643     private boolean clipTo(Rectangle destRect, Rectangle clip) {
2644         int x1 = Math.max(destRect.x, clip.x);
2645         int x2 = Math.min(destRect.x + destRect.width, clip.x + clip.width);
2646         int y1 = Math.max(destRect.y, clip.y);
2647         int y2 = Math.min(destRect.y + destRect.height, clip.y + clip.height);
2648         if (((x2 - x1) < 0) || ((y2 - y1) < 0)) {
2649             destRect.width = -1; // Set both just to be safe
2650             destRect.height = -1;
2651             return false;
2652         } else {
2653             destRect.x = x1;
2654             destRect.y = y1;
2655             destRect.width = x2 - x1;
2656             destRect.height = y2 - y1;
2657             return true;
2658         }
2659     }
2660 
2661     /**
2662      * Draw a portion of a RenderedImage tile-by-tile with a given
2663      * integer image to user space translation.  The user to
2664      * device transform must also be an integer translation.
2665      */
2666     private void drawTranslatedRenderedImage(RenderedImage img,
2667                                              Rectangle region,
2668                                              int i2uTransX,
2669                                              int i2uTransY) {
2670         // Cache tile grid info
2671         int tileGridXOffset = img.getTileGridXOffset();
2672         int tileGridYOffset = img.getTileGridYOffset();
2673         int tileWidth = img.getTileWidth();
2674         int tileHeight = img.getTileHeight();
2675 
2676         // Determine the tile index extrema in each direction
2677         int minTileX =
2678             getTileIndex(region.x, tileGridXOffset, tileWidth);
2679         int minTileY =
2680             getTileIndex(region.y, tileGridYOffset, tileHeight);
2681         int maxTileX =
2682             getTileIndex(region.x + region.width - 1,
2683                          tileGridXOffset, tileWidth);
2684         int maxTileY =
2685             getTileIndex(region.y + region.height - 1,
2686                          tileGridYOffset, tileHeight);
2687 
2688         // Create a single ColorModel to use for all BufferedImages
2689         ColorModel colorModel = img.getColorModel();
2690 
2691         // Reuse the same Rectangle for each iteration
2692         Rectangle tileRect = new Rectangle();
2693 
2694         for (int ty = minTileY; ty <= maxTileY; ty++) {
2695             for (int tx = minTileX; tx <= maxTileX; tx++) {
2696                 // Get the current tile.
2697                 Raster raster = img.getTile(tx, ty);
2698 
2699                 // Fill in tileRect with the tile bounds
2700                 tileRect.x = tx*tileWidth + tileGridXOffset;
2701                 tileRect.y = ty*tileHeight + tileGridYOffset;
2702                 tileRect.width = tileWidth;
2703                 tileRect.height = tileHeight;
2704 
2705                 // Clip the tile against the image bounds and
2706                 // backwards mapped clip region
2707                 // The result can't be empty
2708                 clipTo(tileRect, region);
2709 
2710                 // Create a WritableRaster containing the tile
2711                 WritableRaster wRaster = null;
2712                 if (raster instanceof WritableRaster) {
2713                     wRaster = (WritableRaster)raster;
2714                 } else {
2715                     // Create a WritableRaster in the same coordinate system
2716                     // as the original raster.
2717                     wRaster =
2718                         Raster.createWritableRaster(raster.getSampleModel(),
2719                                                     raster.getDataBuffer(),
2720                                                     null);
2721                 }
2722 
2723                 // Translate wRaster to start at (0, 0) and to contain
2724                 // only the relevent portion of the tile
2725                 wRaster = wRaster.createWritableChild(tileRect.x, tileRect.y,
2726                                                       tileRect.width,
2727                                                       tileRect.height,
2728                                                       0, 0,
2729                                                       null);
2730 
2731                 // Wrap wRaster in a BufferedImage
2732                 BufferedImage bufImg =
2733                     new BufferedImage(colorModel,
2734                                       wRaster,
2735                                       colorModel.isAlphaPremultiplied(),
2736                                       null);
2737                 // Now we have a BufferedImage starting at (0, 0) that
2738                 // represents data from a Raster starting at
2739                 // (tileRect.x, tileRect.y).  Additionally, it needs
2740                 // to be translated by (i2uTransX, i2uTransY).  We call
2741                 // copyImage to draw just the region of interest
2742                 // without needing to create a child image.
2743                 copyImage(bufImg, tileRect.x + i2uTransX,
2744                           tileRect.y + i2uTransY, 0, 0, tileRect.width,
2745                           tileRect.height, null, null);
2746             }
2747         }
2748     }
2749 
2750     public void drawRenderableImage(RenderableImage img,
2751                                     AffineTransform xform) {
2752 
2753         if (img == null) {
2754             return;
2755         }
2756 
2757         AffineTransform pipeTransform = transform;
2758         AffineTransform concatTransform = new AffineTransform(xform);
2759         concatTransform.concatenate(pipeTransform);
2760         AffineTransform reverseTransform;
2761 
2762         RenderContext rc = new RenderContext(concatTransform);
2763 
2764         try {
2765             reverseTransform = pipeTransform.createInverse();
2766         } catch (NoninvertibleTransformException nte) {
2767             rc = new RenderContext(pipeTransform);
2768             reverseTransform = new AffineTransform();
2769         }
2770 
2771         RenderedImage rendering = img.createRendering(rc);
2772         drawRenderedImage(rendering,reverseTransform);
2773     }
2774 
2775 
2776 
2777     /*
2778      * Transform the bounding box of the BufferedImage
2779      */
2780     protected Rectangle transformBounds(Rectangle rect,
2781                                         AffineTransform tx) {
2782         if (tx.isIdentity()) {
2783             return rect;
2784         }
2785 
2786         Shape s = transformShape(tx, rect);
2787         return s.getBounds();
2788     }
2789 
2790     // text rendering methods
2791     public void drawString(String str, int x, int y) {
2792         if (str == null) {
2793             throw new NullPointerException("String is null");
2794         }
2795 
2796         if (font.hasLayoutAttributes()) {
2797             if (str.length() == 0) {
2798                 return;
2799             }
2800             new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);
2801             return;
2802         }
2803 
2804         try {
2805             textpipe.drawString(this, str, x, y);
2806         } catch (InvalidPipeException e) {
2807             revalidateAll();
2808             try {
2809                 textpipe.drawString(this, str, x, y);
2810             } catch (InvalidPipeException e2) {
2811                 // Still catching the exception; we are not yet ready to
2812                 // validate the surfaceData correctly.  Fail for now and
2813                 // try again next time around.
2814             }
2815         } finally {
2816             surfaceData.markDirty();
2817         }
2818     }
2819 
2820     public void drawString(String str, float x, float y) {
2821         if (str == null) {
2822             throw new NullPointerException("String is null");
2823         }
2824 
2825         if (font.hasLayoutAttributes()) {
2826             if (str.length() == 0) {
2827                 return;
2828             }
2829             new TextLayout(str, font, getFontRenderContext()).draw(this, x, y);
2830             return;
2831         }
2832 
2833         try {
2834             textpipe.drawString(this, str, x, y);
2835         } catch (InvalidPipeException e) {
2836             revalidateAll();
2837             try {
2838                 textpipe.drawString(this, str, x, y);
2839             } catch (InvalidPipeException e2) {
2840                 // Still catching the exception; we are not yet ready to
2841                 // validate the surfaceData correctly.  Fail for now and
2842                 // try again next time around.
2843             }
2844         } finally {
2845             surfaceData.markDirty();
2846         }
2847     }
2848 
2849     public void drawString(AttributedCharacterIterator iterator,
2850                            int x, int y) {
2851         if (iterator == null) {
2852             throw new NullPointerException("AttributedCharacterIterator is null");
2853         }
2854         if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2855             return; /* nothing to draw */
2856         }
2857         TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2858         tl.draw(this, (float) x, (float) y);
2859     }
2860 
2861     public void drawString(AttributedCharacterIterator iterator,
2862                            float x, float y) {
2863         if (iterator == null) {
2864             throw new NullPointerException("AttributedCharacterIterator is null");
2865         }
2866         if (iterator.getBeginIndex() == iterator.getEndIndex()) {
2867             return; /* nothing to draw */
2868         }
2869         TextLayout tl = new TextLayout(iterator, getFontRenderContext());
2870         tl.draw(this, x, y);
2871     }
2872 
2873     public void drawGlyphVector(GlyphVector gv, float x, float y)
2874     {
2875         if (gv == null) {
2876             throw new NullPointerException("GlyphVector is null");
2877         }
2878 
2879         try {
2880             textpipe.drawGlyphVector(this, gv, x, y);
2881         } catch (InvalidPipeException e) {
2882             revalidateAll();
2883             try {
2884                 textpipe.drawGlyphVector(this, gv, x, y);
2885             } catch (InvalidPipeException e2) {
2886                 // Still catching the exception; we are not yet ready to
2887                 // validate the surfaceData correctly.  Fail for now and
2888                 // try again next time around.
2889             }
2890         } finally {
2891             surfaceData.markDirty();
2892         }
2893     }
2894 
2895     public void drawChars(char data[], int offset, int length, int x, int y) {
2896 
2897         if (data == null) {
2898             throw new NullPointerException("char data is null");
2899         }
2900         if (offset < 0 || length < 0 || offset + length > data.length) {
2901             throw new ArrayIndexOutOfBoundsException("bad offset/length");
2902         }
2903         if (font.hasLayoutAttributes()) {
2904             if (data.length == 0) {
2905                 return;
2906             }
2907             new TextLayout(new String(data, offset, length),
2908                            font, getFontRenderContext()).draw(this, x, y);
2909             return;
2910         }
2911 
2912         try {
2913             textpipe.drawChars(this, data, offset, length, x, y);
2914         } catch (InvalidPipeException e) {
2915             revalidateAll();
2916             try {
2917                 textpipe.drawChars(this, data, offset, length, x, y);
2918             } catch (InvalidPipeException e2) {
2919                 // Still catching the exception; we are not yet ready to
2920                 // validate the surfaceData correctly.  Fail for now and
2921                 // try again next time around.
2922             }
2923         } finally {
2924             surfaceData.markDirty();
2925         }
2926     }
2927 
2928     public void drawBytes(byte data[], int offset, int length, int x, int y) {
2929         if (data == null) {
2930             throw new NullPointerException("byte data is null");
2931         }
2932         if (offset < 0 || length < 0 || offset + length > data.length) {
2933             throw new ArrayIndexOutOfBoundsException("bad offset/length");
2934         }
2935         /* Byte data is interpreted as 8-bit ASCII. Re-use drawChars loops */
2936         char chData[] = new char[length];
2937         for (int i = length; i-- > 0; ) {
2938             chData[i] = (char)(data[i+offset] & 0xff);
2939         }
2940         if (font.hasLayoutAttributes()) {
2941             if (data.length == 0) {
2942                 return;
2943             }
2944             new TextLayout(new String(chData),
2945                            font, getFontRenderContext()).draw(this, x, y);
2946             return;
2947         }
2948 
2949         try {
2950             textpipe.drawChars(this, chData, 0, length, x, y);
2951         } catch (InvalidPipeException e) {
2952             revalidateAll();
2953             try {
2954                 textpipe.drawChars(this, chData, 0, length, x, y);
2955             } catch (InvalidPipeException e2) {
2956                 // Still catching the exception; we are not yet ready to
2957                 // validate the surfaceData correctly.  Fail for now and
2958                 // try again next time around.
2959             }
2960         } finally {
2961             surfaceData.markDirty();
2962         }
2963     }
2964 // end of text rendering methods
2965 
2966     /**
2967      * Draws an image scaled to x,y,w,h in nonblocking mode with a
2968      * callback object.
2969      */
2970     public boolean drawImage(Image img, int x, int y, int width, int height,
2971                              ImageObserver observer) {
2972         return drawImage(img, x, y, width, height, null, observer);
2973     }
2974 
2975     /**
2976      * Not part of the advertised API but a useful utility method
2977      * to call internally.  This is for the case where we are
2978      * drawing to/from given coordinates using a given width/height,
2979      * but we guarantee that the weidth/height of the src and dest
2980      * areas are equal (no scale needed).
2981      */
2982     public boolean copyImage(Image img, int dx, int dy, int sx, int sy,
2983                              int width, int height, Color bgcolor,
2984                              ImageObserver observer) {
2985         try {
2986             return imagepipe.copyImage(this, img, dx, dy, sx, sy,
2987                                        width, height, bgcolor, observer);
2988         } catch (InvalidPipeException e) {
2989             revalidateAll();
2990             try {
2991                 return imagepipe.copyImage(this, img, dx, dy, sx, sy,
2992                                            width, height, bgcolor, observer);
2993             } catch (InvalidPipeException e2) {
2994                 // Still catching the exception; we are not yet ready to
2995                 // validate the surfaceData correctly.  Fail for now and
2996                 // try again next time around.
2997                 return false;
2998             }
2999         } finally {
3000             surfaceData.markDirty();
3001         }
3002     }
3003 
3004     /**
3005      * Draws an image scaled to x,y,w,h in nonblocking mode with a
3006      * solid background color and a callback object.
3007      */
3008     public boolean drawImage(Image img, int x, int y, int width, int height,
3009                              Color bg, ImageObserver observer) {
3010 
3011         if (img == null) {
3012             return true;
3013         }
3014 
3015         if ((width == 0) || (height == 0)) {
3016             return true;
3017         }
3018         if (width == img.getWidth(null) && height == img.getHeight(null)) {
3019             return copyImage(img, x, y, 0, 0, width, height, bg, observer);
3020         }
3021 
3022         try {
3023             return imagepipe.scaleImage(this, img, x, y, width, height,
3024                                         bg, observer);
3025         } catch (InvalidPipeException e) {
3026             revalidateAll();
3027             try {
3028                 return imagepipe.scaleImage(this, img, x, y, width, height,
3029                                             bg, observer);
3030             } catch (InvalidPipeException e2) {
3031                 // Still catching the exception; we are not yet ready to
3032                 // validate the surfaceData correctly.  Fail for now and
3033                 // try again next time around.
3034                 return false;
3035             }
3036         } finally {
3037             surfaceData.markDirty();
3038         }
3039     }
3040 
3041     /**
3042      * Draws an image at x,y in nonblocking mode.
3043      */
3044     public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
3045         return drawImage(img, x, y, null, observer);
3046     }
3047 
3048     /**
3049      * Draws an image at x,y in nonblocking mode with a solid background
3050      * color and a callback object.
3051      */
3052     public boolean drawImage(Image img, int x, int y, Color bg,
3053                              ImageObserver observer) {
3054 
3055         if (img == null) {
3056             return true;
3057         }
3058 
3059         try {
3060             return imagepipe.copyImage(this, img, x, y, bg, observer);
3061         } catch (InvalidPipeException e) {
3062             revalidateAll();
3063             try {
3064                 return imagepipe.copyImage(this, img, x, y, bg, observer);
3065             } catch (InvalidPipeException e2) {
3066                 // Still catching the exception; we are not yet ready to
3067                 // validate the surfaceData correctly.  Fail for now and
3068                 // try again next time around.
3069                 return false;
3070             }
3071         } finally {
3072             surfaceData.markDirty();
3073         }
3074     }
3075 
3076     /**
3077      * Draws a subrectangle of an image scaled to a destination rectangle
3078      * in nonblocking mode with a callback object.
3079      */
3080     public boolean drawImage(Image img,
3081                              int dx1, int dy1, int dx2, int dy2,
3082                              int sx1, int sy1, int sx2, int sy2,
3083                              ImageObserver observer) {
3084         return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null,
3085                          observer);
3086     }
3087 
3088     /**
3089      * Draws a subrectangle of an image scaled to a destination rectangle in
3090      * nonblocking mode with a solid background color and a callback object.
3091      */
3092     public boolean drawImage(Image img,
3093                              int dx1, int dy1, int dx2, int dy2,
3094                              int sx1, int sy1, int sx2, int sy2,
3095                              Color bgcolor, ImageObserver observer) {
3096 
3097         if (img == null) {
3098             return true;
3099         }
3100 
3101         if (dx1 == dx2 || dy1 == dy2 ||
3102             sx1 == sx2 || sy1 == sy2)
3103         {
3104             return true;
3105         }
3106 
3107         if (((sx2 - sx1) == (dx2 - dx1)) &&
3108             ((sy2 - sy1) == (dy2 - dy1)))
3109         {
3110             // Not a scale - forward it to a copy routine
3111             int srcX, srcY, dstX, dstY, width, height;
3112             if (sx2 > sx1) {
3113                 width = sx2 - sx1;
3114                 srcX = sx1;
3115                 dstX = dx1;
3116             } else {
3117                 width = sx1 - sx2;
3118                 srcX = sx2;
3119                 dstX = dx2;
3120             }
3121             if (sy2 > sy1) {
3122                 height = sy2-sy1;
3123                 srcY = sy1;
3124                 dstY = dy1;
3125             } else {
3126                 height = sy1-sy2;
3127                 srcY = sy2;
3128                 dstY = dy2;
3129             }
3130             return copyImage(img, dstX, dstY, srcX, srcY,
3131                              width, height, bgcolor, observer);
3132         }
3133 
3134         try {
3135             return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3136                                           sx1, sy1, sx2, sy2, bgcolor,
3137                                           observer);
3138         } catch (InvalidPipeException e) {
3139             revalidateAll();
3140             try {
3141                 return imagepipe.scaleImage(this, img, dx1, dy1, dx2, dy2,
3142                                               sx1, sy1, sx2, sy2, bgcolor,
3143                                               observer);
3144             } catch (InvalidPipeException e2) {
3145                 // Still catching the exception; we are not yet ready to
3146                 // validate the surfaceData correctly.  Fail for now and
3147                 // try again next time around.
3148                 return false;
3149             }
3150         } finally {
3151             surfaceData.markDirty();
3152         }
3153     }
3154 
3155     /**
3156      * Draw an image, applying a transform from image space into user space
3157      * before drawing.
3158      * The transformation from user space into device space is done with
3159      * the current transform in the Graphics2D.
3160      * The given transformation is applied to the image before the
3161      * transform attribute in the Graphics2D state is applied.
3162      * The rendering attributes applied include the clip, transform,
3163      * paint or color and composite attributes. Note that the result is
3164      * undefined, if the given transform is non-invertible.
3165      * @param img The image to be drawn.
3166      * @param xform The transformation from image space into user space.
3167      * @param observer The image observer to be notified on the image producing
3168      * progress.
3169      * @see #transform
3170      * @see #setComposite
3171      * @see #setClip
3172      */
3173     public boolean drawImage(Image img,
3174                              AffineTransform xform,
3175                              ImageObserver observer) {
3176 
3177         if (img == null) {
3178             return true;
3179         }
3180 
3181         if (xform == null || xform.isIdentity()) {
3182             return drawImage(img, 0, 0, null, observer);
3183         }
3184 
3185         try {
3186             return imagepipe.transformImage(this, img, xform, observer);
3187         } catch (InvalidPipeException e) {
3188             revalidateAll();
3189             try {
3190                 return imagepipe.transformImage(this, img, xform, observer);
3191             } catch (InvalidPipeException e2) {
3192                 // Still catching the exception; we are not yet ready to
3193                 // validate the surfaceData correctly.  Fail for now and
3194                 // try again next time around.
3195                 return false;
3196             }
3197         } finally {
3198             surfaceData.markDirty();
3199         }
3200     }
3201 
3202     public void drawImage(BufferedImage bImg,
3203                           BufferedImageOp op,
3204                           int x,
3205                           int y)  {
3206 
3207         if (bImg == null) {
3208             return;
3209         }
3210 
3211         try {
3212             imagepipe.transformImage(this, bImg, op, x, y);
3213         } catch (InvalidPipeException e) {
3214             revalidateAll();
3215             try {
3216                 imagepipe.transformImage(this, bImg, op, x, y);
3217             } catch (InvalidPipeException e2) {
3218                 // Still catching the exception; we are not yet ready to
3219                 // validate the surfaceData correctly.  Fail for now and
3220                 // try again next time around.
3221             }
3222         } finally {
3223             surfaceData.markDirty();
3224         }
3225     }
3226 
3227     /**
3228     * Get the rendering context of the font
3229     * within this Graphics2D context.
3230     */
3231     public FontRenderContext getFontRenderContext() {
3232         if (cachedFRC == null) {
3233             int aahint = textAntialiasHint;
3234             if (aahint == SunHints.INTVAL_TEXT_ANTIALIAS_DEFAULT &&
3235                 antialiasHint == SunHints.INTVAL_ANTIALIAS_ON) {
3236                 aahint = SunHints.INTVAL_TEXT_ANTIALIAS_ON;
3237             }
3238             // Translation components should be excluded from the FRC transform
3239             AffineTransform tx = null;
3240             if (transformState >= TRANSFORM_TRANSLATESCALE) {
3241                 if (transform.getTranslateX() == 0 &&
3242                     transform.getTranslateY() == 0) {
3243                     tx = transform;
3244                 } else {
3245                     tx = new AffineTransform(transform.getScaleX(),
3246                                              transform.getShearY(),
3247                                              transform.getShearX(),
3248                                              transform.getScaleY(),
3249                                              0, 0);
3250                 }
3251             }
3252             cachedFRC = new FontRenderContext(tx,
3253              SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING, aahint),
3254              SunHints.Value.get(SunHints.INTKEY_FRACTIONALMETRICS,
3255                                 fractionalMetricsHint));
3256         }
3257         return cachedFRC;
3258     }
3259     private FontRenderContext cachedFRC;
3260 
3261     /**
3262      * This object has no resources to dispose of per se, but the
3263      * doc comments for the base method in java.awt.Graphics imply
3264      * that this object will not be useable after it is disposed.
3265      * So, we sabotage the object to prevent further use to prevent
3266      * developers from relying on behavior that may not work on
3267      * other, less forgiving, VMs that really need to dispose of
3268      * resources.
3269      */
3270     public void dispose() {
3271         surfaceData = NullSurfaceData.theInstance;
3272         invalidatePipe();
3273     }
3274 
3275     /**
3276      * Graphics has a finalize method that automatically calls dispose()
3277      * for subclasses.  For SunGraphics2D we do not need to be finalized
3278      * so that method simply causes us to be enqueued on the Finalizer
3279      * queues for no good reason.  Unfortunately, that method and
3280      * implementation are now considered part of the public contract
3281      * of that base class so we can not remove or gut the method.
3282      * We override it here with an empty method and the VM is smart
3283      * enough to know that if our override is empty then it should not
3284      * mark us as finalizeable.
3285      */
3286     public void finalize() {
3287         // DO NOT REMOVE THIS METHOD
3288     }
3289 
3290     /**
3291      * Returns destination that this Graphics renders to.  This could be
3292      * either an Image or a Component; subclasses of SurfaceData are
3293      * responsible for returning the appropriate object.
3294      */
3295     public Object getDestination() {
3296         return surfaceData.getDestination();
3297     }
3298 
3299     /**
3300      * {@inheritDoc}
3301      *
3302      * @see sun.java2d.DestSurfaceProvider#getDestSurface
3303      */
3304     @Override
3305     public Surface getDestSurface() {
3306         return surfaceData;
3307     }
3308 }