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