1 /*
   2  * Copyright (c) 2009, 2013, 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.impl.ps;
  27 
  28 import java.security.AccessController;
  29 import java.security.PrivilegedAction;
  30 import com.sun.javafx.font.FontResource;
  31 import com.sun.javafx.font.FontStrike;
  32 import com.sun.javafx.font.Metrics;
  33 import com.sun.javafx.font.PrismFontFactory;
  34 import com.sun.javafx.geom.BaseBounds;
  35 import com.sun.javafx.geom.Point2D;
  36 import com.sun.javafx.geom.RectBounds;
  37 import com.sun.javafx.geom.Rectangle;
  38 import com.sun.javafx.geom.Shape;
  39 import com.sun.javafx.geom.transform.Affine2D;
  40 import com.sun.javafx.geom.transform.Affine3D;
  41 import com.sun.javafx.geom.transform.AffineBase;
  42 import com.sun.javafx.geom.transform.BaseTransform;
  43 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  44 import com.sun.javafx.scene.text.GlyphList;
  45 import com.sun.javafx.sg.prism.NGLightBase;
  46 import com.sun.prism.BasicStroke;
  47 import com.sun.prism.CompositeMode;
  48 import com.sun.prism.MaskTextureGraphics;
  49 import com.sun.prism.MultiTexture;
  50 import com.sun.prism.PixelFormat;
  51 import com.sun.prism.RTTexture;
  52 import com.sun.prism.ReadbackGraphics;
  53 import com.sun.prism.ReadbackRenderTarget;
  54 import com.sun.prism.RenderTarget;
  55 import com.sun.prism.Texture;
  56 import com.sun.prism.impl.BaseGraphics;
  57 import com.sun.prism.impl.GlyphCache;
  58 import com.sun.prism.impl.PrismSettings;
  59 import com.sun.prism.impl.VertexBuffer;
  60 import com.sun.prism.impl.ps.BaseShaderContext.MaskType;
  61 import com.sun.prism.impl.shape.MaskData;
  62 import com.sun.prism.impl.shape.ShapeUtil;
  63 import com.sun.prism.paint.Color;
  64 import com.sun.prism.paint.Gradient;
  65 import com.sun.prism.paint.ImagePattern;
  66 import com.sun.prism.paint.LinearGradient;
  67 import com.sun.prism.paint.Paint;
  68 import com.sun.prism.paint.RadialGradient;
  69 import com.sun.prism.ps.Shader;
  70 import com.sun.prism.ps.ShaderGraphics;
  71 
  72 public abstract class BaseShaderGraphics
  73     extends BaseGraphics
  74     implements ShaderGraphics, ReadbackGraphics, MaskTextureGraphics
  75 {
  76     private static Affine2D TEMP_TX2D = new Affine2D();
  77     private static Affine3D TEMP_TX3D = new Affine3D();
  78 
  79     private final BaseShaderContext context;
  80     private Shader externalShader;
  81     private boolean isComplexPaint;
  82 
  83     protected BaseShaderGraphics(BaseShaderContext context,
  84                                  RenderTarget renderTarget)
  85     {
  86         super(context, renderTarget);
  87         this.context = context;
  88     }
  89 
  90     BaseShaderContext getContext() {
  91         return context;
  92     }
  93 
  94     boolean isComplexPaint() {
  95         return isComplexPaint;
  96     }
  97 
  98     public void getPaintShaderTransform(Affine3D ret) {
  99         ret.setTransform(getTransformNoClone());
 100     }
 101 
 102     public Shader getExternalShader() {
 103         return externalShader;
 104     }
 105 
 106     public void setExternalShader(Shader shader) {
 107         this.externalShader = shader;
 108         context.setExternalShader(this, shader);
 109     }
 110 
 111     @Override
 112     public void setPaint(Paint paint) {
 113         if (paint.getType().isGradient()) {
 114             Gradient grad = (Gradient)paint;
 115             isComplexPaint = grad.getNumStops() > PaintHelper.MULTI_MAX_FRACTIONS;
 116         } else {
 117             isComplexPaint = false;
 118         }
 119         super.setPaint(paint);
 120     }
 121 
 122     private NGLightBase lights[] = null;
 123 
 124     public void setLights(NGLightBase lights[]) { this.lights = lights; }
 125 
 126     public final NGLightBase[] getLights() { return this.lights; }
 127 
 128     @Override
 129     public void drawTexture(Texture tex,
 130                             float dx1, float dy1, float dx2, float dy2,
 131                             float sx1, float sy1, float sx2, float sy2)
 132     {
 133         // intercept MultiTexture operations
 134         // FIXME: this should be pushed up to Graphics interface so that non-shader
 135         //        renderers can use MultiTexture too
 136         if (tex instanceof MultiTexture) {
 137             drawMultiTexture((MultiTexture)tex, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2);
 138         } else {
 139             super.drawTexture(tex, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2);
 140         }
 141     }
 142 
 143     @Override
 144     public void drawTexture3SliceH(Texture tex,
 145                                    float dx1, float dy1, float dx2, float dy2,
 146                                    float sx1, float sy1, float sx2, float sy2,
 147                                    float dh1, float dh2, float sh1, float sh2)
 148     {
 149         if (!(tex instanceof MultiTexture)) {
 150             super.drawTexture3SliceH(tex,
 151                                      dx1, dy1, dx2, dy2,
 152                                      sx1, sy1, sx2, sy2,
 153                                      dh1, dh2, sh1, sh2);
 154             return;
 155         }
 156         MultiTexture mtex = (MultiTexture) tex;
 157         drawMultiTexture(mtex, dx1, dy1, dh1, dy2, sx1, sy1, sh1, sy2);
 158         drawMultiTexture(mtex, dh1, dy1, dh2, dy2, sh1, sy1, sh2, sy2);
 159         drawMultiTexture(mtex, dh2, dy1, dx2, dy2, sh2, sy1, sx2, sy2);
 160     }
 161 
 162     @Override
 163     public void drawTexture3SliceV(Texture tex,
 164                                    float dx1, float dy1, float dx2, float dy2,
 165                                    float sx1, float sy1, float sx2, float sy2,
 166                                    float dv1, float dv2, float sv1, float sv2)
 167     {
 168         if (!(tex instanceof MultiTexture)) {
 169             super.drawTexture3SliceV(tex,
 170                                      dx1, dy1, dx2, dy2,
 171                                      sx1, sy1, sx2, sy2,
 172                                      dv1, dv2, sv1, sv2);
 173             return;
 174         }
 175         MultiTexture mtex = (MultiTexture) tex;
 176         drawMultiTexture(mtex, dx1, dy1, dx2, dv1, sx1, sy1, sx2, sv1);
 177         drawMultiTexture(mtex, dx1, dv1, dx2, dv2, sx1, sv1, sx2, sv2);
 178         drawMultiTexture(mtex, dx1, dv2, dx2, dy2, sx1, sv2, sx2, sy2);
 179     }
 180 
 181     @Override
 182     public void drawTexture9Slice(Texture tex,
 183                                   float dx1, float dy1, float dx2, float dy2,
 184                                   float sx1, float sy1, float sx2, float sy2,
 185                                   float dh1, float dv1, float dh2, float dv2,
 186                                   float sh1, float sv1, float sh2, float sv2)
 187     {
 188         if (!(tex instanceof MultiTexture)) {
 189             super.drawTexture9Slice(tex,
 190                                     dx1, dy1, dx2, dy2,
 191                                     sx1, sy1, sx2, sy2,
 192                                     dh1, dv1, dh2, dv2,
 193                                     sh1, sv1, sh2, sv2);
 194             return;
 195         }
 196         MultiTexture mtex = (MultiTexture) tex;
 197         drawMultiTexture(mtex, dx1, dy1, dh1, dv1, sx1, sy1, sh1, sv1);
 198         drawMultiTexture(mtex, dh1, dy1, dh2, dv1, sh1, sy1, sh2, sv1);
 199         drawMultiTexture(mtex, dh2, dy1, dx2, dv1, sh2, sy1, sx2, sv1);
 200 
 201         drawMultiTexture(mtex, dx1, dv1, dh1, dv2, sx1, sv1, sh1, sv2);
 202         drawMultiTexture(mtex, dh1, dv1, dh2, dv2, sh1, sv1, sh2, sv2);
 203         drawMultiTexture(mtex, dh2, dv1, dx2, dv2, sh2, sv1, sx2, sv2);
 204 
 205         drawMultiTexture(mtex, dx1, dv2, dh1, dy2, sx1, sv2, sh1, sy2);
 206         drawMultiTexture(mtex, dh1, dv2, dh2, dy2, sh1, sv2, sh2, sy2);
 207         drawMultiTexture(mtex, dh2, dv2, dx2, dy2, sh2, sv2, sx2, sy2);
 208     }
 209 
 210     private static float calculateScaleFactor(float contentDim, float physicalDim) {
 211         if (contentDim == physicalDim) {
 212             return 1f;
 213         }
 214         // we have to subtract 1 to eliminate the "green line of death"
 215         return (contentDim-1) / physicalDim;
 216     }
 217 
 218     protected void drawMultiTexture(MultiTexture tex,
 219                                   float dx1, float dy1, float dx2, float dy2,
 220                                   float sx1, float sy1, float sx2, float sy2)
 221     {
 222         // The following is safe; this method does not mutate the transform
 223         BaseTransform xform = getTransformNoClone();
 224         if (isSimpleTranslate) {
 225             xform = IDENT;
 226             dx1 += transX;
 227             dy1 += transY;
 228             dx2 += transX;
 229             dy2 += transY;
 230         }
 231 
 232         Texture textures[] = ((MultiTexture)tex).getTextures();
 233         Shader shader = context.validateTextureOp(this, xform, textures, tex.getPixelFormat());
 234 
 235         if (null == shader) {
 236             // FIXME: throw exception?? We can't do anything without the shader..
 237             return;
 238         }
 239 
 240         if (tex.getPixelFormat() == PixelFormat.MULTI_YCbCr_420) {
 241             Texture lumaTex = textures[PixelFormat.YCBCR_PLANE_LUMA];
 242             Texture cbTex = textures[PixelFormat.YCBCR_PLANE_CHROMABLUE];
 243             Texture crTex = textures[PixelFormat.YCBCR_PLANE_CHROMARED];
 244 
 245             // sampler scaling factors
 246             float imgWidth = (float)tex.getContentWidth();
 247             float imgHeight = (float)tex.getContentHeight();
 248             float lumaScaleX, lumaScaleY;
 249             float alphaScaleX, alphaScaleY;
 250             float cbScaleX, cbScaleY;
 251             float crScaleX, crScaleY;
 252 
 253             lumaScaleX = calculateScaleFactor(imgWidth, (float)lumaTex.getPhysicalWidth());
 254             lumaScaleY = calculateScaleFactor(imgHeight, (float)lumaTex.getPhysicalHeight());
 255 
 256             if (textures.length > 3) {
 257                 Texture alphaTex = textures[PixelFormat.YCBCR_PLANE_ALPHA];
 258                 alphaScaleX = calculateScaleFactor(imgWidth, (float)alphaTex.getPhysicalWidth());
 259                 alphaScaleY = calculateScaleFactor(imgHeight, (float)alphaTex.getPhysicalHeight());
 260             } else {
 261                 alphaScaleX = alphaScaleY = 0f;
 262             }
 263 
 264             float chromaWidth = (float)Math.floor((double)imgWidth/2.0);
 265             float chromaHeight = (float)Math.floor((double)imgHeight/2.0);
 266 
 267             cbScaleX = calculateScaleFactor(chromaWidth, (float)cbTex.getPhysicalWidth());
 268             cbScaleY = calculateScaleFactor(chromaHeight, (float)cbTex.getPhysicalHeight());
 269             crScaleX = calculateScaleFactor(chromaWidth, (float)crTex.getPhysicalWidth());
 270             crScaleY = calculateScaleFactor(chromaHeight, (float)crTex.getPhysicalHeight());
 271 
 272             shader.setConstant("lumaAlphaScale", lumaScaleX, lumaScaleY, alphaScaleX, alphaScaleY);
 273             shader.setConstant("cbCrScale", cbScaleX, cbScaleY, crScaleX, crScaleY);
 274 
 275             float tx1 = sx1 / imgWidth;
 276             float ty1 = sy1 / imgHeight;
 277             float tx2 = sx2 / imgWidth;
 278             float ty2 = sy2 / imgHeight;
 279 
 280             VertexBuffer vb = context.getVertexBuffer();
 281             vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2);
 282         } else {
 283             // should have been caught by validateTextureOp, but just in case
 284             throw new UnsupportedOperationException("Unsupported multitexture format "+tex.getPixelFormat());
 285         }
 286     }
 287 
 288     public void drawTextureRaw2(Texture src1, Texture src2,
 289                                 float dx1, float dy1, float dx2, float dy2,
 290                                 float t1x1, float t1y1, float t1x2, float t1y2,
 291                                 float t2x1, float t2y1, float t2x2, float t2y2)
 292     {
 293         // The following is safe; this method does not mutate the transform
 294         BaseTransform xform = getTransformNoClone();
 295         if (isSimpleTranslate) {
 296             xform = IDENT;
 297             dx1 += transX;
 298             dy1 += transY;
 299             dx2 += transX;
 300             dy2 += transY;
 301         }
 302         context.validateTextureOp(this, xform, src1, src2,
 303                                   PixelFormat.INT_ARGB_PRE);
 304 
 305         VertexBuffer vb = context.getVertexBuffer();
 306         vb.addQuad(dx1, dy1, dx2, dy2,
 307                    t1x1, t1y1, t1x2, t1y2,
 308                    t2x1, t2y1, t2x2, t2y2);
 309     }
 310 
 311     public void drawMappedTextureRaw2(Texture src1, Texture src2,
 312                                       float dx1, float dy1, float dx2, float dy2,
 313                                       float t1x11, float t1y11, float t1x21, float t1y21,
 314                                       float t1x12, float t1y12, float t1x22, float t1y22,
 315                                       float t2x11, float t2y11, float t2x21, float t2y21,
 316                                       float t2x12, float t2y12, float t2x22, float t2y22)
 317     {
 318         // The following is safe; this method does not mutate the transform
 319         BaseTransform xform = getTransformNoClone();
 320         if (isSimpleTranslate) {
 321             xform = IDENT;
 322             dx1 += transX;
 323             dy1 += transY;
 324             dx2 += transX;
 325             dy2 += transY;
 326         }
 327         context.validateTextureOp(this, xform, src1, src2,
 328                                   PixelFormat.INT_ARGB_PRE);
 329 
 330         VertexBuffer vb = context.getVertexBuffer();
 331         vb.addMappedQuad(dx1, dy1, dx2, dy2,
 332                          t1x11, t1y11, t1x21, t1y21,
 333                          t1x12, t1y12, t1x22, t1y22,
 334                          t2x11, t2y11, t2x21, t2y21,
 335                          t2x12, t2y12, t2x22, t2y22);
 336     }
 337 
 338     public void drawPixelsMasked(RTTexture imgtex, RTTexture masktex,
 339                                  int dx, int dy, int dw, int dh,
 340                                  int ix, int iy, int mx, int my)
 341     {
 342         if (dw <= 0 || dh <= 0) return;
 343         float iw = imgtex.getPhysicalWidth();
 344         float ih = imgtex.getPhysicalHeight();
 345         float mw = masktex.getPhysicalWidth();
 346         float mh = masktex.getPhysicalHeight();
 347         float dx1 = dx;
 348         float dy1 = dy;
 349         float dx2 = dx + dw;
 350         float dy2 = dy + dh;
 351         float ix1 = ix / iw;
 352         float iy1 = iy / ih;
 353         float ix2 = (ix + dw) / iw;
 354         float iy2 = (iy + dh) / ih;
 355         float mx1 = mx / mw;
 356         float my1 = my / mh;
 357         float mx2 = (mx + dw) / mw;
 358         float my2 = (my + dh) / mh;
 359         context.validateMaskTextureOp(this, IDENT, imgtex, masktex,
 360                                       PixelFormat.INT_ARGB_PRE);
 361         VertexBuffer vb = context.getVertexBuffer();
 362         vb.addQuad(dx1, dy1, dx2, dy2,
 363                    ix1, iy1, ix2, iy2,
 364                    mx1, my1, mx2, my2);
 365     }
 366 
 367     public void maskInterpolatePixels(RTTexture imgtex, RTTexture masktex,
 368                                       int dx, int dy, int dw, int dh,
 369                                       int ix, int iy, int mx, int my)
 370     {
 371         if (dw <= 0 || dh <= 0) return;
 372         float iw = imgtex.getPhysicalWidth();
 373         float ih = imgtex.getPhysicalHeight();
 374         float mw = masktex.getPhysicalWidth();
 375         float mh = masktex.getPhysicalHeight();
 376         float dx1 = dx;
 377         float dy1 = dy;
 378         float dx2 = dx + dw;
 379         float dy2 = dy + dh;
 380         float ix1 = ix / iw;
 381         float iy1 = iy / ih;
 382         float ix2 = (ix + dw) / iw;
 383         float iy2 = (iy + dh) / ih;
 384         float mx1 = mx / mw;
 385         float my1 = my / mh;
 386         float mx2 = (mx + dw) / mw;
 387         float my2 = (my + dh) / mh;
 388         CompositeMode oldmode = getCompositeMode();
 389         setCompositeMode(CompositeMode.DST_OUT);
 390         context.validateTextureOp(this, IDENT, masktex,
 391                                   PixelFormat.INT_ARGB_PRE);
 392         VertexBuffer vb = context.getVertexBuffer();
 393         vb.addQuad(dx1, dy1, dx2, dy2,
 394                    mx1, my1, mx2, my2);
 395 
 396         setCompositeMode(CompositeMode.ADD);
 397         context.validateMaskTextureOp(this, IDENT, imgtex, masktex,
 398                                       PixelFormat.INT_ARGB_PRE);
 399         vb.addQuad(dx1, dy1, dx2, dy2,
 400                    ix1, iy1, ix2, iy2,
 401                    mx1, my1, mx2, my2);
 402 
 403         setCompositeMode(oldmode);
 404     }
 405 
 406     private void renderWithComplexPaint(Shape shape, BasicStroke stroke,
 407                                         float bx, float by, float bw, float bh)
 408     {
 409         // creating/updating the mask texture may unset the current
 410         // texture used by pending vertices, so flush the vertex buffer first
 411         context.flushVertexBuffer();
 412 
 413         // The following is safe; this method does not mutate the transform
 414         BaseTransform xform = getTransformNoClone();
 415         MaskData maskData =
 416             ShapeUtil.rasterizeShape(shape, stroke, getFinalClipNoClone(), xform, true);
 417         int maskW = maskData.getWidth();
 418         int maskH = maskData.getHeight();
 419 
 420         float dx1 = maskData.getOriginX();
 421         float dy1 = maskData.getOriginY();
 422         float dx2 = dx1 + maskW;
 423         float dy2 = dy1 + maskH;
 424 
 425         // Note that we could use multitexturing here (upload mask
 426         // data to texture unit 0 and paint data to texture unit 1
 427         // then set the texture mode to modulate) but this operation is
 428         // already plenty slow and not worth optimizing at this time,
 429         // so for now we will merge the mask with the paint data in software.
 430         Gradient grad = (Gradient)paint;
 431         TEMP_TX2D.setToTranslation(-dx1, -dy1);
 432         TEMP_TX2D.concatenate(xform);
 433         Texture tex = context.getGradientTexture(grad, TEMP_TX2D,
 434                                                  maskW, maskH, maskData,
 435                                                  bx, by, bw, bh);
 436 
 437         float tx1 = 0f;
 438         float ty1 = 0f;
 439         float tx2 = tx1 + ((float)maskW) / tex.getPhysicalWidth();
 440         float ty2 = ty1 + ((float)maskH) / tex.getPhysicalHeight();
 441 
 442         // the mask has been generated in device space, so we use
 443         // identity transform here
 444         VertexBuffer vb = context.getVertexBuffer();
 445         context.validateTextureOp(this, IDENT, tex, null, tex.getPixelFormat());
 446         vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2);
 447         tex.unlock();
 448     }
 449 
 450     @Override
 451     protected void renderShape(Shape shape, BasicStroke stroke,
 452                                float bx, float by, float bw, float bh)
 453     {
 454         if (isComplexPaint) {
 455             renderWithComplexPaint(shape, stroke, bx, by, bw, bh);
 456             return;
 457         }
 458 
 459         // creating/updating the mask texture may unset the current
 460         // texture used by pending vertices, so flush the vertex buffer first
 461         context.flushVertexBuffer();
 462 
 463         // The following is safe; this method does not mutate the transform
 464         BaseTransform xform = getTransformNoClone();
 465         MaskData maskData =
 466             ShapeUtil.rasterizeShape(shape, stroke, getFinalClipNoClone(), xform, true);
 467         Texture maskTex = context.getMaskTexture(maskData, false);
 468         int maskW = maskData.getWidth();
 469         int maskH = maskData.getHeight();
 470 
 471         float dx1 = maskData.getOriginX();
 472         float dy1 = maskData.getOriginY();
 473         float dx2 = dx1 + maskW;
 474         float dy2 = dy1 + maskH;
 475         float tx1 = 0f;
 476         float ty1 = 0f;
 477         float tx2 = tx1 + ((float)maskW) / maskTex.getPhysicalWidth();
 478         float ty2 = ty1 + ((float)maskH) / maskTex.getPhysicalHeight();
 479 
 480         if (PrismSettings.primTextureSize != 0) {
 481             // the mask has been generated in device space, so we use
 482             // identity transform here
 483             Shader shader =
 484                 context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE, maskTex,
 485                                         bx, by, bw, bh);
 486 
 487             VertexBuffer vb = context.getVertexBuffer();
 488             vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2,
 489                     getPaintTextureTx(xform, shader, bx, by, bw, bh));
 490         } else {
 491             // the mask has been generated in device space, so we use
 492             // identity transform here
 493             context.validatePaintOp(this, IDENT, maskTex, bx, by, bw, bh);
 494 
 495             VertexBuffer vb = context.getVertexBuffer();
 496             vb.addQuad(dx1, dy1, dx2, dy2, tx1, ty1, tx2, ty2);
 497         }
 498         maskTex.unlock();
 499     }
 500 
 501     private static float getStrokeExpansionFactor(BasicStroke stroke) {
 502         if (stroke.getType() == BasicStroke.TYPE_OUTER) {
 503             return 1f;
 504         } else if (stroke.getType() == BasicStroke.TYPE_CENTERED) {
 505             return 0.5f;
 506         } else {
 507             return 0f;
 508         }
 509     }
 510 
 511     private static final float FRINGE_FACTOR;
 512     static {
 513         String v = (String) AccessController.doPrivileged(new PrivilegedAction() {
 514             public Object run() {
 515                 return System.getProperty("prism.primshaderpad");
 516             }
 517         });
 518         if (v == null) {
 519             FRINGE_FACTOR = -0.5f;
 520         } else {
 521             FRINGE_FACTOR = -Float.valueOf(v);
 522             System.out.println("Prism ShaderGraphics primitive shader pad = "+FRINGE_FACTOR);
 523         }
 524     }
 525 
 526     private BaseTransform extract3Dremainder(BaseTransform xform) {
 527         if (xform.is2D()) {
 528             return IDENT;
 529         }
 530         TEMP_TX3D.setTransform(xform);
 531         TEMP_TX2D.setTransform(xform.getMxx(), xform.getMyx(),
 532                                xform.getMxy(), xform.getMyy(),
 533                                xform.getMxt(), xform.getMyt());
 534         try {
 535             TEMP_TX2D.invert();
 536             TEMP_TX3D.concatenate(TEMP_TX2D);
 537         } catch (NoninvertibleTransformException ex) {
 538         }
 539         return TEMP_TX3D;
 540     }
 541 
 542     private void renderGeneralRoundedRect(float rx, float ry, float rw, float rh,
 543                                           float arcw, float arch,
 544                                           MaskType type, BasicStroke stroke)
 545     {
 546         // NOTE: using floats here for now, not sure if it's a problem yet...
 547         float bx, by, bw, bh, ifractw, ifracth;
 548         float ox, oy, wdx, wdy, hdx, hdy;
 549         if (stroke == null) {
 550             bx = rx;
 551             by = ry;
 552             bw = rw;
 553             bh = rh;
 554             ifractw = ifracth = 0f;
 555         } else {
 556             float sw = stroke.getLineWidth();
 557             float ow = getStrokeExpansionFactor(stroke) * sw;
 558             bx = rx - ow;
 559             by = ry - ow;
 560             ow *= 2f;
 561             bw = rw + ow;
 562             bh = rh + ow;
 563             if (arcw > 0 && arch > 0) {
 564                 arcw += ow;
 565                 arch += ow;
 566             } else {
 567                 if (stroke.getLineJoin() == BasicStroke.JOIN_ROUND) {
 568                     arcw = arch = ow;
 569                     type = MaskType.DRAW_ROUNDRECT;
 570                 } else {
 571                     arcw = arch = 0f;
 572                 }
 573             }
 574             ifractw = (bw - sw * 2f) / bw;
 575             ifracth = (bh - sw * 2f) / bh;
 576             if (ifractw <= 0f || ifracth <= 0f) {
 577                 type = type.getFillType();
 578             }
 579         }
 580 
 581         // The following is safe; this method does not mutate the transform
 582         BaseTransform xform = getTransformNoClone();
 583         BaseTransform rendertx;
 584         if (isSimpleTranslate) {
 585             wdx = hdy = 1f;
 586             wdy = hdx = 0f;
 587             ox = bx + transX;
 588             oy = by + transY;
 589             rendertx = IDENT;
 590         } else {
 591             rendertx = extract3Dremainder(xform);
 592             wdx = (float)xform.getMxx();
 593             hdx = (float)xform.getMxy();
 594             wdy = (float)xform.getMyx();
 595             hdy = (float)xform.getMyy();
 596             ox = (bx * wdx) + (by * hdx) + (float)xform.getMxt();
 597             oy = (bx * wdy) + (by * hdy) + (float)xform.getMyt();
 598         }
 599 
 600         wdx *= bw;
 601         wdy *= bw;
 602         hdx *= bh;
 603         hdy *= bh;
 604 
 605         float arcfractw = arcw / bw;
 606         float arcfracth = arch / bh;
 607         renderGeneralRoundedPgram(ox, oy, wdx, wdy, hdx, hdy,
 608                                   arcfractw, arcfracth, ifractw, ifracth,
 609                                   rendertx, type, rx, ry, rw, rh);
 610     }
 611 
 612     private void renderGeneralRoundedPgram(float ox, float oy,
 613                                            float wvecx, float wvecy,
 614                                            float hvecx, float hvecy,
 615                                            float arcfractw, float arcfracth,
 616                                            float ifractw, float ifracth,
 617                                            BaseTransform rendertx, MaskType type,
 618                                            float rx, float ry, float rw, float rh)
 619     {
 620         float wlen = len(wvecx, wvecy);
 621         float hlen = len(hvecx, hvecy);
 622         if (wlen == 0 || hlen == 0) {
 623             // parallelogram has collapsed to a line or point
 624             return;
 625         }
 626 
 627         // Calculate the 4 corners of the pgram in device space.
 628         // Note that the UL,UR,LL,LR (Upper/Lower Left/Right) designations
 629         // are virtual since the wdxy and hdxy vectors can point in any
 630         // direction depending on the transform being applied.
 631         float xUL = ox;
 632         float yUL = oy;
 633         float xUR = ox + wvecx;
 634         float yUR = oy + wvecy;
 635         float xLL = ox + hvecx;
 636         float yLL = oy + hvecy;
 637         float xLR = xUR + hvecx;
 638         float yLR = yUR + hvecy;
 639 
 640         // Calculate the unit vectors along each side of the pgram as well
 641         // as the device space perpendicular dimension across the pgram
 642         // (which is different than the lengths of the two pairs of sides
 643         //  since it is measured perpendicular to the "other" sides, not
 644         //  along the original sides).
 645         // The perpendicular dimension is the dot product of the perpendicular
 646         // of the unit vector for one pair of sides with the displacement
 647         // vector for the other pair of sides.
 648         // The unit vector perpendicular to (dx,dy) is given by:
 649         // normx = dx / len(dx, dy);
 650         // normy = dy / len(dx, dy);
 651         // The (ccw) perpendicular vectors would then be:
 652         // unitperpx = +normy = +dy / len;
 653         // unitperpy = -normx = -dx / len;
 654         // Thus the perpendicular width and height distances are:
 655         // pwdist = wvec.uphvec = wvecx * (hvecy/hlen) - wvecy * (hvecx/hlen)
 656         // phdist = hvec.upwvec = hvecx * (wvecy/wlen) - hvecy * (wvecx/wlen)
 657         // If we factor out the divide by the lengths then we are left
 658         // with numerators that are simply negations of each other:
 659         // pwdist = (wvecx * hvecy - wvecy * hvecx) / hlen
 660         // phdist = (hvecx * wvecy - hvecy * wvecx) / wlen
 661         // Finally we multiply by 0.5 since we want the distance to the
 662         // edges of the parallelgram from the center point, not across the
 663         // whole pgram.  And, since we want the absolute value, we can
 664         // ignore the fact that the numerator is negated between the two
 665         // formulas since we will just have to negate one of the 2 values
 666         // afterwards anyway to make them both positive.
 667         // Note that the numerators are the formula for the area of the
 668         // parallelogram (without the abs() operation).  Dividing by the
 669         // length of one of the sides would then give the length of the
 670         // perpendicular across to the other side.
 671         float halfarea = (wvecx * hvecy - wvecy * hvecx) * 0.5f;
 672         float pwdist = halfarea / hlen;
 673         float phdist = halfarea / wlen;
 674         if (pwdist < 0) pwdist = -pwdist;
 675         if (phdist < 0) phdist = -phdist;
 676 
 677         // Now we calculate the normalized unit vectors.
 678         float nwvecx = wvecx / wlen;
 679         float nwvecy = wvecy / wlen;
 680         float nhvecx = hvecx / hlen;
 681         float nhvecy = hvecy / hlen;
 682 
 683         // Bias the parallelogram corner coordinates "outward" by half a
 684         // pixel distance.
 685         // The most general way to do this is to move each parallelogram
 686         // edge outward by a specified distance and then find the new
 687         // intersection point.
 688         // The general form for the intersection of 2 lines is:
 689         // line1 = (x1,y1) -> (x2,y2)
 690         // line2 = (x3,y3) -> (x4,y4)
 691         // t = ((x4-x3)(y1-y3) - (y4-y3)(x1-x3))
 692         //   / ((y4-y3)(x2-x1) - (x4-x3)(y2-y1))
 693         // intersection point = (x1 + t*(x2-x1), y1 + t*(y2-y1))
 694         // Now consider if these lines are displaced versions of 2
 695         // other lines which share a common end point (such as is the
 696         // case of pushing 2 adjacent edges of the pgram outward:
 697         // line1 = (xa+dx1, ya+dy1) -> (xb+dx1, yb+dy1)
 698         // line2 = (xa+dx2, ya+dy2) -> (xc+dx2, yc+dy2)
 699         // "x4-x3" = (xc+dx2) - (xa+dx2) = xc-xa
 700         // "y4-y3" = (yc+dy2) - (ya+dy2) = yc-ya
 701         // "x2-x1" = (xb+dx1) - (xa+dx1) = xb-xa
 702         // "y2-y1" = (yb+dy1) - (ya+dy1) = yb-ya
 703         // "y1-y3" = (y1+dy1) - (y1+dy2) = dy1 - dy2
 704         // "x1-x3" = (xa+dx1) - (xa+dx2) = dx1 - dx2
 705         // t = ((xc-xa)(dy1-dy2) - ((yc-ya)(dx1-dx2))
 706         //   / ((yc-ya)(xb-xa) - (xc-xa)(yb-ya))
 707         // Now we need to displace these 2 lines "outward" by half a
 708         // pixel distance.  We will start by looking at applying a unit
 709         // pixel distance and then cutting the adjustment in half (it
 710         // can be seen to scale linearly when you look at the final
 711         // equations).  This is achieved by applying one of our unit
 712         // vectors with a cw rotation to one line and the other unit
 713         // vector with a ccw rotation to the other line.  Ideally we
 714         // would choose which to apply cw vs. ccw by the way that this
 715         // corner of the pgram is turning, but as it turns out, the
 716         // consequences of getting it backward is that our "t" will
 717         // turn out negative (just negate all of the d[xy][12] values
 718         // in the t equation above and you will see that it changes
 719         // sign.  Since we are calculating the new corner using the
 720         // equation newx = (xa+dx1) + t*(xb-xa), we want the value of
 721         // t that drives the point "away from xb,yb", in other words,
 722         // we want the negative t.  So, if t is positive then we want
 723         // to use negative t and reverse the perpendicular offset we
 724         // applied to xa:
 725         // t < 0 => newx = (xa+dx) + t*(xb-xa) = xa + (dx + t*(xb-xa))
 726         // t > 0 => newx = (xa-dx) - t*(xb-xa) = xa - (dx + t*(xb-xa))
 727         // where (copying t equation from above again):
 728         // t = ((xc-xa)(dy1-dy2) - ((yc-ya)(dx1-dx2))
 729         //   / ((yc-ya)(xb-xa) - (xc-xa)(yb-ya))
 730         // For xa,ya = xUL,yUL and xb,yb = xUR,yUR and xc,yc = xLL,yLL:
 731         // [xy]b - [xy]a = [xy]UR - [xy]UL = wvec
 732         // [xy]c - [xy]a = [xy]LL - [xy]UL = hvec
 733         // dx1,dy1 = +nwvecy, -nwvecx  // ccw on xa->xb
 734         // dx2,dy2 = -nhvecy, +nhvecx  //  cw on xa->xc
 735         // dx1 - dx2 = +nwvecy - -nhvecy = nwvecy + nhvecy
 736         // dy1 - dy2 = -nwvecx - +nhvecx = -(nwvecx + nhvecx)
 737         float num = -hvecx*(nwvecx + nhvecx) - hvecy*(nwvecy + nhvecy);
 738         float den = hvecy*wvecx - hvecx*wvecy;
 739         float t = num / den;
 740         // Negating the sign of t and multiplying by 0.5 gets us the
 741         // proper sign for the offset and cuts it down to half a pixel
 742         float factor = FRINGE_FACTOR * Math.signum(t);
 743         float offx = (t * wvecx + nwvecy) * factor;
 744         float offy = (t * wvecy - nwvecx) * factor;
 745         xUL += offx; yUL += offy;
 746         // x22 is offset by the reverse amounts
 747         xLR -= offx; yLR -= offy;
 748         // For xa,ya = xUR,yUR and xb,yb = xLR,yLR and xc,yc = xUL,yUL
 749         // Note that xa,ya => xc,yc is negative of wvec
 750         // [xy]b - [xy]a = [xy]LR - [xy]UR = +hvec
 751         // [xy]c - [xy]a = [xy]UL - [xy]UR = -wvec
 752         // dx1,dy1                          = +nhvecy, -nhvecx
 753         // dx2,dy2 = -(-nwvecy), +(-nwvecx) = +nwvecy, -nwvecx
 754         // dx1 - dx2                     = nhvecy - nwvecy
 755         // dy1 - dy2 = -nhvecx - -nwvecx = nwvecx - nhvecx
 756         // den = -wvecy * hvecx - -wvecx * hvecy
 757         //     = hvecy * wvecx - hvecx * wvecy (already computed)
 758         // num = -wvecx * (nwvecx - nhvecx) - -wvecy * (nhvecy - nwvecy)
 759         //     = wvecy * (nhvecy - nwvecy) - wvecx * (nwvecx - nhvecx)
 760         num = wvecy * (nhvecy - nwvecy) - wvecx * (nwvecx - nhvecx);
 761         t = num / den;
 762         factor = FRINGE_FACTOR * Math.signum(t);
 763         offx = (t * hvecx + nhvecy) * factor;
 764         offy = (t * hvecy - nhvecx) * factor;
 765         xUR += offx; yUR += offy;
 766         xLL -= offx; yLL -= offy;
 767 
 768         // texture coordinates (uv) will be calculated using a transform
 769         // by the perpendicular unit vectors so that the texture U
 770         // coordinates measure our progress along the pwdist axis (i.e.
 771         // perpendicular to hvec and the texture V coordinates measure
 772         // our progress along phdist (perpendicular to wvec):
 773         // u = x * nhvecy - y * nhvecx;
 774         // v = x * nwvecy - y * nwvecx;
 775 
 776         // We now calculate the uv paramters for the 4 corners such that
 777         //     uv(cx,cy) = 0,0
 778         //     uv(corners - center) = +/-(wlen/2), +/-(hlen/2)
 779         // Note that:
 780         //     uv(corner - center) = uv(corner) - uv(center)
 781         // Calculate the center of the parallelogram and its uv values
 782         float xC = (xUL + xLR) * 0.5f;
 783         float yC = (yUL + yLR) * 0.5f;
 784         float uC = xC * nhvecy - yC * nhvecx;
 785         float vC = xC * nwvecy - yC * nwvecx;
 786         // Now use that to calculate the corner values relative to the center
 787         float uUL = xUL * nhvecy - yUL * nhvecx - uC;
 788         float vUL = xUL * nwvecy - yUL * nwvecx - vC;
 789         float uUR = xUR * nhvecy - yUR * nhvecx - uC;
 790         float vUR = xUR * nwvecy - yUR * nwvecx - vC;
 791         float uLL = xLL * nhvecy - yLL * nhvecx - uC;
 792         float vLL = xLL * nwvecy - yLL * nwvecx - vC;
 793         float uLR = xLR * nhvecy - yLR * nhvecx - uC;
 794         float vLR = xLR * nwvecy - yLR * nwvecx - vC;
 795 
 796         // the pgram params have been calculated in device space, so we use
 797         // identity transform here
 798         if (type == MaskType.DRAW_ROUNDRECT || type == MaskType.FILL_ROUNDRECT) {
 799             float oarcw = pwdist * arcfractw;
 800             float oarch = phdist * arcfracth;
 801             if (oarcw < 0.5 || oarch < 0.5) {
 802                 // The pgram renderer fades the entire primitive if the arc
 803                 // sizes fall below 0.5 pixels since the interiors act as if
 804                 // they are sampled at the center of the indicated circle and
 805                 // radii smaller than that produce less than full coverage
 806                 // even at the circle center.  If the arcwh are less than
 807                 // 0.5 then the difference in area of the corner pixels
 808                 // compared to a PGRAM primitive of the same size is just the
 809                 // tiny corner cutout of area (4-PI)/16 which is less than
 810                 // .06 pixels.  Thus, we can convert the primitive to a
 811                 // PGRAM without any loss of precision.
 812                 type = (type == MaskType.DRAW_ROUNDRECT)
 813                     ? MaskType.DRAW_PGRAM : MaskType.FILL_PGRAM;
 814             } else {
 815                 float flatw = pwdist - oarcw;
 816                 float flath = phdist - oarch;
 817                 float ivalw, ivalh;
 818                 if (type == MaskType.DRAW_ROUNDRECT) {
 819                     float iwdist = pwdist * ifractw;
 820                     float ihdist = phdist * ifracth;
 821                     // First we calculate the inner arc radii and see if they
 822                     // are large enough to render.
 823                     ivalw = iwdist - flatw;
 824                     ivalh = ihdist - flath;
 825                     // As above we need to fix things if we get inner arc
 826                     // radii below half a pixel.  We have a special shader
 827                     // for doing a "semi round" rect which has a round outer
 828                     // shell and a rectangular (pgram) inner shell...
 829                     if (ivalw < 0.5f || ivalh < 0.5f) {
 830                         // inner val is idim
 831                         ivalw = iwdist;
 832                         ivalh = ihdist;
 833                         type = MaskType.DRAW_SEMIROUNDRECT;
 834                     } else {
 835                         // inner val is invarcradii
 836                         ivalw = 1.0f / ivalw;
 837                         ivalh = 1.0f / ivalh;
 838                     }
 839                 } else {
 840                     // Not used by FILL_ROUNDRECT, but we need constant
 841                     // values that will not cause an unnecessary vertex
 842                     // buffer flush in the validateOp below.
 843                     ivalw = ivalh = 0f;
 844                 }
 845                 oarcw = 1.0f / oarcw;
 846                 oarch = 1.0f / oarch;
 847                 Shader shader =
 848                     context.validatePaintOp(this, rendertx, type,
 849                                             rx, ry, rw, rh,
 850                                             oarcw, oarch,
 851                                             ivalw, ivalh, 0, 0);
 852                 shader.setConstant("oinvarcradii", oarcw, oarch);
 853                 if (type == MaskType.DRAW_ROUNDRECT) {
 854                     shader.setConstant("iinvarcradii", ivalw, ivalh);
 855                 } else if (type == MaskType.DRAW_SEMIROUNDRECT) {
 856                     shader.setConstant("idim", ivalw, ivalh);
 857                 }
 858                 pwdist = flatw;
 859                 phdist = flath;
 860             }
 861         } // no else here as we may have converted an RRECT to a PGRAM above
 862         if (type == MaskType.DRAW_PGRAM || type == MaskType.DRAW_ELLIPSE) {
 863             float idimw = pwdist * ifractw;
 864             float idimh = phdist * ifracth;
 865             if (type == MaskType.DRAW_ELLIPSE) {
 866                 if (Math.abs(pwdist - phdist) < .01) {
 867                     type = MaskType.DRAW_CIRCLE;
 868                     // The phdist and idimh parameters will not be used by this
 869                     // shader, but we do need the maximum single pixel coverage
 870                     // so we coopt them to be min(1.0, area):
 871                     phdist = (float) Math.min(1.0, phdist * phdist * Math.PI);
 872                     idimh = (float) Math.min(1.0, idimh * idimh * Math.PI);
 873                 } else {
 874                     // the ellipse drawing shader uses inverted arc dimensions
 875                     // to turn divides into multiplies
 876                     pwdist = 1.0f / pwdist;
 877                     phdist = 1.0f / phdist;
 878                     idimw = 1.0f / idimw;
 879                     idimh = 1.0f / idimh;
 880                 }
 881             }
 882             Shader shader =
 883                 context.validatePaintOp(this, rendertx, type,
 884                                         rx, ry, rw, rh,
 885                                         idimw, idimh, 0f, 0f, 0f, 0f);
 886             shader.setConstant("idim", idimw, idimh);
 887         } else if (type == MaskType.FILL_ELLIPSE) {
 888             if (Math.abs(pwdist - phdist) < .01) {
 889                 type = MaskType.FILL_CIRCLE;
 890                 // The phdist parameter will not be used by this shader,
 891                 // but we do need the maximum single pixel contribution
 892                 // so we coopt the value to be min(1.0, area):
 893                 phdist = (float) Math.min(1.0, phdist * phdist * Math.PI);
 894             } else {
 895                 // the ellipse filling shader uses inverted arc dimensions to
 896                 // turn divides into multiplies:
 897                 pwdist = 1.0f / pwdist;
 898                 phdist = 1.0f / phdist;
 899                 uUL *= pwdist;    vUL *= phdist;
 900                 uUR *= pwdist;    vUR *= phdist;
 901                 uLL *= pwdist;    vLL *= phdist;
 902                 uLR *= pwdist;    vLR *= phdist;
 903             }
 904             context.validatePaintOp(this, rendertx, type, rx, ry, rw, rh);
 905         } else if (type == MaskType.FILL_PGRAM) {
 906             context.validatePaintOp(this, rendertx, type, rx, ry, rw, rh);
 907         }
 908 
 909         context.getVertexBuffer().addMappedPgram(xUL, yUL, xUR, yUR,
 910                                                  xLL, yLL, xLR, yLR,
 911                                                  uUL, vUL, uUR, vUR,
 912                                                  uLL, vLL, uLR, vLR,
 913                                                  pwdist, phdist);
 914     }
 915 
 916     AffineBase getPaintTextureTx(BaseTransform renderTx, Shader shader,
 917                                  float rx, float ry, float rw, float rh)
 918     {
 919         switch (paint.getType()) {
 920             case COLOR:
 921                 return null;
 922             case LINEAR_GRADIENT:
 923                 return PaintHelper.getLinearGradientTx((LinearGradient) paint,
 924                                                        shader, renderTx,
 925                                                        rx, ry, rw, rh);
 926             case RADIAL_GRADIENT:
 927                 return PaintHelper.getRadialGradientTx((RadialGradient) paint,
 928                                                        shader, renderTx,
 929                                                        rx, ry, rw, rh);
 930             case IMAGE_PATTERN:
 931                 return PaintHelper.getImagePatternTx(this, (ImagePattern) paint,
 932                                                      shader, renderTx,
 933                                                      rx, ry, rw, rh);
 934         }
 935         throw new InternalError("Unrecogized paint type: "+paint);
 936     }
 937 
 938     // The second set of rectangular bounds are for validating a
 939     // proportional paint.  They should be identical to the first
 940     // set for a fillRect() operation, but they may be different
 941     // for a vertical or horizontal drawLine() operation.
 942     boolean fillPrimRect(float x, float y, float w, float h,
 943                          Texture rectTex, Texture wrapTex,
 944                          float bx, float by, float bw, float bh)
 945     {
 946         BaseTransform xform = getTransformNoClone();
 947         float mxx = (float) xform.getMxx();
 948         float mxy = (float) xform.getMxy();
 949         float mxt = (float) xform.getMxt();
 950         float myx = (float) xform.getMyx();
 951         float myy = (float) xform.getMyy();
 952         float myt = (float) xform.getMyt();
 953         float dxdist = len(mxx, myx);
 954         float dydist = len(mxy, myy);
 955         if (dxdist == 0.0f || dydist == 0.0f) {
 956             // entire path has collapsed and occupies no area
 957             return true;
 958         }
 959         float pixelw = 1.0f / dxdist;
 960         float pixelh = 1.0f / dydist;
 961         float x0 = x - pixelw * 0.5f;
 962         float y0 = y - pixelh * 0.5f;
 963         float x1 = x + w + pixelw * 0.5f;
 964         float y1 = y + h + pixelh * 0.5f;
 965         int cellw = (int) Math.ceil(w * dxdist - 1.0f/512.0f);
 966         int cellh = (int) Math.ceil(h * dydist - 1.0f/512.0f);
 967         VertexBuffer vb = context.getVertexBuffer();
 968         int max = context.getRectTextureMaxSize();
 969         if (cellw <= max && cellh <= max) {
 970             float u0 = ((cellw * (cellw + 1)) / 2) - 0.5f;
 971             float v0 = ((cellh * (cellh + 1)) / 2) - 0.5f;
 972             float u1 = u0 + cellw + 1.0f;
 973             float v1 = v0 + cellh + 1.0f;
 974             u0 /= rectTex.getPhysicalWidth();
 975             v0 /= rectTex.getPhysicalHeight();
 976             u1 /= rectTex.getPhysicalWidth();
 977             v1 /= rectTex.getPhysicalHeight();
 978             if (xform.isTranslateOrIdentity()) {
 979                 x0 += mxt;
 980                 y0 += myt;
 981                 x1 += mxt;
 982                 y1 += myt;
 983                 xform = IDENT;
 984             } else if (xform.is2D()) {
 985                 Shader shader =
 986                     context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE, rectTex,
 987                                             bx, by, bw, bh);
 988                 AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh);
 989                 if (paintTx == null) {
 990                     vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt,
 991                                       x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt,
 992                                       x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt,
 993                                       x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt,
 994                                       u0, v0, u1, v0, u0, v1, u1, v1, 0, 0);
 995                 } else {
 996                     vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt,
 997                                       x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt,
 998                                       x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt,
 999                                       x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt,
1000                                       u0, v0, u1, v0, u0, v1, u1, v1,
1001                                       x0, y0, x1, y1, paintTx);
1002                 }
1003                 return true;
1004             } else {
1005                 System.out.println("Not a 2d transform!");
1006                 mxt = myt = 0.0f;
1007             }
1008             Shader shader =
1009                 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rectTex,
1010                                         bx, by, bw, bh);
1011             AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh);
1012             if (paintTx == null) {
1013                 vb.addQuad(x0, y0, x1, y1,
1014                            u0, v0, u1, v1);
1015             } else {
1016                 paintTx.translate(-mxt, -myt);
1017                 vb.addQuad(x0, y0, x1, y1,
1018                            u0, v0, u1, v1,
1019                            paintTx);
1020             }
1021             return true;
1022         }
1023         if (wrapTex == null) {
1024             return false;
1025         }
1026         float u0 = 0.5f / wrapTex.getPhysicalWidth();
1027         float v0 = 0.5f / wrapTex.getPhysicalHeight();
1028         float uc = (cellw * 0.5f + 1.0f) / wrapTex.getPhysicalWidth();
1029         float vc = (cellh * 0.5f + 1.0f) / wrapTex.getPhysicalHeight();
1030         float xc = x + w * 0.5f;
1031         float yc = y + h * 0.5f;
1032         if (xform.isTranslateOrIdentity()) {
1033             x0 += mxt;
1034             y0 += myt;
1035             xc += mxt;
1036             yc += myt;
1037             x1 += mxt;
1038             y1 += myt;
1039             xform = IDENT;
1040         } else if (xform.is2D()) {
1041             Shader shader =
1042                 context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE, wrapTex,
1043                                         bx, by, bw, bh);
1044             AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh);
1045             float mxx_x0 = mxx * x0, myx_x0 = myx * x0;
1046             float mxy_y0 = mxy * y0, myy_y0 = myy * y0;
1047             float mxx_xc = mxx * xc, myx_xc = myx * xc;
1048             float mxy_yc = mxy * yc, myy_yc = myy * yc;
1049             float mxx_x1 = mxx * x1, myx_x1 = myx * x1;
1050             float mxy_y1 = mxy * y1, myy_y1 = myy * y1;
1051             // xcc,ycc used in all 4 quads
1052             float xcc = mxx_xc + mxy_yc + mxt;
1053             float ycc = myx_xc + myy_yc + myt;
1054             // xcn, ycn and xnc, ync all used in 2 quads each
1055             float xc0 = mxx_xc + mxy_y0 + mxt;
1056             float yc0 = myx_xc + myy_y0 + myt;
1057             float x0c = mxx_x0 + mxy_yc + mxt;
1058             float y0c = myx_x0 + myy_yc + myt;
1059             float xc1 = mxx_xc + mxy_y1 + mxt;
1060             float yc1 = myx_xc + myy_y1 + myt;
1061             float x1c = mxx_x1 + mxy_yc + mxt;
1062             float y1c = myx_x1 + myy_yc + myt;
1063             // Note that all quads use same 00->c0->0c->cc coordinates for
1064             // the inner and outer uv texture coordinates regardless of the
1065             // reflection of the quad of vertex coordinates
1066 
1067             if (paintTx == null) {
1068                 // quad1 - 00 -> c0 -> 0c -> cc
1069                 vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt,
1070                                            xc0, yc0, x0c, y0c, xcc, ycc,
1071                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc, 0, 0);
1072                 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c)
1073                 vb.addMappedPgram(x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt,
1074                                            xc0, yc0, x1c, y1c, xcc, ycc,
1075                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc, 0, 0);
1076                 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c)
1077                 vb.addMappedPgram(x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt,
1078                                            xc1, yc1, x0c, y0c, xcc, ycc,
1079                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc, 0, 0);
1080                 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c)
1081                 vb.addMappedPgram(x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt,
1082                                            xc1, yc1, x1c, y1c, xcc, ycc,
1083                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc, 0, 0);
1084             } else {
1085                 // quad1 - 00 -> c0 -> 0c -> cc
1086                 vb.addMappedPgram(x0 * mxx + y0 * mxy + mxt, x0 * myx + y0 * myy + myt,
1087                                            xc0, yc0, x0c, y0c, xcc, ycc,
1088                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1089                                   x0, y0, xc, yc, paintTx);
1090                 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c)
1091                 vb.addMappedPgram(x1 * mxx + y0 * mxy + mxt, x1 * myx + y0 * myy + myt,
1092                                            xc0, yc0, x1c, y1c, xcc, ycc,
1093                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1094                                   x1, y0, xc, yc, paintTx);
1095                 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c)
1096                 vb.addMappedPgram(x0 * mxx + y1 * mxy + mxt, x0 * myx + y1 * myy + myt,
1097                                            xc1, yc1, x0c, y0c, xcc, ycc,
1098                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1099                                   x0, y1, xc, yc, paintTx);
1100                 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c)
1101                 vb.addMappedPgram(x1 * mxx + y1 * mxy + mxt, x1 * myx + y1 * myy + myt,
1102                                            xc1, yc1, x1c, y1c, xcc, ycc,
1103                                   u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1104                                   x1, y1, xc, yc, paintTx);
1105             }
1106             return true;
1107         } else {
1108             System.out.println("Not a 2d transform!");
1109             mxt = myt = 0;
1110         }
1111         Shader shader =
1112             context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, wrapTex,
1113                                     bx, by, bw, bh);
1114         AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh);
1115         if (paintTx != null) {
1116             paintTx.translate(-mxt, -myt);
1117         }
1118         vb.addQuad(x0, y0, xc, yc,
1119                    u0, v0, uc, vc,
1120                    paintTx);
1121         vb.addQuad(x1, y0, xc, yc,
1122                    u0, v0, uc, vc,
1123                    paintTx);
1124         vb.addQuad(x0, y1, xc, yc,
1125                    u0, v0, uc, vc,
1126                    paintTx);
1127         vb.addQuad(x1, y1, xc, yc,
1128                    u0, v0, uc, vc,
1129                    paintTx);
1130         return true;
1131     }
1132 
1133     boolean drawPrimRect(float x, float y, float w, float h) {
1134         float lw = stroke.getLineWidth();
1135         float pad = getStrokeExpansionFactor(stroke) * lw;
1136         BaseTransform xform = getTransformNoClone();
1137         float mxx = (float) xform.getMxx();
1138         float mxy = (float) xform.getMxy();
1139         float mxt = (float) xform.getMxt();
1140         float myx = (float) xform.getMyx();
1141         float myy = (float) xform.getMyy();
1142         float myt = (float) xform.getMyt();
1143         float dxdist = len(mxx, myx);
1144         float dydist = len(mxy, myy);
1145         if (dxdist == 0.0f || dydist == 0.0f) {
1146             // entire path has collapsed and occupies no area
1147             return true;
1148         }
1149         float pixelw = 1.0f / dxdist;
1150         float pixelh = 1.0f / dydist;
1151         float x0 = x - pad - pixelw * 0.5f;
1152         float y0 = y - pad - pixelh * 0.5f;
1153         float xc = x + w * 0.5f;
1154         float yc = y + h * 0.5f;
1155         float x1 = x + w + pad + pixelw * 0.5f;
1156         float y1 = y + h + pad + pixelh * 0.5f;
1157         Texture rTex = context.getWrapRectTexture();
1158         float wscale = 1.0f / rTex.getPhysicalWidth();
1159         float hscale = 1.0f / rTex.getPhysicalHeight();
1160         float ou0 = 0.5f * wscale;
1161         float ov0 = 0.5f * hscale;
1162         float ouc = ((w * 0.5f + pad) * dxdist + 1.0f) * wscale;
1163         float ovc = ((h * 0.5f + pad) * dydist + 1.0f) * hscale;
1164         float offsetx = lw * dxdist * wscale;
1165         float offsety = lw * dydist * hscale;
1166         VertexBuffer vb = context.getVertexBuffer();
1167         if (xform.isTranslateOrIdentity()) {
1168             x0 += mxt;
1169             y0 += myt;
1170             xc += mxt;
1171             yc += myt;
1172             x1 += mxt;
1173             y1 += myt;
1174             xform = IDENT;
1175         } else if (xform.is2D()) {
1176             Shader shader =
1177                 context.validatePaintOp(this, IDENT, MaskType.ALPHA_TEXTURE_DIFF,
1178                                         rTex, x, y, w, h,
1179                                         offsetx, offsety, 0, 0, 0, 0);
1180             shader.setConstant("innerOffset", offsetx, offsety);
1181             AffineBase paintTx = getPaintTextureTx(IDENT, shader, x, y, w, h);
1182             float mxx_x0 = mxx * x0, myx_x0 = myx * x0;
1183             float mxy_y0 = mxy * y0, myy_y0 = myy * y0;
1184             float mxx_xc = mxx * xc, myx_xc = myx * xc;
1185             float mxy_yc = mxy * yc, myy_yc = myy * yc;
1186             float mxx_x1 = mxx * x1, myx_x1 = myx * x1;
1187             float mxy_y1 = mxy * y1, myy_y1 = myy * y1;
1188 
1189             // xcc,ycc used in all 4 quads
1190             float xcc = mxx_xc + mxy_yc + mxt;
1191             float ycc = myx_xc + myy_yc + myt;
1192             // xcn, ycn and xnc, ync all used in 2 quads each
1193             float xc0 = mxx_xc + mxy_y0 + mxt;
1194             float yc0 = myx_xc + myy_y0 + myt;
1195             float x0c = mxx_x0 + mxy_yc + mxt;
1196             float y0c = myx_x0 + myy_yc + myt;
1197             float xc1 = mxx_xc + mxy_y1 + mxt;
1198             float yc1 = myx_xc + myy_y1 + myt;
1199             float x1c = mxx_x1 + mxy_yc + mxt;
1200             float y1c = myx_x1 + myy_yc + myt;
1201             // Note that all quads use same 00->c0->0c->cc coordinates for
1202             // the inner and outer uv texture coordinates regardless of the
1203             // reflection of the quad of vertex coordinates
1204 
1205             if (paintTx == null) {
1206                 // quad1 - 00 -> c0 -> 0c -> cc
1207                 vb.addMappedPgram(mxx_x0 + mxy_y0 + mxt, myx_x0 + myy_y0 + myt,
1208                                             xc0, yc0, x0c, y0c, xcc, ycc,
1209                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1210                                   0, 0);
1211                 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c)
1212                 vb.addMappedPgram(mxx_x1 + mxy_y0 + mxt, myx_x1 + myy_y0 + myt,
1213                                             xc0, yc0, x1c, y1c, xcc, ycc,
1214                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1215                                   0, 0);
1216                 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c)
1217                 vb.addMappedPgram(mxx_x0 + mxy_y1 + mxt, myx_x0 + myy_y1 + myt,
1218                                             xc1, yc1, x0c, y0c, xcc, ycc,
1219                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1220                                   0, 0);
1221                 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c)
1222                 vb.addMappedPgram(mxx_x1 + mxy_y1 + mxt, myx_x1 + myy_y1 + myt,
1223                                             xc1, yc1, x1c, y1c, xcc, ycc,
1224                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1225                                   0, 0);
1226             } else {
1227                 // quad1 - 00 -> c0 -> 0c -> cc
1228                 vb.addMappedPgram(mxx_x0 + mxy_y0 + mxt, myx_x0 + myy_y0 + myt,
1229                                             xc0, yc0, x0c, y0c, xcc, ycc,
1230                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1231                                   x0, y0, xc, yc, paintTx);
1232                 // quad2 - 10 -> c0 -> 1c -> cc (reflect quad1 around x=c)
1233                 vb.addMappedPgram(mxx_x1 + mxy_y0 + mxt, myx_x1 + myy_y0 + myt,
1234                                             xc0, yc0, x1c, y1c, xcc, ycc,
1235                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1236                                   x1, y0, xc, yc, paintTx);
1237                 // quad3 - 01 -> c1 -> 0c -> cc (reflect quad1 around y=c)
1238                 vb.addMappedPgram(mxx_x0 + mxy_y1 + mxt, myx_x0 + myy_y1 + myt,
1239                                             xc1, yc1, x0c, y0c, xcc, ycc,
1240                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1241                                   x0, y1, xc, yc, paintTx);
1242                 // quad4 - 11 -> c1 -> 1c -> cc (reflect quad1 around x=c and y=c)
1243                 vb.addMappedPgram(mxx_x1 + mxy_y1 + mxt, myx_x1 + myy_y1 + myt,
1244                                             xc1, yc1, x1c, y1c, xcc, ycc,
1245                                   ou0, ov0, ouc, ov0, ou0, ovc, ouc, ovc,
1246                                   x1, y1, xc, yc, paintTx);
1247             }
1248             rTex.unlock();
1249             return true;
1250         } else {
1251             System.out.println("Not a 2d transform!");
1252             mxt = myt = 0.0f;
1253         }
1254         Shader shader =
1255             context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE_DIFF,
1256                                     rTex, x, y, w, h,
1257                                     offsetx, offsety, 0, 0, 0, 0);
1258         shader.setConstant("innerOffset", offsetx, offsety);
1259         AffineBase paintTx = getPaintTextureTx(IDENT, shader, x, y, w, h);
1260         if (paintTx != null) {
1261             paintTx.translate(-mxt, -myt);
1262         }
1263         vb.addQuad( x0,  y0,  xc,  yc,
1264                    ou0, ov0, ouc, ovc,
1265                    paintTx);
1266         vb.addQuad( x1,  y0,  xc,  yc,
1267                    ou0, ov0, ouc, ovc,
1268                    paintTx);
1269         vb.addQuad( x0,  y1,  xc,  yc,
1270                    ou0, ov0, ouc, ovc,
1271                    paintTx);
1272         vb.addQuad( x1,  y1,  xc,  yc,
1273                    ou0, ov0, ouc, ovc,
1274                    paintTx);
1275         rTex.unlock();
1276         return true;
1277     }
1278 
1279     boolean drawPrimDiagonal(float x1, float y1, float x2, float y2,
1280                              float lw, int cap,
1281                              float bx, float by, float bw, float bh)
1282     {
1283         // assert x1 != x2 && y1 != y2, otherwise caller would have
1284         // vectored us to fillPrimRect()
1285         if (stroke.getType() == BasicStroke.TYPE_CENTERED) {
1286             lw *= 0.5f;
1287         }
1288         float dx = x2 - x1;
1289         float dy = y2 - y1;
1290         float len = len(dx, dy);
1291         dx /= len;
1292         dy /= len;
1293         float ldx = dx * lw;
1294         float ldy = dy * lw;
1295         // First expand perpendicularly using (ldy, -ldx)
1296         float xUL = x1 + ldy,  yUL = y1 - ldx;
1297         float xUR = x2 + ldy,  yUR = y2 - ldx;
1298         float xLL = x1 - ldy,  yLL = y1 + ldx;
1299         float xLR = x2 - ldy,  yLR = y2 + ldx;
1300         if (cap == BasicStroke.CAP_SQUARE) {
1301             // Then add SQUARE end caps using (ldx, ldy) if needed
1302             xUL -= ldx;  yUL -= ldy;
1303             xLL -= ldx;  yLL -= ldy;
1304             xUR += ldx;  yUR += ldy;
1305             xLR += ldx;  yLR += ldy;
1306         }
1307 
1308         float hdx, hdy, vdx, vdy;
1309         int cellw, cellh;
1310         BaseTransform xform = getTransformNoClone();
1311         float mxt = (float) xform.getMxt();
1312         float myt = (float) xform.getMyt();
1313         if (xform.isTranslateOrIdentity()) {
1314             hdx = dx;  hdy =  dy;
1315             vdx = dy;  vdy = -dx;
1316             cellw = (int) Math.ceil(len(xUR - xUL, yUR - yUL));
1317             cellh = (int) Math.ceil(len(xLL - xUL, yLL - yUL));
1318             xform = IDENT;
1319         } else if (xform.is2D()) {
1320             float mxx = (float) xform.getMxx();
1321             float mxy = (float) xform.getMxy();
1322             float myx = (float) xform.getMyx();
1323             float myy = (float) xform.getMyy();
1324             float tx, ty;
1325             tx = mxx * xUL + mxy * yUL;
1326             ty = myx * xUL + myy * yUL;
1327             xUL = tx;  yUL = ty;
1328             tx = mxx * xUR + mxy * yUR;
1329             ty = myx * xUR + myy * yUR;
1330             xUR = tx;  yUR = ty;
1331             tx = mxx * xLL + mxy * yLL;
1332             ty = myx * xLL + myy * yLL;
1333             xLL = tx;  yLL = ty;
1334             tx = mxx * xLR + mxy * yLR;
1335             ty = myx * xLR + myy * yLR;
1336             xLR = tx;  yLR = ty;
1337             // hdx, hdy are transformed unit vectors along the line
1338             hdx = mxx * dx + mxy * dy;
1339             hdy = myx * dx + myy * dy;
1340             float dlen = len(hdx, hdy);
1341             if (dlen == 0.0f) return true;
1342             hdx /= dlen;
1343             hdy /= dlen;
1344             // vdx, vdy are transformed perpendicular unit vectors
1345             // (perpendicular to the line in user space, but then transformed)
1346             vdx = mxx * dy - mxy * dx;
1347             vdy = myx * dy - myy * dx;
1348             dlen = len(vdx, vdy);
1349             if (dlen == 0.0f) return true;
1350             vdx /= dlen;
1351             vdy /= dlen;
1352             cellw = (int) Math.ceil(Math.abs((xUR - xUL) * hdx + (yUR - yUL) * hdy));
1353             cellh = (int) Math.ceil(Math.abs((xLL - xUL) * vdx + (yLL - yUL) * vdy));
1354             xform = IDENT;
1355         } else {
1356             System.out.println("Not a 2d transform!");
1357             return false;
1358         }
1359         hdx *= 0.5f;
1360         hdy *= 0.5f;
1361         vdx *= 0.5f;
1362         vdy *= 0.5f;
1363         xUL = xUL + mxt + vdx - hdx;
1364         yUL = yUL + myt + vdy - hdy;
1365         xUR = xUR + mxt + vdx + hdx;
1366         yUR = yUR + myt + vdy + hdy;
1367         xLL = xLL + mxt - vdx - hdx;
1368         yLL = yLL + myt - vdy - hdy;
1369         xLR = xLR + mxt - vdx + hdx;
1370         yLR = yLR + myt - vdy + hdy;
1371         VertexBuffer vb = context.getVertexBuffer();
1372         int cellmax = context.getRectTextureMaxSize();
1373         if (cellh <= cellmax) {
1374             float v0 = ((cellh * (cellh + 1)) / 2) - 0.5f;
1375             float v1 = v0 + cellh + 1.0f;
1376             Texture rTex = context.getRectTexture();
1377             v0 /= rTex.getPhysicalHeight();
1378             v1 /= rTex.getPhysicalHeight();
1379             if (cellw <= cellmax) {
1380                 float u0 = ((cellw * (cellw + 1)) / 2) - 0.5f;
1381                 float u1 = u0 + cellw + 1.0f;
1382                 u0 /= rTex.getPhysicalWidth();
1383                 u1 /= rTex.getPhysicalWidth();
1384                 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex,
1385                                         bx, by, bw, bh);
1386                 vb.addMappedPgram(xUL, yUL, xUR, yUR, xLL, yLL, xLR, yLR,
1387                                    u0,  v0,  u1,  v0,  u0,  v1,  u1,  v1,
1388                                   0, 0);
1389 //                System.out.print("1"); System.out.flush();
1390                 rTex.unlock();
1391                 return true;
1392             }
1393             // long thin line (cellw is along the line, cellh is across it)
1394             if (cellw <= cellmax * 2 - 1) {
1395                 // 2-slice the line at its midpoint.
1396                 // use the cellmax,cellh cell for maximum coverage of each half
1397                 // we can use at most (cellmax-0.5) per half so that we do not
1398                 // see the antialias drop off on the last half pixel of the far
1399                 // end of the cell.  This lets us support a line of "length" up
1400                 // to (cellmax-0.5 + cellmax-0.5) or (cellmax*2-1).
1401                 float xUC = (xUL + xUR) * 0.5f;
1402                 float yUC = (yUL + yUR) * 0.5f;
1403                 float xLC = (xLL + xLR) * 0.5f;
1404                 float yLC = (yLL + yLR) * 0.5f;
1405                 float u0 = ((cellmax * (cellmax + 1)) / 2) - 0.5f;
1406                 float u1 = u0 + 0.5f + cellw * 0.5f;
1407                 u0 /= rTex.getPhysicalWidth();
1408                 u1 /= rTex.getPhysicalWidth();
1409                 context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex,
1410                                         bx, by, bw, bh);
1411                 // first half of line x1,y1 -> midpoint
1412                 vb.addMappedPgram(xUL, yUL, xUC, yUC, xLL, yLL, xLC, yLC,
1413                                    u0,  v0,  u1,  v0,  u0,  v1,  u1,  v1,
1414                                   0, 0);
1415                 // second half of line midpoint -> x2,y2
1416                 vb.addMappedPgram(xUR, yUR, xUC, yUC, xLR, yLR, xLC, yLC,
1417                                    u0,  v0,  u1,  v0,  u0,  v1,  u1,  v1,
1418                                   0, 0);
1419 //                System.out.print("2"); System.out.flush();
1420                 rTex.unlock();
1421                 return true;
1422             }
1423             // Finally, 3-slice the line (left edge, huge middle, right edge)
1424             float u0 = 0.5f / rTex.getPhysicalWidth();
1425             float u1 = 1.5f / rTex.getPhysicalWidth();
1426             // The lower case L or R indicates "inner left" or "inner right"
1427             hdx *= 2.0f;
1428             hdy *= 2.0f;
1429             float xUl = xUL + hdx;
1430             float yUl = yUL + hdy;
1431             float xUr = xUR - hdx;
1432             float yUr = yUR - hdy;
1433             float xLl = xLL + hdx;
1434             float yLl = yLL + hdy;
1435             float xLr = xLR - hdx;
1436             float yLr = yLR - hdy;
1437             context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex,
1438                                     bx, by, bw, bh);
1439             // first pixel of line x1,y1 -> x1,y1+pixel
1440             vb.addMappedPgram(xUL, yUL, xUl, yUl, xLL, yLL, xLl, yLl,
1441                                u0,  v0,  u1,  v0,  u0,  v1,  u1,  v1,
1442                               0, 0);
1443             // middle part of line x1,y1+pixel -> x2,y2-pixel
1444             vb.addMappedPgram(xUl, yUl, xUr, yUr, xLl, yLl, xLr, yLr,
1445                                u1,  v0,  u1,  v0,  u1,  v1,  u1,  v1,
1446                               0, 0);
1447             // last part of line x2,y2-pixel -> x2,y2
1448             vb.addMappedPgram(xUr, yUr, xUR, yUR, xLr, yLr, xLR, yLR,
1449                                u1,  v0,  u0,  v0,  u1,  v1,  u0,  v1,
1450                               0, 0);
1451 //            System.out.print("3"); System.out.flush();
1452             rTex.unlock();
1453             return true;
1454         }
1455         // we could 2 and 3 slice extremely wide short lines, but they
1456         // are very rare in practice so we just jump straight to a
1457         // standard 4-slice off of the wrap-rect texture
1458         float xUC = (xUL + xUR) * 0.5f;
1459         float yUC = (yUL + yUR) * 0.5f;
1460         float xLC = (xLL + xLR) * 0.5f;
1461         float yLC = (yLL + yLR) * 0.5f;
1462         float xCL = (xUL + xLL) * 0.5f;
1463         float yCL = (yUL + yLL) * 0.5f;
1464         float xCR = (xUR + xLR) * 0.5f;
1465         float yCR = (yUR + yLR) * 0.5f;
1466         float xCC = (xUC + xLC) * 0.5f;
1467         float yCC = (yUC + yLC) * 0.5f;
1468         Texture rTex = context.getWrapRectTexture();
1469         float u0 = 0.5f / rTex.getPhysicalWidth();
1470         float v0 = 0.5f / rTex.getPhysicalHeight();
1471         float uc = (cellw * 0.5f + 1.0f) / rTex.getPhysicalWidth();
1472         float vc = (cellh * 0.5f + 1.0f) / rTex.getPhysicalHeight();
1473         context.validatePaintOp(this, xform, MaskType.ALPHA_TEXTURE, rTex,
1474                                 bx, by, bw, bh);
1475         vb.addMappedPgram(xUL, yUL, xUC, yUC, xCL, yCL, xCC, yCC,
1476                             u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1477                             0, 0);
1478         vb.addMappedPgram(xUR, yUR, xUC, yUC, xCR, yCR, xCC, yCC,
1479                             u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1480                             0, 0);
1481         vb.addMappedPgram(xLL, yLL, xLC, yLC, xCL, yCL, xCC, yCC,
1482                             u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1483                             0, 0);
1484         vb.addMappedPgram(xLR, yLR, xLC, yLC, xCR, yCR, xCC, yCC,
1485                             u0,  v0,  uc,  v0,  u0,  vc,  uc,  vc,
1486                             0, 0);
1487 //        System.out.print("4"); System.out.flush();
1488         rTex.unlock();
1489         return true;
1490     }
1491 
1492     public void fillRect(float x, float y, float w, float h) {
1493         if (w <= 0 || h <= 0) {
1494             return;
1495         }
1496         if (isComplexPaint) {
1497             scratchRRect.setRoundRect(x, y, w, h, 0, 0);
1498             renderWithComplexPaint(scratchRRect, null, x, y, w, h);
1499             return;
1500         }
1501         if (PrismSettings.primTextureSize != 0) {
1502             Texture rTex = context.getRectTexture();
1503             Texture wTex = context.getWrapRectTexture();
1504             boolean success = fillPrimRect(x, y, w, h, rTex, wTex, x, y, w, h);
1505             rTex.unlock();
1506             wTex.unlock();
1507             if (success) return;
1508         }
1509         renderGeneralRoundedRect(x, y, w, h, 0f, 0f,
1510                                  MaskType.FILL_PGRAM, null);
1511     }
1512 
1513     public void fillEllipse(float x, float y, float w, float h) {
1514         if (w <= 0 || h <= 0) {
1515             return;
1516         }
1517         if (isComplexPaint) {
1518             scratchEllipse.setFrame(x, y, w, h);
1519             renderWithComplexPaint(scratchEllipse, null, x, y, w, h);
1520             return;
1521         }
1522         if (PrismSettings.primTextureSize != 0) {
1523             if (fillPrimRect(x, y, w, h,
1524                              context.getOvalTexture(),
1525                              null,
1526                              x, y, w, h))
1527             {
1528                 return;
1529             }
1530         }
1531         renderGeneralRoundedRect(x, y, w, h, w, h,
1532                                  MaskType.FILL_ELLIPSE, null);
1533     }
1534 
1535     public void fillRoundRect(float x, float y, float w, float h,
1536                               float arcw, float arch)
1537     {
1538         arcw = Math.min(Math.abs(arcw), w);
1539         arch = Math.min(Math.abs(arch), h);
1540 
1541         if (w <= 0 || h <= 0) {
1542             return;
1543         }
1544         if (isComplexPaint) {
1545             scratchRRect.setRoundRect(x, y, w, h, arcw, arch);
1546             renderWithComplexPaint(scratchRRect, null, x, y, w, h);
1547             return;
1548         }
1549         renderGeneralRoundedRect(x, y, w, h, arcw, arch,
1550                                  MaskType.FILL_ROUNDRECT, null);
1551     }
1552 
1553     public void fillQuad(float x1, float y1, float x2, float y2) {
1554         float bx, by, bw, bh;
1555         if (x1 <= x2) {
1556             bx = x1;
1557             bw = x2 - x1;
1558         } else {
1559             bx = x2;
1560             bw = x1 - x2;
1561         }
1562         if (y1 <= y2) {
1563             by = y1;
1564             bh = y2 - y1;
1565         } else {
1566             by = y2;
1567             bh = y1 - y2;
1568         }
1569 
1570         if (isComplexPaint) {
1571             scratchRRect.setRoundRect(bx, by, bw, bh, 0, 0);
1572             renderWithComplexPaint(scratchRRect, null, bx, by, bw, bh);
1573             return;
1574         }
1575 
1576         BaseTransform xform = getTransformNoClone();
1577         if (PrismSettings.primTextureSize != 0) {
1578             float mxt, myt;
1579             if (xform.isTranslateOrIdentity()) {
1580                 mxt = (float) xform.getMxt();
1581                 myt = (float) xform.getMyt();
1582                 xform = IDENT;
1583                 x1 += mxt;
1584                 y1 += myt;
1585                 x2 += mxt;
1586                 y2 += myt;
1587             } else {
1588                 mxt = myt = 0.0f;
1589             }
1590             Shader shader =
1591                 context.validatePaintOp(this, xform, MaskType.ALPHA_ONE, null,
1592                                         bx, by, bw, bh);
1593             AffineBase paintTx = getPaintTextureTx(IDENT, shader, bx, by, bw, bh);
1594             if (paintTx != null) {
1595                 paintTx.translate(-mxt, -myt);
1596             }
1597             context.getVertexBuffer().addQuad(x1, y1, x2, y2, 0, 0, 0, 0, paintTx);
1598             return;
1599         }
1600         if (isSimpleTranslate) {
1601             xform = IDENT;
1602             bx += transX;
1603             by += transY;
1604         }
1605         context.validatePaintOp(this, xform, MaskType.SOLID, bx, by, bw, bh);
1606 
1607         VertexBuffer vb = context.getVertexBuffer();
1608         vb.addQuad(bx, by, bx+bw, by+bh);
1609     }
1610 
1611     @Override
1612     public void fillTriangles(VertexBuffer tris, int numVerts,
1613                               float bx, float by, float bw, float bh)
1614     {
1615         if (isComplexPaint) {
1616             throw new AssertionError("fillTriangles() not supported for complex paints");
1617         }
1618 
1619         BaseTransform xform = getTransformNoClone();
1620         context.validatePaintOp(this, xform, MaskType.SOLID, bx, by, bw, bh);
1621 
1622         VertexBuffer vb = context.getVertexBuffer();
1623         vb.addVerts(tris, numVerts);
1624     }
1625 
1626     void fillCubicCurves(VertexBuffer tris, int numVerts,
1627                          float bx, float by, float bw, float bh)
1628     {
1629         if (isComplexPaint) {
1630             throw new AssertionError("fillCubicCurves() not supported for complex paints");
1631         }
1632 
1633         BaseTransform xform = getTransformNoClone();
1634         context.validatePaintOp(this, xform, MaskType.FILL_CUBICCURVE, bx, by, bw, bh);
1635 
1636         VertexBuffer vb = context.getVertexBuffer();
1637         vb.addVerts(tris, numVerts);
1638     }
1639 
1640     private static final double SQRT_2 = Math.sqrt(2.0);
1641     private static boolean canUseStrokeShader(BasicStroke bs) {
1642         // RT-27378
1643         // TODO: Expand the cases that renderGeneralRoundRect() can handle...
1644         return (!bs.isDashed() &&
1645                 (bs.getType() == BasicStroke.TYPE_INNER ||
1646                  bs.getLineJoin() == BasicStroke.JOIN_ROUND ||
1647                  (bs.getLineJoin() == BasicStroke.JOIN_MITER &&
1648                   bs.getMiterLimit() >= SQRT_2)));
1649     }
1650 
1651     public void blit(RTTexture srcTex, RTTexture dstTex,
1652                      int srcX0, int srcY0, int srcX1, int srcY1,
1653                      int dstX0, int dstY0, int dstX1, int dstY1) {
1654         if (dstTex == null) {
1655             context.setRenderTarget(this);
1656         } else {
1657             context.setRenderTarget((BaseGraphics)dstTex.createGraphics());
1658         }
1659         context.blit(srcTex, dstTex, srcX0, srcY0, srcX1, srcY1,
1660                 dstX0, dstY0, dstX1, dstY1);
1661     }
1662 
1663     public void drawRect(float x, float y, float w, float h) {
1664         if (w < 0 || h < 0) {
1665             return;
1666         }
1667         if (w == 0 || h == 0) {
1668             drawLine(x, y, x + w, y + h);
1669             return;
1670         }
1671         if (isComplexPaint) {
1672             scratchRRect.setRoundRect(x, y, w, h, 0, 0);
1673             renderWithComplexPaint(scratchRRect, stroke, x, y, w, h);
1674             return;
1675         }
1676         if (canUseStrokeShader(stroke)) {
1677             if (PrismSettings.primTextureSize != 0 &&
1678                 stroke.getLineJoin() != BasicStroke.CAP_ROUND)
1679             {
1680                 if (drawPrimRect(x, y, w, h)) {
1681                     return;
1682                 }
1683             }
1684             renderGeneralRoundedRect(x, y, w, h, 0f, 0f,
1685                                      MaskType.DRAW_PGRAM, stroke);
1686             return;
1687         }
1688         scratchRRect.setRoundRect(x, y, w, h, 0, 0);
1689         renderShape(scratchRRect, stroke, x, y, w, h);
1690     }
1691 
1692     private boolean checkInnerCurvature(float arcw, float arch) {
1693         // Test to see if inner ellipse satisfies (flattening < 0.5)
1694         // otherwise it will not be approximated by a "parallel ellipse"
1695         // very well and we should just use shape rendering.
1696 
1697         // RT-27378
1698         // TODO: Implement better "distance to ellipse" formulas in the shaders
1699         float inset = stroke.getLineWidth() *
1700             (1f - getStrokeExpansionFactor(stroke));
1701         arcw -= inset;
1702         arch -= inset;
1703         // Note that if either inset arcw,h go to <= 0 then we will invoke the
1704         // fill primitive for ellipse, or the round rect primitive will
1705         // invoke its "tiny inner corner" fixes and we will be safe
1706         return (arcw <= 0 || arch <= 0 ||
1707                 (arcw * 2f > arch && arch * 2f > arcw));
1708     }
1709 
1710     public void drawEllipse(float x, float y, float w, float h) {
1711         if (w < 0 || h < 0) {
1712             return;
1713         }
1714         if (!isComplexPaint && !stroke.isDashed() &&
1715             checkInnerCurvature(w, h))
1716         {
1717             renderGeneralRoundedRect(x, y, w, h, w, h,
1718                                      MaskType.DRAW_ELLIPSE, stroke);
1719             return;
1720         }
1721         scratchEllipse.setFrame(x, y, w, h);
1722         renderShape(scratchEllipse, stroke, x, y, w, h);
1723     }
1724 
1725     public void drawRoundRect(float x, float y, float w, float h,
1726                               float arcw, float arch)
1727     {
1728         arcw = Math.min(Math.abs(arcw), w);
1729         arch = Math.min(Math.abs(arch), h);
1730 
1731         if (w < 0 || h < 0) {
1732             return;
1733         }
1734         if (!isComplexPaint && !stroke.isDashed() &&
1735             checkInnerCurvature(arcw, arch))
1736         {
1737             renderGeneralRoundedRect(x, y, w, h, arcw, arch,
1738                                      MaskType.DRAW_ROUNDRECT, stroke);
1739             return;
1740         }
1741         scratchRRect.setRoundRect(x, y, w, h, arcw, arch);
1742         renderShape(scratchRRect, stroke, x, y, w, h);
1743     }
1744 
1745     public void drawLine(float x1, float y1, float x2, float y2) {
1746         float bx, by, bw, bh;
1747         if (x1 <= x2) {
1748             bx = x1;
1749             bw = x2 - x1;
1750         } else {
1751             bx = x2;
1752             bw = x1 - x2;
1753         }
1754         if (y1 <= y2) {
1755             by = y1;
1756             bh = y2 - y1;
1757         } else {
1758             by = y2;
1759             bh = y1 - y2;
1760         }
1761 
1762         // RT-27378
1763         // TODO: casting down to floats everywhere here; evaluate later
1764         // to see if this is enough precision...
1765         // TODO: stroke normalization control?
1766         if (stroke.getType() == BasicStroke.TYPE_INNER) {
1767             return;
1768         }
1769         if (isComplexPaint) {
1770             scratchLine.setLine(x1, y1, x2, y2);
1771             renderWithComplexPaint(scratchLine, stroke, bx, by, bw, bh);
1772             return;
1773         }
1774         int cap = stroke.getEndCap();
1775         if (stroke.isDashed()) {
1776             // NOTE: we could construct the GeneralPath directly
1777             // for CAP_ROUND and save a lot of processing in that case...
1778             // And again, we would need to deal with dropout control...
1779             scratchLine.setLine(x1, y1, x2, y2);
1780             renderShape(scratchLine, stroke, bx, by, bw, bh);
1781             return;
1782         }
1783         float lw = stroke.getLineWidth();
1784         if (PrismSettings.primTextureSize != 0 &&
1785             cap != BasicStroke.CAP_ROUND)
1786         {
1787             float pad = lw;
1788             if (stroke.getType() == BasicStroke.TYPE_CENTERED) {
1789                 pad *= 0.5f;
1790             }
1791             if (bw == 0.0f || bh == 0.0f) {
1792                 float padx, pady;
1793                 if (cap == BasicStroke.CAP_SQUARE) {
1794                     // CAP_SQUARE pads the same on all sides
1795                     padx = pady = pad;
1796 //                    System.out.print("S"); System.out.flush();
1797                 } else if (bw != 0.0f) {
1798                     // Horizontal CAP_BUTT line - widen vertically
1799                     padx = 0.0f;
1800                     pady = pad;
1801 //                    System.out.print("H"); System.out.flush();
1802                 } else if (bh != 0.0f) {
1803                     // Vertical CAP_BUTT line - widen horizontally
1804                     padx = pad;
1805                     pady = 0.0f;
1806 //                    System.out.print("V"); System.out.flush();
1807                 } else {
1808 //                    System.out.print("0"); System.out.flush();
1809                     // Zero length line - NOP for CAP_BUTT
1810                     return;
1811                 }
1812                 Texture rTex = context.getRectTexture();
1813                 Texture wTex = context.getWrapRectTexture();
1814                 boolean success = fillPrimRect(bx - padx,        by - pady,
1815                                                bw + padx + padx, bh + pady + pady,
1816                                                rTex, wTex, bx, by, bw, bh);
1817                 rTex.unlock();
1818                 wTex.unlock();
1819                 if (success) return;
1820             } else {
1821                 if (drawPrimDiagonal(x1, y1, x2, y2, lw, cap,
1822                                      bx, by, bw, bh))
1823                 {
1824                     return;
1825                 }
1826             }
1827         }
1828 //        System.out.print("#"); System.out.flush();
1829         if (stroke.getType() == BasicStroke.TYPE_OUTER) {
1830             lw *= 2f;
1831         }
1832         float dx = x2 - x1;
1833         float dy = y2 - y1;
1834         float len = len(dx, dy);
1835         float ldx, ldy;  // lw length vector in direction of line in user space
1836         if (len == 0) {
1837             if (cap == BasicStroke.CAP_BUTT) {
1838                 return;
1839             }
1840             ldx = lw;
1841             ldy = 0;
1842         } else {
1843             ldx = lw * dx / len;
1844             ldy = lw * dy / len;
1845         }
1846         // The following is safe; this method does not mutate the transform
1847         BaseTransform xform = getTransformNoClone();
1848         BaseTransform rendertx;
1849         float pdx, pdy;  // ldx,ldy rotated 90 in user space then transformed
1850         if (isSimpleTranslate) {
1851             double tx = xform.getMxt();
1852             double ty = xform.getMyt();
1853             x1 += tx;
1854             y1 += ty;
1855             x2 += tx;
1856             y2 += ty;
1857             pdx = ldy;
1858             pdy = -ldx;
1859             rendertx = IDENT;
1860         } else {
1861             rendertx = extract3Dremainder(xform);
1862             double coords[] = {x1, y1, x2, y2};
1863             xform.transform(coords, 0, coords, 0, 2);
1864             x1 = (float)coords[0];
1865             y1 = (float)coords[1];
1866             x2 = (float)coords[2];
1867             y2 = (float)coords[3];
1868             dx = x2 - x1;
1869             dy = y2 - y1;
1870             coords[0] = ldx;
1871             coords[1] = ldy;
1872             coords[2] = ldy;
1873             coords[3] = -ldx;
1874             xform.deltaTransform(coords, 0, coords, 0, 2);
1875             ldx = (float) coords[0];
1876             ldy = (float) coords[1];
1877             pdx = (float) coords[2];
1878             pdy = (float) coords[3];
1879         }
1880         float px = x1 - pdx / 2f;
1881         float py = y1 - pdy / 2f;
1882         float arcfractw, arcfracth;
1883         MaskType type;
1884         if (cap != BasicStroke.CAP_BUTT) {
1885             px -= ldx / 2f;
1886             py -= ldy / 2f;
1887             dx += ldx;
1888             dy += ldy;
1889             if (cap == BasicStroke.CAP_ROUND) {
1890                 arcfractw = len(ldx, ldy) / len(dx, dy);
1891                 arcfracth = 1f;
1892                 type = MaskType.FILL_ROUNDRECT;
1893             } else {
1894                 arcfractw = arcfracth = 0f;
1895                 type = MaskType.FILL_PGRAM;
1896             }
1897         } else {
1898             arcfractw = arcfracth = 0f;
1899             type = MaskType.FILL_PGRAM;
1900         }
1901         renderGeneralRoundedPgram(px, py, dx, dy, pdx, pdy,
1902                                   arcfractw, arcfracth, 0f, 0f,
1903                                   rendertx, type,
1904                                   bx, by, bw, bh);
1905     }
1906 
1907     private static float len(float x, float y) {
1908         return ((x == 0f) ? Math.abs(y)
1909                 : ((y == 0f) ? Math.abs(x)
1910                    : (float)Math.sqrt(x * x + y * y)));
1911     }
1912 
1913     private boolean lcdSampleInvalid = false;
1914 
1915     public void setNodeBounds(RectBounds bounds) {
1916         nodeBounds = bounds;
1917         lcdSampleInvalid = bounds != null;
1918     }
1919 
1920     private void initLCDSampleRT() {
1921         if (lcdSampleInvalid) {
1922             RectBounds textBounds = new RectBounds();
1923             getTransformNoClone().transform(nodeBounds, textBounds);
1924             Rectangle clipRect = getClipRectNoClone();
1925             if (clipRect != null && !clipRect.isEmpty()) {
1926                 // Reduce sample area if there is any clipping bounds set
1927                 textBounds.intersectWith(clipRect);
1928             }
1929             // LCD mixing with background will often lead to extra pixel at edge
1930             // thus adding a single pixel, as padding, at edges and bottom.
1931             float bx = textBounds.getMinX() - 1.0f;
1932             float by = textBounds.getMinY();
1933             float bw = textBounds.getWidth() + 2.0f;
1934             float bh = textBounds.getHeight() + 1.0f;
1935 
1936             context.validateLCDBuffer(getRenderTarget());
1937 
1938             // Create a graphics for us to render into the LCDBuffer
1939             // Note this also sets the current RenderTarget as the LCDBuffer
1940             BaseShaderGraphics bsg = (BaseShaderGraphics) context.getLCDBuffer().createGraphics();
1941             bsg.setCompositeMode(CompositeMode.SRC);
1942             context.validateLCDOp(bsg, IDENT, (Texture) getRenderTarget(), null, true, null);
1943 
1944             int srch = getRenderTarget().getPhysicalHeight();
1945             int srcw = getRenderTarget().getPhysicalWidth();
1946             float tx1 = bx / srcw;
1947             float ty1 = by / srch;
1948             float tx2 = (bx + bw) / srcw;
1949             float ty2 = (by + bh) / srch;
1950 
1951             //sample the source RT in the following bounds and store it in the LCDBuffer RT.
1952             bsg.drawLCDBuffer(bx, by, bw, bh, tx1, ty1, tx2, ty2);
1953             context.setRenderTarget(this);
1954         }
1955         lcdSampleInvalid = false;
1956     }
1957 
1958     public void drawString(GlyphList gl, FontStrike strike, float x, float y,
1959                            Color selectColor, int selectStart, int selectEnd) {
1960 
1961         if (isComplexPaint ||
1962             paint.getType().isImagePattern() ||
1963             strike.drawAsShapes())
1964         {
1965             // FontStrike.drawAsShapes() may be true for very large font sizes
1966             // in which case so no glyph images are cached.
1967             // The Prism Text node handles such cases directly, but Webnode relies
1968             // upon Prism's drawString method, so we need to handle it here too.
1969 
1970             // this is not a very optimal approach and may not hit exactly
1971             // the same pixels that we would hit in the case where the
1972             // glyph cache is used, but the complex paint case is not
1973             // common enough to warrant further optimization
1974             BaseTransform xform = BaseTransform.getTranslateInstance(x, y);
1975             Shape shape = strike.getOutline(gl, xform);
1976             fill(shape);
1977             return;
1978         }
1979 
1980         BaseTransform xform = getTransformNoClone();
1981 
1982         Paint textPaint = getPaint();
1983         Color textColor = textPaint.getType() == Paint.Type.COLOR ?
1984                           (Color) textPaint : null;
1985 
1986         CompositeMode blendMode = getCompositeMode();
1987         // LCD support requires several attributes to function:
1988         // FontStrike supports LCD, SRC_OVER CompositeMode and Paint is a COLOR
1989         boolean lcdSupported = blendMode == CompositeMode.SRC_OVER &&
1990                                textColor != null &&
1991                                xform.is2D() &&
1992                                !getRenderTarget().isAntiAliasing();
1993 
1994         /* If the surface can't support LCD text we need to replace an
1995          * LCD mode strike with the equivalent grey scale one.
1996          */
1997         if (strike.getAAMode() == FontResource.AA_LCD && !lcdSupported) {
1998             FontResource fr = strike.getFontResource();
1999             float size = strike.getSize();
2000             BaseTransform tx = strike.getTransform();
2001             strike = fr.getStrike(size, tx, FontResource.AA_GREYSCALE);
2002         }
2003 
2004         float bx = 0f, by = 0f, bw = 0f, bh = 0f;
2005         if (paint.getType().isGradient() && ((Gradient)paint).isProportional()) {
2006             // If drawString is called directly without using setNodeBounds,
2007             // then nodeBounds is null, and we must determine the bounds based
2008             // on the str(vs. the node).
2009             RectBounds textBounds = nodeBounds;
2010             if (textBounds == null) {
2011                 Metrics m = strike.getMetrics();
2012                 float pad = -m.getAscent() * 0.4f;
2013                 textBounds = new RectBounds(-pad,
2014                                             m.getAscent(),
2015                                             gl.getWidth() + 2.0f *pad,
2016                                             m.getDescent() + m.getLineGap());
2017                 bx = x;
2018                 by = y;
2019             }
2020 
2021             bx += textBounds.getMinX();
2022             by += textBounds.getMinY();
2023             bw = textBounds.getWidth();
2024             bh = textBounds.getHeight();
2025         }
2026 
2027         BaseBounds clip = null;
2028         Point2D p2d = new Point2D(x, y);
2029         if (isSimpleTranslate) {
2030             /* Only use clip for simple transforms so that coordinates in the
2031              * glyph list (user space) and the coordinates of the clip
2032              * (device space) can be intersected.
2033              */
2034             clip = getFinalClipNoClone();
2035             xform = IDENT;
2036             p2d.x += transX;
2037             p2d.y += transY;
2038         }
2039 
2040         /* Cache look up needs to be on the font as rendered, including
2041          * AA mode, metrics mode, glyph transform (pt size combined with
2042          * the full graphics transform). Most of this info is expected to
2043          * be in the font, which here is close to being a full strike
2044          * description.
2045          */
2046         GlyphCache glyphCache = context.getGlyphCache(strike);
2047         Texture cacheTex = glyphCache.getBackingStore();
2048 
2049         //Since we currently cannot support LCD text on transparant surfaces, we
2050         //verify that we are drawing to an opaque surface.
2051         if (strike.getAAMode() == FontResource.AA_LCD) {
2052             if (nodeBounds == null) {
2053                 // If drawString is called directly without using
2054                 // setNodeBounds then we must determine the bounds of the str,
2055                 // before we render background to texture.
2056                 // This is slow, but required by webnode.
2057 
2058                 Metrics m = strike.getMetrics();
2059                 // Ruff guess for padding, since lots of glyphs exceed advance
2060                 RectBounds textBounds =
2061                         new RectBounds(x - 2,
2062                                        y + m.getAscent(),
2063                                        x + 2 + gl.getWidth(),
2064                                        y + 1 + m.getDescent() + m.getLineGap());
2065 
2066                 setNodeBounds(textBounds);
2067                 initLCDSampleRT();
2068                 setNodeBounds(null);
2069             } else {
2070                 initLCDSampleRT();
2071             }
2072             float invgamma = PrismFontFactory.getLCDContrast();
2073             float gamma = 1.0f/invgamma;
2074             textColor = new Color((float)Math.pow(textColor.getRed(),   invgamma),
2075                                   (float)Math.pow(textColor.getGreen(), invgamma),
2076                                   (float)Math.pow(textColor.getBlue(),  invgamma),
2077                                   (float)Math.pow(textColor.getAlpha(), invgamma));
2078             if (selectColor != null) {
2079                 selectColor = new Color(
2080                         (float)Math.pow(selectColor.getRed(),   invgamma),
2081                         (float)Math.pow(selectColor.getGreen(), invgamma),
2082                         (float)Math.pow(selectColor.getBlue(),  invgamma),
2083                         (float)Math.pow(selectColor.getAlpha(), invgamma));
2084             }
2085 
2086             // In order to handle transparency, the LCD shader need to manually
2087             // composite source with destination. Thus, SRC_OVER compositing
2088             // needs to be set to SRC, while shader is active.
2089             setCompositeMode(CompositeMode.SRC);
2090 
2091             //set our 2nd LCD shader.
2092             Shader shader = context.validateLCDOp(this, IDENT,
2093                                                 context.getLCDBuffer(),
2094                                                 cacheTex, false, textColor);
2095 
2096             float unitXCoord = 1.0f/((float)cacheTex.getPhysicalWidth());
2097             shader.setConstant("gamma", gamma, invgamma, unitXCoord);
2098             setCompositeMode(blendMode); // Restore composite mode
2099         } else {
2100             context.validatePaintOp(this, IDENT, cacheTex, bx, by, bw, bh);
2101         }
2102         if (isSimpleTranslate) {
2103             // Applying this rounding allows for smoother text animation,
2104             // when animating simple translated text.
2105             // Asking glyph textures to be rendered at non-integral
2106             // locations produces very poor text. This doesn't solve
2107             // the problem for scaled (etc) cases, but addresses a
2108             // common case.
2109             p2d.y = Math.round(p2d.y);
2110             p2d.x = Math.round(p2d.x);
2111         }
2112         glyphCache.render(context, gl, p2d.x, p2d.y, selectStart, selectEnd,
2113                           selectColor, textColor, xform, clip);
2114     }
2115 
2116     //This function is used by the LCD path to render a quad into the
2117     //LCD RTT Texture. here the presentable is set as input and
2118     //sampled using texture coordinates. This is later used in a
2119     //second pass to provide the dst color as input.
2120     private void drawLCDBuffer(float bx, float by, float bw, float bh,
2121             float tx1, float ty1, float tx2, float ty2)
2122     {
2123         context.setRenderTarget(this);
2124         context.getVertexBuffer().addQuad(bx, by, bx + bw, by + bh, tx1, ty1, tx2, ty2);
2125     }
2126 
2127     public boolean canReadBack() {
2128         RenderTarget rt = getRenderTarget();
2129         return rt instanceof ReadbackRenderTarget &&
2130             ((ReadbackRenderTarget) rt).getBackBuffer() != null;
2131     }
2132 
2133     public RTTexture readBack(Rectangle view) {
2134         RenderTarget rt = getRenderTarget();
2135         context.flushVertexBuffer();
2136         context.validateLCDBuffer(rt);
2137         RTTexture lcdrtt = context.getLCDBuffer();
2138         Texture bbtex = ((ReadbackRenderTarget) rt).getBackBuffer();
2139 
2140         float x1 = view.x;
2141         float y1 = view.y;
2142         float x2 = x1 + view.width;
2143         float y2 = y1 + view.height;
2144 
2145         // Create a graphics for us to render into the LCDBuffer
2146         // Note this also sets the current RenderTarget as the LCDBuffer
2147         BaseShaderGraphics bsg = (BaseShaderGraphics) lcdrtt.createGraphics();
2148         bsg.setCompositeMode(CompositeMode.SRC);
2149         context.validateTextureOp(bsg, IDENT, bbtex, bbtex.getPixelFormat());
2150 
2151         // sample the source RT in the following bounds and store it in the LCDBuffer RT.
2152         bsg.drawTexture(bbtex, 0, 0, view.width, view.height, x1, y1, x2, y2);
2153         context.flushVertexBuffer();
2154 
2155         // set the RenderTarget back to this.
2156         context.setRenderTarget(this);
2157         return lcdrtt;
2158     }
2159 
2160     public void releaseReadBackBuffer(RTTexture rtt) {
2161         // This will be needed when we track LCD buffer locks and uses.
2162         // (See RT-29488)
2163 //        context.releaseLCDBuffer();
2164     }
2165 
2166     public void setup3DRendering() {
2167         context.setRenderTarget(this);
2168     }
2169 }