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