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}