1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.sg.prism;
  27 
  28 import javafx.geometry.VPos;
  29 import javafx.scene.text.Font;
  30 import java.nio.IntBuffer;
  31 import java.util.concurrent.ExecutionException;
  32 import java.util.concurrent.FutureTask;
  33 import java.util.LinkedList;
  34 import com.sun.javafx.font.PGFont;
  35 import com.sun.javafx.geom.Arc2D;
  36 import com.sun.javafx.geom.BaseBounds;
  37 import com.sun.javafx.geom.DirtyRegionContainer;
  38 import com.sun.javafx.geom.DirtyRegionPool;
  39 import com.sun.javafx.geom.Path2D;
  40 import com.sun.javafx.geom.PathIterator;
  41 import com.sun.javafx.geom.RectBounds;
  42 import com.sun.javafx.geom.Rectangle;
  43 import com.sun.javafx.geom.RoundRectangle2D;
  44 import com.sun.javafx.geom.Shape;
  45 import com.sun.javafx.geom.transform.Affine2D;
  46 import com.sun.javafx.geom.transform.BaseTransform;
  47 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  48 import com.sun.javafx.text.PrismTextLayout;
  49 import com.sun.javafx.tk.RenderJob;
  50 import com.sun.javafx.tk.Toolkit;
  51 import com.sun.prism.BasicStroke;
  52 import com.sun.prism.CompositeMode;
  53 import com.sun.prism.Graphics;
  54 import com.sun.prism.GraphicsPipeline;
  55 import com.sun.prism.Image;
  56 import com.sun.prism.MaskTextureGraphics;
  57 import com.sun.prism.PrinterGraphics;
  58 import com.sun.prism.RTTexture;
  59 import com.sun.prism.ResourceFactory;
  60 import com.sun.prism.Texture;
  61 import com.sun.prism.Texture.WrapMode;
  62 import com.sun.prism.paint.Color;
  63 import com.sun.prism.paint.Paint;
  64 import com.sun.scenario.effect.Blend;
  65 import com.sun.scenario.effect.Blend.Mode;
  66 import com.sun.scenario.effect.Effect;
  67 import com.sun.scenario.effect.FilterContext;
  68 import com.sun.scenario.effect.Filterable;
  69 import com.sun.scenario.effect.ImageData;
  70 import com.sun.scenario.effect.impl.prism.PrDrawable;
  71 import com.sun.scenario.effect.impl.prism.PrFilterContext;
  72 import com.sun.scenario.effect.impl.prism.PrTexture;
  73 import javafx.scene.text.FontSmoothingType;
  74 
  75 /**
  76  */
  77 public class NGCanvas extends NGNode {
  78     public static final byte                 ATTR_BASE = 0;
  79     public static final byte GLOBAL_ALPHA  = ATTR_BASE + 0;
  80     public static final byte COMP_MODE     = ATTR_BASE + 1;
  81     public static final byte FILL_PAINT    = ATTR_BASE + 2;
  82     public static final byte STROKE_PAINT  = ATTR_BASE + 3;
  83     public static final byte LINE_WIDTH    = ATTR_BASE + 4;
  84     public static final byte LINE_CAP      = ATTR_BASE + 5;
  85     public static final byte LINE_JOIN     = ATTR_BASE + 6;
  86     public static final byte MITER_LIMIT   = ATTR_BASE + 7;
  87     public static final byte FONT          = ATTR_BASE + 8;
  88     public static final byte TEXT_ALIGN    = ATTR_BASE + 9;
  89     public static final byte TEXT_BASELINE = ATTR_BASE + 10;
  90     public static final byte TRANSFORM     = ATTR_BASE + 11;
  91     public static final byte EFFECT        = ATTR_BASE + 12;
  92     public static final byte PUSH_CLIP     = ATTR_BASE + 13;
  93     public static final byte POP_CLIP      = ATTR_BASE + 14;
  94     public static final byte ARC_TYPE      = ATTR_BASE + 15;
  95     public static final byte FILL_RULE     = ATTR_BASE + 16;
  96     public static final byte DASH_ARRAY    = ATTR_BASE + 17;
  97     public static final byte DASH_OFFSET   = ATTR_BASE + 18;
  98     public static final byte FONT_SMOOTH   = ATTR_BASE + 19;
  99 
 100     public static final byte                     OP_BASE = 20;
 101     public static final byte FILL_RECT         = OP_BASE + 0;
 102     public static final byte STROKE_RECT       = OP_BASE + 1;
 103     public static final byte CLEAR_RECT        = OP_BASE + 2;
 104     public static final byte STROKE_LINE       = OP_BASE + 3;
 105     public static final byte FILL_OVAL         = OP_BASE + 4;
 106     public static final byte STROKE_OVAL       = OP_BASE + 5;
 107     public static final byte FILL_ROUND_RECT   = OP_BASE + 6;
 108     public static final byte STROKE_ROUND_RECT = OP_BASE + 7;
 109     public static final byte FILL_ARC          = OP_BASE + 8;
 110     public static final byte STROKE_ARC        = OP_BASE + 9;
 111     public static final byte FILL_TEXT         = OP_BASE + 10;
 112     public static final byte STROKE_TEXT       = OP_BASE + 11;
 113 
 114     public static final byte                PATH_BASE = 40;
 115     public static final byte PATHSTART    = PATH_BASE + 0;
 116     public static final byte MOVETO       = PATH_BASE + 1;
 117     public static final byte LINETO       = PATH_BASE + 2;
 118     public static final byte QUADTO       = PATH_BASE + 3;
 119     public static final byte CUBICTO      = PATH_BASE + 4;
 120     public static final byte CLOSEPATH    = PATH_BASE + 5;
 121     public static final byte PATHEND      = PATH_BASE + 6;
 122     public static final byte FILL_PATH    = PATH_BASE + 7;
 123     public static final byte STROKE_PATH  = PATH_BASE + 8;
 124 
 125     public static final byte                   IMG_BASE = 50;
 126     public static final byte DRAW_IMAGE      = IMG_BASE + 0;
 127     public static final byte DRAW_SUBIMAGE   = IMG_BASE + 1;
 128     public static final byte PUT_ARGB        = IMG_BASE + 2;
 129     public static final byte PUT_ARGBPRE_BUF = IMG_BASE + 3;
 130 
 131     public static final byte                   FX_BASE = 60;
 132     public static final byte FX_APPLY_EFFECT = FX_BASE + 0;
 133 
 134     public static final byte                   UTIL_BASE = 70;
 135     public static final byte RESET           = UTIL_BASE + 0;
 136     public static final byte SET_DIMS        = UTIL_BASE + 1;
 137 
 138     public static final byte CAP_BUTT   = 0;
 139     public static final byte CAP_ROUND  = 1;
 140     public static final byte CAP_SQUARE = 2;
 141 
 142     public static final byte JOIN_MITER = 0;
 143     public static final byte JOIN_ROUND = 1;
 144     public static final byte JOIN_BEVEL = 2;
 145 
 146     public static final byte ARC_OPEN   = 0;
 147     public static final byte ARC_CHORD  = 1;
 148     public static final byte ARC_PIE    = 2;
 149 
 150     public static final byte SMOOTH_GRAY = (byte) FontSmoothingType.GRAY.ordinal();
 151     public static final byte SMOOTH_LCD  = (byte) FontSmoothingType.LCD.ordinal();
 152 
 153     public static final byte ALIGN_LEFT       = 0;
 154     public static final byte ALIGN_CENTER     = 1;
 155     public static final byte ALIGN_RIGHT      = 2;
 156     public static final byte ALIGN_JUSTIFY    = 3;
 157 
 158     public static final byte BASE_TOP        = 0;
 159     public static final byte BASE_MIDDLE     = 1;
 160     public static final byte BASE_ALPHABETIC = 2;
 161     public static final byte BASE_BOTTOM     = 3;
 162 
 163     public static final byte FILL_RULE_NON_ZERO = 0;
 164     public static final byte FILL_RULE_EVEN_ODD = 1;
 165 
 166     static enum InitType {
 167         CLEAR,
 168         FILL_WHITE,
 169         PRESERVE_UPPER_LEFT
 170     }
 171 
 172     static class RenderBuf {
 173         final InitType init_type;
 174         RTTexture tex;
 175         Graphics g;
 176         EffectInput input;
 177         private PixelData savedPixelData = null;
 178 
 179         public RenderBuf(InitType init_type) {
 180             this.init_type = init_type;
 181         }
 182 
 183         public void dispose() {
 184             if (tex != null) tex.dispose();
 185 
 186             tex = null;
 187             g = null;
 188             input = null;
 189         }
 190 
 191         public boolean validate(Graphics resg, int tw, int th) {
 192             int cw, ch;
 193             boolean create;
 194             if (tex == null) {
 195                 cw = ch = 0;
 196                 create = true;
 197             } else {
 198                 cw = tex.getContentWidth();
 199                 ch = tex.getContentHeight();
 200                 tex.lock();
 201                 create = tex.isSurfaceLost() || cw < tw || ch < th;
 202             }
 203             if (create) {
 204                 RTTexture oldtex = tex;
 205                 ResourceFactory factory = (resg == null)
 206                     ? GraphicsPipeline.getDefaultResourceFactory()
 207                     : resg.getResourceFactory();
 208                 RTTexture newtex =
 209                     factory.createRTTexture(tw, th, WrapMode.CLAMP_TO_ZERO);
 210                 this.tex = newtex;
 211                 this.g = newtex.createGraphics();
 212                 this.input = new EffectInput(newtex);
 213                 if (oldtex != null) {
 214                     if (init_type == InitType.PRESERVE_UPPER_LEFT) {
 215                         g.setCompositeMode(CompositeMode.SRC);
 216                         if (oldtex.isSurfaceLost()) {
 217                             if (savedPixelData != null) {
 218                                 savedPixelData.restore(g, cw, ch);
 219                             }
 220                         } else {
 221                             g.drawTexture(oldtex, 0, 0, cw, ch);
 222                         }
 223                         g.setCompositeMode(CompositeMode.SRC_OVER);
 224                     }
 225                     oldtex.unlock();
 226                     oldtex.dispose();
 227                 }
 228                 if (init_type == InitType.FILL_WHITE) {
 229                     g.clear(Color.WHITE);
 230                 }
 231                 return true;
 232             } else {
 233                 if (this.g == null) {
 234                     this.g = tex.createGraphics();
 235                     if (this.g == null) {
 236                         tex.dispose();
 237                         ResourceFactory factory = (resg == null)
 238                             ? GraphicsPipeline.getDefaultResourceFactory()
 239                             : resg.getResourceFactory();
 240                         tex = factory.createRTTexture(tw, th, WrapMode.CLAMP_TO_ZERO);
 241                         this.g = tex.createGraphics();
 242                         this.input = new EffectInput(tex);
 243                         if (savedPixelData != null) {
 244                             g.setCompositeMode(CompositeMode.SRC);
 245                             savedPixelData.restore(g, tw, th);
 246                             g.setCompositeMode(CompositeMode.SRC_OVER);
 247                         } else if (init_type == InitType.FILL_WHITE) {
 248                             g.clear(Color.WHITE);
 249                         }
 250                         return true;
 251                     }
 252                 }
 253             }
 254             if (init_type == InitType.CLEAR) {
 255                 g.clear();
 256             }
 257             return false;
 258         }
 259 
 260         private void save(int tw, int th) {
 261             if (tex.isVolatile()) {
 262                 if (savedPixelData == null) {
 263                     savedPixelData = new PixelData(tw, th);
 264                 }
 265                 savedPixelData.save(tex);
 266             }
 267         }
 268     }
 269 
 270     // Saved pixel data used to preserve the image that backs the canvas if the
 271     // RTT is volatile.
 272     private static class PixelData {
 273         private IntBuffer pixels = null;
 274         private boolean validPixels = false;
 275         private int cw, ch;
 276 
 277         private PixelData(int cw, int ch) {
 278             this.cw = cw;
 279             this.ch = ch;
 280             pixels = IntBuffer.allocate(cw*ch);
 281         }
 282 
 283         private void save(RTTexture tex) {
 284             int tw = tex.getContentWidth();
 285             int th = tex.getContentHeight();
 286             if (cw < tw || ch < th) {
 287                 cw = tw;
 288                 ch = th;
 289                 pixels = IntBuffer.allocate(cw*ch);
 290             }
 291             pixels.rewind();
 292             tex.readPixels(pixels);
 293             validPixels = true;
 294         }
 295 
 296         private void restore(Graphics g, int tw, int th) {
 297             if (validPixels) {
 298                 Image img = Image.fromIntArgbPreData(pixels, tw, th);
 299                 ResourceFactory factory = g.getResourceFactory();
 300                 Texture tempTex =
 301                     factory.createTexture(img,
 302                                           Texture.Usage.DEFAULT,
 303                                           Texture.WrapMode.CLAMP_TO_EDGE);
 304                 g.drawTexture(tempTex, 0, 0, tw, th);
 305                 tempTex.dispose();
 306             }
 307         }
 308     }
 309 
 310     private static Blend BLENDER = new MyBlend(Mode.SRC_OVER, null, null);
 311 
 312     private GrowableDataBuffer thebuf;
 313 
 314     private int tw, th;
 315     private int cw, ch;
 316     private RenderBuf cv;
 317     private RenderBuf temp;
 318     private RenderBuf clip;
 319 
 320     private float globalAlpha;
 321     private Blend.Mode blendmode;
 322     private Paint fillPaint, strokePaint;
 323     private float linewidth;
 324     private int linecap, linejoin;
 325     private float miterlimit;
 326     private double[] dashes;
 327     private float dashOffset;
 328     private BasicStroke stroke;
 329     private Path2D path;
 330     private NGText ngtext;
 331     private PrismTextLayout textLayout;
 332     private PGFont pgfont;
 333     private int smoothing;
 334     private int align;
 335     private int baseline;
 336     private Affine2D transform;
 337     private Affine2D inverseTransform;
 338     private boolean inversedirty;
 339     private LinkedList<Path2D> clipStack;
 340     private int clipsRendered;
 341     private boolean clipIsRect;
 342     private Rectangle clipRect;
 343     private Effect effect;
 344     private int arctype;
 345 
 346     static float TEMP_COORDS[] = new float[6];
 347     private static Arc2D TEMP_ARC = new Arc2D();
 348     private static RectBounds TEMP_RECTBOUNDS = new RectBounds();
 349 
 350     public NGCanvas() {
 351         cv = new RenderBuf(InitType.PRESERVE_UPPER_LEFT);
 352         temp = new RenderBuf(InitType.CLEAR);
 353         clip = new RenderBuf(InitType.FILL_WHITE);
 354 
 355         path = new Path2D();
 356         ngtext = new NGText();
 357         textLayout = new PrismTextLayout();
 358         transform = new Affine2D();
 359         clipStack = new LinkedList<Path2D>();
 360         initAttributes();
 361     }
 362 
 363     private void initAttributes() {
 364         globalAlpha = 1.0f;
 365         blendmode = Mode.SRC_OVER;
 366         fillPaint = Color.BLACK;
 367         strokePaint = Color.BLACK;
 368         linewidth = 1.0f;
 369         linecap = BasicStroke.CAP_SQUARE;
 370         linejoin = BasicStroke.JOIN_MITER;
 371         miterlimit = 10f;
 372         dashes = null;
 373         dashOffset = 0.0f;
 374         stroke = null;
 375         path.setWindingRule(Path2D.WIND_NON_ZERO);
 376         // ngtext stores no state between render operations
 377         // textLayout stores no state between render operations
 378         pgfont = (PGFont) Font.getDefault().impl_getNativeFont();
 379         smoothing = SMOOTH_GRAY;
 380         align = ALIGN_LEFT;
 381         baseline = VPos.BASELINE.ordinal();
 382         transform.setToScale(highestPixelScale, highestPixelScale);
 383         clipStack.clear();
 384         resetClip(false);
 385     }
 386 
 387     static final Affine2D TEMP_PATH_TX = new Affine2D();
 388     static final int numCoords[] = { 2, 2, 4, 6, 0 };
 389     Shape untransformedPath = new Shape() {
 390 
 391         @Override
 392         public RectBounds getBounds() {
 393             if (transform.isTranslateOrIdentity()) {
 394                 RectBounds rb = path.getBounds();
 395                 if (transform.isIdentity()) {
 396                     return rb;
 397                 } else {
 398                     float tx = (float) transform.getMxt();
 399                     float ty = (float) transform.getMyt();
 400                     return new RectBounds(rb.getMinX() - tx, rb.getMinY() - ty,
 401                                           rb.getMaxX() - tx, rb.getMaxY() - ty);
 402                 }
 403             }
 404             // We could use Shape.accumulate, but that method optimizes the
 405             // bounds for curves and the optimized code above will simply ask
 406             // the path for its bounds - which in this case of a Path2D would
 407             // simply accumulate all of the coordinates in the buffer.  So,
 408             // we write a simpler accumulator loop here to be consistent with
 409             // the optimized case above.
 410             float x0 = Float.POSITIVE_INFINITY;
 411             float y0 = Float.POSITIVE_INFINITY;
 412             float x1 = Float.NEGATIVE_INFINITY;
 413             float y1 = Float.NEGATIVE_INFINITY;
 414             PathIterator pi = path.getPathIterator(getInverseTransform());
 415             while (!pi.isDone()) {
 416                 int ncoords = numCoords[pi.currentSegment(TEMP_COORDS)];
 417                 for (int i = 0; i < ncoords; i += 2) {
 418                     if (x0 > TEMP_COORDS[i+0]) x0 = TEMP_COORDS[i+0];
 419                     if (x1 < TEMP_COORDS[i+0]) x1 = TEMP_COORDS[i+0];
 420                     if (y0 > TEMP_COORDS[i+1]) y0 = TEMP_COORDS[i+1];
 421                     if (y1 < TEMP_COORDS[i+1]) y1 = TEMP_COORDS[i+1];
 422                 }
 423                 pi.next();
 424             }
 425             return new RectBounds(x0, y0, x1, y1);
 426         }
 427 
 428         @Override
 429         public boolean contains(float x, float y) {
 430             TEMP_COORDS[0] = x;
 431             TEMP_COORDS[1] = y;
 432             transform.transform(TEMP_COORDS, 0, TEMP_COORDS, 0, 1);
 433             x = TEMP_COORDS[0];
 434             y = TEMP_COORDS[1];
 435             return path.contains(x, y);
 436         }
 437 
 438         @Override
 439         public boolean intersects(float x, float y, float w, float h) {
 440             if (transform.isTranslateOrIdentity()) {
 441                 x += transform.getMxt();
 442                 y += transform.getMyt();
 443                 return path.intersects(x, y, w, h);
 444             }
 445             PathIterator pi = path.getPathIterator(getInverseTransform());
 446             int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h);
 447             // int mask = (windingRule == WIND_NON_ZERO ? -1 : 2);
 448             // return (crossings == Shape.RECT_INTERSECTS ||
 449             //             (crossings & mask) != 0);
 450             // with wind == NON_ZERO, then mask == -1 and
 451             // since REC_INTERSECTS != 0, we simplify to:
 452             return (crossings != 0);
 453         }
 454 
 455         @Override
 456         public boolean contains(float x, float y, float w, float h) {
 457             if (transform.isTranslateOrIdentity()) {
 458                 x += transform.getMxt();
 459                 y += transform.getMyt();
 460                 return path.contains(x, y, w, h);
 461             }
 462             PathIterator pi = path.getPathIterator(getInverseTransform());
 463             int crossings = Shape.rectCrossingsForPath(pi, x, y, x+w, y+h);
 464             // int mask = (windingRule == WIND_NON_ZERO ? -1 : 2);
 465             // return (crossings != Shape.RECT_INTERSECTS &&
 466             //             (crossings & mask) != 0);
 467             // with wind == NON_ZERO, then mask == -1 we simplify to:
 468             return (crossings != Shape.RECT_INTERSECTS && crossings != 0);
 469         }
 470 
 471         public BaseTransform getCombinedTransform(BaseTransform tx) {
 472             if (transform.isIdentity()) return tx;
 473             if (transform.equals(tx)) return null;
 474             Affine2D inv = getInverseTransform();
 475             if (tx == null || tx.isIdentity()) return inv;
 476             TEMP_PATH_TX.setTransform(tx);
 477             TEMP_PATH_TX.concatenate(inv);
 478             return TEMP_PATH_TX;
 479         }
 480 
 481         @Override
 482         public PathIterator getPathIterator(BaseTransform tx) {
 483             return path.getPathIterator(getCombinedTransform(tx));
 484         }
 485 
 486         @Override
 487         public PathIterator getPathIterator(BaseTransform tx, float flatness) {
 488             return path.getPathIterator(getCombinedTransform(tx), flatness);
 489         }
 490 
 491         @Override
 492         public Shape copy() {
 493             throw new UnsupportedOperationException("Not supported yet.");
 494         }
 495     };
 496 
 497     private Affine2D getInverseTransform() {
 498         if (inverseTransform == null) {
 499             inverseTransform = new Affine2D();
 500             inversedirty = true;
 501         }
 502         if (inversedirty) {
 503             inverseTransform.setTransform(transform);
 504             try {
 505                 inverseTransform.invert();
 506             } catch (NoninvertibleTransformException e) {
 507                 inverseTransform.setToScale(0, 0);
 508             }
 509             inversedirty = false;
 510         }
 511         return inverseTransform;
 512     }
 513 
 514     @Override
 515     protected boolean hasOverlappingContents() {
 516         return true;
 517     }
 518 
 519     private static void shapebounds(Shape shape, RectBounds bounds,
 520                                     BaseTransform transform)
 521     {
 522         TEMP_COORDS[0] = TEMP_COORDS[1] = Float.POSITIVE_INFINITY;
 523         TEMP_COORDS[2] = TEMP_COORDS[3] = Float.NEGATIVE_INFINITY;
 524         Shape.accumulate(TEMP_COORDS, shape, transform);
 525         bounds.setBounds(TEMP_COORDS[0], TEMP_COORDS[1],
 526                          TEMP_COORDS[2], TEMP_COORDS[3]);
 527     }
 528 
 529     private static void strokebounds(BasicStroke stroke, Shape shape,
 530                                      RectBounds bounds, BaseTransform transform)
 531     {
 532         TEMP_COORDS[0] = TEMP_COORDS[1] = Float.POSITIVE_INFINITY;
 533         TEMP_COORDS[2] = TEMP_COORDS[3] = Float.NEGATIVE_INFINITY;
 534         stroke.accumulateShapeBounds(TEMP_COORDS, shape, transform);
 535         bounds.setBounds(TEMP_COORDS[0], TEMP_COORDS[1],
 536                             TEMP_COORDS[2], TEMP_COORDS[3]);
 537     }
 538 
 539     private static void runOnRenderThread(final Runnable r) {
 540         // We really need a standard mechanism to detect the render thread !
 541         if (Thread.currentThread().getName().startsWith("QuantumRenderer")) {
 542             r.run();
 543         } else {
 544             FutureTask<Void> f = new FutureTask<Void>(r, null);
 545             Toolkit.getToolkit().addRenderJob(new RenderJob(f));
 546             try {
 547                 // block until job is complete
 548                 f.get();
 549             } catch (ExecutionException ex) {
 550                 throw new AssertionError(ex);
 551             } catch (InterruptedException ex) {
 552                 // ignore; recovery is impossible
 553             }
 554         }
 555     }
 556 
 557     private boolean printedCanvas(Graphics g) {
 558        final RTTexture localTex = cv.tex;
 559        if (!(g instanceof PrinterGraphics) || localTex  == null) {
 560           return false;
 561         }
 562         ResourceFactory factory = g.getResourceFactory();
 563         boolean isCompatTex = factory.isCompatibleTexture(localTex);
 564         if (isCompatTex) {
 565             return false;
 566         }
 567 
 568         final int tw = localTex.getContentWidth();
 569         final int th = localTex.getContentHeight();
 570         final RTTexture tmpTex =
 571               factory.createRTTexture(tw, th, WrapMode.CLAMP_TO_ZERO);
 572         final Graphics texg = tmpTex.createGraphics();
 573         texg.setCompositeMode(CompositeMode.SRC);
 574         if (cv.savedPixelData == null) {
 575             final PixelData pd = new PixelData(cw, ch);
 576             runOnRenderThread(() -> {
 577               pd.save(localTex);
 578               pd.restore(texg, tw, th);
 579             });
 580         } else {
 581             cv.savedPixelData.restore(texg, tw, th);
 582         }
 583         g.drawTexture(tmpTex, 0, 0, tw, th);
 584         tmpTex.unlock();
 585         tmpTex.dispose();
 586         return true;        
 587     }
 588 
 589     @Override
 590     protected void renderContent(Graphics g) {
 591         if (printedCanvas(g)) return;
 592         initCanvas(g);
 593         if (cv.tex != null) {
 594             if (thebuf != null) {
 595                 renderStream(thebuf);
 596                 GrowableDataBuffer.returnBuffer(thebuf);
 597                 thebuf = null;
 598             }
 599             float dw = tw / highestPixelScale;
 600             float dh = th / highestPixelScale;
 601             g.drawTexture(cv.tex,
 602                           0, 0, dw, dh,
 603                           0, 0, tw, th);
 604             // Must save the pixels every frame if RTT is volatile.
 605             cv.save(tw, th);
 606         }
 607         this.temp.g = this.clip.g = this.cv.g = null;
 608     }
 609 
 610     @Override
 611     public void renderForcedContent(Graphics gOptional) {
 612         if (thebuf != null) {
 613             initCanvas(gOptional);
 614             if (cv.tex != null) {
 615                 renderStream(thebuf);
 616                 GrowableDataBuffer.returnBuffer(thebuf);
 617                 thebuf = null;
 618                 cv.save(tw, th);
 619             }
 620             this.temp.g = this.clip.g = this.cv.g = null;
 621         }
 622     }
 623 
 624     private void initCanvas(Graphics g) {
 625         if (tw <= 0 || th <= 0) {
 626             cv.dispose();
 627             return;
 628         }
 629         if (cv.validate(g, tw, th)) {
 630             // If the texture was recreated then we add a permanent
 631             // "useful" and extra "lock" status to it.
 632             cv.tex.contentsUseful();
 633             cv.tex.makePermanent();
 634             cv.tex.lock();
 635         }
 636     }
 637 
 638     private void clearCanvas(int x, int y, int w, int h) {
 639         cv.g.setCompositeMode(CompositeMode.CLEAR);
 640         cv.g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
 641         cv.g.fillQuad(x, y, x+w, y+h);
 642         cv.g.setCompositeMode(CompositeMode.SRC_OVER);
 643     }
 644 
 645     private void resetClip(boolean andDispose) {
 646         if (andDispose) clip.dispose();
 647         clipsRendered = 0;
 648         clipIsRect = true;
 649         clipRect = null;
 650     }
 651 
 652     private static final float CLIPRECT_TOLERANCE = 1.0f / 256.0f;
 653     private static final Rectangle TEMP_RECT = new Rectangle();
 654     private boolean initClip() {
 655         boolean clipValidated;
 656         if (clipIsRect) {
 657             clipValidated = false;
 658         } else {
 659             clipValidated = true;
 660             if (clip.validate(cv.g, tw, th)) {
 661                 clip.tex.contentsUseful();
 662                 // Reset, but do not dispose - we just validated (and cleared) it...
 663                 resetClip(false);
 664             }
 665         }
 666         int clipSize = clipStack.size();
 667         while (clipsRendered < clipSize) {
 668             Path2D clippath = clipStack.get(clipsRendered++);
 669             if (clipIsRect) {
 670                 if (clippath.checkAndGetIntRect(TEMP_RECT, CLIPRECT_TOLERANCE)) {
 671                     if (clipRect == null) {
 672                         clipRect = new Rectangle(TEMP_RECT);
 673                     } else {
 674                         clipRect.intersectWith(TEMP_RECT);
 675                     }
 676                     continue;
 677                 }
 678                 clipIsRect = false;
 679                 if (!clipValidated) {
 680                     clipValidated = true;
 681                     if (clip.validate(cv.g, tw, th)) {
 682                         clip.tex.contentsUseful();
 683                         // No need to reset, this is our first fill.
 684                     }
 685                 }
 686                 if (clipRect != null) {
 687                     renderClip(new RoundRectangle2D(clipRect.x, clipRect.y,
 688                                                     clipRect.width, clipRect.height,
 689                                                     0, 0));
 690                 }
 691             }
 692             shapebounds(clippath, TEMP_RECTBOUNDS, BaseTransform.IDENTITY_TRANSFORM);
 693             TEMP_RECT.setBounds(TEMP_RECTBOUNDS);
 694             if (clipRect == null) {
 695                 clipRect = new Rectangle(TEMP_RECT);
 696             } else {
 697                 clipRect.intersectWith(TEMP_RECT);
 698             }
 699             renderClip(clippath);
 700         }
 701         if (clipValidated && clipIsRect) {
 702             clip.tex.unlock();
 703         }
 704         return !clipIsRect;
 705     }
 706 
 707     private void renderClip(Shape clippath) {
 708         temp.validate(cv.g, tw, th);
 709         temp.g.setPaint(Color.WHITE);
 710         temp.g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
 711         temp.g.fill(clippath);
 712         blendAthruBintoC(temp, Mode.SRC_IN, clip, null, CompositeMode.SRC, clip);
 713         temp.tex.unlock();
 714     }
 715 
 716     private Rectangle applyEffectOnAintoC(Effect definput,
 717                                           Effect effect,
 718                                           BaseTransform transform,
 719                                           Rectangle outputClip,
 720                                           CompositeMode comp,
 721                                           RenderBuf destbuf)
 722     {
 723         FilterContext fctx =
 724             PrFilterContext.getInstance(destbuf.tex.getAssociatedScreen());
 725         ImageData id =
 726             effect.filter(fctx, transform, outputClip, null, definput);
 727         Rectangle r = id.getUntransformedBounds();
 728         Filterable f = id.getUntransformedImage();
 729         Texture tex = ((PrTexture) f).getTextureObject();
 730         destbuf.g.setTransform(id.getTransform());
 731         destbuf.g.setCompositeMode(comp);
 732         destbuf.g.drawTexture(tex, r.x, r.y, r.width, r.height);
 733         destbuf.g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
 734         destbuf.g.setCompositeMode(CompositeMode.SRC_OVER);
 735         Rectangle resultBounds = id.getTransformedBounds(outputClip);
 736         id.unref();
 737         return resultBounds;
 738     }
 739 
 740     private void blendAthruBintoC(RenderBuf drawbuf,
 741                                   Mode mode,
 742                                   RenderBuf clipbuf,
 743                                   RectBounds bounds,
 744                                   CompositeMode comp,
 745                                   RenderBuf destbuf)
 746     {
 747         BLENDER.setTopInput(drawbuf.input);
 748         BLENDER.setBottomInput(clipbuf.input);
 749         BLENDER.setMode(mode);
 750         Rectangle blendclip;
 751         if (bounds != null) {
 752             blendclip = new Rectangle(bounds);
 753         } else {
 754             blendclip = null;
 755         }
 756         applyEffectOnAintoC(null, BLENDER,
 757                             BaseTransform.IDENTITY_TRANSFORM, blendclip,
 758                             comp, destbuf);
 759     }
 760 
 761     private void setupFill(Graphics gr) {
 762         gr.setPaint(fillPaint);
 763     }
 764 
 765     private BasicStroke getStroke() {
 766         if (stroke == null) {
 767             stroke = new BasicStroke(linewidth, linecap, linejoin,
 768                                      miterlimit, dashes, dashOffset);
 769         }
 770         return stroke;
 771     }
 772 
 773     private void setupStroke(Graphics gr) {
 774         gr.setStroke(getStroke());
 775         gr.setPaint(strokePaint);
 776     }
 777 
 778     private static final int prcaps[] = {
 779         BasicStroke.CAP_BUTT,
 780         BasicStroke.CAP_ROUND,
 781         BasicStroke.CAP_SQUARE,
 782     };
 783     private static final int prjoins[] = {
 784         BasicStroke.JOIN_MITER,
 785         BasicStroke.JOIN_ROUND,
 786         BasicStroke.JOIN_BEVEL,
 787     };
 788     private static final int prbases[] = {
 789         VPos.TOP.ordinal(),
 790         VPos.CENTER.ordinal(),
 791         VPos.BASELINE.ordinal(),
 792         VPos.BOTTOM.ordinal(),
 793     };
 794     private static final Affine2D TEMP_TX = new Affine2D();
 795     private void renderStream(GrowableDataBuffer buf) {
 796         while (buf.hasValues()) {
 797             int token = buf.getByte();
 798             switch (token) {
 799                 case RESET:
 800                     initAttributes();
 801                     // RESET is always followed by SET_DIMS
 802                     // Setting cwh = twh avoids unnecessary double clears
 803                     this.cw = this.tw;
 804                     this.ch = this.th;
 805                     clearCanvas(0, 0, this.tw, this.th);
 806                     break;
 807                 case SET_DIMS:
 808                     int neww = (int) Math.ceil(buf.getFloat() * highestPixelScale);
 809                     int newh = (int) Math.ceil(buf.getFloat() * highestPixelScale);
 810                     int clearx = Math.min(neww, this.cw);
 811                     int cleary = Math.min(newh, this.ch);
 812                     if (clearx < this.tw) {
 813                         // tw is set to the final width, we simulate all of
 814                         // the intermediate changes in size by making sure
 815                         // that all pixels outside of any size change are
 816                         // cleared at the stream point where they happened
 817                         clearCanvas(clearx, 0, this.tw-clearx, this.th);
 818                     }
 819                     if (cleary < this.th) {
 820                         // th is set to the final width, we simulate all of
 821                         // the intermediate changes in size by making sure
 822                         // that all pixels outside of any size change are
 823                         // cleared at the stream point where they happened
 824                         clearCanvas(0, cleary, this.tw, this.th-cleary);
 825                     }
 826                     this.cw = neww;
 827                     this.ch = newh;
 828                     break;
 829                 case PATHSTART:
 830                     path.reset();
 831                     break;
 832                 case MOVETO:
 833                     path.moveTo(buf.getFloat(), buf.getFloat());
 834                     break;
 835                 case LINETO:
 836                     path.lineTo(buf.getFloat(), buf.getFloat());
 837                     break;
 838                 case QUADTO:
 839                     path.quadTo(buf.getFloat(), buf.getFloat(),
 840                                 buf.getFloat(), buf.getFloat());
 841                     break;
 842                 case CUBICTO:
 843                     path.curveTo(buf.getFloat(), buf.getFloat(),
 844                                  buf.getFloat(), buf.getFloat(),
 845                                  buf.getFloat(), buf.getFloat());
 846                     break;
 847                 case CLOSEPATH:
 848                     path.closePath();
 849                     break;
 850                 case PATHEND:
 851                     if (highestPixelScale != 1.0f) {
 852                         TEMP_TX.setToScale(highestPixelScale, highestPixelScale);
 853                         path.transform(TEMP_TX);
 854                     }
 855                     break;
 856                 case PUSH_CLIP:
 857                 {
 858                     Path2D clippath = (Path2D) buf.getObject();
 859                     if (highestPixelScale != 1.0f) {
 860                         TEMP_TX.setToScale(highestPixelScale, highestPixelScale);
 861                         clippath.transform(TEMP_TX);
 862                     }
 863                     clipStack.addLast(clippath);
 864                     break;
 865                 }
 866                 case POP_CLIP:
 867                     // Let it be recreated when next needed
 868                     resetClip(true);
 869                     clipStack.removeLast();
 870                     break;
 871                 case ARC_TYPE:
 872                 {
 873                     byte type = buf.getByte();
 874                     switch (type) {
 875                         case ARC_OPEN:  arctype = Arc2D.OPEN;  break;
 876                         case ARC_CHORD: arctype = Arc2D.CHORD; break;
 877                         case ARC_PIE:   arctype = Arc2D.PIE;   break;
 878                     }
 879                     break;
 880                 }
 881                 case PUT_ARGB:
 882                 {
 883                     float dx1 = buf.getInt();
 884                     float dy1 = buf.getInt();
 885                     int argb = buf.getInt();
 886                     Graphics gr = cv.g;
 887                     gr.setExtraAlpha(1.0f);
 888                     gr.setCompositeMode(CompositeMode.SRC);
 889                     gr.setTransform(BaseTransform.IDENTITY_TRANSFORM);
 890                     dx1 *= highestPixelScale;
 891                     dy1 *= highestPixelScale;
 892                     float a = ((argb) >>> 24) / 255.0f;
 893                     float r = (((argb) >> 16) & 0xff) / 255.0f;
 894                     float g = (((argb) >>  8) & 0xff) / 255.0f;
 895                     float b = (((argb)      ) & 0xff) / 255.0f;
 896                     gr.setPaint(new Color(r, g, b, a));
 897                     // Note that we cannot use fillRect here because SRC
 898                     // mode does not interact well with antialiasing.
 899                     // fillQuad does hard edges which matches the concept
 900                     // of setting adjacent abutting, non-overlapping "pixels"
 901                     gr.fillQuad(dx1, dy1, dx1+highestPixelScale, dy1+highestPixelScale);
 902                     gr.setCompositeMode(CompositeMode.SRC_OVER);
 903                     break;
 904                 }
 905                 case PUT_ARGBPRE_BUF:
 906                 {
 907                     float dx1 = buf.getInt();
 908                     float dy1 = buf.getInt();
 909                     int w  = buf.getInt();
 910                     int h  = buf.getInt();
 911                     byte[] data = (byte[]) buf.getObject();
 912                     Image img = Image.fromByteBgraPreData(data, w, h);
 913                     Graphics gr = cv.g;
 914                     ResourceFactory factory = gr.getResourceFactory();
 915                     Texture tex =
 916                         factory.getCachedTexture(img, Texture.WrapMode.CLAMP_TO_EDGE);
 917                     gr.setTransform(BaseTransform.IDENTITY_TRANSFORM);
 918                     gr.setCompositeMode(CompositeMode.SRC);
 919                     float dx2 = dx1 + w;
 920                     float dy2 = dy1 + h;
 921                     dx1 *= highestPixelScale;
 922                     dy1 *= highestPixelScale;
 923                     dx2 *= highestPixelScale;
 924                     dy2 *= highestPixelScale;
 925                     gr.drawTexture(tex,
 926                                    dx1, dy1, dx2, dy2,
 927                                    0, 0, w, h);
 928                     tex.contentsNotUseful();
 929                     tex.unlock();
 930                     gr.setCompositeMode(CompositeMode.SRC_OVER);
 931                     break;
 932                 }
 933                 case TRANSFORM:
 934                 {
 935                     double mxx = buf.getDouble() * highestPixelScale;
 936                     double mxy = buf.getDouble() * highestPixelScale;
 937                     double mxt = buf.getDouble() * highestPixelScale;
 938                     double myx = buf.getDouble() * highestPixelScale;
 939                     double myy = buf.getDouble() * highestPixelScale;
 940                     double myt = buf.getDouble() * highestPixelScale;
 941                     transform.setTransform(mxx, myx, mxy, myy, mxt, myt);
 942                     inversedirty = true;
 943                     break;
 944                 }
 945                 case GLOBAL_ALPHA:
 946                     globalAlpha = buf.getFloat();
 947                     break;
 948                 case FILL_RULE:
 949                     if (buf.getByte() == FILL_RULE_NON_ZERO) {
 950                         path.setWindingRule(Path2D.WIND_NON_ZERO);
 951                     } else {
 952                         path.setWindingRule(Path2D.WIND_EVEN_ODD);
 953                     }
 954                     break;
 955                 case COMP_MODE:
 956                     blendmode = (Blend.Mode)buf.getObject();
 957                     break;
 958                 case FILL_PAINT:
 959                     fillPaint = (Paint) buf.getObject();
 960                     break;
 961                 case STROKE_PAINT:
 962                     strokePaint = (Paint) buf.getObject();
 963                     break;
 964                 case LINE_WIDTH:
 965                     linewidth = buf.getFloat();
 966                     stroke = null;
 967                     break;
 968                 case LINE_CAP:
 969                     linecap = prcaps[buf.getUByte()];
 970                     stroke = null;
 971                     break;
 972                 case LINE_JOIN:
 973                     linejoin = prjoins[buf.getUByte()];
 974                     stroke = null;
 975                     break;
 976                 case MITER_LIMIT:
 977                     miterlimit = buf.getFloat();
 978                     stroke = null;
 979                     break;
 980                 case DASH_ARRAY:
 981                     dashes = (double[]) buf.getObject();
 982                     stroke = null;
 983                     break;
 984                 case DASH_OFFSET:
 985                     dashOffset = buf.getFloat();
 986                     stroke = null;
 987                     break;
 988                 case FONT:
 989                     pgfont = (PGFont) buf.getObject();
 990                     break;
 991                 case FONT_SMOOTH:
 992                     smoothing = buf.getUByte();
 993                     break;
 994                 case TEXT_ALIGN:
 995                     align = buf.getUByte();
 996                     break;
 997                 case TEXT_BASELINE:
 998                     baseline = prbases[buf.getUByte()];
 999                     break;
1000                 case FX_APPLY_EFFECT:
1001                 {
1002                     Effect e = (Effect) buf.getObject();
1003                     RenderBuf dest = clipStack.isEmpty() ? cv : temp;
1004                     BaseTransform tx;
1005                     if (highestPixelScale != 1.0f) {
1006                         TEMP_TX.setToScale(highestPixelScale, highestPixelScale);
1007                         tx = TEMP_TX;
1008                         cv.input.setPixelScale(highestPixelScale);
1009                     } else {
1010                         tx = BaseTransform.IDENTITY_TRANSFORM;
1011                     }
1012                     applyEffectOnAintoC(cv.input, e,
1013                                         tx, null,
1014                                         CompositeMode.SRC, dest);
1015                     cv.input.setPixelScale(1.0f);
1016                     if (dest != cv) {
1017                         blendAthruBintoC(dest, Mode.SRC_IN, clip,
1018                                          null, CompositeMode.SRC, cv);
1019                     }
1020                     break;
1021                 }
1022                 case EFFECT:
1023                     effect = (Effect) buf.getObject();
1024                     break;
1025                 case FILL_PATH:
1026                 case STROKE_PATH:
1027                 case STROKE_LINE:
1028                 case FILL_RECT:
1029                 case CLEAR_RECT:
1030                 case STROKE_RECT:
1031                 case FILL_OVAL:
1032                 case STROKE_OVAL:
1033                 case FILL_ROUND_RECT:
1034                 case STROKE_ROUND_RECT:
1035                 case FILL_ARC:
1036                 case STROKE_ARC:
1037                 case DRAW_IMAGE:
1038                 case DRAW_SUBIMAGE:
1039                 case FILL_TEXT:
1040                 case STROKE_TEXT:
1041                 {
1042                     RenderBuf dest;
1043                     boolean tempvalidated;
1044                     boolean clipvalidated = initClip();
1045                     if (clipvalidated) {
1046                         temp.validate(cv.g, tw, th);
1047                         tempvalidated = true;
1048                         dest = temp;
1049                     } else if (blendmode != Blend.Mode.SRC_OVER) {
1050                         temp.validate(cv.g, tw, th);
1051                         tempvalidated = true;
1052                         dest = temp;
1053                     } else {
1054                         tempvalidated = false;
1055                         dest = cv;
1056                     }
1057                     if (effect != null) {
1058                         buf.save();
1059                         handleRenderOp(token, buf, null, TEMP_RECTBOUNDS);
1060                         RenderInput ri =
1061                             new RenderInput(token, buf, transform, TEMP_RECTBOUNDS);
1062                         // If we are rendering to cv then we need the results of
1063                         // the effect to be applied "SRC_OVER" onto the canvas.
1064                         // If we are rendering to temp then either SRC or SRC_OVER
1065                         // would work since we know it would have been freshly
1066                         // erased above, but using the more common SRC_OVER may save
1067                         // having to update the hardware blend equations.
1068                         Rectangle resultBounds =
1069                             applyEffectOnAintoC(ri, effect,
1070                                                 transform, clipRect,
1071                                                 CompositeMode.SRC_OVER, dest);
1072                         if (dest != cv) {
1073                             TEMP_RECTBOUNDS.setBounds(resultBounds.x, resultBounds.y,
1074                                                       resultBounds.x + resultBounds.width,
1075                                                       resultBounds.y + resultBounds.height);
1076                         }
1077                     } else {
1078                         Graphics g = dest.g;
1079                         g.setExtraAlpha(globalAlpha);
1080                         g.setTransform(transform);
1081                         g.setClipRect(clipRect);
1082                         // If we are not rendering directly to the canvas then
1083                         // we need to save the bounds for the later stages.
1084                         RectBounds optSaveBounds =
1085                             (dest != cv) ? TEMP_RECTBOUNDS : null;
1086                         handleRenderOp(token, buf, g, optSaveBounds);
1087                         g.setClipRect(null);
1088                     }
1089                     if (clipvalidated) {
1090                         CompositeMode compmode;
1091                         if (blendmode == Blend.Mode.SRC_OVER) {
1092                             // For the SRC_OVER case we can point the clip
1093                             // operation directly to the screen with the Prism
1094                             // SRC_OVER composite mode.
1095                             dest = cv;
1096                             compmode = CompositeMode.SRC_OVER;
1097                         } else {
1098                             // Here we are blending the rendered pixels that
1099                             // were output to the temp buffer above against the
1100                             // pixels of the canvas and we need to put them
1101                             // back into the temp buffer.  We must use SRC
1102                             // mode here so that the erased (or reduced) pixels
1103                             // actually get reduced to their new alpha.
1104                             // assert: dest == temp;
1105                             compmode = CompositeMode.SRC;
1106                         }
1107                         if (clipRect != null) {
1108                             TEMP_RECTBOUNDS.intersectWith(clipRect);
1109                         }
1110                         if (!TEMP_RECTBOUNDS.isEmpty()) {
1111                             if (dest == cv && cv.g instanceof MaskTextureGraphics) {
1112                                 MaskTextureGraphics mtg = (MaskTextureGraphics) cv.g;
1113                                 int dx = (int) Math.floor(TEMP_RECTBOUNDS.getMinX());
1114                                 int dy = (int) Math.floor(TEMP_RECTBOUNDS.getMinY());
1115                                 int dw = (int) Math.ceil(TEMP_RECTBOUNDS.getMaxX()) - dx;
1116                                 int dh = (int) Math.ceil(TEMP_RECTBOUNDS.getMaxY()) - dy;
1117                                 mtg.drawPixelsMasked(temp.tex, clip.tex,
1118                                                      dx, dy, dw, dh,
1119                                                      dx, dy, dx, dy);
1120                             } else {
1121                                 blendAthruBintoC(temp, Mode.SRC_IN, clip,
1122                                                  TEMP_RECTBOUNDS, compmode, dest);
1123                             }
1124                         }
1125                     }
1126                     if (blendmode != Blend.Mode.SRC_OVER) {
1127                         // We always use SRC mode here because the results of
1128                         // the blend operation are final and must replace
1129                         // the associated pixel in the canvas with no further
1130                         // blending math.
1131                         if (clipRect != null) {
1132                             TEMP_RECTBOUNDS.intersectWith(clipRect);
1133                         }
1134                         blendAthruBintoC(temp, blendmode, cv,
1135                                          TEMP_RECTBOUNDS, CompositeMode.SRC, cv);
1136                     }
1137                     if (clipvalidated) {
1138                         clip.tex.unlock();
1139                     }
1140                     if (tempvalidated) {
1141                         temp.tex.unlock();
1142                     }
1143                     break;
1144                 }
1145                 default:
1146                     throw new InternalError("Unrecognized PGCanvas token: "+token);
1147             }
1148         }
1149     }
1150 
1151     /**
1152      * Calculate bounds and/or render one single rendering operation.
1153      * All of the data for the rendering operation should be consumed
1154      * so that the buffer is left at the next token in the stream.
1155      *
1156      * @param token the stream token for the rendering op
1157      * @param buf the GrowableDataBuffer to get rendering info from
1158      * @param gr  the Graphics to render to, if not null
1159      * @param bounds the RectBounds to accumulate bounds into, if not null
1160      */
1161     public void handleRenderOp(int token, GrowableDataBuffer buf,
1162                                Graphics gr, RectBounds bounds)
1163     {
1164         boolean strokeBounds = false;
1165         boolean transformBounds = false;
1166         switch (token) {
1167             case FILL_PATH:
1168             {
1169                 if (bounds != null) {
1170                     shapebounds(path, bounds, BaseTransform.IDENTITY_TRANSFORM);
1171                 }
1172                 if (gr != null) {
1173                     setupFill(gr);
1174                     gr.fill(untransformedPath);
1175                 }
1176                 break;
1177             }
1178             case STROKE_PATH:
1179             {
1180                 if (bounds != null) {
1181                     strokebounds(getStroke(), untransformedPath, bounds, transform);
1182                 }
1183                 if (gr != null) {
1184                     setupStroke(gr);
1185                     gr.draw(untransformedPath);
1186                 }
1187                 break;
1188             }
1189             case STROKE_LINE:
1190             {
1191                 float x1 = buf.getFloat();
1192                 float y1 = buf.getFloat();
1193                 float x2 = buf.getFloat();
1194                 float y2 = buf.getFloat();
1195                 if (bounds != null) {
1196                     bounds.setBoundsAndSort(x1, y1, x2, y2);
1197                     strokeBounds = true;
1198                     transformBounds = true;
1199                 }
1200                 if (gr != null) {
1201                     setupStroke(gr);
1202                     gr.drawLine(x1, y1, x2, y2);
1203                 }
1204                 break;
1205             }
1206             case STROKE_RECT:
1207             case STROKE_OVAL:
1208                 strokeBounds = true;
1209             case FILL_RECT:
1210             case CLEAR_RECT:
1211             case FILL_OVAL:
1212             {
1213                 float x = buf.getFloat();
1214                 float y = buf.getFloat();
1215                 float w = buf.getFloat();
1216                 float h = buf.getFloat();
1217                 if (bounds != null) {
1218                     bounds.setBounds(x, y, x+w, y+h);
1219                     transformBounds = true;
1220                 }
1221                 if (gr != null) {
1222                     switch (token) {
1223                         case FILL_RECT:
1224                             setupFill(gr);
1225                             gr.fillRect(x, y, w, h);
1226                             break;
1227                         case FILL_OVAL:
1228                             setupFill(gr);
1229                             gr.fillEllipse(x, y, w, h);
1230                             break;
1231                         case STROKE_RECT:
1232                             setupStroke(gr);
1233                             gr.drawRect(x, y, w, h);
1234                             break;
1235                         case STROKE_OVAL:
1236                             setupStroke(gr);
1237                             gr.drawEllipse(x, y, w, h);
1238                             break;
1239                         case CLEAR_RECT:
1240                             gr.setCompositeMode(CompositeMode.CLEAR);
1241                             gr.fillRect(x, y, w, h);
1242                             gr.setCompositeMode(CompositeMode.SRC_OVER);
1243                             break;
1244                     }
1245                 }
1246                 break;
1247             }
1248             case STROKE_ROUND_RECT:
1249                 strokeBounds = true;
1250             case FILL_ROUND_RECT:
1251             {
1252                 float x = buf.getFloat();
1253                 float y = buf.getFloat();
1254                 float w = buf.getFloat();
1255                 float h = buf.getFloat();
1256                 float aw = buf.getFloat();
1257                 float ah = buf.getFloat();
1258                 if (bounds != null) {
1259                     bounds.setBounds(x, y, x+w, y+h);
1260                     transformBounds = true;
1261                 }
1262                 if (gr != null) {
1263                     if (token == FILL_ROUND_RECT) {
1264                         setupFill(gr);
1265                         gr.fillRoundRect(x, y, w, h, aw, ah);
1266                     } else {
1267                         setupStroke(gr);
1268                         gr.drawRoundRect(x, y, w, h, aw, ah);
1269                     }
1270                 }
1271                 break;
1272             }
1273             case FILL_ARC:
1274             case STROKE_ARC:
1275             {
1276                 float x = buf.getFloat();
1277                 float y = buf.getFloat();
1278                 float w = buf.getFloat();
1279                 float h = buf.getFloat();
1280                 float as = buf.getFloat();
1281                 float ae = buf.getFloat();
1282                 TEMP_ARC.setArc(x, y, w, h, as, ae, arctype);
1283                 if (token == FILL_ARC) {
1284                     if (bounds != null) {
1285                         shapebounds(TEMP_ARC, bounds, transform);
1286                     }
1287                     if (gr != null) {
1288                         setupFill(gr);
1289                         gr.fill(TEMP_ARC);
1290                     }
1291                 } else {
1292                     if (bounds != null) {
1293                         strokebounds(getStroke(), TEMP_ARC, bounds, transform);
1294                     }
1295                     if (gr != null) {
1296                         setupStroke(gr);
1297                         gr.draw(TEMP_ARC);
1298                     }
1299                 }
1300                 break;
1301             }
1302             case DRAW_IMAGE:
1303             case DRAW_SUBIMAGE:
1304             {
1305                 float dx = buf.getFloat();
1306                 float dy = buf.getFloat();
1307                 float dw = buf.getFloat();
1308                 float dh = buf.getFloat();
1309                 Image img = (Image) buf.getObject();
1310                 float sx, sy, sw, sh;
1311                 if (token == DRAW_IMAGE) {
1312                     sx = sy = 0f;
1313                     sw = img.getWidth();
1314                     sh = img.getHeight();
1315                 } else {
1316                     sx = buf.getFloat();
1317                     sy = buf.getFloat();
1318                     sw = buf.getFloat();
1319                     sh = buf.getFloat();
1320                     float ps = img.getPixelScale();
1321                     if (ps != 1.0f) {
1322                         sx *= ps;
1323                         sy *= ps;
1324                         sw *= ps;
1325                         sh *= ps;
1326                     }
1327                 }
1328                 if (bounds != null) {
1329                     bounds.setBounds(dx, dy, dx+dw, dy+dh);
1330                     transformBounds = true;
1331                 }
1332                 if (gr != null) {
1333                     ResourceFactory factory = gr.getResourceFactory();
1334                     Texture tex =
1335                         factory.getCachedTexture(img, Texture.WrapMode.CLAMP_TO_EDGE);
1336                     gr.drawTexture(tex,
1337                                    dx, dy, dx+dw, dy+dh,
1338                                    sx, sy, sx+sw, sy+sh);
1339                     tex.unlock();
1340                 }
1341                 break;
1342             }
1343             case FILL_TEXT:
1344             case STROKE_TEXT:
1345             {
1346                 float x = buf.getFloat();
1347                 float y = buf.getFloat();
1348                 float maxWidth = buf.getFloat();
1349                 boolean rtl = buf.getBoolean();
1350                 String string = (String) buf.getObject();
1351                 int dir = rtl ? PrismTextLayout.DIRECTION_RTL :
1352                                 PrismTextLayout.DIRECTION_LTR;
1353 
1354                 textLayout.setContent(string, pgfont);
1355                 textLayout.setAlignment(align);
1356                 textLayout.setDirection(dir);
1357                 float xAlign = 0, yAlign = 0;
1358                 BaseBounds layoutBounds = textLayout.getBounds();
1359                 float layoutWidth = layoutBounds.getWidth();
1360                 float layoutHeight = layoutBounds.getHeight();
1361                 switch (align) {
1362                     case ALIGN_RIGHT: xAlign = layoutWidth; break;
1363                     case ALIGN_CENTER: xAlign = layoutWidth / 2; break;
1364                 }
1365                 switch (baseline) {
1366                     case BASE_ALPHABETIC: yAlign = -layoutBounds.getMinY(); break;
1367                     case BASE_MIDDLE: yAlign = layoutHeight / 2; break;
1368                     case BASE_BOTTOM: yAlign = layoutHeight; break;
1369                 }
1370                 float scaleX = 1;
1371                 float layoutX = 0;
1372                 float layoutY = y - yAlign;
1373                 if (maxWidth > 0.0 && layoutWidth > maxWidth) {
1374                     float sx = maxWidth / layoutWidth;
1375                     if (rtl) {
1376                         layoutX = -((x + maxWidth) / sx - xAlign);
1377                         scaleX = -sx;
1378                     } else {
1379                         layoutX = x / sx - xAlign;
1380                         scaleX = sx;
1381                     }
1382                 } else {
1383                     if (rtl) {
1384                         layoutX = -(x - xAlign + layoutWidth);
1385                         scaleX = -1;
1386                     } else {
1387                         layoutX = x - xAlign;
1388                     }
1389                 }
1390                 if (bounds != null) {
1391                     computeTextLayoutBounds(bounds, transform, scaleX, layoutX, layoutY, token);
1392                 }
1393                 if (gr != null) {
1394                     if (scaleX != 1) {
1395                         gr.scale(scaleX, 1);
1396                     }
1397                     ngtext.setLayoutLocation(-layoutX, -layoutY);
1398                     if (token == FILL_TEXT) {
1399                         ngtext.setMode(NGShape.Mode.FILL);
1400                         ngtext.setFillPaint(fillPaint);
1401                         if (fillPaint.isProportional() || smoothing == SMOOTH_LCD) {
1402                             RectBounds textBounds = new RectBounds();
1403                             computeTextLayoutBounds(textBounds, BaseTransform.IDENTITY_TRANSFORM,
1404                                                     1, layoutX, layoutY, token);
1405                             ngtext.setContentBounds(textBounds);
1406                         }
1407                     } else {
1408                         // SMOOTH_LCD does not apply to stroked text
1409                         if (strokePaint.isProportional() /* || smoothing == SMOOTH_LCD */) {
1410                             RectBounds textBounds = new RectBounds();
1411                             computeTextLayoutBounds(textBounds, BaseTransform.IDENTITY_TRANSFORM,
1412                                                     1, layoutX, layoutY, token);
1413                             ngtext.setContentBounds(textBounds);
1414                         }
1415                         ngtext.setMode(NGShape.Mode.STROKE);
1416                         ngtext.setDrawStroke(getStroke());
1417                         ngtext.setDrawPaint(strokePaint);
1418                     }
1419                     ngtext.setFont(pgfont);
1420                     ngtext.setFontSmoothingType(smoothing);
1421                     ngtext.setGlyphs(textLayout.getRuns());
1422                     ngtext.renderContent(gr);
1423                 }
1424                 break;
1425             }
1426             default:
1427                 throw new InternalError("Unrecognized PGCanvas rendering token: "+token);
1428         }
1429         if (bounds != null) {
1430             if (strokeBounds) {
1431                 BasicStroke s = getStroke();
1432                 if (s.getType() != BasicStroke.TYPE_INNER) {
1433                     float lw = s.getLineWidth();
1434                     if (s.getType() == BasicStroke.TYPE_CENTERED) {
1435                         lw /= 2f;
1436                     }
1437                     bounds.grow(lw, lw);
1438                 }
1439             }
1440             if (transformBounds) {
1441                 txBounds(bounds, transform);
1442             }
1443         }
1444     }
1445 
1446     void computeTextLayoutBounds(RectBounds bounds, BaseTransform transform,
1447                                  float scaleX, float layoutX, float layoutY,
1448                                  int token)
1449     {
1450         textLayout.getBounds(null, bounds);
1451         TEMP_TX.setTransform(transform);
1452         TEMP_TX.scale(scaleX, 1);
1453         TEMP_TX.translate(layoutX, layoutY);
1454         TEMP_TX.transform(bounds, bounds);
1455         if (token == STROKE_TEXT) {
1456             int flag = PrismTextLayout.TYPE_TEXT;
1457             Shape textShape = textLayout.getShape(flag, null);
1458             RectBounds shapeBounds = new RectBounds();
1459             strokebounds(getStroke(), textShape, shapeBounds, TEMP_TX);
1460             bounds.unionWith(shapeBounds);
1461         }
1462     }
1463 
1464     static void txBounds(RectBounds bounds, BaseTransform transform) {
1465         switch (transform.getType()) {
1466             case BaseTransform.TYPE_IDENTITY:
1467                 break;
1468             case BaseTransform.TYPE_TRANSLATION:
1469                 float tx = (float) transform.getMxt();
1470                 float ty = (float) transform.getMyt();
1471                 bounds.setBounds(bounds.getMinX() + tx, bounds.getMinY() + ty,
1472                                  bounds.getMaxX() + tx, bounds.getMaxY() + ty);
1473                 break;
1474             default:
1475                 BaseBounds txbounds = transform.transform(bounds, bounds);
1476                 if (txbounds != bounds) {
1477                     bounds.setBounds(txbounds.getMinX(), txbounds.getMinY(),
1478                                      txbounds.getMaxX(), txbounds.getMaxY());
1479                 }
1480                 break;
1481         }
1482     }
1483 
1484     static void inverseTxBounds(RectBounds bounds, BaseTransform transform) {
1485         switch (transform.getType()) {
1486             case BaseTransform.TYPE_IDENTITY:
1487                 break;
1488             case BaseTransform.TYPE_TRANSLATION:
1489                 float tx = (float) transform.getMxt();
1490                 float ty = (float) transform.getMyt();
1491                 bounds.setBounds(bounds.getMinX() - tx, bounds.getMinY() - ty,
1492                                  bounds.getMaxX() - tx, bounds.getMaxY() - ty);
1493                 break;
1494             default:
1495                 try {
1496                     BaseBounds txbounds = transform.inverseTransform(bounds, bounds);
1497                     if (txbounds != bounds) {
1498                         bounds.setBounds(txbounds.getMinX(), txbounds.getMinY(),
1499                                         txbounds.getMaxX(), txbounds.getMaxY());
1500                     }
1501                 } catch (NoninvertibleTransformException e) {
1502                     bounds.makeEmpty();
1503                 }
1504                 break;
1505         }
1506     }
1507 
1508     public void updateBounds(float w, float h) {
1509         this.tw = (int) Math.ceil(w * highestPixelScale);
1510         this.th = (int) Math.ceil(h * highestPixelScale);
1511         geometryChanged();
1512     }
1513 
1514     // Returns true if we are falling behind in rendering (i.e. we
1515     // have unrendered data at the time of the synch.  This tells
1516     // the FX layer that it should consider emitting a RESET if it
1517     // detects a full-canvas clear command even if it looks like it
1518     // is superfluous.
1519     public boolean updateRendering(GrowableDataBuffer buf) {
1520         if (buf.isEmpty()) {
1521             GrowableDataBuffer.returnBuffer(buf);
1522             return (this.thebuf != null);
1523         }
1524         boolean reset = (buf.peekByte(0) == RESET);
1525         GrowableDataBuffer retbuf;
1526         if (reset || this.thebuf == null) {
1527             retbuf = this.thebuf;
1528             this.thebuf = buf;
1529         } else {
1530             this.thebuf.append(buf);
1531             retbuf = buf;
1532         }
1533         geometryChanged();
1534         if (retbuf != null) {
1535             GrowableDataBuffer.returnBuffer(retbuf);
1536             return true;
1537         }
1538         return false;
1539     }
1540 
1541     class RenderInput extends Effect {
1542         float x, y, w, h;
1543         int token;
1544         GrowableDataBuffer buf;
1545         Affine2D savedBoundsTx = new Affine2D();
1546 
1547         public RenderInput(int token, GrowableDataBuffer buf,
1548                            BaseTransform boundsTx, RectBounds rb)
1549         {
1550             this.token = token;
1551             this.buf = buf;
1552             savedBoundsTx.setTransform(boundsTx);
1553             this.x = rb.getMinX();
1554             this.y = rb.getMinY();
1555             this.w = rb.getWidth();
1556             this.h = rb.getHeight();
1557         }
1558 
1559         @Override
1560         public ImageData filter(FilterContext fctx, BaseTransform transform,
1561                                 Rectangle outputClip, Object renderHelper,
1562                                 Effect defaultInput)
1563         {
1564             BaseBounds bounds = getBounds(transform, defaultInput);
1565             if (outputClip != null) {
1566                 bounds.intersectWith(outputClip);
1567             }
1568             Rectangle r = new Rectangle(bounds);
1569             if (r.width < 1) r.width = 1;
1570             if (r.height < 1) r.height = 1;
1571             PrDrawable ret = (PrDrawable) Effect.getCompatibleImage(fctx, r.width, r.height);
1572             if (ret != null) {
1573                 Graphics g = ret.createGraphics();
1574                 g.setExtraAlpha(globalAlpha);
1575                 g.translate(-r.x, -r.y);
1576                 if (transform != null) {
1577                     g.transform(transform);
1578                 }
1579                 buf.restore();
1580                 handleRenderOp(token, buf, g, null);
1581             }
1582             return new ImageData(fctx, ret, r);
1583         }
1584 
1585         @Override
1586         public AccelType getAccelType(FilterContext fctx) {
1587             throw new UnsupportedOperationException("Not supported yet.");
1588         }
1589 
1590         @Override
1591         public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
1592             RectBounds ret = new RectBounds(x, y, x + w, y + h);
1593             if (!transform.equals(savedBoundsTx)) {
1594                 inverseTxBounds(ret, savedBoundsTx);
1595                 txBounds(ret, transform);
1596             }
1597             return ret;
1598         }
1599 
1600         @Override
1601         public boolean reducesOpaquePixels() {
1602             return false;
1603         }
1604 
1605         @Override
1606         public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
1607             return null; // Never called
1608         }
1609 
1610     }
1611 
1612     static class MyBlend extends Blend {
1613         public MyBlend(Mode mode, Effect bottomInput, Effect topInput) {
1614             super(mode, bottomInput, topInput);
1615         }
1616 
1617         @Override
1618         public Rectangle getResultBounds(BaseTransform transform,
1619                                          Rectangle outputClip,
1620                                          ImageData... inputDatas)
1621         {
1622             // There is a bug in the ImageData class that means that the
1623             // outputClip will not be taken into account, so we override
1624             // here and apply it ourselves.
1625             Rectangle r = super.getResultBounds(transform, outputClip, inputDatas);
1626             r.intersectWith(outputClip);
1627             return r;
1628         }
1629     }
1630 
1631     static class EffectInput extends Effect {
1632         RTTexture tex;
1633         float pixelscale;
1634 
1635         EffectInput(RTTexture tex) {
1636             this.tex = tex;
1637             this.pixelscale = 1.0f;
1638         }
1639 
1640         public void setPixelScale(float scale) {
1641             this.pixelscale = scale;
1642         }
1643 
1644         @Override
1645         public ImageData filter(FilterContext fctx, BaseTransform transform,
1646                                 Rectangle outputClip, Object renderHelper,
1647                                 Effect defaultInput)
1648         {
1649             Filterable f = PrDrawable.create(fctx, tex);
1650             Rectangle r = new Rectangle(tex.getContentWidth(), tex.getContentHeight());
1651             f.lock();
1652             ImageData id = new ImageData(fctx, f, r);
1653             id.setReusable(true);
1654             if (pixelscale != 1.0f || !transform.isIdentity()) {
1655                 Affine2D a2d = new Affine2D();
1656                 a2d.scale(1.0f / pixelscale, 1.0f / pixelscale);
1657                 a2d.concatenate(transform);
1658                 id = id.transform(a2d);
1659             }
1660             return id;
1661         }
1662 
1663         @Override
1664         public AccelType getAccelType(FilterContext fctx) {
1665             throw new UnsupportedOperationException("Not supported yet.");
1666         }
1667 
1668         @Override
1669         public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
1670             Rectangle r = new Rectangle(tex.getContentWidth(), tex.getContentHeight());
1671             return transformBounds(transform, new RectBounds(r));
1672         }
1673 
1674         @Override
1675         public boolean reducesOpaquePixels() {
1676             return false;
1677         }
1678 
1679         @Override
1680         public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
1681             return null; // Never called
1682         }
1683     }
1684 }