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