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