1 /*
   2  * Copyright (c) 2011, 2017, 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.webkit.prism;
  27 
  28 import com.sun.glass.ui.Screen;
  29 import com.sun.javafx.font.FontStrike;
  30 import com.sun.javafx.font.Metrics;
  31 import com.sun.javafx.font.PGFont;
  32 import com.sun.javafx.geom.*;
  33 import com.sun.javafx.geom.transform.Affine2D;
  34 import com.sun.javafx.geom.transform.Affine3D;
  35 import com.sun.javafx.geom.transform.BaseTransform;
  36 import com.sun.javafx.scene.text.GlyphList;
  37 import com.sun.javafx.scene.text.TextLayout;
  38 import com.sun.javafx.sg.prism.*;
  39 import com.sun.javafx.text.TextRun;
  40 import com.sun.prism.*;
  41 import com.sun.prism.paint.Color;
  42 import com.sun.prism.paint.Gradient;
  43 import com.sun.prism.paint.ImagePattern;
  44 import com.sun.prism.paint.Paint;
  45 import com.sun.scenario.effect.*;
  46 import com.sun.scenario.effect.impl.prism.PrDrawable;
  47 import com.sun.scenario.effect.impl.prism.PrEffectHelper;
  48 import com.sun.scenario.effect.impl.prism.PrFilterContext;
  49 import com.sun.webkit.graphics.*;
  50 
  51 import java.nio.ByteBuffer;
  52 import java.nio.ByteOrder;
  53 import java.security.AccessController;
  54 import java.security.PrivilegedAction;
  55 import java.util.ArrayList;
  56 import java.util.List;
  57 import java.util.logging.Level;
  58 import java.util.logging.Logger;
  59 
  60 import static com.sun.scenario.effect.Blend.Mode.*;
  61 import com.sun.scenario.effect.impl.Renderer;
  62 import com.sun.scenario.effect.impl.prism.PrRenderer;
  63 
  64 class WCGraphicsPrismContext extends WCGraphicsContext {
  65 
  66     public enum Type {
  67         /**
  68          * Base context associated with the topmost page buffer.
  69          * Created and disposed during a single render pass.
  70          */
  71         PRIMARY,
  72 
  73         /**
  74          * A context associated with a dedicated buffer representing
  75          * a separate render target like canvas, buffered image etc.
  76          * Its life cycle is not limited to a single render pass.
  77          */
  78         DEDICATED
  79     }
  80 
  81     private final static Logger log =
  82         Logger.getLogger(WCGraphicsPrismContext.class.getName());
  83     private final static boolean DEBUG_DRAW_CLIP_SHAPE = Boolean.valueOf(
  84             AccessController.doPrivileged((PrivilegedAction<String>) () ->
  85             System.getProperty("com.sun.webkit.debugDrawClipShape", "false")));
  86 
  87     Graphics baseGraphics;
  88     private BaseTransform baseTransform;
  89 
  90     private final List<ContextState> states = new ArrayList<ContextState>();
  91 
  92     private ContextState state = new ContextState();
  93 
  94     // Cache for getPlatformGraphics
  95     private Graphics cachedGraphics = null;
  96 
  97     private int fontSmoothingType;
  98     private boolean isRootLayerValid = false;
  99 
 100     WCGraphicsPrismContext(Graphics g) {
 101         state.setClip(g.getClipRect());
 102         state.setAlpha(g.getExtraAlpha());
 103         baseGraphics = g;
 104         initBaseTransform(g.getTransformNoClone());
 105     }
 106 
 107     WCGraphicsPrismContext() {
 108     }
 109 
 110     public Type type() {
 111         return Type.PRIMARY;
 112     }
 113 
 114     final void initBaseTransform(BaseTransform t) {
 115         baseTransform = new Affine3D(t);
 116         state.setTransform((Affine3D)baseTransform);
 117     }
 118 
 119     private void resetCachedGraphics() {
 120         cachedGraphics = null;
 121     }
 122 
 123     @Override
 124     public Object getPlatformGraphics() {
 125         return getGraphics(false);
 126     }
 127 
 128     Graphics getGraphics(boolean checkClip) {
 129         if (cachedGraphics == null) {
 130             Layer l = state.getLayerNoClone();
 131             cachedGraphics = (l != null)
 132                     ? l.getGraphics()
 133                     : baseGraphics;
 134 
 135             state.apply(cachedGraphics);
 136 
 137             if (log.isLoggable(Level.FINE)) {
 138                 log.fine("getPlatformGraphics for " + this + " : " +
 139                          cachedGraphics);
 140             }
 141         }
 142 
 143         Rectangle clip = cachedGraphics.getClipRectNoClone();
 144         return (checkClip && clip!=null && clip.isEmpty())
 145             ? null
 146             : cachedGraphics;
 147     }
 148 
 149     public void saveState()
 150     {
 151         state.markAsRestorePoint();
 152         saveStateInternal();
 153     }
 154 
 155     private void saveStateInternal()
 156     {
 157         states.add(state);
 158         state = state.clone();
 159     }
 160 
 161     private void startNewLayer(Layer layer) {
 162         saveStateInternal();
 163 
 164         // layer has the same bounds as clip, so we have to translate
 165         Rectangle clip = state.getClipNoClone();
 166 
 167         //left-side (post-) translate.
 168         //NB! an order of transforms is essential!
 169         Affine3D newTr = new Affine3D(BaseTransform.getTranslateInstance(
 170                 -clip.x,
 171                 -clip.y));
 172         newTr.concatenate(state.getTransformNoClone());
 173 
 174         //move clip to (0, 0) - start of texture
 175         clip.x = 0;
 176         clip.y = 0;
 177         //no-clone - no-set!
 178 
 179         Graphics g = getGraphics(true);
 180         if (g != null && g != baseGraphics) {
 181             layer.init(g);
 182         }
 183 
 184         state.setTransform(newTr);
 185         state.setLayer(layer);
 186 
 187         resetCachedGraphics();
 188     }
 189 
 190     private void renderLayer(final Layer layer) {
 191         WCTransform cur = getTransform();
 192 
 193         //translate to (layer.getX(), layer.getY())
 194         setTransform(new WCTransform(
 195             1.0, 0.0,
 196             0.0, 1.0,
 197             layer.getX(), layer.getY()));
 198 
 199         // composite drawing delegated to the layer rendering
 200         Graphics g = getGraphics(true);
 201         if (g != null) {
 202             layer.render(g);
 203         }
 204 
 205         //restore transform
 206         setTransform(cur);
 207     }
 208 
 209     private void restoreStateInternal() {
 210         int size = states.size();
 211         if (size == 0) {
 212             assert false: "Unbalanced restoreState";
 213             return;
 214         }
 215 
 216         Layer layer = state.getLayerNoClone();
 217         state = states.remove(size - 1);
 218         if (layer != state.getLayerNoClone()) {
 219             renderLayer(layer);
 220             layer.dispose();
 221             if (log.isLoggable(Level.FINE)) {
 222                 log.fine("Popped layer " + layer);
 223             }
 224         } else {
 225             resetCachedGraphics();
 226         }
 227     }
 228 
 229     public void restoreState()
 230     {
 231         log.fine("restoring state");
 232         do {
 233             restoreStateInternal();
 234         } while ( !state.isRestorePoint() );
 235     }
 236 
 237     /**
 238      *  Renders all layers to the underlaying Graphics, but preserves the
 239      *  current state and the states stack
 240      */
 241     private void flushAllLayers() {
 242         if (state == null) {
 243             // context disposed
 244             return;
 245         }
 246 
 247         if (isRootLayerValid) {
 248             log.fine("FlushAllLayers: root layer is valid, skipping");
 249             return;
 250         }
 251 
 252         if (log.isLoggable(Level.FINE)) {
 253             log.fine("FlushAllLayers");
 254         }
 255 
 256         ContextState currentState = state;
 257 
 258         for (int i = states.size() - 1; i >=0; i--) {
 259             Layer layer = state.getLayerNoClone();
 260             state = states.get(i);
 261             if (layer != state.getLayerNoClone()) {
 262                 renderLayer(layer);
 263             } else {
 264                 resetCachedGraphics();
 265             }
 266         }
 267 
 268         Layer layer = state.getLayerNoClone();
 269         if (layer != null) {
 270             renderLayer(layer);
 271         }
 272 
 273         state = currentState;
 274         isRootLayerValid = true;
 275     }
 276 
 277 
 278     public void dispose() {
 279         if (!states.isEmpty()) {
 280             log.fine("Unbalanced saveState/restoreState");
 281         }
 282         for (ContextState state: states) {
 283             if (state.getLayerNoClone() != null) {
 284                 state.getLayerNoClone().dispose();
 285             }
 286         }
 287         states.clear();
 288 
 289         if (state != null && state.getLayerNoClone() != null) {
 290             state.getLayerNoClone().dispose();
 291         }
 292         state = null;
 293     }
 294 
 295 
 296     public void setClip(WCPath path, boolean isOut) {
 297         Affine3D tr = new Affine3D(state.getTransformNoClone());
 298         path.transform(
 299                 tr.getMxx(), tr.getMyx(),
 300                 tr.getMxy(), tr.getMyy(),
 301                 tr.getMxt(), tr.getMyt());
 302         //path now is in node coordinates, as well as clip
 303 
 304         if (!isOut) {
 305             WCRectangle pathBounds = path.getBounds();
 306 
 307             // path bounds could be fractional so 'inclusive' rounding
 308             // is used for determining clip rectangle
 309             int pixelX = (int) Math.floor(pathBounds.getX());
 310             int pixelY = (int) Math.floor(pathBounds.getY());
 311             int pixelW = (int) Math.ceil(pathBounds.getMaxX()) - pixelX;
 312             int pixelH = (int) Math.ceil(pathBounds.getMaxY()) - pixelY;
 313 
 314             state.clip(new Rectangle(pixelX, pixelY, pixelW, pixelH));
 315         }
 316 
 317         Rectangle clip = state.getClipNoClone();
 318 
 319         if (isOut) {
 320             path.addRect(clip.x, clip.y, clip.width, clip.height);
 321             //Out clip path is always EVENODD.
 322         }
 323 
 324         path.translate(-clip.x, -clip.y);
 325 
 326         Layer layer = new ClipLayer(
 327             getGraphics(false), clip, path, type() == Type.DEDICATED);
 328 
 329         startNewLayer(layer);
 330 
 331         if (log.isLoggable(Level.FINE)) {
 332             log.fine("setClip(WCPath " + path.getID() + ")");
 333             log.fine("Pushed layer " + layer);
 334         }
 335     }
 336 
 337     private Rectangle transformClip(Rectangle localClip) {
 338         if (localClip==null) {
 339             return null;
 340         }
 341 
 342         float[] points = new float[] {
 343             localClip.x, localClip.y,
 344             localClip.x + localClip.width, localClip.y,
 345             localClip.x, localClip.y + localClip.height,
 346             localClip.x  + localClip.width, localClip.y + localClip.height};
 347         state.getTransformNoClone().transform(points, 0, points, 0, 4);
 348         float minX = Math.min(
 349                points[0], Math.min(
 350                points[2], Math.min(
 351                points[4], points[6])));
 352         float maxX = Math.max(
 353                points[0], Math.max(
 354                points[2], Math.max(
 355                points[4], points[6])));
 356         float minY = Math.min(
 357                points[1], Math.min(
 358                points[3], Math.min(
 359                points[5], points[7])));
 360         float maxY = Math.max(
 361                points[1], Math.max(
 362                points[3], Math.max(
 363                points[5], points[7])));
 364         return new Rectangle(new RectBounds(minX, minY, maxX, maxY));
 365 
 366 /* #1 loose rotate
 367         state.getTransformNoClone().transform(localClip, localClip);
 368 */
 369 /* #2 problem with negative coordinates
 370         RectBounds rb = TransformedShape.transformedShape(
 371             new RoundRectangle2D(localClip.x, localClip.y, localClip.width, localClip.height, 0, 0),
 372             state.getTransformNoClone()).getBounds();
 373         return rb.isEmpty()
 374             ? null
 375             : new Rectangle(rb);
 376  */
 377     }
 378 
 379     private void setClip(Rectangle shape) {
 380         Affine3D tr = state.getTransformNoClone();
 381         if (tr.getMxy() == 0 && tr.getMxz() == 0
 382          && tr.getMyx() == 0 && tr.getMyz() == 0
 383          && tr.getMzx() == 0 && tr.getMzy() == 0) {
 384             //There is no rotation here: scale + translation.
 385             //Fast & easy!
 386             state.clip(transformClip(shape));
 387             if (log.isLoggable(Level.FINE)) {
 388                 log.log(Level.FINE, "setClip({0})", shape);
 389             }
 390             if (DEBUG_DRAW_CLIP_SHAPE) {
 391                 //Draw clip shape
 392                 Rectangle rc = state.getClipNoClone();
 393                 if (rc != null && rc.width >= 2 && rc.height >= 2) {
 394                     WCTransform cur = getTransform();
 395                     //translate to (layer.getX(), layer.getY())
 396                     setTransform(new WCTransform(
 397                         1.0, 0.0,
 398                         0.0, 1.0,
 399                         0.0, 0.0));
 400 
 401                     Graphics g2d = getGraphics(true);
 402                     if (g2d != null) {
 403                         float fbase = (float)Math.random();
 404                         g2d.setPaint(new Color(
 405                                 fbase,
 406                                 1f - fbase,
 407                                 0.5f,
 408                                 0.1f));
 409                         g2d.setStroke(new BasicStroke());
 410                         g2d.fillRect(rc.x, rc.y, rc.width, rc.height);
 411 
 412                         g2d.setPaint(new Color(
 413                                 1f - fbase,
 414                                 fbase,
 415                                 0.5f,
 416                                 1f));
 417                         g2d.drawRect(rc.x, rc.y, rc.width, rc.height);
 418                     }
 419                     //restore transform
 420                     setTransform(cur);
 421                     state.clip(new Rectangle(rc.x+1, rc.y+1, rc.width-2, rc.height-2));
 422                 }
 423             }
 424             if (cachedGraphics != null) {
 425                 cachedGraphics.setClipRect(state.getClipNoClone());
 426             }
 427         } else {
 428             //twisted axis set
 429             WCPath path = new WCPathImpl();
 430             path.addRect(shape.x, shape.y, shape.width, shape.height);
 431             setClip(path, false);
 432         }
 433     }
 434 
 435     public void setClip(int cx, int cy, int cw, int ch) {
 436         setClip(new Rectangle(cx, cy, cw, ch));
 437     }
 438 
 439     public void setClip(WCRectangle c) {
 440         setClip(new Rectangle((int)c.getX(), (int)c.getY(),
 441                               (int)c.getWidth(), (int)c.getHeight()));
 442     }
 443 
 444     public WCRectangle getClip() {
 445         Rectangle r = state.getClipNoClone();
 446         return r == null ? null : new WCRectangle(r.x, r.y, r.width, r.height);
 447     }
 448 
 449     protected Rectangle getClipRectNoClone() {
 450         return state.getClipNoClone();
 451     }
 452 
 453     protected Affine3D getTransformNoClone() {
 454         return state.getTransformNoClone();
 455     }
 456 
 457     public void translate(float x, float y) {
 458         if (log.isLoggable(Level.FINE)) {
 459             log.log(Level.FINE, "translate({0},{1})", new Object[] {x, y});
 460         }
 461         state.translate(x, y);
 462         if (cachedGraphics != null) {
 463             cachedGraphics.translate(x, y);
 464         }
 465     }
 466 
 467     public void scale(float sx, float sy) {
 468         if (log.isLoggable(Level.FINE)) {
 469             log.fine("scale(" + sx + " " + sy + ")");
 470         }
 471         state.scale(sx, sy);
 472         if (cachedGraphics != null) {
 473             cachedGraphics.scale(sx, sy);
 474         }
 475     }
 476 
 477     public void rotate(float radians) {
 478         if (log.isLoggable(Level.FINE)) {
 479             log.fine("rotate(" + radians + ")");
 480         }
 481         state.rotate(radians);
 482         if (cachedGraphics != null) {
 483             cachedGraphics.setTransform(state.getTransformNoClone());
 484         }
 485     }
 486 
 487     // overriden in WCBufferedContext
 488     protected boolean shouldRenderRect(float x, float y, float w, float h,
 489                                        DropShadow shadow, BasicStroke stroke)
 490     {
 491         return true;
 492     }
 493 
 494     // overriden in WCBufferedContext
 495     protected boolean shouldRenderShape(Shape shape, DropShadow shadow, BasicStroke stroke) {
 496         return true;
 497     }
 498 
 499     // overriden in WCBufferedContext
 500     protected boolean shouldCalculateIntersection() {
 501         return false;
 502     }
 503 
 504     @Override
 505     public void fillRect(final float x, final float y, final float w, final float h, final Integer rgba) {
 506         if (log.isLoggable(Level.FINE)) {
 507             String format = (rgba != null)
 508                     ? "fillRect(%f, %f, %f, %f, 0x%x)"
 509                     : "fillRect(%f, %f, %f, %f, null)";
 510             log.fine(String.format(format, x, y, w, h, rgba));
 511         }
 512         if (!shouldRenderRect(x, y, w, h, state.getShadowNoClone(), null)) {
 513             return;
 514         }
 515         new Composite() {
 516             @Override void doPaint(Graphics g) {
 517                 Paint paint = (rgba != null) ? createColor(rgba) : state.getPaintNoClone();
 518                 DropShadow shadow = state.getShadowNoClone();
 519                 if (shadow != null) {
 520                     final NGRectangle node = new NGRectangle();
 521                     node.updateRectangle(x, y, w, h, 0, 0);
 522                     render(g, shadow, paint, null, node);
 523                 } else {
 524                     g.setPaint(paint);
 525                     g.fillRect(x, y, w, h);
 526                 }
 527             }
 528         }.paint();
 529     }
 530 
 531     @Override
 532     public void fillRoundedRect(final float x, final float y, final float w, final float h,
 533         final float topLeftW, final float topLeftH, final float topRightW, final float topRightH,
 534         final float bottomLeftW, final float bottomLeftH, final float bottomRightW, final float bottomRightH,
 535         final int rgba)
 536     {
 537         if (log.isLoggable(Level.FINE)) {
 538             log.fine(String.format("fillRoundedRect(%f, %f, %f, %f, "
 539                     + "%f, %f, %f, %f, %f, %f, %f, %f, 0x%x)",
 540                     x, y, w, h, topLeftW, topLeftH, topRightW, topRightH,
 541                     bottomLeftW, bottomLeftH, bottomRightW, bottomRightH, rgba));
 542         }
 543         if (!shouldRenderRect(x, y, w, h, state.getShadowNoClone(), null)) {
 544             return;
 545         }
 546         new Composite() {
 547             @Override void doPaint(Graphics g) {
 548                 // Prism only supports single arcWidth/Height.
 549                 // We work around by calculating average width and height here
 550 
 551                 float arcW = (topLeftW + topRightW + bottomLeftW + bottomRightW) / 2;
 552                 float arcH = (topLeftH + topRightH + bottomLeftH + bottomRightH) / 2;
 553 
 554                 Paint paint = createColor(rgba);
 555                 DropShadow shadow = state.getShadowNoClone();
 556                 if (shadow != null) {
 557                     final NGRectangle node = new NGRectangle();
 558                     node.updateRectangle(x, y, w, h, arcW, arcH);
 559                     render(g, shadow, paint, null, node);
 560                 } else {
 561                     g.setPaint(paint);
 562                     g.fillRoundRect(x, y, w, h, arcW, arcH);
 563                 }
 564             }
 565         }.paint();
 566     }
 567 
 568     @Override
 569     public void clearRect(final float x, final float y, final float w, final float h) {
 570         if (log.isLoggable(Level.FINE)) {
 571             log.fine(String.format("clearRect(%f, %f, %f, %f)", x, y, w, h));
 572         }
 573         if (shouldCalculateIntersection()) {
 574             // No intersection is applicable for clearRect.
 575             return;
 576         }
 577         new Composite() {
 578             @Override void doPaint(Graphics g) {
 579                 g.clearQuad(x, y, x + w, y + h);
 580             }
 581         }.paint();
 582     }
 583 
 584     @Override
 585     public void setFillColor(int rgba) {
 586         if (log.isLoggable(Level.FINE)) {
 587             log.log(Level.FINE, String.format("setFillColor(0x%x)", rgba));
 588         }
 589         state.setPaint(createColor(rgba));
 590     }
 591 
 592     @Override
 593     public void setFillGradient(WCGradient gradient) {
 594         if (log.isLoggable(Level.FINE)) {
 595             log.fine("setFillGradient(" + gradient + ")");
 596         }
 597         state.setPaint((Gradient) gradient.getPlatformGradient());
 598     }
 599 
 600     @Override
 601     public void setTextMode(boolean fill, boolean stroke, boolean clip) {
 602         if (log.isLoggable(Level.FINE)) {
 603             log.fine("setTextMode(fill:" + fill + ",stroke:" + stroke + ",clip:" + clip + ")");
 604         }
 605         state.setTextMode(fill, stroke, clip);
 606     }
 607 
 608     @Override
 609     public void setFontSmoothingType(int fontSmoothingType) {
 610         this.fontSmoothingType = fontSmoothingType;
 611     }
 612 
 613     @Override
 614     public int getFontSmoothingType() {
 615         return fontSmoothingType;
 616     }
 617 
 618     @Override
 619     public void setStrokeStyle(int style) {
 620         if (log.isLoggable(Level.FINE)) {
 621             log.log(Level.FINE, "setStrokeStyle({0})", style);
 622         }
 623         state.getStrokeNoClone().setStyle(style);
 624     }
 625 
 626     @Override
 627     public void setStrokeColor(int rgba) {
 628         if (log.isLoggable(Level.FINE)) {
 629             log.log(Level.FINE, String.format("setStrokeColor(0x%x)", rgba));
 630         }
 631         state.getStrokeNoClone().setPaint(createColor(rgba));
 632     }
 633 
 634     @Override
 635     public void setStrokeWidth(float width) {
 636         if (log.isLoggable(Level.FINE)) {
 637             log.log(Level.FINE, "setStrokeWidth({0})", new Object[] { width });
 638         }
 639         state.getStrokeNoClone().setThickness(width);
 640     }
 641 
 642     @Override
 643     public void setStrokeGradient(WCGradient gradient) {
 644         if (log.isLoggable(Level.FINE)) {
 645             log.fine("setStrokeGradient(" + gradient + ")");
 646         }
 647         state.getStrokeNoClone().setPaint((Gradient) gradient.getPlatformGradient());
 648     }
 649 
 650     @Override
 651     public void setLineDash(float offset, float... sizes) {
 652         if (log.isLoggable(Level.FINE)) {
 653             StringBuilder s = new StringBuilder("[");
 654             for (int i=0; i < sizes.length; i++) {
 655                 s.append(sizes[i]).append(',');
 656             }
 657             s.append(']');
 658             log.log(Level.FINE, "setLineDash({0},{1}", new Object[] {offset, s});
 659         }
 660         state.getStrokeNoClone().setDashOffset(offset);
 661         if (sizes != null) {
 662             boolean allZero = true;
 663             for (int i = 0; i < sizes.length; i++) {
 664                 if (sizes[i] != 0) {
 665                     allZero = false;
 666                     break;
 667                 }
 668             }
 669             if (allZero) {
 670                 sizes = null;
 671             }
 672         }
 673         state.getStrokeNoClone().setDashSizes(sizes);
 674     }
 675 
 676     @Override
 677     public void setLineCap(int lineCap) {
 678         if (log.isLoggable(Level.FINE)) {
 679             log.fine("setLineCap(" + lineCap + ")");
 680         }
 681         state.getStrokeNoClone().setLineCap(lineCap);
 682     }
 683 
 684     @Override
 685     public void setLineJoin(int lineJoin) {
 686         if (log.isLoggable(Level.FINE)) {
 687             log.fine("setLineJoin(" + lineJoin + ")");
 688         }
 689         state.getStrokeNoClone().setLineJoin(lineJoin);
 690     }
 691 
 692     @Override
 693     public void setMiterLimit(float miterLimit) {
 694         if (log.isLoggable(Level.FINE)) {
 695             log.fine("setMiterLimit(" + miterLimit + ")");
 696         }
 697         state.getStrokeNoClone().setMiterLimit(miterLimit);
 698     }
 699 
 700     @Override
 701     public void setShadow(float dx, float dy, float blur, int rgba) {
 702         if (log.isLoggable(Level.FINE)) {
 703             String format = "setShadow(%f, %f, %f, 0x%x)";
 704             log.fine(String.format(format, dx, dy, blur, rgba));
 705         }
 706         state.setShadow(createShadow(dx, dy, blur, rgba));
 707     }
 708 
 709     @Override
 710     public void drawPolygon(final WCPath path, final boolean shouldAntialias) {
 711         if (log.isLoggable(Level.FINE)) {
 712             log.log(Level.FINE, "drawPolygon({0})",
 713                     new Object[] {shouldAntialias});
 714         }
 715         if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(), null,
 716                                 state.getStrokeNoClone().getPlatformStroke()))
 717         {
 718             return;
 719         }
 720         new Composite() {
 721             @Override void doPaint(Graphics g) {
 722                 Path2D p2d = (Path2D) path.getPlatformPath();
 723                 g.setPaint(state.getPaintNoClone());
 724                 g.fill(p2d);
 725                 if (state.getStrokeNoClone().apply(g)) {
 726                     g.draw(p2d);
 727                 }
 728             }
 729         }.paint();
 730     }
 731 
 732     @Override
 733     public void drawLine(final int x0, final int y0, final int x1, final int y1) {
 734         if (log.isLoggable(Level.FINE)) {
 735             log.log(Level.FINE, "drawLine({0}, {1}, {2}, {3})",
 736                     new Object[] {x0, y0, x1, y1});
 737         }
 738         Line2D line = new Line2D(x0, y0, x1, y1);
 739         if (!shouldRenderShape(line, null, state.getStrokeNoClone().getPlatformStroke())) {
 740             return;
 741         }
 742         new Composite() {
 743             @Override void doPaint(Graphics g) {
 744                 if (state.getStrokeNoClone().apply(g)) {
 745                     g.drawLine(x0, y0, x1, y1);
 746                 }
 747             }
 748         }.paint();
 749     }
 750 
 751     @Override
 752     public void drawPattern(
 753         final WCImage texture,
 754         final WCRectangle srcRect,
 755         final WCTransform patternTransform,
 756         final WCPoint phase,
 757         final WCRectangle destRect)
 758     {
 759         if (log.isLoggable(Level.FINE)) {
 760             log.log(Level.FINE, "drawPattern({0}, {1}, {2}, {3})",
 761                     new Object[] {destRect.getIntX(), destRect.getIntY(),
 762                                   destRect.getIntWidth(),
 763                                   destRect.getIntHeight()});
 764         }
 765         if (!shouldRenderRect(destRect.getX(), destRect.getY(),
 766                               destRect.getWidth(), destRect.getHeight(), null, null))
 767         {
 768             return;
 769         }
 770         if (texture != null) {
 771             new Composite() {
 772                 @Override void doPaint(Graphics g) {
 773                     // The handling of pattern transform is modeled after the WebKit
 774                     // ImageCG.cpp's Image::drawPattern()
 775                     float adjustedX = phase.getX()
 776                             + srcRect.getX() * (float) patternTransform.getMatrix()[0];
 777                     float adjustedY = phase.getY()
 778                             + srcRect.getY() * (float) patternTransform.getMatrix()[3];
 779                     float scaledTileWidth =
 780                             srcRect.getWidth() * (float) patternTransform.getMatrix()[0];
 781                     float scaledTileHeight =
 782                             srcRect.getHeight() * (float) patternTransform.getMatrix()[3];
 783 
 784                     Image img = ((PrismImage)texture).getImage();
 785 
 786                     // Create subImage only if srcRect doesn't fit the texture bounds. See RT-20193.
 787                     if (!srcRect.contains(new WCRectangle(0, 0, texture.getWidth(), texture.getHeight()))) {
 788 
 789                         img = img.createSubImage(srcRect.getIntX(),
 790                                                  srcRect.getIntY(),
 791                                                  (int)Math.ceil(srcRect.getWidth()),
 792                                                  (int)Math.ceil(srcRect.getHeight()));
 793                     }
 794                     g.setPaint(new ImagePattern(
 795                                img,
 796                                adjustedX, adjustedY,
 797                                scaledTileWidth, scaledTileHeight,
 798                                false, false));
 799 
 800                     g.fillRect(destRect.getX(), destRect.getY(),
 801                                destRect.getWidth(), destRect.getHeight());
 802                 }
 803             }.paint();
 804         }
 805     }
 806 
 807     @Override
 808     public void drawImage(final WCImage img,
 809                           final float dstx, final float dsty, final float dstw, final float dsth,
 810                           final float srcx, final float srcy, final float srcw, final float srch)
 811     {
 812         if (log.isLoggable(Level.FINE)){
 813             log.log(Level.FINE, "drawImage(img, dst({0},{1},{2},{3}), " +
 814                     "src({4},{5},{6},{7}))",
 815                     new Object[] {dstx, dsty, dstw, dsth,
 816                                   srcx, srcy, srcw, srch});
 817         }
 818         if (!shouldRenderRect(dstx, dsty, dstw, dsth, state.getShadowNoClone(), null)) {
 819             return;
 820         }
 821         if (img instanceof PrismImage) {
 822             new Composite() {
 823                 @Override void doPaint(Graphics g) {
 824                     PrismImage pi = (PrismImage) img;
 825                     DropShadow shadow = state.getShadowNoClone();
 826                     if (shadow != null) {
 827                         NGImageView node = new NGImageView();
 828                         node.setImage(pi.getImage());
 829                         node.setX(dstx);
 830                         node.setY(dsty);
 831                         node.setViewport(srcx, srcy, srcw, srch, dstw, dsth);
 832                         node.setContentBounds(new RectBounds(dstx, dsty, dstx + dstw, dsty + dsth));
 833                         render(g, shadow, null, null, node);
 834                     } else {
 835                         pi.draw(g,
 836                                 (int) dstx, (int) dsty,
 837                                 (int) (dstx + dstw), (int) (dsty + dsth),
 838                                 (int) srcx, (int) srcy,
 839                                 (int) (srcx + srcw), (int) (srcy + srch));
 840                     }
 841                 }
 842             }.paint();
 843         }
 844     }
 845 
 846     @Override
 847     public void drawBitmapImage(final ByteBuffer image, final int x, final int y, final int w, final int h) {
 848         if (!shouldRenderRect(x, y, w, h, null, null)) {
 849             return;
 850         }
 851         new Composite() {
 852             @Override void doPaint(Graphics g) {
 853                 image.order(ByteOrder.nativeOrder());
 854                 Image img = Image.fromByteBgraPreData(image, w, h);
 855                 ResourceFactory rf = g.getResourceFactory();
 856                 Texture txt = rf.createTexture(img, Texture.Usage.STATIC, Texture.WrapMode.REPEAT);
 857                 g.drawTexture(txt, x, y, x + w, y + h, 0, 0, w, h);
 858                 txt.dispose();
 859             }
 860         }.paint();
 861     }
 862 
 863     @Override
 864     public void drawIcon(WCIcon icon, int x, int y) {
 865         if (log.isLoggable(Level.FINE)) {
 866             log.log(Level.FINE, "UNIMPLEMENTED drawIcon ({0}, {1})",
 867                     new Object[] {x, y});
 868         }
 869     }
 870 
 871     @Override
 872     public void drawRect(final int x, final int y, final int w, final int h) {
 873         if (log.isLoggable(Level.FINE)) {
 874             log.log(Level.FINE, "drawRect({0}, {1}, {2}, {3})",
 875                     new Object[]{x, y, w, h});
 876         }
 877         if (!shouldRenderRect(x, y, w, h,
 878                               null, state.getStrokeNoClone().getPlatformStroke()))
 879         {
 880             return;
 881         }
 882         new Composite() {
 883             @Override void doPaint(Graphics g) {
 884                 Paint c = state.getPaintNoClone();
 885                 if (c != null && c.isOpaque()) {
 886                     g.setPaint(c);
 887                     g.fillRect(x, y, w, h);
 888                 }
 889 
 890                 if (state.getStrokeNoClone().apply(g)) {
 891                     g.drawRect(x, y, w, h);
 892                 }
 893             }
 894         }.paint();
 895     }
 896 
 897     @Override
 898     public void drawString(final WCFont f, final int[] glyphs,
 899                            final float[] advances, final float x, final float y)
 900     {
 901         if (log.isLoggable(Level.FINE)) {
 902             log.fine(String.format(
 903                     "Drawing %d glyphs @(%.1f, %.1f)",
 904                     glyphs.length, x, y));
 905         }
 906         PGFont font = (PGFont)f.getPlatformFont();
 907         TextRun gl = TextUtilities.createGlyphList(glyphs, advances, x, y);
 908 
 909         DropShadow shadow = state.getShadowNoClone();
 910         BasicStroke stroke = state.isTextStroke()
 911                 ? state.getStrokeNoClone().getPlatformStroke()
 912                 : null;
 913 
 914         final FontStrike strike = font.getStrike(getTransformNoClone(), getFontSmoothingType());
 915         if (shouldCalculateIntersection()) {
 916             Metrics m = strike.getMetrics();
 917             gl.setMetrics(m.getAscent(), m.getDescent(), m.getLineGap());
 918             if (!shouldRenderRect(x, y, gl.getWidth(), gl.getHeight(), shadow, stroke)) {
 919                 return;
 920             }
 921         }
 922         new Composite() {
 923             @Override void doPaint(Graphics g) {
 924                 Paint paint = state.isTextFill()
 925                         ? state.getPaintNoClone()
 926                         : null;
 927                 if (shadow != null) {
 928                     final NGText span = new NGText();
 929                     span.setGlyphs(new GlyphList[] {gl});
 930                     span.setFont(font);
 931                     span.setFontSmoothingType(fontSmoothingType);
 932                     render(g, shadow, paint, stroke, span);
 933                 } else {
 934                     if (paint != null) {
 935                         g.setPaint(paint);
 936                         g.drawString(gl, strike, x, y, null, 0, 0);
 937                     }
 938                     if (stroke != null) {
 939                         paint = state.getStrokeNoClone().getPaint();
 940                         if (paint != null) {
 941                             g.setPaint(paint);
 942                             g.setStroke(stroke);
 943                             g.draw(strike.getOutline(gl, BaseTransform.getTranslateInstance(x, y)));
 944                         }
 945                     }
 946                 }
 947             }
 948         }.paint();
 949     }
 950 
 951     @Override public void drawString(WCFont f, String str, boolean rtl,
 952             int from, int to, float x, float y)
 953     {
 954         if (log.isLoggable(Level.FINE)) {
 955             log.fine(String.format(
 956                     "str='%s' (length=%d), from=%d, to=%d, rtl=%b, @(%.1f, %.1f)",
 957                     str, str.length(), from, to, rtl, x, y));
 958         }
 959         TextLayout layout = TextUtilities.createLayout(
 960                 str.substring(from, to), f.getPlatformFont());
 961         int count = 0;
 962         GlyphList[] runs = layout.getRuns();
 963         for (GlyphList run: runs) {
 964             count += run.getGlyphCount();
 965         }
 966 
 967         int[] glyphs = new int[count];
 968         float[] adv = new float[count];
 969         count = 0;
 970         for (GlyphList run: layout.getRuns()) {
 971             int gc = run.getGlyphCount();
 972             for (int i = 0; i < gc; i++) {
 973                 glyphs[count] = run.getGlyphCode(i);
 974                 adv[count] = run.getPosX(i + 1) - run.getPosX(i);
 975                 count++;
 976             }
 977         }
 978 
 979         // adjust x coordinate (see RT-29908)
 980         if (rtl) {
 981             x += (TextUtilities.getLayoutWidth(str.substring(from), f.getPlatformFont()) -
 982                   layout.getBounds().getWidth());
 983         } else {
 984             x += TextUtilities.getLayoutWidth(str.substring(0, from), f.getPlatformFont());
 985         }
 986         drawString(f, glyphs, adv, x, y);
 987     }
 988 
 989     @Override
 990     public void setComposite(int composite) {
 991         log.log(Level.FINE, "setComposite({0})", composite);
 992         state.setCompositeOperation(composite);
 993     }
 994 
 995     @Override
 996     public void drawEllipse(final int x, final int y, final int w, final int h) {
 997         if (log.isLoggable(Level.FINE)) {
 998             log.log(Level.FINE, "drawEllipse({0}, {1}, {2}, {3})",
 999                     new Object[] { x, y, w, h});
1000         }
1001         if (!shouldRenderRect(x, y, w, h,
1002                               null, state.getStrokeNoClone().getPlatformStroke()))
1003         {
1004             return;
1005         }
1006         new Composite() {
1007             @Override void doPaint(Graphics g) {
1008                 g.setPaint(state.getPaintNoClone());
1009                 g.fillEllipse(x, y, w, h);
1010                 if (state.getStrokeNoClone().apply(g)) {
1011                     g.drawEllipse(x, y, w, h);
1012                 }
1013             }
1014         }.paint();
1015     }
1016 
1017     private final static BasicStroke focusRingStroke =
1018         new BasicStroke(1.1f, BasicStroke.CAP_BUTT,
1019                          BasicStroke.JOIN_ROUND, 0.0f,
1020                          new float[] {1.0f}, 0.0f);
1021 
1022     @Override
1023     public void drawFocusRing(final int x, final int y, final int w, final int h, final int rgba) {
1024         if (log.isLoggable(Level.FINE)) {
1025             log.log(Level.FINE,
1026                     String.format("drawFocusRing: %d, %d, %d, %d, 0x%x",
1027                                   x, y, w, h, rgba));
1028         }
1029         if (!shouldRenderRect(x, y, w, h, null, focusRingStroke)) {
1030             return;
1031         }
1032         new Composite() {
1033             @Override void doPaint(Graphics g) {
1034                 g.setPaint(createColor(rgba));
1035                 BasicStroke stroke = g.getStroke();
1036                 g.setStroke(focusRingStroke);
1037                 g.drawRoundRect(x, y, w, h, 4, 4);
1038                 g.setStroke(stroke);
1039             }
1040         }.paint();
1041     }
1042 
1043     public void setAlpha(float alpha) {
1044         log.log(Level.FINE, "setAlpha({0})", alpha);
1045 
1046         state.setAlpha(alpha);
1047 
1048         if (null != cachedGraphics) {
1049             cachedGraphics.setExtraAlpha(state.getAlpha());
1050         }
1051     }
1052 
1053     public float getAlpha() {
1054         return state.getAlpha();
1055     }
1056 
1057     @Override public void beginTransparencyLayer(float opacity) {
1058         TransparencyLayer layer = new TransparencyLayer(
1059                 getGraphics(false), state.getClipNoClone(), opacity);
1060 
1061         if (log.isLoggable(Level.FINE)) {
1062             log.fine(String.format("beginTransparencyLayer(%s)", layer));
1063         }
1064 
1065         //[saveStateIntertal] will work as [saveState]
1066         state.markAsRestorePoint();
1067 
1068         startNewLayer(layer);
1069     }
1070 
1071     @Override public void endTransparencyLayer() {
1072         if (log.isLoggable(Level.FINE)) {
1073             log.fine(String.format("endTransparencyLayer(%s)", state.getLayerNoClone()));
1074         }
1075 
1076         //pair to [startNewLayer] that works as [saveState] call
1077         restoreState();
1078     }
1079 
1080     @Override
1081     public void drawWidget(final RenderTheme theme, final Ref widget, final int x, final int y) {
1082         WCSize s = theme.getWidgetSize(widget);
1083         if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) {
1084             return;
1085         }
1086         new Composite() {
1087             @Override void doPaint(Graphics g) {
1088                 theme.drawWidget(WCGraphicsPrismContext.this, widget, x, y);
1089             }
1090         }.paint();
1091     }
1092 
1093     @Override
1094     public void drawScrollbar(final ScrollBarTheme theme, final Ref widget, int x, int y,
1095                               int pressedPart, int hoveredPart)
1096     {
1097         if (log.isLoggable(Level.FINE)) {
1098             log.fine(String.format("drawScrollbar(%s, %s, x = %d, y = %d)", theme, widget, x, y));
1099         }
1100 
1101         WCSize s = theme.getWidgetSize(widget);
1102         if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) {
1103             return;
1104         }
1105         new Composite() {
1106             @Override void doPaint(Graphics g) {
1107                 theme.paint(WCGraphicsPrismContext.this, widget, x, y, pressedPart, hoveredPart);
1108             }
1109         }.paint();
1110     }
1111 
1112     private static Rectangle intersect(Rectangle what, Rectangle with) {
1113         if (what == null) {
1114             return with;
1115         }
1116         RectBounds b = what.toRectBounds();
1117         b.intersectWith(with);
1118         what.setBounds(b);
1119         return what;
1120     }
1121 
1122     static Color createColor(int rgba) {
1123         float a = (0xFF & (rgba >> 24)) / 255.0f;
1124         float r = (0xFF & (rgba >> 16)) / 255.0f;
1125         float g = (0xFF & (rgba >> 8)) / 255.0f;
1126         float b = (0xFF & (rgba)) / 255.0f;
1127         return new Color(r, g, b, a);
1128     }
1129 
1130     private static Color4f createColor4f(int rgba) {
1131         float a = (0xFF & (rgba >> 24)) / 255.0f;
1132         float r = (0xFF & (rgba >> 16)) / 255.0f;
1133         float g = (0xFF & (rgba >> 8)) / 255.0f;
1134         float b = (0xFF & (rgba)) / 255.0f;
1135         return new Color4f(r, g, b, a);
1136     }
1137 
1138     private DropShadow createShadow(float dx, float dy, float blur, int rgba) {
1139         if (dx == 0f && dy == 0f && blur == 0f) {
1140             return null;
1141         }
1142         DropShadow shadow = new DropShadow();
1143         shadow.setOffsetX((int) dx);
1144         shadow.setOffsetY((int) dy);
1145         shadow.setRadius((blur < 0f) ? 0f : (blur > 127f) ? 127f : blur);
1146         shadow.setColor(createColor4f(rgba));
1147         return shadow;
1148     }
1149 
1150     private void render(Graphics g, Effect effect, Paint paint, BasicStroke stroke, NGNode node) {
1151         if (node instanceof NGShape) {
1152             NGShape shape = (NGShape) node;
1153             Shape realShape = shape.getShape();
1154             Paint strokePaint = state.getStrokeNoClone().getPaint();
1155             if ((stroke != null) && (strokePaint != null)) {
1156                 realShape = stroke.createStrokedShape(realShape);
1157                 shape.setDrawStroke(stroke);
1158                 shape.setDrawPaint(strokePaint);
1159                 shape.setMode((paint == null) ? NGShape.Mode.STROKE : NGShape.Mode.STROKE_FILL);
1160             } else {
1161                 shape.setMode((paint == null) ? NGShape.Mode.EMPTY : NGShape.Mode.FILL);
1162             }
1163             shape.setFillPaint(paint);
1164             shape.setContentBounds(realShape.getBounds());
1165         }
1166         boolean culling = g.hasPreCullingBits();
1167         g.setHasPreCullingBits(false);
1168         node.setEffect(effect);
1169         node.render(g);
1170         g.setHasPreCullingBits(culling);
1171     }
1172 
1173     private static final class ContextState {
1174         private final WCStrokeImpl stroke = new WCStrokeImpl();
1175         private Rectangle clip;
1176         private Paint paint;
1177         private float alpha;
1178 
1179         private boolean textFill = true;
1180         private boolean textStroke = false;
1181         private boolean textClip = false;
1182         private boolean restorePoint = false;
1183 
1184         private DropShadow shadow;
1185         private Affine3D xform;
1186         private Layer layer;
1187         private int compositeOperation;
1188 
1189         private ContextState() {
1190             clip = null;
1191             paint = Color.BLACK;
1192             stroke.setPaint(Color.BLACK);
1193             alpha = 1.0f;
1194             xform = new Affine3D();
1195             compositeOperation = COMPOSITE_SOURCE_OVER;
1196         }
1197 
1198         private ContextState(ContextState state) {
1199             stroke.copyFrom(state.getStrokeNoClone());
1200             setPaint(state.getPaintNoClone());
1201             clip = state.getClipNoClone();
1202             if (clip != null) {
1203                 clip = new Rectangle(clip);
1204             }
1205             xform = new Affine3D(state.getTransformNoClone());
1206             setShadow(state.getShadowNoClone());
1207             setLayer(state.getLayerNoClone());
1208             setAlpha(state.getAlpha());
1209             setTextMode(state.isTextFill(), state.isTextStroke(), state.isTextClip());
1210             setCompositeOperation(state.getCompositeOperation());
1211         }
1212 
1213         @Override
1214         protected ContextState clone() {
1215             return new ContextState(this);
1216         }
1217 
1218         private void apply(Graphics g) {
1219             //TODO: Verify if we need to apply more properties from state
1220             g.setTransform(getTransformNoClone());
1221             g.setClipRect(getClipNoClone());
1222             g.setExtraAlpha(getAlpha());
1223         }
1224 
1225         private int getCompositeOperation() {
1226             return compositeOperation;
1227         }
1228 
1229         private void setCompositeOperation(int compositeOperation) {
1230             this.compositeOperation = compositeOperation;
1231         }
1232 
1233         private WCStrokeImpl getStrokeNoClone() {
1234             return stroke;
1235         }
1236 
1237         private Paint getPaintNoClone() {
1238             return paint;
1239         }
1240 
1241         private void setPaint(Paint paint) {
1242             this.paint = paint;
1243         }
1244 
1245         private Rectangle getClipNoClone() {
1246             return clip;
1247         }
1248 
1249         private Layer getLayerNoClone() {
1250             return layer;
1251         }
1252 
1253         private void setLayer(Layer layer) {
1254             this.layer = layer;
1255         }
1256 
1257         private void setClip(Rectangle area) {
1258             clip = area;
1259         }
1260 
1261         private void clip(Rectangle area) {
1262             if (null == clip) {
1263                 clip = area;
1264             } else {
1265                 clip.intersectWith(area);
1266             }
1267         }
1268 
1269         private void setAlpha(float alpha) {
1270             this.alpha = alpha;
1271         }
1272 
1273         private float getAlpha() {
1274             return alpha;
1275         }
1276 
1277         private void setTextMode(boolean fill, boolean stroke, boolean clip) {
1278             textFill = fill;
1279             textStroke = stroke;
1280             textClip = clip;
1281         }
1282 
1283         private boolean isTextFill() {
1284             return textFill;
1285         }
1286 
1287         private boolean isTextStroke() {
1288             return textStroke;
1289         }
1290 
1291         private boolean isTextClip() {
1292             return textClip;
1293         }
1294 
1295         private void markAsRestorePoint() {
1296             restorePoint = true;
1297         }
1298 
1299         private boolean isRestorePoint() {
1300             return restorePoint;
1301         }
1302 
1303         private void setShadow(DropShadow shadow) {
1304             this.shadow = shadow;
1305         }
1306 
1307         private DropShadow getShadowNoClone() {
1308             return shadow;
1309         }
1310 
1311         private Affine3D getTransformNoClone() {
1312             return xform;
1313         }
1314 
1315         private void setTransform(final Affine3D at) {
1316             this.xform.setTransform(at);
1317         }
1318 
1319         private void concatTransform(Affine3D at) {
1320             xform.concatenate(at);
1321         }
1322 
1323         private void translate(double dx, double dy) {
1324             xform.translate(dx, dy);
1325         }
1326 
1327         private void scale(double sx, double sy) {
1328             xform.scale(sx,sy);
1329         }
1330 
1331         private void rotate(double radians) {
1332             xform.rotate(radians);
1333         }
1334     }
1335 
1336     private abstract static class Layer {
1337         FilterContext fctx;
1338         PrDrawable buffer;
1339         Graphics graphics;
1340         final Rectangle bounds;
1341         boolean permanent;
1342 
1343         Layer(Graphics g, Rectangle bounds, boolean permanent) {
1344             this.bounds = new Rectangle(bounds);
1345             this.permanent = permanent;
1346 
1347             // avoid creating zero-size drawable, see also RT-21410
1348             int w = Math.max(bounds.width, 1);
1349             int h = Math.max(bounds.height, 1);
1350             fctx = getFilterContext(g);
1351             if (permanent) {
1352                 ResourceFactory f = GraphicsPipeline.getDefaultResourceFactory();
1353                 RTTexture rtt = f.createRTTexture(w, h, Texture.WrapMode.CLAMP_NOT_NEEDED);
1354                 rtt.makePermanent();
1355                 buffer = ((PrRenderer)Renderer.getRenderer(fctx)).createDrawable(rtt);
1356             } else {
1357                 buffer = (PrDrawable) Effect.getCompatibleImage(fctx, w, h);
1358             }
1359         }
1360 
1361         Graphics getGraphics() {
1362             if (graphics == null) {
1363                 graphics = buffer.createGraphics();
1364             }
1365             return graphics;
1366         }
1367 
1368         abstract void init(Graphics g);
1369 
1370         abstract void render(Graphics g);
1371 
1372         private void dispose() {
1373             if (buffer != null) {
1374                 if (permanent) {
1375                     buffer.flush(); // releases the resource
1376                 } else {
1377                     Effect.releaseCompatibleImage(fctx, buffer);
1378                 }
1379                 fctx = null;
1380                 buffer = null;
1381             }
1382         }
1383 
1384         private double getX() { return (double) bounds.x; }
1385         private double getY() { return (double) bounds.y; }
1386     }
1387 
1388     private final class TransparencyLayer extends Layer {
1389         private final float opacity;
1390 
1391         private TransparencyLayer(Graphics g, Rectangle bounds, float opacity) {
1392             super(g, bounds, false);
1393             this.opacity = opacity;
1394         }
1395 
1396         @Override void init(Graphics g) {
1397             state.setCompositeOperation(COMPOSITE_SOURCE_OVER);
1398         }
1399 
1400         @Override void render(Graphics g) {
1401             new Composite() {
1402                 @Override void doPaint(Graphics g) {
1403                     float op = g.getExtraAlpha();
1404                     g.setExtraAlpha(opacity);
1405                     Affine3D tx = new Affine3D(g.getTransformNoClone());
1406                     g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
1407                     g.drawTexture(buffer.getTextureObject(),
1408                             bounds.x, bounds.y, bounds.width, bounds.height);
1409                     g.setTransform(tx);
1410                     g.setExtraAlpha(op);
1411                 }
1412             }.paint(g);
1413         }
1414 
1415         @Override public String toString() {
1416             return String.format("TransparencyLayer[%d,%d + %dx%d, opacity %.2f]",
1417                 bounds.x, bounds.y, bounds.width, bounds.height, opacity);
1418         }
1419     }
1420 
1421     private static final class ClipLayer extends Layer {
1422         private final WCPath normalizedToClipPath;
1423         private boolean srcover;
1424 
1425         private ClipLayer(Graphics g, Rectangle bounds, WCPath normalizedToClipPath,
1426                           boolean permanent)
1427         {
1428             super(g, bounds, permanent);
1429             this.normalizedToClipPath = normalizedToClipPath;
1430             srcover = true;
1431         }
1432 
1433         @Override void init(Graphics g) {
1434             RTTexture texture = null;
1435             ReadbackGraphics readbackGraphics = null;
1436             try {
1437                 readbackGraphics = (ReadbackGraphics) g;
1438                 texture = readbackGraphics.readBack(bounds);
1439                 getGraphics().drawTexture(texture, 0, 0, bounds.width, bounds.height);
1440             } finally {
1441                 if (readbackGraphics != null && texture != null) {
1442                     readbackGraphics.releaseReadBackBuffer(texture);
1443                 }
1444             }
1445             srcover = false;
1446         }
1447 
1448         @Override void render(Graphics g) {
1449             Path2D p2d = ((WCPathImpl)normalizedToClipPath).getPlatformPath();
1450 
1451             // render normalizedToClipPath to a drawable
1452             PrDrawable bufferImg = (PrDrawable) Effect.getCompatibleImage(
1453                     fctx, bounds.width, bounds.height);
1454             Graphics bufferGraphics = bufferImg.createGraphics();
1455 
1456             bufferGraphics.setPaint(Color.BLACK);
1457             bufferGraphics.fill(p2d);
1458 
1459             // blend buffer and clipImg onto |g|
1460             if (g instanceof MaskTextureGraphics && ! (g instanceof PrinterGraphics)) {
1461                 MaskTextureGraphics mg = (MaskTextureGraphics) g;
1462                 if (srcover) {
1463                     mg.drawPixelsMasked(buffer.getTextureObject(),
1464                                         bufferImg.getTextureObject(),
1465                                         bounds.x, bounds.y, bounds.width, bounds.height,
1466                                         0, 0, 0, 0);
1467                 } else {
1468                     mg.maskInterpolatePixels(buffer.getTextureObject(),
1469                                              bufferImg.getTextureObject(),
1470                                              bounds.x, bounds.y, bounds.width, bounds.height,
1471                                              0, 0, 0, 0);
1472                 }
1473             } else {
1474                 Blend blend = new Blend(Blend.Mode.SRC_IN,
1475                         new PassThrough(bufferImg, bounds.width, bounds.height),
1476                         new PassThrough(buffer, bounds.width, bounds.height));
1477                 Affine3D tx = new Affine3D(g.getTransformNoClone());
1478                 g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
1479                 PrEffectHelper.render(blend, g, bounds.x, bounds.y, null);
1480                 g.setTransform(tx);
1481             }
1482 
1483             Effect.releaseCompatibleImage(fctx, bufferImg);
1484         }
1485 
1486         @Override public String toString() {
1487             return String.format("ClipLayer[%d,%d + %dx%d, path %s]",
1488                     bounds.x, bounds.y, bounds.width, bounds.height,
1489                     normalizedToClipPath);
1490         }
1491     }
1492 
1493     private abstract class Composite {
1494         abstract void doPaint(Graphics g);
1495 
1496         void paint() {
1497             paint(getGraphics(true));
1498         }
1499 
1500         void paint(Graphics g) {
1501             if (g != null) {
1502                 CompositeMode oldCompositeMode = g.getCompositeMode();
1503                 switch (state.getCompositeOperation()) {
1504                     // decode operations that don't require Blend first
1505                     case COMPOSITE_COPY:
1506                         g.setCompositeMode(CompositeMode.SRC);
1507                         doPaint(g);
1508                         g.setCompositeMode(oldCompositeMode);
1509                         break;
1510                     case COMPOSITE_SOURCE_OVER:
1511                         g.setCompositeMode(CompositeMode.SRC_OVER);
1512                         doPaint(g);
1513                         g.setCompositeMode(oldCompositeMode);
1514                         break;
1515                     default:
1516                         // other operations require usage of Blend
1517                         blend(g);
1518                         break;
1519                 }
1520                 isRootLayerValid = false;
1521             }
1522         }
1523 
1524         private void blend(Graphics g) {
1525             FilterContext fctx = getFilterContext(g);
1526             PrDrawable dstImg = null;
1527             PrDrawable srcImg = null;
1528             ReadbackGraphics readBackGraphics = null;
1529             RTTexture texture = null;
1530             Rectangle clip = state.getClipNoClone();
1531             WCImage image = getImage();
1532             try {
1533                 if (image != null && image instanceof PrismImage) {
1534                     // blending on canvas
1535                     dstImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height);
1536                     Graphics dstG = dstImg.createGraphics();
1537                     ((PrismImage) image).draw(dstG,
1538                             0, 0, clip.width, clip.height,
1539                             clip.x, clip.y, clip.width, clip.height);
1540                 } else {
1541                     // blending on page
1542                     readBackGraphics = (ReadbackGraphics) g;
1543                     texture = readBackGraphics.readBack(clip);
1544                     dstImg = PrDrawable.create(fctx, texture);
1545                 }
1546 
1547                 srcImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height);
1548                 Graphics srcG = srcImg.createGraphics();
1549                 state.apply(srcG);
1550                 doPaint(srcG);
1551 
1552                 g.clear();
1553                 PrEffectHelper.render(createEffect(dstImg, srcImg, clip.width, clip.height), g, 0, 0, null);
1554 
1555             } finally {
1556                 if (srcImg != null) {
1557                     Effect.releaseCompatibleImage(fctx, srcImg);
1558                 }
1559                 if (dstImg != null) {
1560                     if (readBackGraphics != null && texture != null) {
1561                         readBackGraphics.releaseReadBackBuffer(texture);
1562                     } else {
1563                         Effect.releaseCompatibleImage(fctx, dstImg);
1564                     }
1565                 }
1566             }
1567         }
1568 
1569         // provides some syntax sugar for createEffect()
1570         private Effect createBlend(Blend.Mode mode,
1571                                    PrDrawable dstImg,
1572                                    PrDrawable srcImg,
1573                                    int width,
1574                                    int height)
1575         {
1576             return new Blend(
1577                     mode,
1578                     new PassThrough(dstImg, width, height),
1579                     new PassThrough(srcImg, width, height));
1580         }
1581 
1582         private Effect createEffect(PrDrawable dstImg,
1583                                     PrDrawable srcImg,
1584                                     int width,
1585                                     int height)
1586         {
1587             switch (state.getCompositeOperation()) {
1588                 case COMPOSITE_CLEAR: // same as xor
1589                 case COMPOSITE_XOR:
1590                     return new Blend(
1591                             SRC_OVER,
1592                             createBlend(SRC_OUT, dstImg, srcImg, width, height),
1593                             createBlend(SRC_OUT, srcImg, dstImg, width, height)
1594                     );
1595                 case COMPOSITE_SOURCE_IN:
1596                     return createBlend(SRC_IN, dstImg, srcImg, width, height);
1597                 case COMPOSITE_SOURCE_OUT:
1598                     return createBlend(SRC_OUT, dstImg, srcImg, width, height);
1599                 case COMPOSITE_SOURCE_ATOP:
1600                     return createBlend(SRC_ATOP, dstImg, srcImg, width, height);
1601                 case COMPOSITE_DESTINATION_OVER:
1602                     return createBlend(SRC_OVER, srcImg, dstImg, width, height);
1603                 case COMPOSITE_DESTINATION_IN:
1604                     return createBlend(SRC_IN, srcImg, dstImg, width, height);
1605                 case COMPOSITE_DESTINATION_OUT:
1606                     return createBlend(SRC_OUT, srcImg, dstImg, width, height);
1607                 case COMPOSITE_DESTINATION_ATOP:
1608                     return createBlend(SRC_ATOP, srcImg, dstImg, width, height);
1609                 case COMPOSITE_HIGHLIGHT:
1610                     return createBlend(ADD, dstImg, srcImg, width, height);
1611                 default:
1612                     return createBlend(SRC_OVER, dstImg, srcImg, width, height);
1613             }
1614         }
1615     }
1616 
1617     private static final class PassThrough extends Effect {
1618         private final PrDrawable img;
1619         private final int width;
1620         private final int height;
1621 
1622         private PassThrough(PrDrawable img, int width, int height) {
1623             this.img = img;
1624             this.width = width;
1625             this.height = height;
1626         }
1627 
1628         @Override public ImageData filter(
1629                 FilterContext fctx,
1630                 BaseTransform transform,
1631                 Rectangle outputClip,
1632                 Object renderHelper,
1633                 Effect defaultInput) {
1634             // We have an unpaired lock() here, because unlocking is done
1635             // internally by ImageData. See RT-33625 for details.
1636             img.lock();
1637             ImageData imgData = new ImageData(fctx, img, new Rectangle(
1638                                               (int) transform.getMxt(),
1639                                               (int) transform.getMyt(),
1640                                               width, height));
1641             imgData.setReusable(true);
1642             return imgData;
1643         }
1644 
1645         @Override public RectBounds getBounds(
1646                 BaseTransform transform,
1647                 Effect defaultInput) {
1648             return null;
1649         }
1650 
1651         @Override public AccelType getAccelType(FilterContext fctx) {
1652             return AccelType.INTRINSIC;
1653         }
1654 
1655         @Override
1656         public boolean reducesOpaquePixels() {
1657             return false;
1658         }
1659 
1660         @Override
1661         public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
1662             return null;
1663         }
1664     }
1665 
1666     private static FilterContext getFilterContext(Graphics g) {
1667         Screen screen = g.getAssociatedScreen();
1668         if (screen == null) {
1669             ResourceFactory factory = g.getResourceFactory();
1670             return PrFilterContext.getPrinterContext(factory);
1671         } else {
1672             return PrFilterContext.getInstance(screen);
1673         }
1674     }
1675 
1676     @Override
1677     public void strokeArc(final int x, final int y, final int w, final int h,
1678                           final int startAngle, final int angleSpan)
1679     {
1680         if (log.isLoggable(Level.FINE)) {
1681             log.fine(String.format("strokeArc(%d, %d, %d, %d, %d, %d)",
1682                                    x, y, w, h, startAngle, angleSpan));
1683         }
1684         Arc2D arc = new Arc2D(x, y, w, h, startAngle, angleSpan, Arc2D.OPEN);
1685         if (state.getStrokeNoClone().isApplicable() &&
1686             !shouldRenderShape(arc, null, state.getStrokeNoClone().getPlatformStroke()))
1687         {
1688             return;
1689         }
1690         new Composite() {
1691             @Override void doPaint(Graphics g) {
1692                 if (state.getStrokeNoClone().apply(g)) {
1693                     g.draw(arc);
1694                 }
1695             }
1696         }.paint();
1697     }
1698 
1699     @Override
1700     public WCImage getImage() {
1701         return null;
1702     }
1703 
1704     @Override
1705     public void strokeRect(final float x, final float y, final float w, final float h,
1706                            final float lineWidth) {
1707         if (log.isLoggable(Level.FINE)) {
1708             log.fine(String.format("strokeRect_FFFFF(%f, %f, %f, %f, %f)",
1709                                    x, y, w, h, lineWidth));
1710         }
1711         BasicStroke stroke = new BasicStroke(
1712             lineWidth,
1713             BasicStroke.CAP_BUTT,
1714             BasicStroke.JOIN_MITER,
1715             Math.max(1.0f, lineWidth),
1716             state.getStrokeNoClone().getDashSizes(),
1717             state.getStrokeNoClone().getDashOffset());
1718 
1719         if (!shouldRenderRect(x, y, w, h, null, stroke)) {
1720             return;
1721         }
1722         new Composite() {
1723             @Override void doPaint(Graphics g) {
1724                 g.setStroke(stroke);
1725                 Paint paint = state.getStrokeNoClone().getPaint();
1726                 if (paint == null) {
1727                     paint = state.getPaintNoClone();
1728                 }
1729                 g.setPaint(paint);
1730                 g.drawRect(x, y, w, h);
1731             }
1732         }.paint();
1733     }
1734 
1735     @Override
1736     public void strokePath(final WCPath path) {
1737         log.fine("strokePath");
1738         if (path != null) {
1739             final BasicStroke stroke = state.getStrokeNoClone().getPlatformStroke();
1740             final DropShadow shadow = state.getShadowNoClone();
1741             final Path2D p2d = (Path2D)path.getPlatformPath();
1742 
1743             if ((stroke == null && shadow == null) ||
1744                 !shouldRenderShape(p2d, shadow, stroke))
1745             {
1746                 return;
1747             }
1748             new Composite() {
1749                 @Override void doPaint(Graphics g) {
1750                     if (shadow != null) {
1751                         final NGPath node = new NGPath();
1752                         node.updateWithPath2d(p2d);
1753                         render(g, shadow, null, stroke, node);
1754                     } else if (stroke != null) {
1755                         Paint paint = state.getStrokeNoClone().getPaint();
1756                         if (paint == null) {
1757                             paint = state.getPaintNoClone();
1758                         }
1759                         g.setPaint(paint);
1760                         g.setStroke(stroke);
1761                         g.draw(p2d);
1762                     }
1763                 }
1764             }.paint();
1765         }
1766     }
1767 
1768     @Override
1769     public void fillPath(final WCPath path) {
1770         log.fine("fillPath");
1771         if (path != null) {
1772             if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(),
1773                                    state.getShadowNoClone(), null))
1774             {
1775                 return;
1776             }
1777             new Composite() {
1778                 @Override void doPaint(Graphics g) {
1779                     Path2D p2d = (Path2D) path.getPlatformPath();
1780                     Paint paint = state.getPaintNoClone();
1781                     DropShadow shadow = state.getShadowNoClone();
1782                     if (shadow != null) {
1783                         final NGPath node = new NGPath();
1784                         node.updateWithPath2d(p2d);
1785                         render(g, shadow, paint, null, node);
1786                     } else {
1787                         g.setPaint(paint);
1788                         g.fill(p2d);
1789                     }
1790                 }
1791             }.paint();
1792         }
1793     }
1794 
1795     public void setTransform(WCTransform tm) {
1796         double m[] = tm.getMatrix();
1797         Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5]));
1798         if (state.getLayerNoClone() == null) {
1799             at.preConcatenate(baseTransform);
1800         }
1801         state.setTransform(at);
1802         resetCachedGraphics();
1803     }
1804 
1805     public WCTransform getTransform() {
1806         Affine3D xf = state.getTransformNoClone();
1807         return new WCTransform(xf.getMxx(), xf.getMyx(),
1808                                xf.getMxy(), xf.getMyy(),
1809                                xf.getMxt(), xf.getMyt());
1810     }
1811 
1812     public void concatTransform(WCTransform tm) {
1813         double m[] = tm.getMatrix();
1814         Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5]));
1815         state.concatTransform(at);
1816         resetCachedGraphics();
1817     }
1818 
1819     @Override
1820     public void flush() {
1821         flushAllLayers();
1822     }
1823 
1824     @Override
1825     public WCGradient createLinearGradient(WCPoint p1, WCPoint p2) {
1826         return new WCLinearGradient(p1, p2);
1827     }
1828 
1829     @Override
1830     public WCGradient createRadialGradient(WCPoint p1, float r1, WCPoint p2, float r2) {
1831         return new WCRadialGradient(p1, r1, p2, r2);
1832     }
1833 }