    001/* ===================================================
    002 * JFreeSVG : an SVG library for the Java(tm) platform
    003 * ===================================================
    004 *
    005 * (C)opyright 2013, 2014, by Object Refinery Limited.  All rights reserved.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreesvg/index.html
    008 *
    009 * This program is free software: you can redistribute it and/or modify
    010 * it under the terms of the GNU General Public License as published by
    011 * the Free Software Foundation, either version 3 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This program is distributed in the hope that it will be useful,
    015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
    016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    017 * GNU General Public License for more details.
    018 *
    019 * You should have received a copy of the GNU General Public License
    020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    021 *
    022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    023 * Other names may be trademarks of their respective owners.]
    024 *
    025 * If you do not wish to be bound by the terms of the GPL, an alternative
    026 * commercial license can be purchased.  For details, please see visit the
    027 * JFreeSVG home page:
    028 *
    029 * http://www.jfree.org/jfreesvg
    030 *
    031 */
    032
    033package org.jfree.graphics2d.canvas;
    034
    035import java.awt.AlphaComposite;
    036import java.awt.BasicStroke;
    037import java.awt.Color;
    038import java.awt.Composite;
    039import java.awt.Font;
    040import java.awt.FontMetrics;
    041import java.awt.GradientPaint;
    042import java.awt.Graphics;
    043import java.awt.Graphics2D;
    044import java.awt.GraphicsConfiguration;
    045import java.awt.Image;
    046import java.awt.Paint;
    047import java.awt.Rectangle;
    048import java.awt.RenderingHints;
    049import java.awt.Shape;
    050import java.awt.Stroke;
    051import java.awt.font.FontRenderContext;
    052import java.awt.font.GlyphVector;
    053import java.awt.font.TextLayout;
    054import java.awt.geom.AffineTransform;
    055import java.awt.geom.Arc2D;
    056import java.awt.geom.Area;
    057import java.awt.geom.Ellipse2D;
    058import java.awt.geom.GeneralPath;
    059import java.awt.geom.Line2D;
    060import java.awt.geom.NoninvertibleTransformException;
    061import java.awt.geom.Path2D;
    062import java.awt.geom.PathIterator;
    063import java.awt.geom.Point2D;
    064import java.awt.geom.Rectangle2D;
    065import java.awt.geom.RoundRectangle2D;
    066import java.awt.image.BufferedImage;
    067import java.awt.image.BufferedImageOp;
    068import java.awt.image.ImageObserver;
    069import java.awt.image.RenderedImage;
    070import java.awt.image.renderable.RenderableImage;
    071import java.text.AttributedCharacterIterator;
    072import java.text.DecimalFormat;
    073import java.text.DecimalFormatSymbols;
    074import java.util.Map;
    075import org.jfree.graphics2d.Args;
    076import org.jfree.graphics2d.GraphicsUtils;
    077
    078/**
    079 * A <code>Graphics2D</code> implementation that writes out JavaScript code
    080 * that will draw to an HTML5 Canvas.
    081 * <p>
    082 * Implementation notes:
    083 * <ul>
    084 * <li>all rendering hints are ignored;</li>
    085 * <li>images are not yet supported;</li>
    086 * <li>the <code>drawString()</code> methods that work with an
    087 * <code>AttributedCharacterIterator</code> currently ignore the formatting
    088 * information.</li>
    089 * </ul>
    090 * <p>
    091 * For some demos of the use of this class, please look in the
    092 * <code>org.jfree.graphics2d.demo</code> package in the <code>src</code>
    093 * directory.
    094 */
    095public final class CanvasGraphics2D extends Graphics2D {
    096
    097    /** The canvas ID. */
    098    private String canvasID;
    099
    100    /** The buffer for all the Javascript output. */
    101    private StringBuilder sb;
    102
    103    /** Rendering hints (all ignored). */
    104    private RenderingHints hints;
    105
    106    private Shape clip;;
    107
    108    private Paint paint = Color.BLACK;
    109
    110    private Color color = Color.BLACK;
    111
    112    private Composite composite = AlphaComposite.getInstance(
    113            AlphaComposite.SRC_OVER, 1.0f);
    114
    115    private Stroke stroke = new BasicStroke(1.0f);
    116
    117    private Font font = new Font("SansSerif", Font.PLAIN, 12);
    118
    119    private AffineTransform transform = new AffineTransform();
    120
    121    /** The background color, presently ignored. */
    122    private Color background = Color.BLACK;
    123
    124    /** A hidden image used for font metrics. */
    125    private BufferedImage image = new BufferedImage(10, 10,
    126            BufferedImage.TYPE_INT_RGB);;
    127
    128    /**
    129     * An instance that is lazily instantiated in drawLine and then
    130     * subsequently reused to avoid creating a lot of garbage.
    131     */
    132    private Line2D line;
    133
    134    /**
    135     * An instance that is lazily instantiated in fillRect and then
    136     * subsequently reused to avoid creating a lot of garbage.
    137     */
    138    Rectangle2D rect;
    139
    140    /**
    141     * An instance that is lazily instantiated in draw/fillRoundRect and then
    142     * subsequently reused to avoid creating a lot of garbage.
    143     */
    144    private RoundRectangle2D roundRect;
    145
    146     /**
    147     * An instance that is lazily instantiated in draw/fillOval and then
    148     * subsequently reused to avoid creating a lot of garbage.
    149     */
    150   private Ellipse2D oval;
    151
    152    /**
    153     * An instance that is lazily instantiated in draw/fillArc and then
    154     * subsequently reused to avoid creating a lot of garbage.
    155     */
    156    private Arc2D arc;
    157
    158    /**
    159     * The number of decimal places to use when writing the matrix values
    160     * for transformations.
    161     */
    162    private int transformDP;
    163
    164    /**
    165     * The decimal formatter for transform matrices.
    166     */
    167    private DecimalFormat transformFormat;
    168
    169    /**
    170     * The number of decimal places to use when writing coordinates for
    171     * geometrical shapes.
    172     */
    173    private int geometryDP;
    174
    175    /**
    176     * The decimal formatter for coordinates of geometrical shapes.
    177     */
    178    private DecimalFormat geometryFormat = new DecimalFormat("0.##");
    179
    180    /**
    181     * Creates a new instance.  The canvas ID is stored but not used in the
    182     * current implementation.
    183     *
    184     * @param canvasID  the canvas ID.
    185     */
    186    public CanvasGraphics2D(String canvasID) {
    187        this.canvasID = canvasID;
    188        this.sb = new StringBuilder();
    189        this.hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
    190                RenderingHints.VALUE_ANTIALIAS_ON);
    191        this.clip = null;
    192        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
    193        dfs.setDecimalSeparator('.');
    194        this.transformFormat = new DecimalFormat("0.######", dfs);
    195        this.geometryFormat = new DecimalFormat("0.##", dfs);
    196    }
    197
    198    /**
    199     * Returns the canvas ID that was passed to the constructor.
    200     *
    201     * @return The canvas ID.
    202     */
    203    public String getCanvasID() {
    204        return this.canvasID;
    205    }
    206
    207    /**
    208     * Returns the number of decimal places used to write the transformation
    209     * matrices in the Javascript output.  The default value is 6.
    210     * <p>
    211     * Note that there is a separate attribute to control the number of decimal
    212     * places for geometrical elements in the output (see
    213     * {@link #getGeometryDP()}).
    214     *
    215     * @return The number of decimal places.
    216     *
    217     * @see #setTransformDP(int)
    218     */
    219    public int getTransformDP() {
    220        return this.transformDP;
    221    }
    222
    223    /**
    224     * Sets the number of decimal places used to write the transformation
    225     * matrices in the Javascript output.  Values in the range 1 to 10 will be
    226     * used to configure a formatter to that number of decimal places, for all
    227     * other values we revert to the normal <code>String</code> conversion of
    228     * <code>double</code> primitives (approximately 16 decimals places).
    229     * <p>
    230     * Note that there is a separate attribute to control the number of decimal
    231     * places for geometrical elements in the output (see
    232     * {@link #setGeometryDP(int)}).
    233     *
    234     * @param dp  the number of decimal places (normally 1 to 10).
    235     *
    236     * @see #getTransformDP()
    237     */
    238    public void setTransformDP(int dp) {
    239        this.transformDP = dp;
    240        if (dp < 1 || dp > 10) {
    241            this.transformFormat = null;
    242            return;
    243        }
    244        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
    245        dfs.setDecimalSeparator('.');
    246        this.transformFormat = new DecimalFormat("0."
    247                + "##########".substring(0, dp), dfs);
    248    }
    249
    250    /**
    251     * Returns the number of decimal places used to write the coordinates
    252     * of geometrical shapes.  The default value is 2.
    253     * <p>
    254     * Note that there is a separate attribute to control the number of decimal
    255     * places for transform matrices in the output (see
    256     * {@link #getTransformDP()}).
    257     *
    258     * @return The number of decimal places.
    259     */
    260    public int getGeometryDP() {
    261        return this.geometryDP;
    262    }
    263
    264    /**
    265     * Sets the number of decimal places used to write the coordinates of
    266     * geometrical shapes in the Javascript output.  Values in the range 1 to 10
    267     * will be used to configure a formatter to that number of decimal places,
    268     * for all other values we revert to the normal String conversion of double
    269     * primitives (approximately 16 decimals places).
    270     * <p>
    271     * Note that there is a separate attribute to control the number of decimal
    272     * places for transform matrices in the output (see
    273     * {@link #setTransformDP(int)}).
    274     *
    275     * @param dp  the number of decimal places (normally 1 to 10).
    276     */
    277    public void setGeometryDP(int dp) {
    278        this.geometryDP = dp;
    279        if (dp < 1 || dp > 10) {
    280            this.geometryFormat = null;
    281            return;
    282        }
    283        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
    284        dfs.setDecimalSeparator('.');
    285        this.geometryFormat = new DecimalFormat("0."
    286                + "##########".substring(0, dp), dfs);
    287    }
    288
    289    /**
    290     * Not yet implemented.
    291     *
    292     * @return The graphics configuration.
    293     */
    294    @Override
    295    public GraphicsConfiguration getDeviceConfiguration() {
    296        throw new UnsupportedOperationException("Not supported yet."); //TODO
    297    }
    298
    299    /**
    300     * Creates a new graphics object that is a copy of this graphics object.
    301     *
    302     * @return A new graphics object.
    303     */
    304    @Override
    305    public Graphics create() {
    306        CanvasGraphics2D copy = new CanvasGraphics2D(this.canvasID);
    307        copy.setRenderingHints(getRenderingHints());
    308        copy.setClip(getClip());
    309        copy.setPaint(getPaint());
    310        copy.setColor(getColor());
    311        copy.setComposite(getComposite());
    312        copy.setStroke(getStroke());
    313        copy.setFont(getFont());
    314        copy.setTransform(getTransform());
    315        copy.setBackground(getBackground());
    316        return copy;
    317    }
    318
    319    /**
    320     * Returns the paint used to draw or fill shapes (or text).  The default
    321     * value is {@link Color#BLACK}.
    322     *
    323     * @return The paint (never <code>null</code>).
    324     *
    325     * @see #setPaint(java.awt.Paint)
    326     */
    327    @Override
    328    public Paint getPaint() {
    329        return this.paint;
    330    }
    331
    332    /**
    333     * Sets the paint used to draw or fill shapes (or text).  If
    334     * <code>paint</code> is an instance of <code>Color</code>, this method will
    335     * also update the current color attribute (see {@link #getColor()}). If
    336     * you pass <code>null</code> to this method, it does nothing (in
    337     * accordance with the JDK specification).
    338     *
    339     * @param paint  the paint (<code>null</code> is permitted but ignored).
    340     *
    341     * @see #getPaint()
    342     */
    343    @Override
    344    public void setPaint(Paint paint) {
    345        if (paint == null) {
    346            return;
    347        }
    348        this.paint = paint;
    349        if (paint instanceof Color) {
    350            setColor((Color) paint);
    351        } else if (paint instanceof GradientPaint) {
    352            GradientPaint gp = (GradientPaint) paint;
    353            Point2D p1 = gp.getPoint1();
    354            Point2D p2 = gp.getPoint2();
    355            this.sb.append("var g = ctx.createLinearGradient(")
    356                    .append(geomDP(p1.getX())).append(",")
    357                    .append(geomDP(p1.getY())).append(",")
    358                    .append(geomDP(p2.getX())).append(",")
    359                    .append(geomDP(p2.getY())).append(");");
    360            this.sb.append("g.addColorStop(0,'").append(
    361                    toCSSColorValue(gp.getColor1())).append("');");
    362            this.sb.append("g.addColorStop(1,'").append(
    363                    toCSSColorValue(gp.getColor2())).append("');");
    364            this.sb.append("ctx.fillStyle=g;");
    365        } else {
    366            System.err.println("setPaint(" + paint + ")");
    367        }
    368    }
    369
    370    /**
    371     * Returns the foreground color.  This method exists for backwards
    372     * compatibility in AWT, you should use the {@link #getPaint()} method.
    373     *
    374     * @return The foreground color (never <code>null</code>).
    375     *
    376     * @see #getPaint()
    377     */
    378    @Override
    379    public Color getColor() {
    380        return this.color;
    381    }
    382
    383    /**
    384     * Sets the foreground color.  This method exists for backwards
    385     * compatibility in AWT, you should use the
    386     * {@link #setPaint(java.awt.Paint)} method.
    387     *
    388     * @param c  the color (<code>null</code> permitted but ignored).
    389     *
    390     * @see #setPaint(java.awt.Paint)
    391     */
    392    @Override
    393    public void setColor(Color c) {
    394        if (c == null || this.color.equals(c)) {
    395            return;  // nothing to do
    396        }
    397        this.color = c;
    398        this.paint = c;
    399        String cssColor = toCSSColorValue(c);
    400        // TODO: we could avoid writing both of these by tracking dirty
    401        // flags and only writing the appropriate style when required
    402        this.sb.append("ctx.fillStyle=\"").append(cssColor).append("\";");
    403        this.sb.append("ctx.strokeStyle=\"").append(cssColor).append("\";");
    404    }
    405
    406    /**
    407     * A utility method that translates a Color object to a CSS color string.
    408     *
    409     * @param c  the color (<code>null</code> not permitted).
    410     *
    411     * @return The CSS string for the color specification.
    412     */
    413    private String toCSSColorValue(Color c) {
    414        return "rgba(" + c.getRed() + "," + c.getGreen() + "," + c.getBlue()
    415                + "," + c.getAlpha() / 255.0f + ")";
    416    }
    417
    418    /**
    419     * Returns the background color.  The default value is {@link Color#BLACK}.
    420     * This is used by the {@link #clearRect(int, int, int, int)} method.
    421     *
    422     * @return The background color (possibly <code>null</code>).
    423     *
    424     * @see #setBackground(java.awt.Color)
    425     */
    426    @Override
    427    public Color getBackground() {
    428        return this.background;
    429    }
    430
    431    /**
    432     * Sets the background color.  This is used by the
    433     * {@link #clearRect(int, int, int, int)} method.  The reference
    434     * implementation allows <code>null</code> for the background color so
    435     * we allow that too (but for that case, the clearRect method will do
    436     * nothing).
    437     *
    438     * @param color  the color (<code>null</code> permitted).
    439     *
    440     * @see #getBackground()
    441     */
    442    @Override
    443    public void setBackground(Color color) {
    444        this.background = color;
    445    }
    446
    447    /**
    448     * Returns the current composite.
    449     *
    450     * @return The current composite (never <code>null</code>).
    451     *
    452     * @see #setComposite(java.awt.Composite)
    453     */
    454    @Override
    455    public Composite getComposite() {
    456        return this.composite;
    457    }
    458
    459    /**
    460     * Sets the composite (only <code>AlphaComposite</code> is handled).
    461     *
    462     * @param comp  the composite (<code>null</code> not permitted).
    463     *
    464     * @see #getComposite()
    465     */
    466    @Override
    467    public void setComposite(Composite comp) {
    468        Args.nullNotPermitted(comp, "comp");
    469        this.composite = comp;
    470        if (comp instanceof AlphaComposite) {
    471            AlphaComposite ac = (AlphaComposite) comp;
    472            sb.append("ctx.globalAlpha=").append(ac.getAlpha()).append(";");
    473            sb.append("ctx.globalCompositeOperation=\"").append(
    474                    toJSCompositeRuleName(ac.getRule())).append("\";");
    475        }
    476    }
    477
    478    private String toJSCompositeRuleName(int rule) {
    479        switch (rule) {
    480            case AlphaComposite.CLEAR:
    481                return "xor";
    482            case AlphaComposite.SRC_IN:
    483                return "source-in";
    484            case AlphaComposite.SRC_OUT:
    485                return "source-out";
    486            case AlphaComposite.SRC_OVER:
    487                return "source-over";
    488            case AlphaComposite.SRC_ATOP:
    489                return "source-atop";
    490            case AlphaComposite.DST_IN:
    491                return "destination-in";
    492            case AlphaComposite.DST_OUT:
    493                return "destination-out";
    494            case AlphaComposite.DST_OVER:
    495                return "destination-over";
    496            case AlphaComposite.DST_ATOP:
    497                return "destination-atop";
    498            default:
    499                throw new IllegalArgumentException("Unknown/unhandled 'rule' "
    500                        + rule);
    501        }
    502    }
    503
    504    /**
    505     * Returns the current stroke (used when drawing shapes).
    506     *
    507     * @return The current stroke (never <code>null</code>).
    508     *
    509     * @see #setStroke(java.awt.Stroke)
    510     */
    511    @Override
    512    public Stroke getStroke() {
    513        return this.stroke;
    514    }
    515
    516    /**
    517     * Sets the stroke that will be used to draw shapes.  Only
    518     * <code>BasicStroke</code> is supported.
    519     *
    520     * @param s  the stroke (<code>null</code> not permitted).
    521     *
    522     * @see #getStroke()
    523     */
    524    @Override
    525    public void setStroke(Stroke s) {
    526        Args.nullNotPermitted(s, "s");
    527        this.stroke = s;
    528        if (s instanceof BasicStroke) {
    529            BasicStroke bs = (BasicStroke) s;
    530            sb.append("ctx.lineWidth=").append(bs.getLineWidth()).append(";");
    531            if (bs.getDashArray() != null) {
    532                sb.append("ctx.setLineDash([");
    533                for (int i = 0; i < bs.getDashArray().length; i++) {
    534                    if (i != 0 ) {
    535                        sb.append(",");
    536                    }
    537                    sb.append((int) bs.getDashArray()[i]);
    538                }
    539                sb.append("]);");
    540            } else {
    541               sb.append("ctx.setLineDash([]);");
    542            }
    543        }
    544    }
    545
    546    /**
    547     * Returns the current value for the specified hint.  Note that all hints
    548     * are currently ignored in this implementation.
    549     *
    550     * @param hintKey  the hint key (<code>null</code> permitted, but the
    551     *     result will be <code>null</code> also).
    552     *
    553     * @return The current value for the specified hint
    554     *     (possibly <code>null</code>).
    555     *
    556     * @see #setRenderingHint(java.awt.RenderingHints.Key, java.lang.Object)
    557     */
    558    @Override
    559    public Object getRenderingHint(RenderingHints.Key hintKey) {
    560        return this.hints.get(hintKey);
    561    }
    562
    563    /**
    564     * Sets the value for a hint.  Note that all hints are currently
    565     * ignored in this implementation.
    566     *
    567     * @param hintKey  the hint key (<code>null</code> not permitted).
    568     * @param hintValue  the hint value.
    569     *
    570     * @see #getRenderingHint(java.awt.RenderingHints.Key)
    571     */
    572    @Override
    573    public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {
    574        this.hints.put(hintKey, hintValue);
    575    }
    576
    577    /**
    578     * Returns a copy of the rendering hints.  Modifying the returned copy
    579     * will have no impact on the state of this Graphics2D instance.
    580     *
    581     * @return The rendering hints (never <code>null</code>).
    582     *
    583     * @see #setRenderingHints(java.util.Map)
    584     */
    585    @Override
    586    public RenderingHints getRenderingHints() {
    587        return (RenderingHints) this.hints.clone();
    588    }
    589
    590    /**
    591     * Sets the rendering hints to the specified collection.
    592     *
    593     * @param hints  the new set of hints (<code>null</code> not permitted).
    594     *
    595     * @see #getRenderingHints()
    596     */
    597    @Override
    598    public void setRenderingHints(Map<?, ?> hints) {
    599        this.hints.clear();
    600        this.hints.putAll(hints);
    601    }
    602
    603    /**
    604     * Adds all the supplied rendering hints.
    605     *
    606     * @param hints  the hints (<code>null</code> not permitted).
    607     */
    608    @Override
    609    public void addRenderingHints(Map<?, ?> hints) {
    610        this.hints.putAll(hints);
    611    }
    612
    613    /**
    614     * Draws the specified shape with the current <code>paint</code> and
    615     * <code>stroke</code>.  There is direct handling for <code>Line2D</code>,
    616     * <code>Rectangle2D</code> and <code>Path2D</code>. All other shapes are
    617     * mapped to a <code>GeneralPath</code> and then drawn (effectively as
    618     * <code>Path2D</code> objects).
    619     *
    620     * @param s  the shape (<code>null</code> not permitted).
    621     *
    622     * @see #fill(java.awt.Shape)
    623     */
    624    @Override
    625    public void draw(Shape s) {
    626        if (s instanceof Line2D || s instanceof Rectangle2D
    627                || s instanceof Path2D) {
    628            shapeToPath(s);
    629            sb.append("ctx.stroke();");
    630        } else {
    631            draw(new GeneralPath(s));
    632        }
    633    }
    634
    635    /**
    636     * Fills the specified shape with the current <code>paint</code>.  There is
    637     * direct handling for <code>Rectangle2D</code> and <code>Path2D</code>.
    638     * All other shapes are mapped to a <code>GeneralPath</code> and then
    639     * filled.
    640     *
    641     * @param s  the shape (<code>null</code> not permitted).
    642     *
    643     * @see #draw(java.awt.Shape)
    644     */
    645    @Override
    646    public void fill(Shape s) {
    647        if (s instanceof Rectangle2D) {
    648            Rectangle2D r = (Rectangle2D) s;
    649            if (r.isEmpty()) {
    650                return;
    651            }
    652            sb.append("ctx.fillRect(").append(geomDP(r.getX())).append(",")
    653                    .append(geomDP(r.getY())).append(",")
    654                    .append(geomDP(r.getWidth())).append(",")
    655                    .append(geomDP(r.getHeight())).append(");");
    656        } else if (s instanceof Path2D) {
    657            shapeToPath(s);
    658            sb.append("ctx.fill();");
    659        } else {
    660            fill(new GeneralPath(s));
    661        }
    662    }
    663
    664    private void shapeToPath(Shape s) {
    665        if (s instanceof Line2D) {
    666            Line2D l = (Line2D) s;
    667            sb.append("ctx.beginPath();");
    668            sb.append("ctx.moveTo(").append(geomDP(l.getX1())).append(",")
    669                    .append(geomDP(l.getY1())).append(");");
    670            sb.append("ctx.lineTo(").append(geomDP(l.getX2())).append(",")
    671                    .append(geomDP(l.getY2())).append(");");
    672            sb.append("ctx.closePath();");
    673        } else if (s instanceof Rectangle2D) {
    674            Rectangle2D r = (Rectangle2D) s;
    675            sb.append("ctx.beginPath();");
    676            sb.append("ctx.rect(").append(geomDP(r.getX())).append(",")
    677                    .append(geomDP(r.getY())).append(",")
    678                    .append(geomDP(r.getWidth())).append(",")
    679                    .append(geomDP(r.getHeight())).append(");");
    680            sb.append("ctx.closePath();");
    681        }
    682        else if (s instanceof Path2D) {
    683            Path2D p = (Path2D) s;
    684            float[] coords = new float[6];
    685            double[] closePt = null;
    686            PathIterator iterator = p.getPathIterator(getTransform());
    687            sb.append("ctx.beginPath();");
    688            while (!iterator.isDone()) {
    689                int type = iterator.currentSegment(coords);
    690                switch (type) {
    691                case (PathIterator.SEG_MOVETO):
    692                    closePt = new double[2];
    693                    closePt[0] = coords[0];
    694                    closePt[1] = coords[1];
    695                    sb.append("ctx.moveTo(").append(geomDP(coords[0]))
    696                            .append(",").append(geomDP(coords[1])).append(");");
    697                    break;
    698                case (PathIterator.SEG_LINETO):
    699                    sb.append("ctx.lineTo(").append(geomDP(coords[0]))
    700                            .append(",").append(geomDP(coords[1])).append(");");
    701                    break;
    702                case (PathIterator.SEG_QUADTO):
    703                    sb.append("ctx.quadraticCurveTo(")
    704                            .append(geomDP(coords[0])).append(",")
    705                            .append(geomDP(coords[1])).append(",")
    706                            .append(geomDP(coords[2])).append(",")
    707                            .append(geomDP(coords[3])).append(");");
    708                    break;
    709                case (PathIterator.SEG_CUBICTO):
    710                    sb.append("ctx.bezierCurveTo(")
    711                            .append(geomDP(coords[0])).append(",")
    712                            .append(geomDP(coords[1])).append(",")
    713                            .append(geomDP(coords[2])).append(",")
    714                            .append(geomDP(coords[3])).append(",")
    715                            .append(geomDP(coords[4])).append(",")
    716                            .append(geomDP(coords[5])).append(");");
    717                    break;
    718                case (PathIterator.SEG_CLOSE):
    719                    if (closePt != null) {
    720                        sb.append("ctx.lineTo(")
    721                                .append(geomDP(closePt[0])).append(",")
    722                                .append(geomDP(closePt[1])).append(");");
    723                    }
    724                    break;
    725                default:
    726                    break;
    727                }
    728                iterator.next();
    729            }
    730            //sb.append("ctx.closePath();");
    731        } else {
    732            throw new RuntimeException("Unhandled shape " + s);
    733        }
    734    }
    735
    736    /**
    737     * Returns the current font used for drawing text.
    738     *
    739     * @return The current font (never <code>null</code>).
    740     *
    741     * @see #setFont(java.awt.Font)
    742     */
    743    @Override
    744    public Font getFont() {
    745        return this.font;
    746    }
    747
    748    /**
    749     * Sets the font to be used for drawing text.
    750     *
    751     * @param font  the font (<code>null</code> is permitted but ignored).
    752     *
    753     * @see #getFont()
    754     */
    755    @Override
    756    public void setFont(Font font) {
    757        if (font == null || this.font.equals(font)) {
    758            return;
    759        }
    760        this.font = font;
    761        this.sb.append("ctx.font=\"").append(font.getSize()).append("px ")
    762                .append(font.getFontName()).append("\";");
    763    }
    764
    765    /**
    766     * Returns the font metrics for the specified font.
    767     *
    768     * @param f  the font.
    769     *
    770     * @return The font metrics.
    771     */
    772    @Override
    773    public FontMetrics getFontMetrics(Font f) {
    774        return this.image.createGraphics().getFontMetrics(f);
    775    }
    776
    777    /**
    778     * Returns the font render context.  The implementation here returns the
    779     * <code>FontRenderContext</code> for an image that is maintained
    780     * internally (as for {@link #getFontMetrics}).
    781     *
    782     * @return The font render context.
    783     */
    784    @Override
    785    public FontRenderContext getFontRenderContext() {
    786        return this.image.createGraphics().getFontRenderContext();
    787    }
    788
    789    /**
    790     * Draws a string at <code>(x, y)</code>.  The start of the text at the
    791     * baseline level will be aligned with the <code>(x, y)</code> point.
    792     *
    793     * @param str  the string (<code>null</code> not permitted).
    794     * @param x  the x-coordinate.
    795     * @param y  the y-coordinate.
    796     *
    797     * @see #drawString(java.lang.String, float, float)
    798     */
    799    @Override
    800    public void drawString(String str, int x, int y) {
    801        drawString(str, (float) x, (float) y);
    802    }
    803
    804    /**
    805     * Draws a string at <code>(x, y)</code>. The start of the text at the
    806     * baseline level will be aligned with the <code>(x, y)</code> point.
    807     *
    808     * @param str  the string (<code>null</code> not permitted).
    809     * @param x  the x-coordinate.
    810     * @param y  the y-coordinate.
    811     */
    812    @Override
    813    public void drawString(String str, float x, float y) {
    814        if (str == null) {
    815            throw new NullPointerException("Null 'str' argument.");
    816        }
    817        sb.append("ctx.save();");
    818        if (this.paint instanceof Color) {
    819            this.sb.append("ctx.fillStyle=\"").append(toCSSColorValue(
    820                    (Color) this.paint)).append("\";");
    821        } else {
    822            setPaint(this.paint);
    823        }
    824        sb.append("ctx.fillText(\"").append(str).append("\",")
    825                .append(geomDP(x)).append(",").append(geomDP(y)).append(");");
    826        sb.append("ctx.restore();");
    827    }
    828
    829    /**
    830     * Draws a string of attributed characters at <code>(x, y)</code>.  The
    831     * call is delegated to
    832     * {@link #drawString(java.text.AttributedCharacterIterator, float, float)}.
    833     *
    834     * @param iterator  an iterator for the characters.
    835     * @param x  the x-coordinate.
    836     * @param y  the x-coordinate.
    837     */
    838    @Override
    839    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
    840        drawString(iterator, (float) x, (float) y);
    841    }
    842
    843    /**
    844     * Draws a string of attributed characters at <code>(x, y)</code>.
    845     *
    846     * @param iterator  an iterator over the characters (<code>null</code> not
    847     *     permitted).
    848     * @param x  the x-coordinate.
    849     * @param y  the y-coordinate.
    850     */
    851    @Override
    852    public void drawString(AttributedCharacterIterator iterator, float x,
    853            float y) {
    854        TextLayout layout = new TextLayout(iterator, getFontRenderContext());
    855        layout.draw(this, x, y);
    856    }
    857
    858    /**
    859     * Draws the specified glyph vector at the location (x, y).
    860     *
    861     * @param g  the glyph vector.
    862     * @param x  the x-coordinate.
    863     * @param y  the y-coordinate.
    864     */
    865    @Override
    866    public void drawGlyphVector(GlyphVector g, float x, float y) {
    867        fill(g.getOutline(x, y));
    868    }
    869
    870    /**
    871     * Translates the origin to <code>(tx, ty)</code>.  This call is delegated
    872     * to {@link #translate(double, double)}.
    873     *
    874     * @param tx  the x-translation.
    875     * @param ty  the y-translation.
    876     */
    877    @Override
    878    public void translate(int tx, int ty) {
    879        translate((double) tx, (double) ty);
    880    }
    881
    882    /**
    883     * Applies the translation (tx, ty).
    884     *
    885     * @param tx  the x-translation.
    886     * @param ty  the y-translation.
    887     */
    888    @Override
    889    public void translate(double tx, double ty) {
    890        AffineTransform t = getTransform();
    891        t.translate(tx, ty);
    892        setTransform(t);
    893    }
    894
    895    /**
    896     * Applies a rotation (anti-clockwise) about <code>(0, 0)</code>.
    897     *
    898     * @param theta  the rotation angle (in radians).
    899     */
    900    @Override
    901    public void rotate(double theta) {
    902        AffineTransform t = AffineTransform.getRotateInstance(theta);
    903        transform(t);
    904    }
    905
    906    /**
    907     * Applies a rotation (anti-clockwise) about <code>(x, y)</code>.
    908     *
    909     * @param theta  the rotation angle (in radians).
    910     * @param x  the x-coordinate.
    911     * @param y  the y-coordinate.
    912     */
    913    @Override
    914    public void rotate(double theta, double x, double y) {
    915        translate(x, y);
    916        rotate(theta);
    917        translate(-x, -y);
    918    }
    919
    920    /**
    921     * Applies a scale transformation.
    922     *
    923     * @param sx  the x-scaling factor.
    924     * @param sy  the y-scaling factor.
    925     */
    926    @Override
    927    public void scale(double sx, double sy) {
    928        AffineTransform t = getTransform();
    929        t.scale(sx, sy);
    930        setTransform(t);
    931    }
    932
    933    /**
    934     * Applies a shear transformation. This is equivalent to the following
    935     * call to the <code>transform</code> method:
    936     * <br><br>
    937     * <ul><li>
    938     * <code>transform(AffineTransform.getShearInstance(shx, shy));</code>
    939     * </ul>
    940     *
    941     * @param shx  the x-shear factor.
    942     * @param shy  the y-shear factor.
    943     */
    944    @Override
    945    public void shear(double shx, double shy) {
    946        transform(AffineTransform.getShearInstance(shx, shy));
    947    }
    948
    949    /**
    950     * Applies this transform to the existing transform by concatenating it.
    951     *
    952     * @param t  the transform (<code>null</code> not permitted).
    953     */
    954    @Override
    955    public void transform(AffineTransform t) {
    956        this.sb.append("ctx.transform(");
    957        this.sb.append(transformDP(t.getScaleX())).append(","); // m00
    958        this.sb.append(transformDP(t.getShearY())).append(","); // m10
    959        this.sb.append(transformDP(t.getShearX())).append(","); // m01
    960        this.sb.append(transformDP(t.getScaleY())).append(",");  // m11
    961        this.sb.append(transformDP(t.getTranslateX())).append(","); // m02
    962        this.sb.append(transformDP(t.getTranslateY())); // m12
    963        this.sb.append(");");
    964        AffineTransform tx = getTransform();
    965        tx.concatenate(t);
    966        setTransform(tx);
    967    }
    968
    969    /**
    970     * Returns a copy of the current transform.
    971     *
    972     * @return A copy of the current transform (never <code>null</code>).
    973     *
    974     * @see #setTransform(java.awt.geom.AffineTransform)
    975     */
    976    @Override
    977    public AffineTransform getTransform() {
    978        return (AffineTransform) this.transform.clone();
    979    }
    980
    981    /**
    982     * Sets the transform.
    983     *
    984     * @param t  the new transform (<code>null</code> permitted, resets to the
    985     *     identity transform).
    986     */
    987    @Override
    988    public void setTransform(AffineTransform t) {
    989        if (t == null) {
    990            this.transform = new AffineTransform();
    991        } else {
    992            this.transform = new AffineTransform(t);
    993        }
    994        this.sb.append("ctx.setTransform(");
    995        this.sb.append(transformDP(transform.getScaleX())).append(","); // m00
    996        this.sb.append(transformDP(transform.getShearY())).append(","); // m10
    997        this.sb.append(transformDP(transform.getShearX())).append(","); // m01
    998        this.sb.append(transformDP(transform.getScaleY())).append(",");  // m11
    999        this.sb.append(transformDP(transform.getTranslateX())).append(","); // m02
    1000        this.sb.append(transformDP(transform.getTranslateY())); // m12
    1001        this.sb.append(");");
    1002    }
    1003
    1004    /**
    1005     * Returns <code>true</code> if the rectangle (in device space) intersects
    1006     * with the shape (the interior, if <code>onStroke</code> is false,
    1007     * otherwise the stroked outline of the shape).
    1008     *
    1009     * @param rect  a rectangle (in device space).
    1010     * @param s the shape.
    1011     * @param onStroke  test the stroked outline only?
    1012     *
    1013     * @return A boolean.
    1014     */
    1015    @Override
    1016    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
    1017        Shape ts;
    1018        if (onStroke) {
    1019            ts = this.transform.createTransformedShape(
    1020                    this.stroke.createStrokedShape(s));
    1021        } else {
    1022            ts = this.transform.createTransformedShape(s);
    1023        }
    1024        if (!rect.getBounds2D().intersects(ts.getBounds2D())) {
    1025            return false;
    1026        }
    1027        Area a1 = new Area(rect);
    1028        Area a2 = new Area(ts);
    1029        a1.intersect(a2);
    1030        return !a1.isEmpty();
    1031    }
    1032
    1033    /**
    1034     * Not yet implemented.
    1035     */
    1036    @Override
    1037    public void setPaintMode() {
    1038        throw new UnsupportedOperationException("Not supported yet."); //TODO
    1039    }
    1040
    1041    /**
    1042     * Not yet implemented.
    1043     *
    1044     * @param c1  the color.
    1045     */
    1046    @Override
    1047    public void setXORMode(Color c1) {
    1048        throw new UnsupportedOperationException("Not supported yet."); //TODO
    1049    }
    1050
    1051    /**
    1052     * Returns the clip bounds.
    1053     *
    1054     * @return The clip bounds (possibly <code>null</code>).
    1055     */
    1056    @Override
    1057    public Rectangle getClipBounds() {
    1058        if (this.clip == null) {
    1059            return null;
    1060        }
    1061        return getClip().getBounds();
    1062    }
    1063
    1064    /**
    1065     * Returns the user clipping region.  The initial default value is
    1066     * <code>null</code>.
    1067     *
    1068     * @return The user clipping region (possibly <code>null</code>).
    1069     *
    1070     * @see #setClip(java.awt.Shape)
    1071     */
    1072    @Override
    1073    public Shape getClip() {
    1074        if (this.clip == null) {
    1075            return null;
    1076        }
    1077        AffineTransform inv;
    1078        try {
    1079            inv = this.transform.createInverse();
    1080            return inv.createTransformedShape(this.clip);
    1081        } catch (NoninvertibleTransformException ex) {
    1082            return null;
    1083        }
    1084    }
    1085
    1086    /**
    1087     * Sets the user clipping region.
    1088     *
    1089     * @param shape  the new user clipping region (<code>null</code> permitted).
    1090     *
    1091     * @see #getClip()
    1092     */
    1093    @Override
    1094    public void setClip(Shape shape) {
    1095        // null is handled fine here...
    1096        this.clip = this.transform.createTransformedShape(shape);
    1097    }
    1098
    1099    /**
    1100     * Clips to the intersection of the current clipping region and the
    1101     * specified shape.
    1102     *
    1103     * According to the Oracle API specification, this method will accept a
    1104     * <code>null</code> argument, but there is an open bug report (since 2004)
    1105     * that suggests this is wrong:
    1106     *
    1107     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6206189
    1108     *
    1109     * @param s  the clip shape (<code>null</code> not permitted).
    1110     */
    1111    @Override
    1112    public void clip(Shape s) {
    1113        if (this.clip == null) {
    1114            setClip(s);
    1115            return;
    1116        }
    1117        Shape ts = this.transform.createTransformedShape(s);
    1118        if (!ts.intersects(this.clip.getBounds2D())) {
    1119            setClip(new Rectangle2D.Double());
    1120        } else {
    1121          Area a1 = new Area(ts);
    1122          Area a2 = new Area(this.clip);
    1123          a1.intersect(a2);
    1124          this.clip = new Path2D.Double(a1);
    1125        }
    1126    }
    1127
    1128    /**
    1129     * Clips to the intersection of the current clipping region and the
    1130     * specified rectangle.
    1131     *
    1132     * @param x  the x-coordinate.
    1133     * @param y  the y-coordinate.
    1134     * @param width  the width.
    1135     * @param height  the height.
    1136     */
    1137    @Override
    1138    public void clipRect(int x, int y, int width, int height) {
    1139        setRect(x, y, width, height);
    1140        clip(this.rect);
    1141    }
    1142
    1143    /**
    1144     * Sets the user clipping region to the specified rectangle.
    1145     *
    1146     * @param x  the x-coordinate.
    1147     * @param y  the y-coordinate.
    1148     * @param width  the width.
    1149     * @param height  the height.
    1150     *
    1151     * @see #getClip()
    1152     */
    1153    @Override
    1154    public void setClip(int x, int y, int width, int height) {
    1155        setRect(x, y, width, height);
    1156        setClip(this.rect);
    1157    }
    1158
    1159    /**
    1160     * Not yet implemented.
    1161     *
    1162     * @param x  the x-coordinate.
    1163     * @param y  the y-coordinate.
    1164     * @param width  the width.
    1165     * @param height  the height.
    1166     * @param dx  the destination x-offset.
    1167     * @param dy  the destination y-offset.
    1168     */
    1169    @Override
    1170    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
    1171        throw new UnsupportedOperationException("Not supported yet."); //TODO
    1172    }
    1173
    1174    /**
    1175     * Draws a line from <code>(x1, y1)</code> to <code>(x2, y2)</code> using
    1176     * the current <code>paint</code> and <code>stroke</code>.
    1177     *
    1178     * @param x1  the x-coordinate of the start point.
    1179     * @param y1  the y-coordinate of the start point.
    1180     * @param x2  the x-coordinate of the end point.
    1181     * @param y2  the x-coordinate of the end point.
    1182     */
    1183    @Override
    1184    public void drawLine(int x1, int y1, int x2, int y2) {
    1185        if (this.line == null) {
    1186            this.line = new Line2D.Double(x1, y1, x2, y2);
    1187        } else {
    1188            this.line.setLine(x1, y1, x2, y2);
    1189        }
    1190        draw(this.line);
    1191    }
    1192
    1193    /**
    1194     * Fills the specified rectangle with the current <code>paint</code>.
    1195     *
    1196     * @param x  the x-coordinate.
    1197     * @param y  the y-coordinate.
    1198     * @param width  the rectangle width.
    1199     * @param height  the rectangle height.
    1200     */
    1201    @Override
    1202    public void fillRect(int x, int y, int width, int height) {
    1203        setRect(x, y, width, height);
    1204        fill(this.rect);
    1205    }
    1206
    1207    /**
    1208     * Clears the specified rectangle by filling it with the current
    1209     * background color.  If the background color is <code>null</code>, this
    1210     * method will do nothing.
    1211     *
    1212     * @param x  the x-coordinate.
    1213     * @param y  the y-coordinate.
    1214     * @param width  the width.
    1215     * @param height  the height.
    1216     *
    1217     * @see #getBackground()
    1218     */
    1219    @Override
    1220    public void clearRect(int x, int y, int width, int height) {
    1221        if (getBackground() == null) {
    1222            return;  // we can't do anything
    1223        }
    1224        Paint saved = getPaint();
    1225        setPaint(getBackground());
    1226        fillRect(x, y, width, height);
    1227        setPaint(saved);
    1228    }
    1229
    1230    /**
    1231     * Draws a rectangle with rounded corners using the current
    1232     * <code>paint</code> and <code>stroke</code>.
    1233     *
    1234     * @param x  the x-coordinate.
    1235     * @param y  the y-coordinate.
    1236     * @param width  the width.
    1237     * @param height  the height.
    1238     * @param arcWidth  the arc-width.
    1239     * @param arcHeight  the arc-height.
    1240     *
    1241     * @see #fillRoundRect(int, int, int, int, int, int)
    1242     */
    1243    @Override
    1244    public void drawRoundRect(int x, int y, int width, int height,
    1245            int arcWidth, int arcHeight) {
    1246        setRoundRect(x, y, width, height, arcWidth, arcHeight);
    1247        draw(this.roundRect);
    1248    }
    1249
    1250    /**
    1251     * Fills a rectangle with rounded corners using the current
    1252     * <code>paint</code>.
    1253     *
    1254     * @param x  the x-coordinate.
    1255     * @param y  the y-coordinate.
    1256     * @param width  the width.
    1257     * @param height  the height.
    1258     * @param arcWidth  the arc-width.
    1259     * @param arcHeight  the arc-height.
    1260     *
    1261     * @see #drawRoundRect(int, int, int, int, int, int)
    1262     */
    1263    @Override
    1264    public void fillRoundRect(int x, int y, int width, int height,
    1265            int arcWidth, int arcHeight) {
    1266        setRoundRect(x, y, width, height, arcWidth, arcHeight);
    1267        fill(this.roundRect);
    1268    }
    1269
    1270    /**
    1271     * Draws an oval framed by the rectangle <code>(x, y, width, height)</code>
    1272     * using the current <code>paint</code> and <code>stroke</code>.
    1273     *
    1274     * @param x  the x-coordinate.
    1275     * @param y  the y-coordinate.
    1276     * @param width  the width.
    1277     * @param height  the height.
    1278     *
    1279     * @see #fillOval(int, int, int, int)
    1280     */
    1281    @Override
    1282    public void drawOval(int x, int y, int width, int height) {
    1283        setOval(x, y, width, height);
    1284        draw(this.oval);
    1285    }
    1286
    1287    /**
    1288     * Fills an oval framed by the rectangle <code>(x, y, width, height)</code>.
    1289     *
    1290     * @param x  the x-coordinate.
    1291     * @param y  the y-coordinate.
    1292     * @param width  the width.
    1293     * @param height  the height.
    1294     *
    1295     * @see #drawOval(int, int, int, int)
    1296     */
    1297    @Override
    1298    public void fillOval(int x, int y, int width, int height) {
    1299        setOval(x, y, width, height);
    1300        fill(this.oval);
    1301    }
    1302
    1303    /**
    1304     * Draws an arc contained within the rectangle
    1305     * <code>(x, y, width, height)</code>, starting at <code>startAngle</code>
    1306     * and continuing through <code>arcAngle</code> degrees using
    1307     * the current <code>paint</code> and <code>stroke</code>.
    1308     *
    1309     * @param x  the x-coordinate.
    1310     * @param y  the y-coordinate.
    1311     * @param width  the width.
    1312     * @param height  the height.
    1313     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
    1314     * @param arcAngle  the angle (anticlockwise) in degrees.
    1315     *
    1316     * @see #fillArc(int, int, int, int, int, int)
    1317     */
    1318    @Override
    1319    public void drawArc(int x, int y, int width, int height, int startAngle,
    1320            int arcAngle) {
    1321        setArc(x, y, width, height, startAngle, arcAngle);
    1322        draw(this.arc);
    1323    }
    1324
    1325    /**
    1326     * Fills an arc contained within the rectangle
    1327     * <code>(x, y, width, height)</code>, starting at <code>startAngle</code>
    1328     * and continuing through <code>arcAngle</code> degrees, using
    1329     * the current <code>paint</code>
    1330     *
    1331     * @param x  the x-coordinate.
    1332     * @param y  the y-coordinate.
    1333     * @param width  the width.
    1334     * @param height  the height.
    1335     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
    1336     * @param arcAngle  the angle (anticlockwise) in degrees.
    1337     *
    1338     * @see #drawArc(int, int, int, int, int, int)
    1339     */
    1340    @Override
    1341    public void fillArc(int x, int y, int width, int height, int startAngle,
    1342            int arcAngle) {
    1343        setArc(x, y, width, height, startAngle, arcAngle);
    1344        fill(this.arc);
    1345    }
    1346
    1347    /**
    1348     * Draws the specified multi-segment line using the current
    1349     * <code>paint</code> and <code>stroke</code>.
    1350     *
    1351     * @param xPoints  the x-points.
    1352     * @param yPoints  the y-points.
    1353     * @param nPoints  the number of points to use for the polyline.
    1354     */
    1355    @Override
    1356    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
    1357        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints,
    1358                false);
    1359        draw(p);
    1360    }
    1361
    1362    /**
    1363     * Draws the specified polygon using the current <code>paint</code> and
    1364     * <code>stroke</code>.
    1365     *
    1366     * @param xPoints  the x-points.
    1367     * @param yPoints  the y-points.
    1368     * @param nPoints  the number of points to use for the polygon.
    1369     *
    1370     * @see #fillPolygon(int[], int[], int)      */
    1371    @Override
    1372    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
    1373        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints,
    1374                true);
    1375        draw(p);
    1376    }
    1377
    1378    /**
    1379     * Fills the specified polygon using the current <code>paint</code>.
    1380     *
    1381     * @param xPoints  the x-points.
    1382     * @param yPoints  the y-points.
    1383     * @param nPoints  the number of points to use for the polygon.
    1384     *
    1385     * @see #drawPolygon(int[], int[], int)
    1386     */
    1387    @Override
    1388    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
    1389        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints,
    1390                true);
    1391        fill(p);
    1392    }
    1393
    1394    /**
    1395     * Draws an image with the specified transform. Note that the
    1396     * <code>observer</code> is ignored.
    1397     *
    1398     * @param img  the image.
    1399     * @param xform  the transform.
    1400     * @param obs  the image observer (ignored).
    1401     *
    1402     * @return {@code true} if the image is drawn.
    1403     */
    1404    @Override
    1405    public boolean drawImage(Image img, AffineTransform xform,
    1406            ImageObserver obs) {
    1407        AffineTransform savedTransform = getTransform();
    1408        transform(xform);
    1409        boolean result = drawImage(img, 0, 0, obs);
    1410        setTransform(savedTransform);
    1411        return result;
    1412    }
    1413
    1414    /**
    1415     * Draws the image resulting from applying the <code>BufferedImageOp</code>
    1416     * to the specified image at the location <code>(x, y)</code>.
    1417     *
    1418     * @param img  the image.
    1419     * @param op  the operation.
    1420     * @param x  the x-coordinate.
    1421     * @param y  the y-coordinate.
    1422     */
    1423    @Override
    1424    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
    1425        BufferedImage imageToDraw = op.filter(img, null);
    1426        drawImage(imageToDraw, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
    1427    }
    1428
    1429    /**
    1430     * Draws the rendered image.
    1431     *
    1432     * @param img  the image.
    1433     * @param xform  the transform.
    1434     */
    1435    @Override
    1436    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
    1437        BufferedImage bi = GraphicsUtils.convertRenderedImage(img);
    1438        drawImage(bi, xform, null);
    1439    }
    1440
    1441    /**
    1442     * Draws the renderable image.
    1443     *
    1444     * @param img  the renderable image.
    1445     * @param xform  the transform.
    1446     */
    1447    @Override
    1448    public void drawRenderableImage(RenderableImage img,
    1449            AffineTransform xform) {
    1450        RenderedImage ri = img.createDefaultRendering();
    1451        drawRenderedImage(ri, xform);
    1452    }
    1453
    1454    /**
    1455     * Draws an image at the location <code>(x, y)</code>.  Note that the
    1456     * <code>observer</code> is ignored.
    1457     *
    1458     * @param img  the image.
    1459     * @param x  the x-coordinate.
    1460     * @param y  the y-coordinate.
    1461     * @param observer  ignored.
    1462     *
    1463     * @return {@code true} if the image is drawn.
    1464     */
    1465    @Override
    1466    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
    1467        int w = img.getWidth(null);
    1468        if (w < 0) {
    1469            return false;
    1470        }
    1471        int h = img.getHeight(null);
    1472        if (h < 0) {
    1473            return false;
    1474        }
    1475        return drawImage(img, x, y, w, h, observer);
    1476    }
    1477
    1478    /**
    1479     * Not yet supported (but no exception is thrown).
    1480     *
    1481     * @param img  the image.
    1482     * @param x  the x-coordinate.
    1483     * @param y  the y-coordinate.
    1484     * @param width  the width.
    1485     * @param height  the height.
    1486     * @param observer  the observer (<code>null</code> permitted).
    1487     *
    1488     * @return A boolean.
    1489     */
    1490    @Override
    1491    public boolean drawImage(Image img, int x, int y, int width, int height,
    1492            ImageObserver observer) {
    1493        // TODO : implement this
    1494        return false;
    1495    }
    1496
    1497    /**
    1498     * Draws an image at the location <code>(x, y)</code>.  Note that the
    1499     * <code>observer</code> is ignored.
    1500     *
    1501     * @param img  the image.
    1502     * @param x  the x-coordinate.
    1503     * @param y  the y-coordinate.
    1504     * @param bgcolor  the background color (<code>null</code> permitted).
    1505     * @param observer  ignored.
    1506     *
    1507     * @return {@code true} if the image is drawn.
    1508     */
    1509    @Override
    1510    public boolean drawImage(Image img, int x, int y, Color bgcolor,
    1511            ImageObserver observer) {
    1512        int w = img.getWidth(null);
    1513        if (w < 0) {
    1514            return false;
    1515        }
    1516        int h = img.getHeight(null);
    1517        if (h < 0) {
    1518            return false;
    1519        }
    1520        return drawImage(img, x, y, w, h, bgcolor, observer);
    1521    }
    1522
    1523    /**
    1524     * Draws an image to the rectangle <code>(x, y, w, h)</code> (scaling it if
    1525     * required), first filling the background with the specified color.  Note
    1526     * that the <code>observer</code> is ignored.
    1527     *
    1528     * @param img  the image.
    1529     * @param x  the x-coordinate.
    1530     * @param y  the y-coordinate.
    1531     * @param w  the width.
    1532     * @param h  the height.
    1533     * @param bgcolor  the background color (<code>null</code> permitted).
    1534     * @param observer  ignored.
    1535     *
    1536     * @return {@code true} if the image is drawn.
    1537     */
    1538    @Override
    1539    public boolean drawImage(Image img, int x, int y, int w, int h,
    1540            Color bgcolor, ImageObserver observer) {
    1541        Paint saved = getPaint();
    1542        setPaint(bgcolor);
    1543        fillRect(x, y, w, h);
    1544        setPaint(saved);
    1545        return drawImage(img, x, y, w, h, observer);
    1546    }
    1547
    1548    /**
    1549     * Draws part of an image (defined by the source rectangle
    1550     * <code>(sx1, sy1, sx2, sy2)</code>) into the destination rectangle
    1551     * <code>(dx1, dy1, dx2, dy2)</code>.  Note that the <code>observer</code>
    1552     * is ignored.
    1553     *
    1554     * @param img  the image.
    1555     * @param dx1  the x-coordinate for the top left of the destination.
    1556     * @param dy1  the y-coordinate for the top left of the destination.
    1557     * @param dx2  the x-coordinate for the bottom right of the destination.
    1558     * @param dy2  the y-coordinate for the bottom right of the destination.
    1559     * @param sx1 the x-coordinate for the top left of the source.
    1560     * @param sy1 the y-coordinate for the top left of the source.
    1561     * @param sx2 the x-coordinate for the bottom right of the source.
    1562     * @param sy2 the y-coordinate for the bottom right of the source.
    1563     *
    1564     * @return {@code true} if the image is drawn.
    1565     */
    1566    @Override
    1567    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
    1568            int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
    1569        int w = dx2 - dx1;
    1570        int h = dy2 - dy1;
    1571        BufferedImage img2 = new BufferedImage(BufferedImage.TYPE_INT_ARGB,
    1572                w, h);
    1573        Graphics2D g2 = img2.createGraphics();
    1574        g2.drawImage(img, 0, 0, w, h, sx1, sy1, sx2, sy2, null);
    1575        return drawImage(img2, dx1, dx2, null);
    1576    }
    1577
    1578    /**
    1579     * Draws part of an image (defined by the source rectangle
    1580     * <code>(sx1, sy1, sx2, sy2)</code>) into the destination rectangle
    1581     * <code>(dx1, dy1, dx2, dy2)</code>.  The destination rectangle is first
    1582     * cleared by filling it with the specified <code>bgcolor</code>. Note that
    1583     * the <code>observer</code> is ignored.
    1584     *
    1585     * @param img  the image.
    1586     * @param dx1  the x-coordinate for the top left of the destination.
    1587     * @param dy1  the y-coordinate for the top left of the destination.
    1588     * @param dx2  the x-coordinate for the bottom right of the destination.
    1589     * @param dy2  the y-coordinate for the bottom right of the destination.
    1590     * @param sx1 the x-coordinate for the top left of the source.
    1591     * @param sy1 the y-coordinate for the top left of the source.
    1592     * @param sx2 the x-coordinate for the bottom right of the source.
    1593     * @param sy2 the y-coordinate for the bottom right of the source.
    1594     * @param bgcolor  the background color (<code>null</code> permitted).
    1595     * @param observer  ignored.
    1596     *
    1597     * @return {@code true} if the image is drawn.
    1598     */
    1599    @Override
    1600    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
    1601            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
    1602            ImageObserver observer) {
    1603        Paint saved = getPaint();
    1604        setPaint(bgcolor);
    1605        fillRect(dx1, dy1, dx2 - dx1, dy2 - dy1);
    1606        setPaint(saved);
    1607        return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
    1608    }
    1609
    1610    /**
    1611     * Does nothing.
    1612     */
    1613    @Override
    1614    public void dispose() {
    1615        // nothing to do
    1616    }
    1617
    1618    /**
    1619     * Returns the script that has been generated by calls to this
    1620     * <code>Graphics2D</code> implementation.
    1621     *
    1622     * @return The script.
    1623     */
    1624    public String getScript() {
    1625        return this.sb.toString();
    1626    }
    1627
    1628    private String transformDP(double d) {
    1629        if (this.transformFormat != null) {
    1630            return transformFormat.format(d);
    1631        } else {
    1632            return String.valueOf(d);
    1633        }
    1634    }
    1635
    1636    private String geomDP(double d) {
    1637        if (this.geometryFormat != null) {
    1638            return geometryFormat.format(d);
    1639        } else {
    1640            return String.valueOf(d);
    1641        }
    1642    }
    1643
    1644    /**
    1645     * Sets the attributes of the reusable {@link Rectangle2D} object that is
    1646     * used by the {@link CanvasGraphics2D#drawRect(int, int, int, int)} and
    1647     * {@link CanvasGraphics2D#fillRect(int, int, int, int)} methods.
    1648     *
    1649     * @param x  the x-coordinate.
    1650     * @param y  the y-coordinate.
    1651     * @param width  the width.
    1652     * @param height  the height.
    1653     */
    1654    private void setRect(int x, int y, int width, int height) {
    1655        if (this.rect == null) {
    1656            this.rect = new Rectangle2D.Double(x, y, width, height);
    1657        } else {
    1658            this.rect.setRect(x, y, width, height);
    1659        }
    1660    }
    1661
    1662    /**
    1663     * Sets the attributes of the reusable {@link RoundRectangle2D} object that
    1664     * is used by the {@link #drawRoundRect(int, int, int, int, int, int)} and
    1665     * {@link #fillRoundRect(int, int, int, int, int, int)} methods.
    1666     *
    1667     * @param x  the x-coordinate.
    1668     * @param y  the y-coordinate.
    1669     * @param width  the width.
    1670     * @param height  the height.
    1671     * @param arcWidth  the arc width.
    1672     * @param arcHeight  the arc height.
    1673     */
    1674    private void setRoundRect(int x, int y, int width, int height, int arcWidth,
    1675            int arcHeight) {
    1676        if (this.roundRect == null) {
    1677            this.roundRect = new RoundRectangle2D.Double(x, y, width, height,
    1678                    arcWidth, arcHeight);
    1679        } else {
    1680            this.roundRect.setRoundRect(x, y, width, height,
    1681                    arcWidth, arcHeight);
    1682        }
    1683    }
    1684
    1685    /**
    1686     * Sets the attributes of the reusable {@link Arc2D} object that is used by
    1687     * {@link #drawArc(int, int, int, int, int, int)} and
    1688     * {@link #fillArc(int, int, int, int, int, int)} methods.
    1689     *
    1690     * @param x  the x-coordinate.
    1691     * @param y  the y-coordinate.
    1692     * @param width  the width.
    1693     * @param height  the height.
    1694     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
    1695     * @param arcAngle  the angle (anticlockwise) in degrees.
    1696     */
    1697    private void setArc(int x, int y, int width, int height, int startAngle,
    1698            int arcAngle) {
    1699        if (this.arc == null) {
    1700            this.arc = new Arc2D.Double(x, y, width, height, startAngle,
    1701                    arcAngle, Arc2D.OPEN);
    1702        } else {
    1703            this.arc.setArc(x, y, width, height, startAngle, arcAngle,
    1704                    Arc2D.OPEN);
    1705        }
    1706    }
    1707
    1708    /**
    1709     * Sets the attributes of the reusable {@link Ellipse2D} object that is
    1710     * used by the {@link #drawOval(int, int, int, int)} and
    1711     * {@link #fillOval(int, int, int, int)} methods.
    1712     *
    1713     * @param x  the x-coordinate.
    1714     * @param y  the y-coordinate.
    1715     * @param width  the width.
    1716     * @param height  the height.
    1717     */
    1718    private void setOval(int x, int y, int width, int height) {
    1719        if (this.oval == null) {
    1720            this.oval = new Ellipse2D.Double(x, y, width, height);
    1721        } else {
    1722            this.oval.setFrame(x, y, width, height);
    1723        }
    1724    }
    1725}