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