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}