1 /*
   2  * Copyright (c) 2012, 2016, 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 javafx.scene.canvas;
  27 
  28 import com.sun.javafx.geom.Arc2D;
  29 import com.sun.javafx.geom.IllegalPathStateException;
  30 import com.sun.javafx.geom.Path2D;
  31 import com.sun.javafx.geom.PathIterator;
  32 import com.sun.javafx.geom.transform.Affine2D;
  33 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  34 import com.sun.javafx.image.*;
  35 import com.sun.javafx.image.impl.ByteBgraPre;
  36 import com.sun.javafx.sg.prism.GrowableDataBuffer;
  37 import com.sun.javafx.sg.prism.NGCanvas;
  38 import com.sun.javafx.text.FontHelper;
  39 import com.sun.javafx.tk.Toolkit;
  40 import com.sun.scenario.effect.EffectHelper;
  41 import javafx.geometry.NodeOrientation;
  42 import javafx.geometry.VPos;
  43 import javafx.scene.effect.Blend;
  44 import javafx.scene.effect.BlendMode;
  45 import javafx.scene.effect.Effect;
  46 import javafx.scene.image.Image;
  47 import javafx.scene.image.PixelFormat;
  48 import javafx.scene.image.PixelReader;
  49 import javafx.scene.image.PixelWriter;
  50 import javafx.scene.paint.Color;
  51 import javafx.scene.paint.Paint;
  52 import javafx.scene.shape.ArcType;
  53 import javafx.scene.shape.FillRule;
  54 import javafx.scene.shape.StrokeLineCap;
  55 import javafx.scene.shape.StrokeLineJoin;
  56 import javafx.scene.text.Font;
  57 import javafx.scene.text.TextAlignment;
  58 import javafx.scene.transform.Affine;
  59 
  60 import java.nio.Buffer;
  61 import java.nio.ByteBuffer;
  62 import java.nio.IntBuffer;
  63 import java.util.Arrays;
  64 import java.util.LinkedList;
  65 import javafx.scene.text.FontSmoothingType;
  66 
  67 /**
  68  * This class is used to issue draw calls to a {@code Canvas} using a buffer.
  69  * <p>
  70  * Each call pushes the necessary parameters onto the buffer
  71  * where they will be later rendered onto the image of the {@code Canvas} node
  72  * by the rendering thread at the end of a pulse.
  73  * <p>
  74  * A {@code Canvas} only contains one {@code GraphicsContext}, and only one buffer.
  75  * If it is not attached to any scene, then it can be modified by any thread,
  76  * as long as it is only used from one thread at a time. Once a {@code Canvas}
  77  * node is attached to a scene, it must be modified on the JavaFX Application
  78  * Thread.
  79  * <p>
  80  * Calling any method on the {@code GraphicsContext} is considered modifying
  81  * its corresponding {@code Canvas} and is subject to the same threading
  82  * rules.
  83  * <p>
  84  * A {@code GraphicsContext} also manages a stack of state objects that can
  85  * be saved or restored at anytime.
  86  * <p>
  87  * The {@code GraphicsContext} maintains the following rendering attributes
  88  * which affect various subsets of the rendering methods:
  89  * <table class="overviewSummary" style="width:80%; margin-left:auto; margin-right:auto">
  90  * <tr>
  91  * <th class="colLast" style="width:15%">Attribute</th>
  92  * <th class="colLast" style="width:10%; text-align:center">Save/Restore?</th>
  93  * <th class="colLast" style="width:10%; text-align:center">Default value</th>
  94  * <th class="colLast">Description</th>
  95  * </tr>
  96  *
  97  * <tr><th colspan="3"><a name="comm-attr"><p align="center">Common Rendering Attributes</p></a></th></tr>
  98  * <tr class="rowColor">
  99  * <td class="colLast" style="width:15%">{@link #clip() Clip}</td>
 100  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 101  * <td class="colLast" style="width:10%; text-align:center">No clipping</td>
 102  * <td class="colLast">
 103  * An anti-aliased intersection of various clip paths to which rendering
 104  * is restricted.
 105  * </td></tr>
 106  * <tr class="altColor">
 107  * <td class="colLast" style="width:15%">{@link #setGlobalAlpha(double) Global Alpha}</td>
 108  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 109  * <td class="colLast" style="width:10%; text-align:center">{@code 1.0}</td>
 110  * <td class="colLast">
 111  * An opacity value that controls the visibility or fading of each rendering
 112  * operation.
 113  * </td></tr>
 114  * <tr class="rowColor">
 115  * <td class="colLast" style="width:15%">{@link #setGlobalBlendMode(javafx.scene.effect.BlendMode) Global Blend Mode}</td>
 116  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 117  * <td class="colLast" style="width:10%; text-align:center">{@link BlendMode#SRC_OVER SRC_OVER}</td>
 118  * <td class="colLast">
 119  * A {@link BlendMode} enum value that controls how pixels from each rendering
 120  * operation are composited into the existing image.
 121  * </td></tr>
 122  * <tr class="altColor">
 123  * <td class="colLast" style="width:15%">{@link #setTransform(javafx.scene.transform.Affine) Transform}</td>
 124  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 125  * <td class="colLast" style="width:10%; text-align:center">{@code Identity}</td>
 126  * <td class="colLast">
 127  * A 3x2 2D affine transformation matrix that controls how coordinates are
 128  * mapped onto the logical pixels of the canvas image.
 129  * </td></tr>
 130  * <tr class="altColor">
 131  * <td class="colLast" style="width:15%">{@link #setEffect(javafx.scene.effect.Effect) Effect}</td>
 132  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 133  * <td class="colLast" style="width:10%; text-align:center">{@code null}</td>
 134  * <td class="colLast">
 135  * An {@link Effect} applied individually to each rendering operation.
 136  * </td></tr>
 137  *
 138  * <tr><th colspan="3"><a name="fill-attr"><p align="center">Fill Attributes</p></a></th></tr>
 139  * <tr class="rowColor">
 140  * <td class="colLast" style="width:15%">{@link #setFill(javafx.scene.paint.Paint) Fill Paint}</td>
 141  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 142  * <td class="colLast" style="width:10%; text-align:center">{@link Color#BLACK BLACK}</td>
 143  * <td class="colLast">
 144  * The {@link Paint} to be applied to the interior of shapes in a
 145  * fill operation.
 146  * </td></tr>
 147  *
 148  * <tr><th colspan="3"><a name="strk-attr"><p align="center">Stroke Attributes</p></a></th></tr>
 149  * <tr class="rowColor">
 150  * <td class="colLast" style="width:15%">{@link #setStroke(javafx.scene.paint.Paint) Stroke Paint}</td>
 151  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 152  * <td class="colLast" style="width:10%; text-align:center">{@link Color#BLACK BLACK}</td>
 153  * <td class="colLast">
 154  * The {@link Paint} to be applied to the boundary of shapes in a
 155  * stroke operation.
 156  * </td></tr>
 157  * <tr class="altColor">
 158  * <td class="colLast" style="width:15%">{@link #setLineWidth(double) Line Width}</td>
 159  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 160  * <td class="colLast" style="width:10%; text-align:center">{@code 1.0}</td>
 161  * <td class="colLast">
 162  * The width of the stroke applied to the boundary of shapes in a
 163  * stroke operation.
 164  * </td></tr>
 165  * <tr class="rowColor">
 166  * <td class="colLast" style="width:15%">{@link #setLineCap(javafx.scene.shape.StrokeLineCap) Line Cap}</td>
 167  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 168  * <td class="colLast" style="width:10%; text-align:center">{@link StrokeLineCap#SQUARE SQUARE}</td>
 169  * <td class="colLast">
 170  * The style of the end caps applied to the beginnings and ends of each
 171  * dash and/or subpath in a stroke operation.
 172  * </td></tr>
 173  * <tr class="altColor">
 174  * <td class="colLast" style="width:15%">{@link #setLineJoin(javafx.scene.shape.StrokeLineJoin) Line Join}</td>
 175  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 176  * <td class="colLast" style="width:10%; text-align:center">{@link StrokeLineJoin#MITER MITER}</td>
 177  * <td class="colLast">
 178  * The style of the joins applied between individual segments in the boundary
 179  * paths of shapes in a stroke operation.
 180  * </td></tr>
 181  * <tr class="rowColor">
 182  * <td class="colLast" style="width:15%">{@link #setMiterLimit(double) Miter Limit}</td>
 183  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 184  * <td class="colLast" style="width:10%; text-align:center">{@code 10.0}</td>
 185  * <td class="colLast">
 186  * The ratio limit of how far a {@link StrokeLineJoin#MITER MITER} line join
 187  * may extend in the direction of a sharp corner between segments in the
 188  * boundary path of a shape, relative to the line width, before it is truncated
 189  * to a {@link StrokeLineJoin#BEVEL BEVEL} join in a stroke operation.
 190  * </td></tr>
 191  * <tr class="altColor">
 192  * <td class="colLast" style="width:15%">{@link #setLineDashes(double...) Dashes}</td>
 193  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 194  * <td class="colLast" style="width:10%; text-align:center">{@code null}</td>
 195  * <td class="colLast">
 196  * The array of dash lengths to be applied to the segments in the boundary
 197  * of shapes in a stroke operation.
 198  * </td></tr>
 199  * <tr class="rowColor">
 200  * <td class="colLast" style="width:15%">{@link #setLineDashOffset(double) Dash Offset}</td>
 201  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 202  * <td class="colLast" style="width:10%; text-align:center">{@code 0.0}</td>
 203  * <td class="colLast">
 204  * The distance offset into the array of dash lengths at which to start the
 205  * dashing of the segments in the boundary of shapes in a stroke operation.
 206  * </td></tr>
 207  *
 208  * <tr><th colspan="3"><a name="text-attr"><p align="center">Text Attributes</p></a></th></tr>
 209  * <tr class="rowColor">
 210  * <td class="colLast" style="width:15%">{@link #setFont(javafx.scene.text.Font) Font}</td>
 211  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 212  * <td class="colLast" style="width:10%; text-align:center">{@link Font#getDefault() Default Font}</td>
 213  * <td class="colLast">
 214  * The font used for all fill and stroke text operations.
 215  * </td></tr>
 216  * <tr class="altColor">
 217  * <td class="colLast" style="width:15%">{@link #setTextAlign(javafx.scene.text.TextAlignment) Text Align}</td>
 218  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 219  * <td class="colLast" style="width:10%; text-align:center">{@link TextAlignment#LEFT LEFT}</td>
 220  * <td class="colLast">
 221  * The horizontal alignment of text with respect to the {@code X} coordinate
 222  * specified in the text operation.
 223  * </td></tr>
 224  * <tr class="rowColor">
 225  * <td class="colLast" style="width:15%">{@link #setTextBaseline(javafx.geometry.VPos) Text Baseline}</td>
 226  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 227  * <td class="colLast" style="width:10%; text-align:center">{@link VPos#BASELINE BASELINE}</td>
 228  * <td class="colLast">
 229  * The vertical position of the text relative to the {@code Y} coordinate
 230  * specified in the text operation.
 231  * </td></tr>
 232  * <tr class="altColor">
 233  * <td class="colLast" style="width:15%">{@link #setFontSmoothingType(javafx.scene.text.FontSmoothingType) Font Smoothing}</td>
 234  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 235  * <td class="colLast" style="width:10%; text-align:center">{@link FontSmoothingType#GRAY GRAY}</td>
 236  * <td class="colLast">
 237  * The type of smoothing (antialiasing) applied to the glyphs in the font
 238  * for all fill text operations.
 239  * </td></tr>
 240  *
 241  * <tr><th colspan="3"><a name="path-attr"><p align="center">Path Attributes</p></a></th></tr>
 242  * <tr class="rowColor">
 243  * <td class="colLast" style="width:15%">{@link #beginPath() Current Path}</td>
 244  * <td class="colLast" style="width:10%; text-align:center; color:#c00">No</td>
 245  * <td class="colLast" style="width:10%; text-align:center">Empty path</td>
 246  * <td class="colLast">
 247  * The path constructed using various path construction methods to be used
 248  * in various path filling, stroking, or clipping operations.
 249  * </td></tr>
 250  * <tr class="altColor">
 251  * <td class="colLast" style="width:15%">{@link #setFillRule(javafx.scene.shape.FillRule) Fill Rule}</td>
 252  * <td class="colLast" style="width:10%; text-align:center; color:#0c0">Yes</td>
 253  * <td class="colLast" style="width:10%; text-align:center">{@link FillRule#NON_ZERO NON_ZERO}</td>
 254  * <td class="colLast">
 255  * The method used to determine the interior of paths for a path fill or
 256  * clip operation.
 257  * </td></tr>
 258  * </table>
 259  *
 260  * <p>
 261  * <a name="attr-ops-table">
 262  * The various rendering methods on the {@code GraphicsContext} use the
 263  * following sets of rendering attributes:
 264  * </a>
 265  * <table class="overviewSummary" style="width:80%; margin-left:auto; margin-right:auto">
 266  * <tr>
 267  * <th class="colLast" style="width:25%">Method</th>
 268  * <th class="colLast" style="width:15%; text-align:center"><a href="#comm-attr">Common Rendering Attributes</a></th>
 269  * <th class="colLast" style="width:15%; text-align:center"><a href="#fill-attr">Fill Attributes</a></th>
 270  * <th class="colLast" style="width:15%; text-align:center"><a href="#strk-attr">Stroke Attributes</a></th>
 271  * <th class="colLast" style="width:15%; text-align:center"><a href="#text-attr">Text Attributes</a></th>
 272  * <th class="colLast" style="width:15%; text-align:center"><a href="#path-attr">Path Attributes</a></th>
 273  * </tr>
 274  *
 275  * <tr><th colspan="1"><p align="center">Basic Shape Rendering</p></th></tr>
 276  * <tr class="rowColor">
 277  * <td class="colLast" style="width:25%">
 278  * <a name="fill-basic-ops">
 279  * {@link #fillRect(double, double, double, double) fillRect()},
 280  * {@link #fillRoundRect(double, double, double, double, double, double) fillRoundRect()},
 281  * {@link #fillOval(double, double, double, double) fillOval()},
 282  * {@link #fillArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) fillArc()}
 283  * </a>
 284  * </td>
 285  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 286  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 287  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 288  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 289  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 290  * </tr>
 291  * <tr class="altColor">
 292  * <td class="colLast" style="width:25%">
 293  * <a name="strk-basic-ops">
 294  * {@link #strokeLine(double, double, double, double) strokeLine()},
 295  * {@link #strokeRect(double, double, double, double) strokeRect()},
 296  * {@link #strokeRoundRect(double, double, double, double, double, double) strokeRoundRect()},
 297  * {@link #strokeOval(double, double, double, double) strokeOval()},
 298  * {@link #strokeArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) strokeArc()}
 299  * </a>
 300  * </td>
 301  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 302  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 303  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 304  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 305  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 306  * </tr>
 307  * <tr class="rowColor">
 308  * <td class="colLast" style="width:25%">
 309  * <a name="fill-basic-ops">
 310  * {@link #clearRect(double, double, double, double) clearRect()}
 311  * </a>
 312  * </td>
 313  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#base-fn-1">[1]</a></td>
 314  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 315  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 316  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 317  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 318  * </tr>
 319  * <tr class="altColor">
 320  * <td class="colLast" style="width:25%">
 321  * <a name="strk-basic-ops">
 322  * {@link #fillPolygon(double[], double[], int) fillPolygon()}
 323  * </a>
 324  * </td>
 325  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 326  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 327  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 328  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 329  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#base-fn-2">[2]</a></td>
 330  * </tr>
 331  * <tr class="rowColor">
 332  * <td class="colLast" style="width:25%">
 333  * <a name="strk-basic-ops">
 334  * {@link #strokePolygon(double[], double[], int) strokePolygon()},
 335  * {@link #strokePolyline(double[], double[], int) strokePolyline()}
 336  * </a>
 337  * </td>
 338  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 339  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 340  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 341  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 342  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 343  * </tr>
 344  * <tr><td colspan="6"><p align="center">
 345  * <a name="base-fn-1">[1]</a> Only the Transform, Clip, and Effect apply to clearRect()<br>
 346  * <a name="base-fn-2">[2]</a> Only the Fill Rule applies to fillPolygon(), the current path is left unchanged
 347  * </p></td></tr>
 348  *
 349  * <tr><th colspan="1"><p align="center">Text Rendering</p></th></tr>
 350  * <tr class="rowColor">
 351  * <td class="colLast" style="width:25%">
 352  * <a name="fill-text-ops">
 353  * {@link #fillText(java.lang.String, double, double) fillText()},
 354  * {@link #fillText(java.lang.String, double, double, double) fillText(with maxWidth)}
 355  * </a>
 356  * </td>
 357  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 358  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 359  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 360  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#text-fn-3">[3]</a></td>
 361  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 362  * </tr>
 363  * <tr class="altColor">
 364  * <td class="colLast" style="width:25%">
 365  * <a name="strk-text-ops">
 366  * {@link #strokeText(java.lang.String, double, double) strokeText()},
 367  * {@link #strokeText(java.lang.String, double, double, double) strokeText(with maxWidth)}
 368  * </a>
 369  * </td>
 370  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 371  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 372  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 373  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#text-fn-3">[3]</a></td>
 374  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 375  * </tr>
 376  * <tr><td colspan="6"><p align="center">
 377  * <a name="text-fn-3">[3]</a> The Font Smoothing attribute only applies to filled text
 378  * </p></td></tr>
 379  *
 380  * <tr><th colspan="1"><p align="center">Path Rendering</p></th></tr>
 381  * <tr class="rowColor">
 382  * <td class="colLast" style="width:25%">
 383  * {@link #beginPath() beginPath()},
 384  * {@link #moveTo(double, double) moveTo()},
 385  * {@link #lineTo(double, double) lineTo()},
 386  * {@link #quadraticCurveTo(double, double, double, double) quadraticCurveTo()},
 387  * {@link #bezierCurveTo(double, double, double, double, double, double) bezierCurveTo()},
 388  * {@link #arc(double, double, double, double, double, double) arc()},
 389  * {@link #arcTo(double, double, double, double, double) arcTo()},
 390  * {@link #appendSVGPath(java.lang.String) appendSVGPath()},
 391  * {@link #closePath() closePath()},
 392  * {@link #rect(double, double, double, double) rect()}
 393  * </td>
 394  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-4">[4]</a></td>
 395  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 396  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 397  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 398  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 399  * </tr>
 400  * <tr class="altColor">
 401  * <td class="colLast" style="width:25%">
 402  * <a name="fill-path-ops">
 403  * {@link #fill() fill()}
 404  * </a>
 405  * </td>
 406  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-4">[4]</a></td>
 407  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 408  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 409  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 410  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 411  * </tr>
 412  * <tr class="rowColor">
 413  * <td class="colLast" style="width:25%">
 414  * <a name="strk-path-ops">
 415  * {@link #stroke() stroke()}
 416  * </a>
 417  * </td>
 418  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-4">[4]</a></td>
 419  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 420  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 421  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 422  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes <a href="#path-fn-5">[5]</a></td>
 423  * </tr>
 424  * <tr class="altColor">
 425  * <td class="colLast" style="width:25%">
 426  * <a name="clip-path-ops">
 427  * {@link #clip() clip()}
 428  * </a>
 429  * </td>
 430  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 431  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 432  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 433  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 434  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 435  * </tr>
 436  * <tr><td colspan="6"><p align="center">
 437  * <a name="path-fn-4">[4]</a> Transform applied only during path construction<br>
 438  * <a name="path-fn-5">[5]</a> Fill Rule only used for fill() and clip()
 439  * </p></td></tr>
 440  *
 441  * <tr><th colspan="1"><p align="center">Image Rendering</p></th></tr>
 442  * <tr class="rowColor">
 443  * <td class="colLast" style="width:25%">
 444  * <a name="draw-img-ops">
 445  * {@link #drawImage(javafx.scene.image.Image, double, double) drawImage(all forms)}
 446  * </a>
 447  * </td>
 448  * <td class="colLast" style="width:15%; text-align:center; color:#0c0">Yes</td>
 449  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 450  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 451  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 452  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 453  * </tr>
 454  *
 455  * <tr><th colspan="1"><p align="center">Miscellaneous</p></th></tr>
 456  * <tr class="rowColor">
 457  * <td class="colLast" style="width:25%">
 458  * {@link #applyEffect(javafx.scene.effect.Effect) applyEffect()},
 459  * {@link #getPixelWriter() PixelWriter methods}
 460  * </td>
 461  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 462  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 463  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 464  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 465  * <td class="colLast" style="width:15%; text-align:center; color:#c00">No</td>
 466  * </tr>
 467  * </table>
 468  *
 469  * <p>Example:</p>
 470  *
 471  * <p>
 472  * <pre>
 473 import javafx.scene.*;
 474 import javafx.scene.paint.*;
 475 import javafx.scene.canvas.*;
 476 
 477 Group root = new Group();
 478 Scene s = new Scene(root, 300, 300, Color.BLACK);
 479 
 480 final Canvas canvas = new Canvas(250,250);
 481 GraphicsContext gc = canvas.getGraphicsContext2D();
 482 
 483 gc.setFill(Color.BLUE);
 484 gc.fillRect(75,75,100,100);
 485 
 486 root.getChildren().add(canvas);
 487  * </pre>
 488  * </p>
 489  *
 490  * @since JavaFX 2.2
 491  */
 492 public final class GraphicsContext {
 493     Canvas theCanvas;
 494     Path2D path;
 495     boolean pathDirty;
 496 
 497     State curState;
 498     LinkedList<State> stateStack;
 499     LinkedList<Path2D> clipStack;
 500 
 501     GraphicsContext(Canvas theCanvas) {
 502         this.theCanvas = theCanvas;
 503         this.path = new Path2D();
 504         pathDirty = true;
 505 
 506         this.curState = new State();
 507         this.stateStack = new LinkedList<State>();
 508         this.clipStack = new LinkedList<Path2D>();
 509     }
 510 
 511     static class State {
 512         double globalAlpha;
 513         BlendMode blendop;
 514         Affine2D transform;
 515         Paint fill;
 516         Paint stroke;
 517         double linewidth;
 518         StrokeLineCap linecap;
 519         StrokeLineJoin linejoin;
 520         double miterlimit;
 521         double dashes[];
 522         double dashOffset;
 523         int numClipPaths;
 524         Font font;
 525         FontSmoothingType fontsmoothing;
 526         TextAlignment textalign;
 527         VPos textbaseline;
 528         Effect effect;
 529         FillRule fillRule;
 530 
 531         State() {
 532             init();
 533         }
 534 
 535         final void init() {
 536             set(1.0, BlendMode.SRC_OVER,
 537                 new Affine2D(),
 538                 Color.BLACK, Color.BLACK,
 539                 1.0, StrokeLineCap.SQUARE, StrokeLineJoin.MITER, 10.0,
 540                 null, 0.0,
 541                 0,
 542                 Font.getDefault(), FontSmoothingType.GRAY,
 543                 TextAlignment.LEFT, VPos.BASELINE,
 544                 null, FillRule.NON_ZERO);
 545         }
 546 
 547         State(State copy) {
 548             set(copy.globalAlpha, copy.blendop,
 549                 new Affine2D(copy.transform),
 550                 copy.fill, copy.stroke,
 551                 copy.linewidth, copy.linecap, copy.linejoin, copy.miterlimit,
 552                 copy.dashes, copy.dashOffset,
 553                 copy.numClipPaths,
 554                 copy.font, copy.fontsmoothing, copy.textalign, copy.textbaseline,
 555                 copy.effect, copy.fillRule);
 556         }
 557 
 558         final void set(double globalAlpha, BlendMode blendop,
 559                        Affine2D transform, Paint fill, Paint stroke,
 560                        double linewidth, StrokeLineCap linecap,
 561                        StrokeLineJoin linejoin, double miterlimit,
 562                        double dashes[], double dashOffset,
 563                        int numClipPaths,
 564                        Font font, FontSmoothingType smoothing,
 565                        TextAlignment align, VPos baseline,
 566                        Effect effect, FillRule fillRule)
 567         {
 568             this.globalAlpha = globalAlpha;
 569             this.blendop = blendop;
 570             this.transform = transform;
 571             this.fill = fill;
 572             this.stroke = stroke;
 573             this.linewidth = linewidth;
 574             this.linecap = linecap;
 575             this.linejoin = linejoin;
 576             this.miterlimit = miterlimit;
 577             this.dashes = dashes;
 578             this.dashOffset = dashOffset;
 579             this.numClipPaths = numClipPaths;
 580             this.font = font;
 581             this.fontsmoothing = smoothing;
 582             this.textalign = align;
 583             this.textbaseline = baseline;
 584             this.effect = effect;
 585             this.fillRule = fillRule;
 586         }
 587 
 588         State copy() {
 589             return new State(this);
 590         }
 591 
 592         void restore(GraphicsContext ctx) {
 593             ctx.setGlobalAlpha(globalAlpha);
 594             ctx.setGlobalBlendMode(blendop);
 595             ctx.setTransform(transform.getMxx(), transform.getMyx(),
 596                              transform.getMxy(), transform.getMyy(),
 597                              transform.getMxt(), transform.getMyt());
 598             ctx.setFill(fill);
 599             ctx.setStroke(stroke);
 600             ctx.setLineWidth(linewidth);
 601             ctx.setLineCap(linecap);
 602             ctx.setLineJoin(linejoin);
 603             ctx.setMiterLimit(miterlimit);
 604             ctx.setLineDashes(dashes);
 605             ctx.setLineDashOffset(dashOffset);
 606             GrowableDataBuffer buf = ctx.getBuffer();
 607             while (ctx.curState.numClipPaths > numClipPaths) {
 608                 ctx.curState.numClipPaths--;
 609                 ctx.clipStack.removeLast();
 610                 buf.putByte(NGCanvas.POP_CLIP);
 611             }
 612             ctx.setFillRule(fillRule);
 613             ctx.setFont(font);
 614             ctx.setFontSmoothingType(fontsmoothing);
 615             ctx.setTextAlign(textalign);
 616             ctx.setTextBaseline(textbaseline);
 617             ctx.setEffect(effect);
 618         }
 619     }
 620 
 621     private GrowableDataBuffer getBuffer() {
 622         return theCanvas.getBuffer();
 623     }
 624 
 625     private float coords[] = new float[6];
 626     private static final byte pgtype[] = {
 627         NGCanvas.MOVETO,
 628         NGCanvas.LINETO,
 629         NGCanvas.QUADTO,
 630         NGCanvas.CUBICTO,
 631         NGCanvas.CLOSEPATH,
 632     };
 633     private static final int numsegs[] = { 2, 2, 4, 6, 0, };
 634 
 635     private void markPathDirty() {
 636         pathDirty = true;
 637     }
 638 
 639     private void writePath(byte command) {
 640         updateTransform();
 641         GrowableDataBuffer buf = getBuffer();
 642         if (pathDirty) {
 643             buf.putByte(NGCanvas.PATHSTART);
 644             PathIterator pi = path.getPathIterator(null);
 645             while (!pi.isDone()) {
 646                 int pitype = pi.currentSegment(coords);
 647                 buf.putByte(pgtype[pitype]);
 648                 for (int i = 0; i < numsegs[pitype]; i++) {
 649                     buf.putFloat(coords[i]);
 650                 }
 651                 pi.next();
 652             }
 653             buf.putByte(NGCanvas.PATHEND);
 654             pathDirty = false;
 655         }
 656         buf.putByte(command);
 657     }
 658 
 659     private void writePaint(Paint p, byte command) {
 660         GrowableDataBuffer buf = getBuffer();
 661         buf.putByte(command);
 662         buf.putObject(Toolkit.getPaintAccessor().getPlatformPaint(p));
 663     }
 664 
 665     private void writeArcType(ArcType closure) {
 666         byte type;
 667         switch (closure) {
 668             case OPEN:  type = NGCanvas.ARC_OPEN;  break;
 669             case CHORD: type = NGCanvas.ARC_CHORD; break;
 670             case ROUND: type = NGCanvas.ARC_PIE;   break;
 671             default: return;  // ignored for consistency with other attributes
 672         }
 673         writeParam(type, NGCanvas.ARC_TYPE);
 674     }
 675 
 676     private void writeRectParams(GrowableDataBuffer buf,
 677                                  double x, double y, double w, double h,
 678                                  byte command)
 679     {
 680         buf.putByte(command);
 681         buf.putFloat((float) x);
 682         buf.putFloat((float) y);
 683         buf.putFloat((float) w);
 684         buf.putFloat((float) h);
 685     }
 686 
 687     private void writeOp4(double x, double y, double w, double h, byte command) {
 688         updateTransform();
 689         writeRectParams(getBuffer(), x, y, w, h, command);
 690     }
 691 
 692     private void writeOp6(double x, double y, double w, double h,
 693                           double v1, double v2, byte command)
 694     {
 695         updateTransform();
 696         GrowableDataBuffer buf = getBuffer();
 697         buf.putByte(command);
 698         buf.putFloat((float) x);
 699         buf.putFloat((float) y);
 700         buf.putFloat((float) w);
 701         buf.putFloat((float) h);
 702         buf.putFloat((float) v1);
 703         buf.putFloat((float) v2);
 704     }
 705 
 706     private float polybuf[] = new float[512];
 707     private void flushPolyBuf(GrowableDataBuffer buf,
 708                               float polybuf[], int n, byte command)
 709     {
 710         curState.transform.transform(polybuf, 0, polybuf, 0, n/2);
 711         for (int i = 0; i < n; i += 2) {
 712             buf.putByte(command);
 713             buf.putFloat(polybuf[i]);
 714             buf.putFloat(polybuf[i+1]);
 715             command = NGCanvas.LINETO;
 716         }
 717     }
 718     private void writePoly(double xPoints[], double yPoints[], int nPoints,
 719                            boolean close, byte command)
 720     {
 721         if (xPoints == null || yPoints == null) return;
 722         GrowableDataBuffer buf = getBuffer();
 723         buf.putByte(NGCanvas.PATHSTART);
 724         int pos = 0;
 725         byte polycmd = NGCanvas.MOVETO;
 726         for (int i = 0; i < nPoints; i++) {
 727             if (pos >= polybuf.length) {
 728                 flushPolyBuf(buf, polybuf, pos, polycmd);
 729                 pos = 0;
 730                 polycmd = NGCanvas.LINETO;
 731             }
 732             polybuf[pos++] = (float) xPoints[i];
 733             polybuf[pos++] = (float) yPoints[i];
 734         }
 735         flushPolyBuf(buf, polybuf, pos, polycmd);
 736         if (close) {
 737             buf.putByte(NGCanvas.CLOSEPATH);
 738         }
 739         buf.putByte(NGCanvas.PATHEND);
 740         // Transform needs to be updated for rendering attributes even though
 741         // we have already trasnformed the points as we sent them.
 742         updateTransform();
 743         buf.putByte(command);
 744         // Now that we have changed the PG layer path, we need to mark our path dirty.
 745         markPathDirty();
 746     }
 747 
 748     private void writeImage(Image img,
 749                             double dx, double dy, double dw, double dh)
 750     {
 751         if (img == null || img.getProgress() < 1.0) return;
 752         Object platformImg = Toolkit.getImageAccessor().getPlatformImage(img);
 753         if (platformImg == null) return;
 754         updateTransform();
 755         GrowableDataBuffer buf = getBuffer();
 756         writeRectParams(buf, dx, dy, dw, dh, NGCanvas.DRAW_IMAGE);
 757         buf.putObject(platformImg);
 758     }
 759 
 760     private void writeImage(Image img,
 761                             double dx, double dy, double dw, double dh,
 762                             double sx, double sy, double sw, double sh)
 763     {
 764         if (img == null || img.getProgress() < 1.0) return;
 765         Object platformImg = Toolkit.getImageAccessor().getPlatformImage(img);
 766         if (platformImg == null) return;
 767         updateTransform();
 768         GrowableDataBuffer buf = getBuffer();
 769         writeRectParams(buf, dx, dy, dw, dh, NGCanvas.DRAW_SUBIMAGE);
 770         buf.putFloat((float) sx);
 771         buf.putFloat((float) sy);
 772         buf.putFloat((float) sw);
 773         buf.putFloat((float) sh);
 774         buf.putObject(platformImg);
 775     }
 776 
 777     private void writeText(String text, double x, double y, double maxWidth,
 778                            byte command)
 779     {
 780         if (text == null) return;
 781         updateTransform();
 782         GrowableDataBuffer buf = getBuffer();
 783         buf.putByte(command);
 784         buf.putFloat((float) x);
 785         buf.putFloat((float) y);
 786         buf.putFloat((float) maxWidth);
 787         buf.putBoolean(theCanvas.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT);
 788         buf.putObject(text);
 789     }
 790 
 791     void writeParam(double v, byte command) {
 792         GrowableDataBuffer buf = getBuffer();
 793         buf.putByte(command);
 794         buf.putFloat((float) v);
 795     }
 796 
 797     private void writeParam(byte v, byte command) {
 798         GrowableDataBuffer buf = getBuffer();
 799         buf.putByte(command);
 800         buf.putByte(v);
 801     }
 802 
 803     private boolean txdirty;
 804     private void updateTransform() {
 805         if (txdirty) {
 806             txdirty = false;
 807             GrowableDataBuffer buf = getBuffer();
 808             buf.putByte(NGCanvas.TRANSFORM);
 809             buf.putDouble(curState.transform.getMxx());
 810             buf.putDouble(curState.transform.getMxy());
 811             buf.putDouble(curState.transform.getMxt());
 812             buf.putDouble(curState.transform.getMyx());
 813             buf.putDouble(curState.transform.getMyy());
 814             buf.putDouble(curState.transform.getMyt());
 815         }
 816     }
 817 
 818     void updateDimensions() {
 819         GrowableDataBuffer buf = getBuffer();
 820         buf.putByte(NGCanvas.SET_DIMS);
 821         buf.putFloat((float) theCanvas.getWidth());
 822         buf.putFloat((float) theCanvas.getHeight());
 823     }
 824 
 825     private void reset() {
 826         GrowableDataBuffer buf = getBuffer();
 827         // Only reset if we have a significant amount of data to omit,
 828         // this prevents a common occurence of "setFill(bg); fillRect();"
 829         // at the start of a session from invoking a reset.
 830         // But, do a reset anyway if the rendering layer has been falling
 831         // behind because that lets the synchronization step throw out the
 832         // older buffers that have been backing up.
 833         if (buf.writeValuePosition() > Canvas.DEFAULT_VAL_BUF_SIZE ||
 834             theCanvas.isRendererFallingBehind())
 835         {
 836             buf.reset();
 837             buf.putByte(NGCanvas.RESET);
 838             updateDimensions();
 839             txdirty = true;
 840             pathDirty = true;
 841             State s = this.curState;
 842             int numClipPaths = this.curState.numClipPaths;
 843             this.curState = new State();
 844             for (int i = 0; i < numClipPaths; i++) {
 845                 Path2D clip = clipStack.get(i);
 846                 buf.putByte(NGCanvas.PUSH_CLIP);
 847                 buf.putObject(clip);
 848             }
 849             this.curState.numClipPaths = numClipPaths;
 850             s.restore(this);
 851         }
 852     }
 853 
 854     private void resetIfCovers(Paint p, double x, double y, double w, double h) {
 855         Affine2D tx = this.curState.transform;
 856         if (tx.isTranslateOrIdentity()) {
 857             x += tx.getMxt();
 858             y += tx.getMyt();
 859             if (x > 0 || y > 0 ||
 860                 (x+w) < theCanvas.getWidth() ||
 861                 (y+h) < theCanvas.getHeight())
 862             {
 863                 return;
 864             }
 865         } else {
 866 //          quad test for coverage...?
 867             return;
 868         }
 869         if (p != null) {
 870             if (this.curState.blendop != BlendMode.SRC_OVER) return;
 871             if (!p.isOpaque() || this.curState.globalAlpha < 1.0) return;
 872         }
 873         if (this.curState.numClipPaths > 0) return;
 874         if (this.curState.effect != null) return;
 875         reset();
 876     }
 877 
 878     /**
 879     * Gets the {@code Canvas} that the {@code GraphicsContext} is issuing draw
 880     * commands to. There is only ever one {@code Canvas} for a
 881     * {@code GraphicsContext}.
 882     *
 883     * @return Canvas the canvas that this {@code GraphicsContext} is issuing draw
 884     * commands to.
 885     */
 886     public Canvas getCanvas() {
 887         return theCanvas;
 888     }
 889 
 890     /**
 891      * Saves the following attributes onto a stack.
 892      * <ul>
 893      *     <li>Global Alpha</li>
 894      *     <li>Global Blend Operation</li>
 895      *     <li>Transform</li>
 896      *     <li>Fill Paint</li>
 897      *     <li>Stroke Paint</li>
 898      *     <li>Line Width</li>
 899      *     <li>Line Cap</li>
 900      *     <li>Line Join</li>
 901      *     <li>Miter Limit</li>
 902      *     <li>Clip</li>
 903      *     <li>Font</li>
 904      *     <li>Text Align</li>
 905      *     <li>Text Baseline</li>
 906      *     <li>Effect</li>
 907      *     <li>Fill Rule</li>
 908      * </ul>
 909      * This method does NOT alter the current state in any way. Also, note that
 910      * the current path is not saved.
 911      */
 912     public void save() {
 913         stateStack.push(curState.copy());
 914     }
 915 
 916     /**
 917      * Pops the state off of the stack, setting the following attributes to their
 918      * value at the time when that state was pushed onto the stack. If the stack
 919      * is empty then nothing is changed.
 920      *
 921      * <ul>
 922      *     <li>Global Alpha</li>
 923      *     <li>Global Blend Operation</li>
 924      *     <li>Transform</li>
 925      *     <li>Fill Paint</li>
 926      *     <li>Stroke Paint</li>
 927      *     <li>Line Width</li>
 928      *     <li>Line Cap</li>
 929      *     <li>Line Join</li>
 930      *     <li>Miter Limit</li>
 931      *     <li>Clip</li>
 932      *     <li>Font</li>
 933      *     <li>Text Align</li>
 934      *     <li>Text Baseline</li>
 935      *     <li>Effect</li>
 936      *     <li>Fill Rule</li>
 937      * </ul>
 938      * Note that the current path is not restored.
 939      */
 940     public void restore() {
 941         if (!stateStack.isEmpty()) {
 942             State savedState = stateStack.pop();
 943             savedState.restore(this);
 944             txdirty = true;
 945         }
 946     }
 947 
 948     /**
 949      * Translates the current transform by x, y.
 950      * @param x value to translate along the x axis.
 951      * @param y value to translate along the y axis.
 952      */
 953     public void translate(double x, double y) {
 954         curState.transform.translate(x, y);
 955         txdirty = true;
 956     }
 957 
 958     /**
 959      * Scales the current transform by x, y.
 960      * @param x value to scale in the x axis.
 961      * @param y value to scale in the y axis.
 962      */
 963     public void scale(double x, double y) {
 964         curState.transform.scale(x, y);
 965         txdirty = true;
 966     }
 967 
 968     /**
 969      * Rotates the current transform in degrees.
 970      * @param degrees value in degrees to rotate the current transform.
 971      */
 972     public void rotate(double degrees) {
 973         curState.transform.rotate(Math.toRadians(degrees));
 974         txdirty = true;
 975     }
 976 
 977     /**
 978      * Concatenates the input with the current transform.
 979      *
 980      * @param mxx - the X coordinate scaling element of the 3x4 matrix
 981      * @param myx - the Y coordinate shearing element of the 3x4 matrix
 982      * @param mxy - the X coordinate shearing element of the 3x4 matrix
 983      * @param myy - the Y coordinate scaling element of the 3x4 matrix
 984      * @param mxt - the X coordinate translation element of the 3x4 matrix
 985      * @param myt - the Y coordinate translation element of the 3x4 matrix
 986      */
 987     public void transform(double mxx, double myx,
 988                           double mxy, double myy,
 989                           double mxt, double myt)
 990     {
 991         curState.transform.concatenate(mxx, mxy, mxt,
 992                                        myx, myy, myt);
 993         txdirty = true;
 994     }
 995 
 996     /**
 997      * Concatenates the input with the current transform. Only 2D transforms are
 998      * supported. The only values used are the X and Y scaling, translation, and
 999      * shearing components of a transform. A {@code null} value is treated as identity.
1000      *
1001      * @param xform The affine to be concatenated with the current transform or null.
1002      */
1003     public void transform(Affine xform) {
1004         if (xform == null) return;
1005         curState.transform.concatenate(xform.getMxx(), xform.getMxy(), xform.getTx(),
1006                                        xform.getMyx(), xform.getMyy(), xform.getTy());
1007         txdirty = true;
1008     }
1009 
1010     /**
1011      * Sets the current transform.
1012      * @param mxx - the X coordinate scaling element of the 3x4 matrix
1013      * @param myx - the Y coordinate shearing element of the 3x4 matrix
1014      * @param mxy - the X coordinate shearing element of the 3x4 matrix
1015      * @param myy - the Y coordinate scaling element of the 3x4 matrix
1016      * @param mxt - the X coordinate translation element of the 3x4 matrix
1017      * @param myt - the Y coordinate translation element of the 3x4 matrix
1018      */
1019     public void setTransform(double mxx, double myx,
1020                              double mxy, double myy,
1021                              double mxt, double myt)
1022     {
1023         curState.transform.setTransform(mxx, myx,
1024                                         mxy, myy,
1025                                         mxt, myt);
1026         txdirty = true;
1027     }
1028 
1029     /**
1030      * Sets the current transform. Only 2D transforms are supported. The only
1031      * values used are the X and Y scaling, translation, and shearing components
1032      * of a transform.
1033      *
1034      * @param xform The affine to be copied and used as the current transform.
1035      */
1036     public void setTransform(Affine xform) {
1037         curState.transform.setTransform(xform.getMxx(), xform.getMyx(),
1038                                         xform.getMxy(), xform.getMyy(),
1039                                         xform.getTx(), xform.getTy());
1040         txdirty = true;
1041     }
1042 
1043     /**
1044      * Copies the current transform into the supplied object, creating
1045      * a new {@link Affine} object if it is null, and returns the object
1046      * containing the copy.
1047      *
1048      * @param xform A transform object that will be used to hold the result.
1049      * If xform is non null, then this method will copy the current transform
1050      * into that object. If xform is null a new transform object will be
1051      * constructed. In either case, the return value is a copy of the current
1052      * transform.
1053      *
1054      * @return A copy of the current transform.
1055      */
1056     public Affine getTransform(Affine xform) {
1057         if (xform == null) {
1058             xform = new Affine();
1059         }
1060 
1061         xform.setMxx(curState.transform.getMxx());
1062         xform.setMxy(curState.transform.getMxy());
1063         xform.setMxz(0);
1064         xform.setTx(curState.transform.getMxt());
1065         xform.setMyx(curState.transform.getMyx());
1066         xform.setMyy(curState.transform.getMyy());
1067         xform.setMyz(0);
1068         xform.setTy(curState.transform.getMyt());
1069         xform.setMzx(0);
1070         xform.setMzy(0);
1071         xform.setMzz(1);
1072         xform.setTz(0);
1073 
1074         return xform;
1075     }
1076 
1077     /**
1078      * Returns a copy of the current transform.
1079      *
1080      * @return a copy of the transform of the current state.
1081      */
1082     public Affine getTransform() {
1083         return getTransform(null);
1084     }
1085 
1086     /**
1087      * Sets the global alpha of the current state.
1088      * The default value is {@code 1.0}.
1089      * Any valid double can be set, but only values in the range
1090      * {@code [0.0, 1.0]} are valid and the nearest value in that
1091      * range will be used for rendering.
1092      * The global alpha is a <a href="#comm-attr">common attribute</a>
1093      * used for nearly all rendering methods as specified in the
1094      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1095      *
1096      * @param alpha the new alpha value, clamped to {@code [0.0, 1.0]}
1097      *              during actual use.
1098      */
1099     public void setGlobalAlpha(double alpha) {
1100         if (curState.globalAlpha != alpha) {
1101             curState.globalAlpha = alpha;
1102             alpha = (alpha > 1.0) ? 1.0 : (alpha < 0.0) ? 0.0 : alpha;
1103             writeParam(alpha, NGCanvas.GLOBAL_ALPHA);
1104         }
1105     }
1106 
1107     /**
1108      * Gets the current global alpha.
1109      * The default value is {@code 1.0}.
1110      * The global alpha is a <a href="#comm-attr">common attribute</a>
1111      * used for nearly all rendering methods as specified in the
1112      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1113      *
1114      * @return the current global alpha.
1115      */
1116     public double getGlobalAlpha() {
1117         return curState.globalAlpha;
1118     }
1119 
1120     /**
1121      * Sets the global blend mode.
1122      * The default value is {@link BlendMode#SRC_OVER SRC_OVER}.
1123      * A {@code null} value will be ignored and the current value will remain unchanged.
1124      * The blend mode is a <a href="#comm-attr">common attribute</a>
1125      * used for nearly all rendering methods as specified in the
1126      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1127      *
1128      * @param op the {@code BlendMode} that will be set or null.
1129      */
1130     public void setGlobalBlendMode(BlendMode op) {
1131         if (op != null && op != curState.blendop) {
1132             GrowableDataBuffer buf = getBuffer();
1133             curState.blendop = op;
1134             buf.putByte(NGCanvas.COMP_MODE);
1135             buf.putObject(EffectHelper.getToolkitBlendMode(op));
1136         }
1137     }
1138 
1139     /**
1140      * Gets the global blend mode.
1141      * The default value is {@link BlendMode#SRC_OVER SRC_OVER}.
1142      * The blend mode is a <a href="#comm-attr">common attribute</a>
1143      * used for nearly all rendering methods as specified in the
1144      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1145      *
1146      * @return the global {@code BlendMode} of the current state.
1147      */
1148     public BlendMode getGlobalBlendMode() {
1149         return curState.blendop;
1150     }
1151 
1152     /**
1153      * Sets the current fill paint attribute.
1154      * The default value is {@link Color#BLACK BLACK}.
1155      * The fill paint is a <a href="#fill-attr">fill attribute</a>
1156      * used for any of the fill methods as specified in the
1157      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1158      * A {@code null} value will be ignored and the current value will remain unchanged.
1159      *
1160      * @param p The {@code Paint} to be used as the fill {@code Paint} or null.
1161      */
1162     public void setFill(Paint p) {
1163         if (p != null && curState.fill != p) {
1164             curState.fill = p;
1165             writePaint(p, NGCanvas.FILL_PAINT);
1166         }
1167     }
1168 
1169     /**
1170      * Gets the current fill paint attribute.
1171      * The default value is {@link Color#BLACK BLACK}.
1172      * The fill paint is a <a href="#fill-attr">fill attribute</a>
1173      * used for any of the fill methods as specified in the
1174      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1175      *
1176      * @return p The {@code Paint} to be used as the fill {@code Paint}.
1177      */
1178     public Paint getFill() {
1179         return curState.fill;
1180     }
1181 
1182     /**
1183      * Sets the current stroke paint attribute.
1184      * The default value is {@link Color#BLACK BLACK}.
1185      * The stroke paint is a <a href="#strk-attr">stroke attribute</a>
1186      * used for any of the stroke methods as specified in the
1187      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1188      * A {@code null} value will be ignored and the current value will remain unchanged.
1189      *
1190      * @param p The Paint to be used as the stroke Paint or null.
1191      */
1192     public void setStroke(Paint p) {
1193         if (p != null && curState.stroke != p) {
1194             curState.stroke = p;
1195             writePaint(p, NGCanvas.STROKE_PAINT);
1196         }
1197     }
1198 
1199     /**
1200      * Gets the current stroke.
1201      * The default value is {@link Color#BLACK BLACK}.
1202      * The stroke paint is a <a href="#strk-attr">stroke attribute</a>
1203      * used for any of the stroke methods as specified in the
1204      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1205      *
1206      * @return the {@code Paint} to be used as the stroke {@code Paint}.
1207      */
1208     public Paint getStroke() {
1209         return curState.stroke;
1210     }
1211 
1212     /**
1213      * Sets the current line width.
1214      * The default value is {@code 1.0}.
1215      * The line width is a <a href="#strk-attr">stroke attribute</a>
1216      * used for any of the stroke methods as specified in the
1217      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1218      * An infinite or non-positive value outside of the range {@code (0, +inf)}
1219      * will be ignored and the current value will remain unchanged.
1220      *
1221      * @param lw value in the range {0-positive infinity}, with any other value
1222      * being ignored and leaving the value unchanged.
1223      */
1224     public void setLineWidth(double lw) {
1225         // Per W3C spec: On setting, zero, negative, infinite, and NaN
1226         // values must be ignored, leaving the value unchanged
1227         if (lw > 0 && lw < Double.POSITIVE_INFINITY) {
1228             if (curState.linewidth != lw) {
1229                 curState.linewidth = lw;
1230                 writeParam(lw, NGCanvas.LINE_WIDTH);
1231             }
1232         }
1233     }
1234 
1235     /**
1236      * Gets the current line width.
1237      * The default value is {@code 1.0}.
1238      * The line width is a <a href="#strk-attr">stroke attribute</a>
1239      * used for any of the stroke methods as specified in the
1240      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1241      *
1242      * @return value between 0 and infinity.
1243      */
1244     public double getLineWidth() {
1245         return curState.linewidth;
1246     }
1247 
1248     /**
1249      * Sets the current stroke line cap.
1250      * The default value is {@link StrokeLineCap#SQUARE SQUARE}.
1251      * The line cap is a <a href="#strk-attr">stroke attribute</a>
1252      * used for any of the stroke methods as specified in the
1253      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1254      * A {@code null} value will be ignored and the current value will remain unchanged.
1255      *
1256      * @param cap {@code StrokeLineCap} with a value of Butt, Round, or Square or null.
1257      */
1258     public void setLineCap(StrokeLineCap cap) {
1259         if (cap != null && curState.linecap != cap) {
1260             byte v;
1261             switch (cap) {
1262                 case BUTT: v = NGCanvas.CAP_BUTT; break;
1263                 case ROUND: v = NGCanvas.CAP_ROUND; break;
1264                 case SQUARE: v = NGCanvas.CAP_SQUARE; break;
1265                 default: return;
1266             }
1267             curState.linecap = cap;
1268             writeParam(v, NGCanvas.LINE_CAP);
1269         }
1270     }
1271 
1272     /**
1273      * Gets the current stroke line cap.
1274      * The default value is {@link StrokeLineCap#SQUARE SQUARE}.
1275      * The line cap is a <a href="#strk-attr">stroke attribute</a>
1276      * used for any of the stroke methods as specified in the
1277      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1278      *
1279      * @return {@code StrokeLineCap} with a value of Butt, Round, or Square.
1280      */
1281     public StrokeLineCap getLineCap() {
1282         return curState.linecap;
1283     }
1284 
1285     /**
1286      * Sets the current stroke line join.
1287      * The default value is {@link StrokeLineJoin#MITER}.
1288      * The line join is a <a href="#strk-attr">stroke attribute</a>
1289      * used for any of the stroke methods as specified in the
1290      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1291      * A {@code null} value will be ignored and the current value will remain unchanged.
1292      *
1293      * @param join {@code StrokeLineJoin} with a value of Miter, Bevel, or Round or null.
1294      */
1295     public void setLineJoin(StrokeLineJoin join) {
1296         if (join != null && curState.linejoin != join) {
1297             byte v;
1298             switch (join) {
1299                 case MITER: v = NGCanvas.JOIN_MITER; break;
1300                 case BEVEL: v = NGCanvas.JOIN_BEVEL; break;
1301                 case ROUND: v = NGCanvas.JOIN_ROUND; break;
1302                 default: return;
1303             }
1304             curState.linejoin = join;
1305             writeParam(v, NGCanvas.LINE_JOIN);
1306         }
1307     }
1308 
1309     /**
1310      * Gets the current stroke line join.
1311      * The default value is {@link StrokeLineJoin#MITER}.
1312      * The line join is a <a href="#strk-attr">stroke attribute</a>
1313      * used for any of the stroke methods as specified in the
1314      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1315      *
1316      * @return {@code StrokeLineJoin} with a value of Miter, Bevel, or Round.
1317      */
1318     public StrokeLineJoin getLineJoin() {
1319         return curState.linejoin;
1320     }
1321 
1322     /**
1323      * Sets the current miter limit.
1324      * The default value is {@code 10.0}.
1325      * The miter limit is a <a href="#strk-attr">stroke attribute</a>
1326      * used for any of the stroke methods as specified in the
1327      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1328      * An infinite or non-positive value outside of the range {@code (0, +inf)}
1329      * will be ignored and the current value will remain unchanged.
1330      *
1331      * @param ml miter limit value between 0 and positive infinity with
1332      * any other value being ignored and leaving the value unchanged.
1333      */
1334     public void setMiterLimit(double ml) {
1335         // Per W3C spec: On setting, zero, negative, infinite, and NaN
1336         // values must be ignored, leaving the value unchanged
1337         if (ml > 0.0 && ml < Double.POSITIVE_INFINITY) {
1338             if (curState.miterlimit != ml) {
1339                 curState.miterlimit = ml;
1340                 writeParam(ml, NGCanvas.MITER_LIMIT);
1341             }
1342         }
1343     }
1344 
1345     /**
1346      * Gets the current miter limit.
1347      * The default value is {@code 10.0}.
1348      * The miter limit is a <a href="#strk-attr">stroke attribute</a>
1349      * used for any of the stroke methods as specified in the
1350      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1351      *
1352      * @return the miter limit value in the range {@code 0.0-positive infinity}
1353      */
1354     public double getMiterLimit() {
1355         return curState.miterlimit;
1356     }
1357 
1358     /**
1359      * Sets the current stroke line dash pattern to a normalized copy of
1360      * the argument.
1361      * The default value is {@code null}.
1362      * The line dash array is a <a href="#strk-attr">stroke attribute</a>
1363      * used for any of the stroke methods as specified in the
1364      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1365      * If the array is {@code null} or empty or contains all {@code 0} elements
1366      * then dashing will be disabled and the current dash array will be set
1367      * to {@code null}.
1368      * If any of the elements of the array are a negative, infinite, or NaN
1369      * value outside the range {@code [0, +inf)} then the entire array will
1370      * be ignored and the current dash array will remain unchanged.
1371      * If the array is an odd length then it will be treated as if it
1372      * were two copies of the array appended to each other.
1373      *
1374      * @param dashes the array of finite non-negative dash lengths
1375      * @since JavaFX 8u40
1376      */
1377     public void setLineDashes(double... dashes) {
1378         if (dashes == null || dashes.length == 0) {
1379             if (curState.dashes == null) {
1380                 return;
1381             }
1382             curState.dashes = null;
1383         } else {
1384             boolean allZeros = true;
1385             for (int i = 0; i < dashes.length; i++) {
1386                 double d = dashes[i];
1387                 if (d >= 0.0 && d < Double.POSITIVE_INFINITY) {
1388                     // Non-NaN, finite, non-negative
1389                     // Test cannot be inverted or it will not implicitly test for NaN
1390                     if (d > 0) {
1391                         allZeros = false;
1392                     }
1393                 } else {
1394                     return;
1395                 }
1396             }
1397             if (allZeros) {
1398                 if (curState.dashes == null) {
1399                     return;
1400                 }
1401                 curState.dashes = null;
1402             } else {
1403                 int dashlen = dashes.length;
1404                 if ((dashlen & 1) == 0) {
1405                     curState.dashes = Arrays.copyOf(dashes, dashlen);
1406                 } else {
1407                     curState.dashes = Arrays.copyOf(dashes, dashlen * 2);
1408                     System.arraycopy(dashes, 0, curState.dashes, dashlen, dashlen);
1409                 }
1410             }
1411         }
1412         GrowableDataBuffer buf = getBuffer();
1413         buf.putByte(NGCanvas.DASH_ARRAY);
1414         buf.putObject(curState.dashes);
1415     }
1416 
1417     /**
1418      * Gets a copy of the current line dash array.
1419      * The default value is {@code null}.
1420      * The array may be normalized by the validation tests in the
1421      * {@link #setLineDashes(double...)} method.
1422      * The line dash array is a <a href="#strk-attr">stroke attribute</a>
1423      * used for any of the stroke methods as specified in the
1424      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1425      *
1426      * @return a copy of the current line dash array.
1427      * @since JavaFX 8u40
1428      */
1429     public double[] getLineDashes() {
1430         if (curState.dashes == null) {
1431             return null;
1432         }
1433         return Arrays.copyOf(curState.dashes, curState.dashes.length);
1434     }
1435 
1436     /**
1437      * Sets the line dash offset.
1438      * The default value is {@code 0.0}.
1439      * The line dash offset is a <a href="#strk-attr">stroke attribute</a>
1440      * used for any of the stroke methods as specified in the
1441      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1442      * An infinite or NaN value outside of the range {@code (-inf, +inf)}
1443      * will be ignored and the current value will remain unchanged.
1444      *
1445      * @param dashOffset the line dash offset in the range {@code (-inf, +inf)}
1446      * @since JavaFX 8u40
1447      */
1448     public void setLineDashOffset(double dashOffset) {
1449         // Per W3C spec: On setting, infinite, and NaN
1450         // values must be ignored, leaving the value unchanged
1451         if (dashOffset > Double.NEGATIVE_INFINITY && dashOffset < Double.POSITIVE_INFINITY) {
1452             curState.dashOffset = dashOffset;
1453             writeParam(dashOffset, NGCanvas.DASH_OFFSET);
1454         }
1455     }
1456 
1457     /**
1458      * Gets the current line dash offset.
1459      * The default value is {@code 0.0}.
1460      * The line dash offset is a <a href="#strk-attr">stroke attribute</a>
1461      * used for any of the stroke methods as specified in the
1462      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1463      *
1464      * @return the line dash offset in the range {@code (-inf, +inf)}
1465      * @since JavaFX 8u40
1466      */
1467     public double getLineDashOffset() {
1468         return curState.dashOffset;
1469     }
1470 
1471     /**
1472      * Sets the current Font.
1473      * The default value is specified by {@link Font#getDefault()}.
1474      * The font is a <a href="#text-attr">text attribute</a>
1475      * used for any of the text methods as specified in the
1476      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1477      * A {@code null} value will be ignored and the current value will remain unchanged.
1478      *
1479      * @param f the Font or null.
1480      */
1481     public void setFont(Font f) {
1482         if (f != null && curState.font != f) {
1483             curState.font = f;
1484             GrowableDataBuffer buf = getBuffer();
1485             buf.putByte(NGCanvas.FONT);
1486             buf.putObject(FontHelper.getNativeFont(f));
1487         }
1488     }
1489 
1490     /**
1491      * Gets the current Font.
1492      * The default value is specified by {@link Font#getDefault()}.
1493      * The font is a <a href="#text-attr">text attribute</a>
1494      * used for any of the text methods as specified in the
1495      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1496      *
1497      * @return the Font
1498      */
1499     public Font getFont() {
1500         return curState.font;
1501     }
1502 
1503     /**
1504      * Sets the current Font Smoothing Type.
1505      * The default value is {@link FontSmoothingType#GRAY GRAY}.
1506      * The font smoothing type is a <a href="#text-attr">text attribute</a>
1507      * used for any of the text methods as specified in the
1508      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1509      * A {@code null} value will be ignored and the current value will remain unchanged.
1510      * <p>
1511      * <b>Note</b> that the {@code FontSmoothingType} value of
1512      * {@link FontSmoothingType#LCD LCD} is only supported over an opaque
1513      * background.  {@code LCD} text will generally appear as {@code GRAY}
1514      * text over transparent or partially transparent pixels, and in some
1515      * implementations it may not be supported at all on a {@link Canvas}
1516      * because the required support does not exist for surfaces which contain
1517      * an alpha channel as all {@code Canvas} objects do.
1518      *
1519      * @param fontsmoothing the {@link FontSmoothingType} or null
1520      * @since JavaFX 8u40
1521      */
1522     public void setFontSmoothingType(FontSmoothingType fontsmoothing) {
1523         if (fontsmoothing != null && fontsmoothing != curState.fontsmoothing) {
1524             curState.fontsmoothing = fontsmoothing;
1525             writeParam((byte) fontsmoothing.ordinal(), NGCanvas.FONT_SMOOTH);
1526         }
1527     }
1528 
1529     /**
1530      * Gets the current Font Smoothing Type.
1531      * The default value is {@link FontSmoothingType#GRAY GRAY}.
1532      * The font smoothing type is a <a href="#text-attr">text attribute</a>
1533      * used for any of the text methods as specified in the
1534      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1535      *
1536      * @return the {@link FontSmoothingType}
1537      * @since JavaFX 8u40
1538      */
1539     public FontSmoothingType getFontSmoothingType() {
1540         return curState.fontsmoothing;
1541     }
1542 
1543     /**
1544      * Defines horizontal text alignment, relative to the text {@code x} origin.
1545      * The default value is {@link TextAlignment#LEFT LEFT}.
1546      * The text alignment is a <a href="#text-attr">text attribute</a>
1547      * used for any of the text methods as specified in the
1548      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1549      * <p>
1550      * Let horizontal bounds represent the logical width of a single line of
1551      * text. Where each line of text has a separate horizontal bounds.
1552      * <p>
1553      * Then TextAlignment is specified as:
1554      * <ul>
1555      * <li>Left: the left edge of the horizontal bounds will be at {@code x}.
1556      * <li>Center: the center, halfway between left and right edge, of the
1557      * horizontal bounds will be at {@code x}.
1558      * <li>Right: the right edge of the horizontal bounds will be at {@code x}.
1559      * </ul>
1560      * <p>
1561      *
1562      * Note: Canvas does not support line wrapping, therefore the text
1563      * alignment Justify is identical to left aligned text.
1564      * <p>
1565      * A {@code null} value will be ignored and the current value will remain unchanged.
1566      *
1567      * @param align {@code TextAlignment} with values of Left, Center, Right or null.
1568      */
1569     public void setTextAlign(TextAlignment align) {
1570         if (align != null && curState.textalign != align) {
1571             byte a;
1572             switch (align) {
1573                 case LEFT: a = NGCanvas.ALIGN_LEFT; break;
1574                 case CENTER: a = NGCanvas.ALIGN_CENTER; break;
1575                 case RIGHT: a = NGCanvas.ALIGN_RIGHT; break;
1576                 case JUSTIFY: a = NGCanvas.ALIGN_JUSTIFY; break;
1577                 default: return;
1578             }
1579             curState.textalign = align;
1580             writeParam(a, NGCanvas.TEXT_ALIGN);
1581         }
1582     }
1583 
1584     /**
1585      * Gets the current {@code TextAlignment}.
1586      * The default value is {@link TextAlignment#LEFT LEFT}.
1587      * The text alignment is a <a href="#text-attr">text attribute</a>
1588      * used for any of the text methods as specified in the
1589      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1590      *
1591      * @return {@code TextAlignment} with values of Left, Center, Right, or
1592      * Justify.
1593      */
1594     public TextAlignment getTextAlign() {
1595         return curState.textalign;
1596     }
1597 
1598     /**
1599      * Sets the current Text Baseline.
1600      * The default value is {@link VPos#BASELINE BASELINE}.
1601      * The text baseline is a <a href="#text-attr">text attribute</a>
1602      * used for any of the text methods as specified in the
1603      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1604      * A {@code null} value will be ignored and the current value will remain unchanged.
1605      *
1606      * @param baseline {@code VPos} with values of Top, Center, Baseline, or Bottom or null.
1607      */
1608     public void setTextBaseline(VPos baseline) {
1609         if (baseline != null && curState.textbaseline != baseline) {
1610             byte b;
1611             switch (baseline) {
1612                 case TOP: b = NGCanvas.BASE_TOP; break;
1613                 case CENTER: b = NGCanvas.BASE_MIDDLE; break;
1614                 case BASELINE: b = NGCanvas.BASE_ALPHABETIC; break;
1615                 case BOTTOM: b = NGCanvas.BASE_BOTTOM; break;
1616                 default: return;
1617             }
1618             curState.textbaseline = baseline;
1619             writeParam(b, NGCanvas.TEXT_BASELINE);
1620         }
1621     }
1622 
1623     /**
1624      * Gets the current Text Baseline.
1625      * The default value is {@link VPos#BASELINE BASELINE}.
1626      * The text baseline is a <a href="#text-attr">text attribute</a>
1627      * used for any of the text methods as specified in the
1628      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1629      *
1630      * @return {@code VPos} with values of Top, Center, Baseline, or Bottom
1631      */
1632     public VPos getTextBaseline() {
1633         return curState.textbaseline;
1634     }
1635 
1636     /**
1637      * Fills the given string of text at position x, y
1638      * with the current fill paint attribute.
1639      * A {@code null} text value will be ignored.
1640      * <p>
1641      * This method will be affected by any of the
1642      * <a href="#comm-attr">global common</a>,
1643      * <a href="#fill-attr">fill</a>,
1644      * or <a href="#text-attr">text</a>
1645      * attributes as specified in the
1646      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1647      * </p>
1648      *
1649      * @param text the string of text or null.
1650      * @param x position on the x axis.
1651      * @param y position on the y axis.
1652      */
1653     public void fillText(String text, double x, double y) {
1654         writeText(text, x, y, 0, NGCanvas.FILL_TEXT);
1655     }
1656 
1657     /**
1658      * Draws the given string of text at position x, y
1659      * with the current stroke paint attribute.
1660      * A {@code null} text value will be ignored.
1661      * <p>
1662      * This method will be affected by any of the
1663      * <a href="#comm-attr">global common</a>,
1664      * <a href="#strk-attr">stroke</a>,
1665      * or <a href="#text-attr">text</a>
1666      * attributes as specified in the
1667      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1668      * </p>
1669      *
1670      * @param text the string of text or null.
1671      * @param x position on the x axis.
1672      * @param y position on the y axis.
1673      */
1674     public void strokeText(String text, double x, double y) {
1675         writeText(text, x, y, 0, NGCanvas.STROKE_TEXT);
1676     }
1677 
1678     /**
1679      * Fills text and includes a maximum width of the string.
1680      * If the width of the text extends past max width, then it will be sized
1681      * to fit.
1682      * A {@code null} text value will be ignored.
1683      * <p>
1684      * This method will be affected by any of the
1685      * <a href="#comm-attr">global common</a>,
1686      * <a href="#fill-attr">fill</a>,
1687      * or <a href="#text-attr">text</a>
1688      * attributes as specified in the
1689      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1690      * </p>
1691      *
1692      * @param text the string of text or null.
1693      * @param x position on the x axis.
1694      * @param y position on the y axis.
1695      * @param maxWidth  maximum width the text string can have.
1696      */
1697     public void fillText(String text, double x, double y, double maxWidth) {
1698         if (maxWidth <= 0) return;
1699         writeText(text, x, y, maxWidth, NGCanvas.FILL_TEXT);
1700     }
1701 
1702     /**
1703      * Draws text with stroke paint and includes a maximum width of the string.
1704      * If the width of the text extends past max width, then it will be sized
1705      * to fit.
1706      * A {@code null} text value will be ignored.
1707      * <p>
1708      * This method will be affected by any of the
1709      * <a href="#comm-attr">global common</a>,
1710      * <a href="#strk-attr">stroke</a>,
1711      * or <a href="#text-attr">text</a>
1712      * attributes as specified in the
1713      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1714      * </p>
1715      *
1716      * @param text the string of text or null.
1717      * @param x position on the x axis.
1718      * @param y position on the y axis.
1719      * @param maxWidth  maximum width the text string can have.
1720      */
1721     public void strokeText(String text, double x, double y, double maxWidth) {
1722         if (maxWidth <= 0) return;
1723         writeText(text, x, y, maxWidth, NGCanvas.STROKE_TEXT);
1724     }
1725 
1726 
1727     /**
1728      * Set the filling rule attribute for determining the interior of paths
1729      * in fill or clip operations.
1730      * The default value is {@code FillRule.NON_ZERO}.
1731      * A {@code null} value will be ignored and the current value will remain unchanged.
1732      * The fill rule is a <a href="#path-attr">path attribute</a>
1733      * used for any of the fill or clip path methods as specified in the
1734      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1735      *
1736      * @param fillRule {@code FillRule} with a value of  Even_odd or Non_zero or null.
1737      */
1738      public void setFillRule(FillRule fillRule) {
1739          if (fillRule != null && curState.fillRule != fillRule) {
1740             byte b;
1741             if (fillRule == FillRule.EVEN_ODD) {
1742                 b = NGCanvas.FILL_RULE_EVEN_ODD;
1743             } else {
1744                 b = NGCanvas.FILL_RULE_NON_ZERO;
1745             }
1746             curState.fillRule = fillRule;
1747             writeParam(b, NGCanvas.FILL_RULE);
1748         }
1749      }
1750 
1751     /**
1752      * Get the filling rule attribute for determining the interior of paths
1753      * in fill and clip operations.
1754      * The default value is {@code FillRule.NON_ZERO}.
1755      * The fill rule is a <a href="#path-attr">path attribute</a>
1756      * used for any of the fill or clip path methods as specified in the
1757      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
1758      *
1759      * @return current fill rule.
1760      */
1761      public FillRule getFillRule() {
1762          return curState.fillRule;
1763      }
1764 
1765     /**
1766      * Resets the current path to empty.
1767      * The default path is empty.
1768      * The current path is a <a href="#path-attr">path attribute</a>
1769      * used for any of the path methods as specified in the
1770      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1771      * and <b>is not affected</b> by the {@link #save()} and
1772      * {@link #restore()} operations.
1773      */
1774     public void beginPath() {
1775         path.reset();
1776         markPathDirty();
1777     }
1778 
1779     /**
1780      * Issues a move command for the current path to the given x,y coordinate.
1781      * The coordinates are transformed by the current transform as they are
1782      * added to the path and unaffected by subsequent changes to the transform.
1783      * The current path is a <a href="#path-attr">path attribute</a>
1784      * used for any of the path methods as specified in the
1785      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1786      * and <b>is not affected</b> by the {@link #save()} and
1787      * {@link #restore()} operations.
1788      *
1789      * @param x0 the X position for the move to command.
1790      * @param y0 the Y position for the move to command.
1791      */
1792     public void moveTo(double x0, double y0) {
1793         coords[0] = (float) x0;
1794         coords[1] = (float) y0;
1795         curState.transform.transform(coords, 0, coords, 0, 1);
1796         path.moveTo(coords[0], coords[1]);
1797         markPathDirty();
1798     }
1799 
1800     /**
1801      * Adds segments to the current path to make a line to the given x,y
1802      * coordinate.
1803      * The coordinates are transformed by the current transform as they are
1804      * added to the path and unaffected by subsequent changes to the transform.
1805      * The current path is a <a href="#path-attr">path attribute</a>
1806      * used for any of the path methods as specified in the
1807      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1808      * and <b>is not affected</b> by the {@link #save()} and
1809      * {@link #restore()} operations.
1810      *
1811      * @param x1 the X coordinate of the ending point of the line.
1812      * @param y1 the Y coordinate of the ending point of the line.
1813      */
1814     public void lineTo(double x1, double y1) {
1815         coords[0] = (float) x1;
1816         coords[1] = (float) y1;
1817         curState.transform.transform(coords, 0, coords, 0, 1);
1818         if (path.getNumCommands() == 0) {
1819             path.moveTo(coords[0], coords[1]);
1820         }
1821         path.lineTo(coords[0], coords[1]);
1822         markPathDirty();
1823     }
1824 
1825     /**
1826      * Adds segments to the current path to make a quadratic Bezier curve.
1827      * The coordinates are transformed by the current transform as they are
1828      * added to the path and unaffected by subsequent changes to the transform.
1829      * The current path is a <a href="#path-attr">path attribute</a>
1830      * used for any of the path methods as specified in the
1831      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1832      * and <b>is not affected</b> by the {@link #save()} and
1833      * {@link #restore()} operations.
1834      *
1835      * @param xc the X coordinate of the control point
1836      * @param yc the Y coordinate of the control point
1837      * @param x1 the X coordinate of the end point
1838      * @param y1 the Y coordinate of the end point
1839      */
1840     public void quadraticCurveTo(double xc, double yc, double x1, double y1) {
1841         coords[0] = (float) xc;
1842         coords[1] = (float) yc;
1843         coords[2] = (float) x1;
1844         coords[3] = (float) y1;
1845         curState.transform.transform(coords, 0, coords, 0, 2);
1846         if (path.getNumCommands() == 0) {
1847             path.moveTo(coords[0], coords[1]);
1848         }
1849         path.quadTo(coords[0], coords[1], coords[2], coords[3]);
1850         markPathDirty();
1851     }
1852 
1853     /**
1854      * Adds segments to the current path to make a cubic Bezier curve.
1855      * The coordinates are transformed by the current transform as they are
1856      * added to the path and unaffected by subsequent changes to the transform.
1857      * The current path is a <a href="#path-attr">path attribute</a>
1858      * used for any of the path methods as specified in the
1859      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1860      * and <b>is not affected</b> by the {@link #save()} and
1861      * {@link #restore()} operations.
1862      *
1863      * @param xc1 the X coordinate of first Bezier control point.
1864      * @param yc1 the Y coordinate of the first Bezier control point.
1865      * @param xc2 the X coordinate of the second Bezier control point.
1866      * @param yc2 the Y coordinate of the second Bezier control point.
1867      * @param x1  the X coordinate of the end point.
1868      * @param y1  the Y coordinate of the end point.
1869      */
1870     public void bezierCurveTo(double xc1, double yc1, double xc2, double yc2, double x1, double y1) {
1871         coords[0] = (float) xc1;
1872         coords[1] = (float) yc1;
1873         coords[2] = (float) xc2;
1874         coords[3] = (float) yc2;
1875         coords[4] = (float) x1;
1876         coords[5] = (float) y1;
1877         curState.transform.transform(coords, 0, coords, 0, 3);
1878         if (path.getNumCommands() == 0) {
1879             path.moveTo(coords[0], coords[1]);
1880         }
1881         path.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
1882         markPathDirty();
1883     }
1884 
1885     /**
1886      * Adds segments to the current path to make an arc.
1887      * The coordinates are transformed by the current transform as they are
1888      * added to the path and unaffected by subsequent changes to the transform.
1889      * The current path is a <a href="#path-attr">path attribute</a>
1890      * used for any of the path methods as specified in the
1891      * <a href="#attr-ops-table">Rendering Attributes Table</a>
1892      * and <b>is not affected</b> by the {@link #save()} and
1893      * {@link #restore()} operations.
1894      * <p>
1895      * If {@code p0} is the current point in the path and {@code p1} is the
1896      * point specified by {@code (x1, y1)} and {@code p2} is the point
1897      * specified by {@code (x2, y2)}, then the arc segments appended will
1898      * be segments along the circumference of a circle of the specified
1899      * radius touching and inscribed into the convex (interior) side of
1900      * {@code p0->p1->p2}.  The path will contain a line segment (if
1901      * needed) to the tangent point between that circle and {@code p0->p1}
1902      * followed by circular arc segments to reach the tangent point between
1903      * the circle and {@code p1->p2} and will end with the current point at
1904      * that tangent point (not at {@code p2}).
1905      * Note that the radius and circularity of the arc segments will be
1906      * measured or considered relative to the current transform, but the
1907      * resulting segments that are computed from those untransformed
1908      * points will then be transformed when they are added to the path.
1909      * Since all computation is done in untransformed space, but the
1910      * pre-existing path segments are all transformed, the ability to
1911      * correctly perform the computation may implicitly depend on being
1912      * able to inverse transform the current end of the current path back
1913      * into untransformed coordinates.
1914      * </p>
1915      * <p>
1916      * If there is no way to compute and inscribe the indicated circle
1917      * for any reason then the entire operation will simply append segments
1918      * to force a line to point {@code p1}.  Possible reasons that the
1919      * computation may fail include:
1920      * <ul>
1921      * <li>The current path is empty.</li>
1922      * <li>The segments {@code p0->p1->p2} are colinear.</li>
1923      * <li>the current transform is non-invertible so that the current end
1924      * point of the current path cannot be untransformed for computation.</li>
1925      * </ul>
1926      * </p>
1927      *
1928      * @param x1 the X coordinate of the first point of the arc.
1929      * @param y1 the Y coordinate of the first point of the arc.
1930      * @param x2 the X coordinate of the second point of the arc.
1931      * @param y2 the Y coordinate of the second point of the arc.
1932      * @param radius the radius of the arc in the range {0.0-positive infinity}.
1933      */
1934     public void arcTo(double x1, double y1, double x2, double y2, double radius) {
1935         if (path.getNumCommands() == 0) {
1936             moveTo(x1, y1);
1937             lineTo(x1, y1);
1938         } else if (!tryArcTo((float) x1, (float) y1, (float) x2, (float) y2,
1939                              (float) radius))
1940         {
1941             lineTo(x1, y1);
1942         }
1943     }
1944 
1945     private static double lenSq(double x0, double y0, double x1, double y1) {
1946         x1 -= x0;
1947         y1 -= y0;
1948         return x1 * x1 + y1 * y1;
1949     }
1950 
1951     private boolean tryArcTo(float x1, float y1, float x2, float y2, float radius) {
1952         float x0, y0;
1953         if (curState.transform.isTranslateOrIdentity()) {
1954             x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
1955             y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
1956         } else {
1957             coords[0] = path.getCurrentX();
1958             coords[1] = path.getCurrentY();
1959             try {
1960                 curState.transform.inverseTransform(coords, 0, coords, 0, 1);
1961             } catch (NoninvertibleTransformException e) {
1962                 return false;
1963             }
1964             x0 = coords[0];
1965             y0 = coords[1];
1966         }
1967         // call x1,y1 the corner point
1968         // If 2*theta is the angle described by p0->p1->p2
1969         // then theta is the angle described by p0->p1->centerpt and
1970         // centerpt->p1->p2
1971         // We know that the distance from the arc center to the tangent points
1972         // is r, and if A is the distance from the corner to the tangent point
1973         // then we know:
1974         // tan(theta) = r/A
1975         // A = r / sin(theta)
1976         // B = A * cos(theta) = r * (sin/cos) = r * tan
1977         // We use the cosine rule on the triangle to get the 2*theta angle:
1978         // cosB = (a^2 + c^2 - b^2) / (2ac)
1979         // where a and c are the adjacent sides and b is the opposite side
1980         // i.e. a = p0->p1, c=p1->p2, b=p0->p2
1981         // Then we can use the tan^2 identity to compute B:
1982         // tan^2 = (1 - cos(2theta)) / (1 + cos(2theta))
1983         double lsq01 = lenSq(x0, y0, x1, y1);
1984         double lsq12 = lenSq(x1, y1, x2, y2);
1985         double lsq02 = lenSq(x0, y0, x2, y2);
1986         double len01 = Math.sqrt(lsq01);
1987         double len12 = Math.sqrt(lsq12);
1988         double cosnum = lsq01 + lsq12 - lsq02;
1989         double cosden = 2.0 * len01 * len12;
1990         if (cosden == 0.0 || radius <= 0f) {
1991             return false;
1992         }
1993         double cos_2theta = cosnum / cosden;
1994         double tansq_den = (1.0 + cos_2theta);
1995         if (tansq_den == 0.0) {
1996             return false;
1997         }
1998         double tansq_theta = (1.0 - cos_2theta) / tansq_den;
1999         double A = radius / Math.sqrt(tansq_theta);
2000         double tx0 = x1 + (A / len01) * (x0 - x1);
2001         double ty0 = y1 + (A / len01) * (y0 - y1);
2002         double tx1 = x1 + (A / len12) * (x2 - x1);
2003         double ty1 = y1 + (A / len12) * (y2 - y1);
2004         // The midpoint between the two tangent points
2005         double mx = (tx0 + tx1) / 2.0;
2006         double my = (ty0 + ty1) / 2.0;
2007         // similar triangles tell us that:
2008         // len(m,center)/len(m,tangent) = len(m,tangent)/len(corner,m)
2009         // len(m,center) = lensq(m,tangent)/len(corner,m)
2010         // center = m + (m - p1) * len(m,center) / len(corner,m)
2011         //   = m + (m - p1) * (lensq(m,tangent) / lensq(corner,m))
2012         double lenratioden = lenSq(mx, my, x1, y1);
2013         if (lenratioden == 0.0) {
2014             return false;
2015         }
2016         double lenratio = lenSq(mx, my, tx0, ty0) / lenratioden;
2017         double cx = mx + (mx - x1) * lenratio;
2018         double cy = my + (my - y1) * lenratio;
2019         if (!(cx == cx && cy == cy)) {
2020             return false;
2021         }
2022         // Looks like we are good to draw, first we have to get to the
2023         // initial tangent point with a line segment.
2024         if (tx0 != x0 || ty0 != y0) {
2025             lineTo(tx0, ty0);
2026         }
2027         // We need sin(arc/2), cos(arc/2)
2028         // and possibly sin(arc/4), cos(arc/4) if we need 2 cubic beziers
2029         // We have tan(theta) = tan(tri/2)
2030         // arc = 180-tri
2031         // arc/2 = (180-tri)/2 = 90-(tri/2)
2032         // sin(arc/2) = sin(90-(tri/2)) = cos(tri/2)
2033         // cos(arc/2) = cos(90-(tri/2)) = sin(tri/2)
2034         // 2theta = tri, therefore theta = tri/2
2035         // cos(tri/2)^2 = (1+cos(tri)) / 2.0 = (1+cos_2theta)/2.0
2036         // sin(tri/2)^2 = (1-cos(tri)) / 2.0 = (1-cos_2theta)/2.0
2037         // sin(arc/2) = cos(tri/2) = sqrt((1+cos_2theta)/2.0)
2038         // cos(arc/2) = sin(tri/2) = sqrt((1-cos_2theta)/2.0)
2039         // We compute cos(arc/2) here as we need it in either case below
2040         double coshalfarc = Math.sqrt((1.0 - cos_2theta) / 2.0);
2041         boolean ccw = (ty0 - cy) * (tx1 - cx) > (ty1 - cy) * (tx0 - cx);
2042         // If the arc covers more than 90 degrees then we must use 2
2043         // cubic beziers to get a decent approximation.
2044         // arc = 180-tri
2045         // arc = 180-2*theta
2046         // arc > 90 implies 2*theta < 90
2047         // 2*theta < 90 implies cos_2theta > 0
2048         // So, we need 2 cubics if cos_2theta > 0
2049         if (cos_2theta <= 0.0) {
2050             // 1 cubic bezier
2051             double sinhalfarc = Math.sqrt((1.0 + cos_2theta) / 2.0);
2052             double cv = 4.0 / 3.0 * sinhalfarc / (1.0 + coshalfarc);
2053             if (ccw) cv = -cv;
2054             double cpx0 = tx0 - cv * (ty0 - cy);
2055             double cpy0 = ty0 + cv * (tx0 - cx);
2056             double cpx1 = tx1 + cv * (ty1 - cy);
2057             double cpy1 = ty1 - cv * (tx1 - cx);
2058             bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
2059         } else {
2060             // 2 cubic beziers
2061             // We need sin(arc/4) and cos(arc/4)
2062             // We computed cos(arc/2), so we can compute them as follows:
2063             // sin(arc/4) = sqrt((1 - cos(arc/2)) / 2)
2064             // cos(arc/4) = sart((1 + cos(arc/2)) / 2)
2065             double sinqtrarc = Math.sqrt((1.0 - coshalfarc) / 2.0);
2066             double cosqtrarc = Math.sqrt((1.0 + coshalfarc) / 2.0);
2067             double cv = 4.0 / 3.0 * sinqtrarc / (1.0 + cosqtrarc);
2068             if (ccw) cv = -cv;
2069             double midratio = radius / Math.sqrt(lenratioden);
2070             double midarcx = cx + (x1 - mx) * midratio;
2071             double midarcy = cy + (y1 - my) * midratio;
2072             double cpx0 = tx0 - cv * (ty0 - cy);
2073             double cpy0 = ty0 + cv * (tx0 - cx);
2074             double cpx1 = midarcx + cv * (midarcy - cy);
2075             double cpy1 = midarcy - cv * (midarcx - cx);
2076             bezierCurveTo(cpx0, cpy0, cpx1, cpy1, midarcx, midarcy);
2077             cpx0 = midarcx - cv * (midarcy - cy);
2078             cpy0 = midarcy + cv * (midarcx - cx);
2079             cpx1 = tx1 + cv * (ty1 - cy);
2080             cpy1 = ty1 - cv * (tx1 - cx);
2081             bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
2082         }
2083         return true;
2084     }
2085 
2086     /**
2087      * Adds path elements to the current path to make an arc that uses Euclidean
2088      * degrees. This Euclidean orientation sweeps from East to North, then West,
2089      * then South, then back to East.
2090      * The coordinates are transformed by the current transform as they are
2091      * added to the path and unaffected by subsequent changes to the transform.
2092      * The current path is a <a href="#path-attr">path attribute</a>
2093      * used for any of the path methods as specified in the
2094      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2095      * and <b>is not affected</b> by the {@link #save()} and
2096      * {@link #restore()} operations.
2097      *
2098      * @param centerX the center x position of the arc.
2099      * @param centerY the center y position of the arc.
2100      * @param radiusX the x radius of the arc.
2101      * @param radiusY the y radius of the arc.
2102      * @param startAngle the starting angle of the arc in the range {@code 0-360.0}
2103      * @param length  the length of the baseline of the arc.
2104      */
2105     public void arc(double centerX, double centerY,
2106                     double radiusX, double radiusY,
2107                     double startAngle, double length)
2108     {
2109         Arc2D arc = new Arc2D((float) (centerX - radiusX), // x
2110                               (float) (centerY - radiusY), // y
2111                               (float) (radiusX * 2.0), // w
2112                               (float) (radiusY * 2.0), // h
2113                               (float) startAngle,
2114                               (float) length,
2115                               Arc2D.OPEN);
2116         path.append(arc.getPathIterator(curState.transform), true);
2117         markPathDirty();
2118     }
2119 
2120     /**
2121      * Adds path elements to the current path to make a rectangle.
2122      * The coordinates are transformed by the current transform as they are
2123      * added to the path and unaffected by subsequent changes to the transform.
2124      * The current path is a <a href="#path-attr">path attribute</a>
2125      * used for any of the path methods as specified in the
2126      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2127      * and <b>is not affected</b> by the {@link #save()} and
2128      * {@link #restore()} operations.
2129      *
2130      * @param x x position of the upper left corner of the rectangle.
2131      * @param y y position of the upper left corner of the rectangle.
2132      * @param w width of the rectangle.
2133      * @param h height of the rectangle.
2134      */
2135     public void rect(double x, double y, double w, double h) {
2136         coords[0] = (float) x;
2137         coords[1] = (float) y;
2138         coords[2] = (float) w;
2139         coords[3] = (float) 0;
2140         coords[4] = (float) 0;
2141         coords[5] = (float) h;
2142         curState.transform.deltaTransform(coords, 0, coords, 0, 3);
2143         float x0 = coords[0] + (float) curState.transform.getMxt();
2144         float y0 = coords[1] + (float) curState.transform.getMyt();
2145         float dx1 = coords[2];
2146         float dy1 = coords[3];
2147         float dx2 = coords[4];
2148         float dy2 = coords[5];
2149         path.moveTo(x0, y0);
2150         path.lineTo(x0+dx1, y0+dy1);
2151         path.lineTo(x0+dx1+dx2, y0+dy1+dy2);
2152         path.lineTo(x0+dx2, y0+dy2);
2153         path.closePath();
2154         markPathDirty();
2155 //        path.moveTo(x0, y0); // not needed, closepath leaves pen at moveto
2156     }
2157 
2158     /**
2159      * Appends an SVG Path string to the current path. If there is no current
2160      * path the string must then start with either type of move command.
2161      * A {@code null} value or incorrect SVG path will be ignored.
2162      * The coordinates are transformed by the current transform as they are
2163      * added to the path and unaffected by subsequent changes to the transform.
2164      * The current path is a <a href="#path-attr">path attribute</a>
2165      * used for any of the path methods as specified in the
2166      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2167      * and <b>is not affected</b> by the {@link #save()} and
2168      * {@link #restore()} operations.
2169      *
2170      * @param svgpath the SVG Path string.
2171      */
2172     public void appendSVGPath(String svgpath) {
2173         if (svgpath == null) return;
2174         boolean prependMoveto = true;
2175         boolean skipMoveto = true;
2176         for (int i = 0; i < svgpath.length(); i++) {
2177             switch (svgpath.charAt(i)) {
2178                 case ' ':
2179                 case '\t':
2180                 case '\r':
2181                 case '\n':
2182                     continue;
2183                 case 'M':
2184                     prependMoveto = skipMoveto = false;
2185                     break;
2186                 case 'm':
2187                     if (path.getNumCommands() == 0) {
2188                         // An initial relative moveTo becomes absolute
2189                         prependMoveto = false;
2190                     }
2191                     // Even if we prepend an initial moveTo in the temp
2192                     // path, we do not want to delete the resulting initial
2193                     // moveTo because the relative moveto will be folded
2194                     // into it by an optimization in the Path2D object.
2195                     skipMoveto = false;
2196                     break;
2197             }
2198             break;
2199         }
2200         Path2D p2d = new Path2D();
2201         if (prependMoveto && path.getNumCommands() > 0) {
2202             float x0, y0;
2203             if (curState.transform.isTranslateOrIdentity()) {
2204                 x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
2205                 y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
2206             } else {
2207                 coords[0] = path.getCurrentX();
2208                 coords[1] = path.getCurrentY();
2209                 try {
2210                     curState.transform.inverseTransform(coords, 0, coords, 0, 1);
2211                 } catch (NoninvertibleTransformException e) {
2212                 }
2213                 x0 = coords[0];
2214                 y0 = coords[1];
2215             }
2216             p2d.moveTo(x0, y0);
2217         } else {
2218             skipMoveto = false;
2219         }
2220         try {
2221             p2d.appendSVGPath(svgpath);
2222             PathIterator pi = p2d.getPathIterator(curState.transform);
2223             if (skipMoveto) {
2224                 // We need to delete the initial moveto and let the path
2225                 // extend from the actual existing geometry.
2226                 pi.next();
2227             }
2228             path.append(pi, false);
2229         } catch (IllegalArgumentException | IllegalPathStateException ex) {
2230             //Ignore incorrect path
2231         }
2232     }
2233 
2234     /**
2235      * Closes the path.
2236      * The current path is a <a href="#path-attr">path attribute</a>
2237      * used for any of the path methods as specified in the
2238      * <a href="#attr-ops-table">Rendering Attributes Table</a>
2239      * and <b>is not affected</b> by the {@link #save()} and
2240      * {@link #restore()} operations.
2241      */
2242     public void closePath() {
2243         if (path.getNumCommands() > 0) {
2244             path.closePath();
2245             markPathDirty();
2246         }
2247     }
2248 
2249     /**
2250      * Fills the path with the current fill paint.
2251      * <p>
2252      * This method will be affected by any of the
2253      * <a href="#comm-attr">global common</a>,
2254      * <a href="#fill-attr">fill</a>,
2255      * or <a href="#path-attr">path</a>
2256      * attributes as specified in the
2257      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2258      * Note that the path segments were transformed as they were originally
2259      * added to the current path so the current transform will not affect
2260      * those path segments again, but it may affect other attributes in
2261      * affect at the time of the {@code fill()} operation.
2262      * </p>
2263      */
2264     public void fill() {
2265         writePath(NGCanvas.FILL_PATH);
2266     }
2267 
2268     /**
2269      * Strokes the path with the current stroke paint.
2270      * <p>
2271      * This method will be affected by any of the
2272      * <a href="#comm-attr">global common</a>,
2273      * <a href="#strk-attr">stroke</a>,
2274      * or <a href="#path-attr">path</a>
2275      * attributes as specified in the
2276      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2277      * Note that the path segments were transformed as they were originally
2278      * added to the current path so the current transform will not affect
2279      * those path segments again, but it may affect other attributes in
2280      * affect at the time of the {@code stroke()} operation.
2281      * </p>
2282      */
2283     public void stroke() {
2284         writePath(NGCanvas.STROKE_PATH);
2285     }
2286 
2287     /**
2288      * Intersects the current clip with the current path and applies it to
2289      * subsequent rendering operation as an anti-aliased mask.
2290      * The current clip is a <a href="#comm-attr">common attribute</a>
2291      * used for nearly all rendering operations as specified in the
2292      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2293      * <p>
2294      * This method will itself be affected only by the
2295      * <a href="#path-attr">path</a>
2296      * attributes as specified in the
2297      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2298      * Note that the path segments were transformed as they were originally
2299      * added to the current path so the current transform will not affect
2300      * those path segments again, but it may affect other attributes in
2301      * affect at the time of the {@code stroke()} operation.
2302      * </p>
2303      */
2304     public void clip() {
2305         Path2D clip = new Path2D(path);
2306         clipStack.addLast(clip);
2307         curState.numClipPaths++;
2308         GrowableDataBuffer buf = getBuffer();
2309         buf.putByte(NGCanvas.PUSH_CLIP);
2310         buf.putObject(clip);
2311     }
2312 
2313     /**
2314      * Returns true if the the given x,y point is inside the path.
2315      *
2316      * @param x the X coordinate to use for the check.
2317      * @param y the Y coordinate to use for the check.
2318      * @return true if the point given is inside the path, false
2319      * otherwise.
2320      */
2321     public boolean isPointInPath(double x, double y) {
2322         // TODO: HTML5 considers points on the path to be inside, but we
2323         // implement a halfin-halfout approach...
2324         return path.contains((float) x, (float) y);
2325     }
2326 
2327     /**
2328      * Clears a portion of the canvas with a transparent color value.
2329      * <p>
2330      * This method will be affected only by the current transform, clip,
2331      * and effect.
2332      * </p>
2333      *
2334      * @param x X position of the upper left corner of the rectangle.
2335      * @param y Y position of the upper left corner of the rectangle.
2336      * @param w width of the rectangle.
2337      * @param h height of the rectangle.
2338      */
2339     public void clearRect(double x, double y, double w, double h) {
2340         if (w != 0 && h != 0) {
2341             resetIfCovers(null, x, y, w, h);
2342             writeOp4(x, y, w, h, NGCanvas.CLEAR_RECT);
2343         }
2344     }
2345 
2346     /**
2347      * Fills a rectangle using the current fill paint.
2348      * <p>
2349      * This method will be affected by any of the
2350      * <a href="#comm-attr">global common</a>
2351      * or <a href="#fill-attr">fill</a>
2352      * attributes as specified in the
2353      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2354      * </p>
2355      *
2356      * @param x the X position of the upper left corner of the rectangle.
2357      * @param y the Y position of the upper left corner of the rectangle.
2358      * @param w the width of the rectangle.
2359      * @param h the height of the rectangle.
2360      */
2361     public void fillRect(double x, double y, double w, double h) {
2362         if (w != 0 && h != 0) {
2363             resetIfCovers(this.curState.fill, x, y, w, h);
2364             writeOp4(x, y, w, h, NGCanvas.FILL_RECT);
2365         }
2366     }
2367 
2368     /**
2369      * Strokes a rectangle using the current stroke paint.
2370      * <p>
2371      * This method will be affected by any of the
2372      * <a href="#comm-attr">global common</a>
2373      * or <a href="#strk-attr">stroke</a>
2374      * attributes as specified in the
2375      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2376      * </p>
2377      *
2378      * @param x the X position of the upper left corner of the rectangle.
2379      * @param y the Y position of the upper left corner of the rectangle.
2380      * @param w the width of the rectangle.
2381      * @param h the height of the rectangle.
2382      */
2383     public void strokeRect(double x, double y, double w, double h) {
2384         if (w != 0 || h != 0) {
2385             writeOp4(x, y, w, h, NGCanvas.STROKE_RECT);
2386         }
2387     }
2388 
2389     /**
2390      * Fills an oval using the current fill paint.
2391      * <p>
2392      * This method will be affected by any of the
2393      * <a href="#comm-attr">global common</a>
2394      * or <a href="#fill-attr">fill</a>
2395      * attributes as specified in the
2396      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2397      * </p>
2398      *
2399      * @param x the X coordinate of the upper left bound of the oval.
2400      * @param y the Y coordinate of the upper left bound of the oval.
2401      * @param w the width at the center of the oval.
2402      * @param h the height at the center of the oval.
2403      */
2404     public void fillOval(double x, double y, double w, double h) {
2405         if (w != 0 && h != 0) {
2406             writeOp4(x, y, w, h, NGCanvas.FILL_OVAL);
2407         }
2408     }
2409 
2410     /**
2411      * Strokes an oval using the current stroke paint.
2412      * <p>
2413      * This method will be affected by any of the
2414      * <a href="#comm-attr">global common</a>
2415      * or <a href="#strk-attr">stroke</a>
2416      * attributes as specified in the
2417      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2418      * </p>
2419      *
2420      * @param x the X coordinate of the upper left bound of the oval.
2421      * @param y the Y coordinate of the upper left bound of the oval.
2422      * @param w the width at the center of the oval.
2423      * @param h the height at the center of the oval.
2424      */
2425     public void strokeOval(double x, double y, double w, double h) {
2426         if (w != 0 || h != 0) {
2427             writeOp4(x, y, w, h, NGCanvas.STROKE_OVAL);
2428         }
2429     }
2430 
2431     /**
2432      * Fills an arc using the current fill paint. A {@code null} ArcType or
2433      * non positive width or height will cause the render command to be ignored.
2434      * <p>
2435      * This method will be affected by any of the
2436      * <a href="#comm-attr">global common</a>
2437      * or <a href="#fill-attr">fill</a>
2438      * attributes as specified in the
2439      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2440      * </p>
2441      *
2442      * @param x the X coordinate of the arc.
2443      * @param y the Y coordinate of the arc.
2444      * @param w the width of the arc.
2445      * @param h the height of the arc.
2446      * @param startAngle the starting angle of the arc in degrees.
2447      * @param arcExtent the angular extent of the arc in degrees.
2448      * @param closure closure type (Round, Chord, Open) or null.
2449      */
2450     public void fillArc(double x, double y, double w, double h,
2451                         double startAngle, double arcExtent, ArcType closure)
2452     {
2453         if (w != 0 && h != 0 && closure != null) {
2454             writeArcType(closure);
2455             writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.FILL_ARC);
2456         }
2457     }
2458 
2459     /**
2460      * Strokes an Arc using the current stroke paint. A {@code null} ArcType or
2461      * non positive width or height will cause the render command to be ignored.
2462      * <p>
2463      * This method will be affected by any of the
2464      * <a href="#comm-attr">global common</a>
2465      * or <a href="#strk-attr">stroke</a>
2466      * attributes as specified in the
2467      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2468      * </p>
2469      *
2470      * @param x the X coordinate of the arc.
2471      * @param y the Y coordinate of the arc.
2472      * @param w the width of the arc.
2473      * @param h the height of the arc.
2474      * @param startAngle the starting angle of the arc in degrees.
2475      * @param arcExtent arcExtent the angular extent of the arc in degrees.
2476      * @param closure closure type (Round, Chord, Open) or null
2477      */
2478     public void strokeArc(double x, double y, double w, double h,
2479                         double startAngle, double arcExtent, ArcType closure)
2480     {
2481         if (w != 0 && h != 0 && closure != null) {
2482             writeArcType(closure);
2483             writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.STROKE_ARC);
2484         }
2485     }
2486 
2487     /**
2488      * Fills a rounded rectangle using the current fill paint.
2489      * <p>
2490      * This method will be affected by any of the
2491      * <a href="#comm-attr">global common</a>
2492      * or <a href="#fill-attr">fill</a>
2493      * attributes as specified in the
2494      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2495      * </p>
2496      *
2497      * @param x the X coordinate of the upper left bound of the oval.
2498      * @param y the Y coordinate of the upper left bound of the oval.
2499      * @param w the width at the center of the oval.
2500      * @param h the height at the center of the oval.
2501      * @param arcWidth the arc width of the rectangle corners.
2502      * @param arcHeight the arc height of the rectangle corners.
2503      */
2504     public void fillRoundRect(double x, double y, double w, double h,
2505                               double arcWidth, double arcHeight)
2506     {
2507         if (w != 0 && h != 0) {
2508             writeOp6(x, y, w, h, arcWidth, arcHeight, NGCanvas.FILL_ROUND_RECT);
2509         }
2510     }
2511 
2512     /**
2513      * Strokes a rounded rectangle using the current stroke paint.
2514      * <p>
2515      * This method will be affected by any of the
2516      * <a href="#comm-attr">global common</a>
2517      * or <a href="#strk-attr">stroke</a>
2518      * attributes as specified in the
2519      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2520      * </p>
2521      *
2522      * @param x the X coordinate of the upper left bound of the oval.
2523      * @param y the Y coordinate of the upper left bound of the oval.
2524      * @param w the width at the center of the oval.
2525      * @param h the height at the center of the oval.
2526      * @param arcWidth the arc width of the rectangle corners.
2527      * @param arcHeight the arc height of the rectangle corners.
2528      */
2529     public void strokeRoundRect(double x, double y, double w, double h,
2530                               double arcWidth, double arcHeight)
2531     {
2532         if (w != 0 && h != 0) {
2533             writeOp6(x, y, w, h, arcWidth, arcHeight, NGCanvas.STROKE_ROUND_RECT);
2534         }
2535     }
2536 
2537     /**
2538      * Strokes a line using the current stroke paint.
2539      * <p>
2540      * This method will be affected by any of the
2541      * <a href="#comm-attr">global common</a>
2542      * or <a href="#strk-attr">stroke</a>
2543      * attributes as specified in the
2544      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2545      * </p>
2546      *
2547      * @param x1 the X coordinate of the starting point of the line.
2548      * @param y1 the Y coordinate of the starting point of the line.
2549      * @param x2 the X coordinate of the ending point of the line.
2550      * @param y2 the Y coordinate of the ending point of the line.
2551      */
2552     public void strokeLine(double x1, double y1, double x2, double y2) {
2553         writeOp4(x1, y1, x2, y2, NGCanvas.STROKE_LINE);
2554     }
2555 
2556     /**
2557      * Fills a polygon with the given points using the currently set fill paint.
2558      * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
2559      * <p>
2560      * This method will be affected by any of the
2561      * <a href="#comm-attr">global common</a>,
2562      * <a href="#fill-attr">fill</a>,
2563      * or <a href="#path-attr">Fill Rule</a>
2564      * attributes as specified in the
2565      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2566      * </p>
2567      *
2568      * @param xPoints array containing the x coordinates of the polygon's points or null.
2569      * @param yPoints array containing the y coordinates of the polygon's points or null.
2570      * @param nPoints the number of points that make the polygon.
2571      */
2572     public void fillPolygon(double xPoints[], double yPoints[], int nPoints) {
2573         if (nPoints >= 3) {
2574             writePoly(xPoints, yPoints, nPoints, true, NGCanvas.FILL_PATH);
2575         }
2576     }
2577 
2578     /**
2579      * Strokes a polygon with the given points using the currently set stroke paint.
2580      * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
2581      * <p>
2582      * This method will be affected by any of the
2583      * <a href="#comm-attr">global common</a>
2584      * or <a href="#strk-attr">stroke</a>
2585      * attributes as specified in the
2586      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2587      * </p>
2588      *
2589      * @param xPoints array containing the x coordinates of the polygon's points or null.
2590      * @param yPoints array containing the y coordinates of the polygon's points or null.
2591      * @param nPoints the number of points that make the polygon.
2592      */
2593     public void strokePolygon(double xPoints[], double yPoints[], int nPoints) {
2594         if (nPoints >= 2) {
2595             writePoly(xPoints, yPoints, nPoints, true, NGCanvas.STROKE_PATH);
2596         }
2597     }
2598 
2599     /**
2600      * Strokes a polyline with the given points using the currently set stroke
2601      * paint attribute.
2602      * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
2603      * <p>
2604      * This method will be affected by any of the
2605      * <a href="#comm-attr">global common</a>
2606      * or <a href="#strk-attr">stroke</a>
2607      * attributes as specified in the
2608      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2609      * </p>
2610      *
2611      * @param xPoints array containing the x coordinates of the polyline's points or null.
2612      * @param yPoints array containing the y coordinates of the polyline's points or null.
2613      * @param nPoints the number of points that make the polyline.
2614      */
2615     public void strokePolyline(double xPoints[], double yPoints[], int nPoints) {
2616         if (nPoints >= 2) {
2617             writePoly(xPoints, yPoints, nPoints, false, NGCanvas.STROKE_PATH);
2618         }
2619     }
2620 
2621     /**
2622      * Draws an image at the given x, y position using the width
2623      * and height of the given image.
2624      * A {@code null} image value or an image still in progress will be ignored.
2625      * <p>
2626      * This method will be affected by any of the
2627      * <a href="#comm-attr">global common</a>
2628      * attributes as specified in the
2629      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2630      * </p>
2631      *
2632      * @param img the image to be drawn or null.
2633      * @param x the X coordinate on the destination for the upper left of the image.
2634      * @param y the Y coordinate on the destination for the upper left of the image.
2635      */
2636     public void drawImage(Image img, double x, double y) {
2637         if (img == null) return;
2638         double sw = img.getWidth();
2639         double sh = img.getHeight();
2640         writeImage(img, x, y, sw, sh);
2641     }
2642 
2643     /**
2644      * Draws an image into the given destination rectangle of the canvas. The
2645      * Image is scaled to fit into the destination rectagnle.
2646      * A {@code null} image value or an image still in progress will be ignored.
2647      * <p>
2648      * This method will be affected by any of the
2649      * <a href="#comm-attr">global common</a>
2650      * attributes as specified in the
2651      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2652      * </p>
2653      *
2654      * @param img the image to be drawn or null.
2655      * @param x the X coordinate on the destination for the upper left of the image.
2656      * @param y the Y coordinate on the destination for the upper left of the image.
2657      * @param w the width of the destination rectangle.
2658      * @param h the height of the destination rectangle.
2659      */
2660     public void drawImage(Image img, double x, double y, double w, double h) {
2661         writeImage(img, x, y, w, h);
2662     }
2663 
2664     /**
2665      * Draws the specified source rectangle of the given image to the given
2666      * destination rectangle of the Canvas.
2667      * A {@code null} image value or an image still in progress will be ignored.
2668      * <p>
2669      * This method will be affected by any of the
2670      * <a href="#comm-attr">global common</a>
2671      * attributes as specified in the
2672      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2673      * </p>
2674      *
2675      * @param img the image to be drawn or null.
2676      * @param sx the source rectangle's X coordinate position.
2677      * @param sy the source rectangle's Y coordinate position.
2678      * @param sw the source rectangle's width.
2679      * @param sh the source rectangle's height.
2680      * @param dx the destination rectangle's X coordinate position.
2681      * @param dy the destination rectangle's Y coordinate position.
2682      * @param dw the destination rectangle's width.
2683      * @param dh the destination rectangle's height.
2684      */
2685     public void drawImage(Image img,
2686                           double sx, double sy, double sw, double sh,
2687                           double dx, double dy, double dw, double dh)
2688     {
2689         writeImage(img, dx, dy, dw, dh, sx, sy, sw, sh);
2690     }
2691 
2692     private PixelWriter writer;
2693     /**
2694      * Returns a {@link PixelWriter} object that can be used to modify
2695      * the pixels of the {@link Canvas} associated with this
2696      * {@code GraphicsContext}.
2697      * All coordinates in the {@code PixelWriter} methods on the returned
2698      * object will be in device space since they refer directly to pixels
2699      * and no other rendering attributes will be applied when modifying
2700      * pixels using this object.
2701      *
2702      * @return the {@code PixelWriter} for modifying the pixels of this
2703      *         {@code Canvas}
2704      */
2705     public PixelWriter getPixelWriter() {
2706         if (writer == null) {
2707             writer = new PixelWriter() {
2708                 @Override
2709                 public PixelFormat<ByteBuffer> getPixelFormat() {
2710                     return PixelFormat.getByteBgraPreInstance();
2711                 }
2712 
2713                 private BytePixelSetter getSetter() {
2714                     return ByteBgraPre.setter;
2715                 }
2716 
2717                 @Override
2718                 public void setArgb(int x, int y, int argb) {
2719                     GrowableDataBuffer buf = getBuffer();
2720                     buf.putByte(NGCanvas.PUT_ARGB);
2721                     buf.putInt(x);
2722                     buf.putInt(y);
2723                     buf.putInt(argb);
2724                 }
2725 
2726                 @Override
2727                 public void setColor(int x, int y, Color c) {
2728                     if (c == null) throw new NullPointerException("Color cannot be null");
2729                     int a = (int) Math.round(c.getOpacity() * 255.0);
2730                     int r = (int) Math.round(c.getRed() * 255.0);
2731                     int g = (int) Math.round(c.getGreen() * 255.0);
2732                     int b = (int) Math.round(c.getBlue() * 255.0);
2733                     setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b);
2734                 }
2735 
2736                 private void writePixelBuffer(int x, int y, int w, int h,
2737                                               byte[] pixels)
2738                 {
2739                     GrowableDataBuffer buf = getBuffer();
2740                     buf.putByte(NGCanvas.PUT_ARGBPRE_BUF);
2741                     buf.putInt(x);
2742                     buf.putInt(y);
2743                     buf.putInt(w);
2744                     buf.putInt(h);
2745                     buf.putObject(pixels);
2746                 }
2747 
2748                 private int[] checkBounds(int x, int y, int w, int h,
2749                                           PixelFormat<? extends Buffer> pf,
2750                                           int scan)
2751                 {
2752                     // assert (w >= 0 && h >= 0) - checked by caller
2753                     int cw = (int) Math.ceil(theCanvas.getWidth());
2754                     int ch = (int) Math.ceil(theCanvas.getHeight());
2755                     if (x >= 0 && y >= 0 && x+w <= cw && y+h <= ch) {
2756                         return null;
2757                     }
2758                     int offset = 0;
2759                     if (x < 0) {
2760                         w += x;
2761                         if (w < 0) return null;
2762                         if (pf != null) {
2763                             switch (pf.getType()) {
2764                                 case BYTE_BGRA:
2765                                 case BYTE_BGRA_PRE:
2766                                     offset -= x * 4;
2767                                     break;
2768                                 case BYTE_RGB:
2769                                     offset -= x * 3;
2770                                     break;
2771                                 case BYTE_INDEXED:
2772                                 case INT_ARGB:
2773                                 case INT_ARGB_PRE:
2774                                     offset -= x;
2775                                     break;
2776                                 default:
2777                                     throw new InternalError("unknown Pixel Format");
2778                             }
2779                         }
2780                         x = 0;
2781                     }
2782                     if (y < 0) {
2783                         h += y;
2784                         if (h < 0) return null;
2785                         offset -= y * scan;
2786                         y = 0;
2787                     }
2788                     if (x + w > cw) {
2789                         w = cw - x;
2790                         if (w < 0) return null;
2791                     }
2792                     if (y + h > ch) {
2793                         h = ch - y;
2794                         if (h < 0) return null;
2795                     }
2796                     return new int[] {
2797                         x, y, w, h, offset
2798                     };
2799                 }
2800 
2801                 @Override
2802                 public <T extends Buffer> void
2803                     setPixels(int x, int y, int w, int h,
2804                               PixelFormat<T> pixelformat,
2805                               T buffer, int scan)
2806                 {
2807                     if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
2808                     if (buffer == null) throw new NullPointerException("Buffer cannot be null");
2809                     if (w <= 0 || h <= 0) return;
2810                     int offset = buffer.position();
2811                     int adjustments[] = checkBounds(x, y, w, h,
2812                                                     pixelformat, scan);
2813                     if (adjustments != null) {
2814                         x = adjustments[0];
2815                         y = adjustments[1];
2816                         w = adjustments[2];
2817                         h = adjustments[3];
2818                         offset += adjustments[4];
2819                     }
2820 
2821                     byte pixels[] = new byte[w * h * 4];
2822                     ByteBuffer dst = ByteBuffer.wrap(pixels);
2823 
2824                     PixelGetter<T> getter = PixelUtils.getGetter(pixelformat);
2825                     PixelConverter<T, ByteBuffer> converter =
2826                         PixelUtils.getConverter(getter, getSetter());
2827                     converter.convert(buffer, offset, scan,
2828                                       dst, 0, w * 4,
2829                                       w, h);
2830                     writePixelBuffer(x, y, w, h, pixels);
2831                 }
2832 
2833                 @Override
2834                 public void setPixels(int x, int y, int w, int h,
2835                                       PixelFormat<ByteBuffer> pixelformat,
2836                                       byte[] buffer, int offset, int scanlineStride)
2837                 {
2838                     if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
2839                     if (buffer == null) throw new NullPointerException("Buffer cannot be null");
2840                     if (w <= 0 || h <= 0) return;
2841                     int adjustments[] = checkBounds(x, y, w, h,
2842                                                     pixelformat, scanlineStride);
2843                     if (adjustments != null) {
2844                         x = adjustments[0];
2845                         y = adjustments[1];
2846                         w = adjustments[2];
2847                         h = adjustments[3];
2848                         offset += adjustments[4];
2849                     }
2850 
2851                     byte pixels[] = new byte[w * h * 4];
2852 
2853                     BytePixelGetter getter = PixelUtils.getByteGetter(pixelformat);
2854                     ByteToBytePixelConverter converter =
2855                         PixelUtils.getB2BConverter(getter, getSetter());
2856                     converter.convert(buffer, offset, scanlineStride,
2857                                       pixels, 0, w * 4,
2858                                       w, h);
2859                     writePixelBuffer(x, y, w, h, pixels);
2860                 }
2861 
2862                 @Override
2863                 public void setPixels(int x, int y, int w, int h,
2864                                       PixelFormat<IntBuffer> pixelformat,
2865                                       int[] buffer, int offset, int scanlineStride)
2866                 {
2867                     if (pixelformat == null) throw new NullPointerException("PixelFormat cannot be null");
2868                     if (buffer == null) throw new NullPointerException("Buffer cannot be null");
2869                     if (w <= 0 || h <= 0) return;
2870                     int adjustments[] = checkBounds(x, y, w, h,
2871                                                     pixelformat, scanlineStride);
2872                     if (adjustments != null) {
2873                         x = adjustments[0];
2874                         y = adjustments[1];
2875                         w = adjustments[2];
2876                         h = adjustments[3];
2877                         offset += adjustments[4];
2878                     }
2879 
2880                     byte pixels[] = new byte[w * h * 4];
2881 
2882                     IntPixelGetter getter = PixelUtils.getIntGetter(pixelformat);
2883                     IntToBytePixelConverter converter =
2884                         PixelUtils.getI2BConverter(getter, getSetter());
2885                     converter.convert(buffer, offset, scanlineStride,
2886                                       pixels, 0, w * 4,
2887                                       w, h);
2888                     writePixelBuffer(x, y, w, h, pixels);
2889                 }
2890 
2891                 @Override
2892                 public void setPixels(int dstx, int dsty, int w, int h,
2893                                       PixelReader reader, int srcx, int srcy)
2894                 {
2895                     if (reader == null) throw new NullPointerException("Reader cannot be null");
2896                     if (w <= 0 || h <= 0) return;
2897                     int adjustments[] = checkBounds(dstx, dsty, w, h, null, 0);
2898                     if (adjustments != null) {
2899                         int newx = adjustments[0];
2900                         int newy = adjustments[1];
2901                         srcx += newx - dstx;
2902                         srcy += newy - dsty;
2903                         dstx = newx;
2904                         dsty = newy;
2905                         w = adjustments[2];
2906                         h = adjustments[3];
2907                     }
2908 
2909                     byte pixels[] = new byte[w * h * 4];
2910                     reader.getPixels(srcx, srcy, w, h,
2911                                      PixelFormat.getByteBgraPreInstance(),
2912                                      pixels, 0, w * 4);
2913                     writePixelBuffer(dstx, dsty, w, h, pixels);
2914                 }
2915             };
2916         }
2917         return writer;
2918     }
2919 
2920     /**
2921      * Sets the effect to be applied after the next draw call, or null to
2922      * disable effects.
2923      * The current effect is a <a href="#comm-attr">common attribute</a>
2924      * used for nearly all rendering operations as specified in the
2925      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2926      *
2927      * @param e the effect to use, or null to disable effects
2928      */
2929     public void setEffect(Effect e) {
2930         GrowableDataBuffer buf = getBuffer();
2931         buf.putByte(NGCanvas.EFFECT);
2932         if (e == null) {
2933             curState.effect = null;
2934             buf.putObject(null);
2935         } else {
2936             curState.effect = EffectHelper.copy(e);
2937             EffectHelper.sync(curState.effect);
2938             buf.putObject(EffectHelper.getPeer(curState.effect));
2939         }
2940     }
2941 
2942     /**
2943      * Gets a copy of the effect to be applied after the next draw call.
2944      * A null return value means that no effect will be applied after subsequent
2945      * rendering calls.
2946      * The current effect is a <a href="#comm-attr">common attribute</a>
2947      * used for nearly all rendering operations as specified in the
2948      * <a href="#attr-ops-table">Rendering Attributes Table</a>.
2949      *
2950      * @param e an {@code Effect} object that may be used to store the
2951      *        copy of the current effect, if it is of a compatible type
2952      * @return the current effect used for all rendering calls,
2953      *         or null if there is no current effect
2954      */
2955     public Effect getEffect(Effect e) {
2956         return curState.effect == null ? null : EffectHelper.copy(curState.effect);
2957     }
2958 
2959     /**
2960      * Applies the given effect to the entire bounds of the canvas and stores
2961      * the result back into the same canvas.
2962      * A {@code null} value will be ignored.
2963      * The effect will be applied without any other rendering attributes and
2964      * under an Identity coordinate transform.
2965      * Since the effect is applied to the entire bounds of the canvas, some
2966      * effects may have a confusing result, such as a Reflection effect
2967      * that will apply its reflection off of the bottom of the canvas even if
2968      * only a portion of the canvas has been rendered to and will not be
2969      * visible unless a negative offset is used to bring the reflection back
2970      * into view.
2971      *
2972      * @param e the effect to apply onto the entire destination or null.
2973      */
2974     public void applyEffect(Effect e) {
2975         if (e == null) return;
2976         GrowableDataBuffer buf = getBuffer();
2977         buf.putByte(NGCanvas.FX_APPLY_EFFECT);
2978         Effect effect = EffectHelper.copy(e);
2979         EffectHelper.sync(effect);
2980         buf.putObject(EffectHelper.getPeer(effect));
2981     }
2982 }