001/* =====================================================================
002 * JFreePDF : a fast, light-weight PDF library for the Java(tm) platform
003 * =====================================================================
004 * 
005 * (C)opyright 2013-2020, by Object Refinery Limited.  All rights reserved.
006 *
007 * Project Info:  http://www.object-refinery.com/orsonpdf/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 * Orson PDF home page:
028 * 
029 * http://www.object-refinery.com/orsonpdf/index.html
030 * 
031 */
032
033package org.jfree.pdf;
034
035import java.awt.AlphaComposite;
036import java.awt.BasicStroke;
037import java.awt.Color;
038import java.awt.Composite;
039import java.awt.Font;
040import java.awt.FontMetrics;
041import java.awt.GradientPaint;
042import java.awt.Graphics;
043import java.awt.Graphics2D;
044import java.awt.GraphicsConfiguration;
045import java.awt.Image;
046import java.awt.Paint;
047import java.awt.RadialGradientPaint;
048import java.awt.Rectangle;
049import java.awt.RenderingHints;
050import java.awt.Shape;
051import java.awt.Stroke;
052import java.awt.font.FontRenderContext;
053import java.awt.font.GlyphVector;
054import java.awt.font.TextLayout;
055import java.awt.geom.AffineTransform;
056import java.awt.geom.Arc2D;
057import java.awt.geom.Area;
058import java.awt.geom.Ellipse2D;
059import java.awt.geom.GeneralPath;
060import java.awt.geom.Line2D;
061import java.awt.geom.NoninvertibleTransformException;
062import java.awt.geom.Path2D;
063import java.awt.geom.Rectangle2D;
064import java.awt.geom.RoundRectangle2D;
065import java.awt.image.BufferedImage;
066import java.awt.image.BufferedImageOp;
067import java.awt.image.ImageObserver;
068import java.awt.image.RenderedImage;
069import java.awt.image.renderable.RenderableImage;
070import java.text.AttributedCharacterIterator;
071import java.text.AttributedString;
072import java.util.Map;
073import org.jfree.pdf.stream.GraphicsStream;
074import org.jfree.pdf.util.Args;
075import org.jfree.pdf.util.GraphicsUtils;
076
077/**
078 * A {@code Graphics2D} implementation that writes to PDF format.  For 
079 * typical usage, see the documentation for the {@link PDFDocument} class.
080 * <p>
081 * For some demos of the use of this class, please check out the
082 * JFree Demos project at GitHub (https://github.com/jfree/jfree-demos).
083 */
084public final class PDFGraphics2D extends Graphics2D {
085
086    int width;
087    
088    int height;
089    
090    /** Rendering hints (all ignored). */
091    private RenderingHints hints;
092    
093    private Paint paint = Color.WHITE;
094    
095    private Color color = Color.WHITE;
096    
097    private Color background = Color.WHITE;
098    
099    private Composite composite = AlphaComposite.getInstance(
100            AlphaComposite.SRC_OVER, 1.0f);
101
102    private Stroke stroke = new BasicStroke(1.0f);
103
104    private AffineTransform transform = new AffineTransform();
105
106    /** The user clip (can be null). */
107    private Shape clip = null;
108    
109    private Font font = new Font("SansSerif", Font.PLAIN, 12);
110    
111    /** A hidden image used for font metrics. */
112    private final BufferedImage image = new BufferedImage(10, 10, 
113            BufferedImage.TYPE_INT_RGB);
114
115    /**
116     * An instance that is lazily instantiated in drawLine and then 
117     * subsequently reused to avoid creating a lot of garbage.
118     */
119    private Line2D line;
120        
121    /**
122     * An instance that is lazily instantiated in fillRect and then 
123     * subsequently reused to avoid creating a lot of garbage.
124     */
125    Rectangle2D rect;
126    
127    /**
128     * An instance that is lazily instantiated in draw/fillRoundRect and then
129     * subsequently reused to avoid creating a lot of garbage.
130     */
131    private RoundRectangle2D roundRect;
132    
133    /**
134     * An instance that is lazily instantiated in draw/fillOval and then
135     * subsequently reused to avoid creating a lot of garbage.
136     */
137    private Ellipse2D oval;
138    
139    /**
140     * An instance that is lazily instantiated in draw/fillArc and then
141     * subsequently reused to avoid creating a lot of garbage.
142     */
143    private Arc2D arc;
144    
145    /** The content created by the Graphics2D instance. */
146    private GraphicsStream gs;
147    
148    private GraphicsConfiguration deviceConfiguration;
149
150    /** 
151     * The font render context.  The fractional metrics flag solves the glyph
152     * positioning issue identified by Christoph Nahr:
153     * http://news.kynosarges.org/2014/06/28/glyph-positioning-in-jfreesvg-orsonpdf/
154     */
155    private final FontRenderContext fontRenderContext = new FontRenderContext(
156            null, false, true);
157
158    /** 
159     * When an instance is created via the {@link #create()} method, a copy
160     * of the transform in effect is retained so it can be restored once the
161     * child instance is disposed.  See issue #4 at GitHub.
162     */ 
163    AffineTransform originalTransform;
164
165    /**
166     * Creates a new instance of {@code PDFGraphics2D}.  You won't 
167     * normally create this directly, instead you will call the 
168     * {@link Page#getGraphics2D()} method.
169     * 
170     * @param gs  the graphics stream ({@code null} not permitted).
171     * @param width  the width.
172     * @param height  the height.
173     */
174    PDFGraphics2D(GraphicsStream gs, int width, int height) {
175        this(gs, width, height, false);
176    }
177
178    /**
179     * Creates a new instance of {@code PDFGraphics2D}.  You won't 
180     * normally create this directly, instead you will call the 
181     * {@link Page#getGraphics2D()} method.
182     * 
183     * @param gs  the graphics stream ({@code null} not permitted).
184     * @param width  the width.
185     * @param height  the height.
186     * @param skipJava2DTransform  a flag that allows the PDF to Java2D 
187     *        transform to be skipped (used for watermarks which are appended
188     *        to an existing stream that already has the transform).
189     */
190    PDFGraphics2D(GraphicsStream gs, int width, int height, 
191            boolean skipJava2DTransform) {
192        Args.nullNotPermitted(gs, "gs");
193        this.width = width;
194        this.height = height;
195        this.hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, 
196                RenderingHints.VALUE_ANTIALIAS_ON);
197        this.gs = gs;
198        // flip the y-axis to match the Java2D convention
199        if (!skipJava2DTransform) {
200            this.gs.applyTransform(AffineTransform.getTranslateInstance(0.0, 
201                    height));
202            this.gs.applyTransform(AffineTransform.getScaleInstance(1.0, -1.0));
203        }
204        this.gs.applyFont(getFont());
205        this.gs.applyStrokeColor(getColor());
206        this.gs.applyFillColor(getColor());
207        this.gs.applyStroke(getStroke());
208    }
209
210    /**
211     * Returns a new {@code PDFGraphics2D} instance that is a copy of this
212     * instance.
213     * 
214     * @return A new graphics object.
215     */
216    @Override
217    public Graphics create() {
218        PDFGraphics2D copy = new PDFGraphics2D(this.gs, this.width, 
219                this.height, true);
220        copy.setRenderingHints(getRenderingHints());
221        copy.setTransform(getTransform());
222        copy.originalTransform = getTransform();
223        copy.setClip(getClip());
224        copy.setPaint(getPaint());
225        copy.setColor(getColor());
226        copy.setComposite(getComposite());
227        copy.setStroke(getStroke());
228        copy.setFont(getFont());
229        copy.setBackground(getBackground());
230        return copy;
231    }
232 
233    /**
234     * Returns the paint used to draw or fill shapes (or text).  The default 
235     * value is {@link Color#WHITE}.
236     * 
237     * @return The paint (never {@code null}).
238     *
239     * @see #setPaint(java.awt.Paint) 
240     */
241   @Override
242    public Paint getPaint() {
243        return this.paint;
244    }
245
246    /**
247     * Sets the paint used to draw or fill shapes (or text).  If 
248     * {@code paint} is an instance of {@code Color}, this method will
249     * also update the current color attribute (see {@link #getColor()}). If 
250     * you pass {@code null} to this method, it does nothing (in 
251     * accordance with the JDK specification).
252     * 
253     * @param paint  the paint ({@code null} is permitted but ignored).
254     * 
255     * @see #getPaint() 
256     */
257    @Override
258    public void setPaint(Paint paint) {
259        if (paint == null) {
260            return;
261        }
262        if (paint instanceof Color) {
263            setColor((Color) paint);
264            return;
265        }
266        this.paint = paint;
267        if (paint instanceof GradientPaint) {
268            GradientPaint gp = (GradientPaint) paint;
269            this.gs.applyStrokeGradient(gp);
270            this.gs.applyFillGradient(gp);
271        } else if (paint instanceof RadialGradientPaint) {
272            RadialGradientPaint rgp = (RadialGradientPaint) paint;
273            this.gs.applyStrokeGradient(rgp);
274            this.gs.applyFillGradient(rgp);
275        }
276    }
277
278    /**
279     * Returns the foreground color.  This method exists for backwards
280     * compatibility in AWT, you should normally use the {@link #getPaint()} 
281     * method instead.
282     * 
283     * @return The foreground color (never {@code null}).
284     * 
285     * @see #getPaint() 
286     */
287    @Override
288    public Color getColor() {
289        return this.color;
290    }
291
292    /**
293     * Sets the foreground color.  This method exists for backwards 
294     * compatibility in AWT, you should normally use the 
295     * {@link #setPaint(java.awt.Paint)} method.
296     * 
297     * @param c  the color ({@code null} permitted but ignored). 
298     * 
299     * @see #setPaint(java.awt.Paint) 
300     */
301    @Override
302    public void setColor(Color c) {
303        if (c == null || this.paint.equals(c)) {
304            return;
305        }
306        this.color = c;
307        this.paint = c;
308        this.gs.applyStrokeColor(c);
309        this.gs.applyFillColor(c);
310    }
311    
312    /**
313     * Returns the background color.  The default value is {@link Color#BLACK}.
314     * This is used by the {@link #clearRect(int, int, int, int)} method.
315     * 
316     * @return The background color (possibly {@code null}). 
317     * 
318     * @see #setBackground(java.awt.Color) 
319     */
320    @Override
321    public Color getBackground() {
322        return this.background;
323    }
324
325    /**
326     * Sets the background color.  This is used by the 
327     * {@link #clearRect(int, int, int, int)} method.  The reference 
328     * implementation allows {@code null} for the background color so we allow 
329     * that too (but for that case, the {@code clearRect()} method will do 
330     * nothing).
331     * 
332     * @param color  the color ({@code null} permitted).
333     * 
334     * @see #getBackground() 
335     */
336    @Override
337    public void setBackground(Color color) {
338        this.background = color;
339    }
340
341    /**
342     * Returns the current composite.
343     * 
344     * @return The current composite (never {@code null}).
345     * 
346     * @see #setComposite(java.awt.Composite) 
347     */
348    @Override
349    public Composite getComposite() {
350        return this.composite;
351    }
352    
353    /**
354     * Sets the composite (only {@code AlphaComposite} is handled).
355     * 
356     * @param comp  the composite ({@code null} not permitted).
357     * 
358     * @see #getComposite() 
359     */
360    @Override
361    public void setComposite(Composite comp) {
362        Args.nullNotPermitted(comp, "comp");
363        this.composite = comp;
364        if (comp instanceof AlphaComposite) {
365            AlphaComposite ac = (AlphaComposite) comp;
366            this.gs.applyComposite(ac);
367        } else {
368            this.gs.applyComposite(null);
369        }
370    }
371    
372    /**
373     * Returns the current stroke (used when drawing shapes). 
374     * 
375     * @return The current stroke (never {@code null}). 
376     * 
377     * @see #setStroke(java.awt.Stroke) 
378     */
379    @Override
380    public Stroke getStroke() {
381        return this.stroke;
382    }
383
384    /**
385     * Sets the stroke that will be used to draw shapes.  Only 
386     * {@code BasicStroke} is supported.
387     * 
388     * @param s  the stroke ({@code null} not permitted).
389     * 
390     * @see #getStroke() 
391     */
392    @Override
393    public void setStroke(Stroke s) {
394        Args.nullNotPermitted(s, "s");
395        if (this.stroke.equals(s)) {
396            return;
397        }
398        this.stroke = s;
399        this.gs.applyStroke(s);
400    }
401
402    /**
403     * Returns the current value for the specified hint.  See the 
404     * {@link PDFHints} class for details of the supported hints.
405     * 
406     * @param hintKey  the hint key ({@code null} permitted, but the
407     *     result will be {@code null} also).
408     * 
409     * @return The current value for the specified hint (possibly {@code null}).
410     * 
411     * @see #setRenderingHint(java.awt.RenderingHints.Key, java.lang.Object) 
412     */
413    @Override
414    public Object getRenderingHint(RenderingHints.Key hintKey) {
415        return this.hints.get(hintKey);
416    }
417
418    /**
419     * Sets the value for a hint.  See the {@link PDFHints} class for details
420     * of the supported hints.
421     * 
422     * @param hintKey  the hint key.
423     * @param hintValue  the hint value.
424     * 
425     * @see #getRenderingHint(java.awt.RenderingHints.Key) 
426     */
427    @Override
428    public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {
429        this.hints.put(hintKey, hintValue);
430    }
431
432    /**
433     * Returns a copy of the rendering hints.  Modifying the returned copy
434     * will have no impact on the state of this {@code Graphics2D} 
435     * instance.
436     * 
437     * @return The rendering hints (never {@code null}). 
438     * 
439     * @see #setRenderingHints(java.util.Map) 
440     */
441    @Override
442    public RenderingHints getRenderingHints() {
443        return (RenderingHints) this.hints.clone();
444    }
445
446    /**
447     * Sets the rendering hints to the specified collection.
448     * 
449     * @param hints  the new set of hints ({@code null} not permitted).
450     * 
451     * @see #getRenderingHints() 
452     */
453    @Override
454    public void setRenderingHints(Map<?, ?> hints) {
455        this.hints.clear();
456        this.hints.putAll(hints);
457    }
458
459    /**
460     * Adds all the supplied rendering hints.
461     * 
462     * @param hints  the hints ({@code null} not permitted).
463     */
464    @Override
465    public void addRenderingHints(Map<?, ?> hints) {
466        this.hints.putAll(hints);
467    }
468
469    private Shape invTransformedClip(Shape clip) {
470        Shape result = clip;
471        try {
472            AffineTransform inv = this.transform.createInverse();
473            result = inv.createTransformedShape(clip);
474        } catch (NoninvertibleTransformException e) {
475            //
476        }
477        return result;
478    }
479    /**
480     * Draws the specified shape with the current {@code paint} and 
481     * {@code stroke}.  There is direct handling for {@code Line2D} 
482     * and {@code Path2D} instances.  All other shapes are mapped to a 
483     * {@code GeneralPath} and then drawn (effectively as {@code Path2D} 
484     * objects).
485     * 
486     * @param s  the shape ({@code null} not permitted). 
487     * 
488     * @see #fill(java.awt.Shape) 
489     */
490    @Override
491    public void draw(Shape s) {
492        if (!(this.stroke instanceof BasicStroke)) {
493            fill(this.stroke.createStrokedShape(s));
494            return;
495        }
496        if (s instanceof Line2D) {
497            if (this.clip != null) {
498                this.gs.pushGraphicsState();
499                this.gs.applyClip(invTransformedClip(this.clip));
500                this.gs.drawLine((Line2D) s);
501                this.gs.popGraphicsState();
502            } else {
503                this.gs.drawLine((Line2D) s);
504            }
505        } else if (s instanceof Path2D) {
506            if (this.clip != null) {
507                this.gs.pushGraphicsState();
508                this.gs.applyClip(invTransformedClip(this.clip));
509                this.gs.drawPath2D((Path2D) s);
510                this.gs.popGraphicsState();
511            } else {
512                this.gs.drawPath2D((Path2D) s);                
513            }
514        } else {
515            draw(new GeneralPath(s));  // fallback
516        }
517    }
518
519    /**
520     * Fills the specified shape with the current {@code paint}.  There is
521     * direct handling for {@code Path2D} instances. All other shapes are 
522     * mapped to a {@code GeneralPath} and then filled.
523     * 
524     * @param s  the shape ({@code null} not permitted). 
525     * 
526     * @see #draw(java.awt.Shape) 
527     */    
528    @Override
529    public void fill(Shape s) {
530        if (s instanceof Path2D) {
531            if (this.clip != null) {
532                this.gs.pushGraphicsState();
533                this.gs.applyClip(invTransformedClip(this.clip));
534                this.gs.fillPath2D((Path2D) s);
535                this.gs.popGraphicsState();
536            } else {
537                this.gs.fillPath2D((Path2D) s);
538            }
539        } else {
540            fill(new GeneralPath(s));  // fallback
541        }
542    }
543
544    /**
545     * Returns the current font used for drawing text.
546     * 
547     * @return The current font (never {@code null}).
548     * 
549     * @see #setFont(java.awt.Font) 
550     */
551    @Override
552    public Font getFont() {
553        return this.font;
554    }
555
556    /**
557     * Sets the font to be used for drawing text.
558     * 
559     * @param font  the font ({@code null} is permitted but ignored).
560     * 
561     * @see #getFont() 
562     */
563    @Override
564    public void setFont(Font font) {
565        if (font == null || this.font.equals(font)) {
566            return;
567        }
568        this.font = font;
569        this.gs.applyFont(font);
570    }
571
572    /**
573     * Returns the font metrics for the specified font.
574     * 
575     * @param f  the font.
576     * 
577     * @return The font metrics. 
578     */
579    @Override
580    public FontMetrics getFontMetrics(Font f) {
581        return this.image.createGraphics().getFontMetrics(f);
582    }
583
584    /**
585     * Returns the font render context.  The implementation here returns the
586     * {@code FontRenderContext} for an image that is maintained 
587     * internally (as for {@link #getFontMetrics}).
588     * 
589     * @return The font render context.
590     */
591    @Override
592    public FontRenderContext getFontRenderContext() {
593        return this.fontRenderContext;
594    }
595
596    /**
597     * Draws a string at {@code (x, y)}.  The start of the text at the
598     * baseline level will be aligned with the {@code (x, y)} point.
599     * 
600     * @param str  the string ({@code null} not permitted).
601     * @param x  the x-coordinate.
602     * @param y  the y-coordinate.
603     * 
604     * @see #drawString(java.lang.String, float, float) 
605     */
606    @Override
607    public void drawString(String str, int x, int y) {
608        drawString(str, (float) x, (float) y);
609    }
610
611    /**
612     * Draws a string at {@code (x, y)}. The start of the text at the
613     * baseline level will be aligned with the {@code (x, y)} point.
614     * 
615     * @param str  the string ({@code null} not permitted).
616     * @param x  the x-coordinate.
617     * @param y  the y-coordinate.
618     */
619    @Override
620    public void drawString(String str, float x, float y) {
621        if (str == null) {
622            throw new NullPointerException("Null 'str' argument.");
623        }
624        if (str.isEmpty()) {
625            return; // nothing to do
626        }
627        if (this.clip != null) {
628            this.gs.pushGraphicsState();
629            this.gs.applyClip(invTransformedClip(this.clip));
630        }
631
632        // the following hint allows the user to switch between standard
633        // text output and drawing text as vector graphics
634        if (!PDFHints.VALUE_DRAW_STRING_TYPE_VECTOR.equals(
635                this.hints.get(PDFHints.KEY_DRAW_STRING_TYPE))) {
636            this.gs.drawString(str, x, y);
637        } else {
638            AttributedString as = new AttributedString(str, 
639                    this.font.getAttributes());
640            drawString(as.getIterator(), x, y);
641        }
642        
643        if (this.clip != null) {
644            this.gs.popGraphicsState();
645        }
646    }
647
648    /**
649     * Draws a string of attributed characters at {@code (x, y)}.  The call is 
650     * delegated to 
651     * {@link #drawString(java.text.AttributedCharacterIterator, float, float)}. 
652     * 
653     * @param iterator  an iterator for the characters.
654     * @param x  the x-coordinate.
655     * @param y  the x-coordinate.
656     */
657    @Override
658    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
659        drawString(iterator, (float) x, (float) y); 
660    }
661
662    /**
663     * Draws a string of attributed characters at {@code (x, y)}. 
664     * <p>
665     * <b>LIMITATION</b>: in the current implementation, the string is drawn 
666     * using the current font and the formatting is ignored.
667     * 
668     * @param iterator  an iterator over the characters ({@code null} not 
669     *     permitted).
670     * @param x  the x-coordinate.
671     * @param y  the y-coordinate.
672     */
673    @Override
674    public void drawString(AttributedCharacterIterator iterator, float x, 
675            float y) {
676        TextLayout layout = new TextLayout(iterator, getFontRenderContext());
677        layout.draw(this, x, y);
678    }
679
680    /**
681     * Draws the specified glyph vector at the location {@code (x, y)}.
682     * 
683     * @param g  the glyph vector ({@code null} not permitted).
684     * @param x  the x-coordinate.
685     * @param y  the y-coordinate.
686     */
687    @Override
688    public void drawGlyphVector(GlyphVector g, float x, float y) {
689        fill(g.getOutline(x, y));
690    }
691
692    /**
693     * Applies the translation {@code (tx, ty)}.  This call is delegated 
694     * to {@link #translate(double, double)}.
695     * 
696     * @param tx  the x-translation.
697     * @param ty  the y-translation.
698     * 
699     * @see #translate(double, double) 
700     */
701    @Override
702    public void translate(int tx, int ty) {
703        translate((double) tx, (double) ty);
704    }
705
706    /**
707     * Applies the translation {@code (tx, ty)}.
708     * 
709     * @param tx  the x-translation.
710     * @param ty  the y-translation.
711     */
712    @Override
713    public void translate(double tx, double ty) {
714        AffineTransform t = getTransform();
715        t.translate(tx, ty);
716        setTransform(t);
717    }
718
719    /**
720     * Applies a rotation (anti-clockwise) about {@code (0, 0)}.
721     * 
722     * @param theta  the rotation angle (in radians). 
723     */
724    @Override
725    public void rotate(double theta) {
726        AffineTransform t = getTransform();
727        t.rotate(theta);
728        setTransform(t);
729    }
730
731    /**
732     * Applies a rotation (anti-clockwise) about {@code (x, y)}.
733     * 
734     * @param theta  the rotation angle (in radians).
735     * @param x  the x-coordinate.
736     * @param y  the y-coordinate.
737     */
738    @Override
739    public void rotate(double theta, double x, double y) {
740        translate(x, y);
741        rotate(theta);
742        translate(-x, -y);
743    }
744
745    /**
746     * Applies a scale transformation.
747     * 
748     * @param sx  the x-scaling factor.
749     * @param sy  the y-scaling factor.
750     */
751    @Override
752    public void scale(double sx, double sy) {
753        AffineTransform t = getTransform();
754        t.scale(sx, sy);
755        setTransform(t);
756    }
757
758    /**
759     * Applies a shear transformation. This is equivalent to the following 
760     * call to the {@code transform} method:
761     *
762     * <ul><li>
763     * {@code transform(AffineTransform.getShearInstance(shx, shy));}
764     * </ul>
765     * 
766     * @param shx  the x-shear factor.
767     * @param shy  the y-shear factor.
768     */
769    @Override
770    public void shear(double shx, double shy) {
771        AffineTransform t = AffineTransform.getShearInstance(shx, shy);
772        transform(t);
773    }
774
775    /**
776     * Applies this transform to the existing transform by concatenating it.
777     * 
778     * @param t  the transform ({@code null} not permitted). 
779     */
780    @Override
781    public void transform(AffineTransform t) {
782        AffineTransform tx = getTransform();
783        tx.concatenate(t);
784        setTransform(tx);
785    }
786
787    /**
788     * Returns a copy of the current transform.
789     * 
790     * @return A copy of the current transform (never {@code null}).
791     * 
792     * @see #setTransform(java.awt.geom.AffineTransform) 
793     */
794    @Override
795    public AffineTransform getTransform() {
796        return (AffineTransform) this.transform.clone();
797    }
798
799    /**
800     * Sets the transform.
801     * 
802     * @param t  the new transform ({@code null} permitted, resets to the
803     *     identity transform).
804     * 
805     * @see #getTransform() 
806     */
807    @Override
808    public void setTransform(AffineTransform t) {
809        if (t == null) {
810            this.transform = new AffineTransform();
811        } else {
812            this.transform = new AffineTransform(t);
813        }
814        this.gs.setTransform(this.transform);
815    }
816
817    /**
818     * Returns {@code true} if the rectangle (in device space) intersects
819     * with the shape (the interior, if {@code onStroke} is false, 
820     * otherwise the stroked outline of the shape).
821     * 
822     * @param rect  a rectangle (in device space).
823     * @param s the shape.
824     * @param onStroke  test the stroked outline only?
825     * 
826     * @return A boolean. 
827     */
828    @Override
829    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
830        Shape ts;
831        if (onStroke) {
832            ts = this.transform.createTransformedShape(
833                    this.stroke.createStrokedShape(s));
834        } else {
835            ts = this.transform.createTransformedShape(s);
836        }
837        if (!rect.getBounds2D().intersects(ts.getBounds2D())) {
838            return false;
839        }
840        Area a1 = new Area(rect);
841        Area a2 = new Area(ts);
842        a1.intersect(a2);
843        return !a1.isEmpty();
844    }
845
846    /**
847     * Returns the device configuration associated with this {@code Graphics2D}.
848     * 
849     * @return The graphics configuration.
850     */
851    @Override
852    public GraphicsConfiguration getDeviceConfiguration() {
853        if (this.deviceConfiguration == null) {
854            this.deviceConfiguration = new PDFGraphicsConfiguration(this.width,
855                    this.height);
856        }
857        return this.deviceConfiguration;
858    }
859
860    /**
861     * Does nothing in this {@code PDFGraphics2D} implementation.
862     */
863    @Override
864    public void setPaintMode() {
865        // do nothing
866    }
867
868    /**
869     * Does nothing in this {@code PDFGraphics2D} implementation.
870     * 
871     * @param c  ignored
872     */
873    @Override
874    public void setXORMode(Color c) {
875        // do nothing
876    }
877
878    /**
879     * Returns the user clipping region.  The initial default value is 
880     * {@code null}.
881     * 
882     * @return The user clipping region (possibly {@code null}).
883     * 
884     * @see #setClip(java.awt.Shape) 
885     */
886    @Override
887    public Shape getClip() {
888        if (this.clip == null) {
889            return null;
890        }
891        AffineTransform inv;
892        try {
893            inv = this.transform.createInverse();
894            return inv.createTransformedShape(this.clip);
895        } catch (NoninvertibleTransformException ex) {
896            return null;
897        }
898    }
899
900    /**
901     * Sets the user clipping region.
902     * 
903     * @param shape  the new user clipping region ({@code null} permitted).
904     * 
905     * @see #getClip()
906     */
907    @Override
908    public void setClip(Shape shape) {
909        // null is handled fine here...
910        this.clip = this.transform.createTransformedShape(shape);
911        // the clip does not get applied to the PDF output immediately,
912        // instead it is applied with each draw (or fill) operation by
913        // pushing the current graphics state, applying the clip, doing the 
914        // draw/fill, then popping the graphics state to restore it to the
915        // previous clip
916    }
917
918    /**
919     * Returns the bounds of the user clipping region.  If the user clipping
920     * region is {@code null}, this method will return {@code null}.
921     * 
922     * @return The clip bounds (possibly {@code null}). 
923     * 
924     * @see #getClip() 
925     */
926    @Override
927    public Rectangle getClipBounds() {
928        Shape s = getClip();
929        return s != null ? s.getBounds() : null;
930    }
931
932    /**
933     * Clips to the intersection of the current clipping region and the
934     * specified shape. 
935     * <p>
936     * According to the Oracle API specification, this method will accept a 
937     * {@code null} argument, but there is an open bug report (since 2004) 
938     * that suggests this is wrong:
939     * <p>
940     * <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6206189">
941     * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6206189</a>
942     * 
943     * @param s  the clip shape ({@code null} not permitted). 
944     */
945    @Override
946    public void clip(Shape s) {
947        if (this.clip == null) {
948            setClip(s);
949            return;
950        }
951        Shape ts = this.transform.createTransformedShape(s);
952        if (!ts.intersects(this.clip.getBounds2D())) {
953            setClip(new Rectangle2D.Double());
954        } else {
955          Area a1 = new Area(ts);
956          Area a2 = new Area(this.clip);
957          a1.intersect(a2);
958          this.clip = new Path2D.Double(a1);
959        }
960    }
961
962    /**
963     * Clips to the intersection of the current clipping region and the 
964     * specified rectangle.
965     * 
966     * @param x  the x-coordinate.
967     * @param y  the y-coordinate.
968     * @param width  the width.
969     * @param height  the height.
970     */
971    @Override
972    public void clipRect(int x, int y, int width, int height) {
973        setRect(x, y, width, height);
974        clip(this.rect);
975    }
976
977    /**
978     * Sets the user clipping region to the specified rectangle.
979     * 
980     * @param x  the x-coordinate.
981     * @param y  the y-coordinate.
982     * @param width  the width.
983     * @param height  the height.
984     * 
985     * @see #getClip() 
986     */
987    @Override
988    public void setClip(int x, int y, int width, int height) {
989        // delegate...
990        setClip(new Rectangle(x, y, width, height));
991    }
992
993    /**
994     * Draws a line from {@code (x1, y1)} to {@code (x2, y2)} using 
995     * the current {@code paint} and {@code stroke}.
996     * 
997     * @param x1  the x-coordinate of the start point.
998     * @param y1  the y-coordinate of the start point.
999     * @param x2  the x-coordinate of the end point.
1000     * @param y2  the x-coordinate of the end point.
1001     */
1002    @Override
1003    public void drawLine(int x1, int y1, int x2, int y2) {
1004        if (this.line == null) {
1005            this.line = new Line2D.Double(x1, y1, x2, y2);
1006        } else {
1007            this.line.setLine(x1, y1, x2, y2);
1008        }
1009        draw(this.line);
1010    }
1011
1012    /**
1013     * Fills the specified rectangle with the current {@code paint}.
1014     * 
1015     * @param x  the x-coordinate.
1016     * @param y  the y-coordinate.
1017     * @param width  the rectangle width.
1018     * @param height  the rectangle height.
1019     */
1020    @Override
1021    public void fillRect(int x, int y, int width, int height) {
1022        if (this.rect == null) {
1023            this.rect = new Rectangle2D.Double(x, y, width, height);
1024        } else {
1025            this.rect.setRect(x, y, width, height);
1026        }
1027        fill(this.rect);
1028    }
1029
1030    /**
1031     * Clears the specified rectangle by filling it with the current 
1032     * background color.  If the background color is {@code null}, this
1033     * method will do nothing.
1034     * 
1035     * @param x  the x-coordinate.
1036     * @param y  the y-coordinate.
1037     * @param width  the width.
1038     * @param height  the height.
1039     * 
1040     * @see #getBackground() 
1041     */
1042    @Override
1043    public void clearRect(int x, int y, int width, int height) {
1044        if (getBackground() == null) {
1045            return;  // we can't do anything
1046        }
1047        Paint saved = getPaint();
1048        setPaint(getBackground());
1049        fillRect(x, y, width, height);
1050        setPaint(saved);
1051    }
1052
1053    /**
1054     * Draws a rectangle with rounded corners using the current 
1055     * {@code paint} and {@code stroke}.
1056     * 
1057     * @param x  the x-coordinate.
1058     * @param y  the y-coordinate.
1059     * @param width  the width.
1060     * @param height  the height.
1061     * @param arcWidth  the arc-width.
1062     * @param arcHeight  the arc-height.
1063     * 
1064     * @see #fillRoundRect(int, int, int, int, int, int) 
1065     */
1066    @Override
1067    public void drawRoundRect(int x, int y, int width, int height, 
1068            int arcWidth, int arcHeight) {
1069        setRoundRect(x, y, width, height, arcWidth, arcHeight);
1070        draw(this.roundRect);
1071    }
1072
1073    /**
1074     * Fills a rectangle with rounded corners.
1075     * 
1076     * @param x  the x-coordinate.
1077     * @param y  the y-coordinate.
1078     * @param width  the width.
1079     * @param height  the height.
1080     * @param arcWidth  the arc-width.
1081     * @param arcHeight  the arc-height.
1082     */
1083    @Override
1084    public void fillRoundRect(int x, int y, int width, int height, 
1085            int arcWidth, int arcHeight) {
1086        setRoundRect(x, y, width, height, arcWidth, arcHeight);
1087        fill(this.roundRect);
1088    }
1089
1090    /**
1091     * Draws an oval framed by the rectangle {@code (x, y, width, height)}
1092     * using the current {@code paint} and {@code stroke}.
1093     * 
1094     * @param x  the x-coordinate.
1095     * @param y  the y-coordinate.
1096     * @param width  the width.
1097     * @param height  the height.
1098     * 
1099     * @see #fillOval(int, int, int, int) 
1100     */
1101    @Override
1102    public void drawOval(int x, int y, int width, int height) {
1103        setOval(x, y, width, height);
1104        draw(this.oval);
1105    }
1106
1107    /**
1108     * Fills an oval framed by the rectangle {@code (x, y, width, height)}.
1109     * 
1110     * @param x  the x-coordinate.
1111     * @param y  the y-coordinate.
1112     * @param width  the width.
1113     * @param height  the height.
1114     * 
1115     * @see #drawOval(int, int, int, int) 
1116     */
1117    @Override
1118    public void fillOval(int x, int y, int width, int height) {
1119        setOval(x, y, width, height);
1120        fill(this.oval);
1121    }
1122
1123    /**
1124     * Draws an arc contained within the rectangle 
1125     * {@code (x, y, width, height)}, starting at {@code startAngle}
1126     * and continuing through {@code arcAngle} degrees using 
1127     * the current {@code paint} and {@code stroke}.
1128     * 
1129     * @param x  the x-coordinate.
1130     * @param y  the y-coordinate.
1131     * @param width  the width.
1132     * @param height  the height.
1133     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
1134     * @param arcAngle  the angle (anticlockwise) in degrees.
1135     * 
1136     * @see #fillArc(int, int, int, int, int, int) 
1137     */
1138    @Override
1139    public void drawArc(int x, int y, int width, int height, int startAngle, 
1140            int arcAngle) {
1141        setArc(x, y, width, height, startAngle, arcAngle);
1142        draw(this.arc);
1143    }
1144
1145    /**
1146     * Fills an arc contained within the rectangle 
1147     * {@code (x, y, width, height)}, starting at {@code startAngle}
1148     * and continuing through {@code arcAngle} degrees, using 
1149     * the current {@code paint}
1150     * 
1151     * @param x  the x-coordinate.
1152     * @param y  the y-coordinate.
1153     * @param width  the width.
1154     * @param height  the height.
1155     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
1156     * @param arcAngle  the angle (anticlockwise) in degrees.
1157     * 
1158     * @see #drawArc(int, int, int, int, int, int) 
1159     */
1160    @Override
1161    public void fillArc(int x, int y, int width, int height, int startAngle, 
1162            int arcAngle) {
1163        setArc(x, y, width, height, startAngle, arcAngle);
1164        fill(this.arc);
1165    }
1166    
1167    /**
1168     * Draws the specified multi-segment line using the current 
1169     * {@code paint} and {@code stroke}.
1170     * 
1171     * @param xPoints  the x-points.
1172     * @param yPoints  the y-points.
1173     * @param nPoints  the number of points to use for the polyline.
1174     */
1175    @Override
1176    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
1177        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints,
1178                false);
1179        draw(p);
1180    }
1181
1182    /**
1183     * Draws the specified polygon using the current {@code paint} and 
1184     * {@code stroke}.
1185     * 
1186     * @param xPoints  the x-points.
1187     * @param yPoints  the y-points.
1188     * @param nPoints  the number of points to use for the polygon.
1189     * 
1190     * @see #fillPolygon(int[], int[], int)      */
1191    @Override
1192    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
1193        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints, 
1194                true);
1195        draw(p);
1196    }
1197
1198    /**
1199     * Fills the specified polygon using the current {@code paint}.
1200     * 
1201     * @param xPoints  the x-points.
1202     * @param yPoints  the y-points.
1203     * @param nPoints  the number of points to use for the polygon.
1204     * 
1205     * @see #drawPolygon(int[], int[], int) 
1206     */
1207    @Override
1208    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
1209        GeneralPath p = GraphicsUtils.createPolygon(xPoints, yPoints, nPoints, 
1210                true);
1211        fill(p);
1212    }
1213
1214    /**
1215     * Draws an image with the specified transform. Note that the 
1216     * {@code obs} is ignored.     
1217     * 
1218     * @param img  the image.
1219     * @param xform  the transform ({@code null} permitted).
1220     * @param observer  the image observer (ignored).
1221     * 
1222     * @return {@code true} if the image is drawn. 
1223     */
1224    @Override
1225    public boolean drawImage(Image img, AffineTransform xform, 
1226            ImageObserver observer) {
1227        AffineTransform savedTransform = getTransform();
1228        if (xform != null) {
1229            transform(xform);
1230        }
1231        boolean result = drawImage(img, 0, 0, observer);
1232        if (xform != null) {
1233            setTransform(savedTransform);
1234        }
1235        return result;
1236    }
1237
1238    /**
1239     * Draws the image resulting from applying the {@code BufferedImageOp}
1240     * to the specified image at the location {@code (x, y)}.
1241     * 
1242     * @param img  the image.
1243     * @param op  the operation.
1244     * @param x  the x-coordinate.
1245     * @param y  the y-coordinate.
1246     */
1247    @Override
1248    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
1249        BufferedImage imageToDraw = img;
1250        if (op != null) {
1251            imageToDraw = op.filter(img, null);
1252        }
1253        drawImage(imageToDraw, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
1254    }
1255
1256    /**
1257     * Draws the rendered image. When {@code img} is {@code null} this method
1258     * does nothing.
1259     * 
1260     * @param img  the image ({@code null} permitted).
1261     * @param xform  the transform.
1262     */
1263    @Override
1264    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
1265        if (img == null) { // to match the behaviour specified in the JDK
1266            return;
1267        }
1268        BufferedImage bi = GraphicsUtils.convertRenderedImage(img);
1269        drawImage(bi, xform, null);
1270    }
1271
1272    /**
1273     * Draws the renderable image.
1274     * 
1275     * @param img  the renderable image.
1276     * @param xform  the transform.
1277     */
1278    @Override
1279    public void drawRenderableImage(RenderableImage img, 
1280            AffineTransform xform) {
1281        RenderedImage ri = img.createDefaultRendering();
1282        drawRenderedImage(ri, xform);
1283    }
1284
1285    /**
1286     * Draws an image at the location {@code (x, y)}.  Note that the 
1287     * {@code observer} is ignored.
1288     * 
1289     * @param img  the image.
1290     * @param x  the x-coordinate.
1291     * @param y  the y-coordinate.
1292     * @param observer  ignored.
1293     * 
1294     * @return {@code true} if the image is drawn. 
1295     */
1296    @Override
1297    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
1298        int w = img.getWidth(observer);
1299        if (w < 0) {
1300            return false;
1301        }
1302        int h = img.getHeight(observer);
1303        if (h < 0) {
1304            return false;
1305        }
1306        return drawImage(img, x, y, w, h, observer);
1307    }
1308
1309    /**
1310     * Draws the image into the rectangle defined by {@code (x, y, w, h)}.  
1311     * Note that the {@code observer} is ignored (it is not useful in this
1312     * context).
1313     * 
1314     * @param img  the image.
1315     * @param x  the x-coordinate.
1316     * @param y  the y-coordinate.
1317     * @param w  the width.
1318     * @param h  the height.
1319     * @param observer  ignored.
1320     * 
1321     * @return {@code true} if the image is drawn. 
1322     */
1323    @Override
1324    public boolean drawImage(Image img, int x, int y, int w, int h, 
1325            ImageObserver observer) {
1326        if (this.clip != null) {
1327            this.gs.pushGraphicsState();
1328            this.gs.applyClip(invTransformedClip(this.clip));
1329            this.gs.drawImage(img, x, y, w, h);
1330            this.gs.popGraphicsState();
1331        } else {
1332            this.gs.drawImage(img, x, y, w, h);
1333        }
1334        return true;
1335    }
1336
1337    /**
1338     * Draws an image at the location {@code (x, y)}.  Note that the 
1339     * {@code observer} is ignored.
1340     * 
1341     * @param img  the image.
1342     * @param x  the x-coordinate.
1343     * @param y  the y-coordinate.
1344     * @param bgcolor  the background color ({@code null} permitted).
1345     * @param observer  ignored.
1346     * 
1347     * @return {@code true} if the image is drawn. 
1348     */
1349    @Override
1350    public boolean drawImage(Image img, int x, int y, Color bgcolor, 
1351            ImageObserver observer) {
1352        int w = img.getWidth(null);
1353        if (w < 0) {
1354            return false;
1355        }
1356        int h = img.getHeight(null);
1357        if (h < 0) {
1358            return false;
1359        }
1360        return drawImage(img, x, y, w, h, bgcolor, observer);
1361    }
1362
1363    /**
1364     * Draws an image to the rectangle {@code (x, y, w, h)} (scaling it if
1365     * required), first filling the background with the specified color.  Note 
1366     * that the {@code observer} is ignored.
1367     * 
1368     * @param img  the image.
1369     * @param x  the x-coordinate.
1370     * @param y  the y-coordinate.
1371     * @param w  the width.
1372     * @param h  the height.
1373     * @param bgcolor  the background color ({@code null} permitted).
1374     * @param observer  ignored.
1375     * 
1376     * @return {@code true} if the image is drawn.      
1377     */
1378    @Override
1379    public boolean drawImage(Image img, int x, int y, int w, int h, 
1380            Color bgcolor, ImageObserver observer) {
1381        Paint saved = getPaint();
1382        setPaint(bgcolor);
1383        fillRect(x, y, w, h);
1384        setPaint(saved);
1385        return drawImage(img, x, y, w, h, observer);
1386    }
1387
1388    /**
1389     * Draws part of an image (defined by the source rectangle 
1390     * {@code (sx1, sy1, sx2, sy2)}) into the destination rectangle
1391     * {@code (dx1, dy1, dx2, dy2)}.  Note that the {@code observer} 
1392     * is ignored.
1393     * 
1394     * @param img  the image.
1395     * @param dx1  the x-coordinate for the top left of the destination.
1396     * @param dy1  the y-coordinate for the top left of the destination.
1397     * @param dx2  the x-coordinate for the bottom right of the destination.
1398     * @param dy2  the y-coordinate for the bottom right of the destination.
1399     * @param sx1 the x-coordinate for the top left of the source.
1400     * @param sy1 the y-coordinate for the top left of the source.
1401     * @param sx2 the x-coordinate for the bottom right of the source.
1402     * @param sy2 the y-coordinate for the bottom right of the source.
1403     * 
1404     * @return {@code true} if the image is drawn. 
1405     */
1406    @Override
1407    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 
1408            int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
1409        int w = dx2 - dx1;
1410        int h = dy2 - dy1;
1411        BufferedImage img2 = new BufferedImage(w, h, 
1412                BufferedImage.TYPE_INT_ARGB);
1413        Graphics2D g2 = img2.createGraphics();
1414        g2.drawImage(img, 0, 0, w, h, sx1, sy1, sx2, sy2, null);
1415        return drawImage(img2, dx1, dy1, null);
1416    }
1417
1418    /**
1419     * Draws part of an image (defined by the source rectangle 
1420     * {@code (sx1, sy1, sx2, sy2)}) into the destination rectangle
1421     * {@code (dx1, dy1, dx2, dy2)}.  The destination rectangle is first
1422     * cleared by filling it with the specified {@code bgcolor}. Note that
1423     * the {@code observer} is ignored. 
1424     * 
1425     * @param img  the image.
1426     * @param dx1  the x-coordinate for the top left of the destination.
1427     * @param dy1  the y-coordinate for the top left of the destination.
1428     * @param dx2  the x-coordinate for the bottom right of the destination.
1429     * @param dy2  the y-coordinate for the bottom right of the destination.
1430     * @param sx1 the x-coordinate for the top left of the source.
1431     * @param sy1 the y-coordinate for the top left of the source.
1432     * @param sx2 the x-coordinate for the bottom right of the source.
1433     * @param sy2 the y-coordinate for the bottom right of the source.
1434     * @param bgcolor  the background color ({@code null} permitted).
1435     * @param observer  ignored.
1436     * 
1437     * @return {@code true} if the image is drawn. 
1438     */
1439    @Override
1440    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, 
1441            int sx1, int sy1, int sx2, int sy2, Color bgcolor, 
1442            ImageObserver observer) {
1443        Paint saved = getPaint();
1444        setPaint(bgcolor);
1445        fillRect(dx1, dy1, dx2 - dx1, dy2 - dy1);
1446        setPaint(saved);
1447        return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
1448    }
1449
1450    /**
1451     * This method does nothing.  The operation assumes that the output is in 
1452     * bitmap form, which is not the case for PDF, so we silently ignore
1453     * this method call.
1454     * 
1455     * @param x  the x-coordinate.
1456     * @param y  the y-coordinate.
1457     * @param width  the width of the area.
1458     * @param height  the height of the area.
1459     * @param dx  the delta x.
1460     * @param dy  the delta y.
1461     */
1462    @Override
1463    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
1464        // do nothing, this operation is silently ignored.
1465    }
1466    
1467    /**
1468     * Performs any actions required when the graphics instance is finished
1469     * with.  Here we restore the transform on the graphics stream if this
1470     * instance was created via the {@link #create() } method.  See issue #4
1471     * at GitHub for background info.
1472     */
1473    @Override
1474    public void dispose() {
1475        if (this.originalTransform != null) {
1476            this.gs.setTransform(this.originalTransform);
1477        }
1478    }
1479
1480    /**
1481     * Sets the attributes of the reusable {@link Rectangle2D} object that is
1482     * used by the {@link #drawRect(int, int, int, int)} and 
1483     * {@link #fillRect(int, int, int, int)} methods.
1484     * 
1485     * @param x  the x-coordinate.
1486     * @param y  the y-coordinate.
1487     * @param width  the width.
1488     * @param height  the height.
1489     */
1490    private void setRect(int x, int y, int width, int height) {
1491        if (this.rect == null) {
1492            this.rect = new Rectangle2D.Double(x, y, width, height);
1493        } else {
1494            this.rect.setRect(x, y, width, height);
1495        }
1496    }
1497
1498    /**
1499     * Sets the attributes of the reusable {@link RoundRectangle2D} object that
1500     * is used by the {@link #drawRoundRect(int, int, int, int, int, int)} and
1501     * {@link #fillRoundRect(int, int, int, int, int, int)} methods.
1502     * 
1503     * @param x  the x-coordinate.
1504     * @param y  the y-coordinate.
1505     * @param width  the width.
1506     * @param height  the height.
1507     * @param arcWidth  the arc width.
1508     * @param arcHeight  the arc height.
1509     */
1510    private void setRoundRect(int x, int y, int width, int height, int arcWidth, 
1511            int arcHeight) {
1512        if (this.roundRect == null) {
1513            this.roundRect = new RoundRectangle2D.Double(x, y, width, height, 
1514                    arcWidth, arcHeight);
1515        } else {
1516            this.roundRect.setRoundRect(x, y, width, height, 
1517                    arcWidth, arcHeight);
1518        }        
1519    }
1520
1521    /**
1522     * Sets the attributes of the reusable {@link Ellipse2D} object that is 
1523     * used by the {@link #drawOval(int, int, int, int)} and
1524     * {@link #fillOval(int, int, int, int)} methods.
1525     * 
1526     * @param x  the x-coordinate.
1527     * @param y  the y-coordinate.
1528     * @param width  the width.
1529     * @param height  the height.
1530     */
1531    private void setOval(int x, int y, int width, int height) {
1532        if (this.oval == null) {
1533            this.oval = new Ellipse2D.Double(x, y, width, height);
1534        } else {
1535            this.oval.setFrame(x, y, width, height);
1536        }
1537    }
1538
1539    /**
1540     * Sets the attributes of the reusable {@link Arc2D} object that is used by
1541     * {@link #drawArc(int, int, int, int, int, int)} and 
1542     * {@link #fillArc(int, int, int, int, int, int)} methods.
1543     * 
1544     * @param x  the x-coordinate.
1545     * @param y  the y-coordinate.
1546     * @param width  the width.
1547     * @param height  the height.
1548     * @param startAngle  the start angle in degrees, 0 = 3 o'clock.
1549     * @param arcAngle  the angle (anticlockwise) in degrees.
1550     */
1551    private void setArc(int x, int y, int width, int height, int startAngle, 
1552            int arcAngle) {
1553        if (this.arc == null) {
1554            this.arc = new Arc2D.Double(x, y, width, height, startAngle, 
1555                    arcAngle, Arc2D.OPEN);
1556        } else {
1557            this.arc.setArc(x, y, width, height, startAngle, arcAngle, 
1558                    Arc2D.OPEN);
1559        }        
1560    }
1561 
1562}