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