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.logging.PlatformLogger;
  37 import com.sun.javafx.logging.PlatformLogger.Level;
  38 import com.sun.javafx.scene.text.GlyphList;
  39 import com.sun.javafx.scene.text.TextLayout;
  40 import com.sun.javafx.sg.prism.*;
  41 import com.sun.javafx.text.TextRun;
  42 import com.sun.prism.*;
  43 import com.sun.prism.paint.Color;
  44 import com.sun.prism.paint.Gradient;
  45 import com.sun.prism.paint.ImagePattern;
  46 import com.sun.prism.paint.Paint;
  47 import com.sun.scenario.effect.*;
  48 import com.sun.scenario.effect.impl.prism.PrDrawable;
  49 import com.sun.scenario.effect.impl.prism.PrEffectHelper;
  50 import com.sun.scenario.effect.impl.prism.PrFilterContext;
  51 import com.sun.webkit.graphics.*;
  52 
  53 import java.nio.ByteBuffer;
  54 import java.nio.ByteOrder;
  55 import java.security.AccessController;
  56 import java.security.PrivilegedAction;
  57 import java.util.ArrayList;
  58 import java.util.List;
  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 PlatformLogger log =
  82             PlatformLogger.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.fine(String.format("drawFocusRing: %d, %d, %d, %d, 0x%x", x, y, w, h, rgba));
