    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}