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}