All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hfg.svg.AbstractSvgNode Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
package com.hfg.svg;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.hfg.css.CSSDeclaration;
import com.hfg.css.CSSProperty;
import com.hfg.css.CssUtil;
import com.hfg.graphics.Graphics2DState;
import com.hfg.graphics.GraphicsUtil;
import com.hfg.graphics.units.GfxSize;
import com.hfg.graphics.units.GfxUnits;
import com.hfg.html.attribute.HTMLColor;
import com.hfg.util.StringBuilderPlus;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.xml.XMLAttribute;
import com.hfg.xml.XMLNamespace;
import com.hfg.xml.XMLTag;
import com.hfg.xml.XMLizable;

//------------------------------------------------------------------------------
/**
 * SVG (Scalable Vector Graphics) tag base class.
 *
 * @author J. Alex Taylor, hairyfatguy.com
 */
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------

public abstract class AbstractSvgNode extends XMLTag implements SvgNode
{
   private static final Pattern FILL_PATTERN = Pattern.compile("\\bfill\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
   private static final Pattern OPACITY_PATTERN = Pattern.compile("\\bopacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
   private static final Pattern FILL_OPACITY_PATTERN = Pattern.compile("\\bfill-opacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
   private static final Pattern STROKE_PATTERN = Pattern.compile("\\bstroke\\s*:\\s*([^;$]+)", Pattern.CASE_INSENSITIVE);
   private static final Pattern STROKE_WIDTH_PATTERN = Pattern.compile("\\bstroke-width\\s*:\\s*([^;\\s]+?)(:?px)?", Pattern.CASE_INSENSITIVE);
   private static final Pattern STROKE_OPACITY_PATTERN = Pattern.compile("\\bstroke-opacity\\s*:\\s*([^;\\s]+)", Pattern.CASE_INSENSITIVE);
   private static final Pattern TRANSFORM_PATTERN = Pattern.compile("\\btransform\\s*:\\s*([^;$]+)", Pattern.CASE_INSENSITIVE);

   //---------------------------------------------------------------------------
   public AbstractSvgNode(String inTagName)
   {
      super(inTagName);
      setNamespace(XMLNamespace.SVG);
   }

   //---------------------------------------------------------------------------
   protected void initFromXMLTag(XMLTag inXMLTag)
   {
      inXMLTag.verifyTagName(getTagName());

      if (CollectionUtil.hasValues(inXMLTag.getAttributes()))
      {
         for (XMLAttribute attr : inXMLTag.getAttributes())
         {
            setAttribute(attr.clone());
         }
      }

      List subtagsAndContent = inXMLTag.getContentPlusSubtagList();
      if (CollectionUtil.hasValues(subtagsAndContent))
      {
         for (Object object : subtagsAndContent)
         {
            if (object instanceof XMLTag)
            {
               addSubtag(SVG.constructFromXMLTag((XMLTag) object));
            }
            else
            {
               addContent((String)object);
            }
         }
      }
   }


   //---------------------------------------------------------------------------
   public SvgNode setId(String inValue)
   {
      setAttribute(SvgAttr.id, inValue);
      return this;
   }


   //--------------------------------------------------------------------------
   public SvgNode addStyle(String inValue)
   {
      CssUtil.addStyle(this, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgNode setStyle(String inValue)
   {
      setAttribute(SvgAttr.style, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgNode setTransform(String inValue)
   {
      setAttribute(SvgAttr.transform, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgNode setFilter(String inValue)
   {
      if (! inValue.startsWith("url("))
      {
         inValue = "url(#" + inValue + ")";
      }

      setAttribute(SvgAttr.CLASS, inValue);
      return this;
   }

   //--------------------------------------------------------------------------
   public SvgNode setClass(String inValue)
   {
      setAttribute(SvgAttr.CLASS, inValue);
      return this;
   }

   //--------------------------------------------------------------------------
   public SvgNode addClass(String inValue)
   {
      String oldValue = getAttributeValue(SvgAttr.CLASS);
      if (oldValue != null)
      {
         inValue = oldValue + " " + inValue;
      }
      setAttribute(SvgAttr.CLASS, inValue);
      return this;
   }

   //--------------------------------------------------------------------------
   /**
    Not called getClass() for obvious reasons.
    */
   public String getClassAttribute()
   {
      return getAttributeValue(SvgAttr.CLASS);
   }


   //---------------------------------------------------------------------------
   public SvgNode setOnMouseOver(String inValue)
   {
      setAttribute(SvgAttr.onmouseover, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgNode setOnMouseOut(String inValue)
   {
      setAttribute(SvgAttr.onmouseout, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgNode setOnMouseDown(String inValue)
   {
      setAttribute(SvgAttr.onmousedown, inValue);
      return this;
   }

   //---------------------------------------------------------------------------
   public SvgNode setOnClick(String inValue)
   {
      setAttribute(SvgAttr.onclick, inValue);
      return this;
   }

   //--------------------------------------------------------------------------
   public void draw(Graphics2D g2)
   {
      // Save settings
      Graphics2DState origState = new Graphics2DState(g2);

      applyTransforms(g2);

      Composite composite = getG2Composite();
      if (composite != null)
      {
         g2.setComposite(composite);
      }

      Font locallyAdjustedFont = getAdjustedFont(origState.getFont());
      if (locallyAdjustedFont != null)
      {
         g2.setFont(locallyAdjustedFont);
      }

      drawSubnodes(g2);

      // Restore settings
      origState.applyTo(g2);
   }

   //---------------------------------------------------------------------------
   public String getCssTransform()
   {
      String transform = null;

      String style = getAttributeValue(SvgAttr.style);
      if (StringUtil.isSet(style))
      {
         List cssDeclarations = CSSDeclaration.parse(style);
         for (CSSDeclaration cssDeclaration : cssDeclarations)
         {
            if (cssDeclaration.getProperty().equals(CSSProperty.transform))
            {
               transform = cssDeclaration.getValue();
               break;
            }
         }
      }

      return transform;
   }

   //---------------------------------------------------------------------------
   /**
    Specifies the upper left corner of the bounding rectangle.
    */
   public SvgNode setPosition(Point2D inValue)
   {
      Rectangle bbox = getBoundsBox();

      StringBuilderPlus transform = new StringBuilderPlus(getCssTransform()).setDelimiter(" ");
      transform.delimitedAppend("translate(" + (inValue.getX() - bbox.getX()) + ", " + (inValue.getY() - bbox.getY()) + ")");

      setTransform(transform.toString());

      return this;
   }

   //---------------------------------------------------------------------------
   public Rectangle getBoundsBox()
   {
      Integer minX = null;
      String xString = getAttributeValue(SvgAttr.x);
      if (StringUtil.isSet(xString))
      {
         if (xString.endsWith("%"))
         {
            // TODO: How to handle percents?
         }
         else
         {
            minX = Integer.parseInt(xString);
         }
      }

      Integer minY = null;
      String yString = getAttributeValue(SvgAttr.y);
      if (StringUtil.isSet(yString))
      {
         if (yString.endsWith("%"))
         {
            // TODO: How to handle percents?
         }
         else
         {
            minY = Integer.parseInt(yString);
         }
      }

      Integer maxX = null;
      Integer maxY = null;

      for (XMLizable node : getSubtags())
      {
         if (node instanceof SvgNode)
         {
            Rectangle rect = ((SvgNode)node).getBoundsBox();
            if (rect != null)
            {
               if (null == minX || rect.getX() < minX) minX = (int) rect.getX();
               if (null == minY || rect.getY() < minY) minY = (int) rect.getY();
               if (null == maxX || rect.getMaxX() > maxX) maxX = (int) rect.getMaxX();
               if (null == maxY || rect.getMaxY() > maxY) maxY = (int) rect.getMaxY();
            }
         }
      }

      Rectangle boundsBox = null;
      if (minX != null
            && minY != null
            && maxX != null
            && maxY != null)
      {
         boundsBox = new Rectangle(minX, minY, maxX - minX, maxY - minY);
         adjustBoundsForTransform(boundsBox);
      }

      return boundsBox;
   }

   //---------------------------------------------------------------------------
   public Point getCenterPoint()
   {
      Rectangle bbox = getBoundsBox();

      return new Point((int) (bbox.getX() + (bbox.getWidth() / 2f)),
                       (int) (bbox.getY() + (bbox.getHeight()) / 2f));
   }



   //---------------------------------------------------------------------------
   protected void rangeCheckOpacityValue(float inValue)
   {
      if (inValue > 1 || inValue < 0)
      {
         throw new RuntimeException("Illegal opacity value: " + inValue + "! Must be between 0 and 1 (inclusive).");
      }
   }

   //---------------------------------------------------------------------------
   // TODO: Font style and weight
   protected Font getAdjustedFont(Font inOrigFont)
   {
      Font font = null;


      if (hasAttribute(SvgAttr.style))
      {
         List declarations = CSSDeclaration.parse(getAttributeValue(SvgAttr.style));
         for (CSSDeclaration declaration : declarations)
         {
            if (declaration.getProperty().equals(CSSProperty.font_size))
            {
               if (null == font)
               {
                  font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
               }

               GfxSize size = GfxSize.allocate(declaration.getValue(), GfxUnits.pixels);
               font = new Font(font.getFontName(), font.getStyle(), size.toInt(GfxUnits.points));
            }
            else if (declaration.getProperty().equals(CSSProperty.font_family))
            {
               if (null == font)
               {
                  font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
               }

               font = new Font(declaration.getValue(), font.getStyle(), font.getSize());
            }
         }
      }

      if (hasAttribute(SvgAttr.fontSize))
      {
         if (null == font)
         {
            font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
         }

         GfxSize size = GfxSize.allocate(getAttributeValue(SvgAttr.fontSize), GfxUnits.pixels);
         font = new Font(font.getFontName(), font.getStyle(), size.toInt(GfxUnits.points));
      }

      if (hasAttribute(SvgAttr.fontFamily))
      {
         if (null == font)
         {
            font = new Font(inOrigFont.getFontName(), inOrigFont.getStyle(), inOrigFont.getSize());
         }

         font = new Font(getAttributeValue(SvgAttr.fontFamily), font.getStyle(), font.getSize());
      }


      return font;
   }

   //--------------------------------------------------------------------------
   protected void applyTransforms(Graphics2D g2)
   {
      if (getAttributeValue(SvgAttr.transform) != null)
      {
         applyTransform(g2, getAttributeValue(SvgAttr.transform));
      }


      if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
      {
         Matcher m = TRANSFORM_PATTERN.matcher(getAttributeValue(SvgAttr.style));
         if (m.find())
         {
            applyTransform(g2, m.group(1));
         }
      }
   }

   //---------------------------------------------------------------------------
   protected void applyTransform(Graphics2D g2, String inTransformAttributeValue)
   {
      // Ex: "rotate(-90.0 118 9) translate(-33.0 3.0 )"
      if (StringUtil.isSet(inTransformAttributeValue))
      {
         String transformValue = inTransformAttributeValue.trim();

         int index;
         while ((index = transformValue.indexOf("(")) > 0)
         {
            String functionName = transformValue.substring(0, index).trim();
            int endArgIndex = transformValue.indexOf(")");
            String[] functionArgs = transformValue.substring(index + 1, endArgIndex).split("\\s*[,\\s]\\s*");
            for (int i = 0; i < functionArgs.length; i++)
            {
               if (functionArgs[i].endsWith("px"))
               {
                  functionArgs[i] = functionArgs[i].substring(0, functionArgs[i].length() - 2);
               }
               else if (functionArgs[i].endsWith("deg"))
               {
                  functionArgs[i] = functionArgs[i].substring(0, functionArgs[i].length() - 3);
               }
            }

            switch (functionName)
            {
               case "rotate":
                  // The first arg is the rotation angle in degrees. The optional second and third args are the anchor point for rotation.
                  // If no anchor point is specified, used the center point of the bounds box.
                  Rectangle contentRectangle = getBoundsBox();

                  double anchorPointX = (3 == functionArgs.length ? Double.parseDouble(functionArgs[1]) : contentRectangle.getX() + (contentRectangle.getWidth() / 2));
                  double anchorPointY = (3 == functionArgs.length ? Double.parseDouble(functionArgs[2]) : contentRectangle.getY() + (contentRectangle.getHeight() / 2));

                  AffineTransform rotation = new AffineTransform();

                  rotation.rotate(Math.toRadians(Double.parseDouble(functionArgs[0])), anchorPointX, anchorPointY);

                  g2.transform(rotation);
                  break;

               case "translate":
                  AffineTransform translation = new AffineTransform();

                  // The y value is optional. Use zero if it was not specified
                  double yTranslation = (functionArgs.length > 1 ? Double.parseDouble(functionArgs[1]) : 0);

                  translation.translate(Double.parseDouble(functionArgs[0]), yTranslation);
                  g2.transform(translation);
                  break;

               case "translateX":
                  translation = new AffineTransform();

                  translation.translate(Double.parseDouble(functionArgs[0]), 0);
                  g2.transform(translation);
                  break;

               case "translateY":
                  translation = new AffineTransform();

                  translation.translate(0, Double.parseDouble(functionArgs[0]));
                  g2.transform(translation);
                  break;

               default:
                  System.err.println(StringUtil.singleQuote(functionName) + " is not a currently supported transform function for conversion to Graphics2D!");
            }

            if (endArgIndex < transformValue.length() - 1)
            {
               transformValue = transformValue.substring(endArgIndex + 1);
            }
            else
            {
               break;
            }
         }
      }
   }


   //--------------------------------------------------------------------------
   protected void adjustBoundsForTransform(Rectangle inBoundsBox)
   {
      if (hasAttribute(SvgAttr.transform))
      {
         String value = getAttributeValue(SvgAttr.transform);
         Matcher m = SvgAttr.TRANSLATE_PATTERN.matcher(value);
         if (m.find())
         {
            int translateX = (int) Float.parseFloat(m.group(1));
            int translateY = (int) Float.parseFloat(m.group(2));

            inBoundsBox.translate(translateX, translateY);
         }
         else
         {
            m = SvgAttr.ROTATE_PATTERN.matcher(value);
            if (m.find())
            {
               Point2D centerOfRotation;
               float angle = Float.parseFloat(m.group(1));
               if (m.group(2) != null)
               {
                  // A center of rotation was specified
                  centerOfRotation = new Point2D.Float(Float.parseFloat(m.group(2)),
                                                       Float.parseFloat(m.group(3)));
               }
               else
               {
                  // Default the center of rotation to the center of the bounds box
                  centerOfRotation = new Point2D.Double(inBoundsBox.getX() + (inBoundsBox.getWidth() / 2),
                                                       inBoundsBox.getY() + (inBoundsBox.getHeight() / 2));

               }

               Rectangle adjustedBounds = GraphicsUtil.rotate(inBoundsBox, angle, centerOfRotation).getBounds();
               inBoundsBox.setBounds(adjustedBounds);
            }
         }

      }
   }

   //--------------------------------------------------------------------------
   protected void drawSubnodes(Graphics2D g2)
   {
      if (CollectionUtil.hasValues(getSubtags()))
      {
         for (XMLizable node : getSubtags())
         {
            if (node instanceof AbstractSvgNode)
            {
               ((AbstractSvgNode)node).draw(g2);
            }
         }
      }
   }

   //--------------------------------------------------------------------------
   protected Composite getG2Composite()
   {
      String opacityString = null;
      if (StringUtil.isSet(getAttributeValue(SvgAttr.opacity)))
      {
         if (! getAttributeValue(SvgAttr.opacity).equals(SvgAttr.Value.none))
         {
            opacityString = getAttributeValue(SvgAttr.opacity);
         }
      }
      else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
      {
         Matcher m = OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style));
         if (m.find())
         {
            opacityString = m.group(1);
         }
      }

      Composite composite = null;
      if (StringUtil.isSet(opacityString))
      {
         composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, Float.parseFloat(opacityString.trim()));
      }

      return composite;
   }

   //--------------------------------------------------------------------------
   protected Paint getG2Paint()
   {
      String colorString = null;
      if (StringUtil.isSet(getAttributeValue(SvgAttr.fill)))
      {
         if (! getAttributeValue(SvgAttr.fill).equals(SvgAttr.Value.none))
         {
            colorString = getAttributeValue(SvgAttr.fill);
         }
      }
      else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
      {
         Matcher m = FILL_PATTERN.matcher(getAttributeValue(SvgAttr.style));
         if (m.find())
         {
            colorString = m.group(1);
         }
      }

      Color color = null;
      if (StringUtil.isSet(colorString))
      {
         if (colorString.startsWith("#"))
         {
            color = Color.decode(colorString);
         }
         else
         {
            color = Color.getColor(colorString);
         }
      }

      String opacityString = null;
      if (StringUtil.isSet(getAttributeValue(SvgAttr.fillOpacity)))
      {
         if (! getAttributeValue(SvgAttr.fillOpacity).equals(SvgAttr.Value.none))
         {
            opacityString = getAttributeValue(SvgAttr.fillOpacity);
         }
      }
      else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
      {
         Matcher m = FILL_OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style));
         if (m.find())
         {
            opacityString = m.group(1);
         }
      }

      if (color != null
          && StringUtil.isSet(opacityString))
      {
         color = new Color(color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f, Float.parseFloat(opacityString));
      }

      return color;
   }

   //--------------------------------------------------------------------------
   protected Stroke getG2Stroke()
   {
      Stroke stroke = null;
      if (StringUtil.isSet(getAttributeValue(SvgAttr.strokeWidth)))
      {
         stroke = new BasicStroke(Integer.parseInt(getAttributeValue(SvgAttr.strokeWidth)));
      }
      else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
      {
         Matcher m = STROKE_WIDTH_PATTERN.matcher(getAttributeValue(SvgAttr.style));
         if (m.find())
         {
            stroke = new BasicStroke(Integer.parseInt(m.group(1)));
         }
      }

      return stroke;
   }

   //--------------------------------------------------------------------------
   protected Color getG2StrokeColor()
   {
      String colorString = null;
      if (StringUtil.isSet(getAttributeValue(SvgAttr.stroke)))
      {
         colorString = getAttributeValue(SvgAttr.stroke);
      }
      else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
      {
         Matcher m = STROKE_PATTERN.matcher(getAttributeValue(SvgAttr.style));
         if (m.find())
         {
            colorString = m.group(1);
         }
      }

      Color color = null;
      if (StringUtil.isSet(colorString))
      {
         if (colorString.startsWith("#"))
         {
            try
            {
               color = Color.decode(colorString);
            }
            catch (Exception e)
            {
               throw new RuntimeException("Problem decoding color string " + StringUtil.singleQuote(colorString) + "!");
            }
         }
         else
         {
            color = HTMLColor.valueOf(colorString);
         }
      }

      String opacityString = null;
      if (StringUtil.isSet(getAttributeValue(SvgAttr.strokeOpacity)))
      {
         if (! getAttributeValue(SvgAttr.strokeOpacity).equals(SvgAttr.Value.none))
         {
            opacityString = getAttributeValue(SvgAttr.strokeOpacity);
         }
      }
      else if (StringUtil.isSet(getAttributeValue(SvgAttr.style)))
      {
         Matcher m = STROKE_OPACITY_PATTERN.matcher(getAttributeValue(SvgAttr.style));
         if (m.find())
         {
            opacityString = m.group(1);
         }
      }

      if (StringUtil.isSet(opacityString))
      {
         if (null == color)
         {
            color = Color.BLACK;
         }

         color = new Color(color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f, Float.parseFloat(opacityString));
      }

      return color;
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy