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