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