1 /*
   2  * Copyright (c) 2010, 2015, 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 com.sun.prism.j2d;
  27 
  28 import java.awt.LinearGradientPaint;
  29 import java.awt.font.GlyphVector;
  30 import java.awt.geom.NoninvertibleTransformException;
  31 import java.awt.geom.Rectangle2D;
  32 import java.lang.ref.WeakReference;
  33 import java.util.List;
  34 import java.util.concurrent.ConcurrentHashMap;
  35 import com.sun.glass.ui.Screen;
  36 import com.sun.javafx.PlatformUtil;
  37 import com.sun.javafx.font.CompositeGlyphMapper;
  38 import com.sun.javafx.font.CompositeStrike;
  39 import com.sun.javafx.font.FontResource;
  40 import com.sun.javafx.font.FontStrike;
  41 import com.sun.javafx.font.Metrics;
  42 import com.sun.javafx.geom.PathIterator;
  43 import com.sun.javafx.geom.RectBounds;
  44 import com.sun.javafx.geom.Rectangle;
  45 import com.sun.javafx.geom.Shape;
  46 import com.sun.javafx.geom.transform.Affine2D;
  47 import com.sun.javafx.geom.transform.BaseTransform;
  48 import com.sun.javafx.scene.text.GlyphList;
  49 import com.sun.javafx.sg.prism.NGCamera;
  50 import com.sun.javafx.sg.prism.NGLightBase;
  51 import com.sun.javafx.sg.prism.NodePath;
  52 import com.sun.prism.BasicStroke;
  53 import com.sun.prism.CompositeMode;
  54 import com.sun.prism.MaskTextureGraphics;
  55 import com.sun.prism.RTTexture;
  56 import com.sun.prism.ReadbackGraphics;
  57 import com.sun.prism.RenderTarget;
  58 import com.sun.prism.ResourceFactory;
  59 import com.sun.prism.Texture;
  60 import com.sun.prism.Texture.WrapMode;
  61 import com.sun.prism.impl.PrismSettings;
  62 import com.sun.prism.j2d.paint.MultipleGradientPaint.ColorSpaceType;
  63 import com.sun.prism.j2d.paint.RadialGradientPaint;
  64 import com.sun.prism.paint.Color;
  65 import com.sun.prism.paint.Gradient;
  66 import com.sun.prism.paint.ImagePattern;
  67 import com.sun.prism.paint.LinearGradient;
  68 import com.sun.prism.paint.Paint;
  69 import com.sun.prism.paint.RadialGradient;
  70 import com.sun.prism.paint.Stop;
  71 import static java.awt.RenderingHints.KEY_ANTIALIASING;
  72 import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
  73 import static java.awt.RenderingHints.VALUE_ANTIALIAS_OFF;
  74 import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
  75 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB;
  76 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
  77 
  78 public class J2DPrismGraphics
  79     // Do not subclass BaseGraphics without fixing drawTextureVO below...
  80     implements ReadbackGraphics, MaskTextureGraphics
  81     // Do not implement RectShadowGraphics without fixing RT-15016 (note that
  82     // BaseGraphics implements RectShadowGraphics).
  83 {
  84     static {
  85         // Assuming direct translation of BasicStroke enums:
  86         assert(com.sun.prism.BasicStroke.CAP_BUTT == java.awt.BasicStroke.CAP_BUTT);
  87         assert(com.sun.prism.BasicStroke.CAP_ROUND == java.awt.BasicStroke.CAP_ROUND);
  88         assert(com.sun.prism.BasicStroke.CAP_SQUARE == java.awt.BasicStroke.CAP_SQUARE);
  89         assert(com.sun.prism.BasicStroke.JOIN_BEVEL == java.awt.BasicStroke.JOIN_BEVEL);
  90         assert(com.sun.prism.BasicStroke.JOIN_MITER == java.awt.BasicStroke.JOIN_MITER);
  91         assert(com.sun.prism.BasicStroke.JOIN_ROUND == java.awt.BasicStroke.JOIN_ROUND);
  92         // Assuming direct translation of PathIterator enums:
  93         assert(com.sun.javafx.geom.PathIterator.WIND_EVEN_ODD == java.awt.geom.PathIterator.WIND_EVEN_ODD);
  94         assert(com.sun.javafx.geom.PathIterator.WIND_NON_ZERO == java.awt.geom.PathIterator.WIND_NON_ZERO);
  95         assert(com.sun.javafx.geom.PathIterator.SEG_MOVETO == java.awt.geom.PathIterator.SEG_MOVETO);
  96         assert(com.sun.javafx.geom.PathIterator.SEG_LINETO == java.awt.geom.PathIterator.SEG_LINETO);
  97         assert(com.sun.javafx.geom.PathIterator.SEG_QUADTO == java.awt.geom.PathIterator.SEG_QUADTO);
  98         assert(com.sun.javafx.geom.PathIterator.SEG_CUBICTO == java.awt.geom.PathIterator.SEG_CUBICTO);
  99         assert(com.sun.javafx.geom.PathIterator.SEG_CLOSE == java.awt.geom.PathIterator.SEG_CLOSE);
 100     }
 101     static final LinearGradientPaint.CycleMethod LGP_CYCLE_METHODS[] = {
 102         LinearGradientPaint.CycleMethod.NO_CYCLE,
 103         LinearGradientPaint.CycleMethod.REFLECT,
 104         LinearGradientPaint.CycleMethod.REPEAT,
 105     };
 106     static final RadialGradientPaint.CycleMethod RGP_CYCLE_METHODS[] = {
 107         RadialGradientPaint.CycleMethod.NO_CYCLE,
 108         RadialGradientPaint.CycleMethod.REFLECT,
 109         RadialGradientPaint.CycleMethod.REPEAT,
 110     };
 111 
 112     private static final BasicStroke DEFAULT_STROKE =
 113         new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f);
 114     private static final Paint DEFAULT_PAINT = Color.WHITE;
 115     static java.awt.geom.AffineTransform J2D_IDENTITY =
 116         new java.awt.geom.AffineTransform();
 117     private int clipRectIndex;
 118     private boolean hasPreCullingBits = false;
 119     private float pixelScale = 1.0f;
 120 
 121     static java.awt.Color toJ2DColor(Color c) {
 122         return new java.awt.Color(c.getRed(),
 123                                   c.getGreen(),
 124                                   c.getBlue(),
 125                                   c.getAlpha());
 126     }
 127 
 128     /*
 129      * Ensure that no fractions are equal
 130      *
 131      * Note that the J2D objects reject equal fractions, but the FX versions
 132      * allow them.
 133      *
 134      * The FX version treats values with equal fractions such that as you
 135      * approach the fractional value from below it interpolates to the
 136      * first color associated with that fraction and as you interpolate
 137      * away from it from above it interpolates the last such color.
 138      *
 139      * To get the J2D version to exhibit the FX behavior we collapse all
 140      * adjacent fractional values into a pair of values that are stored
 141      * with a pair of immediately adjacent floating point values.  This way
 142      * they have unique fractions, but no fractional value can be generated
 143      * which fits between them.  Yet, as you approach from below it will
 144      * interpolate to the first of the pair of colors and as you move away
 145      * above it, the second value will take precedence for interpolation.
 146      *
 147      * Math.ulp() is used to generate an "immediately adjacent fp value".
 148      */
 149     static int fixFractions(float fractions[], java.awt.Color colors[]) {
 150         float fprev = fractions[0];
 151         int i = 1;  // index of next incoming color/fractions we will examine
 152         int n = 1;  // index of next outgoing color/fraction we will store
 153         while (i < fractions.length) {
 154             float f = fractions[i];
 155             java.awt.Color c = colors[i++];
 156             if (f <= fprev) {
 157                 // If we find any duplicates after we reach 1.0 we can
 158                 // just ignore the rest of the array.  Not only is there
 159                 // no more "fraction room" to assign them to, but we will
 160                 // never generate a fraction >1.0 to access them anyway
 161                 if (f >= 1.0f) break;
 162                 // Find all fractions that are either fprev or fprev+ulp
 163                 // and collapse them into two entries, the first at fprev
 164                 // which is already stored, and the last matching entry
 165                 // will be stored with fraction fprev+ulp
 166                 f = fprev + Math.ulp(fprev);
 167                 while (i < fractions.length) {
 168                     if (fractions[i] > f) break;
 169                     // We continue to remember the color of the last
 170                     // "matching" entry so it can be stored below
 171                     c = colors[i++];
 172                 }
 173             }
 174             fractions[n] = fprev = f;
 175             colors[n++] = c;
 176         }
 177         return n;
 178     }
 179 
 180     java.awt.Paint toJ2DPaint(Paint p, java.awt.geom.Rectangle2D b) {
 181         if (p instanceof Color) {
 182             return toJ2DColor((Color) p);
 183         } else if (p instanceof Gradient) {
 184             Gradient g = (Gradient) p;
 185             if (g.isProportional()) {
 186                 if (b == null) {
 187                     return null;
 188                 }
 189             }
 190             List<Stop> stops = g.getStops();
 191             int n = stops.size();
 192             float fractions[] = new float[n];
 193             java.awt.Color colors[] = new java.awt.Color[n];
 194             float prevf = -1f;
 195             boolean needsFix = false;
 196             for (int i = 0; i < n; i++) {
 197                 Stop stop = stops.get(i);
 198                 float f = stop.getOffset();
 199                 needsFix = (needsFix || f <= prevf);
 200                 fractions[i] = prevf = f;
 201                 colors[i] = toJ2DColor(stop.getColor());
 202             }
 203             if (needsFix) {
 204                 n = fixFractions(fractions, colors);
 205                 if (n < fractions.length) {
 206                     float newf[] = new float[n];
 207                     System.arraycopy(fractions, 0, newf, 0, n);
 208                     fractions = newf;
 209                     java.awt.Color newc[] = new java.awt.Color[n];
 210                     System.arraycopy(colors, 0, newc, 0, n);
 211                     colors = newc;
 212                 }
 213             }
 214             if (g instanceof LinearGradient) {
 215                 LinearGradient lg = (LinearGradient) p;
 216                 float x1 = lg.getX1();
 217                 float y1 = lg.getY1();
 218                 float x2 = lg.getX2();
 219                 float y2 = lg.getY2();
 220                 if (g.isProportional()) {
 221                     float x = (float) b.getX();
 222                     float y = (float) b.getY();
 223                     float w = (float) b.getWidth();
 224                     float h = (float) b.getHeight();
 225                     x1 = x + w * x1;
 226                     y1 = y + h * y1;
 227                     x2 = x + w * x2;
 228                     y2 = y + h * y2;
 229                 }
 230                 if (x1 == x2 && y1 == y1) {
 231                     // Hardware pipelines use an inverse transform of
 232                     // all zeros to choose colors when the start and end
 233                     // point are the same so that the first color is
 234                     // always chosen...
 235                     return colors[0];
 236                 }
 237                 java.awt.geom.Point2D p1 =
 238                     new java.awt.geom.Point2D.Float(x1, y1);
 239                 java.awt.geom.Point2D p2 =
 240                     new java.awt.geom.Point2D.Float(x2, y2);
 241                 LinearGradientPaint.CycleMethod method =
 242                     LGP_CYCLE_METHODS[g.getSpreadMethod()];
 243                 return new LinearGradientPaint(p1, p2, fractions, colors, method);
 244             } else if (g instanceof RadialGradient) {
 245                 RadialGradient rg = (RadialGradient) g;
 246                 float cx = rg.getCenterX();
 247                 float cy = rg.getCenterY();
 248                 float r = rg.getRadius();
 249                 double fa = Math.toRadians(rg.getFocusAngle());
 250                 float fd = rg.getFocusDistance();
 251                 java.awt.geom.AffineTransform at = J2D_IDENTITY;
 252                 if (g.isProportional()) {
 253                     float x = (float) b.getX();
 254                     float y = (float) b.getY();
 255                     float w = (float) b.getWidth();
 256                     float h = (float) b.getHeight();
 257                     float dim = Math.min(w, h);
 258                     float bcx = x + w * 0.5f;
 259                     float bcy = y + h * 0.5f;
 260                     cx = bcx + (cx - 0.5f) * dim;
 261                     cy = bcy + (cy - 0.5f) * dim;
 262                     r *= dim;
 263                     if (w != h && w != 0.0 && h != 0.0) {
 264                         at = java.awt.geom.AffineTransform.getTranslateInstance(bcx, bcy);
 265                         at.scale(w / dim, h / dim);
 266                         at.translate(-bcx, -bcy);
 267                     }
 268                 }
 269                 java.awt.geom.Point2D center =
 270                     new java.awt.geom.Point2D.Float(cx, cy);
 271                 float fx = (float) (cx + fd * r * Math.cos(fa));
 272                 float fy = (float) (cy + fd * r * Math.sin(fa));
 273                 java.awt.geom.Point2D focus =
 274                     new java.awt.geom.Point2D.Float(fx, fy);
 275                 RadialGradientPaint.CycleMethod method =
 276                     RGP_CYCLE_METHODS[g.getSpreadMethod()];
 277                 return new RadialGradientPaint(center, r, focus, fractions, colors,
 278                                                method, ColorSpaceType.SRGB, at);
 279             }
 280         } else if (p instanceof ImagePattern) {
 281             ImagePattern imgpat = (ImagePattern) p;
 282             float x = imgpat.getX();
 283             float y = imgpat.getY();
 284             float w = imgpat.getWidth();
 285             float h = imgpat.getHeight();
 286             if (p.isProportional()) {
 287                 if (b == null) {
 288                     return null;
 289                 }
 290                 float bx = (float) b.getX();
 291                 float by = (float) b.getY();
 292                 float bw = (float) b.getWidth();
 293                 float bh = (float) b.getHeight();
 294                 w += x;
 295                 h += y;
 296                 x = bx + x * bw;
 297                 y = by + y * bh;
 298                 w = bx + w * bw;
 299                 h = by + h * bh;
 300                 w -= x;
 301                 h -= y;
 302             }
 303             Texture tex =
 304                 getResourceFactory().getCachedTexture(imgpat.getImage(), WrapMode.REPEAT);
 305             java.awt.image.BufferedImage bimg = ((J2DTexture) tex).getBufferedImage();
 306             tex.unlock();
 307             return new java.awt.TexturePaint(bimg, tmpRect(x, y, w, h));
 308         }
 309         throw new UnsupportedOperationException("Paint "+p+" not supported yet.");
 310     }
 311 
 312     static java.awt.Stroke toJ2DStroke(BasicStroke stroke) {
 313         float lineWidth = stroke.getLineWidth();
 314         int type = stroke.getType();
 315         if (type != BasicStroke.TYPE_CENTERED) {
 316             lineWidth *= 2;
 317         }
 318         java.awt.BasicStroke bs =
 319                 new java.awt.BasicStroke(lineWidth,
 320                                          stroke.getEndCap(),
 321                                          stroke.getLineJoin(),
 322                                          stroke.getMiterLimit(),
 323                                          stroke.getDashArray(),
 324                                          stroke.getDashPhase());
 325         if (type == BasicStroke.TYPE_INNER) {
 326             return new InnerStroke(bs);
 327         } else if (type == BasicStroke.TYPE_OUTER) {
 328             return new OuterStroke(bs);
 329         } else {
 330             return bs;
 331         }
 332     }
 333 
 334     private static ConcurrentHashMap<java.awt.Font,
 335                                      WeakReference<java.awt.Font>>
 336         fontMap = new ConcurrentHashMap<java.awt.Font,
 337                                         WeakReference<java.awt.Font>>();
 338     private static volatile int cleared = 0;
 339 
 340     private static java.awt.Font toJ2DFont(FontStrike strike) {
 341         FontResource fr = strike.getFontResource();
 342         java.awt.Font j2dfont;
 343         Object peer = fr.getPeer();
 344         if (peer == null && fr.isEmbeddedFont()) {
 345             J2DFontFactory.registerFont(fr);
 346             peer = fr.getPeer();
 347         }
 348         if (peer != null && peer instanceof java.awt.Font) {
 349             j2dfont = (java.awt.Font)peer;
 350         } else {
 351             if (PlatformUtil.isMac()) {
 352                 // Looking up J2D fonts via full name is not reliable on the
 353                 // Mac, however using the PostScript font name is. The likely
 354                 // cause is Mac platform internals heavy reliance on PostScript
 355                 // names for font identification.
 356                 String psName = fr.getPSName();
 357                 // dummy size
 358                 j2dfont = new java.awt.Font(psName, java.awt.Font.PLAIN, 12);
 359 
 360                 // REMIND: Due to bugs in j2d font lookup, these two workarounds
 361                 // are required to ensure the correct font is used. Once fixed
 362                 // in the jdk these workarounds should be removed.
 363                 if (!j2dfont.getPSName().equals(psName)) {
 364                     // 1. Lookup font via family and style. This covers the
 365                     // case when the J2D PostScript name does not match psName
 366                     // in font file. For example "HelveticaCYBold" has the
 367                     // psName "HelveticaCY-Bold" in j2d.
 368                     int style = fr.isBold() ? java.awt.Font.BOLD : 0;
 369                     style = style | (fr.isItalic() ? java.awt.Font.ITALIC : 0);
 370                     j2dfont = new java.awt.Font(fr.getFamilyName(), style, 12);
 371 
 372                     if(!j2dfont.getPSName().equals(psName)) {
 373                         // 2. J2D seems to be unable to find a few fonts where
 374                         // psName == familyName.  Workaround is an exhaustive
 375                         // search of all fonts.
 376                         java.awt.Font[] allj2dFonts =
 377                                 java.awt.GraphicsEnvironment.
 378                                 getLocalGraphicsEnvironment().getAllFonts();
 379                         for (java.awt.Font f : allj2dFonts) {
 380                             if (f.getPSName().equals(psName)) {
 381                                 j2dfont = f;
 382                                 break;
 383                             }
 384                         }
 385                     }
 386                 }
 387             } else {
 388                 // dummy size
 389                 j2dfont = new java.awt.Font(fr.getFullName(),
 390                                             java.awt.Font.PLAIN, 12);
 391             }
 392 
 393             // Adding j2dfont as peer is OK since fr is a decomposed
 394             // FontResource. Thus preventing font lookup next time we render.
 395             fr.setPeer(j2dfont);
 396         }
 397         // deriveFont(...) still has a bug and will cause #2 problem to occur
 398         j2dfont = j2dfont.deriveFont(strike.getSize()); // exact float font size
 399         java.awt.Font compFont = null;
 400         WeakReference<java.awt.Font> ref = fontMap.get(j2dfont);
 401         if (ref != null) {
 402             compFont = ref.get();
 403             if (compFont == null) {
 404                 cleared++;
 405             }
 406         }
 407         if (compFont == null) {
 408             if (fontMap.size() > 100 && cleared > 10) { // purge the map.
 409                 for (java.awt.Font key : fontMap.keySet()) {
 410                     ref = fontMap.get(key);
 411                     if (ref == null || ref.get() == null) {
 412                         fontMap.remove(key);
 413                     }
 414                 }
 415                 cleared = 0;
 416             }
 417             compFont = J2DFontFactory.getCompositeFont(j2dfont);
 418             ref = new WeakReference(compFont);
 419             fontMap.put(j2dfont, ref);
 420         }
 421         return compFont;
 422     }
 423 
 424     public static java.awt.geom.AffineTransform
 425         toJ2DTransform(BaseTransform t)
 426     {
 427         return new java.awt.geom.AffineTransform(t.getMxx(), t.getMyx(),
 428                                                  t.getMxy(), t.getMyy(),
 429                                                  t.getMxt(), t.getMyt());
 430     }
 431 
 432     private static java.awt.geom.AffineTransform tmpAT =
 433         new java.awt.geom.AffineTransform();
 434     static java.awt.geom.AffineTransform tmpJ2DTransform(BaseTransform t)
 435     {
 436         tmpAT.setTransform(t.getMxx(), t.getMyx(),
 437                            t.getMxy(), t.getMyy(),
 438                            t.getMxt(), t.getMyt());
 439         return tmpAT;
 440     }
 441 
 442     static BaseTransform toPrTransform(java.awt.geom.AffineTransform t)
 443     {
 444         return BaseTransform.getInstance(t.getScaleX(), t.getShearY(),
 445                                          t.getShearX(), t.getScaleY(),
 446                                          t.getTranslateX(), t.getTranslateY());
 447     }
 448 
 449     static Rectangle toPrRect(java.awt.Rectangle r)
 450     {
 451         return new Rectangle(r.x, r.y, r.width, r.height);
 452     }
 453 
 454     private static java.awt.geom.Path2D tmpQuadShape =
 455         new java.awt.geom.Path2D.Float();
 456     private static java.awt.Shape tmpQuad(float x1, float y1,
 457                                           float x2, float y2)
 458     {
 459         tmpQuadShape.reset();
 460         tmpQuadShape.moveTo(x1, y1);
 461         tmpQuadShape.lineTo(x2, y1);
 462         tmpQuadShape.lineTo(x2, y2);
 463         tmpQuadShape.lineTo(x1, y2);
 464         tmpQuadShape.closePath();
 465         return tmpQuadShape;
 466     }
 467 
 468     private static java.awt.geom.Rectangle2D.Float tmpRect =
 469         new java.awt.geom.Rectangle2D.Float();
 470     private static java.awt.geom.Rectangle2D tmpRect(float x, float y, float w, float h) {
 471         tmpRect.setRect(x, y, w, h);
 472         return tmpRect;
 473     }
 474 
 475     private static java.awt.geom.Ellipse2D tmpEllipse =
 476         new java.awt.geom.Ellipse2D.Float();
 477     private static java.awt.Shape tmpEllipse(float x, float y, float w, float h) {
 478         tmpEllipse.setFrame(x, y, w, h);
 479         return tmpEllipse;
 480     }
 481 
 482     private static java.awt.geom.RoundRectangle2D tmpRRect =
 483         new java.awt.geom.RoundRectangle2D.Float();
 484     private static java.awt.Shape tmpRRect(float x, float y, float w, float h,
 485                                            float aw, float ah)
 486     {
 487         tmpRRect.setRoundRect(x, y, w, h, aw, ah);
 488         return tmpRRect;
 489     }
 490 
 491     private static java.awt.geom.Line2D tmpLine =
 492         new java.awt.geom.Line2D.Float();
 493     private static java.awt.Shape tmpLine(float x1, float y1, float x2, float y2) {
 494         tmpLine.setLine(x1, y1, x2, y2);
 495         return tmpLine;
 496     }
 497 
 498     private static AdaptorShape tmpAdaptor = new AdaptorShape();
 499     private static java.awt.Shape tmpShape(Shape s) {
 500         tmpAdaptor.setShape(s);
 501         return tmpAdaptor;
 502     }
 503 
 504     private boolean antialiasedShape = true;
 505     J2DPresentable target;
 506     java.awt.Graphics2D g2d;
 507     Affine2D transform;
 508     Rectangle clipRect;
 509     RectBounds devClipRect;
 510     RectBounds finalClipRect;
 511     Paint paint;
 512     boolean paintWasProportional;
 513     BasicStroke stroke;
 514     boolean cull;
 515 
 516     J2DPrismGraphics(J2DPresentable target, java.awt.Graphics2D g2d) {
 517         this(g2d, target.getContentWidth(), target.getContentHeight());
 518         this.target = target;
 519     }
 520 
 521     J2DPrismGraphics(java.awt.Graphics2D g2d, int width, int height) {
 522         this.g2d = g2d;
 523         captureTransform(g2d);
 524         this.transform = new Affine2D();
 525         this.devClipRect = new RectBounds(0, 0, width, height);
 526         this.finalClipRect = new RectBounds(0, 0, width, height);
 527         this.cull = true;
 528 
 529         g2d.setRenderingHint(java.awt.RenderingHints.KEY_STROKE_CONTROL,
 530                              java.awt.RenderingHints.VALUE_STROKE_PURE);
 531         g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
 532                               java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
 533         g2d.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION,
 534                              java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR);
 535         /* Set the text hints to those most equivalent to FX rendering.
 536          * Will need to revisit this since its unlikely to be sufficient.
 537          */
 538         g2d.setRenderingHint(java.awt.RenderingHints.KEY_FRACTIONALMETRICS,
 539                            java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_ON);
 540         g2d.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
 541                              java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 542 
 543 
 544         setTransform(BaseTransform.IDENTITY_TRANSFORM);
 545         setPaint(DEFAULT_PAINT);
 546         setStroke(DEFAULT_STROKE);
 547     }
 548 
 549     public RenderTarget getRenderTarget() {
 550         return target;
 551     }
 552 
 553     public Screen getAssociatedScreen() {
 554         return target.getAssociatedScreen();
 555     }
 556 
 557     public ResourceFactory getResourceFactory() {
 558         return target.getResourceFactory();
 559     }
 560 
 561     public void reset() {
 562     }
 563 
 564     public Rectangle getClipRect() {
 565         return clipRect == null ? null : new Rectangle(clipRect);
 566     }
 567 
 568     public Rectangle getClipRectNoClone() {
 569         return clipRect;
 570     }
 571 
 572     public RectBounds getFinalClipNoClone() {
 573         return finalClipRect;
 574     }
 575 
 576     public void setClipRect(Rectangle clipRect) {
 577         this.finalClipRect.setBounds(devClipRect);
 578         if (clipRect == null) {
 579             this.clipRect = null;
 580             g2d.setClip(null);
 581         } else {
 582             this.clipRect = new Rectangle(clipRect);
 583             this.finalClipRect.intersectWith(clipRect);
 584             setTransformG2D(J2D_IDENTITY);
 585             g2d.setClip(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
 586             setTransformG2D(tmpJ2DTransform(transform));
 587         }
 588     }
 589 
 590     private java.awt.AlphaComposite getAWTComposite() {
 591         return (java.awt.AlphaComposite) g2d.getComposite();
 592     }
 593 
 594     public float getExtraAlpha() {
 595         return getAWTComposite().getAlpha();
 596     }
 597 
 598     public void setExtraAlpha(float extraAlpha) {
 599         g2d.setComposite(getAWTComposite().derive(extraAlpha));
 600     }
 601 
 602     public CompositeMode getCompositeMode() {
 603         int rule = getAWTComposite().getRule();
 604         switch (rule) {
 605             case java.awt.AlphaComposite.CLEAR:
 606                 return CompositeMode.CLEAR;
 607             case java.awt.AlphaComposite.SRC:
 608                 return CompositeMode.SRC;
 609             case java.awt.AlphaComposite.SRC_OVER:
 610                 return CompositeMode.SRC_OVER;
 611             default:
 612                 throw new InternalError("Unrecognized AlphaCompsite rule: "+rule);
 613         }
 614     }
 615 
 616     public void setCompositeMode(CompositeMode mode) {
 617         java.awt.AlphaComposite awtComp = getAWTComposite();
 618         switch (mode) {
 619             case CLEAR:
 620                 awtComp = awtComp.derive(java.awt.AlphaComposite.CLEAR);
 621                 break;
 622             case SRC:
 623                 awtComp = awtComp.derive(java.awt.AlphaComposite.SRC);
 624                 break;
 625             case SRC_OVER:
 626                 awtComp = awtComp.derive(java.awt.AlphaComposite.SRC_OVER);
 627                 break;
 628             default:
 629                 throw new InternalError("Unrecognized composite mode: "+mode);
 630         }
 631         g2d.setComposite(awtComp);
 632     }
 633 
 634     public Paint getPaint() {
 635         return paint;
 636     }
 637 
 638     public void setPaint(Paint paint) {
 639         this.paint = paint;
 640         java.awt.Paint j2dpaint = toJ2DPaint(paint, null);
 641         if (j2dpaint == null) {
 642             paintWasProportional = true;
 643         } else {
 644             paintWasProportional = false;
 645             g2d.setPaint(j2dpaint);
 646         }
 647     }
 648 
 649     public BasicStroke getStroke() {
 650         return stroke;
 651     }
 652 
 653     public void setStroke(BasicStroke stroke) {
 654         this.stroke = stroke;
 655         g2d.setStroke(toJ2DStroke(stroke));
 656     }
 657 
 658     public BaseTransform getTransformNoClone() {
 659         return transform;
 660     }
 661 
 662     public void translate(float tx, float ty) {
 663         transform.translate(tx, ty);
 664         g2d.translate(tx, ty);
 665     }
 666 
 667     public void scale(float sx, float sy) {
 668         transform.scale(sx, sy);
 669         g2d.scale(sx, sy);
 670     }
 671 
 672     public void transform(BaseTransform xform) {
 673         if (!xform.is2D()) {
 674             // No-op until we support 3D
 675             return;
 676         }
 677         transform.concatenate(xform);
 678         setTransformG2D(tmpJ2DTransform(transform));
 679     }
 680 
 681     public void setTransform(BaseTransform xform) {
 682         // TODO: Modify PrEffectHelper to not pass a null... (RT-27384)
 683         if (xform == null) xform = BaseTransform.IDENTITY_TRANSFORM;
 684         transform.setTransform(xform);
 685         setTransformG2D(tmpJ2DTransform(transform));
 686     }
 687 
 688     public void setTransform(double m00, double m10,
 689                              double m01, double m11,
 690                              double m02, double m12)
 691     {
 692         transform.setTransform(m00, m10, m01, m11, m02, m12);
 693         setTransformG2D(tmpJ2DTransform(transform));
 694     }
 695 
 696     public void clear() {
 697         clear(Color.TRANSPARENT);
 698     }
 699 
 700     public void clear(Color color) {
 701         this.getRenderTarget().setOpaque(color.isOpaque());
 702         clear(toJ2DColor(color));
 703     }
 704 
 705     void clear(java.awt.Color c) {
 706         java.awt.Graphics2D gtmp = (java.awt.Graphics2D) g2d.create();
 707         gtmp.setTransform(J2D_IDENTITY);
 708         gtmp.setComposite(java.awt.AlphaComposite.Src);
 709         gtmp.setColor(c);
 710         gtmp.fillRect(0, 0, target.getContentWidth(), target.getContentHeight());
 711         gtmp.dispose();
 712     }
 713 
 714     public void clearQuad(float x1, float y1, float x2, float y2) {
 715         g2d.setComposite(java.awt.AlphaComposite.Clear);
 716         g2d.fill(tmpQuad(x1, y1, x2, y2));
 717     }
 718 
 719     void fill(java.awt.Shape shape) {
 720         if (paintWasProportional) {
 721             if (nodeBounds != null) {
 722                 g2d.setPaint(toJ2DPaint(paint, nodeBounds));
 723             } else {
 724                 g2d.setPaint(toJ2DPaint(paint, shape.getBounds2D()));
 725             }
 726         }
 727         g2d.fill(shape);
 728     }
 729 
 730     public void fill(Shape shape) {
 731         fill(tmpShape(shape));
 732     }
 733 
 734     public void fillRect(float x, float y, float width, float height) {
 735         fill(tmpRect(x, y, width, height));
 736     }
 737 
 738     public void fillRoundRect(float x, float y, float width, float height,
 739                               float arcw, float arch)
 740     {
 741         fill(tmpRRect(x, y, width, height, arcw, arch));
 742     }
 743 
 744     public void fillEllipse(float x, float y, float width, float height) {
 745         fill(tmpEllipse(x, y, width, height));
 746     }
 747 
 748     public void fillQuad(float x1, float y1, float x2, float y2) {
 749         fill(tmpQuad(x1, y1, x2, y2));
 750     }
 751 
 752     void draw(java.awt.Shape shape) {
 753         if (paintWasProportional) {
 754             if (nodeBounds != null) {
 755                 g2d.setPaint(toJ2DPaint(paint, nodeBounds));
 756             } else {
 757                 g2d.setPaint(toJ2DPaint(paint, shape.getBounds2D()));
 758             }
 759         }
 760         try {
 761             g2d.draw(shape);
 762         } catch (Throwable t) {
 763             // Workaround for JDK bug 6670624
 764             // We may get a Ductus PRError (extends RuntimeException)
 765             // or we may get an InternalError (extends Error)
 766             // The only common superclass of the two is Throwable...
 767         }
 768     }
 769 
 770     public void draw(Shape shape) {
 771         draw(tmpShape(shape));
 772     }
 773 
 774     public void drawLine(float x1, float y1, float x2, float y2) {
 775         draw(tmpLine(x1, y1, x2, y2));
 776     }
 777 
 778     public void drawRect(float x, float y, float width, float height) {
 779         draw(tmpRect(x, y, width, height));
 780     }
 781 
 782     public void drawRoundRect(float x, float y, float width, float height, float arcw, float arch) {
 783         draw(tmpRRect(x, y, width, height, arcw, arch));
 784     }
 785 
 786     public void drawEllipse(float x, float y, float width, float height) {
 787         draw(tmpEllipse(x, y, width, height));
 788     }
 789 
 790     Rectangle2D nodeBounds = null;
 791 
 792     public void setNodeBounds(RectBounds bounds) {
 793         nodeBounds = bounds != null ?
 794                 new Rectangle2D.Float(bounds.getMinX(), bounds.getMinY(),
 795                                       bounds.getWidth(),bounds.getHeight()) :
 796                 null;
 797     }
 798 
 799     private void drawString(GlyphList gl, int start, int end,
 800                             FontStrike strike, float x, float y) {
 801         if (start == end) return;
 802         int count = end - start;
 803         int[] glyphs = new int[count];
 804         for (int i = 0; i < count; i++) {
 805             glyphs[i] = gl.getGlyphCode(start + i) & CompositeGlyphMapper.GLYPHMASK;
 806         }
 807         java.awt.Font j2dfont = toJ2DFont(strike);
 808         GlyphVector gv = j2dfont.createGlyphVector(g2d.getFontRenderContext(), glyphs);
 809         java.awt.geom.Point2D pt = new java.awt.geom.Point2D.Float();
 810         for (int i = 0; i < count; i++) {
 811             pt.setLocation(gl.getPosX(start + i), gl.getPosY(start + i));
 812             gv.setGlyphPosition(i, pt);
 813         }
 814         g2d.drawGlyphVector(gv, x, y);
 815     }
 816 
 817     public void drawString(GlyphList gl, FontStrike strike, float x, float y,
 818                            Color selectColor, int start, int end) {
 819 
 820         int count = gl.getGlyphCount();
 821         if (count == 0) return;
 822 
 823         // In JDK6, setting graphics AA disables fast text loops
 824         g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);
 825 
 826         // If the surface has Alpha, JDK will ignore the LCD loops.
 827         // So for this to have any effect we need to fix JDK, or
 828         // ensure an opaque surface type.
 829         if (strike.getAAMode() == FontResource.AA_LCD) {
 830             g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_LCD_HRGB);
 831         }
 832 
 833         if (paintWasProportional) {
 834             Rectangle2D rectBounds = nodeBounds;
 835             if (rectBounds == null) {
 836                 Metrics m = strike.getMetrics();
 837                 rectBounds = new Rectangle2D.Float(0,
 838                                                    m.getAscent(),
 839                                                    gl.getWidth(),
 840                                                    m.getLineHeight());
 841             }
 842             g2d.setPaint(toJ2DPaint(paint, rectBounds));
 843         }
 844 
 845         CompositeStrike cStrike = null;
 846         int slot = 0;
 847         if (strike instanceof CompositeStrike) {
 848             cStrike = (CompositeStrike)strike;
 849             int glyphCode = gl.getGlyphCode(0);
 850             slot = cStrike.getStrikeSlotForGlyph(glyphCode);
 851         }
 852         java.awt.Color sColor = null;
 853         java.awt.Color tColor = null;
 854         boolean selected = false;
 855         if (selectColor != null) {
 856             sColor = toJ2DColor(selectColor);
 857             tColor = g2d.getColor();
 858             int offset = gl.getCharOffset(0);
 859             selected = start <= offset && offset < end;
 860         }
 861         int index = 0;
 862         if (sColor != null || cStrike != null) {
 863             /* Draw a segment every time selection or font changes */
 864             for (int i = 1; i < count; i++) {
 865                 if (sColor != null) {
 866                     int offset = gl.getCharOffset(i);
 867                     boolean glyphSelected = start <= offset && offset < end;
 868                     if (selected != glyphSelected) {
 869                         if (cStrike != null) {
 870                             strike = cStrike.getStrikeSlot(slot);
 871                         }
 872                         g2d.setColor(selected ? sColor : tColor);
 873                         drawString(gl, index, i, strike, x, y);
 874                         index = i;
 875                         selected = glyphSelected;
 876                     }
 877                 }
 878                 if (cStrike != null) {
 879                     int glyphCode = gl.getGlyphCode(i);
 880                     int glyphSlot = cStrike.getStrikeSlotForGlyph(glyphCode);
 881                     if (slot != glyphSlot) {
 882                         strike = cStrike.getStrikeSlot(slot);
 883                         if (sColor != null) {
 884                             g2d.setColor(selected ? sColor : tColor);
 885                         }
 886                         drawString(gl, index, i, strike, x, y);
 887                         index = i;
 888                         slot = glyphSlot;
 889                     }
 890                 }
 891             }
 892 
 893             /* Set strike and color to draw the last segment */
 894             if (cStrike != null) {
 895                 strike = cStrike.getStrikeSlot(slot);
 896             }
 897             if (sColor != null) {
 898                 g2d.setColor(selected ? sColor : tColor);
 899             }
 900         }
 901         drawString(gl, index, count, strike, x, y);
 902 
 903         /* Always restore the graphics to its initial color */
 904         if (selectColor != null) {
 905             g2d.setColor(tColor);
 906         }
 907 
 908         // Set hints back to the default.
 909         g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
 910         g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
 911     }
 912 
 913     /**
 914      * Overridden by printing subclass to preserve the printer graphics
 915      * transform.
 916      */
 917     protected void setTransformG2D(java.awt.geom.AffineTransform tx) {
 918         g2d.setTransform(tx);
 919     }
 920 
 921     /**
 922      * Needed only by printing subclass, which over-rides it.
 923      */
 924     protected void captureTransform(java.awt.Graphics2D g2d) {
 925         return;
 926     }
 927 
 928     public void drawMappedTextureRaw(Texture tex,
 929                                      float dx1, float dy1, float dx2, float dy2,
 930                                      float tx11, float ty11, float tx21, float ty21,
 931                                      float tx12, float ty12, float tx22, float ty22)
 932     {
 933         java.awt.Image img = ((J2DTexture) tex).getBufferedImage();
 934         float mxx = tx21-tx11;
 935         float myx = ty21-ty11;
 936         float mxy = tx12-tx11;
 937         float myy = ty12-ty11;
 938 //        assert(Math.abs(mxx - (tx22-tx12)) < .000001);
 939 //        assert(Math.abs(myx - (ty22-ty12)) < .000001);
 940 //        assert(Math.abs(mxy - (tx22-tx21)) < .000001);
 941 //        assert(Math.abs(myy - (ty22-ty21)) < .000001);
 942         setTransformG2D(J2D_IDENTITY);
 943         tmpAT.setTransform(mxx, myx, mxy, myy, tx11, ty11);
 944         try {
 945             tmpAT.invert();
 946             g2d.translate(dx1, dy1);
 947             g2d.scale(dx2-dx1, dy2-dy1);
 948             g2d.transform(tmpAT);
 949             g2d.drawImage(img, 0, 0, 1, 1, null);
 950         } catch (NoninvertibleTransformException e) {
 951         }
 952         setTransform(transform);
 953     }
 954 
 955     public void drawTexture(Texture tex, float x, float y, float w, float h) {
 956         java.awt.Image img = ((J2DTexture) tex).getBufferedImage();
 957         g2d.drawImage(img, (int) x, (int) y, (int) (x+w), (int) (y+h), 0, 0, (int)w, (int) h, null);
 958     }
 959 
 960     public void drawTexture(Texture tex,
 961                             float dx1, float dy1, float dx2, float dy2,
 962                             float sx1, float sy1, float sx2, float sy2)
 963     {
 964         java.awt.Image img = ((J2DTexture) tex).getBufferedImage();
 965         // Simply casting the subimage coordinates to integers does not
 966         // produce the same behavior as the Prism hw pipelines (see RT-19270).
 967         g2d.drawImage(img,
 968                 (int) dx1, (int) dy1, (int) dx2, (int) dy2,
 969                 (int) sx1, (int) sy1, (int) sx2, (int) sy2,
 970                 null);
 971     }
 972 
 973     @Override
 974     public void drawTexture3SliceH(Texture tex,
 975                                    float dx1, float dy1, float dx2, float dy2,
 976                                    float sx1, float sy1, float sx2, float sy2,
 977                                    float dh1, float dh2, float sh1, float sh2)
 978     {
 979         // Workaround for problems in NGRegion which may pass zero-width
 980         // source image area.
 981         if (sh1 + 0.1f > sh2) sh2 += 1;
 982         drawTexture(tex, dx1, dy1, dh1, dy2, sx1, sy1, sh1, sy2);
 983         drawTexture(tex, dh1, dy1, dh2, dy2, sh1, sy1, sh2, sy2);
 984         drawTexture(tex, dh2, dy1, dx2, dy2, sh2, sy1, sx2, sy2);
 985     }
 986 
 987     @Override
 988     public void drawTexture3SliceV(Texture tex,
 989                                    float dx1, float dy1, float dx2, float dy2,
 990                                    float sx1, float sy1, float sx2, float sy2,
 991                                    float dv1, float dv2, float sv1, float sv2)
 992     {
 993         // Workaround for problems in NGRegion which may pass zero-height
 994         // source image area.
 995         if (sv1 +0.1f > sv2) sv2 += 1;
 996         drawTexture(tex, dx1, dy1, dx2, dv1, sx1, sy1, sx2, sv1);
 997         drawTexture(tex, dx1, dv1, dx2, dv2, sx1, sv1, sx2, sv2);
 998         drawTexture(tex, dx1, dv2, dx2, dy2, sx1, sv2, sx2, sy2);
 999     }
