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