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