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