001/* ===================================================
002 * JFreeSVG : an SVG library for the Java(tm) platform
003 * ===================================================
004 * 
005 * (C)opyright 2013-2016, 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
032package org.jfree.graphics2d.svg;
033
034import java.awt.AlphaComposite;
035import java.awt.BasicStroke;
036import java.awt.Color;
037import java.awt.Composite;
038import java.awt.Font;
039import java.awt.FontMetrics;
040import java.awt.GradientPaint;
041import java.awt.Graphics;
042import java.awt.Graphics2D;
043import java.awt.GraphicsConfiguration;
044import java.awt.Image;
045import java.awt.LinearGradientPaint;
046import java.awt.MultipleGradientPaint.CycleMethod;
047import java.awt.Paint;
048import java.awt.RadialGradientPaint;
049import java.awt.Rectangle;
050import java.awt.RenderingHints;
051import java.awt.Shape;
052import java.awt.Stroke;
053import java.awt.font.FontRenderContext;
054import java.awt.font.GlyphVector;
055import java.awt.font.TextLayout;
056import java.awt.geom.AffineTransform;
057import java.awt.geom.Arc2D;
058import java.awt.geom.Area;
059import java.awt.geom.Ellipse2D;
060import java.awt.geom.GeneralPath;
061import java.awt.geom.Line2D;
062import java.awt.geom.NoninvertibleTransformException;
063import java.awt.geom.Path2D;
064import java.awt.geom.PathIterator;
065import java.awt.geom.Point2D;
066import java.awt.geom.Rectangle2D;
067import java.awt.geom.RoundRectangle2D;
068import java.awt.image.BufferedImage;
069import java.awt.image.BufferedImageOp;
070import java.awt.image.ImageObserver;
071import java.awt.image.RenderedImage;
072import java.awt.image.renderable.RenderableImage;
073import java.io.ByteArrayOutputStream;
074import java.io.IOException;
075import java.text.AttributedCharacterIterator;
076import java.text.AttributedCharacterIterator.Attribute;
077import java.text.AttributedString;
078import java.text.DecimalFormat;
079import java.text.DecimalFormatSymbols;
080import java.util.ArrayList;
081import java.util.HashMap;
082import java.util.HashSet;
083import java.util.List;
084import java.util.Map;
085import java.util.Map.Entry;
086import java.util.Set;
087import java.util.logging.Level;
088import java.util.logging.Logger;
089import javax.imageio.ImageIO;
090import javax.xml.bind.DatatypeConverter;
091import org.jfree.graphics2d.Args;
092import org.jfree.graphics2d.GradientPaintKey;
093import org.jfree.graphics2d.GraphicsUtils;
094import org.jfree.graphics2d.LinearGradientPaintKey;
095import org.jfree.graphics2d.RadialGradientPaintKey;
096
097/**
098 * <p>
099 * A {@code Graphics2D} implementation that creates SVG output.  After 
100 * rendering the graphics via the {@code SVGGraphics2D}, you can retrieve
101 * an SVG element (see {@link #getSVGElement()}) or an SVG document (see 
102 * {@link #getSVGDocument()}) containing your content.
103 * </p>
104 * <b>Usage</b><br>
105 * <p>
106 * Using the {@code SVGGraphics2D} class is straightforward.  First, 
107 * create an instance specifying the height and width of the SVG element that 
108 * will be created.  Then, use standard Java2D API calls to draw content 
109 * into the element.  Finally, retrieve the SVG element that has been 
110 * accumulated.  For example:
111 * </p>
112 * <pre>{@code SVGGraphics2D g2 = new SVGGraphics2D(300, 200);
113 * g2.setPaint(Color.RED);
114 * g2.draw(new Rectangle(10, 10, 280, 180);
115 * String svgElement = g2.getSVGElement();}</pre>
116 * <p>
117 * For the content generation step, you can make use of third party libraries,
118 * such as <a href="http://www.jfree.org/jfreechart/">JFreeChart</a> and
119 * <a href="http://www.object-refinery.com/orsoncharts/">Orson Charts</a>, that 
120 * render output using standard Java2D API calls.
121 * </p>
122 * <b>Rendering Hints</b><br>
123 * <p>
124 * The {@code SVGGraphics2D} supports a couple of custom rendering hints -  
125 * for details, refer to the {@link SVGHints} class documentation.  Also see
126 * the examples in this blog post: 
127 * <a href="http://www.object-refinery.com/blog/blog-20140509.html">
128 * Orson Charts 3D / Enhanced SVG Export</a>.
129 * </p>
130 * <b>Other Notes</b><br>
131 * Some additional notes:
132 * <ul>
133 * <li>Images are supported, but for methods with an {@code ImageObserver}
134 * parameter note that the observer is ignored completely.  In any case, using 
135 * images that are not fully loaded already would not be a good idea in the 
136 * context of generating SVG data/files;</li>
137 * 
138 * <li>the {@link #getFontMetrics(java.awt.Font)} and 
139 * {@link #getFontRenderContext()} methods return values that come from an 
140 * internal {@code BufferedImage}, this is a short-cut and we don't know 
141 * if there are any negative consequences (if you know of any, please let us 
142 * know and we'll add the info here or find a way to fix it);</li>
143 * 
144 * <li>there are settings to control the number of decimal places used to
145 * write the coordinates for geometrical elements (default 2dp) and transform
146 * matrices (default 6dp).  These defaults may change in a future release.</li>
147 * 
148 * <li>when an HTML page contains multiple SVG elements, the items within
149 * the DEFS element for each SVG element must have IDs that are unique across 
150 * <em>all</em> SVG elements in the page.  We auto-populate the 
151 * {@code defsKeyPrefix} attribute to help ensure that unique IDs are 
152 * generated.</li>
153 * </ul>
154 *
155 * <p>
156 * For some demos showing how to use this class, look in the
157 * {@code org.jfree.graphics2d.demo} package in the {@code src} directory.
158 * </p>
159 */
160public final class SVGGraphics2D extends Graphics2D {
161
162    /** The prefix for keys used to identify clip paths. */
163    private static final String CLIP_KEY_PREFIX = "clip-";
164    
165    private final int width;
166    
167    private final int height;
168    
169    private final SVGUnits units;
170    
171    /** 
172     * The shape rendering property to set for the SVG element.  Permitted
173     * values are "auto", "crispEdges", "geometricPrecision" and
174     * "optimizeSpeed".
175     */
176    private String shapeRendering = "auto";
177    
178    /**
179     * The text rendering property for the SVG element.  Permitted values 
180     * are "auto", "optimizeSpeed", "optimizeLegibility" and 
181     * "geometricPrecision".
182     */
183    private String textRendering = "auto";
184    
185    /** Rendering hints (see SVGHints). */
186    private final RenderingHints hints;
187    
188    /** 
189     * A flag that controls whether or not the KEY_STROKE_CONTROL hint is
190     * checked.
191     */
192    private boolean checkStrokeControlHint = true;
193    
194    /** 
195     * The number of decimal places to use when writing the matrix values
196     * for transformations. 
197     */
198    private int transformDP;
199    
200    /**
201     * The decimal formatter for transform matrices.
202     */
203    private DecimalFormat transformFormat;
204    
205    /**
206     * The number of decimal places to use when writing coordinates for
207     * geometrical shapes.
208     */
209    private int geometryDP;
210
211    /**
212     * The decimal formatter for coordinates of geometrical shapes.
213     */
214    private DecimalFormat geometryFormat;
215    
216    /** The buffer that accumulates the SVG output. */
217    private StringBuilder sb;
218
219    /** 
220     * A prefix for the keys used in the DEFS element.  This can be used to 
221     * ensure that the keys are unique when creating more than one SVG element
222     * for a single HTML page.
223     */
224    private String defsKeyPrefix = "";
225    
226    /** 
227     * A map of all the gradients used, and the corresponding id.  When 
228     * generating the SVG file, all the gradient paints used must be defined
229     * in the defs element.
230     */
231    private Map<GradientPaintKey, String> gradientPaints 
232            = new HashMap<GradientPaintKey, String>();
233    
234    /** 
235     * A map of all the linear gradients used, and the corresponding id.  When 
236     * generating the SVG file, all the linear gradient paints used must be 
237     * defined in the defs element.
238     */
239    private Map<LinearGradientPaintKey, String> linearGradientPaints 
240            = new HashMap<LinearGradientPaintKey, String>();
241    
242    /** 
243     * A map of all the radial gradients used, and the corresponding id.  When 
244     * generating the SVG file, all the radial gradient paints used must be 
245     * defined in the defs element.
246     */
247    private Map<RadialGradientPaintKey, String> radialGradientPaints
248            = new HashMap<RadialGradientPaintKey, String>();
249    
250    /**
251     * A list of the registered clip regions.  These will be written to the
252     * DEFS element.
253     */
254    private List<String> clipPaths = new ArrayList<String>();
255    
256    /** 
257     * The filename prefix for images that are referenced rather than
258     * embedded but don't have an {@code href} supplied via the 
259     * {@link #KEY_IMAGE_HREF} hint.
260     */
261    private String filePrefix;
262    
263    /** 
264     * The filename suffix for images that are referenced rather than
265     * embedded but don't have an {@code href} supplied via the 
266     * {@link #KEY_IMAGE_HREF} hint.
267     */
268    private String fileSuffix;
269    
270    /** 
271     * A list of images that are referenced but not embedded in the SVG.
272     * After the SVG is generated, the caller can make use of this list to
273     * write PNG files if they don't already exist.  
274     */
275    private List<ImageElement> imageElements;
276    
277    /** The user clip (can be null). */
278    private Shape clip;
279    
280    /** The reference for the current clip. */
281    private String clipRef;
282    
283    /** The current transform. */
284    private AffineTransform transform = new AffineTransform();
285
286    private Paint paint = Color.BLACK;
287    
288    private Color color = Color.BLACK;
289    
290    private Composite composite = AlphaComposite.getInstance(
291            AlphaComposite.SRC_OVER, 1.0f);
292    
293    /** The current stroke. */
294    private Stroke stroke = new BasicStroke(1.0f);
295    
296    /** 
297     * The width of the SVG stroke to use when the user supplies a
298     * BasicStroke with a width of 0.0 (in this case the Java specification
299     * says "If width is set to 0.0f, the stroke is rendered as the thinnest 
300     * possible line for the target device and the antialias hint setting.")
301     */
302    private double zeroStrokeWidth;
303    
304    /** The last font that was set. */
305    private Font font;
306
307    /** 
308     * The font render context.  The fractional metrics flag solves the glyph
309     * positioning issue identified by Christoph Nahr:
310     * http://news.kynosarges.org/2014/06/28/glyph-positioning-in-jfreesvg-orsonpdf/
311     */
312    private final FontRenderContext fontRenderContext = new FontRenderContext(
313            null, false, true);
314
315    /** Maps font family names to alternates (or leaves them unchanged). */
316    private FontMapper fontMapper;
317        
318    /** The background color, used by clearRect(). */
319    private Color background = Color.BLACK;
320
321    /** A hidden image used for font metrics. */
322    private BufferedImage fmImage;
323    
324    private Graphics2D fmImageG2D;
325
326    /**
327     * An instance that is lazily instantiated in drawLine and then 
328     * subsequently reused to avoid creating a lot of garbage.
329     */
330    private Line2D line;
331
332    /**
333     * An instance that is lazily instantiated in fillRect and then 
334     * subsequently reused to avoid creating a lot of garbage.
335     */
336    Rectangle2D rect;
337
338    /**
339     * An instance that is lazily instantiated in draw/fillRoundRect and then
340     * subsequently reused to avoid creating a lot of garbage.
341     */
342    private RoundRectangle2D roundRect;
343    
344    /**
345     * An instance that is lazily instantiated in draw/fillOval and then
346     * subsequently reused to avoid creating a lot of garbage.
347     */
348    private Ellipse2D oval;
349 
350    /**
351     * An instance that is lazily instantiated in draw/fillArc and then
352     * subsequently reused to avoid creating a lot of garbage.
353     */
354    private Arc2D arc;
355 
356    /** 
357     * If the current paint is an instance of {@link GradientPaint}, this
358     * field will contain the reference id that is used in the DEFS element
359     * for that linear gradient.
360     */
361    private String gradientPaintRef = null;
362
363    /** 
364     * The device configuration (this is lazily instantiated in the 
365     * getDeviceConfiguration() method).
366     */
367    private GraphicsConfiguration deviceConfiguration;
368
369    /** A set of element IDs. */
370    private final Set<String> elementIDs;
371    
372    /**
373     * Creates a new instance with the specified width and height.
374     * 
375     * @param width  the width of the SVG element.
376     * @param height  the height of the SVG element.
377     */
378    public SVGGraphics2D(int width, int height) {
379        this(width, height, null, new StringBuilder());
380    }
381
382    /**
383     * Creates a new instance with the specified width and height in the given
384     * units.
385     * 
386     * @param width  the width of the SVG element.
387     * @param height  the height of the SVG element.
388     * @param units  the units for the width and height.
389     * 
390     * @since 3.2
391     */
392    public SVGGraphics2D(int width, int height, SVGUnits units) {
393        this(width, height, units, new StringBuilder());
394    }
395    
396    /**
397     * Creates a new instance with the specified width and height that will
398     * populate the supplied StringBuilder instance.  This constructor is 
399     * used by the {@link #create()} method, but won't normally be called
400     * directly by user code.
401     * 
402     * @param width  the width of the SVG element.
403     * @param height  the height of the SVG element.
404     * @param sb  the string builder ({@code null} not permitted).
405     * 
406     * @since 2.0
407     */
408    public SVGGraphics2D(int width, int height, StringBuilder sb) {
409        this(width, height, null, sb);
410    }
411
412    /**
413     * Creates a new instance with the specified width and height that will
414     * populate the supplied StringBuilder instance.  This constructor is 
415     * used by the {@link #create()} method, but won't normally be called
416     * directly by user code.
417     * 
418     * @param width  the width of the SVG element.
419     * @param height  the height of the SVG element.
420     * @param units  the units for the width and height above ({@code null} 
421     *     permitted).
422     * @param sb  the string builder ({@code null} not permitted).
423     * 
424     * @since 3.2
425     */
426    public SVGGraphics2D(int width, int height, SVGUnits units, 
427            StringBuilder sb) {
428        this.width = width;
429        this.height = height;
430        this.units = units;
431        this.shapeRendering = "auto";
432        this.textRendering = "auto";
433        this.defsKeyPrefix = String.valueOf(System.nanoTime());
434        this.clip = null;
435        this.imageElements = new ArrayList<ImageElement>();
436        this.filePrefix = "image-";
437        this.fileSuffix = ".png";
438        this.font = new Font("SansSerif", Font.PLAIN, 12);
439        this.fontMapper = new StandardFontMapper();
440        this.zeroStrokeWidth = 0.1;
441        this.sb = sb;
442        this.hints = new RenderingHints(SVGHints.KEY_IMAGE_HANDLING, 
443                SVGHints.VALUE_IMAGE_HANDLING_EMBED);
444        // force the formatters to use a '.' for the decimal point
445        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
446        dfs.setDecimalSeparator('.');
447        this.transformFormat = new DecimalFormat("0.######", dfs);
448        this.geometryFormat = new DecimalFormat("0.##", dfs);
449        this.elementIDs = new HashSet<String>();
450    }
451
452    /**
453     * Creates a new instance that is a child of the supplied parent.
454     * 
455     * @param parent  the parent ({@code null} not permitted).
456     */
457    private SVGGraphics2D(SVGGraphics2D parent) {
458        this(parent.width, parent.height, parent.units, parent.sb);
459        this.shapeRendering = parent.shapeRendering;
460        this.textRendering = parent.textRendering;
461        this.fontMapper = parent.fontMapper;
462        getRenderingHints().add(parent.hints);
463        this.checkStrokeControlHint = parent.checkStrokeControlHint;
464        setTransformDP(parent.transformDP);
465        setGeometryDP(parent.geometryDP);
466        this.defsKeyPrefix = parent.defsKeyPrefix;
467        this.gradientPaints = parent.gradientPaints;
468        this.linearGradientPaints = parent.linearGradientPaints;
469        this.radialGradientPaints = parent.radialGradientPaints;
470        this.clipPaths = parent.clipPaths;
471        this.filePrefix = parent.filePrefix;
472        this.fileSuffix = parent.fileSuffix;
473        this.imageElements = parent.imageElements;
474        this.zeroStrokeWidth = parent.zeroStrokeWidth;
475    }
476    
477    /**
478     * Returns the width for the SVG element, specified in the constructor.
479     * This value will be written to the SVG element returned by the 
480     * {@link #getSVGElement()} method.
481     * 
482     * @return The width for the SVG element. 
483     */
484    public int getWidth() {
485        return this.width;
486    }
487    
488    /**
489     * Returns the height for the SVG element, specified in the constructor.
490     * This value will be written to the SVG element returned by the 
491     * {@link #getSVGElement()} method.
492     * 
493     * @return The height for the SVG element. 
494     */
495    public int getHeight() {
496        return this.height;
497    }
498    
499    /**
500     * Returns the units for the width and height of the SVG element's 
501     * viewport, as specified in the constructor.  The default value is 
502     * {@code null}).
503     * 
504     * @return The units (possibly {@code null}).
505     * 
506     * @since 3.2
507     */
508    public SVGUnits getUnits() {
509        return this.units;
510    }
511
512    /**
513     * Returns the value of the 'shape-rendering' property that will be 
514     * written to the SVG element.  The default value is "auto".
515     * 
516     * @return The shape rendering property.
517     * 
518     * @since 2.0
519     */
520    public String getShapeRendering() {
521        return this.shapeRendering;
522    }
523    
524    /**
525     * Sets the value of the 'shape-rendering' property that will be written to
526     * the SVG element.  Permitted values are "auto", "crispEdges", 
527     * "geometricPrecision", "inherit" and "optimizeSpeed".
528     * 
529     * @param value  the new value.
530     * 
531     * @since 2.0
532     */
533    public void setShapeRendering(String value) {
534        if (!value.equals("auto") && !value.equals("crispEdges") 
535                && !value.equals("geometricPrecision") 
536                && !value.equals("optimizeSpeed")) {
537            throw new IllegalArgumentException("Unrecognised value: " + value);
538        }
539        this.shapeRendering = value;
540    }
541    
542    /**
543     * Returns the value of the 'text-rendering' property that will be 
544     * written to the SVG element.  The default value is "auto".
545     * 
546     * @return The text rendering property.
547     * 
548     * @since 2.0
549     */
550    public String getTextRendering() {
551        return this.textRendering;
552    }
553    
554    /**
555     * Sets the value of the 'text-rendering' property that will be written to
556     * the SVG element.  Permitted values are "auto", "optimizeSpeed", 
557     * "optimizeLegibility" and "geometricPrecision".
558     * 
559     * @param value  the new value.
560     * 
561     * @since 2.0
562     */
563    public void setTextRendering(String value) {
564        if (!value.equals("auto") && !value.equals("optimizeSpeed") 
565                && !value.equals("optimizeLegibility") 
566                && !value.equals("geometricPrecision")) {
567            throw new IllegalArgumentException("Unrecognised value: " + value);
568        }
569        this.textRendering = value;
570    }
571    
572    /**
573     * Returns the flag that controls whether or not this object will observe
574     * the {@code KEY_STROKE_CONTROL} rendering hint.  The default value is
575     * {@code true}.
576     * 
577     * @return A boolean.
578     * 
579     * @see #setCheckStrokeControlHint(boolean) 
580     * @since 2.0
581     */
582    public boolean getCheckStrokeControlHint() {
583        return this.checkStrokeControlHint;
584    }
585    
586    /**
587     * Sets the flag that controls whether or not this object will observe
588     * the {@code KEY_STROKE_CONTROL} rendering hint.  When enabled (the 
589     * default), a hint to normalise strokes will write a {@code stroke-style}
590     * attribute with the value {@code crispEdges}. 
591     * 
592     * @param check  the new flag value.
593     * 
594     * @see #getCheckStrokeControlHint() 
595     * @since 2.0
596     */
597    public void setCheckStrokeControlHint(boolean check) {
598        this.checkStrokeControlHint = check;
599    }
600    
601    /**
602     * Returns the prefix used for all keys in the DEFS element.  The default
603     * value is {@code String.valueOf(System.nanoTime())}.
604     * 
605     * @return The prefix string (never {@code null}).
606     * 
607     * @since 1.9
608     */
609    public String getDefsKeyPrefix() {
610        return this.defsKeyPrefix;
611    }
612    
613    /**
614     * Sets the prefix that will be used for all keys in the DEFS element.
615     * If required, this must be set immediately after construction (before any 
616     * content generation methods have been called).
617     * 
618     * @param prefix  the prefix ({@code null} not permitted).
619     * 
620     * @since 1.9
621     */
622    public void setDefsKeyPrefix(String prefix) {
623        Args.nullNotPermitted(prefix, "prefix");
624        this.defsKeyPrefix = prefix;
625    }
626    
627    /**
628     * Returns the number of decimal places used to write the transformation
629     * matrices in the SVG output.  The default value is 6.
630     * <p>
631     * Note that there is a separate attribute to control the number of decimal
632     * places for geometrical elements in the output (see 
633     * {@link #getGeometryDP()}).
634     * 
635     * @return The number of decimal places.
636     * 
637     * @see #setTransformDP(int) 
638     */
639    public int getTransformDP() {
640        return this.transformDP;    
641    }
642    
643    /**
644     * Sets the number of decimal places used to write the transformation
645     * matrices in the SVG output.  Values in the range 1 to 10 will be used
646     * to configure a formatter to that number of decimal places, for all other
647     * values we revert to the normal {@code String} conversion of 
648     * {@code double} primitives (approximately 16 decimals places).
649     * <p>
650     * Note that there is a separate attribute to control the number of decimal
651     * places for geometrical elements in the output (see 
652     * {@link #setGeometryDP(int)}).
653     * 
654     * @param dp  the number of decimal places (normally 1 to 10).
655     * 
656     * @see #getTransformDP() 
657     */
658    public void setTransformDP(int dp) {
659        this.transformDP = dp;
660        if (dp < 1 || dp > 10) {
661            this.transformFormat = null;
662            return;
663        }
664        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
665        dfs.setDecimalSeparator('.');
666        this.transformFormat = new DecimalFormat("0." 
667                + "##########".substring(0, dp), dfs);
668    }
669    
670    /**
671     * Returns the number of decimal places used to write the coordinates
672     * of geometrical shapes.  The default value is 2.
673     * <p>
674     * Note that there is a separate attribute to control the number of decimal
675     * places for transform matrices in the output (see 
676     * {@link #getTransformDP()}).
677     * 
678     * @return The number of decimal places.
679     */
680    public int getGeometryDP() {
681        return this.geometryDP;    
682    }
683    
684    /**
685     * Sets the number of decimal places used to write the coordinates of
686     * geometrical shapes in the SVG output.  Values in the range 1 to 10 will 
687     * be used to configure a formatter to that number of decimal places, for 
688     * all other values we revert to the normal String conversion of double 
689     * primitives (approximately 16 decimals places).
690     * <p>
691     * Note that there is a separate attribute to control the number of decimal
692     * places for transform matrices in the output (see 
693     * {@link #setTransformDP(int)}).
694     * 
695     * @param dp  the number of decimal places (normally 1 to 10). 
696     */
697    public void setGeometryDP(int dp) {
698        this.geometryDP = dp;
699        if (dp < 1 || dp > 10) {
700            this.geometryFormat = null;
701            return;
702        }
703        DecimalFormatSymbols dfs = new DecimalFormatSymbols();
704        dfs.setDecimalSeparator('.');
705        this.geometryFormat = new DecimalFormat("0." 
706                + "##########".substring(0, dp), dfs);
707    }
708    
709    /**
710     * Returns the prefix used to generate a filename for an image that is
711     * referenced from, rather than embedded in, the SVG element.
712     * 
713     * @return The file prefix (never {@code null}).
714     * 
715     * @since 1.5
716     */
717    public String getFilePrefix() {
718        return this.filePrefix;
719    }
720    
721    /**
722     * Sets the prefix used to generate a filename for any image that is
723     * referenced from the SVG element.
724     * 
725     * @param prefix  the new prefix ({@code null} not permitted).
726     * 
727     * @since 1.5
728     */
729    public void setFilePrefix(String prefix) {
730        Args.nullNotPermitted(prefix, "prefix");
731        this.filePrefix = prefix;
732    }
733
734    /**
735     * Returns the suffix used to generate a filename for an image that is
736     * referenced from, rather than embedded in, the SVG element.
737     * 
738     * @return The file suffix (never {@code null}).
739     * 
740     * @since 1.5
741     */
742    public String getFileSuffix() {
743        return this.fileSuffix;
744    }
745    
746    /**
747     * Sets the suffix used to generate a filename for any image that is
748     * referenced from the SVG element.
749     * 
750     * @param suffix  the new prefix ({@code null} not permitted).
751     * 
752     * @since 1.5
753     */
754    public void setFileSuffix(String suffix) {
755        Args.nullNotPermitted(suffix, "suffix");
756        this.fileSuffix = suffix;
757    }
758    
759    /**
760     * Returns the width to use for the SVG stroke when the AWT stroke
761     * specified has a zero width (the default value is {@code 0.1}).  In 
762     * the Java specification for {@code BasicStroke} it states "If width 
763     * is set to 0.0f, the stroke is rendered as the thinnest possible 
764     * line for the target device and the antialias hint setting."  We don't 
765     * have a means to implement that accurately since we must specify a fixed
766     * width.
767     * 
768     * @return The width.
769     * 
770     * @since 1.9
771     */
772    public double getZeroStrokeWidth() {
773        return this.zeroStrokeWidth;
774    }
775    
776    /**
777     * Sets the width to use for the SVG stroke when the current AWT stroke
778     * has a width of 0.0.
779     * 
780     * @param width  the new width (must be 0 or greater).
781     * 
782     * @since 1.9
783     */
784    public void setZeroStrokeWidth(double width) {
785        if (width < 0.0) {
786            throw new IllegalArgumentException("Width cannot be negative.");
787        }
788        this.zeroStrokeWidth = width;
789    }
790 
791    /**
792     * Returns the device configuration associated with this
793     * {@code Graphics2D}.
794     * 
795     * @return The graphics configuration.
796     */
797    @Override
798    public GraphicsConfiguration getDeviceConfiguration() {
799        if (this.deviceConfiguration == null) {
800            this.deviceConfiguration = new SVGGraphicsConfiguration(this.width,
801                    this.height);
802        }
803        return this.deviceConfiguration;
804    }
805
806    /**
807     * Creates a new graphics object that is a copy of this graphics object
808     * (except that it has not accumulated the drawing operations).  Not sure
809     * yet when or why this would be useful when creating SVG output.  Note
810     * that the {@code fontMapper} object ({@link #getFontMapper()}) is shared 
811     * between the existing instance and the new one.
812     * 
813     * @return A new graphics object.
814     */
815    @Override
816    public Graphics create() {
817        SVGGraphics2D copy = new SVGGraphics2D(this);
818        copy.setRenderingHints(getRenderingHints());
819        copy.setTransform(getTransform());
820        copy.setClip(getClip());
821        copy.setPaint(getPaint());
822        copy.setColor(getColor());
823        copy.setComposite(getComposite());
824        copy.setStroke(getStroke());
825        copy.setFont(getFont());
826        copy.setBackground(getBackground());
827        copy.setFilePrefix(getFilePrefix());
828        copy.setFileSuffix(getFileSuffix());
829        return copy;
830    }
831
832    /**
833     * Returns the paint used to draw or fill shapes (or text).  The default 
834     * value is {@link Color#BLACK}.
835     * 
836     * @return The paint (never {@code null}). 
837     * 
838     * @see #setPaint(java.awt.Paint) 
839     */
840    @Override
841    public Paint getPaint() {
842        return this.paint;
843    }
844    
845    /**
846     * Sets the paint used to draw or fill shapes (or text).  If 
847     * {@code paint} is an instance of {@code Color}, this method will
848     * also update the current color attribute (see {@link #getColor()}). If 
849     * you pass {@code null} to this method, it does nothing (in 
850     * accordance with the JDK specification).
851     * 
852     * @param paint  the paint ({@code null} is permitted but ignored).
853     * 
854     * @see #getPaint() 
855     */
856    @Override
857    public void setPaint(Paint paint) {
858        if (paint == null) {
859            return;
860        }
861        this.paint = paint;
862        this.gradientPaintRef = null;
863        if (paint instanceof Color) {
864            setColor((Color) paint);
865        } else if (paint instanceof GradientPaint) {
866            GradientPaint gp = (GradientPaint) paint;
867            GradientPaintKey key = new GradientPaintKey(gp);
868            String ref = this.gradientPaints.get(key);
869            if (ref == null) {
870                int count = this.gradientPaints.keySet().size();
871                String id = this.defsKeyPrefix + "gp" + count;
872                this.elementIDs.add(id);
873                this.gradientPaints.put(key, id);
874                this.gradientPaintRef = id;
875            } else {
876                this.gradientPaintRef = ref;
877            }
878        } else if (paint instanceof LinearGradientPaint) {
879            LinearGradientPaint lgp = (LinearGradientPaint) paint;
880            LinearGradientPaintKey key = new LinearGradientPaintKey(lgp);
881            String ref = this.linearGradientPaints.get(key);
882            if (ref == null) {
883                int count = this.linearGradientPaints.keySet().size();
884                String id = this.defsKeyPrefix + "lgp" + count;
885                this.elementIDs.add(id);
886                this.linearGradientPaints.put(key, id);
887                this.gradientPaintRef = id;
888            }
889        } else if (paint instanceof RadialGradientPaint) {
890            RadialGradientPaint rgp = (RadialGradientPaint) paint;
891            RadialGradientPaintKey key = new RadialGradientPaintKey(rgp);
892            String ref = this.radialGradientPaints.get(key);
893            if (ref == null) {
894                int count = this.radialGradientPaints.keySet().size();
895                String id = this.defsKeyPrefix + "rgp" + count;
896                this.elementIDs.add(id);
897                this.radialGradientPaints.put(key, id);
898                this.gradientPaintRef = id;
899            }
900        }
901    }
902
903    /**
904     * Returns the foreground color.  This method exists for backwards
905     * compatibility in AWT, you should use the {@link #getPaint()} method.
906     * 
907     * @return The foreground color (never {@code null}).
908     * 
909     * @see #getPaint() 
910     */
911    @Override
912    public Color getColor() {
913        return this.color;
914    }
915
916    /**
917     * Sets the foreground color.  This method exists for backwards 
918     * compatibility in AWT, you should use the 
919     * {@link #setPaint(java.awt.Paint)} method.
920     * 
921     * @param c  the color ({@code null} permitted but ignored). 
922     * 
923     * @see #setPaint(java.awt.Paint) 
924     */
925    @Override
926    public void setColor(Color c) {
927        if (c == null) {
928            return;
929        }
930        this.color = c;
931        this.paint = c;
932    }
933
934    /**
935     * Returns the background color.  The default value is {@link Color#BLACK}.
936     * This is used by the {@link #clearRect(int, int, int, int)} method.
937     * 
938     * @return The background color (possibly {@code null}). 
939     * 
940     * @see #setBackground(java.awt.Color) 
941     */
942    @Override
943    public Color getBackground() {
944        return this.background;
945    }
946
947    /**
948     * Sets the background color.  This is used by the 
949     * {@link #clearRect(int, int, int, int)} method.  The reference 
950     * implementation allows {@code null} for the background color so
951     * we allow that too (but for that case, the clearRect method will do 
952     * nothing).
953     * 
954     * @param color  the color ({@code null} permitted).
955     * 
956     * @see #getBackground() 
957     */
958    @Override
959    public void setBackground(Color color) {
960        this.background = color;
961    }
962
963    /**
964     * Returns the current composite.
965     * 
966     * @return The current composite (never {@code null}).
967     * 
968     * @see #setComposite(java.awt.Composite) 
969     */
970    @Override
971    public Composite getComposite() {
972        return this.composite;
973    }
974    
975    /**
976     * Sets the composite (only {@code AlphaComposite} is handled).
977     * 
978     * @param comp  the composite ({@code null} not permitted).
979     * 
980     * @see #getComposite() 
981     */
982    @Override
983    public void setComposite(Composite comp) {
984        if (comp == null) {
985            throw new IllegalArgumentException("Null 'comp' argument.");
986        }
987        this.composite = comp;
988    }
989
990    /**
991     * Returns the current stroke (used when drawing shapes). 
992     * 
993     * @return The current stroke (never {@code null}). 
994     * 
995     * @see #setStroke(java.awt.Stroke) 
996     */
997    @Override
998    public Stroke getStroke() {
999        return this.stroke;
1000    }
1001
1002    /**
1003     * Sets the stroke that will be used to draw shapes.
1004     * 
1005     * @param s  the stroke ({@code null} not permitted).
1006     * 
1007     * @see #getStroke() 
1008     */
1009    @Override
1010    public void setStroke(Stroke s) {
1011        if (s == null) {
1012            throw new IllegalArgumentException("Null 's' argument.");
1013        }
1014        this.stroke = s;
1015    }
1016
1017    /**
1018     * Returns the current value for the specified hint.  See the 
1019     * {@link SVGHints} class for information about the hints that can be
1020     * used with {@code SVGGraphics2D}.
1021     * 
1022     * @param hintKey  the hint key ({@code null} permitted, but the
1023     *     result will be {@code null} also).
1024     * 
1025     * @return The current value for the specified hint 
1026     *     (possibly {@code null}).
1027     * 
1028     * @see #setRenderingHint(java.awt.RenderingHints.Key, java.lang.Object) 
1029     */
1030    @Override
1031    public Object getRenderingHint(RenderingHints.Key hintKey) {
1032        return this.hints.get(hintKey);
1033    }
1034
1035    /**
1036     * Sets the value for a hint.  See the {@link SVGHints} class for 
1037     * information about the hints that can be used with this implementation.
1038     * 
1039     * @param hintKey  the hint key ({@code null} not permitted).
1040     * @param hintValue  the hint value.
1041     * 
1042     * @see #getRenderingHint(java.awt.RenderingHints.Key) 
1043     */
1044    @Override
1045    public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {
1046        if (hintKey == null) {
1047            throw new NullPointerException("Null 'hintKey' not permitted.");
1048        }
1049        // KEY_BEGIN_GROUP and KEY_END_GROUP are handled as special cases that
1050        // never get stored in the hints map...
1051        if (SVGHints.isBeginGroupKey(hintKey)) {
1052            String groupId = null;
1053            String ref = null;
1054            List<Entry> otherKeysAndValues = null;
1055            if (hintValue instanceof String) {
1056                groupId = (String) hintValue;
1057             } else if (hintValue instanceof Map) {
1058                Map hintValueMap = (Map) hintValue;
1059                groupId = (String) hintValueMap.get("id");
1060                ref = (String) hintValueMap.get("ref");
1061                for (final Object obj: hintValueMap.entrySet()) {
1062                   final Entry e = (Entry) obj;
1063                   final Object key = e.getKey();
1064                   if ("id".equals(key) || "ref".equals(key)) {
1065                      continue;
1066                   }
1067                   if (otherKeysAndValues == null) {
1068                      otherKeysAndValues = new ArrayList<Entry>();
1069                   }
1070                   otherKeysAndValues.add(e);
1071                }
1072            }
1073            this.sb.append("<g");
1074            if (groupId != null) {
1075                if (this.elementIDs.contains(groupId)) {
1076                    throw new IllegalArgumentException("The group id (" 
1077                            + groupId + ") is not unique.");
1078                } else {
1079                    this.sb.append(" id=\"").append(groupId).append("\"");
1080                    this.elementIDs.add(groupId);
1081                }
1082            }
1083            if (ref != null) {
1084                this.sb.append(" jfreesvg:ref=\"");
1085                this.sb.append(SVGUtils.escapeForXML(ref)).append("\"");
1086            }
1087            if (otherKeysAndValues != null) {
1088               for (final Entry e: otherKeysAndValues) {
1089                    this.sb.append(" ").append(e.getKey()).append("=\"");
1090                    this.sb.append(SVGUtils.escapeForXML(String.valueOf(
1091                            e.getValue()))).append("\"");
1092               }
1093            }
1094            this.sb.append(">");
1095        } else if (SVGHints.isEndGroupKey(hintKey)) {
1096            this.sb.append("</g>\n");
1097        } else if (SVGHints.isElementTitleKey(hintKey) && (hintValue != null)) {
1098            this.sb.append("<title>");
1099            this.sb.append(SVGUtils.escapeForXML(String.valueOf(hintValue)));
1100            this.sb.append("</title>");     
1101        } else {
1102            this.hints.put(hintKey, hintValue);
1103        }
1104    }
1105
1106    /**
1107     * Returns a copy of the rendering hints.  Modifying the returned copy
1108     * will have no impact on the state of this {@code Graphics2D} instance.
1109     * 
1110     * @return The rendering hints (never {@code null}).
1111     * 
1112     * @see #setRenderingHints(java.util.Map) 
1113     */
1114    @Override
1115    public RenderingHints getRenderingHints() {
1116        return (RenderingHints) this.hints.clone();
1117    }
1118
1119    /**
1120     * Sets the rendering hints to the specified collection.
1121     * 
1122     * @param hints  the new set of hints ({@code null} not permitted).
1123     * 
1124     * @see #getRenderingHints() 
1125     */
1126    @Override
1127    public void setRenderingHints(Map<?, ?> hints) {
1128        this.hints.clear();
1129        addRenderingHints(hints);
1130    }
1131
1132    /**
1133     * Adds all the supplied rendering hints.
1134     * 
1135     * @param hints  the hints ({@code null} not permitted).
1136     */
1137    @Override
1138    public void addRenderingHints(Map<?, ?> hints) {
1139        this.hints.putAll(hints);
1140    }
1141
1142    /**
1143     * A utility method that appends an optional element id if one is 
1144     * specified via the rendering hints.
1145     * 
1146     * @param sb  the string builder ({@code null} not permitted). 
1147     */
1148    private void appendOptionalElementIDFromHint(StringBuilder sb) {
1149        String elementID = (String) this.hints.get(SVGHints.KEY_ELEMENT_ID);
1150        if (elementID != null) {
1151            this.hints.put(SVGHints.KEY_ELEMENT_ID, null); // clear it
1152            if (this.elementIDs.contains(elementID)) {
1153                throw new IllegalStateException("The element id " 
1154                        + elementID + " is already used.");
1155            } else {
1156                this.elementIDs.add(elementID);
1157            }
1158            this.sb.append("id=\"").append(elementID).append("\" ");
1159        }
1160    }
1161    
1162    /**
1163     * Draws the specified shape with the current {@code paint} and 
1164     * {@code stroke}.  There is direct handling for {@code Line2D}, 
1165     * {@code Rectangle2D}, {@code Ellipse2D} and {@code Path2D}.  All other 
1166     * shapes are mapped to a {@code GeneralPath} and then drawn (effectively 
1167     * as {@code Path2D} objects).
1168     * 
1169     * @param s  the shape ({@code null} not permitted).
1170     * 
1171     * @see #fill(java.awt.Shape) 
1172     */
1173    @Override
1174    public void draw(Shape s) {
1175        // if the current stroke is not a BasicStroke then it is handled as
1176        // a special case
1177        if (!(this.stroke instanceof BasicStroke)) {
1178            fill(this.stroke.createStrokedShape(s));
1179            return;
1180        }
1181        if (s instanceof Line2D) {
1182            Line2D l = (Line2D) s;
1183            this.sb.append("<line ");
1184            appendOptionalElementIDFromHint(this.sb);
1185            this.sb.append("x1=\"").append(geomDP(l.getX1()))
1186                    .append("\" y1=\"").append(geomDP(l.getY1()))
1187                    .append("\" x2=\"").append(geomDP(l.getX2()))
1188                    .append("\" y2=\"").append(geomDP(l.getY2()))
1189                    .append("\" ");
1190            this.sb.append("style=\"").append(strokeStyle()).append("\" ");
1191            this.sb.append("transform=\"").append(getSVGTransform(
1192                    this.transform)).append("\" ");
1193            this.sb.append(getClipPathRef());
1194            this.sb.append("/>");
1195        } else if (s instanceof Rectangle2D) {
1196            Rectangle2D r = (Rectangle2D) s;
1197            this.sb.append("<rect ");
1198            appendOptionalElementIDFromHint(this.sb);
1199            this.sb.append("x=\"").append(geomDP(r.getX()))
1200                    .append("\" y=\"").append(geomDP(r.getY()))
1201                    .append("\" width=\"").append(geomDP(r.getWidth()))
1202                    .append("\" height=\"").append(geomDP(r.getHeight()))
1203                    .append("\" ");
1204            this.sb.append("style=\"").append(strokeStyle())
1205                    .append("; fill: none").append("\" ");
1206            this.sb.append("transform=\"").append(getSVGTransform(
1207                    this.transform)).append("\" ");
1208            this.sb.append(getClipPathRef());
1209            this.sb.append("/>");
1210        } else if (s instanceof Ellipse2D) {
1211            Ellipse2D e = (Ellipse2D) s;
1212            this.sb.append("<ellipse ");
1213            appendOptionalElementIDFromHint(this.sb);
1214            this.sb.append("cx=\"").append(geomDP(e.getCenterX()))
1215                    .append("\" cy=\"").append(geomDP(e.getCenterY()))
1216                    .append("\" rx=\"").append(geomDP(e.getWidth() / 2.0))
1217                    .append("\" ry=\"").append(geomDP(e.getHeight() / 2.0))
1218                    .append("\" ");
1219            this.sb.append("style=\"").append(strokeStyle())
1220                    .append("; fill: none").append("\" ");
1221            this.sb.append("transform=\"").append(getSVGTransform(
1222                    this.transform)).append("\" ");
1223            this.sb.append(getClipPathRef());
1224            this.sb.append("/>");        
1225        } else if (s instanceof Path2D) {
1226            Path2D path = (Path2D) s;
1227            this.sb.append("<g ");
1228            appendOptionalElementIDFromHint(this.sb);
1229            this.sb.append("style=\"").append(strokeStyle())
1230                    .append("; fill: none").append("\" ");
1231            this.sb.append("transform=\"").append(getSVGTransform(
1232                    this.transform)).append("\" ");
1233            this.sb.append(getClipPathRef());
1234            this.sb.append(">");
1235            this.sb.append("<path ").append(getSVGPathData(path)).append("/>");
1236            this.sb.append("</g>");
1237        } else {
1238            draw(new GeneralPath(s)); // handled as a Path2D next time through
1239        }
1240    }
1241
1242    /**
1243     * Fills the specified shape with the current {@code paint}.  There is
1244     * direct handling for {@code Rectangle2D}, {@code Ellipse2D} and 
1245     * {@code Path2D}.  All other shapes are mapped to a {@code GeneralPath} 
1246     * and then filled.
1247     * 
1248     * @param s  the shape ({@code null} not permitted). 
1249     * 
1250     * @see #draw(java.awt.Shape) 
1251     */
1252    @Override
1253    public void fill(Shape s) {
1254        if (s instanceof Rectangle2D) {
1255            Rectangle2D r = (Rectangle2D) s;
1256            if (r.isEmpty()) {
1257                return;
1258            }
1259            this.sb.append("<rect ");
1260            appendOptionalElementIDFromHint(this.sb);
1261            this.sb.append("x=\"").append(geomDP(r.getX()))
1262                    .append("\" y=\"").append(geomDP(r.getY()))
1263                    .append("\" width=\"").append(geomDP(r.getWidth()))
1264                    .append("\" height=\"").append(geomDP(r.getHeight()))
1265                    .append("\" ");
1266            this.sb.append("style=\"").append(getSVGFillStyle()).append("\" ");
1267            this.sb.append("transform=\"").append(getSVGTransform(
1268                    this.transform)).append("\" ");
1269            this.sb.append(getClipPathRef());
1270            this.sb.append("/>");
1271        } else if (s instanceof Ellipse2D) {
1272            Ellipse2D e = (Ellipse2D) s;
1273            this.sb.append("<ellipse ");
1274            appendOptionalElementIDFromHint(this.sb);
1275            this.sb.append("cx=\"").append(geomDP(e.getCenterX()))
1276                    .append("\" cy=\"").append(geomDP(e.getCenterY()))
1277                    .append("\" rx=\"").append(geomDP(e.getWidth() / 2.0))
1278                    .append("\" ry=\"").append(geomDP(e.getHeight() / 2.0))
1279                    .append("\" ");
1280            this.sb.append("style=\"").append(getSVGFillStyle()).append("\" ");
1281            this.sb.append("transform=\"").append(getSVGTransform(
1282                    this.transform)).append("\" ");
1283            this.sb.append(getClipPathRef());
1284            this.sb.append("/>");        
1285        } else if (s instanceof Path2D) {
1286            Path2D path = (Path2D) s;
1287            this.sb.append("<g ");
1288            appendOptionalElementIDFromHint(this.sb);
1289            this.sb.append("style=\"").append(getSVGFillStyle());
1290            this.sb.append("; stroke: none").append("\" ");
1291            this.sb.append("transform=\"").append(getSVGTransform(
1292                    this.transform)).append("\" ");
1293            this.sb.append(getClipPathRef());
1294            this.sb.append(">");
1295            this.sb.append("<path ").append(getSVGPathData(path)).append("/>");
1296            this.sb.append("</g>");
1297        }  else {
1298            fill(new GeneralPath(s));  // handled as a Path2D next time through
1299        }
1300    }
1301    
1302    /**
1303     * Creates an SVG path string for the supplied Java2D path.
1304     * 
1305     * @param path  the path ({@code null} not permitted).
1306     * 
1307     * @return An SVG path string. 
1308     */
1309    private String getSVGPathData(Path2D path) {
1310        StringBuilder b = new StringBuilder("d=\"");
1311        float[] coords = new float[6];
1312        boolean first = true;
1313        PathIterator iterator = path.getPathIterator(null);
1314        while (!iterator.isDone()) {
1315            int type = iterator.currentSegment(coords);
1316            if (!first) {
1317                b.append(" ");
1318            }
1319            first = false;
1320            switch (type) {
1321            case (PathIterator.SEG_MOVETO):
1322                b.append("M ").append(geomDP(coords[0])).append(" ")
1323                        .append(geomDP(coords[1]));
1324                break;
1325            case (PathIterator.SEG_LINETO):
1326                b.append("L ").append(geomDP(coords[0])).append(" ")
1327                        .append(geomDP(coords[1]));
1328                break;
1329            case (PathIterator.SEG_QUADTO):
1330                b.append("Q ").append(geomDP(coords[0]))
1331                        .append(" ").append(geomDP(coords[1]))
1332                        .append(" ").append(geomDP(coords[2]))
1333                        .append(" ").append(geomDP(coords[3]));
1334                break;
1335            case (PathIterator.SEG_CUBICTO):
1336                b.append("C ").append(geomDP(coords[0])).append(" ")
1337                        .append(geomDP(coords[1])).append(" ")
1338                        .append(geomDP(coords[2])).append(" ")
1339                        .append(geomDP(coords[3])).append(" ")
1340                        .append(geomDP(coords[4])).append(" ")
1341                        .append(geomDP(coords[5]));
1342                break;
1343            case (PathIterator.SEG_CLOSE):
1344                b.append("Z ");
1345                break;
1346            default:
1347                break;
1348            }
1349            iterator.next();
1350        }  
1351        return b.append("\"").toString();
1352    }
1353
1354    /**
1355     * Returns the current alpha (transparency) in the range 0.0 to 1.0.
1356     * If the current composite is an {@link AlphaComposite} we read the alpha
1357     * value from there, otherwise this method returns 1.0.
1358     * 
1359     * @return The current alpha (transparency) in the range 0.0 to 1.0.
1360     */
1361    private float getAlpha() {
1362       float alpha = 1.0f;
1363       if (this.composite instanceof AlphaComposite) {
1364           AlphaComposite ac = (AlphaComposite) this.composite;
1365           alpha = ac.getAlpha();
1366       }
1367       return alpha;
1368    }
1369
1370    /**
1371     * Returns an SVG color string based on the current paint.  To handle
1372     * {@code GradientPaint} we rely on the {@code setPaint()} method
1373     * having set the {@code gradientPaintRef} attribute.
1374     * 
1375     * @return An SVG color string. 
1376     */
1377    private String svgColorStr() {
1378        String result = "black;";
1379        if (this.paint instanceof Color) {
1380            return rgbColorStr((Color) this.paint);
1381        } else if (this.paint instanceof GradientPaint 
1382                || this.paint instanceof LinearGradientPaint
1383                || this.paint instanceof RadialGradientPaint) {
1384            return "url(#" + this.gradientPaintRef + ")";
1385        }
1386        return result;
1387    }
1388    
1389    /**
1390     * Returns the SVG RGB color string for the specified color.
1391     * 
1392     * @param c  the color ({@code null} not permitted).
1393     * 
1394     * @return The SVG RGB color string.
1395     */
1396    private String rgbColorStr(Color c) {
1397        StringBuilder b = new StringBuilder("rgb(");
1398        b.append(c.getRed()).append(",").append(c.getGreen()).append(",")
1399                .append(c.getBlue()).append(")");
1400        return b.toString();
1401    }
1402    
1403    /**
1404     * Returns a string representing the specified color in RGBA format.
1405     * 
1406     * @param c  the color ({@code null} not permitted).
1407     * 
1408     * @return The SVG RGBA color string.
1409     */
1410    private String rgbaColorStr(Color c) {
1411        StringBuilder b = new StringBuilder("rgba(");
1412        double alphaPercent = c.getAlpha() / 255.0;
1413        b.append(c.getRed()).append(",").append(c.getGreen()).append(",")
1414                .append(c.getBlue());
1415        b.append(",").append(transformDP(alphaPercent));
1416        b.append(")");
1417        return b.toString();
1418    }
1419    
1420    private static final String DEFAULT_STROKE_CAP = "butt";
1421    private static final String DEFAULT_STROKE_JOIN = "miter";
1422    private static final float DEFAULT_MITER_LIMIT = 4.0f;
1423    
1424    /**
1425     * Returns a stroke style string based on the current stroke and
1426     * alpha settings.
1427     * 
1428     * @return A stroke style string.
1429     */
1430    private String strokeStyle() {
1431        double strokeWidth = 1.0f;
1432        String strokeCap = DEFAULT_STROKE_CAP;
1433        String strokeJoin = DEFAULT_STROKE_JOIN;
1434        float miterLimit = DEFAULT_MITER_LIMIT;
1435        float[] dashArray = new float[0];
1436        if (this.stroke instanceof BasicStroke) {
1437            BasicStroke bs = (BasicStroke) this.stroke;
1438            strokeWidth = bs.getLineWidth() > 0.0 ? bs.getLineWidth() 
1439                    : this.zeroStrokeWidth;
1440            switch (bs.getEndCap()) {
1441                case BasicStroke.CAP_ROUND:
1442                    strokeCap = "round";
1443                    break;
1444                case BasicStroke.CAP_SQUARE:
1445                    strokeCap = "square";
1446                    break;
1447                case BasicStroke.CAP_BUTT:
1448                default:
1449                    // already set to "butt"    
1450            }
1451            switch (bs.getLineJoin()) {
1452                case BasicStroke.JOIN_BEVEL:
1453                    strokeJoin = "bevel";
1454                    break;
1455                case BasicStroke.JOIN_ROUND:
1456                    strokeJoin = "round";
1457                    break;
1458                case BasicStroke.JOIN_MITER:
1459                default:
1460                    // already set to "miter"
1461            }
1462            miterLimit = bs.getMiterLimit();
1463            dashArray = bs.getDashArray();
1464        }
1465        StringBuilder b = new StringBuilder();
1466        b.append("stroke-width: ").append(strokeWidth).append(";");
1467        b.append("stroke: ").append(svgColorStr()).append(";");
1468        b.append("stroke-opacity: ").append(getColorAlpha() * getAlpha())
1469                .append(";");
1470        if (!strokeCap.equals(DEFAULT_STROKE_CAP)) {
1471            b.append("stroke-linecap: ").append(strokeCap).append(";");        
1472        }
1473        if (!strokeJoin.equals(DEFAULT_STROKE_JOIN)) {
1474            b.append("stroke-linejoin: ").append(strokeJoin).append(";");        
1475        }
1476        if (Math.abs(DEFAULT_MITER_LIMIT - miterLimit) < 0.001) {
1477            b.append("stroke-miterlimit: ").append(geomDP(miterLimit));        
1478        }
1479        if (dashArray != null && dashArray.length != 0) {
1480            b.append("stroke-dasharray: ");
1481            for (int i = 0; i < dashArray.length; i++) {
1482                if (i != 0) b.append(", ");
1483                b.append(dashArray[i]);
1484            }
1485            b.append(";");
1486        }
1487        if (this.checkStrokeControlHint) {
1488            Object hint = getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
1489            if (RenderingHints.VALUE_STROKE_NORMALIZE.equals(hint) 
1490                    && !this.shapeRendering.equals("crispEdges")) {
1491                b.append("shape-rendering:crispEdges;");
1492            }
1493            if (RenderingHints.VALUE_STROKE_PURE.equals(hint) 
1494                    && !this.shapeRendering.equals("geometricPrecision")) {
1495                b.append("shape-rendering:geometricPrecision;");
1496            }
1497        }
1498        return b.toString();
1499    }
1500    
1501    /**
1502     * Returns the alpha value of the current {@code paint}, or {@code 1.0f} if
1503     * it is not an instance of {@code Color}.
1504     * 
1505     * @return The alpha value (in the range {@code 0.0} to {@code 1.0}. 
1506     */
1507    private float getColorAlpha() {
1508        if (this.paint instanceof Color) {
1509            Color c = (Color) this.paint;
1510            return c.getAlpha() / 255.0f; 
1511        } 
1512        return 1f;
1513    }
1514    
1515    /**
1516     * Returns a fill style string based on the current paint and
1517     * alpha settings.
1518     * 
1519     * @return A fill style string.
1520     */
1521    private String getSVGFillStyle() {
1522        StringBuilder b = new StringBuilder();
1523        b.append("fill: ").append(svgColorStr()).append("; ");
1524        b.append("fill-opacity: ").append(getColorAlpha() * getAlpha());
1525        return b.toString();
1526    }
1527
1528    /**
1529     * Returns the current font used for drawing text.
1530     * 
1531     * @return The current font (never {@code null}).
1532     * 
1533     * @see #setFont(java.awt.Font) 
1534     */
1535    @Override
1536    public Font getFont() {
1537        return this.font;
1538    }
1539
1540    /**
1541     * Sets the font to be used for drawing text.
1542     * 
1543     * @param font  the font ({@code null} is permitted but ignored).
1544     * 
1545     * @see #getFont() 
1546     */
1547    @Override
1548    public void setFont(Font font) {
1549        if (font == null) {
1550            return;
1551        }
1552        this.font = font;
1553    }
1554    
1555    /**
1556     * Returns the font mapper (an object that optionally maps font family
1557     * names to alternates).  The default mapper will convert Java logical 
1558     * font names to the equivalent SVG generic font name, and leave all other
1559     * font names unchanged.
1560     * 
1561     * @return The font mapper (never {@code null}).
1562     * 
1563     * @see #setFontMapper(org.jfree.graphics2d.svg.FontMapper) 
1564     * @since 1.5
1565     */
1566    public FontMapper getFontMapper() {
1567        return this.fontMapper;
1568    }
1569    
1570    /**
1571     * Sets the font mapper.
1572     * 
1573     * @param mapper  the font mapper ({@code null} not permitted).
1574     * 
1575     * @since 1.5
1576     */
1577    public void setFontMapper(FontMapper mapper) {
1578        Args.nullNotPermitted(mapper, "mapper");
1579        this.fontMapper = mapper;
1580    }
1581    
1582    /**
1583     * Returns a string containing font style info.
1584     * 
1585     * @return A string containing font style info.
1586     */
1587    private String getSVGFontStyle() {
1588        StringBuilder b = new StringBuilder();
1589        b.append("fill: ").append(svgColorStr()).append("; ");
1590        b.append("fill-opacity: ").append(getColorAlpha() * getAlpha())
1591                .append("; ");
1592        String fontFamily = this.fontMapper.mapFont(this.font.getFamily());
1593        b.append("font-family: ").append(fontFamily).append("; ");
1594        b.append("font-size: ").append(this.font.getSize()).append("px;");
1595        if (this.font.isBold()) {
1596            b.append(" font-weight: bold;");
1597        }
1598        if (this.font.isItalic()) {
1599            b.append(" font-style: italic;");
1600        }
1601        return b.toString();
1602    }
1603
1604    /**
1605     * Returns the font metrics for the specified font.
1606     * 
1607     * @param f  the font.
1608     * 
1609     * @return The font metrics. 
1610     */
1611    @Override
1612    public FontMetrics getFontMetrics(Font f) {
1613        if (this.fmImage == null) {
1614            this.fmImage = new BufferedImage(10, 10, 
1615                    BufferedImage.TYPE_INT_RGB);
1616            this.fmImageG2D = this.fmImage.createGraphics();
1617            this.fmImageG2D.setRenderingHint(
1618                    RenderingHints.KEY_FRACTIONALMETRICS, 
1619                    RenderingHints.VALUE_FRACTIONALMETRICS_ON);
1620        }
1621        return this.fmImageG2D.getFontMetrics(f);
1622    }
1623    
1624    /**
1625     * Returns the font render context.
1626     * 
1627     * @return The font render context (never {@code null}).
1628     */
1629    @Override
1630    public FontRenderContext getFontRenderContext() {
1631        return this.fontRenderContext;
1632    }
1633
1634    /**
1635     * Draws a string at {@code (x, y)}.  The start of the text at the
1636     * baseline level will be aligned with the {@code (x, y)} point.
1637     * <br><br>
1638     * Note that you can make use of the {@link SVGHints#KEY_TEXT_RENDERING} 
1639     * hint when drawing strings (this is completely optional though). 
1640     * 
1641     * @param str  the string ({@code null} not permitted).
1642     * @param x  the x-coordinate.
1643     * @param y  the y-coordinate.
1644     * 
1645     * @see #drawString(java.lang.String, float, float) 
1646     */
1647    @Override
1648    public void drawString(String str, int x, int y) {
1649        drawString(str, (float) x, (float) y);
1650    }
1651
1652    /**
1653     * Draws a string at {@code (x, y)}. The start of the text at the
1654     * baseline level will be aligned with the {@code (x, y)} point.
1655     * <br><br>
1656     * Note that you can make use of the {@link SVGHints#KEY_TEXT_RENDERING} 
1657     * hint when drawing strings (this is completely optional though). 
1658     * 
1659     * @param str  the string ({@code null} not permitted).
1660     * @param x  the x-coordinate.
1661     * @param y  the y-coordinate.
1662     */
1663    @Override
1664    public void drawString(String str, float x, float y) {
1665        if (str == null) {
1666            throw new NullPointerException("Null 'str' argument.");
1667        }
1668        if (!SVGHints.VALUE_DRAW_STRING_TYPE_VECTOR.equals(
1669                this.hints.get(SVGHints.KEY_DRAW_STRING_TYPE))) {
1670            this.sb.append("<g ");
1671            appendOptionalElementIDFromHint(this.sb);
1672            this.sb.append("transform=\"").append(getSVGTransform(
1673                    this.transform)).append("\">");
1674            this.sb.append("<text x=\"").append(geomDP(x))
1675                    .append("\" y=\"").append(geomDP(y))
1676                    .append("\"");
1677            this.sb.append(" style=\"").append(getSVGFontStyle()).append("\"");
1678            Object hintValue = getRenderingHint(SVGHints.KEY_TEXT_RENDERING);
1679            if (hintValue != null) {
1680                String textRenderValue = hintValue.toString();
1681                this.sb.append(" text-rendering=\"").append(textRenderValue)
1682                        .append("\"");
1683            }
1684            this.sb.append(" ").append(getClipPathRef());
1685            this.sb.append(">");
1686            this.sb.append(SVGUtils.escapeForXML(str)).append("</text>");
1687            this.sb.append("</g>");
1688        } else {
1689            AttributedString as = new AttributedString(str, 
1690                    this.font.getAttributes());
1691            drawString(as.getIterator(), x, y);
1692        }
1693    }
1694
1695    /**
1696     * Draws a string of attributed characters at {@code (x, y)}.  The 
1697     * call is delegated to 
1698     * {@link #drawString(AttributedCharacterIterator, float, float)}. 
1699     * 
1700     * @param iterator  an iterator for the characters.
1701     * @param x  the x-coordinate.
1702     * @param y  the x-coordinate.
1703     */
1704    @Override
1705    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
1706        drawString(iterator, (float) x, (float) y); 
1707    }
1708
1709    /**
1710     * Draws a string of attributed characters at {@code (x, y)}. 
1711     * 
1712     * @param iterator  an iterator over the characters ({@code null} not 
1713     *     permitted).
1714     * @param x  the x-coordinate.
1715     * @param y  the y-coordinate.
1716     */
1717    @Override
1718    public void drawString(AttributedCharacterIterator iterator, float x, 
1719            float y) {
1720        Set<Attribute> s = iterator.getAllAttributeKeys();
1721        if (!s.isEmpty()) {
1722            TextLayout layout = new TextLayout(iterator, 
1723                    getFontRenderContext());
1724            layout.draw(this, x, y);
1725        } else {
1726            StringBuilder strb = new StringBuilder();
1727            iterator.first();
1728            for (int i = iterator.getBeginIndex(); i < iterator.getEndIndex(); 
1729                    i++) {
1730                strb.append(iterator.current());
1731                iterator.next();
1732            }
1733            drawString(strb.toString(), x, y);
1734        }
1735    }
1736
1737    /**
1738     * Draws the specified glyph vector at the location {@code (x, y)}.
1739     * 
1740     * @param g  the glyph vector ({@code null} not permitted).
1741     * @param x  the x-coordinate.
1742     * @param y  the y-coordinate.
1743     */
1744    @Override
1745    public void drawGlyphVector(GlyphVector g, float x, float y) {
1746        fill(g.getOutline(x, y));
1747    }
1748
1749    /**
1750     * Applies the translation {@code (tx, ty)}.  This call is delegated 
1751     * to {@link #translate(double, double)}.
1752     * 
1753     * @param tx  the x-translation.
1754     * @param ty  the y-translation.
1755     * 
1756     * @see #translate(double, double) 
1757     */
1758    @Override
1759    public void translate(int tx, int ty) {
1760        translate((double) tx, (double) ty);
1761    }
1762
1763    /**
1764     * Applies the translation {@code (tx, ty)}.
1765     * 
1766     * @param tx  the x-translation.
1767     * @param ty  the y-translation.
1768     */
1769    @Override
1770    public void translate(double tx, double ty) {
1771        AffineTransform t = getTransform();
1772        t.translate(tx, ty);
1773        setTransform(t);
1774    }
1775
1776    /**
1777     * Applies a rotation (anti-clockwise) about {@code (0, 0)}.
1778     * 
1779     * @param theta  the rotation angle (in radians). 
1780     */
1781    @Override
1782    public void rotate(double theta) {
1783        AffineTransform t = getTransform();
1784        t.rotate(theta);
1785        setTransform(t);
1786    }
1787
1788    /**
1789     * Applies a rotation (anti-clockwise) about {@code (x, y)}.
1790     * 
1791     * @param theta  the rotation angle (in radians).
1792     * @param x  the x-coordinate.
1793     * @param y  the y-coordinate.
1794     */
1795    @Override
1796    public void rotate(double theta, double x, double y) {
1797        translate(x, y);
1798        rotate(theta);
1799        translate(-x, -y);
1800    }
1801
1802    /**
1803     * Applies a scale transformation.
1804     * 
1805     * @param sx  the x-scaling factor.
1806     * @param sy  the y-scaling factor.
1807     */
1808    @Override
1809    public void scale(double sx, double sy) {
1810        AffineTransform t = getTransform();
1811        t.scale(sx, sy);
1812        setTransform(t);
1813    }
1814
1815    /**
1816     * Applies a shear transformation. This is equivalent to the following 
1817     * call to the {@code transform} method:
1818     * <br><br>
1819     * <ul><li>
1820     * {@code transform(AffineTransform.getShearInstance(shx, shy));}
1821     * </ul>
1822     * 
1823     * @param shx  the x-shear factor.
1824     * @param shy  the y-shear factor.
1825     */
1826    @Override
1827    public void shear(double shx, double shy) {
1828        transform(AffineTransform.getShearInstance(shx, shy));
1829    }
1830
1831    /**
1832     * Applies this transform to the existing transform by concatenating it.
1833     * 
1834     * @param t  the transform ({@code null} not permitted). 
1835     */
1836    @Override
1837    public void transform(AffineTransform t) {
1838        AffineTransform tx = getTransform();
1839        tx.concatenate(t);
1840        setTransform(tx);
1841    }
1842
1843    /**
1844     * Returns a copy of the current transform.
1845     * 
1846     * @return A copy of the current transform (never {@code null}).
1847     * 
1848     * @see #setTransform(java.awt.geom.AffineTransform) 
1849     */
1850    @Override
1851    public AffineTransform getTransform() {
1852        return (AffineTransform) this.transform.clone();
1853    }
1854
1855    /**
1856     * Sets the transform.
1857     * 
1858     * @param t  the new transform ({@code null} permitted, resets to the
1859     *     identity transform).
1860     * 
1861     * @see #getTransform() 
1862     */
1863    @Override
1864    public void setTransform(AffineTransform t) {
1865        if (t == null) {
1866            this.transform = new AffineTransform();
1867        } else {
1868            this.transform = new AffineTransform(t);
1869        }
1870        this.clipRef = null;
1871    }
1872
1873    /**
1874     * Returns {@code true} if the rectangle (in device space) intersects
1875     * with the shape (the interior, if {@code onStroke} is {@code false}, 
1876     * otherwise the stroked outline of the shape).
1877     * 
1878     * @param rect  a rectangle (in device space).
1879     * @param s the shape.
1880     * @param onStroke  test the stroked outline only?
1881     * 
1882     * @return A boolean. 
1883     */
1884    @Override
1885    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
1886        Shape ts;
1887        if (onStroke) {
1888            ts = this.transform.createTransformedShape(
1889                    this.stroke.createStrokedShape(s));
1890        } else {
1891            ts = this.transform.createTransformedShape(s);
1892        }
1893        if (!rect.getBounds2D().intersects(ts.getBounds2D())) {
1894            return false;
1895        }
1896        Area a1 = new Area(rect);
1897        Area a2 = new Area(ts);
1898        a1.intersect(a2);
1899        return !a1.isEmpty();
1900    }
1901
1902    /**
1903     * Does nothing in this {@code SVGGraphics2D} implementation.
1904     */
1905    @Override
1906    public void setPaintMode() {
1907        // do nothing
1908    }
1909
1910    /**
1911     * Does nothing in this {@code SVGGraphics2D} implementation.
1912     * 
1913     * @param c  ignored
1914     */
1915    @Override
1916    public void setXORMode(Color c) {
1917        // do nothing
1918    }
1919
1920    /**
1921     * Returns the bounds of the user clipping region.
1922     * 
1923     * @return The clip bounds (possibly {@code null}). 
1924     * 
1925     * @see #getClip() 
1926     */
1927    @Override
1928    public Rectangle getClipBounds() {
1929        if (this.clip == null) {
1930            return null;
1931        }
1932        return getClip().getBounds();
1933    }
1934
1935    /**
1936     * Returns the user clipping region.  The initial default value is 
1937     * {@code null}.
1938     * 
1939     * @return The user clipping region (possibly {@code null}).
1940     * 
1941     * @see #setClip(java.awt.Shape)
1942     */
1943    @Override
1944    public Shape getClip() {
1945        if (this.clip == null) {
1946            return null;
1947        }
1948        AffineTransform inv;
1949        try {
1950            inv = this.transform.createInverse();
1951            return inv.createTransformedShape(this.clip);
1952        } catch (NoninvertibleTransformException ex) {
1953            return null;
1954        }
1955    }
1956
1957    /**
1958     * Sets the user clipping region.
1959     * 
1960     * @param shape  the new user clipping region ({@code null} permitted).
1961     * 
1962     * @see #getClip()
1963     */
1964    @Override
1965    public void setClip(Shape shape) {
1966        // null is handled fine here...
1967        this.clip = this.transform.createTransformedShape(shape);
1968        this.clipRef = null;
1969    }
1970    
1971    /**
1972     * Registers the clip so that we can later write out all the clip 
1973     * definitions in the DEFS element.
1974     * 
1975     * @param clip  the clip (ignored if {@code null}) 
1976     */
1977    private String registerClip(Shape clip) {
1978        if (clip == null) {
1979            this.clipRef = null;
1980            return null;
1981        }
1982        // generate the path
1983        String pathStr = getSVGPathData(new Path2D.Double(clip));
1984        int index = this.clipPaths.indexOf(pathStr);
1985        if (index < 0) {
1986            this.clipPaths.add(pathStr);
1987            index = this.clipPaths.size() - 1;
1988        }
1989        return this.defsKeyPrefix + CLIP_KEY_PREFIX + index;
1990    }
1991    
1992    private String transformDP(double d) {
1993        if (this.transformFormat != null) {
1994            return transformFormat.format(d);            
1995        } else {
1996            return String.valueOf(d);
1997        }
1998    }
1999    
2000    private String geomDP(double d) {
2001        if (this.geometryFormat != null) {
2002            return geometryFormat.format(d);            
2003        } else {
2004            return String.valueOf(d);
2005        }
2006    }
2007    
2008    private String getSVGTransform(AffineTransform t) {
2009        StringBuilder b = new StringBuilder("matrix(");
2010        b.append(transformDP(t.getScaleX())).append(",");
2011        b.append(transformDP(t.getShearY())).append(",");
2012        b.append(transformDP(t.getShearX())).append(",");
2013        b.append(transformDP(t.getScaleY())).append(",");
2014        b.append(transformDP(t.getTranslateX())).append(",");
2015        b.append(transformDP(t.getTranslateY())).append(")");
2016        return b.toString();
2017    }
2018
2019    /**
2020     * Clips to the intersection of the current clipping region and the
2021     * specified shape. 
2022     * 
2023     * According to the Oracle API specification, this method will accept a 
2024     * {@code null} argument, but there is an open bug report (since 2004) 
2025     * that suggests this is wrong:
2026     * <p>
2027     * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6206189">
2028     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6206189</a>
2029     * 
2030     * @param s  the clip shape ({@code null} not permitted). 
2031     */
2032    @Override
2033    public void clip(Shape s) {
2034        if (s instanceof Line2D) {
2035            s = s.getBounds2D();
2036        }
2037        if (this.clip == null) {
2038            setClip(s);
2039            return;
2040        }
2041        Shape ts = this.transform.createTransformedShape(s);
2042        if (!ts.intersects(this.clip.getBounds2D())) {
2043            setClip(new Rectangle2D.Double());
2044        } else {
2045          Area a1 = new Area(ts);
2046          Area a2 = new Area(this.clip);
2047          a1.intersect(a2);
2048          this.clip = new Path2D.Double(a1);
2049        }
2050        this.clipRef = null;
2051    }
2052
2053    /**
2054     * Clips to the intersection of the current clipping region and the 
2055     * specified rectangle.
2056     * 
2057     * @param x  the x-coordinate.
2058     * @param y  the y-coordinate.
2059     * @param width  the width.
2060     * @param height  the height.
2061     */
2062    @Override
2063    public void clipRect(int x, int y, int width, int height) {
2064        setRect(x, y, width, height);
2065        clip(this.rect);
2066    }
2067
2068    /**
2069     * Sets the user clipping region to the specified rectangle.
2070     * 
2071     * @param x  the x-coordinate.
2072     * @param y  the y-coordinate.
2073     * @param width  the width.
2074     * @param height  the height.
2075     * 
2076     * @see #getClip() 
2077     */
2078    @Override
2079    public void setClip(int x, int y, int width, int height) {
2080        setRect(x, y, width, height);
2081        setClip(this.rect);
2082    }
2083
2084    /**
2085     * Draws a line from {@code (x1, y1)} to {@code (x2, y2)} using 
2086     * the current {@code paint} and {@code stroke}.
2087     * 
2088     * @param x1  the x-coordinate of the start point.
2089     * @param y1  the y-coordinate of the start point.
2090     * @param x2  the x-coordinate of the end point.
2091     * @param y2  the x-coordinate of the end point.
2092     */
2093    @Override
2094    public void drawLine(int x1, int y1, int x2, int y2) {
2095        if (this.line == null) {
2096            this.line = new Line2D.Double(x1, y1, x2, y2);
2097        } else {
2098            this.line.setLine(x1, y1, x2, y2);
2099        }
2100        draw(this.line);
2101    }
2102
2103    /**
2104     * Fills the specified rectangle with the current {@code paint}.
2105     * 
2106     * @param x  the x-coordinate.
2107     * @param y  the y-coordinate.
2108     * @param width  the rectangle width.
2109     * @param height  the rectangle height.
2110     */
2111    @Override
2112    public void fillRect(int x, int y, int width, int height) {
2113        setRect(x, y, width, height);
2114        fill(this.rect);
2115    }
2116
2117    /**
2118     * Clears the specified rectangle by filling it with the current 
2119     * background color.  If the background color is {@code null}, this
2120     * method will do nothing.
2121     * 
2122     * @param x  the x-coordinate.
2123     * @param y  the y-coordinate.
2124     * @param width  the width.
2125     * @param height  the height.
2126     * 
2127     * @see #getBackground() 
2128     */
2129    @Override
2130    public void clearRect(int x, int y, int width, int height) {
2131        if (getBackground() == null) {
2132            return;  // we can't do anything
2133        }
2134        Paint saved = getPaint();
2135        setPaint(getBackground());
2136        fillRect(x, y, width, height);
2137        setPaint(saved);
2138    }
2139    
2140    /**
2141     * Draws a rectangle with rounded corners using the current 
2142     * {@code paint} and {@code stroke}.
2143     * 
2144     * @param x  the x-coordinate.
2145     * @param y  the y-coordinate.
2146     * @param width  the width.
2147     * @param height  the height.
2148     * @param arcWidth  the arc-width.
2149     * @param arcHeight  the arc-height.
2150     * 
2151     * @see #fillRoundRect(int, int, int, int, int, int) 
2152     */
2153    @Override
2154    public void drawRoundRect(int x, int y, int width, int height, 
2155            int arcWidth, int arcHeight) {
2156        setRoundRect(x, y, width, height, arcWidth, arcHeight);
2157        draw(this.roundRect);
2158    }
2159
2160    /**
2161     * Fills a rectangle with rounded corners using the current {@code paint}.
2162     * 
2163     * @param x  the x-coordinate.
2164     * @param y  the y-coordinate.
2165     * @param width  the width.
2166     * @param height  the height.
2167     * @param arcWidth  the arc-width.
2168     * @param arcHeight  the arc-height.
2169     * 
2170     * @see #drawRoundRect(int, int, int, int, int, int) 
2171     */
2172    @Override
2173    public void fillRoundRect(int x, int y, int width, int height, 
2174            int arcWidth, int arcHeight) {
2175        setRoundRect(x, y, width, height, arcWidth, arcHeight);
2176        fill(this.roundRect);
2177    }
2178
2179    /**
2180     * Draws an oval framed by the rectangle {@code (x, y, width, height)}
2181     * using the current {@code paint} and {@code stroke}.
2182     * 
2183     * @param x  the x-coordinate.
2184     * @param y  the y-coordinate.
2185     * @param width  the width.
2186     * @param height  the height.
2187     * 
2188     * @see #fillOval(int, int, int, int) 
2189     */
2190    @Override
2191    public void drawOval(int x, int y, int width, int height) {
2192        setOval(x, y, width, height);
2193        draw(this.oval);
2194    }
2195
2196    /**
2197     * Fills an oval framed by the rectangle {@code (x, y, width, height)}.
2198     * 
2199     * @param x  the x-coordinate.
2200     * @param y  the y-coordinate.
2201     * @param width  the width.
2202     * @param height  the height.
2203     * 
2204     * @see #drawOval(int, int, int, int) 
2205     */
2206    @Override
2207    public void fillOval(int x, int y, int width, int height) {
2208        setOval(x, y, width, height);
2209        fill(this.oval);
2210    }
2211
2212    /**
2213     * Draws an arc contained within the rectangle 
2214     * {@code (x, y, width, height)}, starting at {@code startAngle}
2215     * and continuing through {@code arcAngle} degrees using 
2216     * the current {@code paint} and {@code stroke}.
2217     * 
2218     * @param x  the x-coordinate.
2219     * @param y  the y-coordinate.
2220     * @param width  the width.
2221     * @param height  the height.
2222     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
2223     * @param arcAngle  the angle (anticlockwise) in degrees.
2224     * 
2225     * @see #fillArc(int, int, int, int, int, int) 
2226     */
2227    @Override
2228    public void drawArc(int x, int y, int width, int height, int startAngle, 
2229            int arcAngle) {
2230        setArc(x, y, width, height, startAngle, arcAngle);
2231        draw(this.arc);
2232    }
2233
2234    /**
2235     * Fills an arc contained within the rectangle 
2236     * {@code (x, y, width, height)}, starting at {@code startAngle}
2237     * and continuing through {@code arcAngle} degrees, using 
2238     * the current {@code paint}.
2239     * 
2240     * @param x  the x-coordinate.
2241     * @param y  the y-coordinate.
2242     * @param width  the width.
2243     * @param height  the height.
2244     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
2245     * @param arcAngle  the angle (anticlockwise) in degrees.
2246     * 
2247     * @see #drawArc(int, int, int, int, int, int) 
2248     */
2249    @Override
2250    public void fillArc(int x, int y, int width, int height, int startAngle, 
2251            int arcAngle) {
2252        setArc(x, y, width, height, startAngle, arcAngle);
2253        fill(this.arc);
2254    }
2255
2256    /**
2257     * Draws the specified multi-segment line using the current 
2258     * {@code paint} and {@code stroke}.
2259     * 
2260     * @param xPoints  the x-points.
2261     * @param yPoints  the y-points.
2262     * @param nPoints  the number of points to use for the polyline.
2263     */
2264    @Override
2265    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
2266        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints, 
2267                false);
2268        draw(p);
2269    }
2270
2271    /**
2272     * Draws the specified polygon using the current {@code paint} and 
2273     * {@code stroke}.
2274     * 
2275     * @param xPoints  the x-points.
2276     * @param yPoints  the y-points.
2277     * @param nPoints  the number of points to use for the polygon.
2278     * 
2279     * @see #fillPolygon(int[], int[], int)      */
2280    @Override
2281    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
2282        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints, 
2283                true);
2284        draw(p);
2285    }
2286
2287    /**
2288     * Fills the specified polygon using the current {@code paint}.
2289     * 
2290     * @param xPoints  the x-points.
2291     * @param yPoints  the y-points.
2292     * @param nPoints  the number of points to use for the polygon.
2293     * 
2294     * @see #drawPolygon(int[], int[], int) 
2295     */
2296    @Override
2297    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
2298        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints, 
2299                true);
2300        fill(p);
2301    }
2302
2303    /**
2304     * Returns the bytes representing a PNG format image.
2305     * 
2306     * @param img  the image to encode.
2307     * 
2308     * @return The bytes representing a PNG format image. 
2309     */
2310    private byte[] getPNGBytes(Image img) {
2311        RenderedImage ri;
2312        if (img instanceof RenderedImage) {
2313            ri = (RenderedImage) img;
2314        } else {
2315            BufferedImage bi = new BufferedImage(img.getWidth(null), 
2316                    img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
2317            Graphics2D g2 = bi.createGraphics();
2318            g2.drawImage(img, 0, 0, null);
2319            ri = bi;
2320        }
2321        ByteArrayOutputStream baos = new ByteArrayOutputStream();
2322        try {
2323            ImageIO.write(ri, "png", baos);
2324        } catch (IOException ex) {
2325            Logger.getLogger(SVGGraphics2D.class.getName()).log(Level.SEVERE, 
2326                    "IOException while writing PNG data.", ex);
2327        }
2328        return baos.toByteArray();
2329    }  
2330    
2331    /**
2332     * Draws an image at the location {@code (x, y)}.  Note that the 
2333     * {@code observer} is ignored.
2334     * 
2335     * @param img  the image ({@code null} permitted...method will do nothing).
2336     * @param x  the x-coordinate.
2337     * @param y  the y-coordinate.
2338     * @param observer  ignored.
2339     * 
2340     * @return {@code true} if there is no more drawing to be done. 
2341     */
2342    @Override
2343    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
2344        if (img == null) {
2345            return true;
2346        }
2347        int w = img.getWidth(observer);
2348        if (w < 0) {
2349            return false;
2350        }
2351        int h = img.getHeight(observer);
2352        if (h < 0) {
2353            return false;
2354        }
2355        return drawImage(img, x, y, w, h, observer);
2356    }
2357
2358    /**
2359     * Draws the image into the rectangle defined by {@code (x, y, w, h)}.  
2360     * Note that the {@code observer} is ignored (it is not useful in this
2361     * context).
2362     * 
2363     * @param img  the image ({@code null} permitted...draws nothing).
2364     * @param x  the x-coordinate.
2365     * @param y  the y-coordinate.
2366     * @param w  the width.
2367     * @param h  the height.
2368     * @param observer  ignored.
2369     * 
2370     * @return {@code true} if there is no more drawing to be done. 
2371     */
2372    @Override
2373    public boolean drawImage(Image img, int x, int y, int w, int h, 
2374            ImageObserver observer) {
2375
2376        if (img == null) {
2377            return true; 
2378        }
2379        // the rendering hints control whether the image is embedded or
2380        // referenced...
2381        Object hint = getRenderingHint(SVGHints.KEY_IMAGE_HANDLING);
2382        if (SVGHints.VALUE_IMAGE_HANDLING_EMBED.equals(hint)) {
2383            this.sb.append("<image ");
2384            appendOptionalElementIDFromHint(this.sb);
2385            this.sb.append("preserveAspectRatio=\"none\" ");
2386            this.sb.append("xlink:href=\"data:image/png;base64,");
2387            this.sb.append(DatatypeConverter.printBase64Binary(getPNGBytes(
2388                    img)));
2389            this.sb.append("\" ");
2390            this.sb.append(getClipPathRef()).append(" ");
2391            this.sb.append("transform=\"").append(getSVGTransform(
2392                    this.transform)).append("\" ");            
2393            this.sb.append("x=\"").append(geomDP(x))
2394                    .append("\" y=\"").append(geomDP(y))
2395                    .append("\" ");
2396            this.sb.append("width=\"").append(geomDP(w)).append("\" height=\"")
2397                    .append(geomDP(h)).append("\"/>\n");
2398            return true;
2399        } else { // here for SVGHints.VALUE_IMAGE_HANDLING_REFERENCE
2400            int count = this.imageElements.size();
2401            String href = (String) this.hints.get(SVGHints.KEY_IMAGE_HREF);
2402            if (href == null) {
2403                href = this.filePrefix + count + this.fileSuffix;
2404            } else {
2405                // KEY_IMAGE_HREF value is for a single use...
2406                this.hints.put(SVGHints.KEY_IMAGE_HREF, null);
2407            }
2408            ImageElement imageElement = new ImageElement(href, img);
2409            this.imageElements.add(imageElement);
2410            // write an SVG element for the img
2411            this.sb.append("<image ");
2412            appendOptionalElementIDFromHint(this.sb);
2413            this.sb.append("xlink:href=\"");
2414            this.sb.append(href).append("\" ");
2415            this.sb.append(getClipPathRef()).append(" ");
2416            this.sb.append("transform=\"").append(getSVGTransform(
2417                    this.transform)).append("\" ");
2418            this.sb.append("x=\"").append(geomDP(x))
2419                    .append("\" y=\"").append(geomDP(y))
2420                    .append("\" ");
2421            this.sb.append("width=\"").append(geomDP(w)).append("\" height=\"")
2422                    .append(geomDP(h)).append("\"/>\n");
2423            return true;
2424        }
2425    }
2426
2427    /**
2428     * Draws an image at the location {@code (x, y)}.  Note that the 
2429     * {@code observer} is ignored.
2430     * 
2431     * @param img  the image ({@code null} permitted...draws nothing).
2432     * @param x  the x-coordinate.
2433     * @param y  the y-coordinate.
2434     * @param bgcolor  the background color ({@code null} permitted).
2435     * @param observer  ignored.
2436     * 
2437     * @return {@code true} if there is no more drawing to be done. 
2438     */
2439    @Override
2440    public boolean drawImage(Image img, int x, int y, Color bgcolor, 
2441            ImageObserver observer) {
2442        if (img == null) {
2443            return true;
2444        }
2445        int w = img.getWidth(null);
2446        if (w < 0) {
2447            return false;
2448        }
2449        int h = img.getHeight(null);
2450        if (h < 0) {
2451            return false;
2452        }
2453        return drawImage(img, x, y, w, h, bgcolor, observer);
2454    }
2455
2456    /**
2457     * Draws an image to the rectangle {@code (x, y, w, h)} (scaling it if
2458     * required), first filling the background with the specified color.  Note 
2459     * that the {@code observer} is ignored.
2460     * 
2461     * @param img  the image.
2462     * @param x  the x-coordinate.
2463     * @param y  the y-coordinate.
2464     * @param w  the width.
2465     * @param h  the height.
2466     * @param bgcolor  the background color ({@code null} permitted).
2467     * @param observer  ignored.
2468     * 
2469     * @return {@code true} if the image is drawn.      
2470     */
2471    @Override
2472    public boolean drawImage(Image img, int x, int y, int w, int h, 
2473            Color bgcolor, ImageObserver observer) {
2474        Paint saved = getPaint();
2475        setPaint(bgcolor);
2476        fillRect(x, y, w, h);
2477        setPaint(saved);
2478        return drawImage(img, x, y, w, h, observer);
2479    }
2480
2481    /**
2482     * Draws part of an image (defined by the source rectangle 
2483     * {@code (sx1, sy1, sx2, sy2)}) into the destination rectangle
2484     * {@code (dx1, dy1, dx2, dy2)}.  Note that the {@code observer} is ignored.
2485     * 
2486     * @param img  the image.
2487     * @param dx1  the x-coordinate for the top left of the destination.
2488     * @param dy1  the y-coordinate for the top left of the destination.
2489     * @param dx2  the x-coordinate for the bottom right of the destination.
2490     * @param dy2  the y-coordinate for the bottom right of the destination.
2491     * @param sx1 the x-coordinate for the top left of the source.
2492     * @param sy1 the y-coordinate for the top left of the source.
2493     * @param sx2 the x-coordinate for the bottom right of the source.
2494     * @param sy2 the y-coordinate for the bottom right of the source.
2495     * 
2496     * @return {@code true} if the image is drawn. 
2497     */
2498    @Override
2499    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 
2500            int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
2501        int w = dx2 - dx1;
2502        int h = dy2 - dy1;
2503        BufferedImage img2 = new BufferedImage(w, h, 
2504                BufferedImage.TYPE_INT_ARGB);
2505        Graphics2D g2 = img2.createGraphics();
2506        g2.drawImage(img, 0, 0, w, h, sx1, sy1, sx2, sy2, null);
2507        return drawImage(img2, dx1, dy1, null);
2508    }
2509
2510    /**
2511     * Draws part of an image (defined by the source rectangle 
2512     * {@code (sx1, sy1, sx2, sy2)}) into the destination rectangle
2513     * {@code (dx1, dy1, dx2, dy2)}.  The destination rectangle is first
2514     * cleared by filling it with the specified {@code bgcolor}. Note that
2515     * the {@code observer} is ignored. 
2516     * 
2517     * @param img  the image.
2518     * @param dx1  the x-coordinate for the top left of the destination.
2519     * @param dy1  the y-coordinate for the top left of the destination.
2520     * @param dx2  the x-coordinate for the bottom right of the destination.
2521     * @param dy2  the y-coordinate for the bottom right of the destination.
2522     * @param sx1 the x-coordinate for the top left of the source.
2523     * @param sy1 the y-coordinate for the top left of the source.
2524     * @param sx2 the x-coordinate for the bottom right of the source.
2525     * @param sy2 the y-coordinate for the bottom right of the source.
2526     * @param bgcolor  the background color ({@code null} permitted).
2527     * @param observer  ignored.
2528     * 
2529     * @return {@code true} if the image is drawn. 
2530     */
2531    @Override
2532    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 
2533            int sx1, int sy1, int sx2, int sy2, Color bgcolor, 
2534            ImageObserver observer) {
2535        Paint saved = getPaint();
2536        setPaint(bgcolor);
2537        fillRect(dx1, dy1, dx2 - dx1, dy2 - dy1);
2538        setPaint(saved);
2539        return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
2540    }
2541
2542    /**
2543     * Draws the rendered image.
2544     * 
2545     * @param img  the image.
2546     * @param xform  the transform.
2547     */
2548    @Override
2549    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
2550        BufferedImage bi = GraphicsUtils.convertRenderedImage(img);
2551        drawImage(bi, xform, null);
2552    }
2553
2554    /**
2555     * Draws the renderable image.
2556     * 
2557     * @param img  the renderable image.
2558     * @param xform  the transform.
2559     */
2560    @Override
2561    public void drawRenderableImage(RenderableImage img, 
2562            AffineTransform xform) {
2563        RenderedImage ri = img.createDefaultRendering();
2564        drawRenderedImage(ri, xform);
2565    }
2566
2567    /**
2568     * Draws an image with the specified transform. Note that the 
2569     * {@code observer} is ignored.     
2570     * 
2571     * @param img  the image.
2572     * @param xform  the transform ({@code null} permitted).
2573     * @param obs  the image observer (ignored).
2574     * 
2575     * @return {@code true} if the image is drawn. 
2576     */
2577    @Override
2578    public boolean drawImage(Image img, AffineTransform xform, 
2579            ImageObserver obs) {
2580        AffineTransform savedTransform = getTransform();
2581        if (xform != null) {
2582            transform(xform);
2583        }
2584        boolean result = drawImage(img, 0, 0, obs);
2585        if (xform != null) {
2586            setTransform(savedTransform);
2587        }
2588        return result;
2589    }
2590
2591    /**
2592     * Draws the image resulting from applying the {@code BufferedImageOp}
2593     * to the specified image at the location {@code (x, y)}.
2594     * 
2595     * @param img  the image.
2596     * @param op  the operation.
2597     * @param x  the x-coordinate.
2598     * @param y  the y-coordinate.
2599     */
2600    @Override
2601    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
2602        BufferedImage imageToDraw = op.filter(img, null);
2603        drawImage(imageToDraw, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
2604    }
2605
2606    /**
2607     * This method does nothing.  The operation assumes that the output is in 
2608     * bitmap form, which is not the case for SVG, so we silently ignore
2609     * this method call.
2610     * 
2611     * @param x  the x-coordinate.
2612     * @param y  the y-coordinate.
2613     * @param width  the width of the area.
2614     * @param height  the height of the area.
2615     * @param dx  the delta x.
2616     * @param dy  the delta y.
2617     */
2618    @Override
2619    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
2620        // do nothing, this operation is silently ignored.
2621    }
2622
2623    /**
2624     * This method does nothing, there are no resources to dispose.
2625     */
2626    @Override
2627    public void dispose() {
2628        // nothing to do
2629    }
2630
2631    /**
2632     * Returns the SVG element that has been generated by calls to this 
2633     * {@code Graphics2D} implementation.
2634     * 
2635     * @return The SVG element.
2636     */
2637    public String getSVGElement() {
2638        return getSVGElement(null);
2639    }
2640    
2641    /**
2642     * Returns the SVG element that has been generated by calls to this
2643     * {@code Graphics2D} implementation, giving it the specified {@code id}.  
2644     * If {@code id} is {@code null}, the element will have no {@code id} 
2645     * attribute.
2646     * 
2647     * @param id  the element id ({@code null} permitted).
2648     * 
2649     * @return A string containing the SVG element. 
2650     * 
2651     * @since 1.8
2652     */
2653    public String getSVGElement(String id) {
2654        return getSVGElement(id, true, null, null, null);
2655    }
2656    
2657    /**
2658     * Returns the SVG element that has been generated by calls to this
2659     * {@code Graphics2D} implementation, giving it the specified {@code id}.  
2660     * If {@code id} is {@code null}, the element will have no {@code id} 
2661     * attribute.  This method also allows for a {@code viewBox} to be defined,
2662     * along with the settings that handle scaling.
2663     * 
2664     * @param id  the element id ({@code null} permitted).
2665     * @param includeDimensions  include the width and height attributes?
2666     * @param viewBox  the view box specification (if {@code null} then no
2667     *     {@code viewBox} attribute will be defined).
2668     * @param preserveAspectRatio  the value of the {@code preserveAspectRatio} 
2669     *     attribute (if {@code null} then not attribute will be defined).
2670     * @param meetOrSlice  the value of the meetOrSlice attribute.
2671     * 
2672     * @return A string containing the SVG element. 
2673     * 
2674     * @since 3.2
2675     */
2676    public String getSVGElement(String id, boolean includeDimensions, 
2677            ViewBox viewBox, PreserveAspectRatio preserveAspectRatio,
2678            MeetOrSlice meetOrSlice) {
2679        StringBuilder svg = new StringBuilder("<svg ");
2680        if (id != null) {
2681            svg.append("id=\"").append(id).append("\" ");
2682        }
2683        String unitStr = this.units != null ? this.units.toString() : "";
2684        svg.append("xmlns=\"http://www.w3.org/2000/svg\" ")
2685           .append("xmlns:xlink=\"http://www.w3.org/1999/xlink\" ")
2686           .append("xmlns:jfreesvg=\"http://www.jfree.org/jfreesvg/svg\" ");
2687        if (includeDimensions) {
2688            svg.append("width=\"").append(this.width).append(unitStr)
2689               .append("\" height=\"").append(this.height).append(unitStr)
2690               .append("\" ");
2691        }
2692        if (viewBox != null) {
2693            svg.append("viewBox=\"").append(viewBox.valueStr()).append("\" ");
2694            if (preserveAspectRatio != null) {
2695                svg.append("preserveAspectRatio=\"")
2696                        .append(preserveAspectRatio.toString());
2697                if (meetOrSlice != null) {
2698                    svg.append(" ").append(meetOrSlice.toString());
2699                }
2700                svg.append("\" ");                    
2701            }
2702        }
2703        svg.append("text-rendering=\"").append(this.textRendering)
2704           .append("\" shape-rendering=\"").append(this.shapeRendering)
2705           .append("\">\n");
2706        StringBuilder defs = new StringBuilder("<defs>");
2707        for (GradientPaintKey key : this.gradientPaints.keySet()) {
2708            defs.append(getLinearGradientElement(this.gradientPaints.get(key), 
2709                    key.getPaint()));
2710            defs.append("\n");
2711        }
2712        for (LinearGradientPaintKey key : this.linearGradientPaints.keySet()) {
2713            defs.append(getLinearGradientElement(
2714                    this.linearGradientPaints.get(key), key.getPaint()));
2715            defs.append("\n");            
2716        }
2717        for (RadialGradientPaintKey key : this.radialGradientPaints.keySet()) {
2718            defs.append(getRadialGradientElement(
2719                    this.radialGradientPaints.get(key), key.getPaint()));
2720            defs.append("\n");
2721        }
2722        for (int i = 0; i < this.clipPaths.size(); i++) {
2723            StringBuilder b = new StringBuilder("<clipPath id=\"")
2724                    .append(this.defsKeyPrefix).append(CLIP_KEY_PREFIX).append(i)
2725                    .append("\">");
2726            b.append("<path ").append(this.clipPaths.get(i)).append("/>");
2727            b.append("</clipPath>").append("\n");
2728            defs.append(b.toString());
2729        }
2730        defs.append("</defs>\n");
2731        svg.append(defs);
2732        svg.append(this.sb);
2733        svg.append("</svg>");        
2734        return svg.toString();
2735    }
2736    
2737    /**
2738     * Returns an SVG document (this contains the content returned by the
2739     * {@link #getSVGElement()} method, prepended with the required document 
2740     * header).
2741     * 
2742     * @return An SVG document.
2743     */
2744    public String getSVGDocument() {
2745        StringBuilder b = new StringBuilder();
2746        b.append("<?xml version=\"1.0\"?>\n");
2747        b.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" ");
2748        b.append("\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
2749        b.append(getSVGElement());
2750        return b.append("\n").toString();
2751    }
2752    
2753    /**
2754     * Returns the list of image elements that have been referenced in the 
2755     * SVG output but not embedded.  If the image files don't already exist,
2756     * you can use this list as the basis for creating the image files.
2757     * 
2758     * @return The list of image elements.
2759     * 
2760     * @see SVGHints#KEY_IMAGE_HANDLING
2761     */
2762    public List<ImageElement> getSVGImages() {
2763        return this.imageElements;
2764    }
2765    
2766    /**
2767     * Returns a new set containing the element IDs that have been used in
2768     * output so far.
2769     * 
2770     * @return The element IDs.
2771     * 
2772     * @since 1.5
2773     */
2774    public Set<String> getElementIDs() {
2775        return new HashSet<String>(this.elementIDs);
2776    }
2777    
2778    /**
2779     * Returns an element to represent a linear gradient.  All the linear
2780     * gradients that are used get written to the DEFS element in the SVG.
2781     * 
2782     * @param id  the reference id.
2783     * @param paint  the gradient.
2784     * 
2785     * @return The SVG element.
2786     */
2787    private String getLinearGradientElement(String id, GradientPaint paint) {
2788        StringBuilder b = new StringBuilder("<linearGradient id=\"").append(id)
2789                .append("\" ");
2790        Point2D p1 = paint.getPoint1();
2791        Point2D p2 = paint.getPoint2();
2792        b.append("x1=\"").append(geomDP(p1.getX())).append("\" ");
2793        b.append("y1=\"").append(geomDP(p1.getY())).append("\" ");
2794        b.append("x2=\"").append(geomDP(p2.getX())).append("\" ");
2795        b.append("y2=\"").append(geomDP(p2.getY())).append("\" ");
2796        b.append("gradientUnits=\"userSpaceOnUse\">");
2797        Color c1 = paint.getColor1();
2798        b.append("<stop offset=\"0%\" stop-color=\"").append(rgbColorStr(c1))
2799                .append("\"");
2800        if (c1.getAlpha() < 255) {
2801            double alphaPercent = c1.getAlpha() / 255.0;
2802            b.append(" stop-opacity=\"").append(transformDP(alphaPercent))
2803                    .append("\"");
2804        }
2805        b.append("/>");
2806        Color c2 = paint.getColor2();
2807        b.append("<stop offset=\"100%\" stop-color=\"").append(rgbColorStr(c2))
2808                .append("\"");
2809        if (c2.getAlpha() < 255) {
2810            double alphaPercent = c2.getAlpha() / 255.0;
2811            b.append(" stop-opacity=\"").append(transformDP(alphaPercent))
2812                    .append("\"");
2813        }
2814        b.append("/>");
2815        return b.append("</linearGradient>").toString();
2816    }
2817    
2818    /**
2819     * Returns an element to represent a linear gradient.  All the linear
2820     * gradients that are used get written to the DEFS element in the SVG.
2821     * 
2822     * @param id  the reference id.
2823     * @param paint  the gradient.
2824     * 
2825     * @return The SVG element.
2826     */
2827    private String getLinearGradientElement(String id, 
2828            LinearGradientPaint paint) {
2829        StringBuilder b = new StringBuilder("<linearGradient id=\"").append(id)
2830                .append("\" ");
2831        Point2D p1 = paint.getStartPoint();
2832        Point2D p2 = paint.getEndPoint();
2833        b.append("x1=\"").append(geomDP(p1.getX())).append("\" ");
2834        b.append("y1=\"").append(geomDP(p1.getY())).append("\" ");
2835        b.append("x2=\"").append(geomDP(p2.getX())).append("\" ");
2836        b.append("y2=\"").append(geomDP(p2.getY())).append("\" ");
2837        if (!paint.getCycleMethod().equals(CycleMethod.NO_CYCLE)) {
2838            String sm = paint.getCycleMethod().equals(CycleMethod.REFLECT) 
2839                    ? "reflect" : "repeat";
2840            b.append("spreadMethod=\"").append(sm).append("\" ");
2841        }
2842        b.append("gradientUnits=\"userSpaceOnUse\">");
2843        for (int i = 0; i < paint.getFractions().length; i++) {
2844            Color c = paint.getColors()[i];
2845            float fraction = paint.getFractions()[i];
2846            b.append("<stop offset=\"").append(geomDP(fraction * 100))
2847                    .append("%\" stop-color=\"")
2848                    .append(rgbColorStr(c)).append("\"");
2849            if (c.getAlpha() < 255) {
2850                double alphaPercent = c.getAlpha() / 255.0;
2851                b.append(" stop-opacity=\"").append(transformDP(alphaPercent))
2852                        .append("\"");                
2853            }
2854            b.append("/>");
2855        }
2856        return b.append("</linearGradient>").toString();
2857    }
2858    
2859    /**
2860     * Returns an element to represent a radial gradient.  All the radial
2861     * gradients that are used get written to the DEFS element in the SVG.
2862     * 
2863     * @param id  the reference id.
2864     * @param rgp  the radial gradient.
2865     * 
2866     * @return The SVG element. 
2867     */
2868    private String getRadialGradientElement(String id, RadialGradientPaint rgp) {
2869        StringBuilder b = new StringBuilder("<radialGradient id=\"").append(id)
2870                .append("\" gradientUnits=\"userSpaceOnUse\" ");
2871        Point2D center = rgp.getCenterPoint();
2872        Point2D focus = rgp.getFocusPoint();
2873        float radius = rgp.getRadius();
2874        b.append("cx=\"").append(geomDP(center.getX())).append("\" ");
2875        b.append("cy=\"").append(geomDP(center.getY())).append("\" ");
2876        b.append("r=\"").append(geomDP(radius)).append("\" ");
2877        b.append("fx=\"").append(geomDP(focus.getX())).append("\" ");
2878        b.append("fy=\"").append(geomDP(focus.getY())).append("\">");
2879        
2880        Color[] colors = rgp.getColors();
2881        float[] fractions = rgp.getFractions();
2882        for (int i = 0; i < colors.length; i++) {
2883            Color c = colors[i];
2884            float f = fractions[i];
2885            b.append("<stop offset=\"").append(geomDP(f * 100)).append("%\" ");
2886            b.append("stop-color=\"").append(rgbColorStr(c)).append("\"");
2887            if (c.getAlpha() < 255) {
2888                double alphaPercent = c.getAlpha() / 255.0;
2889                b.append(" stop-opacity=\"").append(transformDP(alphaPercent))
2890                        .append("\"");                
2891            }            
2892            b.append("/>");
2893        }
2894        return b.append("</radialGradient>").toString();
2895    }
2896
2897    /**
2898     * Returns a clip path reference for the current user clip.  This is 
2899     * written out on all SVG elements that draw or fill shapes or text.
2900     * 
2901     * @return A clip path reference. 
2902     */
2903    private String getClipPathRef() {
2904        if (this.clip == null) {
2905            return "";
2906        }
2907        if (this.clipRef == null) {
2908            this.clipRef = registerClip(getClip());
2909        }
2910        StringBuilder b = new StringBuilder();
2911        b.append("clip-path=\"url(#").append(this.clipRef).append(")\"");
2912        return b.toString();
2913    }
2914    
2915    /**
2916     * Sets the attributes of the reusable {@link Rectangle2D} object that is
2917     * used by the {@link SVGGraphics2D#drawRect(int, int, int, int)} and 
2918     * {@link SVGGraphics2D#fillRect(int, int, int, int)} methods.
2919     * 
2920     * @param x  the x-coordinate.
2921     * @param y  the y-coordinate.
2922     * @param width  the width.
2923     * @param height  the height.
2924     */
2925    private void setRect(int x, int y, int width, int height) {
2926        if (this.rect == null) {
2927            this.rect = new Rectangle2D.Double(x, y, width, height);
2928        } else {
2929            this.rect.setRect(x, y, width, height);
2930        }
2931    }
2932    
2933    /**
2934     * Sets the attributes of the reusable {@link RoundRectangle2D} object that
2935     * is used by the {@link #drawRoundRect(int, int, int, int, int, int)} and
2936     * {@link #fillRoundRect(int, int, int, int, int, int)} methods.
2937     * 
2938     * @param x  the x-coordinate.
2939     * @param y  the y-coordinate.
2940     * @param width  the width.
2941     * @param height  the height.
2942     * @param arcWidth  the arc width.
2943     * @param arcHeight  the arc height.
2944     */
2945    private void setRoundRect(int x, int y, int width, int height, int arcWidth, 
2946            int arcHeight) {
2947        if (this.roundRect == null) {
2948            this.roundRect = new RoundRectangle2D.Double(x, y, width, height, 
2949                    arcWidth, arcHeight);
2950        } else {
2951            this.roundRect.setRoundRect(x, y, width, height, 
2952                    arcWidth, arcHeight);
2953        }        
2954    }
2955
2956    /**
2957     * Sets the attributes of the reusable {@link Arc2D} object that is used by
2958     * {@link #drawArc(int, int, int, int, int, int)} and 
2959     * {@link #fillArc(int, int, int, int, int, int)} methods.
2960     * 
2961     * @param x  the x-coordinate.
2962     * @param y  the y-coordinate.
2963     * @param width  the width.
2964     * @param height  the height.
2965     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
2966     * @param arcAngle  the angle (anticlockwise) in degrees.
2967     */
2968    private void setArc(int x, int y, int width, int height, int startAngle, 
2969            int arcAngle) {
2970        if (this.arc == null) {
2971            this.arc = new Arc2D.Double(x, y, width, height, startAngle, 
2972                    arcAngle, Arc2D.OPEN);
2973        } else {
2974            this.arc.setArc(x, y, width, height, startAngle, arcAngle, 
2975                    Arc2D.OPEN);
2976        }        
2977    }
2978    
2979    /**
2980     * Sets the attributes of the reusable {@link Ellipse2D} object that is 
2981     * used by the {@link #drawOval(int, int, int, int)} and
2982     * {@link #fillOval(int, int, int, int)} methods.
2983     * 
2984     * @param x  the x-coordinate.
2985     * @param y  the y-coordinate.
2986     * @param width  the width.
2987     * @param height  the height.
2988     */
2989    private void setOval(int x, int y, int width, int height) {
2990        if (this.oval == null) {
2991            this.oval = new Ellipse2D.Double(x, y, width, height);
2992        } else {
2993            this.oval.setFrame(x, y, width, height);
2994        }
2995    }
2996
2997}