public static bool TryGetValueInPx(XAttribute attribute, out float value)
        {
            value = float.NaN;
            var text = attribute?.Value;

            if (string.IsNullOrWhiteSpace(text))
            {
                return(false);
            }

            var    valueText = "";
            string unit      = null;

            foreach (var c in text.Trim())
            {
                if (char.IsLetter(c) && char.ToLower(c) != 'e')
                {
                    unit += c;
                }
                else if (string.IsNullOrWhiteSpace(unit))
                {
                    valueText += c;
                }
            }
            if (string.IsNullOrWhiteSpace(unit))
            {
                return(float.TryParse(valueText, out value));
            }
            var orientation = Orientation.Unknown;

            if (text.Contains("%"))
            {
                switch (attribute.Name.ToString())
                {
                case "x":
                case "cx":
                case "rx":
                case "x1":
                case "x2":
                case "width":
                    orientation = Orientation.Horizontal;
                    break;

                case "y":
                case "cy":
                case "ry":
                case "y1":
                case "y2":
                case "height":
                    orientation = Orientation.Vertical;
                    break;

                default:
                    throw new ArgumentException("unexpected % in unit [" + attribute.Name + "] in element <" + attribute.Parent?.Name + " id='" + attribute.Parent?.Attribute("id")?.Value + "'>");
                }
            }
            return(ElementExtensions.TryGetValueInPx(attribute.Parent, text, orientation, out value));
        }
