001package com.hfg.svg; 002 003import java.awt.*; 004import java.awt.geom.AffineTransform; 005import java.awt.geom.Point2D; 006import java.awt.geom.Rectangle2D; 007import java.util.List; 008import java.util.regex.Matcher; 009import java.util.regex.Pattern; 010 011import com.hfg.css.CSSDeclaration; 012import com.hfg.css.CSSProperty; 013import com.hfg.css.CssUtil; 014import com.hfg.graphics.Graphics2DState; 015import com.hfg.graphics.GraphicsUtil; 016import com.hfg.graphics.units.GfxSize; 017import com.hfg.graphics.units.GfxUnits; 018import com.hfg.html.attribute.HTMLColor; 019import com.hfg.util.StringBuilderPlus; 020import com.hfg.util.StringUtil; 021import com.hfg.util.collection.CollectionUtil; 022import com.hfg.util.io.GZIP; 023import com.hfg.xml.*; 024 025//------------------------------------------------------------------------------ 026/** 027 * SVG (Scalable Vector Graphics) tag base class. 028 * 029 * @author J. Alex Taylor, hairyfatguy.com 030 */ 031//------------------------------------------------------------------------------ 032// com.hfg XML/HTML Coding Library 033// 034// This library is free software; you can redistribute it and/or 035// modify it under the terms of the GNU Lesser General Public 036// License as published by the Free Software Foundation; either 037// version 2.1 of the License, or (at your option) any later version. 038// 039// This library is distributed in the hope that it will be useful, 040// but WITHOUT ANY WARRANTY; without even the implied warranty of 041// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 042// Lesser General Public License for more details. 043// 044// You should have received a copy of the GNU Lesser General Public 045// License along with this library; if not, write to the Free Software 046// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 047// 048// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 049// jataylor@hairyfatguy.com 050//------------------------------------------------------------------------------ 051 052public abstract class AbstractSvgNode extends XMLTag implements SvgNode 053{ 054 private static final Pattern FILL_PATTERN = Pattern.compile("\\bfill\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE); 055 private static final Pattern OPACITY_PATTERN = Pattern.compile("\\bopacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE); 056 private static final Pattern FILL_OPACITY_PATTERN = Pattern.compile("\\bfill-opacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE); 057 private static final Pattern STROKE_PATTERN = Pattern.compile("\\bstroke\\s*:\\s*([^;$]+)", Pattern.CASE_INSENSITIVE); 058 private static final Pattern STROKE_WIDTH_PATTERN = Pattern.compile("\\bstroke-width\\s*:\\s*([^;\\s]+?)(:?px)?", Pattern.CASE_INSENSITIVE); 059 private static final Pattern STROKE_OPACITY_PATTERN = Pattern.compile("\\bstroke-opacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE); 060 private static final Pattern TRANSFORM_PATTERN = Pattern.compile("\\btransform\\s*:\\s*([^;$]+)", Pattern.CASE_INSENSITIVE); 061 062 private XMLTag mTitleTag; 063 064 //--------------------------------------------------------------------------- 065 public AbstractSvgNode(String inTagName) 066 { 067 super(inTagName); 068 setNamespace(XMLNamespace.SVG); 069 } 070 071 //--------------------------------------------------------------------------- 072 protected void initFromXMLTag(XMLTag inXMLTag) 073 { 074 inXMLTag.verifyTagName(getTagName()); 075 076 if (CollectionUtil.hasValues(inXMLTag.getAttributes())) 077 { 078 for (XMLAttribute attr : inXMLTag.getAttributes()) 079 { 080 setAttribute(attr.clone()); 081 } 082 } 083 084 List subtagsAndContent = inXMLTag.getContentPlusSubtagList(); 085 if (CollectionUtil.hasValues(subtagsAndContent)) 086 { 087 for (Object object : subtagsAndContent) 088 { 089 if (object instanceof XMLTag) 090 { 091 addSubtag(SVG.constructFromXMLTag((XMLTag) object)); 092 } 093 else if (object instanceof XMLizable) 094 { 095 addSubtag((XMLizable) object); 096 } 097 else 098 { 099 String content; 100 if (object instanceof byte[]) 101 { 102 // Large content gets gzip-compressed 103 content = GZIP.uncompressToString((byte[]) object); 104 } 105 else 106 { 107 content = (String) object; 108 } 109 110 int index = content.indexOf("<![CDATA["); 111 if (index >= 0) 112 { 113 boolean hideWithCommentsFromLegacyBrowsers = false; 114 content = content.substring(index + 9); 115 if (content.startsWith("//><!--\n")) 116 { 117 content = content.substring(8); 118 hideWithCommentsFromLegacyBrowsers = true; 119 } 120 121 index = content.lastIndexOf("]]>"); 122 content = content.substring(0, index); 123 124 if (content.endsWith("\n//--><!")) 125 { 126 content = content.substring(0, content.length() - 8); 127 } 128 129 addSubtag(new XMLCDATA(content).setHideWithCommentsForLegacyBrowsers(hideWithCommentsFromLegacyBrowsers)); 130 } 131 132 addContent(content); 133 } 134 } 135 } 136 } 137 138 139 //--------------------------------------------------------------------------- 140 public SvgNode setId(String inValue) 141 { 142 setAttribute(SvgAttr.id, inValue); 143 return this; 144 } 145 146 147 //-------------------------------------------------------------------------- 148 public SvgNode addStyle(String inValue) 149 { 150 CssUtil.addStyle(this, inValue); 151 return this; 152 } 153 154 //--------------------------------------------------------------------------- 155 public SvgNode setStyle(String inValue) 156 { 157 setAttribute(SvgAttr.style, inValue); 158 return this; 159 } 160 161 //--------------------------------------------------------------------------- 162 public SvgNode setTitle(String inValue) 163 { 164 if (StringUtil.isSet(inValue)) 165 { 166 if (null == mTitleTag) 167 { 168 mTitleTag = addSubtag(SVG.title); 169 } 170 171 mTitleTag.setContent(inValue); 172 } 173 else if (mTitleTag != null) 174 { 175 removeSubtag(mTitleTag); 176 mTitleTag = null; 177 } 178 179 return this; 180 } 181 182 //--------------------------------------------------------------------------- 183 public SvgNode setTransform(String inValue) 184 { 185 setAttribute(SvgAttr.transform, inValue); 186 return this; 187 } 188 189 //--------------------------------------------------------------------------- 190 public String getTransform() 191 { 192 return getAttributeValue(SvgAttr.transform); 193 } 194 195 //--------------------------------------------------------------------------- 196 public SvgNode setFilter(String inValue) 197 { 198 if (! inValue.startsWith("url(")) 199 { 200 inValue = "url(#" + inValue + ")"; 201 } 202 203 setAttribute(SvgAttr.CLASS, inValue); 204 return this; 205 } 206 207 //-------------------------------------------------------------------------- 208 public SvgNode setClass(String inValue) 209 { 210 setAttribute(SvgAttr.CLASS, inValue); 211 return this; 212 } 213 214 //-------------------------------------------------------------------------- 215 public SvgNode addClass(String inValue) 216 { 217 String oldValue = getAttributeValue(SvgAttr.CLASS); 218 if (oldValue != null) 219 { 220 inValue = oldValue + " " + inValue; 221 } 222 setAttribute(SvgAttr.CLASS, inValue); 223 return this; 224 } 225 226 //-------------------------------------------------------------------------- 227 /** 228 Not called getClass() for obvious reasons. 229 */ 230 public String getClassAttribute() 231 { 232 return getAttributeValue(SvgAttr.CLASS); 233 } 234 235 236 //--------------------------------------------------------------------------- 237 public SvgNode setOnMouseOver(String inValue) 238 { 239 setAttribute(SvgAttr.onmouseover, inValue); 240 return this; 241 } 242 243 //--------------------------------------------------------------------------- 244 public SvgNode setOnMouseOut(String inValue) 245 { 246 setAttribute(SvgAttr.onmouseout, inValue); 247 return this; 248 } 249 250 //--------------------------------------------------------------------------- 251 public SvgNode setOnMouseDown(String inValue) 252 { 253 setAttribute(SvgAttr.onmousedown, inValue); 254 return this; 255 } 256 257 //--------------------------------------------------------------------------- 258 public SvgNode setOnMouseUp(String inValue) 259 { 260 setAttribute(SvgAttr.onmouseup, inValue); 261 return this; 262 } 263 264 //--------------------------------------------------------------------------- 265 public SvgNode setOnClick(String inValue) 266 { 267 setAttribute(SvgAttr.onclick, inValue); 268 return this; 269 } 270 271 //-------------------------------------------------------------------------- 272 public void draw(Graphics2D g2) 273 { 274 // Save settings 275 Graphics2DState origState = new Graphics2DState(g2); 276 277 applyTransforms(g2); 278 279 Composite composite = getG2Composite(); 280 if (composite != null) 281 { 282 g2.setComposite(composite); 283 } 284 285 Font locallyAdjustedFont = getAdjustedFont(origState.getFont()); 286 if (locallyAdjustedFont != null) 287 { 288 g2.setFont(locallyAdjustedFont); 289 } 290 291 drawSubnodes(g2); 292 293 // Restore settings 294 origState.applyTo(g2); 295 } 296 297 //--------------------------------------------------------------------------- 298 public String getCssTransform() 299 { 300 String transform = null; 301 302 String style = getAttributeValue(SvgAttr.style); 303 if (StringUtil.isSet(style)) 304 { 305 List<CSSDeclaration> cssDeclarations = CSSDeclaration.parse(style); 306 for (CSSDeclaration cssDeclaration : cssDeclarations) 307 { 308 if (cssDeclaration.getProperty().equals(CSSProperty.transform)) 309 { 310 transform = cssDeclaration.getValue(); 311 break; 312 } 313 } 314 } 315 316 return transform; 317 } 318 319 //--------------------------------------------------------------------------- 320 /** 321 Specifies the upper left corner of the bounding rectangle. 322 */ 323 public SvgNode setPosition(Point2D inValue) 324 { 325 Rectangle2D bbox = getBoundsBox(); 326 327 StringBuilderPlus transform = new StringBuilderPlus(getCssTransform()).setDelimiter(" "); 328 transform.delimitedAppend("translate(" + (inValue.getX() - bbox.getX()) + ", " + (inValue.getY() - bbox.getY()) + ")"); 329 330 setTransform(transform.toString()); 331 332 return this; 333 } 334 335 //--------------------------------------------------------------------------- 336 public Rectangle2D getBoundsBox() 337 { 338 Double minX = null; 339 String xString = getAttributeValue(SvgAttr.x); 340 if (StringUtil.isSet(xString)) 341 { 342 if (xString.endsWith("%")) 343 { 344 // TODO: How to handle percents? 345 } 346 else 347 { 348 minX = Double.parseDouble(xString); 349 } 350 } 351 352 Double minY = null; 353 String yString = getAttributeValue(SvgAttr.y); 354 if (StringUtil.isSet(yString)) 355 { 356 if (yString.endsWith("%")) 357 { 358 // TODO: How to handle percents? 359 } 360 else 361 { 362 minY = Double.parseDouble(yString); 363 } 364 } 365 366 Double maxX = null; 367 Double maxY = null; 368 369 for (XMLizable node : getSubtags()) 370 { 371 if (node instanceof SvgNode) 372 { 373 Rectangle2D rect = ((SvgNode)node).getBoundsBox(); 374 if (rect != null) 375 { 376 if (null == minX || rect.getX() < minX) minX = rect.getX(); 377 if (null == minY || rect.getY() < minY) minY = rect.getY(); 378 if (null == maxX || rect.getMaxX() > maxX) maxX = rect.getMaxX(); 379 if (null == maxY || rect.getMaxY() > maxY) maxY = rect.getMaxY(); 380 } 381 } 382 } 383 384 Rectangle2D boundsBox = null; 385 if (minX != null 386 && minY != null 387 && maxX != null 388 && maxY != null) 389 { 390 boundsBox = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); 391 adjustBoundsForTransform(boundsBox); 392 } 393 394 return boundsBox; 395 } 396 397 //--------------------------------------------------------------------------- 398 public Point2D getCenterPoint() 399 { 400 Rectangle2D bbox = getBoundsBox(); 401 402 return new Point2D.Double(bbox.getX() + (bbox.getWidth() / 2f), 403 bbox.getY() + (bbox.getHeight()) / 2f); 404 } 405 406 407 408 //--------------------------------------------------------------------------- 409 protected void rangeCheckOpacityValue(float inValue) 410 { 411 if (inValue > 1 || inValue < 0) 412 { 413 throw new RuntimeException("Illegal opacity value: " + inValue + "! Must be between 0 and 1 (inclusive)."); 414 } 415 } 416 417 //--------------------------------------------------------------------------- 418 // TODO: Font style and weight 419 protected Font getAdjustedFont(Font inOrigFont) 420 { 421 Font font = null; 422 423 424 if (hasAttribute(SvgAttr.style)) 425 { 426 List<CSSDeclaration> declarations = CSSDeclaration.parse(getAttributeValue(SvgAttr.style)); 427 for (CSSDeclaration declaration : declarations) 428 { 429 if (declaration.getProperty().equals(CSSProperty.font_size)) 430 { 431 if (null == font) 432 { 433 font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize()); 434 } 435 436 GfxSize size = GfxSize.allocate(declaration.getValue(), GfxUnits.pixels); 437 font = new Font(font.getFontName(), font.getStyle(), size.toInt(GfxUnits.points)); 438 } 439 else if (declaration.getProperty().equals(CSSProperty.font_family)) 440 { 441 if (null == font) 442 { 443 font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize()); 444 } 445 446 font = new Font(declaration.getValue(), font.getStyle(), font.getSize()); 447 } 448 } 449 } 450 451 if (hasAttribute(SvgAttr.fontSize)) 452 { 453 if (null == font) 454 { 455 font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize()); 456 } 457 458 GfxSize size = GfxSize.allocate(getAttributeValue(SvgAttr.fontSize), GfxUnits.pixels); 459 font = new Font(font.getFontName(), font.getStyle(), size.toInt(GfxUnits.points)); 460 } 461 462 if (hasAttribute(SvgAttr.fontFamily)) 463 { 464 if (null == font) 465 { 466 font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize()); 467 } 468 469 font = new Font(getAttributeValue(SvgAttr.fontFamily), font.getStyle(), font.getSize()); 470 } 471 472 473 return font; 474 } 475 476 //-------------------------------------------------------------------------- 477 protected void applyTransforms(Graphics2D g2) 478 { 479 if (getAttributeValue(SvgAttr.transform) != null) 480 { 481 applyTransform(g2, getAttributeValue(SvgAttr.transform)); 482 } 483 484 485 if (StringUtil.isSet(getAttributeValue(SvgAttr.style))) 486 { 487 Matcher m = TRANSFORM_PATTERN.matcher(getAttributeValue(SvgAttr.style)); 488 if (m.find()) 489 { 490 applyTransform(g2, m.group(1)); 491 } 492 } 493 } 494 495 //--------------------------------------------------------------------------- 496 protected void applyTransform(Graphics2D g2, String inTransformAttributeValue) 497 { 498 // Ex: "rotate(-90.0 118 9) translate(-33.0 3.0 )" 499 if (StringUtil.isSet(inTransformAttributeValue)) 500 { 501 String transformValue = inTransformAttributeValue.trim(); 502 503 int index; 504 while ((index = transformValue.indexOf("(")) > 0) 505 { 506 String functionName = transformValue.substring(0, index).trim(); 507 int endArgIndex = transformValue.indexOf(")"); 508 String[] functionArgs = transformValue.substring(index + 1, endArgIndex).split("\\s*[,\\s]\\s*"); 509 for (int i = 0; i < functionArgs.length; i++) 510 { 511 if (functionArgs[i].endsWith("px")) 512 { 513 functionArgs[i] = functionArgs[i].substring(0, functionArgs[i].length() - 2); 514 } 515 else if (functionArgs[i].endsWith("deg")) 516 { 517 functionArgs[i] = functionArgs[i].substring(0, functionArgs[i].length() - 3); 518 } 519 } 520 521 switch (functionName) 522 { 523 case "rotate": 524 // The first arg is the rotation angle in degrees. The optional second and third args are the anchor point for rotation. 525 // If no anchor point is specified, used the center point of the bounds box. 526 Rectangle2D contentRectangle = getBoundsBox(); 527 528 double anchorPointX = (3 == functionArgs.length ? Double.parseDouble(functionArgs[1]) : contentRectangle.getX() + (contentRectangle.getWidth() / 2)); 529 double anchorPointY = (3 == functionArgs.length ? Double.parseDouble(functionArgs[2]) : contentRectangle.getY() + (contentRectangle.getHeight() / 2)); 530 531 AffineTransform rotation = new AffineTransform(); 532 533 rotation.rotate(Math.toRadians(Double.parseDouble(functionArgs[0])), anchorPointX, anchorPointY); 534 535 g2.transform(rotation); 536 break; 537 538 case "translate": 539 AffineTransform translation = new AffineTransform(); 540 541 // The y value is optional. Use zero if it was not specified 542 double yTranslation = (functionArgs.length > 1 ? Double.parseDouble(functionArgs[1]) : 0); 543 544 translation.translate(Double.parseDouble(functionArgs[0]), yTranslation); 545 g2.transform(translation); 546 break; 547 548 case "translateX": 549 translation = new AffineTransform(); 550 551 translation.translate(Double.parseDouble(functionArgs[0]), 0); 552 g2.transform(translation); 553 break; 554 555 case "translateY": 556 translation = new AffineTransform(); 557 558 translation.translate(0, Double.parseDouble(functionArgs[0])); 559 g2.transform(translation); 560 break; 561 562 default: 563 System.err.println(StringUtil.singleQuote(functionName) + " is not a currently supported transform function for conversion to Graphics2D!"); 564 } 565 566 if (endArgIndex < transformValue.length() - 1) 567 { 568 transformValue = transformValue.substring(endArgIndex + 1); 569 } 570 else 571 { 572 break; 573 } 574 } 575 } 576 } 577 578 579 //-------------------------------------------------------------------------- 580 protected void adjustBoundsForTransform(Rectangle2D inBoundsBox) 581 { 582 if (hasAttribute(SvgAttr.transform)) 583 { 584 String value = getAttributeValue(SvgAttr.transform); 585 Matcher m = SvgAttr.TRANSLATE_PATTERN.matcher(value); 586 if (m.find()) 587 { 588 float translateX = Float.parseFloat(m.group(1)); 589 float translateY = Float.parseFloat(m.group(2)); 590 591// inBoundsBox.translate(translateX, translateY); 592 inBoundsBox.setRect(inBoundsBox.getX() + translateX, inBoundsBox.getY() + translateY, inBoundsBox.getWidth(), inBoundsBox.getHeight()); 593 } 594 else 595 { 596 m = SvgAttr.ROTATE_PATTERN.matcher(value); 597 if (m.find()) 598 { 599 Point2D centerOfRotation; 600 float angle = Float.parseFloat(m.group(1)); 601 if (m.group(2) != null) 602 { 603 // A center of rotation was specified 604 centerOfRotation = new Point2D.Float(Float.parseFloat(m.group(2)), 605 Float.parseFloat(m.group(3))); 606 } 607 else 608 { 609 // Default the center of rotation to the center of the bounds box 610 centerOfRotation = new Point2D.Double(inBoundsBox.getX() + (inBoundsBox.getWidth() / 2), 611 inBoundsBox.getY() + (inBoundsBox.getHeight() / 2)); 612 613 } 614 615 Rectangle2D adjustedBounds = GraphicsUtil.rotate(inBoundsBox, angle, centerOfRotation).getBounds2D(); 616 inBoundsBox.setRect(adjustedBounds.getX(), adjustedBounds.getY(), adjustedBounds.getWidth(), adjustedBounds.getHeight()); 617 } 618 } 619 620 } 621 } 622 623 //-------------------------------------------------------------------------- 624 protected void drawSubnodes(Graphics2D g2) 625 { 626 if (CollectionUtil.hasValues(getSubtags())) 627 { 628 for (XMLizable node : getSubtags()) 629 { 630 if (node instanceof AbstractSvgNode) 631 { 632 ((AbstractSvgNode)node).draw(g2); 633 } 634 } 635 } 636 } 637 638 //-------------------------------------------------------------------------- 639 protected Composite getG2Composite() 640 { 641 String opacityString = null; 642 if (StringUtil.isSet(getAttributeValue(SvgAttr.opacity))) 643 { 644 if (! getAttributeValue(SvgAttr.opacity).equals(SvgAttr.Value.none)) 645 { 646 opacityString = getAttributeValue(SvgAttr.opacity); 647 } 648 } 649 else if (StringUtil.isSet(getAttributeValue(SvgAttr.style))) 650 { 651 Matcher m = OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style)); 652 if (m.find()) 653 { 654 opacityString = m.group(1); 655 } 656 } 657 658 Composite composite = null; 659 if (StringUtil.isSet(opacityString)) 660 { 661 composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Float.parseFloat(opacityString.trim())); 662 } 663 664 return composite; 665 } 666 667 //-------------------------------------------------------------------------- 668 protected Paint getG2Paint() 669 { 670 String colorString = null; 671 if (StringUtil.isSet(getAttributeValue(SvgAttr.fill))) 672 { 673 if (! getAttributeValue(SvgAttr.fill).equals(SvgAttr.Value.none)) 674 { 675 colorString = getAttributeValue(SvgAttr.fill); 676 } 677 } 678 else if (StringUtil.isSet(getAttributeValue(SvgAttr.style))) 679 { 680 Matcher m = FILL_PATTERN.matcher(getAttributeValue(SvgAttr.style)); 681 if (m.find()) 682 { 683 colorString = m.group(1); 684 } 685 } 686 687 Color color = null; 688 if (StringUtil.isSet(colorString)) 689 { 690 if (colorString.startsWith("#")) 691 { 692 color = Color.decode(colorString); 693 } 694 else 695 { 696 color = Color.getColor(colorString); 697 } 698 } 699 700 String opacityString = null; 701 if (StringUtil.isSet(getAttributeValue(SvgAttr.fillOpacity))) 702 { 703 if (! getAttributeValue(SvgAttr.fillOpacity).equals(SvgAttr.Value.none)) 704 { 705 opacityString = getAttributeValue(SvgAttr.fillOpacity); 706 } 707 } 708 else if (StringUtil.isSet(getAttributeValue(SvgAttr.style))) 709 { 710 Matcher m = FILL_OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style)); 711 if (m.find()) 712 { 713 opacityString = m.group(1); 714 } 715 } 716 717 if (color != null 718 && StringUtil.isSet(opacityString)) 719 { 720 color = new Color(color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f, Float.parseFloat(opacityString)); 721 } 722 723 return color; 724 } 725 726 //-------------------------------------------------------------------------- 727 protected Stroke getG2Stroke() 728 { 729 Stroke stroke = null; 730 if (StringUtil.isSet(getAttributeValue(SvgAttr.strokeWidth))) 731 { 732 stroke = new BasicStroke(Integer.parseInt(getAttributeValue(SvgAttr.strokeWidth))); 733 } 734 else if (StringUtil.isSet(getAttributeValue(SvgAttr.style))) 735 { 736 Matcher m = STROKE_WIDTH_PATTERN.matcher(getAttributeValue(SvgAttr.style)); 737 if (m.find()) 738 { 739 stroke = new BasicStroke(Integer.parseInt(m.group(1))); 740 } 741 } 742 743 return stroke; 744 } 745 746 //-------------------------------------------------------------------------- 747 protected Color getG2StrokeColor() 748 { 749 String colorString = null; 750 if (StringUtil.isSet(getAttributeValue(SvgAttr.stroke))) 751 { 752 colorString = getAttributeValue(SvgAttr.stroke); 753 } 754 else if (StringUtil.isSet(getAttributeValue(SvgAttr.style))) 755 { 756 Matcher m = STROKE_PATTERN.matcher(getAttributeValue(SvgAttr.style)); 757 if (m.find()) 758 { 759 colorString = m.group(1); 760 } 761 } 762 763 Color color = null; 764 if (StringUtil.isSet(colorString)) 765 { 766 if (colorString.startsWith("#")) 767 { 768 try 769 { 770 color = Color.decode(colorString); 771 } 772 catch (Exception e) 773 { 774 throw new RuntimeException("Problem decoding color string " + StringUtil.singleQuote(colorString) + "!"); 775 } 776 } 777 else 778 { 779 color = HTMLColor.valueOf(colorString); 780 } 781 } 782 783 String opacityString = null; 784 if (StringUtil.isSet(getAttributeValue(SvgAttr.strokeOpacity))) 785 { 786 if (! getAttributeValue(SvgAttr.strokeOpacity).equals(SvgAttr.Value.none)) 787 { 788 opacityString = getAttributeValue(SvgAttr.strokeOpacity); 789 } 790 } 791 else if (StringUtil.isSet(getAttributeValue(SvgAttr.style))) 792 { 793 Matcher m = STROKE_OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style)); 794 if (m.find()) 795 { 796 opacityString = m.group(1); 797 } 798 } 799 800 if (StringUtil.isSet(opacityString)) 801 { 802 if (null == color) 803 { 804 color = Color.BLACK; 805 } 806 807 color = new Color(color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f, Float.parseFloat(opacityString)); 808 } 809 810 return color; 811 } 812}