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