Beispiel #2
0
        public static void ProcessStyleValue(XElement svgElement, string styleText, BaseElement avElement, List <string> warnings, bool local = false)
        {
            if (!string.IsNullOrWhiteSpace(styleText))
            {
                var styleParts = styleText.Split(';');
                foreach (var part in styleParts)
                {
                    if (string.IsNullOrWhiteSpace(part))
                    {
                        continue;
                    }
                    var tokens = part.Split(':');
                    if (tokens.Length == 2)
                    {
                        var cmd   = tokens[0].Trim();
                        var value = tokens[1].Trim();
                        if (AttributeMap.TryGetValue(cmd, out string avAtrName))
                        {
                            /*
                             * if (value == "inherit" && svgElement.InheritedAttributeValue(cmd) is string inheritedValue)
                             * {
                             *  // do nothing because style attributes are already inherited (and thus, should have already been set before reaching here)
                             * }
                             * else */
                            if (cmd == "fill" || cmd == "stroke")
                            {
                                SetColorStyleValue(cmd, avAtrName, value, svgElement, avElement, warnings);
                            }
                            else if (cmd == "stroke-fill")
                            {
                                SetColorStyleValue("fill", AttributeMap["fill"], value, svgElement, avElement, warnings);
                                SetColorStyleValue("stroke", AttributeMap["stroke"], value, svgElement, avElement, warnings);
                            }

                            /*
                             * {
                             * if (value == "none")
                             * {
                             *  avElement.SetAndroidAttributeValue(avAtrName, value);
                             * }
                             * else if (value.StartsWith("url("))
                             * {
                             *  if (!value.StartsWith("url(#"))
                             *      throw new Exception("Only anchor URLs are supported at this time.");
                             *
                             *  //var iri = value.Substring("url(#".Length).Trim(')').Trim();
                             *  var iri = value.SubstringWithTerminator("url(#".Length, ')').Trim();
                             *  if (svgElement.GetRoot() is XElement root)
                             *  {
                             *      if (root.Descendants(Namespace.Svg + "linearGradient").FirstOrDefault(e => e.Attribute("id").Value == iri) is XElement svgLinearGradient)
                             *      {
                             *          if (PaintConverter.ConvertLinearGradient(svgLinearGradient, warnings) is LinearGradient avGradient)
                             *          {
                             *              if (avElement.Element(AndroidVector.Namespace.Aapt + "attr") is XElement aaptAttr)
                             *                  aaptAttr.Remove();
                             *              var aapt = new AaptAttr(cmd + "Color", avGradient);
                             *              avElement.Add(aapt);
                             *              avElement.SetAndroidAttributeValue(avAtrName, null);
                             *              return;
                             *          }
                             *      }
                             *      if (root.Descendants(Namespace.Svg + "radialGradient").FirstOrDefault(e => e.Attribute("id").Value == iri) is XElement svgRadialGradient)
                             *      {
                             *          if (PaintConverter.ConvertRadialGradient(svgRadialGradient, warnings) is RadialGradient avGradient)
                             *          {
                             *              if (avElement.Element(AndroidVector.Namespace.Aapt + "attr") is XElement aaptAttr)
                             *                  aaptAttr.Remove();
                             *              var aapt = new AaptAttr(cmd+"Color", avGradient);
                             *              avElement.Add(aapt);
                             *              avElement.SetAndroidAttributeValue(avAtrName, null);
                             *              return;
                             *          }
                             *      }
                             *      warnings.AddWarning("Ignoring gradient because no element found to complete link [" + value + "].");
                             *      return;
                             *  }
                             *  throw new Exception("Could not find document root");
                             *
                             * }
                             * else
                             * {
                             *  var (hexColor, opacity) = GetHexColorAndFloatOpacity(value, warnings);
                             *  avElement.SetAndroidAttributeValue(avAtrName, hexColor);
                             *  if (!float.IsNaN(opacity))
                             *      avElement.SetAndroidAttributeValue(cmd + "Alpha", opacity);
                             * }
                             *
                             * } */
                            else if (cmd == "stroke-width")
                            {
                                ElementExtensions.TryGetValueInPx(svgElement, value, Orientation.Unknown, out float strokeWidth);
                                avElement.SetAndroidAttributeValue(avAtrName, strokeWidth);
                            }
                            else if (cmd == "fill-rule")
                            {
                                if (value == "evenodd")
                                {
                                    avElement.SetAndroidAttributeValue(avAtrName, AndroidVector.FillType.EvenOdd.ToString().ToCamelCase());
                                }
                                else if (value == "nonzero")
                                {
                                    avElement.SetAndroidAttributeValue(avAtrName, AndroidVector.FillType.NonZero.ToString().ToCamelCase());
                                }
                                else
                                {
                                    warnings.AddWarning("Ignoring fill-rule because value [" + value + "] in <" + svgElement?.Name + " id='" + svgElement?.Attribute("id")?.Value + "'> is unexpected.");
                                }
                            }
                            else if (cmd == "stroke-linecap" || cmd == "stroke-linejoin")
                            {
                                avElement.SetAndroidAttributeValue(avAtrName, value.ToCamelCase());
                                var attr = avElement.AndroidAttribute(avAtrName);
                                if (avElement is AndroidVector.Path path)
                                {
                                    var cap  = path.StrokeLineCap;
                                    var join = path.StrokeLineJoin;
                                }
                            }
                            else
                            {
                                avElement.SetAndroidAttributeValue(avAtrName, value);
                            }
                        }
                        else if (cmd == "display")
                        {
                            avElement.SvgDisplayStyle = value;
                        }
                        else if (cmd == "visibility")
                        {
                            avElement.SvgDisplayStyle = (value == "hidden" || value == "collapse")
                                ? "none"
                                : "visible";
                        }
                        else if (cmd == "opacity" && local)
                        {
                            if (float.TryParse(value, out float opacity))
                            {
                                avElement.SvgOpacity = opacity;
                            }

                            /*
                             * {
                             * if (avElement.AndroidAttribute("fillAlpha") is XAttribute fillAlphaAttribute &&
                             * float.TryParse(fillAlphaAttribute.Value, out float fillAlpha))
                             *  avElement.SetAndroidAttributeValue("fillAlpha", fillAlpha * opacity);
                             * else
                             *  avElement.SetAndroidAttributeValue("fillAlpha", opacity);
                             * if (avElement.AndroidAttribute("strokeAlpha") is XAttribute strokeAlphaAttribute &&
                             * float.TryParse(strokeAlphaAttribute.Value, out float strokeAlpha))
                             *  avElement.SetAndroidAttributeValue("strokeAlpha", strokeAlpha * opacity);
                             * else
                             *  avElement.SetAndroidAttributeValue("strokeAlpha", opacity);
                             * }
                             */
                        }
                        else if (!IgnoreAttributeMap.Contains(cmd))
                        {
                            warnings.AddWarning("Ignoring SVG style [" + cmd + "] because could not map to an AndroidVector attribute.");
                        }
                    }
                    else
                    {
                        warnings.AddWarning("Ignoring SVG style [" + part + "] in <" + svgElement?.Name + " id='" + svgElement?.Attribute("id")?.Value + "'> because could not parce into a style and a value.");
                    }
                }
            }
        }
        public static string ConvertCssShapeToPathData(XElement element, string cssShapeText, List <string> warnings)
        {
            if (string.IsNullOrWhiteSpace(cssShapeText))
            {
                return(null);
            }
            cssShapeText = cssShapeText.Trim().Trim(')');
            var parts = cssShapeText.Split('(');

            if (parts.Length < 1)
            {
                warnings.AddWarning("Ignoring CssShape '" + cssShapeText + "' <" + element?.Name + " id='" + element.Attribute("id")?.Value + "'> because is not recognized shape");
                return(null);
            }
            var cmd = parts[0];

            switch (cmd)
            {
            case "inset":
            {
                var values = ElementExtensions.ToFloatList(element, (parts.Length < 2 || string.IsNullOrWhiteSpace(parts[1])) ? "0% 100% 100% 0%" : parts[1]);
                if (values.Count == 4)
                {
                    return(RectToPathData(values[3], values[0], values[1] - values[3], values[2] - values[0]));
                }
                else if (values.Count == 5)
                {
                    return(RectToPathData(values[3], values[0], values[1] - values[3], values[2] - values[0], values[4], values[4]));
                }
                else
                {
                    warnings.AddWarning("Ignoring CssShape '" + cssShapeText + "' in <" + element?.Name + " id='" + element.Attribute("id")?.Value + "'> because it doesn't have the right number of arguments.");
                    return(null);
                }
            }

            case "circle":
            {
                var radius = ElementExtensions.ToFloatList(element, "100%")[0];
                var center = ElementExtensions.ToFloatList(element, "50% 50%");
                if (parts.Length > 1)
                {
                    parts = parts[1].Split("at");
                    if (ElementExtensions.ToFloatList(element, parts[1]) is List <float> radiusValues && radiusValues.Count > 0)
                    {
                        radius = radiusValues[0];
                    }
                    else
                    {
                        warnings.Add("Could not parse CSS circle radius in '" + cssShapeText + "' in in <" + element?.Name + " id='" + element.Attribute("id")?.Value + "'>.");
                    }
                    if (parts.Length > 1)
                    {
                        if (ElementExtensions.ToFloatList(element, parts[2]) is List <float> centerValues && centerValues.Count > 0)
                        {
                            center = new List <float> {
                                centerValues[0], centerValues.Count > 1 ? centerValues[1] : centerValues[0]
                            }
                        }
                        ;
                        else
                        {
                            warnings.Add("Could not parse CSS circle center in '" + cssShapeText + "' in in <" + element?.Name + " id='" + element.Attribute("id")?.Value + "'>.");
                        }
                    }
                }
                return(CirclePathData(center[0], center[1], radius));
            }

            case "ellipse":
            {
                var radius = ElementExtensions.ToFloatList(element, "100% 100%");
                var center = ElementExtensions.ToFloatList(element, "50% 50%");
                if (parts.Length > 1)
                {
                    parts = parts[1].Split("at");
                    if (ElementExtensions.ToFloatList(element, parts[1]) is List <float> radiusValues && radiusValues.Count > 0)
                    {
                        radius = new List <float> {
                            radiusValues[0], radiusValues.Count > 1 ? radiusValues[1] : radiusValues[0]
                        }
                    }
                    ;
                    else
                    {
                        warnings.Add("Could not parse CSS circle radius in '" + cssShapeText + "' in in <" + element?.Name + " id='" + element.Attribute("id")?.Value + "'>.");
                    }
                    if (parts.Length > 1)
                    {
                        if (ElementExtensions.ToFloatList(element, parts[2]) is List <float> centerValues && centerValues.Count > 0)
                        {
                            center = new List <float> {
                                centerValues[0], centerValues.Count > 1 ? centerValues[1] : centerValues[0]
                            }
                        }
                        ;
                        else
                        {
                            warnings.Add("Could not parse CSS circle center in '" + cssShapeText + "' in in <" + element?.Name + " id='" + element.Attribute("id")?.Value + "'>.");
                        }
                    }
                }
                return(EllipsePathData(center[0], center[1], radius[0], radius[1]));
            }

            case "polygon":
            {
                if (parts.Length > 1)
                {
                    var points = ElementExtensions.ToFloatList(element, parts[1]);
                    if (points.Count > 2)
                    {
                        return(PolygonToPathData(points));
                    }
                }
                warnings.AddWarning("Ignoring CssShape '" + cssShapeText + "' in <" + element?.Name + " id='" + element.Attribute("id")?.Value + "'> because it doesn't have the right number of arguments.");
                return(null);
            }
            }
            return(null);
        }
        /// <summary>
        /// Creates AndroidVector.Clip child  from Clip attribute of SVG element
        /// </summary>
        /// <param name="svgSvgElement"></param>
        /// <param name="avVector"></param>
        /// <param name="warnings"></param>
        /// <returns></returns>
        public static void ConvertClipAttribute(XElement svgSvgElement, AndroidVector.BaseElement av, List <string> warnings)
        {
            const string svgTypeName = "svg";

            if (svgSvgElement.Name != Namespace.Svg + svgTypeName)
            {
                throw new ArgumentException("Only applicable to SVG <svg> element");
            }

            if (av.Name != "vector" && av.Name != "group")
            {
                throw new ArgumentException("Only applicable to Android <vector> and <group> elements.");
            }

            if (av.Attribute("clip") is XAttribute clipAttribute)
            {
                if (clipAttribute.Value == "auto")
                {
                    if (svgSvgElement.Attribute("viewBox") is XAttribute svgViewBoxAttribute)
                    {
                        var args = svgViewBoxAttribute.Value.Split(new char[] { ' ', ',' });
                        if (args.Length < 1 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[0], Orientation.Horizontal, out float x))
                        {
                            warnings.AddWarning("Ignoring <svg clip='auto'> because 'viewBox' does not contain x coordinate.");
                            return;
                        }
                        if (args.Length < 2 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[1], Orientation.Vertical, out float y))
                        {
                            warnings.AddWarning("Ignoring <svg clip='auto'> because 'viewBox' does not contain y coordinate.");
                            return;
                        }
                        if (args.Length < 3 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[2], Orientation.Horizontal, out float w))
                        {
                            warnings.AddWarning("Ignoring <svg clip='auto'> because 'viewBox' does not contain width.");
                            return;
                        }
                        if (args.Length < 4 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[3], Orientation.Vertical, out float h))
                        {
                            warnings.AddWarning("Ignoring <svg clip='auto'> because 'viewBox' does not contain height.");
                            return;
                        }
                        string pathData = " M " + x + "," + y;
                        pathData += " v " + h;
                        pathData += " h " + w;
                        pathData += " v " + (-h);
                        pathData += " z ";
                        av.Add(new XElement("clip-path", new AndroidVector.AndroidAttribute("pathData", pathData)));
                    }
                    else
                    {
                        warnings.AddWarning("Ignoring <svg clip='auto'> because 'viewBox' attribute is not present.");
                        return;
                    }
                }
                else if (clipAttribute.Value.StartsWith("rect("))
                {
                    // top, right, bottom, left
                    var args = clipAttribute.Value.Split(new char[] { ' ', ',', '(', ')' });
                    if (args.Length < 2 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[1], Orientation.Vertical, out float top))
                    {
                        warnings.AddWarning("Ignoring <svg clip='rect(...)'> because 'rect' does not contain top coordinate.");
                        return;
                    }
                    if (args.Length < 3 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[2], Orientation.Horizontal, out float right))
                    {
                        warnings.AddWarning("Ignoring <svg clip='rect(...)'> because 'rect' does not contain right coordinate.");
                        return;
                    }
                    if (args.Length < 4 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[3], Orientation.Vertical, out float bottom))
                    {
                        warnings.AddWarning("Ignoring <svg clip='rect(...)'> because 'rect' does not contain bottom coordinate.");
                        return;
                    }
                    if (args.Length < 5 || !ElementExtensions.TryGetValueInPx(svgSvgElement, args[4], Orientation.Horizontal, out float left))
                    {
                        warnings.AddWarning("Ignoring <svg clip='rect(...)'> because 'rect' does not contain left coordinate.");
                        return;
                    }
                    string pathData = " M " + top + "," + left;
                    pathData += " V " + bottom;
                    pathData += " H " + right;
                    pathData += " V " + top;
                    pathData += " Z ";
                    av.Add(new XElement("clip-path", new AndroidVector.AndroidAttribute("pathData", pathData)));
                }
                else
                {
                    warnings.AddWarning("Ignoring <svg clip='" + clipAttribute.Value + "'> because '" + clipAttribute.Value + "' is an unexpected attribute value.");
                    return;
                }
            }
        }