зеркало из https://github.com/SixLabors/Svg.git
improved path/shape support
This commit is contained in:
Родитель
f41d627f7e
Коммит
aafea14480
|
@ -1,6 +1,10 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<svg width="400" height="250" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<g fill="red" >
|
||||
<rect x="10" y="100" width="90" height="90" stroke="black" stroke-width="15"/>
|
||||
<rect x="160" y="100" rx="20" ry="20" width="90" height="90" fill-opacity="0.5" stroke="black" stroke-width="15"/>
|
||||
</g>
|
||||
|
||||
<rect x="10" y="10" width="90" height="90" stroke="black" fill="none" stroke-width="15"/>
|
||||
<rect x="160" y="10" rx="20" ry="20" width="90" height="90" fill="none" stroke="black" stroke-width="15"/>
|
||||
|
||||
|
@ -14,5 +18,5 @@
|
|||
<polygon points="50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180"
|
||||
stroke="green" fill="transparent" stroke-width="5"/>
|
||||
|
||||
<path d="M20,230 Q40,205 50,230 T90,230" fill="none" stroke="blue" stroke-width="5"/>
|
||||
<path d="M20,230 Q40,205 50,230 T90,230" fill="red" stroke="blue" stroke-width="5"/>
|
||||
</svg>
|
|
@ -0,0 +1,86 @@
|
|||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.Shapes;
|
||||
using AngleSharp.Dom;
|
||||
using System.Linq;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
public static IPath GenerateStroke<TPixel>(this IPath path, Image<TPixel> img, ISvgShape shape) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var strokeWidth = shape.StrokeWidth.AsPixelXAxis(img);
|
||||
if (strokeWidth == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Outliner.GenerateOutline(path, strokeWidth, shape.StrokeLineJoin.Style, shape.StrokeLineCap.Style);
|
||||
}
|
||||
|
||||
public static string GetAttributeValueSelfOrGroup(this ISvgElement element, string attribute)
|
||||
{
|
||||
var val = element.Attributes[attribute]?.Value;
|
||||
|
||||
if (val == null)
|
||||
{
|
||||
val = element.Ancestors<ISvgElement>().Where(x => x.TagName == "g").FirstOrDefault()?.GetAttributeValueSelfOrGroup(attribute);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public static SvgPaint GetPaint(this ISvgElement element, string attribute, string defaultPaintValue, string defaultOpacityValue)
|
||||
{
|
||||
var paint = element.GetAttributeValueSelfOrGroup(attribute) ?? defaultPaintValue;
|
||||
var opacity = element.GetAttributeValueSelfOrGroup(attribute + "-opacity") ?? defaultOpacityValue;
|
||||
|
||||
return SvgPaint.Parse(paint, opacity);
|
||||
}
|
||||
|
||||
public static SvgUnitValue GetUnitValue(this ISvgElement element, string attribute, string defaultValue = null)
|
||||
{
|
||||
var val = element.TryGetUnitValue(attribute, defaultValue);
|
||||
|
||||
return val ?? SvgUnitValue.Unset;
|
||||
}
|
||||
public static SvgUnitValue? TryGetUnitValue(this ISvgElement element, string attribute, string defaultValue = null)
|
||||
{
|
||||
var val = element.GetAttributeValueSelfOrGroup(attribute) ?? defaultValue;
|
||||
|
||||
return SvgUnitValue.Parse(val);
|
||||
}
|
||||
|
||||
public static SvgLineCap GetLineCap(this ISvgElement element, string attribute, string defaultValue = null)
|
||||
{
|
||||
var val = element.TryGetLineCap(attribute, defaultValue);
|
||||
|
||||
return val ?? SvgLineCap.Unset;
|
||||
}
|
||||
|
||||
public static SvgLineCap? TryGetLineCap(this ISvgElement element, string attribute, string defaultValue = null)
|
||||
{
|
||||
var val = element.GetAttributeValueSelfOrGroup(attribute) ?? defaultValue;
|
||||
|
||||
return SvgLineCap.Parse(val);
|
||||
}
|
||||
|
||||
|
||||
public static SvgLineJoin GetLineJoin(this ISvgElement element, string attribute, string defaultValue = null)
|
||||
{
|
||||
var val = element.TryGetLineJoin(attribute, defaultValue);
|
||||
|
||||
return val ?? SvgLineJoin.Unset;
|
||||
}
|
||||
|
||||
public static SvgLineJoin? TryGetLineJoin(this ISvgElement element, string attribute, string defaultValue = null)
|
||||
{
|
||||
var val = element.GetAttributeValueSelfOrGroup(attribute) ?? defaultValue;
|
||||
|
||||
return SvgLineJoin.Parse(val);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal interface ISvgShape
|
||||
{
|
||||
SvgPaint Fill { get; }
|
||||
SvgPaint Stroke { get; }
|
||||
SvgUnitValue StrokeWidth { get; }
|
||||
SvgLineCap StrokeLineCap { get; }
|
||||
SvgLineJoin StrokeLineJoin { get; }
|
||||
}
|
||||
}
|
|
@ -30,10 +30,10 @@ namespace SixLabors.Svg.Dom
|
|||
{
|
||||
var document = new SvgDocument()
|
||||
{
|
||||
X = SvgUnitValue.Parse(element.Attributes["x"]?.Value),
|
||||
Y = SvgUnitValue.Parse(element.Attributes["y"]?.Value),
|
||||
Width = SvgUnitValue.Parse(element.Attributes["width"]?.Value),
|
||||
Height = SvgUnitValue.Parse(element.Attributes["height"]?.Value),
|
||||
X = element.GetUnitValue("x", "0"),
|
||||
Y = element.GetUnitValue("y", "0"),
|
||||
Width = element.GetUnitValue("width"),
|
||||
Height = element.GetUnitValue("height")
|
||||
};
|
||||
|
||||
var children = element.Children.OfType<ISvgElement>();
|
||||
|
|
|
@ -21,6 +21,11 @@ namespace SixLabors.Svg.Dom
|
|||
case "circle":
|
||||
case "ellipse":
|
||||
return await SvgEllipse.LoadAsync(element);
|
||||
case "line":
|
||||
case "polyline":
|
||||
case "polygon":
|
||||
case "path":
|
||||
return await SvgPath.LoadAsync(element);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,10 @@ using System.Threading.Tasks;
|
|||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.Shapes;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgEllipse : SvgElement
|
||||
internal sealed class SvgEllipse : SvgElement, ISvgShape
|
||||
{
|
||||
|
||||
public SvgUnitValue X { get; private set; }
|
||||
|
@ -17,25 +16,30 @@ namespace SixLabors.Svg.Dom
|
|||
public SvgPaint Fill { get; private set; }
|
||||
public SvgPaint Stroke { get; private set; }
|
||||
public SvgUnitValue StrokeWidth { get; private set; }
|
||||
public SvgLineCap StrokeLineCap { get; private set; }
|
||||
public SvgLineJoin StrokeLineJoin { get; private set; }
|
||||
|
||||
public static Task<SvgElement> LoadAsync(ISvgElement element)
|
||||
{
|
||||
var ellipse = new SvgEllipse()
|
||||
{
|
||||
Fill = SvgPaint.Parse(element.Attributes["fill"]?.Value ?? "Black", element.Attributes["fill-opacity"]?.Value ?? "1"),
|
||||
Stroke = SvgPaint.Parse(element.Attributes["stroke"]?.Value ?? "None", element.Attributes["stroke-opacity"]?.Value ?? "1"),
|
||||
StrokeWidth = SvgUnitValue.Parse(element.Attributes["stroke-width"]?.Value ?? "1"),
|
||||
X = SvgUnitValue.Parse(element.Attributes["cx"]?.Value),
|
||||
Y = SvgUnitValue.Parse(element.Attributes["cy"]?.Value)
|
||||
Fill = element.GetPaint("fill", "Black", "1"),
|
||||
Stroke = element.GetPaint("stroke", "None", "1"),
|
||||
StrokeWidth = element.GetUnitValue("stroke-width", "1"),
|
||||
StrokeLineCap = element.GetLineCap("stroke-linecap", "butt"),
|
||||
StrokeLineJoin = element.GetLineJoin("stroke-linejoin", "miter"),
|
||||
X = element.GetUnitValue("cx"),
|
||||
Y = element.GetUnitValue("cy"),
|
||||
};
|
||||
|
||||
if (element.TagName == "circle")
|
||||
{
|
||||
ellipse.RadiusY = ellipse.RadiusX = SvgUnitValue.Parse(element.Attributes["r"]?.Value ?? "0");
|
||||
ellipse.RadiusY = ellipse.RadiusX = element.GetUnitValue("r", "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
ellipse.RadiusY = SvgUnitValue.Parse(element.Attributes["ry"]?.Value ?? "0");
|
||||
ellipse.RadiusX = SvgUnitValue.Parse(element.Attributes["rx"]?.Value ?? "0");
|
||||
ellipse.RadiusX = element.GetUnitValue("rx", "0");
|
||||
ellipse.RadiusY = element.GetUnitValue("ry", "0");
|
||||
}
|
||||
|
||||
return Task.FromResult<SvgElement>(ellipse);
|
||||
|
@ -47,17 +51,19 @@ namespace SixLabors.Svg.Dom
|
|||
|
||||
var fillBrush = Fill.AsBrush<TPixel>();
|
||||
var strokeBrush = Stroke.AsBrush<TPixel>();
|
||||
var strokeWidth = this.StrokeWidth.AsPixelXAxis(image);
|
||||
image.Mutate(x =>
|
||||
{
|
||||
if (fillBrush != null)
|
||||
{
|
||||
x = x.Fill(fillBrush, rect);
|
||||
}
|
||||
if (strokeBrush != null && strokeWidth > 0)
|
||||
if (strokeBrush != null)
|
||||
{
|
||||
var outline = Outliner.GenerateOutline(rect, strokeWidth);
|
||||
x = x.Fill(strokeBrush, outline);
|
||||
var outline = rect.GenerateStroke(image, this);
|
||||
if (outline != null)
|
||||
{
|
||||
x = x.Fill(strokeBrush, outline);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal struct SvgPaint
|
||||
{
|
||||
public static readonly SvgPaint Unset = default(SvgPaint);
|
||||
|
||||
internal SvgPaint(string value, float opacity)
|
||||
{
|
||||
Value = value;
|
||||
Opacity = opacity;
|
||||
}
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
public float Opacity { get; }
|
||||
|
||||
public static SvgPaint Parse(string val, string opacity)
|
||||
{
|
||||
val = val?.Trim() ?? "";
|
||||
opacity = opacity?.Trim() ?? "";
|
||||
|
||||
float opacityVal = 1;
|
||||
if (float.TryParse(opacity, out float op))
|
||||
{
|
||||
opacityVal = op;
|
||||
if (opacityVal < 0)
|
||||
{
|
||||
opacityVal = 0;
|
||||
}
|
||||
if (opacityVal > 1)
|
||||
{
|
||||
opacityVal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new SvgPaint(val, opacityVal);
|
||||
|
||||
}
|
||||
|
||||
private TPixel? GetNamedColor<TPixel>(string name) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var dict = new Dictionary<string, TPixel>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["AliceBlue"] = NamedColors<TPixel>.AliceBlue,
|
||||
["MistyRose"] = NamedColors<TPixel>.MistyRose,
|
||||
["Moccasin"] = NamedColors<TPixel>.Moccasin,
|
||||
["NavajoWhite"] = NamedColors<TPixel>.NavajoWhite,
|
||||
["Navy"] = NamedColors<TPixel>.Navy,
|
||||
["OldLace"] = NamedColors<TPixel>.OldLace,
|
||||
["Olive"] = NamedColors<TPixel>.Olive,
|
||||
["MintCream"] = NamedColors<TPixel>.MintCream,
|
||||
["OliveDrab"] = NamedColors<TPixel>.OliveDrab,
|
||||
["OrangeRed"] = NamedColors<TPixel>.OrangeRed,
|
||||
["Orchid"] = NamedColors<TPixel>.Orchid,
|
||||
["PaleGoldenrod"] = NamedColors<TPixel>.PaleGoldenrod,
|
||||
["PaleGreen"] = NamedColors<TPixel>.PaleGreen,
|
||||
["PaleTurquoise"] = NamedColors<TPixel>.PaleTurquoise,
|
||||
["PaleVioletRed"] = NamedColors<TPixel>.PaleVioletRed,
|
||||
["Orange"] = NamedColors<TPixel>.Orange,
|
||||
["PapayaWhip"] = NamedColors<TPixel>.PapayaWhip,
|
||||
["MidnightBlue"] = NamedColors<TPixel>.MidnightBlue,
|
||||
["MediumTurquoise"] = NamedColors<TPixel>.MediumTurquoise,
|
||||
["LightSteelBlue"] = NamedColors<TPixel>.LightSteelBlue,
|
||||
["LightYellow"] = NamedColors<TPixel>.LightYellow,
|
||||
["Lime"] = NamedColors<TPixel>.Lime,
|
||||
["LimeGreen"] = NamedColors<TPixel>.LimeGreen,
|
||||
["Linen"] = NamedColors<TPixel>.Linen,
|
||||
["Magenta"] = NamedColors<TPixel>.Magenta,
|
||||
["MediumVioletRed"] = NamedColors<TPixel>.MediumVioletRed,
|
||||
["Maroon"] = NamedColors<TPixel>.Maroon,
|
||||
["MediumBlue"] = NamedColors<TPixel>.MediumBlue,
|
||||
["MediumOrchid"] = NamedColors<TPixel>.MediumOrchid,
|
||||
["MediumPurple"] = NamedColors<TPixel>.MediumPurple,
|
||||
["MediumSeaGreen"] = NamedColors<TPixel>.MediumSeaGreen,
|
||||
["MediumSlateBlue"] = NamedColors<TPixel>.MediumSlateBlue,
|
||||
["MediumSpringGreen"] = NamedColors<TPixel>.MediumSpringGreen,
|
||||
["MediumAquamarine"] = NamedColors<TPixel>.MediumAquamarine,
|
||||
["LightSlateGray"] = NamedColors<TPixel>.LightSlateGray,
|
||||
["PeachPuff"] = NamedColors<TPixel>.PeachPuff,
|
||||
["Pink"] = NamedColors<TPixel>.Pink,
|
||||
["SpringGreen"] = NamedColors<TPixel>.SpringGreen,
|
||||
["SteelBlue"] = NamedColors<TPixel>.SteelBlue,
|
||||
["Tan"] = NamedColors<TPixel>.Tan,
|
||||
["Teal"] = NamedColors<TPixel>.Teal,
|
||||
["Thistle"] = NamedColors<TPixel>.Thistle,
|
||||
["Tomato"] = NamedColors<TPixel>.Tomato,
|
||||
["Snow"] = NamedColors<TPixel>.Snow,
|
||||
["Transparent"] = NamedColors<TPixel>.Transparent,
|
||||
["Violet"] = NamedColors<TPixel>.Violet,
|
||||
["Wheat"] = NamedColors<TPixel>.Wheat,
|
||||
["White"] = NamedColors<TPixel>.White,
|
||||
["WhiteSmoke"] = NamedColors<TPixel>.WhiteSmoke,
|
||||
["Yellow"] = NamedColors<TPixel>.Yellow,
|
||||
["YellowGreen"] = NamedColors<TPixel>.YellowGreen,
|
||||
["Turquoise"] = NamedColors<TPixel>.Turquoise,
|
||||
["Peru"] = NamedColors<TPixel>.Peru,
|
||||
["SlateGray"] = NamedColors<TPixel>.SlateGray,
|
||||
["SkyBlue"] = NamedColors<TPixel>.SkyBlue,
|
||||
["Plum"] = NamedColors<TPixel>.Plum,
|
||||
["PowderBlue"] = NamedColors<TPixel>.PowderBlue,
|
||||
["Purple"] = NamedColors<TPixel>.Purple,
|
||||
["RebeccaPurple"] = NamedColors<TPixel>.RebeccaPurple,
|
||||
["Red"] = NamedColors<TPixel>.Red,
|
||||
["RosyBrown"] = NamedColors<TPixel>.RosyBrown,
|
||||
["SlateBlue"] = NamedColors<TPixel>.SlateBlue,
|
||||
["RoyalBlue"] = NamedColors<TPixel>.RoyalBlue,
|
||||
["Salmon"] = NamedColors<TPixel>.Salmon,
|
||||
["SandyBrown"] = NamedColors<TPixel>.SandyBrown,
|
||||
["SeaGreen"] = NamedColors<TPixel>.SeaGreen,
|
||||
["SeaShell"] = NamedColors<TPixel>.SeaShell,
|
||||
["Sienna"] = NamedColors<TPixel>.Sienna,
|
||||
["Silver"] = NamedColors<TPixel>.Silver,
|
||||
["SaddleBrown"] = NamedColors<TPixel>.SaddleBrown,
|
||||
["LightSkyBlue"] = NamedColors<TPixel>.LightSkyBlue,
|
||||
["LightSeaGreen"] = NamedColors<TPixel>.LightSeaGreen,
|
||||
["LightSalmon"] = NamedColors<TPixel>.LightSalmon,
|
||||
["Crimson"] = NamedColors<TPixel>.Crimson,
|
||||
["Cyan"] = NamedColors<TPixel>.Cyan,
|
||||
["DarkBlue"] = NamedColors<TPixel>.DarkBlue,
|
||||
["DarkCyan"] = NamedColors<TPixel>.DarkCyan,
|
||||
["DarkGoldenrod"] = NamedColors<TPixel>.DarkGoldenrod,
|
||||
["DarkGray"] = NamedColors<TPixel>.DarkGray,
|
||||
["Cornsilk"] = NamedColors<TPixel>.Cornsilk,
|
||||
["DarkGreen"] = NamedColors<TPixel>.DarkGreen,
|
||||
["DarkMagenta"] = NamedColors<TPixel>.DarkMagenta,
|
||||
["DarkOliveGreen"] = NamedColors<TPixel>.DarkOliveGreen,
|
||||
["DarkOrange"] = NamedColors<TPixel>.DarkOrange,
|
||||
["DarkOrchid"] = NamedColors<TPixel>.DarkOrchid,
|
||||
["DarkRed"] = NamedColors<TPixel>.DarkRed,
|
||||
["DarkSalmon"] = NamedColors<TPixel>.DarkSalmon,
|
||||
["DarkKhaki"] = NamedColors<TPixel>.DarkKhaki,
|
||||
["DarkSeaGreen"] = NamedColors<TPixel>.DarkSeaGreen,
|
||||
["CornflowerBlue"] = NamedColors<TPixel>.CornflowerBlue,
|
||||
["Chocolate"] = NamedColors<TPixel>.Chocolate,
|
||||
["AntiqueWhite"] = NamedColors<TPixel>.AntiqueWhite,
|
||||
["Aqua"] = NamedColors<TPixel>.Aqua,
|
||||
["Aquamarine"] = NamedColors<TPixel>.Aquamarine,
|
||||
["Azure"] = NamedColors<TPixel>.Azure,
|
||||
["Beige"] = NamedColors<TPixel>.Beige,
|
||||
["Bisque"] = NamedColors<TPixel>.Bisque,
|
||||
["Coral"] = NamedColors<TPixel>.Coral,
|
||||
["Black"] = NamedColors<TPixel>.Black,
|
||||
["Blue"] = NamedColors<TPixel>.Blue,
|
||||
["BlueViolet"] = NamedColors<TPixel>.BlueViolet,
|
||||
["Brown"] = NamedColors<TPixel>.Brown,
|
||||
["BurlyWood"] = NamedColors<TPixel>.BurlyWood,
|
||||
["CadetBlue"] = NamedColors<TPixel>.CadetBlue,
|
||||
["Chartreuse"] = NamedColors<TPixel>.Chartreuse,
|
||||
["BlanchedAlmond"] = NamedColors<TPixel>.BlanchedAlmond,
|
||||
["DarkSlateBlue"] = NamedColors<TPixel>.DarkSlateBlue,
|
||||
["DarkSlateGray"] = NamedColors<TPixel>.DarkSlateGray,
|
||||
["DarkTurquoise"] = NamedColors<TPixel>.DarkTurquoise,
|
||||
["Indigo"] = NamedColors<TPixel>.Indigo,
|
||||
["Ivory"] = NamedColors<TPixel>.Ivory,
|
||||
["Khaki"] = NamedColors<TPixel>.Khaki,
|
||||
["Lavender"] = NamedColors<TPixel>.Lavender,
|
||||
["LavenderBlush"] = NamedColors<TPixel>.LavenderBlush,
|
||||
["LawnGreen"] = NamedColors<TPixel>.LawnGreen,
|
||||
["IndianRed"] = NamedColors<TPixel>.IndianRed,
|
||||
["LemonChiffon"] = NamedColors<TPixel>.LemonChiffon,
|
||||
["LightCoral"] = NamedColors<TPixel>.LightCoral,
|
||||
["LightCyan"] = NamedColors<TPixel>.LightCyan,
|
||||
["LightGoldenrodYellow"] = NamedColors<TPixel>.LightGoldenrodYellow,
|
||||
["LightGray"] = NamedColors<TPixel>.LightGray,
|
||||
["LightGreen"] = NamedColors<TPixel>.LightGreen,
|
||||
["LightPink"] = NamedColors<TPixel>.LightPink,
|
||||
["LightBlue"] = NamedColors<TPixel>.LightBlue,
|
||||
["HotPink"] = NamedColors<TPixel>.HotPink,
|
||||
["Honeydew"] = NamedColors<TPixel>.Honeydew,
|
||||
["GreenYellow"] = NamedColors<TPixel>.GreenYellow,
|
||||
["DarkViolet"] = NamedColors<TPixel>.DarkViolet,
|
||||
["DeepPink"] = NamedColors<TPixel>.DeepPink,
|
||||
["DeepSkyBlue"] = NamedColors<TPixel>.DeepSkyBlue,
|
||||
["DimGray"] = NamedColors<TPixel>.DimGray,
|
||||
["DodgerBlue"] = NamedColors<TPixel>.DodgerBlue,
|
||||
["Firebrick"] = NamedColors<TPixel>.Firebrick,
|
||||
["FloralWhite"] = NamedColors<TPixel>.FloralWhite,
|
||||
["ForestGreen"] = NamedColors<TPixel>.ForestGreen,
|
||||
["Fuchsia"] = NamedColors<TPixel>.Fuchsia,
|
||||
["Gainsboro"] = NamedColors<TPixel>.Gainsboro,
|
||||
["GhostWhite"] = NamedColors<TPixel>.GhostWhite,
|
||||
["Gold"] = NamedColors<TPixel>.Gold,
|
||||
["Goldenrod"] = NamedColors<TPixel>.Goldenrod,
|
||||
["Gray"] = NamedColors<TPixel>.Gray,
|
||||
["Green"] = NamedColors<TPixel>.Green,
|
||||
};
|
||||
|
||||
if (dict.TryGetValue(name, out var pixel))
|
||||
{
|
||||
return pixel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IBrush<TPixel> AsBrush<TPixel>() where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
// lets asume the brush is a color for now
|
||||
// TODO update ColourBuilder to expose named colors
|
||||
var value = this.Value;
|
||||
if (string.IsNullOrWhiteSpace(value) || value.Equals("None", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// if a null prush is returned we should skip
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var color = GetNamedColor<TPixel>(Value) ?? ColorBuilder<TPixel>.FromHex(value);
|
||||
|
||||
|
||||
if(Opacity == 0)
|
||||
{
|
||||
// no-op push here to efficienty sake
|
||||
color = NamedColors<TPixel>.Transparent;
|
||||
} else if (Opacity != 1)
|
||||
{
|
||||
var vector = color.ToScaledVector4();
|
||||
vector.W *= Opacity;
|
||||
color.FromScaledVector4(vector);
|
||||
}
|
||||
|
||||
return new SolidBrush<TPixel>(color);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.Shapes;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgPath : SvgElement, ISvgShape
|
||||
{
|
||||
public IPath Path { get; private set; }
|
||||
|
||||
public SvgPaint Fill { get; private set; }
|
||||
public SvgPaint Stroke { get; private set; }
|
||||
public SvgUnitValue StrokeWidth { get; private set; }
|
||||
public SvgLineCap StrokeLineCap { get; private set; }
|
||||
public SvgLineJoin StrokeLineJoin { get; private set; }
|
||||
|
||||
public IEnumerable<SvgPathOperation> PathOperations { get; private set; }
|
||||
public static Task<SvgElement> LoadAsync(ISvgElement element)
|
||||
{
|
||||
var path = new SvgPath()
|
||||
{
|
||||
Fill = element.GetPaint("fill", "Black", "1"),
|
||||
Stroke = element.GetPaint("stroke", "None", "1"),
|
||||
StrokeWidth = element.GetUnitValue("stroke-width", "1"),
|
||||
StrokeLineCap = element.GetLineCap("stroke-linecap", "butt"),
|
||||
StrokeLineJoin = element.GetLineJoin("stroke-linejoin", "miter"),
|
||||
};
|
||||
|
||||
if (element.TagName == "line")
|
||||
{
|
||||
path.PathOperations = new[] {
|
||||
SvgPathOperation.MoveTo(element.GetUnitValue("x1", "0"), element.GetUnitValue("y1", "0"), false),
|
||||
SvgPathOperation.LineTo(element.GetUnitValue("x2", "0"), element.GetUnitValue("y2", "0"), false),
|
||||
};
|
||||
}
|
||||
else if (element.TagName == "polyline" || element.TagName == "polygon")
|
||||
{
|
||||
var pathData = element.GetAttributeValueSelfOrGroup("points");
|
||||
path.PathOperations = ParsePoints(pathData, element.TagName == "polygon");
|
||||
}
|
||||
else if (element.TagName == "path")
|
||||
{
|
||||
var pathData = element.GetAttributeValueSelfOrGroup("d");
|
||||
path.PathOperations = SvgPathOperation.Parse(pathData);
|
||||
}
|
||||
|
||||
if (path.PathOperations == null)
|
||||
{
|
||||
return Task.FromResult<SvgElement>(null);
|
||||
}
|
||||
|
||||
return Task.FromResult<SvgElement>(path);
|
||||
}
|
||||
|
||||
private static IEnumerable<SvgPathOperation> ParsePoints(string pathData, bool closePath)
|
||||
{
|
||||
if (pathData != null && !pathData.Equals("none", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var parts = pathData.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x =>
|
||||
{
|
||||
if (int.TryParse(x, out var i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{ return (int?)null; }
|
||||
}).Where(x => x != null).Select(x => x.Value).ToArray();
|
||||
if (parts.Length > 4)
|
||||
{
|
||||
var len = parts.Length / 2;
|
||||
if (closePath)
|
||||
{
|
||||
len++;
|
||||
}
|
||||
|
||||
var ops = new SvgPathOperation[len];
|
||||
ops[0] = SvgPathOperation.MoveTo(new SvgUnitValue(parts[0], SvgUnitValue.Units.undefined), new SvgUnitValue(parts[1], SvgUnitValue.Units.undefined), false);
|
||||
|
||||
for (var i = 2; i < parts.Length; i += 2)
|
||||
{
|
||||
ops[i / 2] = SvgPathOperation.LineTo(new SvgUnitValue(parts[i], SvgUnitValue.Units.undefined), new SvgUnitValue(parts[i + 1], SvgUnitValue.Units.undefined), false);
|
||||
}
|
||||
|
||||
if (closePath)
|
||||
{
|
||||
ops[len - 1] = SvgPathOperation.ClosePath();
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override void RenderTo<TPixel>(Image<TPixel> image)
|
||||
{
|
||||
var rect = this.PathOperations.GeneratePath(image);
|
||||
|
||||
var fillBrush = Fill.AsBrush<TPixel>();
|
||||
var strokeBrush = Stroke.AsBrush<TPixel>();
|
||||
image.Mutate(x =>
|
||||
{
|
||||
if (fillBrush != null)
|
||||
{
|
||||
x = x.Fill(fillBrush, rect);
|
||||
}
|
||||
if (strokeBrush != null)
|
||||
{
|
||||
var outline = rect.GenerateStroke(image, this);
|
||||
if (outline != null)
|
||||
{
|
||||
x = x.Fill(strokeBrush, outline);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,401 @@
|
|||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.Primitives;
|
||||
using SixLabors.Shapes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal struct SvgPathOperation
|
||||
{
|
||||
public Operations Op { get; private set; }
|
||||
public bool IsRelative { get; private set; }
|
||||
|
||||
public SvgUnitValue? X { get; private set; }
|
||||
public SvgUnitValue? Y { get; private set; }
|
||||
|
||||
public SvgUnitValue? X1 { get; private set; }
|
||||
public SvgUnitValue? Y1 { get; private set; }
|
||||
|
||||
public SvgUnitValue? X2 { get; private set; }
|
||||
public SvgUnitValue? Y2 { get; private set; }
|
||||
|
||||
public static IEnumerable<SvgPathOperation> Parse(string path)
|
||||
{
|
||||
List<SvgPathOperation> operations = new List<SvgPathOperation>();
|
||||
char cmd = ' ';
|
||||
for (var i = 0; i < path.Length; i++)
|
||||
{
|
||||
switch (path[i])
|
||||
{
|
||||
case 'Z':
|
||||
case 'z':
|
||||
{
|
||||
operations.Add(SvgPathOperation.ClosePath());
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
case 'm':
|
||||
{
|
||||
var relative = path[i] == 'm';
|
||||
if (!TryReadFloat(in path, ref i, out var x) || !TryReadFloat(in path, ref i, out var y))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
operations.Add(SvgPathOperation.MoveTo(x, y, relative));
|
||||
while (TryReadFloat(in path, ref i, out x) && TryReadFloat(in path, ref i, out y))
|
||||
{
|
||||
operations.Add(SvgPathOperation.LineTo(x, y, relative));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
case 'l':
|
||||
{
|
||||
var relative = path[i] == 'l';
|
||||
while (TryReadFloat(in path, ref i, out var x) && TryReadFloat(in path, ref i, out var y))
|
||||
{
|
||||
operations.Add(SvgPathOperation.LineTo(x, y, relative));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Q':
|
||||
case 'q':
|
||||
{
|
||||
var relative = path[i] == 'q';
|
||||
while (TryReadFloat(in path, ref i, out var x1) && TryReadFloat(in path, ref i, out var y1)
|
||||
&& TryReadFloat(in path, ref i, out var x) && TryReadFloat(in path, ref i, out var y))
|
||||
{
|
||||
operations.Add(SvgPathOperation.QuadraticTo(x1, y1, x, y, relative));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'T':
|
||||
case 't':
|
||||
{
|
||||
var relative = path[i] == 't';
|
||||
while (TryReadFloat(in path, ref i, out var x) && TryReadFloat(in path, ref i, out var y))
|
||||
{
|
||||
SvgUnitValue x1;
|
||||
SvgUnitValue y1;
|
||||
var last = operations.Last();
|
||||
|
||||
if (last.Op != Operations.QuadraticTo)
|
||||
{
|
||||
x1 = last.X.Value;
|
||||
y1 = last.Y.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = new SvgUnitValue(last.X.Value.Value + (last.X.Value.Value - last.X1.Value.Value), SvgUnitValue.Units.undefined);
|
||||
y1 = new SvgUnitValue(last.Y.Value.Value + (last.Y.Value.Value - last.Y1.Value.Value), SvgUnitValue.Units.undefined);
|
||||
}
|
||||
|
||||
operations.Add(SvgPathOperation.QuadraticTo(x1, y1, x, y, relative));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
case 'c':
|
||||
{
|
||||
var relative = path[i] == 'c';
|
||||
while (TryReadFloat(in path, ref i, out var x1) && TryReadFloat(in path, ref i, out var y1)
|
||||
&& TryReadFloat(in path, ref i, out var x2) && TryReadFloat(in path, ref i, out var y2)
|
||||
&& TryReadFloat(in path, ref i, out var x) && TryReadFloat(in path, ref i, out var y))
|
||||
{
|
||||
operations.Add(SvgPathOperation.CubicTo(x1, y1, x2, y2, x, y, relative));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
case 's':
|
||||
{
|
||||
var relative = path[i] == 's';
|
||||
while (TryReadFloat(in path, ref i, out var x) && TryReadFloat(in path, ref i, out var y)
|
||||
&& TryReadFloat(in path, ref i, out var x2) && TryReadFloat(in path, ref i, out var y2))
|
||||
{
|
||||
SvgUnitValue x1;
|
||||
SvgUnitValue y1;
|
||||
var last = operations.Last();
|
||||
|
||||
if (last.Op != Operations.CubicTo)
|
||||
{
|
||||
x1 = last.X.Value;
|
||||
y1 = last.Y.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = new SvgUnitValue(last.X.Value.Value + (last.X.Value.Value - last.X2.Value.Value), SvgUnitValue.Units.undefined);
|
||||
y1 = new SvgUnitValue(last.Y.Value.Value + (last.Y.Value.Value - last.Y2.Value.Value), SvgUnitValue.Units.undefined);
|
||||
}
|
||||
|
||||
operations.Add(SvgPathOperation.CubicTo(x1, y1, x2, y2, x, y, relative));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (char.IsWhiteSpace(path[i]) || path[i] == ',' || path[i] == ' ')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
|
||||
private static bool TryReadFloat(in string str, ref int position, out SvgUnitValue value)
|
||||
{
|
||||
// look for the first character that can be ins float
|
||||
int floatStart = 0;
|
||||
|
||||
for (position++; position < str.Length; position++)
|
||||
{
|
||||
if (floatStart == 0)
|
||||
{
|
||||
if (char.IsWhiteSpace(str[position]) || str[position] == ',')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// we can skip 'whitespace'
|
||||
}
|
||||
if (char.IsDigit(str[position]) || str[position] == '-' || str[position] == '.')
|
||||
{
|
||||
if (floatStart == 0) { floatStart = position; }
|
||||
}
|
||||
else
|
||||
{
|
||||
position--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (floatStart == 0)
|
||||
{
|
||||
|
||||
value = SvgUnitValue.Unset;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (position == str.Length)
|
||||
{
|
||||
value = new SvgUnitValue(float.Parse(str.Substring(floatStart)), SvgUnitValue.Units.undefined);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = new SvgUnitValue(float.Parse(str.Substring(floatStart, position - floatStart + 1)), SvgUnitValue.Units.undefined);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public enum Operations
|
||||
{
|
||||
MoveTo,
|
||||
LineTo,
|
||||
ClosePath,
|
||||
QuadraticTo,
|
||||
CubicTo
|
||||
}
|
||||
|
||||
private static SvgPathOperation CubicTo(SvgUnitValue x1, SvgUnitValue y1, SvgUnitValue x2, SvgUnitValue y2, SvgUnitValue x, SvgUnitValue y, bool relative)
|
||||
{
|
||||
return new SvgPathOperation()
|
||||
{
|
||||
IsRelative = relative,
|
||||
Op = Operations.CubicTo,
|
||||
X = x,
|
||||
Y = y,
|
||||
X1 = x1,
|
||||
Y1 = y1,
|
||||
X2 = x2,
|
||||
Y2 = y2
|
||||
};
|
||||
}
|
||||
private static SvgPathOperation QuadraticTo(SvgUnitValue x1, SvgUnitValue y1, SvgUnitValue x, SvgUnitValue y, bool relative)
|
||||
{
|
||||
return new SvgPathOperation()
|
||||
{
|
||||
IsRelative = relative,
|
||||
Op = Operations.QuadraticTo,
|
||||
X = x,
|
||||
Y = y,
|
||||
X1 = x1,
|
||||
Y1 = y1
|
||||
};
|
||||
}
|
||||
|
||||
public static SvgPathOperation MoveTo(SvgUnitValue x, SvgUnitValue y, bool relative)
|
||||
{
|
||||
return new SvgPathOperation()
|
||||
{
|
||||
IsRelative = relative,
|
||||
Op = Operations.MoveTo,
|
||||
X = x,
|
||||
Y = y
|
||||
};
|
||||
}
|
||||
public static SvgPathOperation LineTo(SvgUnitValue x, SvgUnitValue y, bool relative)
|
||||
{
|
||||
return new SvgPathOperation()
|
||||
{
|
||||
IsRelative = relative,
|
||||
Op = Operations.LineTo,
|
||||
X = x,
|
||||
Y = y
|
||||
};
|
||||
}
|
||||
|
||||
internal static SvgPathOperation ClosePath()
|
||||
{
|
||||
return new SvgPathOperation()
|
||||
{
|
||||
Op = Operations.ClosePath,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SvgPathOperationExtensions
|
||||
{
|
||||
private static PointF Point<TPixel>(Image<TPixel> img, PointF currentPoint, SvgUnitValue x, SvgUnitValue y) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var p = new PointF(x.AsPixelXAxis(img), y.AsPixelYAxis(img));
|
||||
return currentPoint + p;
|
||||
}
|
||||
public static PointF PointMain<TPixel>(this SvgPathOperation operation, Image<TPixel> img, PointF currentPoint) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
return Point(img, operation.IsRelative ? currentPoint : default, operation.X.Value, operation.Y.Value);
|
||||
}
|
||||
public static PointF Point1<TPixel>(this SvgPathOperation operation, Image<TPixel> img, PointF currentPoint) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
return Point(img, operation.IsRelative ? currentPoint : default, operation.X1.Value, operation.Y1.Value);
|
||||
}
|
||||
public static PointF Point2<TPixel>(this SvgPathOperation operation, Image<TPixel> img, PointF currentPoint) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
return Point(img, operation.IsRelative ? currentPoint : default, operation.X2.Value, operation.Y2.Value);
|
||||
}
|
||||
|
||||
public static IPath GeneratePath<TPixel>(this IEnumerable<SvgPathOperation> operations, Image<TPixel> img) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var pb = new PathRenderer();
|
||||
|
||||
foreach (var op in operations)
|
||||
{
|
||||
switch (op.Op)
|
||||
{
|
||||
case SvgPathOperation.Operations.MoveTo:
|
||||
pb.MoveTo(op.PointMain(img, pb.CurrentPoint));
|
||||
break;
|
||||
case SvgPathOperation.Operations.LineTo:
|
||||
pb.LineTo(op.PointMain(img, pb.CurrentPoint));
|
||||
break;
|
||||
case SvgPathOperation.Operations.QuadraticTo:
|
||||
pb.QuadraticBezierTo(op.Point1(img, pb.CurrentPoint), op.PointMain(img, pb.CurrentPoint));
|
||||
break;
|
||||
case SvgPathOperation.Operations.CubicTo:
|
||||
pb.CubicBezierTo(op.Point1(img, pb.CurrentPoint), op.Point2(img, pb.CurrentPoint), op.PointMain(img, pb.CurrentPoint));
|
||||
break;
|
||||
case SvgPathOperation.Operations.ClosePath:
|
||||
pb.Close();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pb.Path();
|
||||
}
|
||||
|
||||
internal class PathRenderer
|
||||
{
|
||||
/// <summary>
|
||||
/// The builder. TODO: Should this be a property?
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private readonly PathBuilder builder;
|
||||
private readonly List<IPath> paths = new List<IPath>();
|
||||
private PointF currentPoint = default(PointF);
|
||||
private PointF initalPoint = default(PointF);
|
||||
|
||||
public PointF CurrentPoint => currentPoint;
|
||||
public PointF InitalPoint => initalPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseGlyphBuilder"/> class.
|
||||
/// </summary>
|
||||
public PathRenderer()
|
||||
{
|
||||
// glyphs are renderd realative to bottom left so invert the Y axis to allow it to render on top left origin surface
|
||||
this.builder = new PathBuilder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the paths that have been rendered by this.
|
||||
/// </summary>
|
||||
public IPath Path()
|
||||
{
|
||||
return this.builder.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a cubic bezier from the current point to the <paramref name="point"/>
|
||||
/// </summary>
|
||||
/// <param name="secondControlPoint">The second control point.</param>
|
||||
/// <param name="thirdControlPoint">The third control point.</param>
|
||||
/// <param name="point">The point.</param>
|
||||
public void CubicBezierTo(PointF secondControlPoint, PointF thirdControlPoint, PointF point)
|
||||
{
|
||||
this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point);
|
||||
this.currentPoint = point;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a line from the current point to the <paramref name="point"/>.
|
||||
/// </summary>
|
||||
/// <param name="point">The point.</param>
|
||||
public void LineTo(PointF point)
|
||||
{
|
||||
this.builder.AddLine(this.currentPoint, point);
|
||||
this.currentPoint = point;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to current point to the supplied vector.
|
||||
/// </summary>
|
||||
/// <param name="point">The point.</param>
|
||||
public void MoveTo(PointF point)
|
||||
{
|
||||
this.builder.StartFigure();
|
||||
this.currentPoint = point;
|
||||
this.initalPoint = point;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a quadratics bezier from the current point to the <paramref name="point"/>
|
||||
/// </summary>
|
||||
/// <param name="secondControlPoint">The second control point.</param>
|
||||
/// <param name="point">The point.</param>
|
||||
public void QuadraticBezierTo(PointF secondControlPoint, PointF point)
|
||||
{
|
||||
this.builder.AddBezier(this.currentPoint, secondControlPoint, point);
|
||||
this.currentPoint = point;
|
||||
}
|
||||
|
||||
internal void Close()
|
||||
{
|
||||
this.LineTo(initalPoint);
|
||||
//this.builder.CloseFigure();
|
||||
//this.currentPoint = this.initalPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,38 +2,45 @@
|
|||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using AngleSharp.Css.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.Shapes;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgRect : SvgElement
|
||||
{
|
||||
|
||||
internal sealed class SvgRect : SvgElement, ISvgShape
|
||||
{
|
||||
public SvgUnitValue X { get; private set; }
|
||||
public SvgUnitValue Y { get; private set; }
|
||||
public SvgUnitValue Width { get; private set; }
|
||||
public SvgUnitValue Height { get; private set; }
|
||||
public SvgPaint Fill { get; private set; }
|
||||
public SvgPaint Stroke { get; private set; }
|
||||
public SvgLineCap StrokeLineCap { get; private set; }
|
||||
public SvgLineJoin StrokeLineJoin { get; private set; }
|
||||
public SvgUnitValue StrokeWidth { get; private set; }
|
||||
public SvgUnitValue RadiusX { get; private set; }
|
||||
public SvgUnitValue RadiusY { get; private set; }
|
||||
|
||||
public static Task<SvgElement> LoadAsync(ISvgElement element)
|
||||
{
|
||||
var rx = element.TryGetUnitValue("rx");
|
||||
var ry = element.TryGetUnitValue("ry");
|
||||
return Task.FromResult<SvgElement>(new SvgRect()
|
||||
{
|
||||
Fill = SvgPaint.Parse(element.Attributes["fill"]?.Value ?? "Black", element.Attributes["fill-opacity"]?.Value ?? "1"),
|
||||
Stroke = SvgPaint.Parse(element.Attributes["stroke"]?.Value ?? "None", element.Attributes["stroke-opacity"]?.Value ?? "1"),
|
||||
StrokeWidth = SvgUnitValue.Parse(element.Attributes["stroke-width"]?.Value ?? "1"),
|
||||
X = SvgUnitValue.Parse(element.Attributes["x"]?.Value),
|
||||
Y = SvgUnitValue.Parse(element.Attributes["y"]?.Value),
|
||||
RadiusX = SvgUnitValue.Parse(element.Attributes["rx"]?.Value ?? element.Attributes["ry"]?.Value ?? "0"),
|
||||
RadiusY = SvgUnitValue.Parse(element.Attributes["ry"]?.Value ?? element.Attributes["rx"]?.Value ?? "0"),
|
||||
Width = SvgUnitValue.Parse(element.Attributes["width"]?.Value),
|
||||
Height = SvgUnitValue.Parse(element.Attributes["height"]?.Value),
|
||||
Fill = element.GetPaint("fill", "Black", "1"),
|
||||
Stroke = element.GetPaint("stroke", "None", "1"),
|
||||
StrokeWidth = element.GetUnitValue("stroke-width", "1"),
|
||||
StrokeLineCap = element.GetLineCap("stroke-linecap", "butt"),
|
||||
StrokeLineJoin = element.GetLineJoin("stroke-linejoin", "miter"),
|
||||
X = element.GetUnitValue("x"),
|
||||
Y = element.GetUnitValue("y"),
|
||||
RadiusX = rx ?? ry ?? SvgUnitValue.Zero,
|
||||
RadiusY = ry ?? rx ?? SvgUnitValue.Zero,
|
||||
Width = element.GetUnitValue("width"),
|
||||
Height = element.GetUnitValue("height")
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -51,17 +58,19 @@ namespace SixLabors.Svg.Dom
|
|||
|
||||
var fillBrush = Fill.AsBrush<TPixel>();
|
||||
var strokeBrush = Stroke.AsBrush<TPixel>();
|
||||
var strokeWidth = this.StrokeWidth.AsPixelXAxis(image);
|
||||
image.Mutate(x =>
|
||||
{
|
||||
if (fillBrush != null)
|
||||
{
|
||||
x = x.Fill(fillBrush, rect);
|
||||
}
|
||||
if (strokeBrush != null && strokeWidth > 0)
|
||||
if (strokeBrush != null)
|
||||
{
|
||||
var outline = Outliner.GenerateOutline(rect, strokeWidth);
|
||||
x = x.Fill(strokeBrush, outline);
|
||||
var outline = rect.GenerateStroke(image, this);
|
||||
if (outline != null)
|
||||
{
|
||||
x = x.Fill(strokeBrush, outline);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -83,19 +92,15 @@ namespace SixLabors.Svg.Dom
|
|||
//lets make 3 more positioned correctly, we can do that by translating the orgional artound the center of the image
|
||||
var center = new Vector2(imageWidth / 2F, imageHeight / 2F);
|
||||
|
||||
float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1;
|
||||
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1;
|
||||
float rightPos = imageWidth - cornerToptLeft.Bounds.Width ;
|
||||
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height ;
|
||||
|
||||
// move it across the widthof the image - the width of the shape
|
||||
IPath cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos - 0.5f, 0);
|
||||
IPath cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos - 0.5f);
|
||||
IPath cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos - 0.5f, bottomPos - 0.5f);
|
||||
IPath cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0);
|
||||
IPath cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos);
|
||||
IPath cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos);
|
||||
|
||||
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,226 +1,18 @@
|
|||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.Shapes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal struct SvgPaint
|
||||
{
|
||||
public static readonly SvgPaint Unset = default(SvgPaint);
|
||||
|
||||
internal SvgPaint(string value, float opacity)
|
||||
{
|
||||
Value = value;
|
||||
Opacity = opacity;
|
||||
}
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
public float Opacity { get; }
|
||||
|
||||
public static SvgPaint Parse(string val, string opacity)
|
||||
{
|
||||
val = val?.Trim() ?? "";
|
||||
opacity = opacity?.Trim() ?? "";
|
||||
|
||||
float opacityVal = 1;
|
||||
if (float.TryParse(opacity, out float op))
|
||||
{
|
||||
opacityVal = op;
|
||||
if (opacityVal < 0)
|
||||
{
|
||||
opacityVal = 0;
|
||||
}
|
||||
if (opacityVal > 1)
|
||||
{
|
||||
opacityVal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new SvgPaint(val, opacityVal);
|
||||
|
||||
}
|
||||
|
||||
private TPixel? GetNamedColor<TPixel>(string name) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var dict = new Dictionary<string, TPixel>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["AliceBlue"] = NamedColors<TPixel>.AliceBlue,
|
||||
["MistyRose"] = NamedColors<TPixel>.MistyRose,
|
||||
["Moccasin"] = NamedColors<TPixel>.Moccasin,
|
||||
["NavajoWhite"] = NamedColors<TPixel>.NavajoWhite,
|
||||
["Navy"] = NamedColors<TPixel>.Navy,
|
||||
["OldLace"] = NamedColors<TPixel>.OldLace,
|
||||
["Olive"] = NamedColors<TPixel>.Olive,
|
||||
["MintCream"] = NamedColors<TPixel>.MintCream,
|
||||
["OliveDrab"] = NamedColors<TPixel>.OliveDrab,
|
||||
["OrangeRed"] = NamedColors<TPixel>.OrangeRed,
|
||||
["Orchid"] = NamedColors<TPixel>.Orchid,
|
||||
["PaleGoldenrod"] = NamedColors<TPixel>.PaleGoldenrod,
|
||||
["PaleGreen"] = NamedColors<TPixel>.PaleGreen,
|
||||
["PaleTurquoise"] = NamedColors<TPixel>.PaleTurquoise,
|
||||
["PaleVioletRed"] = NamedColors<TPixel>.PaleVioletRed,
|
||||
["Orange"] = NamedColors<TPixel>.Orange,
|
||||
["PapayaWhip"] = NamedColors<TPixel>.PapayaWhip,
|
||||
["MidnightBlue"] = NamedColors<TPixel>.MidnightBlue,
|
||||
["MediumTurquoise"] = NamedColors<TPixel>.MediumTurquoise,
|
||||
["LightSteelBlue"] = NamedColors<TPixel>.LightSteelBlue,
|
||||
["LightYellow"] = NamedColors<TPixel>.LightYellow,
|
||||
["Lime"] = NamedColors<TPixel>.Lime,
|
||||
["LimeGreen"] = NamedColors<TPixel>.LimeGreen,
|
||||
["Linen"] = NamedColors<TPixel>.Linen,
|
||||
["Magenta"] = NamedColors<TPixel>.Magenta,
|
||||
["MediumVioletRed"] = NamedColors<TPixel>.MediumVioletRed,
|
||||
["Maroon"] = NamedColors<TPixel>.Maroon,
|
||||
["MediumBlue"] = NamedColors<TPixel>.MediumBlue,
|
||||
["MediumOrchid"] = NamedColors<TPixel>.MediumOrchid,
|
||||
["MediumPurple"] = NamedColors<TPixel>.MediumPurple,
|
||||
["MediumSeaGreen"] = NamedColors<TPixel>.MediumSeaGreen,
|
||||
["MediumSlateBlue"] = NamedColors<TPixel>.MediumSlateBlue,
|
||||
["MediumSpringGreen"] = NamedColors<TPixel>.MediumSpringGreen,
|
||||
["MediumAquamarine"] = NamedColors<TPixel>.MediumAquamarine,
|
||||
["LightSlateGray"] = NamedColors<TPixel>.LightSlateGray,
|
||||
["PeachPuff"] = NamedColors<TPixel>.PeachPuff,
|
||||
["Pink"] = NamedColors<TPixel>.Pink,
|
||||
["SpringGreen"] = NamedColors<TPixel>.SpringGreen,
|
||||
["SteelBlue"] = NamedColors<TPixel>.SteelBlue,
|
||||
["Tan"] = NamedColors<TPixel>.Tan,
|
||||
["Teal"] = NamedColors<TPixel>.Teal,
|
||||
["Thistle"] = NamedColors<TPixel>.Thistle,
|
||||
["Tomato"] = NamedColors<TPixel>.Tomato,
|
||||
["Snow"] = NamedColors<TPixel>.Snow,
|
||||
["Transparent"] = NamedColors<TPixel>.Transparent,
|
||||
["Violet"] = NamedColors<TPixel>.Violet,
|
||||
["Wheat"] = NamedColors<TPixel>.Wheat,
|
||||
["White"] = NamedColors<TPixel>.White,
|
||||
["WhiteSmoke"] = NamedColors<TPixel>.WhiteSmoke,
|
||||
["Yellow"] = NamedColors<TPixel>.Yellow,
|
||||
["YellowGreen"] = NamedColors<TPixel>.YellowGreen,
|
||||
["Turquoise"] = NamedColors<TPixel>.Turquoise,
|
||||
["Peru"] = NamedColors<TPixel>.Peru,
|
||||
["SlateGray"] = NamedColors<TPixel>.SlateGray,
|
||||
["SkyBlue"] = NamedColors<TPixel>.SkyBlue,
|
||||
["Plum"] = NamedColors<TPixel>.Plum,
|
||||
["PowderBlue"] = NamedColors<TPixel>.PowderBlue,
|
||||
["Purple"] = NamedColors<TPixel>.Purple,
|
||||
["RebeccaPurple"] = NamedColors<TPixel>.RebeccaPurple,
|
||||
["Red"] = NamedColors<TPixel>.Red,
|
||||
["RosyBrown"] = NamedColors<TPixel>.RosyBrown,
|
||||
["SlateBlue"] = NamedColors<TPixel>.SlateBlue,
|
||||
["RoyalBlue"] = NamedColors<TPixel>.RoyalBlue,
|
||||
["Salmon"] = NamedColors<TPixel>.Salmon,
|
||||
["SandyBrown"] = NamedColors<TPixel>.SandyBrown,
|
||||
["SeaGreen"] = NamedColors<TPixel>.SeaGreen,
|
||||
["SeaShell"] = NamedColors<TPixel>.SeaShell,
|
||||
["Sienna"] = NamedColors<TPixel>.Sienna,
|
||||
["Silver"] = NamedColors<TPixel>.Silver,
|
||||
["SaddleBrown"] = NamedColors<TPixel>.SaddleBrown,
|
||||
["LightSkyBlue"] = NamedColors<TPixel>.LightSkyBlue,
|
||||
["LightSeaGreen"] = NamedColors<TPixel>.LightSeaGreen,
|
||||
["LightSalmon"] = NamedColors<TPixel>.LightSalmon,
|
||||
["Crimson"] = NamedColors<TPixel>.Crimson,
|
||||
["Cyan"] = NamedColors<TPixel>.Cyan,
|
||||
["DarkBlue"] = NamedColors<TPixel>.DarkBlue,
|
||||
["DarkCyan"] = NamedColors<TPixel>.DarkCyan,
|
||||
["DarkGoldenrod"] = NamedColors<TPixel>.DarkGoldenrod,
|
||||
["DarkGray"] = NamedColors<TPixel>.DarkGray,
|
||||
["Cornsilk"] = NamedColors<TPixel>.Cornsilk,
|
||||
["DarkGreen"] = NamedColors<TPixel>.DarkGreen,
|
||||
["DarkMagenta"] = NamedColors<TPixel>.DarkMagenta,
|
||||
["DarkOliveGreen"] = NamedColors<TPixel>.DarkOliveGreen,
|
||||
["DarkOrange"] = NamedColors<TPixel>.DarkOrange,
|
||||
["DarkOrchid"] = NamedColors<TPixel>.DarkOrchid,
|
||||
["DarkRed"] = NamedColors<TPixel>.DarkRed,
|
||||
["DarkSalmon"] = NamedColors<TPixel>.DarkSalmon,
|
||||
["DarkKhaki"] = NamedColors<TPixel>.DarkKhaki,
|
||||
["DarkSeaGreen"] = NamedColors<TPixel>.DarkSeaGreen,
|
||||
["CornflowerBlue"] = NamedColors<TPixel>.CornflowerBlue,
|
||||
["Chocolate"] = NamedColors<TPixel>.Chocolate,
|
||||
["AntiqueWhite"] = NamedColors<TPixel>.AntiqueWhite,
|
||||
["Aqua"] = NamedColors<TPixel>.Aqua,
|
||||
["Aquamarine"] = NamedColors<TPixel>.Aquamarine,
|
||||
["Azure"] = NamedColors<TPixel>.Azure,
|
||||
["Beige"] = NamedColors<TPixel>.Beige,
|
||||
["Bisque"] = NamedColors<TPixel>.Bisque,
|
||||
["Coral"] = NamedColors<TPixel>.Coral,
|
||||
["Black"] = NamedColors<TPixel>.Black,
|
||||
["Blue"] = NamedColors<TPixel>.Blue,
|
||||
["BlueViolet"] = NamedColors<TPixel>.BlueViolet,
|
||||
["Brown"] = NamedColors<TPixel>.Brown,
|
||||
["BurlyWood"] = NamedColors<TPixel>.BurlyWood,
|
||||
["CadetBlue"] = NamedColors<TPixel>.CadetBlue,
|
||||
["Chartreuse"] = NamedColors<TPixel>.Chartreuse,
|
||||
["BlanchedAlmond"] = NamedColors<TPixel>.BlanchedAlmond,
|
||||
["DarkSlateBlue"] = NamedColors<TPixel>.DarkSlateBlue,
|
||||
["DarkSlateGray"] = NamedColors<TPixel>.DarkSlateGray,
|
||||
["DarkTurquoise"] = NamedColors<TPixel>.DarkTurquoise,
|
||||
["Indigo"] = NamedColors<TPixel>.Indigo,
|
||||
["Ivory"] = NamedColors<TPixel>.Ivory,
|
||||
["Khaki"] = NamedColors<TPixel>.Khaki,
|
||||
["Lavender"] = NamedColors<TPixel>.Lavender,
|
||||
["LavenderBlush"] = NamedColors<TPixel>.LavenderBlush,
|
||||
["LawnGreen"] = NamedColors<TPixel>.LawnGreen,
|
||||
["IndianRed"] = NamedColors<TPixel>.IndianRed,
|
||||
["LemonChiffon"] = NamedColors<TPixel>.LemonChiffon,
|
||||
["LightCoral"] = NamedColors<TPixel>.LightCoral,
|
||||
["LightCyan"] = NamedColors<TPixel>.LightCyan,
|
||||
["LightGoldenrodYellow"] = NamedColors<TPixel>.LightGoldenrodYellow,
|
||||
["LightGray"] = NamedColors<TPixel>.LightGray,
|
||||
["LightGreen"] = NamedColors<TPixel>.LightGreen,
|
||||
["LightPink"] = NamedColors<TPixel>.LightPink,
|
||||
["LightBlue"] = NamedColors<TPixel>.LightBlue,
|
||||
["HotPink"] = NamedColors<TPixel>.HotPink,
|
||||
["Honeydew"] = NamedColors<TPixel>.Honeydew,
|
||||
["GreenYellow"] = NamedColors<TPixel>.GreenYellow,
|
||||
["DarkViolet"] = NamedColors<TPixel>.DarkViolet,
|
||||
["DeepPink"] = NamedColors<TPixel>.DeepPink,
|
||||
["DeepSkyBlue"] = NamedColors<TPixel>.DeepSkyBlue,
|
||||
["DimGray"] = NamedColors<TPixel>.DimGray,
|
||||
["DodgerBlue"] = NamedColors<TPixel>.DodgerBlue,
|
||||
["Firebrick"] = NamedColors<TPixel>.Firebrick,
|
||||
["FloralWhite"] = NamedColors<TPixel>.FloralWhite,
|
||||
["ForestGreen"] = NamedColors<TPixel>.ForestGreen,
|
||||
["Fuchsia"] = NamedColors<TPixel>.Fuchsia,
|
||||
["Gainsboro"] = NamedColors<TPixel>.Gainsboro,
|
||||
["GhostWhite"] = NamedColors<TPixel>.GhostWhite,
|
||||
["Gold"] = NamedColors<TPixel>.Gold,
|
||||
["Goldenrod"] = NamedColors<TPixel>.Goldenrod,
|
||||
["Gray"] = NamedColors<TPixel>.Gray,
|
||||
["Green"] = NamedColors<TPixel>.Green,
|
||||
};
|
||||
|
||||
if (dict.TryGetValue(name, out var pixel))
|
||||
{
|
||||
return pixel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IBrush<TPixel> AsBrush<TPixel>() where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
// lets asume the brush is a color for now
|
||||
// TODO update ColourBuilder to expose named colors
|
||||
var value = this.Value;
|
||||
if (string.IsNullOrWhiteSpace(value) || value.Equals("None", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// if a null prush is returned we should skip
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var color = GetNamedColor<TPixel>(Value) ?? ColorBuilder<TPixel>.FromHex(value);
|
||||
|
||||
|
||||
return new SolidBrush<TPixel>(color);
|
||||
|
||||
}
|
||||
}
|
||||
internal struct SvgUnitValue
|
||||
{
|
||||
public static readonly SvgUnitValue Unset = default(SvgUnitValue);
|
||||
public static readonly SvgUnitValue Zero = new SvgUnitValue(0, Units.undefined);
|
||||
public static readonly SvgUnitValue One = new SvgUnitValue(1, Units.undefined);
|
||||
|
||||
public bool IsSet { get; }
|
||||
public float Value { get; }
|
||||
|
@ -272,7 +64,7 @@ namespace SixLabors.Svg.Dom
|
|||
}
|
||||
}
|
||||
|
||||
public static SvgUnitValue Parse(string val)
|
||||
public static SvgUnitValue? Parse(string val)
|
||||
{
|
||||
val = val?.Trim() ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(val))
|
||||
|
@ -314,7 +106,7 @@ namespace SixLabors.Svg.Dom
|
|||
}
|
||||
}
|
||||
|
||||
return Unset;
|
||||
return null;
|
||||
}
|
||||
|
||||
public enum Units
|
||||
|
@ -333,4 +125,71 @@ namespace SixLabors.Svg.Dom
|
|||
}
|
||||
|
||||
|
||||
internal struct SvgLineCap
|
||||
{
|
||||
public static readonly SvgLineCap Unset = default(SvgLineCap);
|
||||
public static readonly SvgLineCap Butt = new SvgLineCap(EndCapStyle.Butt);
|
||||
public static readonly SvgLineCap Round = new SvgLineCap(EndCapStyle.Round);
|
||||
public static readonly SvgLineCap Square = new SvgLineCap(EndCapStyle.Square);
|
||||
|
||||
public bool IsSet { get; }
|
||||
|
||||
public EndCapStyle Style { get; private set; }
|
||||
|
||||
public SvgLineCap(EndCapStyle style)
|
||||
{
|
||||
this.IsSet = true;
|
||||
this.Style = style;
|
||||
}
|
||||
|
||||
public static SvgLineCap? Parse(string val)
|
||||
{
|
||||
val = (val?.Trim().ToLower() ?? "");
|
||||
switch (val)
|
||||
{
|
||||
case "butt":
|
||||
return SvgLineCap.Butt;
|
||||
case "round":
|
||||
return SvgLineCap.Round;
|
||||
case "square":
|
||||
return SvgLineCap.Square;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
internal struct SvgLineJoin
|
||||
{
|
||||
public static readonly SvgLineJoin Unset = default(SvgLineJoin);
|
||||
public static readonly SvgLineJoin Miter = new SvgLineJoin(JointStyle.Miter);
|
||||
public static readonly SvgLineJoin Round = new SvgLineJoin(JointStyle.Round);
|
||||
public static readonly SvgLineJoin Square = new SvgLineJoin(JointStyle.Square);
|
||||
|
||||
public bool IsSet { get; }
|
||||
|
||||
public JointStyle Style { get; private set; }
|
||||
|
||||
public SvgLineJoin(JointStyle style)
|
||||
{
|
||||
this.IsSet = true;
|
||||
this.Style = style;
|
||||
}
|
||||
|
||||
public static SvgLineJoin? Parse(string val)
|
||||
{
|
||||
val = (val?.Trim().ToLower() ?? "");
|
||||
switch (val)
|
||||
{
|
||||
case "miter":
|
||||
return SvgLineJoin.Miter;
|
||||
case "round":
|
||||
return SvgLineJoin.Round;
|
||||
case "bevel":
|
||||
return SvgLineJoin.Square;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard1.3</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="0.10.1" />
|
||||
<PackageReference Include="AngleSharp.Css" Version="0.10.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-dev002362" />
|
||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-dev000112" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,7 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
Загрузка…
Ссылка в новой задаче