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