1026         }
1027         if (!shouldRenderRect(x, y, w, h, null, focusRingStroke)) {
1028             return;
1029         }
1030         new Composite() {
1031             @Override void doPaint(Graphics g) {
1032                 g.setPaint(createColor(rgba));
1033                 BasicStroke stroke = g.getStroke();
1034                 g.setStroke(focusRingStroke);
1035                 g.drawRoundRect(x, y, w, h, 4, 4);
1036                 g.setStroke(stroke);
1037             }
1038         }.paint();
1039     }
1040 
1041     public void setAlpha(float alpha) {
1042         log.fine("setAlpha({0})", alpha);
1043 
1044         state.setAlpha(alpha);
1045 
1046         if (null != cachedGraphics) {
1047             cachedGraphics.setExtraAlpha(state.getAlpha());
1048         }
1049     }
1050 
1051     public float getAlpha() {
1052         return state.getAlpha();
1053     }
1054 
1055     @Override public void beginTransparencyLayer(float opacity) {
1056         TransparencyLayer layer = new TransparencyLayer(
1057                 getGraphics(false), state.getClipNoClone(), opacity);
1058 
1059         if (log.isLoggable(Level.FINE)) {
1060             log.fine(String.format("beginTransparencyLayer(%s)", layer));
1061         }
1062 
1063         //[saveStateIntertal] will work as [saveState]
1064         state.markAsRestorePoint();
1065 
1066         startNewLayer(layer);
1067     }
1068 
1069     @Override public void endTransparencyLayer() {
1070         if (log.isLoggable(Level.FINE)) {
1071             log.fine(String.format("endTransparencyLayer(%s)", state.getLayerNoClone()));
1072         }
1073 
1074         //pair to [startNewLayer] that works as [saveState] call
1075         restoreState();
1076     }
1077 
1078     @Override
1079     public void drawWidget(final RenderTheme theme, final Ref widget, final int x, final int y) {
1080         WCSize s = theme.getWidgetSize(widget);
1081         if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) {
1082             return;
1083         }
1084         new Composite() {
1085             @Override void doPaint(Graphics g) {
1086                 theme.drawWidget(WCGraphicsPrismContext.this, widget, x, y);
1087             }
1088         }.paint();
1089     }
1090 
1091     @Override
1092     public void drawScrollbar(final ScrollBarTheme theme, final Ref widget, int x, int y,
1093                               int pressedPart, int hoveredPart)
1094     {
1095         if (log.isLoggable(Level.FINE)) {
1096             log.fine(String.format("drawScrollbar(%s, %s, x = %d, y = %d)", theme, widget, x, y));
1097         }
1098 
1099         WCSize s = theme.getWidgetSize(widget);
1100         if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) {
1101             return;
1102         }
1103         new Composite() {
1104             @Override void doPaint(Graphics g) {
1105                 theme.paint(WCGraphicsPrismContext.this, widget, x, y, pressedPart, hoveredPart);
1106             }
1107         }.paint();
1108     }
1109 
1110     private static Rectangle intersect(Rectangle what, Rectangle with) {
1111         if (what == null) {
1112             return with;
1113         }
1114         RectBounds b = what.toRectBounds();
1115         b.intersectWith(with);
1116         what.setBounds(b);
1117         return what;
1118     }
1119 
1120     static Color createColor(int rgba) {
1121         float a = (0xFF & (rgba >> 24)) / 255.0f;
1122         float r = (0xFF & (rgba >> 16)) / 255.0f;
1123         float g = (0xFF & (rgba >> 8)) / 255.0f;
1124         float b = (0xFF & (rgba)) / 255.0f;
1125         return new Color(r, g, b, a);
1126     }
1127 
1128     private static Color4f createColor4f(int rgba) {
1129         float a = (0xFF & (rgba >> 24)) / 255.0f;
1130         float r = (0xFF & (rgba >> 16)) / 255.0f;
1131         float g = (0xFF & (rgba >> 8)) / 255.0f;
1132         float b = (0xFF & (rgba)) / 255.0f;
1133         return new Color4f(r, g, b, a);
1134     }
1135 
1136     private DropShadow createShadow(float dx, float dy, float blur, int rgba) {
1137         if (dx == 0f && dy == 0f && blur == 0f) {
1138             return null;
1139         }
1140         DropShadow shadow = new DropShadow();
1141         shadow.setOffsetX((int) dx);
1142         shadow.setOffsetY((int) dy);
1143         shadow.setRadius((blur < 0f) ? 0f : (blur > 127f) ? 127f : blur);
1144         shadow.setColor(createColor4f(rgba));
1145         return shadow;
1146     }
1147 
1148     private void render(Graphics g, Effect effect, Paint paint, BasicStroke stroke, NGNode node) {
1149         if (node instanceof NGShape) {
1150             NGShape shape = (NGShape) node;
1151             Shape realShape = shape.getShape();
1152             Paint strokePaint = state.getStrokeNoClone().getPaint();
1153             if ((stroke != null) && (strokePaint != null)) {
1154                 realShape = stroke.createStrokedShape(realShape);
1155                 shape.setDrawStroke(stroke);
1156                 shape.setDrawPaint(strokePaint);
1157                 shape.setMode((paint == null) ? NGShape.Mode.STROKE : NGShape.Mode.STROKE_FILL);
1158             } else {
1159                 shape.setMode((paint == null) ? NGShape.Mode.EMPTY : NGShape.Mode.FILL);
1160             }
1161             shape.setFillPaint(paint);
1162             shape.setContentBounds(realShape.getBounds());
1163         }
1164         boolean culling = g.hasPreCullingBits();
1165         g.setHasPreCullingBits(false);
1166         node.setEffect(effect);
1167         node.render(g);
1168         g.setHasPreCullingBits(culling);
1169     }
1170 
1171     private static final class ContextState {
1172         private final WCStrokeImpl stroke = new WCStrokeImpl();
1173         private Rectangle clip;
1174         private Paint paint;
1175         private float alpha;
1176 
1177         private boolean textFill = true;
1178         private boolean textStroke = false;
1179         private boolean textClip = false;
1180         private boolean restorePoint = false;
1181 
1182         private DropShadow shadow;
1183         private Affine3D xform;
1184         private Layer layer;
1185         private int compositeOperation;
1186 
1187         private ContextState() {
1188             clip = null;
1189             paint = Color.BLACK;
1190             stroke.setPaint(Color.BLACK);
1191             alpha = 1.0f;
1192             xform = new Affine3D();
1193             compositeOperation = COMPOSITE_SOURCE_OVER;
1194         }
1195 
1196         private ContextState(ContextState state) {
1197             stroke.copyFrom(state.getStrokeNoClone());
1198             setPaint(state.getPaintNoClone());
1199             clip = state.getClipNoClone();
1200             if (clip != null) {
1201                 clip = new Rectangle(clip);
1202             }
1203             xform = new Affine3D(state.getTransformNoClone());
1204             setShadow(state.getShadowNoClone());
1205             setLayer(state.getLayerNoClone());
1206             setAlpha(state.getAlpha());
1207             setTextMode(state.isTextFill(), state.isTextStroke(), state.isTextClip());
1208             setCompositeOperation(state.getCompositeOperation());
1209         }
1210 
1211         @Override
1212         protected ContextState clone() {
1213             return new ContextState(this);
1214         }
1215 
1216         private void apply(Graphics g) {
1217             //TODO: Verify if we need to apply more properties from state
1218             g.setTransform(getTransformNoClone());
1219             g.setClipRect(getClipNoClone());
1220             g.setExtraAlpha(getAlpha());
1221         }
1222 
1223         private int getCompositeOperation() {
1224             return compositeOperation;
1225         }
1226 
1227         private void setCompositeOperation(int compositeOperation) {
1228             this.compositeOperation = compositeOperation;
1229         }
1230 
1231         private WCStrokeImpl getStrokeNoClone() {
1232             return stroke;
1233         }
1234 
1235         private Paint getPaintNoClone() {
1236             return paint;
1237         }
1238 
1239         private void setPaint(Paint paint) {
1240             this.paint = paint;
1241         }
1242 
1243         private Rectangle getClipNoClone() {
1244             return clip;
1245         }
1246 
1247         private Layer getLayerNoClone() {
1248             return layer;
1249         }
1250 
1251         private void setLayer(Layer layer) {
1252             this.layer = layer;
1253         }
1254 
1255         private void setClip(Rectangle area) {
1256             clip = area;
1257         }
1258 
1259         private void clip(Rectangle area) {
1260             if (null == clip) {
1261                 clip = area;
1262             } else {
1263                 clip.intersectWith(area);
1264             }
1265         }
1266 
1267         private void setAlpha(float alpha) {
1268             this.alpha = alpha;
1269         }
1270 
1271         private float getAlpha() {
1272             return alpha;
1273         }
1274 
1275         private void setTextMode(boolean fill, boolean stroke, boolean clip) {
1276             textFill = fill;
1277             textStroke = stroke;
1278             textClip = clip;
1279         }
1280 
1281         private boolean isTextFill() {
1282             return textFill;
1283         }
1284 
1285         private boolean isTextStroke() {
1286             return textStroke;
1287         }
1288 
1289         private boolean isTextClip() {
1290             return textClip;
1291         }
1292 
1293         private void markAsRestorePoint() {
1294             restorePoint = true;
1295         }
1296 
1297         private boolean isRestorePoint() {
1298             return restorePoint;
1299         }
1300 
1301         private void setShadow(DropShadow shadow) {
1302             this.shadow = shadow;
1303         }
1304 
1305         private DropShadow getShadowNoClone() {
1306             return shadow;
1307         }
1308 
1309         private Affine3D getTransformNoClone() {
1310             return xform;
1311         }
1312 
1313         private void setTransform(final Affine3D at) {
1314             this.xform.setTransform(at);
1315         }
1316 
1317         private void concatTransform(Affine3D at) {
1318             xform.concatenate(at);
1319         }
1320 
1321         private void translate(double dx, double dy) {
1322             xform.translate(dx, dy);
1323         }
1324 
1325         private void scale(double sx, double sy) {
1326             xform.scale(sx,sy);
1327         }
1328 
1329         private void rotate(double radians) {
1330             xform.rotate(radians);
1331         }
1332     }
1333 
1334     private abstract static class Layer {
1335         FilterContext fctx;
1336         PrDrawable buffer;
1337         Graphics graphics;
1338         final Rectangle bounds;
1339         boolean permanent;
1340 
1341         Layer(Graphics g, Rectangle bounds, boolean permanent) {
1342             this.bounds = new Rectangle(bounds);
1343             this.permanent = permanent;
1344 
1345             // avoid creating zero-size drawable, see also RT-21410
1346             int w = Math.max(bounds.width, 1);
1347             int h = Math.max(bounds.height, 1);
1348             fctx = getFilterContext(g);
1349             if (permanent) {
1350                 ResourceFactory f = GraphicsPipeline.getDefaultResourceFactory();
1351                 RTTexture rtt = f.createRTTexture(w, h, Texture.WrapMode.CLAMP_NOT_NEEDED);
1352                 rtt.makePermanent();
1353                 buffer = ((PrRenderer)Renderer.getRenderer(fctx)).createDrawable(rtt);
1354             } else {
1355                 buffer = (PrDrawable) Effect.getCompatibleImage(fctx, w, h);
1356             }
1357         }
1358 
1359         Graphics getGraphics() {
1360             if (graphics == null) {
1361                 graphics = buffer.createGraphics();
1362             }
1363             return graphics;
1364         }
1365 
1366         abstract void init(Graphics g);
1367 
1368         abstract void render(Graphics g);
1369 
1370         private void dispose() {
1371             if (buffer != null) {
1372                 if (permanent) {
1373                     buffer.flush(); // releases the resource
1374                 } else {
1375                     Effect.releaseCompatibleImage(fctx, buffer);
1376                 }
1377                 fctx = null;
1378                 buffer = null;
1379             }
1380         }
1381 
1382         private double getX() { return (double) bounds.x; }
1383         private double getY() { return (double) bounds.y; }
1384     }
1385 
1386     private final class TransparencyLayer extends Layer {
1387         private final float opacity;
1388 
1389         private TransparencyLayer(Graphics g, Rectangle bounds, float opacity) {
1390             super(g, bounds, false);
1391             this.opacity = opacity;
1392         }
1393 
1394         @Override void init(Graphics g) {
1395             state.setCompositeOperation(COMPOSITE_SOURCE_OVER);
1396         }
1397 
1398         @Override void render(Graphics g) {
1399             new Composite() {
1400                 @Override void doPaint(Graphics g) {
1401                     float op = g.getExtraAlpha();
1402                     g.setExtraAlpha(opacity);
1403                     Affine3D tx = new Affine3D(g.getTransformNoClone());
1404                     g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
1405                     g.drawTexture(buffer.getTextureObject(),
1406                             bounds.x, bounds.y, bounds.width, bounds.height);
1407                     g.setTransform(tx);
1408                     g.setExtraAlpha(op);
1409                 }
1410             }.paint(g);
1411         }
1412 
1413         @Override public String toString() {
1414             return String.format("TransparencyLayer[%d,%d + %dx%d, opacity %.2f]",
1415                 bounds.x, bounds.y, bounds.width, bounds.height, opacity);
1416         }
1417     }
1418 
1419     private static final class ClipLayer extends Layer {
1420         private final WCPath normalizedToClipPath;
1421         private boolean srcover;
1422 
1423         private ClipLayer(Graphics g, Rectangle bounds, WCPath normalizedToClipPath,
1424                           boolean permanent)
1425         {
1426             super(g, bounds, permanent);
1427             this.normalizedToClipPath = normalizedToClipPath;
1428             srcover = true;
1429         }
1430 
1431         @Override void init(Graphics g) {
1432             RTTexture texture = null;
1433             ReadbackGraphics readbackGraphics = null;
1434             try {
1435                 readbackGraphics = (ReadbackGraphics) g;
1436                 texture = readbackGraphics.readBack(bounds);
1437                 getGraphics().drawTexture(texture, 0, 0, bounds.width, bounds.height);
1438             } finally {
1439                 if (readbackGraphics != null && texture != null) {
1440                     readbackGraphics.releaseReadBackBuffer(texture);
1441                 }
1442             }
1443             srcover = false;
1444         }
1445 
1446         @Override void render(Graphics g) {
1447             Path2D p2d = ((WCPathImpl)normalizedToClipPath).getPlatformPath();
1448 
1449             // render normalizedToClipPath to a drawable
1450             PrDrawable bufferImg = (PrDrawable) Effect.getCompatibleImage(
1451                     fctx, bounds.width, bounds.height);
1452             Graphics bufferGraphics = bufferImg.createGraphics();
1453 
1454             bufferGraphics.setPaint(Color.BLACK);
1455             bufferGraphics.fill(p2d);
1456 
1457             // blend buffer and clipImg onto |g|
1458             if (g instanceof MaskTextureGraphics && ! (g instanceof PrinterGraphics)) {
1459                 MaskTextureGraphics mg = (MaskTextureGraphics) g;
1460                 if (srcover) {
1461                     mg.drawPixelsMasked(buffer.getTextureObject(),
1462                                         bufferImg.getTextureObject(),
1463                                         bounds.x, bounds.y, bounds.width, bounds.height,
1464                                         0, 0, 0, 0);
1465                 } else {
1466                     mg.maskInterpolatePixels(buffer.getTextureObject(),
1467                                              bufferImg.getTextureObject(),
1468                                              bounds.x, bounds.y, bounds.width, bounds.height,
1469                                              0, 0, 0, 0);
1470                 }
1471             } else {
1472                 Blend blend = new Blend(Blend.Mode.SRC_IN,
1473                         new PassThrough(bufferImg, bounds.width, bounds.height),
1474                         new PassThrough(buffer, bounds.width, bounds.height));
1475                 Affine3D tx = new Affine3D(g.getTransformNoClone());
1476                 g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
1477                 PrEffectHelper.render(blend, g, bounds.x, bounds.y, null);
1478                 g.setTransform(tx);
1479             }
1480 
1481             Effect.releaseCompatibleImage(fctx, bufferImg);
1482         }
1483 
1484         @Override public String toString() {
1485             return String.format("ClipLayer[%d,%d + %dx%d, path %s]",
1486                     bounds.x, bounds.y, bounds.width, bounds.height,
1487                     normalizedToClipPath);
1488         }
1489     }
1490 
1491     private abstract class Composite {
1492         abstract void doPaint(Graphics g);
1493 
1494         void paint() {
1495             paint(getGraphics(true));
1496         }
1497 
1498         void paint(Graphics g) {
1499             if (g != null) {
1500                 CompositeMode oldCompositeMode = g.getCompositeMode();
1501                 switch (state.getCompositeOperation()) {
1502                     // decode operations that don't require Blend first
1503                     case COMPOSITE_COPY:
1504                         g.setCompositeMode(CompositeMode.SRC);
1505                         doPaint(g);
1506                         g.setCompositeMode(oldCompositeMode);
1507                         break;
1508                     case COMPOSITE_SOURCE_OVER:
1509                         g.setCompositeMode(CompositeMode.SRC_OVER);
1510                         doPaint(g);
1511                         g.setCompositeMode(oldCompositeMode);
1512                         break;
1513                     default:
1514                         // other operations require usage of Blend
1515                         blend(g);
1516                         break;
1517                 }
1518                 isRootLayerValid = false;
1519             }
1520         }
1521 
1522         private void blend(Graphics g) {
1523             FilterContext fctx = getFilterContext(g);
1524             PrDrawable dstImg = null;
1525             PrDrawable srcImg = null;
1526             ReadbackGraphics readBackGraphics = null;
1527             RTTexture texture = null;
1528             Rectangle clip = state.getClipNoClone();
1529             WCImage image = getImage();
1530             try {
1531                 if (image != null && image instanceof PrismImage) {
1532                     // blending on canvas
1533                     dstImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height);
1534                     Graphics dstG = dstImg.createGraphics();
1535                     ((PrismImage) image).draw(dstG,
1536                             0, 0, clip.width, clip.height,
1537                             clip.x, clip.y, clip.width, clip.height);
1538                 } else {
1539                     // blending on page
1540                     readBackGraphics = (ReadbackGraphics) g;
1541                     texture = readBackGraphics.readBack(clip);
1542                     dstImg = PrDrawable.create(fctx, texture);
1543                 }
1544 
1545                 srcImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height);
1546                 Graphics srcG = srcImg.createGraphics();
1547                 state.apply(srcG);
1548                 doPaint(srcG);
1549 
1550                 g.clear();
1551                 PrEffectHelper.render(createEffect(dstImg, srcImg, clip.width, clip.height), g, 0, 0, null);
1552 
1553             } finally {
1554                 if (srcImg != null) {
1555                     Effect.releaseCompatibleImage(fctx, srcImg);
1556                 }
1557                 if (dstImg != null) {
1558                     if (readBackGraphics != null && texture != null) {
1559                         readBackGraphics.releaseReadBackBuffer(texture);
1560                     } else {
1561                         Effect.releaseCompatibleImage(fctx, dstImg);
1562                     }
1563                 }
1564             }
1565         }
1566 
1567         // provides some syntax sugar for createEffect()
1568         private Effect createBlend(Blend.Mode mode,
1569                                    PrDrawable dstImg,
1570                                    PrDrawable srcImg,
1571                                    int width,
1572                                    int height)
1573         {
1574             return new Blend(
1575                     mode,
1576                     new PassThrough(dstImg, width, height),
1577                     new PassThrough(srcImg, width, height));
1578         }
1579 
1580         private Effect createEffect(PrDrawable dstImg,
1581                                     PrDrawable srcImg,
1582                                     int width,
1583                                     int height)
1584         {
1585             switch (state.getCompositeOperation()) {
1586                 case COMPOSITE_CLEAR: // same as xor
1587                 case COMPOSITE_XOR:
1588                     return new Blend(
1589                             SRC_OVER,
1590                             createBlend(SRC_OUT, dstImg, srcImg, width, height),
1591                             createBlend(SRC_OUT, srcImg, dstImg, width, height)
1592                     );
1593                 case COMPOSITE_SOURCE_IN:
1594                     return createBlend(SRC_IN, dstImg, srcImg, width, height);
1595                 case COMPOSITE_SOURCE_OUT:
1596                     return createBlend(SRC_OUT, dstImg, srcImg, width, height);
1597                 case COMPOSITE_SOURCE_ATOP:
1598                     return createBlend(SRC_ATOP, dstImg, srcImg, width, height);
1599                 case COMPOSITE_DESTINATION_OVER:
1600                     return createBlend(SRC_OVER, srcImg, dstImg, width, height);
1601                 case COMPOSITE_DESTINATION_IN:
1602                     return createBlend(SRC_IN, srcImg, dstImg, width, height);
1603                 case COMPOSITE_DESTINATION_OUT:
1604                     return createBlend(SRC_OUT, srcImg, dstImg, width, height);
1605                 case COMPOSITE_DESTINATION_ATOP:
1606                     return createBlend(SRC_ATOP, srcImg, dstImg, width, height);
1607                 case COMPOSITE_HIGHLIGHT:
1608                     return createBlend(ADD, dstImg, srcImg, width, height);
1609                 default:
1610                     return createBlend(SRC_OVER, dstImg, srcImg, width, height);
1611             }
1612         }
1613     }
1614 
1615     private static final class PassThrough extends Effect {
1616         private final PrDrawable img;
1617         private final int width;
1618         private final int height;
1619 
1620         private PassThrough(PrDrawable img, int width, int height) {
1621             this.img = img;
1622             this.width = width;
1623             this.height = height;
1624         }
1625 
1626         @Override public ImageData filter(
1627                 FilterContext fctx,
1628                 BaseTransform transform,
1629                 Rectangle outputClip,
1630                 Object renderHelper,
1631                 Effect defaultInput) {
1632             // We have an unpaired lock() here, because unlocking is done
1633             // internally by ImageData. See RT-33625 for details.
1634             img.lock();
1635             ImageData imgData = new ImageData(fctx, img, new Rectangle(
1636                                               (int) transform.getMxt(),
1637                                               (int) transform.getMyt(),
1638                                               width, height));
1639             imgData.setReusable(true);
1640             return imgData;
1641         }
1642 
1643         @Override public RectBounds getBounds(
1644                 BaseTransform transform,
1645                 Effect defaultInput) {
1646             return null;
1647         }
1648 
1649         @Override public AccelType getAccelType(FilterContext fctx) {
1650             return AccelType.INTRINSIC;
1651         }
1652 
1653         @Override
1654         public boolean reducesOpaquePixels() {
1655             return false;
1656         }
1657 
1658         @Override
1659         public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
1660             return null;
1661         }
1662     }
1663 
1664     private static FilterContext getFilterContext(Graphics g) {
1665         Screen screen = g.getAssociatedScreen();
1666         if (screen == null) {
1667             ResourceFactory factory = g.getResourceFactory();
1668             return PrFilterContext.getPrinterContext(factory);
1669         } else {
1670             return PrFilterContext.getInstance(screen);
1671         }
1672     }
1673 
1674     @Override
1675     public void strokeArc(final int x, final int y, final int w, final int h,
1676                           final int startAngle, final int angleSpan)
1677     {
1678         if (log.isLoggable(Level.FINE)) {
1679             log.fine(String.format("strokeArc(%d, %d, %d, %d, %d, %d)",
1680                                    x, y, w, h, startAngle, angleSpan));
1681         }
1682         Arc2D arc = new Arc2D(x, y, w, h, startAngle, angleSpan, Arc2D.OPEN);
1683         if (state.getStrokeNoClone().isApplicable() &&
1684             !shouldRenderShape(arc, null, state.getStrokeNoClone().getPlatformStroke()))
1685         {
1686             return;
1687         }
1688         new Composite() {
1689             @Override void doPaint(Graphics g) {
1690                 if (state.getStrokeNoClone().apply(g)) {
1691                     g.draw(arc);
1692                 }
1693             }
1694         }.paint();
1695     }
1696 
1697     @Override
1698     public WCImage getImage() {
1699         return null;
1700     }
1701 
1702     @Override
1703     public void strokeRect(final float x, final float y, final float w, final float h,
1704                            final float lineWidth) {
1705         if (log.isLoggable(Level.FINE)) {
1706             log.fine(String.format("strokeRect_FFFFF(%f, %f, %f, %f, %f)",
1707                                    x, y, w, h, lineWidth));
1708         }
1709         BasicStroke stroke = new BasicStroke(
1710             lineWidth,
1711             BasicStroke.CAP_BUTT,
1712             BasicStroke.JOIN_MITER,
1713             Math.max(1.0f, lineWidth),
1714             state.getStrokeNoClone().getDashSizes(),
1715             state.getStrokeNoClone().getDashOffset());
1716 
1717         if (!shouldRenderRect(x, y, w, h, null, stroke)) {
1718             return;
1719         }
1720         new Composite() {
1721             @Override void doPaint(Graphics g) {
1722                 g.setStroke(stroke);
1723                 Paint paint = state.getStrokeNoClone().getPaint();
1724                 if (paint == null) {
1725                     paint = state.getPaintNoClone();
1726                 }
1727                 g.setPaint(paint);
1728                 g.drawRect(x, y, w, h);
1729             }
1730         }.paint();
1731     }
1732 
1733     @Override
1734     public void strokePath(final WCPath path) {
1735         log.fine("strokePath");
1736         if (path != null) {
1737             final BasicStroke stroke = state.getStrokeNoClone().getPlatformStroke();
1738             final DropShadow shadow = state.getShadowNoClone();
1739             final Path2D p2d = (Path2D)path.getPlatformPath();
1740 
1741             if ((stroke == null && shadow == null) ||
1742                 !shouldRenderShape(p2d, shadow, stroke))
1743             {
1744                 return;
1745             }
1746             new Composite() {
1747                 @Override void doPaint(Graphics g) {
1748                     if (shadow != null) {
1749                         final NGPath node = new NGPath();
1750                         node.updateWithPath2d(p2d);
1751                         render(g, shadow, null, stroke, node);
1752                     } else if (stroke != null) {
1753                         Paint paint = state.getStrokeNoClone().getPaint();
1754                         if (paint == null) {
1755                             paint = state.getPaintNoClone();
1756                         }
1757                         g.setPaint(paint);
1758                         g.setStroke(stroke);
1759                         g.draw(p2d);
1760                     }
1761                 }
1762             }.paint();
1763         }
1764     }
1765 
1766     @Override
1767     public void fillPath(final WCPath path) {
1768         log.fine("fillPath");
1769         if (path != null) {
1770             if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(),
1771                                    state.getShadowNoClone(), null))
1772             {
1773                 return;
1774             }
1775             new Composite() {
1776                 @Override void doPaint(Graphics g) {
1777                     Path2D p2d = (Path2D) path.getPlatformPath();
1778                     Paint paint = state.getPaintNoClone();
1779                     DropShadow shadow = state.getShadowNoClone();
1780                     if (shadow != null) {
1781                         final NGPath node = new NGPath();
1782                         node.updateWithPath2d(p2d);
1783                         render(g, shadow, paint, null, node);
1784                     } else {
1785                         g.setPaint(paint);
1786                         g.fill(p2d);
1787                     }
1788                 }
1789             }.paint();
1790         }
1791     }
1792 
1793     public void setTransform(WCTransform tm) {
1794         double m[] = tm.getMatrix();
1795         Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5]));
1796         if (state.getLayerNoClone() == null) {
1797             at.preConcatenate(baseTransform);
1798         }
1799         state.setTransform(at);
1800         resetCachedGraphics();
1801     }
1802 
1803     public WCTransform getTransform() {
1804         Affine3D xf = state.getTransformNoClone();
1805         return new WCTransform(xf.getMxx(), xf.getMyx(),
1806                                xf.getMxy(), xf.getMyy(),
1807                                xf.getMxt(), xf.getMyt());
1808     }
1809 
1810     public void concatTransform(WCTransform tm) {
1811         double m[] = tm.getMatrix();
1812         Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5]));
1813         state.concatTransform(at);
1814         resetCachedGraphics();
1815     }
1816 
1817     @Override
1818     public void flush() {
1819         flushAllLayers();
1820     }
1821 
1822     @Override
1823     public WCGradient createLinearGradient(WCPoint p1, WCPoint p2) {
1824         return new WCLinearGradient(p1, p2);
1825     }
1826 
1827     @Override
1828     public WCGradient createRadialGradient(WCPoint p1, float r1, WCPoint p2, float r2) {
1829         return new WCRadialGradient(p1, r1, p2, r2);
1830     }
1831 }