1000 
1001     @Override
1002     public void drawTexture9Slice(Texture tex,
1003                                   float dx1, float dy1, float dx2, float dy2,
1004                                   float sx1, float sy1, float sx2, float sy2,
1005                                   float dh1, float dv1, float dh2, float dv2,
1006                                   float sh1, float sv1, float sh2, float sv2)
1007     {
1008         // Workaround for problems in NGRegion which may pass zero-width
1009         // or zero height source image area.
1010         if (sh1 + 0.1f > sh2) sh2 += 1;
1011         if (sv1 + 0.1f > sv2) sv2 += 1;
1012         drawTexture(tex, dx1, dy1, dh1, dv1, sx1, sy1, sh1, sv1);
1013         drawTexture(tex, dh1, dy1, dh2, dv1, sh1, sy1, sh2, sv1);
1014         drawTexture(tex, dh2, dy1, dx2, dv1, sh2, sy1, sx2, sv1);
1015 
1016         drawTexture(tex, dx1, dv1, dh1, dv2, sx1, sv1, sh1, sv2);
1017         drawTexture(tex, dh1, dv1, dh2, dv2, sh1, sv1, sh2, sv2);
1018         drawTexture(tex, dh2, dv1, dx2, dv2, sh2, sv1, sx2, sv2);
1019 
1020         drawTexture(tex, dx1, dv2, dh1, dy2, sx1, sv2, sh1, sy2);
1021         drawTexture(tex, dh1, dv2, dh2, dy2, sh1, sv2, sh2, sy2);
1022         drawTexture(tex, dh2, dv2, dx2, dy2, sh2, sv2, sx2, sy2);
1023     }
1024 
1025     public void drawTextureRaw(Texture tex,
1026                                float dx1, float dy1, float dx2, float dy2,
1027                                float tx1, float ty1, float tx2, float ty2)
1028     {
1029         int w = tex.getContentWidth();
1030         int h = tex.getContentHeight();
1031         tx1 *= w;
1032         ty1 *= h;
1033         tx2 *= w;
1034         ty2 *= h;
1035         drawTexture(tex, dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2);
1036     }
1037 
1038     public void drawTextureVO(Texture tex,
1039                               float topopacity, float botopacity,
1040                               float dx1, float dy1, float dx2, float dy2,
1041                               float sx1, float sy1, float sx2, float sy2)
1042     {
1043         // assert(caller is PrReflectionPeer and buffer is cleared to transparent)
1044         // NOTE: the assert conditions are true because that is the only
1045         // place where this method is used (unless we subclass BaseGraphics),
1046         // but there is no code here to verify that information.
1047         // The workarounds to do this for the general case would cost a lot
1048         // because they would involve creating a temporary intermediate buffer,
1049         // doing the operations below into the buffer, and then applying the
1050         // buffer to the destination.  That is not hard, but it costs a lot
1051         // of buffer allocation (or caching) when it is not really necessary
1052         // given the way this method is called currently.
1053         // Note that isoEdgeMask is ignored here, but since this is only ever
1054         // called by PrReflectionPeer and that code always uses ISOLATE_NONE
1055         // then we would only need to support ISOLATE_NONE.  The code below
1056         // does not yet verify if the results will be compatible with
1057         // ISOLATE_NONE, but given that the source coordinates are rounded to
1058         // integers in drawTexture() there is not much it can do to get exact
1059         // edge condition behavior until that deficiency is fixed (see
1060         // RT-19270 and RT-19271).
1061         java.awt.Paint savepaint = g2d.getPaint();
1062         java.awt.Composite savecomp = g2d.getComposite();
1063         java.awt.Color c1 = new java.awt.Color(1f, 1f, 1f, topopacity);
1064         java.awt.Color c2 = new java.awt.Color(1f, 1f, 1f, botopacity);
1065         g2d.setPaint(new java.awt.GradientPaint(0f, dy1, c1, 0f, dy2, c2, true));
1066         g2d.setComposite(java.awt.AlphaComposite.Src);
1067         int x = (int) Math.floor(Math.min(dx1, dx2));
1068         int y = (int) Math.floor(Math.min(dy1, dy2));
1069         int w = (int) Math.ceil(Math.max(dx1, dx2)) - x;
1070         int h = (int) Math.ceil(Math.max(dy1, dy2)) - y;
1071         g2d.fillRect(x, y, w, h);
1072         g2d.setComposite(java.awt.AlphaComposite.SrcIn);
1073         drawTexture(tex, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2);
1074         g2d.setComposite(savecomp);
1075         g2d.setPaint(savepaint);
1076     }
1077 
1078     public void drawPixelsMasked(RTTexture imgtex, RTTexture masktex,
1079                                  int dx, int dy, int dw, int dh,
1080                                  int ix, int iy, int mx, int my)
1081     {
1082         doDrawMaskTexture((J2DRTTexture) imgtex, (J2DRTTexture) masktex,
1083                           dx, dy, dw, dh,
1084                           ix, iy, mx, my,
1085                           true);
1086     }
1087 
1088     public void maskInterpolatePixels(RTTexture imgtex, RTTexture masktex, int dx,
1089                                       int dy, int dw, int dh, int ix, int iy,
1090                                       int mx, int my) {
1091         doDrawMaskTexture((J2DRTTexture) imgtex, (J2DRTTexture) masktex,
1092                           dx, dy, dw, dh,
1093                           ix, iy, mx, my,
1094                           false);
1095     }
1096 
1097     private void doDrawMaskTexture(J2DRTTexture imgtex, J2DRTTexture masktex,
1098                                    int dx, int dy, int dw, int dh,
1099                                    int ix, int iy, int mx, int my,
1100                                    boolean srcover)
1101     {
1102         int cx0 = clipRect.x;
1103         int cy0 = clipRect.y;
1104         int cx1 = cx0 + clipRect.width;
1105         int cy1 = cy0 + clipRect.height;
1106 
1107         if (dw <= 0 || dh <= 0) return;
1108         if (dx < cx0) {
1109             int bump = cx0 - dx;
1110             if ((dw -= bump) <= 0) return;
1111             ix += bump;
1112             mx += bump;
1113             dx = cx0;
1114         }
1115         if (dy < cy0) {
1116             int bump = cy0 - dy;
1117             if ((dh -= bump) <= 0) return;
1118             iy += bump;
1119             my += bump;
1120             dy = cy0;
1121         }
1122         if (dx + dw > cx1 && (dw = cx1 - dx) <= 0) return;
1123         if (dy + dh > cy1 && (dh = cy1 - dy) <= 0) return;
1124 
1125         int iw = imgtex.getContentWidth();
1126         int ih = imgtex.getContentHeight();
1127         if (ix < 0) {
1128             if ((dw += ix) <= 0) return;
1129             dx -= ix;
1130             mx -= ix;
1131             ix = 0;
1132         }
1133         if (iy < 0) {
1134             if ((dh += iy) <= 0) return;
1135             dy -= iy;
1136             my -= iy;
1137             iy = 0;
1138         }
1139         if (ix + dw > iw && (dw = iw - ix) <= 0) return;
1140         if (iy + dh > ih && (dh = ih - iy) <= 0) return;
1141 
1142         int mw = masktex.getContentWidth();
1143         int mh = masktex.getContentHeight();
1144         if (mx < 0) {
1145             if ((dw += mx) <= 0) return;
1146             dx -= mx;
1147             ix -= mx;
1148             mx = 0;
1149         }
1150         if (my < 0) {
1151             if ((dh += my) <= 0) return;
1152             dy -= my;
1153             iy -= my;
1154             my = 0;
1155         }
1156         if (mx + dw > mw && (dw = mw - mx) <= 0) return;
1157         if (my + dh > mh && (dh = mh - my) <= 0) return;
1158 
1159         int imgbuf[] = imgtex.getPixels();
1160         int maskbuf[] = masktex.getPixels();
1161         java.awt.image.DataBuffer db = target.getBackBuffer().getRaster().getDataBuffer();
1162         int dstbuf[] = ((java.awt.image.DataBufferInt) db).getData();
1163         int iscan = imgtex.getBufferedImage().getWidth();
1164         int mscan = masktex.getBufferedImage().getWidth();
1165         int dscan = target.getBackBuffer().getWidth();
1166         int ioff = iy * iscan + ix;
1167         int moff = my * mscan + mx;
1168         int doff = dy * dscan + dx;
1169         if (srcover) {
1170             for (int y = 0; y < dh; y++) {
1171                 for (int x = 0; x < dw; x++) {
1172                     int a, r, g, b;
1173                     int maskalpha = maskbuf[moff+x] >>> 24;
1174                     if (maskalpha == 0) continue;
1175                     int imgpix = imgbuf[ioff+x];
1176                     a = (imgpix >>> 24);
1177                     if (a == 0) continue;
1178                     if (maskalpha < 0xff) {
1179                         maskalpha += (maskalpha >> 7);
1180                         a *= maskalpha;
1181                         r = ((imgpix >>  16) & 0xff) * maskalpha;
1182                         g = ((imgpix >>   8) & 0xff) * maskalpha;
1183                         b = ((imgpix       ) & 0xff) * maskalpha;
1184                     } else if (a < 0xff) {
1185                         a <<= 8;
1186                         r = ((imgpix >>  16) & 0xff) << 8;
1187                         g = ((imgpix >>   8) & 0xff) << 8;
1188                         b = ((imgpix       ) & 0xff) << 8;
1189                     } else {
1190                         dstbuf[doff+x] = imgpix;
1191                         continue;
1192                     }
1193                     maskalpha = ((a + 128) >> 8);
1194                     maskalpha += (maskalpha >> 7);
1195                     maskalpha = 256 - maskalpha;
1196                     imgpix = dstbuf[doff+x];
1197                     a += ((imgpix >>> 24)       ) * maskalpha + 128;
1198                     r += ((imgpix >>  16) & 0xff) * maskalpha + 128;
1199                     g += ((imgpix >>   8) & 0xff) * maskalpha + 128;
1200                     b += ((imgpix       ) & 0xff) * maskalpha + 128;
1201                     imgpix = ((a >> 8) << 24) +
1202                              ((r >> 8) << 16) +
1203                              ((g >> 8) <<  8) +
1204                              ((b >> 8)      );
1205                     dstbuf[doff+x] = imgpix;
1206                 }
1207                 ioff += iscan;
1208                 moff += mscan;
1209                 doff += dscan;
1210             }
1211         } else {
1212             for (int y = 0; y < dh; y++) {
1213                 for (int x = 0; x < dw; x++) {
1214                     int maskalpha = maskbuf[moff+x] >>> 24;
1215                     if (maskalpha == 0) continue;
1216                     int imgpix = imgbuf[ioff+x];
1217                     if (maskalpha < 0xff) {
1218                         maskalpha += (maskalpha >> 7);
1219                         int a = ((imgpix >>> 24)       ) * maskalpha;
1220                         int r = ((imgpix >>  16) & 0xff) * maskalpha;
1221                         int g = ((imgpix >>   8) & 0xff) * maskalpha;
1222                         int b = ((imgpix       ) & 0xff) * maskalpha;
1223                         maskalpha = 256 - maskalpha;
1224                         imgpix = dstbuf[doff+x];
1225                         a += ((imgpix >>> 24)       ) * maskalpha + 128;
1226                         r += ((imgpix >>  16) & 0xff) * maskalpha + 128;
1227                         g += ((imgpix >>   8) & 0xff) * maskalpha + 128;
1228                         b += ((imgpix       ) & 0xff) * maskalpha + 128;
1229                         imgpix = ((a >> 8) << 24) +
1230                                  ((r >> 8) << 16) +
1231                                  ((g >> 8) <<  8) +
1232                                  ((b >> 8)      );
1233                     }
1234                     dstbuf[doff+x] = imgpix;
1235                 }
1236                 ioff += iscan;
1237                 moff += mscan;
1238                 doff += dscan;
1239             }
1240         }
1241     }
1242 
1243     public boolean canReadBack() {
1244         return true;
1245     }
1246 
1247     public RTTexture readBack(Rectangle view) {
1248         J2DRTTexture rtt = target.getReadbackBuffer();
1249         java.awt.Graphics2D rttg2d = rtt.createAWTGraphics2D();
1250         rttg2d.setComposite(java.awt.AlphaComposite.Src);
1251         int x0 = view.x;
1252         int y0 = view.y;
1253         int w = view.width;
1254         int h = view.height;
1255         int x1 = x0 + w;
1256         int y1 = y0 + h;
1257         rttg2d.drawImage(target.getBackBuffer(),
1258                           0,  0,  w,  h,
1259                          x0, y0, x1, y1, null);
1260         rttg2d.dispose();
1261         return rtt;
1262     }
1263 
1264     public void releaseReadBackBuffer(RTTexture view) {
1265         // This will be needed when we track LCD buffer locks and uses.
1266         // (See RT-29488)
1267 //        target.getReadbackBuffer().unlock();
1268     }
1269 
1270     public NGCamera getCameraNoClone() {
1271         throw new UnsupportedOperationException("Not supported yet.");
1272     }
1273 
1274     public boolean isDepthBuffer() {
1275         return false;
1276     }
1277 
1278     public boolean isDepthTest() {
1279         return false;
1280     }
1281 
1282     public boolean isAlphaTestShader() {
1283         if (PrismSettings.verbose && PrismSettings.forceAlphaTestShader) {
1284             System.out.println("J2D pipe doesn't support shader with alpha testing");
1285         }
1286         return false;
1287     }
1288 
1289     public void setAntialiasedShape(boolean aa) {
1290         antialiasedShape = aa;
1291         g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
1292                 antialiasedShape ? java.awt.RenderingHints.VALUE_ANTIALIAS_ON
1293                         : java.awt.RenderingHints.VALUE_ANTIALIAS_OFF);
1294     }
1295 
1296     public boolean isAntialiasedShape() {
1297         return antialiasedShape;
1298     }
1299 
1300     public void scale(float sx, float sy, float sz) {
1301         throw new UnsupportedOperationException("Not supported yet.");
1302     }
1303 
1304     public void setTransform3D(double mxx, double mxy, double mxz, double mxt,
1305                                double myx, double myy, double myz, double myt,
1306                                double mzx, double mzy, double mzz, double mzt)
1307     {
1308         if (mxz != 0.0 || myz != 0.0 ||
1309             mzx != 0.0 || mzy != 0.0 || mzz != 1.0 || mzt != 0.0)
1310         {
1311             throw new UnsupportedOperationException("3D transforms not supported.");
1312         }
1313         setTransform(mxx, myx, mxy, myy, mxt, myt);
1314     }
1315 
1316     public void setCamera(NGCamera camera) {
1317         // No-op until we support 3D
1318         /*
1319         if (!(camera instanceof PrismParallelCameraImpl)) {
1320 
1321             throw new UnsupportedOperationException(camera+" not supported.");
1322         }
1323         */
1324     }
1325 
1326     public void setDepthBuffer(boolean depthBuffer) {
1327         // No-op until we support 3D
1328     }
1329 
1330     public void setDepthTest(boolean depthTest) {
1331         // No-op until we support 3D
1332     }
1333 
1334     public void sync() {
1335     }
1336 
1337     public void translate(float tx, float ty, float tz) {
1338         throw new UnsupportedOperationException("Not supported yet.");
1339     }
1340 
1341     public void setCulling(boolean cull) {
1342         this.cull = cull;
1343     }
1344 
1345     public boolean isCulling() {
1346         return this.cull;
1347     }
1348 
1349  public void setClipRectIndex(int index) {
1350         this.clipRectIndex = index;
1351     }
1352     public int getClipRectIndex() {
1353         return this.clipRectIndex;
1354     }
1355 
1356     public void setHasPreCullingBits(boolean hasBits) {
1357         this.hasPreCullingBits = hasBits;
1358     }
1359 
1360     public boolean hasPreCullingBits() {
1361         return hasPreCullingBits;
1362     }
1363 
1364     private NodePath renderRoot;
1365     @Override
1366     public void setRenderRoot(NodePath root) {
1367         this.renderRoot = root;
1368     }
1369 
1370     @Override
1371     public NodePath getRenderRoot() {
1372         return renderRoot;
1373     }
1374 
1375     public void setState3D(boolean flag) {
1376     }
1377 
1378     public boolean isState3D() {
1379         return false;
1380     }
1381 
1382     public void setup3DRendering() {
1383     }
1384 
1385     @Override
1386     public void setPixelScaleFactor(float pixelScale) {
1387         this.pixelScale = pixelScale;
1388     }
1389 
1390     @Override
1391     public float getPixelScaleFactor() {
1392         return pixelScale;
1393     }
1394 
1395     @Override
1396     public void blit(RTTexture srcTex, RTTexture dstTex,
1397             int srcX0, int srcY0, int srcX1, int srcY1,
1398             int dstX0, int dstY0, int dstX1, int dstY1) {
1399         throw new UnsupportedOperationException("Not supported yet.");
1400     }
1401 
1402     private static class AdaptorShape implements java.awt.Shape {
1403         private Shape prshape;
1404 
1405         public void setShape(Shape prshape) {
1406             this.prshape = prshape;
1407         }
1408 
1409         public boolean contains(double x, double y) {
1410             return prshape.contains((float) x, (float) y);
1411         }
1412 
1413         public boolean contains(java.awt.geom.Point2D p) {
1414             return contains(p.getX(), p.getY());
1415         }
1416 
1417         public boolean contains(double x, double y, double w, double h) {
1418             return prshape.contains((float) x, (float) y, (float) w, (float) h);
1419         }
1420 
1421         public boolean contains(java.awt.geom.Rectangle2D r) {
1422             return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
1423         }
1424 
1425         public boolean intersects(double x, double y, double w, double h) {
1426             return prshape.intersects((float) x, (float) y, (float) w, (float) h);
1427         }
1428 
1429         public boolean intersects(java.awt.geom.Rectangle2D r) {
1430             return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
1431         }
1432 
1433         public java.awt.Rectangle getBounds() {
1434             return getBounds2D().getBounds();
1435         }
1436 
1437         public java.awt.geom.Rectangle2D getBounds2D() {
1438             RectBounds b = prshape.getBounds();
1439             java.awt.geom.Rectangle2D r2d =
1440                 new java.awt.geom.Rectangle2D.Float();
1441             r2d.setFrameFromDiagonal(b.getMinX(), b.getMinY(), b.getMaxX(), b.getMaxY());
1442             return r2d;
1443         }
1444 
1445         private static AdaptorPathIterator tmpAdaptor =
1446                 new AdaptorPathIterator();
1447         private static java.awt.geom.PathIterator tmpAdaptor(PathIterator pi) {
1448             tmpAdaptor.setIterator(pi);
1449             return tmpAdaptor;
1450         }
1451 
1452         public java.awt.geom.PathIterator
1453             getPathIterator(java.awt.geom.AffineTransform at)
1454         {
1455             BaseTransform tx = (at == null) ? null : toPrTransform(at);
1456             return tmpAdaptor(prshape.getPathIterator(tx));
1457         }
1458 
1459         public java.awt.geom.PathIterator
1460             getPathIterator(java.awt.geom.AffineTransform at,
1461                             double flatness)
1462         {
1463             BaseTransform tx = (at == null) ? null : toPrTransform(at);
1464             return tmpAdaptor(prshape.getPathIterator(tx, (float) flatness));
1465         }
1466     }
1467 
1468     private static class AdaptorPathIterator
1469         implements java.awt.geom.PathIterator
1470     {
1471         private static int NUM_COORDS[] = { 2, 2, 4, 6, 0 };
1472         PathIterator priterator;
1473         float tmpcoords[];
1474 
1475         public void setIterator(PathIterator priterator) {
1476             this.priterator = priterator;
1477         }
1478 
1479         public int currentSegment(float[] coords) {
1480             return priterator.currentSegment(coords);
1481         }
1482 
1483         public int currentSegment(double[] coords) {
1484             if (tmpcoords == null) {
1485                 tmpcoords = new float[6];
1486             }
1487             int ret = priterator.currentSegment(tmpcoords);
1488             for (int i = 0; i < NUM_COORDS[ret]; i++) {
1489                 coords[i] = (double) tmpcoords[i];
1490             }
1491             return ret;
1492         }
1493 
1494         public int getWindingRule() {
1495             return priterator.getWindingRule();
1496         }
1497 
1498         public boolean isDone() {
1499             return priterator.isDone();
1500         }
1501 
1502         public void next() {
1503             priterator.next();
1504         }
1505     }
1506 
1507     static abstract class FilterStroke implements java.awt.Stroke {
1508         protected java.awt.BasicStroke stroke;
1509 
1510         FilterStroke(java.awt.BasicStroke stroke) {
1511             this.stroke = stroke;
1512         }
1513 
1514         abstract protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r);
1515         abstract protected java.awt.Shape makeStrokedShape(java.awt.Shape s);
1516 
1517         public java.awt.Shape createStrokedShape(java.awt.Shape p) {
1518             if (p instanceof java.awt.geom.Rectangle2D) {
1519                 java.awt.Shape s = makeStrokedRect((java.awt.geom.Rectangle2D) p);
1520                 if (s != null) {
1521                     return s;
1522                 }
1523             }
1524             return makeStrokedShape(p);
1525         }
1526 
1527         // ArcIterator.btan(Math.PI/2)
1528         static final double CtrlVal = 0.5522847498307933;
1529 
1530         static java.awt.geom.Point2D cornerArc(java.awt.geom.GeneralPath gp,
1531                                                       float x0, float y0,
1532                                                       float xc, float yc,
1533                                                       float x1, float y1)
1534         {
1535             return cornerArc(gp, x0, y0, xc, yc, x1, y1, 0.5f);
1536         }
1537 
1538         static java.awt.geom.Point2D cornerArc(java.awt.geom.GeneralPath gp,
1539                                                       float x0, float y0,
1540                                                       float xc, float yc,
1541                                                       float x1, float y1, float t)
1542         {
1543             float xc0 = (float) (x0 + CtrlVal * (xc - x0));
1544             float yc0 = (float) (y0 + CtrlVal * (yc - y0));
1545             float xc1 = (float) (x1 + CtrlVal * (xc - x1));
1546             float yc1 = (float) (y1 + CtrlVal * (yc - y1));
1547             gp.curveTo(xc0, yc0, xc1, yc1, x1, y1);
1548 
1549             return new java.awt.geom.Point2D.Float(eval(x0, xc0, xc1, x1, t),
1550                                                    eval(y0, yc0, yc1, y1, t));
1551         }
1552 
1553         static float eval(float c0, float c1, float c2, float c3, float t) {
1554             c0 = c0 + (c1-c0) * t;
1555             c1 = c1 + (c2-c1) * t;
1556             c2 = c2 + (c3-c2) * t;
1557             c0 = c0 + (c1-c0) * t;
1558             c1 = c1 + (c2-c1) * t;
1559             return c0 + (c1-c0) * t;
1560         }
1561     }
1562 
1563     static class InnerStroke extends FilterStroke {
1564         InnerStroke(java.awt.BasicStroke stroke) {
1565             super(stroke);
1566         }
1567 
1568         protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r) {
1569             if (stroke.getDashArray() != null) {
1570                 return null;
1571             }
1572             float pad = stroke.getLineWidth() / 2f;
1573             if (pad >= r.getWidth() || pad >= r.getHeight()) {
1574                 return r;
1575             }
1576             float rx0 = (float) r.getX();
1577             float ry0 = (float) r.getY();
1578             float rx1 = rx0 + (float) r.getWidth();
1579             float ry1 = ry0 + (float) r.getHeight();
1580             java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
1581             gp.moveTo(rx0, ry0);
1582             gp.lineTo(rx1, ry0);
1583             gp.lineTo(rx1, ry1);
1584             gp.lineTo(rx0, ry1);
1585             gp.closePath();
1586             rx0 += pad;
1587             ry0 += pad;
1588             rx1 -= pad;
1589             ry1 -= pad;
1590             gp.moveTo(rx0, ry0);
1591             gp.lineTo(rx0, ry1);
1592             gp.lineTo(rx1, ry1);
1593             gp.lineTo(rx1, ry0);
1594             gp.closePath();
1595             return gp;
1596         }
1597 
1598         // NOTE: This is a work in progress, not used yet
1599         protected java.awt.Shape makeStrokedEllipse(java.awt.geom.Ellipse2D e) {
1600             if (stroke.getDashArray() != null) {
1601                 return null;
1602             }
1603             float pad = stroke.getLineWidth() / 2f;
1604             float w = (float) e.getWidth();
1605             float h = (float) e.getHeight();
1606             if (w - 2*pad > h * 2 || h - 2*pad > w * 2) {
1607                 // If the inner ellipse is too "squashed" then we can not
1608                 // approximate it with just a single cubic per quadrant.
1609                 // NOTE: measure so we can relax this restriction and
1610                 // also consider modifying the code below to insert
1611                 // more cubics in those cases.
1612                 return null;
1613             }
1614             if (pad >= w || pad >= h) {
1615                 return e;
1616             }
1617             float x0 = (float) e.getX();
1618             float y0 = (float) e.getY();
1619             float xc = x0 + w / 2f;
1620             float yc = y0 + h / 2f;
1621             float x1 = x0 + w;
1622             float y1 = y0 + h;
1623             java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
1624             gp.moveTo(xc, y0);
1625             cornerArc(gp, xc, y0, x1, y0, x1, yc);
1626             cornerArc(gp, x1, yc, x1, y1, xc, y1);
1627             cornerArc(gp, xc, y1, x0, y1, x0, yc);
1628             cornerArc(gp, x0, yc, x0, y0, xc, y0);
1629             gp.closePath();
1630             x0 += pad;
1631             y0 += pad;
1632             x1 -= pad;
1633             y1 -= pad;
1634             gp.moveTo(xc, y0);
1635             cornerArc(gp, xc, y0, x0, y0, x0, yc);
1636             cornerArc(gp, x0, yc, x0, y1, xc, y1);
1637             cornerArc(gp, xc, y1, x1, y1, x1, yc);
1638             cornerArc(gp, x1, yc, x1, y0, xc, y0);
1639             gp.closePath();
1640             return gp;
1641         }
1642 
1643         @Override
1644         protected java.awt.Shape makeStrokedShape(java.awt.Shape s) {
1645             java.awt.Shape ss = stroke.createStrokedShape(s);
1646             java.awt.geom.Area b = new java.awt.geom.Area(ss);
1647             b.intersect(new java.awt.geom.Area(s));
1648             return b;
1649         }
1650     }
1651 
1652     static class OuterStroke extends FilterStroke {
1653         static double SQRT_2 = Math.sqrt(2);
1654 
1655         OuterStroke(java.awt.BasicStroke stroke) {
1656             super(stroke);
1657         }
1658 
1659         protected java.awt.Shape makeStrokedRect(java.awt.geom.Rectangle2D r) {
1660             if (stroke.getDashArray() != null) {
1661                 return null;
1662             }
1663             float pad = stroke.getLineWidth() / 2f;
1664             float rx0 = (float) r.getX();
1665             float ry0 = (float) r.getY();
1666             float rx1 = rx0 + (float) r.getWidth();
1667             float ry1 = ry0 + (float) r.getHeight();
1668             java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
1669             // clockwise
1670             gp.moveTo(rx0, ry0);
1671             gp.lineTo(rx1, ry0);
1672             gp.lineTo(rx1, ry1);
1673             gp.lineTo(rx0, ry1);
1674             gp.closePath();
1675             float ox0 = rx0 - pad;
1676             float oy0 = ry0 - pad;
1677             float ox1 = rx1 + pad;
1678             float oy1 = ry1 + pad;
1679             switch (stroke.getLineJoin()) {
1680             case BasicStroke.JOIN_MITER:
1681                 // A miter limit of less than sqrt(2) bevels right angles...
1682                 if (stroke.getMiterLimit() >= SQRT_2) {
1683                     // counter-clockwise
1684                     gp.moveTo(ox0, oy0);
1685                     gp.lineTo(ox0, oy1);
1686                     gp.lineTo(ox1, oy1);
1687                     gp.lineTo(ox1, oy0);
1688                     gp.closePath();
1689                     break;
1690                 }
1691                 // NO BREAK
1692             case BasicStroke.JOIN_BEVEL:
1693                 // counter-clockwise
1694                 gp.moveTo(ox0, ry0);
1695                 gp.lineTo(ox0, ry1);  // left edge
1696                 gp.lineTo(rx0, oy1);  // ll corner
1697                 gp.lineTo(rx1, oy1);  // bottom edge
1698                 gp.lineTo(ox1, ry1);  // lr corner
1699                 gp.lineTo(ox1, ry0);  // right edge
1700                 gp.lineTo(rx1, oy0);  // ur corner
1701                 gp.lineTo(rx0, oy0);  // top edge
1702                 gp.closePath();       // ul corner
1703                 break;
1704             case BasicStroke.JOIN_ROUND:
1705                 // counter-clockwise
1706                 gp.moveTo(ox0, ry0);
1707                 gp.lineTo(ox0, ry1);                          // left edge
1708                 cornerArc(gp, ox0, ry1, ox0, oy1, rx0, oy1);  // ll corner
1709                 gp.lineTo(rx1, oy1);                          // bottom edge
1710                 cornerArc(gp, rx1, oy1, ox1, oy1, ox1, ry1);  // lr corner
1711                 gp.lineTo(ox1, ry0);                          // right edge
1712                 cornerArc(gp, ox1, ry0, ox1, oy0, rx1, oy0);  // ur corner
1713                 gp.lineTo(rx0, oy0);                          // top edge
1714                 cornerArc(gp, rx0, oy0, ox0, oy0, ox0, ry0);  // ul corner
1715                 gp.closePath();
1716                 break;
1717             default:
1718                 throw new InternalError("Unrecognized line join style");
1719             }
1720             return gp;
1721         }
1722 
1723         // NOTE: This is a work in progress, not used yet
1724         protected java.awt.Shape makeStrokedEllipse(java.awt.geom.Ellipse2D e) {
1725             if (stroke.getDashArray() != null) {
1726                 return null;
1727             }
1728             float pad = stroke.getLineWidth() / 2f;
1729             float w = (float) e.getWidth();
1730             float h = (float) e.getHeight();
1731             if (w > h * 2 || h > w * 2) {
1732                 // If the inner ellipse is too "squashed" then we can not
1733                 // approximate it with just a single cubic per quadrant.
1734                 // NOTE: measure so we can relax this restriction and
1735                 // also consider modifying the code below to insert
1736                 // more cubics in those cases.
1737                 return null;
1738             }
1739             float x0 = (float) e.getX();
1740             float y0 = (float) e.getY();
1741             float xc = x0 + w / 2f;
1742             float yc = y0 + h / 2f;
1743             float x1 = x0 + w;
1744             float y1 = y0 + h;
1745             java.awt.geom.GeneralPath gp = new java.awt.geom.GeneralPath();
1746             gp.moveTo(xc, y0);
1747             cornerArc(gp, xc, y0, x1, y0, x1, yc);
1748             cornerArc(gp, x1, yc, x1, y1, xc, y1);
1749             cornerArc(gp, xc, y1, x0, y1, x0, yc);
1750             cornerArc(gp, x0, yc, x0, y0, xc, y0);
1751             gp.closePath();
1752             x0 -= pad;
1753             y0 -= pad;
1754             x1 += pad;
1755             y1 += pad;
1756             gp.moveTo(xc, y0);
1757             cornerArc(gp, xc, y0, x0, y0, x0, yc);
1758             cornerArc(gp, x0, yc, x0, y1, xc, y1);
1759             cornerArc(gp, xc, y1, x1, y1, x1, yc);
1760             cornerArc(gp, x1, yc, x1, y0, xc, y0);
1761             gp.closePath();
1762             return gp;
1763         }
1764 
1765         @Override
1766         protected java.awt.Shape makeStrokedShape(java.awt.Shape s) {
1767             java.awt.Shape ss = stroke.createStrokedShape(s);
1768             java.awt.geom.Area b = new java.awt.geom.Area(ss);
1769             b.subtract(new java.awt.geom.Area(s));
1770             return b;
1771         }
1772     }
1773 
1774     @Override
1775     public void setLights(NGLightBase[] lights) {
1776         // Light are not supported by J2d
1777     }
1778 
1779     @Override
1780     public NGLightBase[] getLights() {
1781         // Light are not supported by J2d
1782         return null;
1783     }
1784 }