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     private static final double SQRT_2 = Math.sqrt(2.0);
1608     private static boolean canUseStrokeShader(BasicStroke bs) {
1609         // RT-27378
1610         // TODO: Expand the cases that renderGeneralRoundRect() can handle...
1611         return (!bs.isDashed() &&
1612                 (bs.getType() == BasicStroke.TYPE_INNER ||
1613                  bs.getLineJoin() == BasicStroke.JOIN_ROUND ||
1614                  (bs.getLineJoin() == BasicStroke.JOIN_MITER &&
1615                   bs.getMiterLimit() >= SQRT_2)));
1616     }
1617 
1618     public void blit(RTTexture srcTex, RTTexture dstTex,
1619                      int srcX0, int srcY0, int srcX1, int srcY1,
1620                      int dstX0, int dstY0, int dstX1, int dstY1) {
1621         if (dstTex == null) {
1622             context.setRenderTarget(this);
1623         } else {
1624             context.setRenderTarget((BaseGraphics)dstTex.createGraphics());
1625         }
1626         context.blit(srcTex, dstTex, srcX0, srcY0, srcX1, srcY1,
1627                 dstX0, dstY0, dstX1, dstY1);
1628     }
1629 
1630     public void drawRect(float x, float y, float w, float h) {
1631         if (w < 0 || h < 0) {
1632             return;
1633         }
1634         if (w == 0 || h == 0) {
1635             drawLine(x, y, x + w, y + h);
1636             return;
1637         }
1638         if (isComplexPaint) {
1639             scratchRRect.setRoundRect(x, y, w, h, 0, 0);
1640             renderWithComplexPaint(scratchRRect, stroke, x, y, w, h);
1641             return;
1642         }
1643         if (canUseStrokeShader(stroke)) {
1644             if (PrismSettings.primTextureSize != 0 &&
1645                 stroke.getLineJoin() != BasicStroke.CAP_ROUND)
1646             {
1647                 if (drawPrimRect(x, y, w, h)) {
1648                     return;
1649                 }
1650             }
1651             renderGeneralRoundedRect(x, y, w, h, 0f, 0f,
1652                                      MaskType.DRAW_PGRAM, stroke);
1653             return;
1654         }
1655         scratchRRect.setRoundRect(x, y, w, h, 0, 0);
1656         renderShape(scratchRRect, stroke, x, y, w, h);
1657     }
1658 
1659     private boolean checkInnerCurvature(float arcw, float arch) {
1660         // Test to see if inner ellipse satisfies (flattening < 0.5)
1661         // otherwise it will not be approximated by a "parallel ellipse"
1662         // very well and we should just use shape rendering.
1663 
1664         // RT-27378
1665         // TODO: Implement better "distance to ellipse" formulas in the shaders
1666         float inset = stroke.getLineWidth() *
1667             (1f - getStrokeExpansionFactor(stroke));
1668         arcw -= inset;
1669         arch -= inset;
1670         // Note that if either inset arcw,h go to <= 0 then we will invoke the
1671         // fill primitive for ellipse, or the round rect primitive will
1672         // invoke its "tiny inner corner" fixes and we will be safe
1673         return (arcw <= 0 || arch <= 0 ||
1674                 (arcw * 2f > arch && arch * 2f > arcw));
1675     }
1676 
1677     public void drawEllipse(float x, float y, float w, float h) {
1678         if (w < 0 || h < 0) {
1679             return;
1680         }
1681         if (!isComplexPaint && !stroke.isDashed() &&
1682             checkInnerCurvature(w, h))
1683         {
1684             renderGeneralRoundedRect(x, y, w, h, w, h,
1685                                      MaskType.DRAW_ELLIPSE, stroke);
1686             return;
1687         }
1688         scratchEllipse.setFrame(x, y, w, h);
1689         renderShape(scratchEllipse, stroke, x, y, w, h);
1690     }
1691 
1692     public void drawRoundRect(float x, float y, float w, float h,
1693                               float arcw, float arch)
1694     {
1695         arcw = Math.min(Math.abs(arcw), w);
1696         arch = Math.min(Math.abs(arch), h);
1697 
1698         if (w < 0 || h < 0) {
1699             return;
1700         }
1701         if (!isComplexPaint && !stroke.isDashed() &&
1702             checkInnerCurvature(arcw, arch))
1703         {
1704             renderGeneralRoundedRect(x, y, w, h, arcw, arch,
1705                                      MaskType.DRAW_ROUNDRECT, stroke);
1706             return;
1707         }
1708         scratchRRect.setRoundRect(x, y, w, h, arcw, arch);
1709         renderShape(scratchRRect, stroke, x, y, w, h);
1710     }
1711 
1712     public void drawLine(float x1, float y1, float x2, float y2) {
1713         float bx, by, bw, bh;
1714         if (x1 <= x2) {
1715             bx = x1;
1716             bw = x2 - x1;
1717         } else {
1718             bx = x2;
1719             bw = x1 - x2;
1720         }
1721         if (y1 <= y2) {
1722             by = y1;
1723             bh = y2 - y1;
1724         } else {
1725             by = y2;
1726             bh = y1 - y2;
1727         }
1728 
1729         // RT-27378
1730         // TODO: casting down to floats everywhere here; evaluate later
1731         // to see if this is enough precision...
1732         // TODO: stroke normalization control?
1733         if (stroke.getType() == BasicStroke.TYPE_INNER) {
1734             return;
1735         }
1736         if (isComplexPaint) {
1737             scratchLine.setLine(x1, y1, x2, y2);
1738             renderWithComplexPaint(scratchLine, stroke, bx, by, bw, bh);
1739             return;
1740         }
1741         int cap = stroke.getEndCap();
1742         if (stroke.isDashed()) {
1743             // NOTE: we could construct the GeneralPath directly
1744             // for CAP_ROUND and save a lot of processing in that case...
1745             // And again, we would need to deal with dropout control...
1746             scratchLine.setLine(x1, y1, x2, y2);
1747             renderShape(scratchLine, stroke, bx, by, bw, bh);
1748             return;
1749         }
1750         float lw = stroke.getLineWidth();
1751         if (PrismSettings.primTextureSize != 0 &&
1752             cap != BasicStroke.CAP_ROUND)
1753         {
1754             float pad = lw;
1755             if (stroke.getType() == BasicStroke.TYPE_CENTERED) {
1756                 pad *= 0.5f;
1757             }
1758             if (bw == 0.0f || bh == 0.0f) {
1759                 float padx, pady;
1760                 if (cap == BasicStroke.CAP_SQUARE) {
1761                     // CAP_SQUARE pads the same on all sides
1762                     padx = pady = pad;
1763 //                    System.out.print("S"); System.out.flush();
1764                 } else if (bw != 0.0f) {
1765                     // Horizontal CAP_BUTT line - widen vertically
1766                     padx = 0.0f;
1767                     pady = pad;
1768 //                    System.out.print("H"); System.out.flush();
1769                 } else if (bh != 0.0f) {
1770                     // Vertical CAP_BUTT line - widen horizontally
1771                     padx = pad;
1772                     pady = 0.0f;
1773 //                    System.out.print("V"); System.out.flush();
1774                 } else {
1775 //                    System.out.print("0"); System.out.flush();
1776                     // Zero length line - NOP for CAP_BUTT
1777                     return;
1778                 }
1779                 Texture rTex = context.getRectTexture();
1780                 Texture wTex = context.getWrapRectTexture();
1781                 boolean success = fillPrimRect(bx - padx,        by - pady,
1782                                                bw + padx + padx, bh + pady + pady,
1783                                                rTex, wTex, bx, by, bw, bh);
1784                 rTex.unlock();
1785                 wTex.unlock();
1786                 if (success) return;
1787             } else {
1788                 if (drawPrimDiagonal(x1, y1, x2, y2, lw, cap,
1789                                      bx, by, bw, bh))
1790                 {
1791                     return;
1792                 }
1793             }
1794         }
1795 //        System.out.print("#"); System.out.flush();
1796         if (stroke.getType() == BasicStroke.TYPE_OUTER) {
1797             lw *= 2f;
1798         }
1799         float dx = x2 - x1;
1800         float dy = y2 - y1;
1801         float len = len(dx, dy);
1802         float ldx, ldy;  // lw length vector in direction of line in user space
1803         if (len == 0) {
1804             if (cap == BasicStroke.CAP_BUTT) {
1805                 return;
1806             }
1807             ldx = lw;
1808             ldy = 0;
1809         } else {
1810             ldx = lw * dx / len;
1811             ldy = lw * dy / len;
1812         }
1813         // The following is safe; this method does not mutate the transform
1814         BaseTransform xform = getTransformNoClone();
1815         BaseTransform rendertx;
1816         float pdx, pdy;  // ldx,ldy rotated 90 in user space then transformed
1817         if (isSimpleTranslate) {
1818             double tx = xform.getMxt();
1819             double ty = xform.getMyt();
1820             x1 += tx;
1821             y1 += ty;
1822             x2 += tx;
1823             y2 += ty;
1824             pdx = ldy;
1825             pdy = -ldx;
1826             rendertx = IDENT;
1827         } else {
1828             rendertx = extract3Dremainder(xform);
1829             double coords[] = {x1, y1, x2, y2};
1830             xform.transform(coords, 0, coords, 0, 2);
1831             x1 = (float)coords[0];
1832             y1 = (float)coords[1];
1833             x2 = (float)coords[2];
1834             y2 = (float)coords[3];
1835             dx = x2 - x1;
1836             dy = y2 - y1;
1837             coords[0] = ldx;
1838             coords[1] = ldy;
1839             coords[2] = ldy;
1840             coords[3] = -ldx;
1841             xform.deltaTransform(coords, 0, coords, 0, 2);
1842             ldx = (float) coords[0];
1843             ldy = (float) coords[1];
1844             pdx = (float) coords[2];
1845             pdy = (float) coords[3];
1846         }
1847         float px = x1 - pdx / 2f;
1848         float py = y1 - pdy / 2f;
1849         float arcfractw, arcfracth;
1850         MaskType type;
1851         if (cap != BasicStroke.CAP_BUTT) {
1852             px -= ldx / 2f;
1853             py -= ldy / 2f;
1854             dx += ldx;
1855             dy += ldy;
1856             if (cap == BasicStroke.CAP_ROUND) {
1857                 arcfractw = len(ldx, ldy) / len(dx, dy);
1858                 arcfracth = 1f;
1859                 type = MaskType.FILL_ROUNDRECT;
1860             } else {
1861                 arcfractw = arcfracth = 0f;
1862                 type = MaskType.FILL_PGRAM;
1863             }
1864         } else {
1865             arcfractw = arcfracth = 0f;
1866             type = MaskType.FILL_PGRAM;
1867         }
1868         renderGeneralRoundedPgram(px, py, dx, dy, pdx, pdy,
1869                                   arcfractw, arcfracth, 0f, 0f,
1870                                   rendertx, type,
1871                                   bx, by, bw, bh);
1872     }
1873 
1874     private static float len(float x, float y) {
1875         return ((x == 0f) ? Math.abs(y)
1876                 : ((y == 0f) ? Math.abs(x)
1877                    : (float)Math.sqrt(x * x + y * y)));
1878     }
1879 
1880     private boolean lcdSampleInvalid = false;
1881 
1882     public void setNodeBounds(RectBounds bounds) {
1883         nodeBounds = bounds;
1884         lcdSampleInvalid = bounds != null;
1885     }
1886 
1887     private void initLCDSampleRT() {
1888         if (lcdSampleInvalid) {
1889             RectBounds textBounds = new RectBounds();
1890             getTransformNoClone().transform(nodeBounds, textBounds);
1891             Rectangle clipRect = getClipRectNoClone();
1892             if (clipRect != null && !clipRect.isEmpty()) {
1893                 // Reduce sample area if there is any clipping bounds set
1894                 textBounds.intersectWith(clipRect);
1895             }
1896             // LCD mixing with background will often lead to extra pixel at edge
1897             // thus adding a single pixel, as padding, at edges and bottom.
1898             float bx = textBounds.getMinX() - 1.0f;
1899             float by = textBounds.getMinY();
1900             float bw = textBounds.getWidth() + 2.0f;
1901             float bh = textBounds.getHeight() + 1.0f;
1902 
1903             context.validateLCDBuffer(getRenderTarget());
1904 
1905             // Create a graphics for us to render into the LCDBuffer
1906             // Note this also sets the current RenderTarget as the LCDBuffer
1907             BaseShaderGraphics bsg = (BaseShaderGraphics) context.getLCDBuffer().createGraphics();
1908             bsg.setCompositeMode(CompositeMode.SRC);
1909             context.validateLCDOp(bsg, IDENT, (Texture) getRenderTarget(), null, true, null);
1910 
1911             int srch = getRenderTarget().getPhysicalHeight();
1912             int srcw = getRenderTarget().getPhysicalWidth();
1913             float tx1 = bx / srcw;
1914             float ty1 = by / srch;
1915             float tx2 = (bx + bw) / srcw;
1916             float ty2 = (by + bh) / srch;
1917 
1918             //sample the source RT in the following bounds and store it in the LCDBuffer RT.
1919             bsg.drawLCDBuffer(bx, by, bw, bh, tx1, ty1, tx2, ty2);
1920             context.setRenderTarget(this);
1921         }
1922         lcdSampleInvalid = false;
1923     }
1924 
1925     public void drawString(GlyphList gl, FontStrike strike, float x, float y,
1926                            Color selectColor, int selectStart, int selectEnd) {
1927 
1928         if (isComplexPaint ||
1929             paint.getType().isImagePattern() ||
1930             strike.drawAsShapes())
1931         {
1932             // FontStrike.drawAsShapes() may be true for very large font sizes
1933             // in which case so no glyph images are cached.
1934             // The Prism Text node handles such cases directly, but Webnode relies
1935             // upon Prism's drawString method, so we need to handle it here too.
1936 
1937             // this is not a very optimal approach and may not hit exactly
1938             // the same pixels that we would hit in the case where the
1939             // glyph cache is used, but the complex paint case is not
1940             // common enough to warrant further optimization
1941             BaseTransform xform = BaseTransform.getTranslateInstance(x, y);
1942             Shape shape = strike.getOutline(gl, xform);
1943             fill(shape);
1944             return;
1945         }
1946 
1947         BaseTransform xform = getTransformNoClone();
1948 
1949         Paint textPaint = getPaint();
1950         Color textColor = textPaint.getType() == Paint.Type.COLOR ?
1951                           (Color) textPaint : null;
1952 
1953         CompositeMode blendMode = getCompositeMode();
1954         // LCD support requires several attributes to function:
1955         // FontStrike supports LCD, SRC_OVER CompositeMode and Paint is a COLOR
1956         boolean lcdSupported = blendMode == CompositeMode.SRC_OVER &&
1957                                textColor != null &&
1958                                xform.is2D() &&
1959                                !getRenderTarget().isAntiAliasing();
1960 
1961         /* If the surface can't support LCD text we need to replace an
1962          * LCD mode strike with the equivalent grey scale one.
1963          */
1964         if (strike.getAAMode() == FontResource.AA_LCD && !lcdSupported) {
1965             FontResource fr = strike.getFontResource();
1966             float size = strike.getSize();
1967             BaseTransform tx = strike.getTransform();
1968             strike = fr.getStrike(size, tx, FontResource.AA_GREYSCALE);
1969         }
1970 
1971         float bx = 0f, by = 0f, bw = 0f, bh = 0f;
1972         if (paint.getType().isGradient() && ((Gradient)paint).isProportional()) {
1973             // If drawString is called directly without using setNodeBounds,
1974             // then nodeBounds is null, and we must determine the bounds based
1975             // on the str(vs. the node).
1976             RectBounds textBounds = nodeBounds;
1977             if (textBounds == null) {
1978                 Metrics m = strike.getMetrics();
1979                 float pad = -m.getAscent() * 0.4f;
1980                 textBounds = new RectBounds(-pad,
1981                                             m.getAscent(),
1982                                             gl.getWidth() + 2.0f *pad,
1983                                             m.getDescent() + m.getLineGap());
1984                 bx = x;
1985                 by = y;
1986             }
1987 
1988             bx += textBounds.getMinX();
1989             by += textBounds.getMinY();
1990             bw = textBounds.getWidth();
1991             bh = textBounds.getHeight();
1992         }
1993 
1994         BaseBounds clip = null;
1995         Point2D p2d = new Point2D(x, y);
1996         if (isSimpleTranslate) {
1997             /* Only use clip for simple transforms so that coordinates in the
1998              * glyph list (user space) and the coordinates of the clip
1999              * (device space) can be intersected.
2000              */
2001             clip = getFinalClipNoClone();
2002             xform = IDENT;
2003             p2d.x += transX;
2004             p2d.y += transY;
2005         }
2006 
2007         /* Cache look up needs to be on the font as rendered, including
2008          * AA mode, metrics mode, glyph transform (pt size combined with
2009          * the full graphics transform). Most of this info is expected to
2010          * be in the font, which here is close to being a full strike
2011          * description.
2012          */
2013         GlyphCache glyphCache = context.getGlyphCache(strike);
2014         Texture cacheTex = glyphCache.getBackingStore();
2015 
2016         //Since we currently cannot support LCD text on transparant surfaces, we
2017         //verify that we are drawing to an opaque surface.
2018         if (strike.getAAMode() == FontResource.AA_LCD) {
2019             if (nodeBounds == null) {
2020                 // If drawString is called directly without using
2021                 // setNodeBounds then we must determine the bounds of the str,
2022                 // before we render background to texture.
2023                 // This is slow, but required by webnode.
2024 
2025                 Metrics m = strike.getMetrics();
2026                 // Ruff guess for padding, since lots of glyphs exceed advance
2027                 RectBounds textBounds =
2028                         new RectBounds(x - 2,
2029                                        y + m.getAscent(),
2030                                        x + 2 + gl.getWidth(),
2031                                        y + 1 + m.getDescent() + m.getLineGap());
2032 
2033                 setNodeBounds(textBounds);
2034                 initLCDSampleRT();
2035                 setNodeBounds(null);
2036             } else {
2037                 initLCDSampleRT();
2038             }
2039             float invgamma = PrismFontFactory.getLCDContrast();
2040             float gamma = 1.0f/invgamma;
2041             textColor = new Color((float)Math.pow(textColor.getRed(),   invgamma),
2042                                   (float)Math.pow(textColor.getGreen(), invgamma),
2043                                   (float)Math.pow(textColor.getBlue(),  invgamma),
2044                                   (float)Math.pow(textColor.getAlpha(), invgamma));
2045             if (selectColor != null) {
2046                 selectColor = new Color(
2047                         (float)Math.pow(selectColor.getRed(),   invgamma),
2048                         (float)Math.pow(selectColor.getGreen(), invgamma),
2049                         (float)Math.pow(selectColor.getBlue(),  invgamma),
2050                         (float)Math.pow(selectColor.getAlpha(), invgamma));
2051             }
2052 
2053             // In order to handle transparency, the LCD shader need to manually
2054             // composite source with destination. Thus, SRC_OVER compositing
2055             // needs to be set to SRC, while shader is active.
2056             setCompositeMode(CompositeMode.SRC);
2057 
2058             //set our 2nd LCD shader.
2059             Shader shader = context.validateLCDOp(this, IDENT,
2060                                                 context.getLCDBuffer(),
2061                                                 cacheTex, false, textColor);
2062 
2063             float unitXCoord = 1.0f/((float)cacheTex.getPhysicalWidth());
2064             shader.setConstant("gamma", gamma, invgamma, unitXCoord);
2065             setCompositeMode(blendMode); // Restore composite mode
2066         } else {
2067             context.validatePaintOp(this, IDENT, cacheTex, bx, by, bw, bh);
2068         }
2069         if (isSimpleTranslate) {
2070             // Applying this rounding allows for smoother text animation,
2071             // when animating simple translated text.
2072             // Asking glyph textures to be rendered at non-integral
2073             // locations produces very poor text. This doesn't solve
2074             // the problem for scaled (etc) cases, but addresses a
2075             // common case.
2076             p2d.y = Math.round(p2d.y);
2077             p2d.x = Math.round(p2d.x);
2078         }
2079         glyphCache.render(context, gl, p2d.x, p2d.y, selectStart, selectEnd,
2080                           selectColor, textColor, xform, clip);
2081     }
2082 
2083     //This function is used by the LCD path to render a quad into the
2084     //LCD RTT Texture. here the presentable is set as input and
2085     //sampled using texture coordinates. This is later used in a
2086     //second pass to provide the dst color as input.
2087     private void drawLCDBuffer(float bx, float by, float bw, float bh,
2088             float tx1, float ty1, float tx2, float ty2)
2089     {
2090         context.setRenderTarget(this);
2091         context.getVertexBuffer().addQuad(bx, by, bx + bw, by + bh, tx1, ty1, tx2, ty2);
2092     }
2093 
2094     public boolean canReadBack() {
2095         RenderTarget rt = getRenderTarget();
2096         return rt instanceof ReadbackRenderTarget &&
2097             ((ReadbackRenderTarget) rt).getBackBuffer() != null;
2098     }
2099 
2100     public RTTexture readBack(Rectangle view) {
2101         RenderTarget rt = getRenderTarget();
2102         context.flushVertexBuffer();
2103         context.validateLCDBuffer(rt);
2104         RTTexture lcdrtt = context.getLCDBuffer();
2105         Texture bbtex = ((ReadbackRenderTarget) rt).getBackBuffer();
2106 
2107         float x1 = view.x;
2108         float y1 = view.y;
2109         float x2 = x1 + view.width;
2110         float y2 = y1 + view.height;
2111 
2112         // Create a graphics for us to render into the LCDBuffer
2113         // Note this also sets the current RenderTarget as the LCDBuffer
2114         BaseShaderGraphics bsg = (BaseShaderGraphics) lcdrtt.createGraphics();
2115         bsg.setCompositeMode(CompositeMode.SRC);
2116         context.validateTextureOp(bsg, IDENT, bbtex, bbtex.getPixelFormat());
2117 
2118         // sample the source RT in the following bounds and store it in the LCDBuffer RT.
2119         bsg.drawTexture(bbtex, 0, 0, view.width, view.height, x1, y1, x2, y2);
2120         context.flushVertexBuffer();
2121 
2122         // set the RenderTarget back to this.
2123         context.setRenderTarget(this);
2124         return lcdrtt;
2125     }
2126 
2127     public void releaseReadBackBuffer(RTTexture rtt) {
2128         // This will be needed when we track LCD buffer locks and uses.
2129         // (See RT-29488)
2130 //        context.releaseLCDBuffer();
2131     }
2132 
2133     public void setup3DRendering() {
2134         context.setRenderTarget(this);
2135     }
2136 }