Added initial work for reading SVG files

This commit is contained in:
Matthew Leibowitz 2016-10-17 00:18:32 +02:00
Родитель a59241b290
Коммит 7d3bb0db78
8 изменённых файлов: 1130 добавлений и 0 удалений

Просмотреть файл

@ -59,6 +59,7 @@ Task ("libs")
ReplaceTextInFiles ("./binding/Binding/Properties/SkiaSharpAssemblyInfo.cs", "{GIT_SHA}", sha);
ReplaceTextInFiles ("./source/SkiaSharp.Views/SkiaSharp.Views.Shared/Properties/SkiaSharpViewsAssemblyInfo.cs", "{GIT_SHA}", sha);
ReplaceTextInFiles ("./source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/Properties/SkiaSharpViewsFormsAssemblyInfo.cs", "{GIT_SHA}", sha);
ReplaceTextInFiles ("./source/SkiaSharp.Svg/SkiaSharp.Svg/Properties/SkiaSharpSvgAssemblyInfo.cs", "{GIT_SHA}", sha);
}
// create all the directories
@ -103,6 +104,8 @@ Task ("libs")
CopyFileToDirectory ("./source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms/bin/Release/SkiaSharp.Views.Forms.dll", "./output/portable/");
CopyFileToDirectory ("./source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.UWP/bin/Release/SkiaSharp.Views.Forms.dll", "./output/uwp/");
// copy SVG
CopyFileToDirectory ("./source/SkiaSharp.Svg/SkiaSharp.Svg/bin/Release/SkiaSharp.Svg.dll", "./output/portable/");
}
if (IsRunningOnUnix ()) {
@ -137,6 +140,9 @@ Task ("libs")
CopyFileToDirectory ("./source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms/bin/Release/SkiaSharp.Views.Forms.dll", "./output/portable/");
CopyFileToDirectory ("./source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/bin/Release/SkiaSharp.Views.Forms.dll", "./output/android/");
CopyFileToDirectory ("./source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/bin/Release/SkiaSharp.Views.Forms.dll", "./output/ios/");
// copy SVG
CopyFileToDirectory ("./source/SkiaSharp.Svg/SkiaSharp.Svg/bin/Release/SkiaSharp.Svg.dll", "./output/portable/");
}
});
@ -266,6 +272,8 @@ Task ("nuget")
PackageNuGet ("./nuget/SkiaSharp.Views.Forms.Mac.nuspec", "./output/");
}
}
// SVG is a PCL
PackageNuGet ("./nuget/SkiaSharp.Svg.nuspec", "./output/");
});
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -313,6 +321,7 @@ Task ("set-versions")
{ "SkiaSharp", "1.54.1" },
{ "SkiaSharp.Views", "1.54.1-beta1" },
{ "SkiaSharp.Views.Forms", "1.54.1-beta1" },
{ "SkiaSharp.Svg", "1.54.1-beta1" },
};
var files = new List<string> ();
@ -348,6 +357,9 @@ Task ("set-versions")
UpdateAssemblyInfo (
"./source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/Properties/SkiaSharpViewsFormsAssemblyInfo.cs",
version, fileVersion, sha);
UpdateAssemblyInfo (
"./source/SkiaSharp.Svg/SkiaSharp.Svg/Properties/SkiaSharpSvgAssemblyInfo.cs",
version, fileVersion, sha);
});
////////////////////////////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<package>
<metadata>
<id>SkiaSharp.Svg</id>
<title>SVG Support for SkiaSharp</title>
<version>1.54.1-beta1</version>
<authors>Xamarin Inc.</authors>
<owners>Xamarin Inc.</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>This package adds support for reading SVG files into SkiaSharp.</description>
<copyright>Copyright (c) Xamarin Inc. 2016</copyright>
<licenseUrl>https://github.com/mono/SkiaSharp/blob/master/LICENSE.md</licenseUrl>
<projectUrl>https://github.com/mono/SkiaSharp</projectUrl>
<iconUrl>https://cdn.rawgit.com/mono/SkiaSharp/v1.53.0/images/skia_256x256.png</iconUrl>
<dependencies>
<dependency id="SkiaSharp" version="1.54.1" />
</dependencies>
</metadata>
<files>
<file src="output/portable/SkiaSharp.Svg.dll" target="lib/portable-net45+win8+wpa81+wp8" />
</files>
</package>

Просмотреть файл

@ -0,0 +1,17 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by Cake.
// </auto-generated>
//------------------------------------------------------------------------------
using System.Reflection;
[assembly: AssemblyTitle("SkiaSharp.Svg")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SkiaSharp.Svg")]
[assembly: AssemblyVersion("1.54.0.0")]
[assembly: AssemblyFileVersion("1.54.1.0")]
[assembly: AssemblyInformationalVersion("1.54.1.0-{GIT_SHA}")]
[assembly: AssemblyCopyright("Xamarin Inc.")]
[assembly: AssemblyTrademark("")]

Просмотреть файл

@ -0,0 +1,997 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
namespace SkiaSharp
{
public class SKSvg
{
private const float DefaultPPI = 160f;
private const bool DefaultThrowOnUnsupportedElement = false;
private static readonly IFormatProvider icult = CultureInfo.InvariantCulture;
private static readonly XNamespace xlink = "http://www.w3.org/1999/xlink";
private static readonly char[] WS = new char[] { ' ', '\t', '\n', '\r' };
private static readonly Regex unitRe = new Regex("px|pt|em|ex|pc|cm|mm|in");
private static readonly Regex percRe = new Regex("%");
private static readonly Regex fillUrlRe = new Regex(@"url\s*\(\s*#([^\)]+)\)");
private static readonly Regex keyValueRe = new Regex(@"\s*([\w-]+)\s*:\s*(.*)");
private static readonly Regex WSRe = new Regex(@"\s{2,}");
private readonly Dictionary<string, XElement> defs = new Dictionary<string, XElement>();
public SKSvg()
: this(DefaultPPI)
{
}
public SKSvg(float pixelsPerInch)
{
PixelsPerInch = pixelsPerInch;
ThrowOnUnsupportedElement = DefaultThrowOnUnsupportedElement;
}
public float PixelsPerInch { get; set; }
public bool ThrowOnUnsupportedElement { get; set; }
public SKPicture Picture { get; private set; }
public string Description { get; private set; }
public string Title { get; private set; }
public string Version { get; private set; }
public SKPicture Load(Stream stream)
{
return Load(XDocument.Load(stream));
}
private SKPicture Load(XDocument xdoc)
{
var svg = xdoc.Root;
var ns = svg.Name.Namespace;
// find the defs (gradients) - and follow all hrefs
foreach (var d in svg.Descendants())
{
var id = d.Attribute("id")?.Value?.Trim();
if (!string.IsNullOrEmpty(id))
defs[id] = ReadDefinition(d);
}
Version = svg.Attribute("version")?.Value;
Title = svg.Element(ns + "title")?.Value;
Description = svg.Element(ns + "desc")?.Value ?? svg.Element(ns + "description")?.Value;
// get the dimensions
var widthA = svg.Attribute("width");
var heightA = svg.Attribute("height");
var width = ReadNumber(widthA);
var height = ReadNumber(heightA);
var size = new SKSize(width, height);
var viewBox = SKRect.Create(size);
var viewBoxA = svg.Attribute("viewBox") ?? svg.Attribute("viewPort");
if (viewBoxA != null)
{
viewBox = ReadRectangle(viewBoxA.Value);
}
if (widthA != null && widthA.Value.Contains("%"))
{
size.Width *= viewBox.Width;
}
if (heightA != null && heightA.Value.Contains("%"))
{
size.Height *= viewBox.Height;
}
// craete the picture from the elements
using (var recorder = new SKPictureRecorder())
using (var canvas = recorder.BeginRecording(SKRect.Create(size)))
{
LoadElements(svg.Elements(), canvas);
Picture = recorder.EndRecording();
}
return Picture;
}
private void LoadElements(IEnumerable<XElement> elements, SKCanvas canvas)
{
foreach (var e in elements)
{
ReadElement(e, canvas);
}
}
private void ReadElement(XElement e, SKCanvas canvas)
{
ReadElement(e, canvas, null, CreatePaint());
}
private void ReadElement(XElement e, SKCanvas canvas, SKPaint stroke, SKPaint fill)
{
ReadPaints(e, ref stroke, ref fill);
// transform matrix
var transform = ReadTransform(e.Attribute("transform")?.Value ?? string.Empty);
canvas.Save();
canvas.Concat(ref transform);
// SVG elements
var elementName = e.Name.LocalName;
switch (elementName)
{
case "text":
if (stroke != null || fill != null)
{
ReadText(e, canvas, stroke?.Clone(), fill?.Clone());
}
break;
case "rect":
if (stroke != null || fill != null)
{
var x = ReadNumber(e.Attribute("x"));
var y = ReadNumber(e.Attribute("y"));
var width = ReadNumber(e.Attribute("width"));
var height = ReadNumber(e.Attribute("height"));
var rx = ReadNumber(e.Attribute("rx"));
var ry = ReadNumber(e.Attribute("ry"));
var rect = SKRect.Create(x, y, width, height);
if (rx > 0 || ry > 0)
{
if (fill != null)
canvas.DrawRoundRect(rect, rx, ry, fill);
if (stroke != null)
canvas.DrawRoundRect(rect, rx, ry, stroke);
}
else
{
if (fill != null)
canvas.DrawRect(rect, fill);
if (stroke != null)
canvas.DrawRect(rect, stroke);
}
}
break;
case "ellipse":
if (stroke != null || fill != null)
{
var cx = ReadNumber(e.Attribute("cx"));
var cy = ReadNumber(e.Attribute("cy"));
var rx = ReadNumber(e.Attribute("rx"));
var ry = ReadNumber(e.Attribute("ry"));
if (fill != null)
canvas.DrawOval(cx, cy, rx, ry, fill);
if (stroke != null)
canvas.DrawOval(cx, cy, rx, ry, stroke);
}
break;
case "circle":
if (stroke != null || fill != null)
{
var cx = ReadNumber(e.Attribute("cx"));
var cy = ReadNumber(e.Attribute("cy"));
var rr = ReadNumber(e.Attribute("r"));
if (fill != null)
canvas.DrawCircle(cx, cy, rr, fill);
if (stroke != null)
canvas.DrawCircle(cx, cy, rr, stroke);
}
break;
case "path":
if (stroke != null || fill != null)
{
var d = e.Attribute("d")?.Value;
if (!string.IsNullOrWhiteSpace(d))
{
var path = SKPath.ParseSvgPathData(d);
if (fill != null)
canvas.DrawPath(path, fill);
if (stroke != null)
canvas.DrawPath(path, stroke);
}
}
break;
case "polygon":
case "polyline":
if (stroke != null || fill != null)
{
var close = elementName == "polygon";
var p = e.Attribute("points")?.Value;
if (!string.IsNullOrWhiteSpace(p))
{
var path = ReadPolyPath(p, close);
if (fill != null)
canvas.DrawPath(path, fill);
if (stroke != null)
canvas.DrawPath(path, stroke);
}
}
break;
case "g":
if (e.HasElements)
{
foreach (var gElement in e.Elements())
{
ReadElement(gElement, canvas, stroke?.Clone(), fill?.Clone());
}
}
break;
case "use":
if (e.HasAttributes)
{
var href = ReadHref(e);
if (href != null)
{
// TODO: copy/process other attributes
var x = ReadNumber(e.Attribute("x"));
var y = ReadNumber(e.Attribute("y"));
var useTransform = SKMatrix.MakeTranslation(x, y);
canvas.Save();
canvas.Concat(ref useTransform);
ReadElement(href, canvas, stroke?.Clone(), fill?.Clone());
canvas.Restore();
}
}
break;
case "line":
if (stroke != null)
{
var x1 = ReadNumber(e.Attribute("x1"));
var x2 = ReadNumber(e.Attribute("x2"));
var y1 = ReadNumber(e.Attribute("y1"));
var y2 = ReadNumber(e.Attribute("y2"));
canvas.DrawLine(x1, y1, x2, y2, stroke);
}
break;
case "switch":
if (e.HasElements)
{
foreach (var ee in e.Elements())
{
var requiredFeatures = ee.Attribute("requiredFeatures");
var requiredExtensions = ee.Attribute("requiredExtensions");
var systemLanguage = ee.Attribute("systemLanguage");
// TODO: evaluate requiredFeatures, requiredExtensions and systemLanguage
var isVisible =
requiredFeatures == null &&
requiredExtensions == null &&
systemLanguage == null;
if (isVisible)
{
ReadElement(ee, canvas, stroke?.Clone(), fill?.Clone());
}
}
}
break;
case "defs":
case "title":
case "desc":
case "description":
// already read earlier
break;
default:
LogOrThrow($"SVG element '{elementName}' is not supported");
break;
}
// restore matrix
canvas.Restore();
}
private void ReadText(XElement e, SKCanvas canvas, SKPaint stroke, SKPaint fill)
{
// TODO: stroke
var x = ReadNumber(e.Attribute("x"));
var y = ReadNumber(e.Attribute("y"));
var xy = new SKPoint(x, y);
ReadFontAttributes(e, fill);
fill.TextAlign = ReadTextAlignment(e);
ReadTextSpans(e, canvas, xy, stroke, fill);
}
private void ReadTextSpans(XElement e, SKCanvas canvas, SKPoint location, SKPaint stroke, SKPaint fill)
{
var nodes = e.Nodes().ToArray();
for (int i = 0; i < nodes.Length; i++)
{
var c = nodes[i];
bool isFirst = i == 0;
bool isLast = i == nodes.Length - 1;
if (c.NodeType == XmlNodeType.Text)
{
// TODO: check for preserve whitespace
var textSegments = ((XText)c).Value.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
var count = textSegments.Length;
if (count > 0)
{
if (isFirst)
textSegments[0] = textSegments[0].TrimStart();
if (isLast)
textSegments[count - 1] = textSegments[count - 1].TrimEnd();
var text = WSRe.Replace(string.Concat(textSegments), " ");
canvas.DrawText(text, location.X, location.Y, fill);
location.X += fill.MeasureText(text);
}
}
else if (c.NodeType == XmlNodeType.Element)
{
var ce = (XElement)c;
if (ce.Name.LocalName == "tspan")
{
var spanFill = fill.Clone();
// the current span may want to change the cursor position
location.X = ReadOptionalNumber(ce.Attribute("x")) ?? location.X;
location.Y = ReadOptionalNumber(ce.Attribute("y")) ?? location.Y;
ReadFontAttributes(ce, spanFill);
var text = ce.Value.Trim();
canvas.DrawText(text, location.X, location.Y, spanFill);
location.X += spanFill.MeasureText(text);
}
}
}
}
private void ReadFontAttributes(XElement e, SKPaint paint)
{
var fontStyle = ReadStyle(e);
string ffamily;
if (!fontStyle.TryGetValue("font-family", out ffamily) || string.IsNullOrWhiteSpace(ffamily))
ffamily = paint.Typeface?.FamilyName;
var fweight = ReadFontWeight(fontStyle, paint.Typeface?.FontWeight ?? (int)SKFontStyleWeight.Normal);
var fwidth = ReadFontWidth(fontStyle, paint.Typeface?.FontWidth ?? (int)SKFontStyleWidth.Normal);
var fstyle = ReadFontStyle(fontStyle, paint.Typeface?.FontSlant ?? SKFontStyleSlant.Upright);
paint.Typeface = SKTypeface.FromFamilyName(ffamily, fweight, fwidth, fstyle);
string fsize;
if (fontStyle.TryGetValue("font-size", out fsize) && !string.IsNullOrWhiteSpace(fsize))
paint.TextSize = ReadNumber(fsize);
}
private static SKFontStyleSlant ReadFontStyle(Dictionary<string, string> fontStyle, SKFontStyleSlant defaultStyle = SKFontStyleSlant.Upright)
{
SKFontStyleSlant style = defaultStyle;
string fstyle;
if (fontStyle.TryGetValue("font-style", out fstyle) && !string.IsNullOrWhiteSpace(fstyle))
{
switch (fstyle)
{
case "italic":
style = SKFontStyleSlant.Italic;
break;
case "oblique":
style = SKFontStyleSlant.Oblique;
break;
case "normal":
style = SKFontStyleSlant.Upright;
break;
default:
style = defaultStyle;
break;
}
}
return style;
}
private int ReadFontWidth(Dictionary<string, string> fontStyle, int defaultWidth = (int)SKFontStyleWidth.Normal)
{
int width = defaultWidth;
string fwidth;
if (fontStyle.TryGetValue("font-stretch", out fwidth) && !string.IsNullOrWhiteSpace(fwidth) && !int.TryParse(fwidth, out width))
{
switch (fwidth)
{
case "ultra-condensed":
width = (int)SKFontStyleWidth.UltraCondensed;
break;
case "extra-condensed":
width = (int)SKFontStyleWidth.ExtraCondensed;
break;
case "condensed":
width = (int)SKFontStyleWidth.Condensed;
break;
case "semi-condensed":
width = (int)SKFontStyleWidth.SemiCondensed;
break;
case "normal":
width = (int)SKFontStyleWidth.Normal;
break;
case "semi-expanded":
width = (int)SKFontStyleWidth.SemiExpanded;
break;
case "expanded":
width = (int)SKFontStyleWidth.Expanded;
break;
case "extra-expanded":
width = (int)SKFontStyleWidth.ExtraExpanded;
break;
case "ultra-expanded":
width = (int)SKFontStyleWidth.UltraExpanded;
break;
case "wider":
width = width + 1;
break;
case "narrower":
width = width - 1;
break;
default:
width = defaultWidth;
break;
}
}
return Math.Min(Math.Max((int)SKFontStyleWidth.UltraCondensed, width), (int)SKFontStyleWidth.UltraExpanded);
}
private int ReadFontWeight(Dictionary<string, string> fontStyle, int defaultWeight = (int)SKFontStyleWeight.Normal)
{
int weight = defaultWeight;
string fweight;
if (fontStyle.TryGetValue("font-weight", out fweight) && !string.IsNullOrWhiteSpace(fweight) && !int.TryParse(fweight, out weight))
{
switch (fweight)
{
case "normal":
weight = (int)SKFontStyleWeight.Normal;
break;
case "bold":
weight = (int)SKFontStyleWeight.Bold;
break;
case "bolder":
weight = weight + 100;
break;
case "lighter":
weight = weight - 100;
break;
default:
weight = defaultWeight;
break;
}
}
return Math.Min(Math.Max((int)SKFontStyleWeight.Thin, weight), (int)SKFontStyleWeight.ExtraBlack);
}
private void LogOrThrow(string message)
{
if (ThrowOnUnsupportedElement)
throw new NotSupportedException(message);
else
Debug.WriteLine(message);
}
private string GetString(Dictionary<string, string> style, string name, string defaultValue = "")
{
string v;
if (style.TryGetValue(name, out v))
return v;
return defaultValue;
}
private Dictionary<string, string> ReadStyle(string style)
{
var d = new Dictionary<string, string>();
var kvs = style.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var kv in kvs)
{
var m = keyValueRe.Match(kv);
if (m.Success)
{
var k = m.Groups[1].Value;
var v = m.Groups[2].Value;
d[k] = v;
}
}
return d;
}
private Dictionary<string, string> ReadStyle(XElement e)
{
// get from local attributes
var dic = e.Attributes().ToDictionary(k => k.Name.LocalName, v => v.Value);
var style = e.Attribute("style")?.Value;
if (!string.IsNullOrWhiteSpace(style))
{
// get from stlye attribute
var styleDic = ReadStyle(style);
// overwrite
foreach (var pair in styleDic)
dic[pair.Key] = pair.Value;
}
return dic;
}
private void ReadPaints(XElement e, ref SKPaint stroke, ref SKPaint fill)
{
ReadPaints(ReadStyle(e), ref stroke, ref fill);
}
private void ReadPaints(Dictionary<string, string> style, ref SKPaint strokePaint, ref SKPaint fillPaint)
{
// stroke
var stroke = GetString(style, "stroke").Trim();
if (string.IsNullOrEmpty(stroke))
{
// no change
}
else if (stroke.Equals("none", StringComparison.OrdinalIgnoreCase))
{
strokePaint = null;
}
else
{
if (strokePaint == null)
strokePaint = CreatePaint(true);
SKColor color;
if (SKColor.TryParse(stroke, out color))
{
// preserve alpha
if (color.Alpha == 255)
strokePaint.Color = color.WithAlpha(strokePaint.Color.Alpha);
else
strokePaint.Color = color;
}
}
// stroke attributes
var strokeWidth = GetString(style, "stroke-width");
if (!string.IsNullOrWhiteSpace(strokeWidth))
{
if (strokePaint == null)
strokePaint = CreatePaint(true);
strokePaint.StrokeWidth = ReadNumber(strokeWidth);
}
var strokeOpacity = GetString(style, "stroke-opacity");
if (!string.IsNullOrWhiteSpace(strokeOpacity))
{
if (strokePaint == null)
strokePaint = CreatePaint(true);
strokePaint.Color = strokePaint.Color.WithAlpha((byte)(ReadNumber(strokeOpacity) * 255));
}
// fill
var fill = GetString(style, "fill").Trim();
if (string.IsNullOrEmpty(fill))
{
// no change
}
else if (fill.Equals("none", StringComparison.OrdinalIgnoreCase))
{
fillPaint = null;
}
else
{
if (fillPaint == null)
fillPaint = CreatePaint();
SKColor color;
if (SKColor.TryParse(fill, out color))
{
// preserve alpha
if (color.Alpha == 255)
fillPaint.Color = color.WithAlpha(fillPaint.Color.Alpha);
else
fillPaint.Color = color;
}
else
{
var read = false;
var urlM = fillUrlRe.Match(fill);
if (urlM.Success)
{
var id = urlM.Groups[1].Value.Trim();
XElement defE;
if (defs.TryGetValue(id, out defE))
{
var gradientShader = ReadGradient(defE);
if (gradientShader != null)
{
// TODO: multiple shaders
fillPaint.Shader = gradientShader;
read = true;
}
// else try another type (eg: image)
}
else
{
LogOrThrow($"Invalid fill url reference: {id}");
}
}
if (!read)
{
LogOrThrow($"Unsupported fill: {fill}");
}
}
}
// fill attributes
var fillOpacity = GetString(style, "fill-opacity");
if (!string.IsNullOrWhiteSpace(fillOpacity))
{
if (fillPaint == null)
fillPaint = CreatePaint();
fillPaint.Color = fillPaint.Color.WithAlpha((byte)(ReadNumber(fillOpacity) * 255));
}
}
private SKPaint CreatePaint(bool stroke = false)
{
return new SKPaint
{
IsAntialias = true,
IsStroke = stroke,
Color = SKColors.Black
};
}
private SKMatrix ReadTransform(string raw)
{
var t = SKMatrix.MakeIdentity();
if (string.IsNullOrWhiteSpace(raw))
{
return t;
}
var calls = raw.Trim().Split(new[] { ')' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var c in calls)
{
var args = c.Split(new[] { '(', ',', ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
var nt = SKMatrix.MakeIdentity();
switch (args[0])
{
case "matrix":
if (args.Length == 7)
{
nt.Values = new float[]
{
ReadNumber(args[1]), ReadNumber(args[3]), ReadNumber(args[5]),
ReadNumber(args[2]), ReadNumber(args[4]), ReadNumber(args[6]),
0, 0, 1
};
}
else
{
LogOrThrow($"Matrices are expected to have 6 elements, this one has {args.Length - 1}");
}
break;
case "translate":
if (args.Length >= 3)
{
nt = SKMatrix.MakeTranslation(ReadNumber(args[1]), ReadNumber(args[2]));
}
else if (args.Length >= 2)
{
nt = SKMatrix.MakeTranslation(ReadNumber(args[1]), 0);
}
break;
case "scale":
if (args.Length >= 3)
{
nt = SKMatrix.MakeScale(ReadNumber(args[1]), ReadNumber(args[2]));
}
else if (args.Length >= 2)
{
var sx = ReadNumber(args[1]);
nt = SKMatrix.MakeScale(sx, sx);
}
break;
case "rotate":
var a = ReadNumber(args[1]);
if (args.Length >= 4)
{
var x = ReadNumber(args[2]);
var y = ReadNumber(args[3]);
var t1 = SKMatrix.MakeTranslation(x, y);
var t2 = SKMatrix.MakeRotationDegrees(a);
var t3 = SKMatrix.MakeTranslation(-x, -y);
SKMatrix.Concat(ref nt, ref t1, ref t2);
SKMatrix.Concat(ref nt, ref nt, ref t3);
}
else
{
nt = SKMatrix.MakeRotationDegrees(a);
}
break;
default:
LogOrThrow($"Can't transform {args[0]}");
break;
}
SKMatrix.Concat(ref t, ref t, ref nt);
}
return t;
}
private SKPath ReadPolyPath(string pointsData, bool closePath)
{
var path = new SKPath();
var points = pointsData.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < points.Length; i++)
{
var point = points[i];
var xy = point.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var x = ReadNumber(xy[0]);
var y = ReadNumber(xy[1]);
if (i == 0)
{
path.MoveTo(x, y);
}
else
{
path.LineTo(x, y);
}
}
if (closePath)
{
path.Close();
}
return path;
}
private SKTextAlign ReadTextAlignment(XElement element)
{
string value = null;
if (element != null)
{
var attrib = element.Attribute("text-anchor");
if (attrib != null && !string.IsNullOrWhiteSpace(attrib.Value))
value = attrib.Value;
else
{
var style = element.Attribute("style");
if (style != null && !string.IsNullOrWhiteSpace(style.Value))
{
value = GetString(ReadStyle(style.Value), "text-anchor");
}
}
}
switch (value)
{
case "end":
return SKTextAlign.Right;
case "middle":
return SKTextAlign.Center;
default:
return SKTextAlign.Left;
}
}
private readonly Dictionary<string, SKPaint> linearGradients = new Dictionary<string, SKPaint>();
private SKShader ReadGradient(XElement defE)
{
switch (defE.Name.LocalName)
{
case "linearGradient":
return ReadLinearGradient(defE);
case "radialGradient":
return ReadRadialGradient(defE);
}
return null;
}
private SKShader ReadRadialGradient(XElement e)
{
var centerX = ReadNumber(e.Attribute("cx"));
var centerY = ReadNumber(e.Attribute("cy"));
//var focusX = ReadOptionalNumber(e.Attribute("fx")) ?? centerX;
//var focusY = ReadOptionalNumber(e.Attribute("fy")) ?? centerY;
var radius = ReadNumber(e.Attribute("r"));
var absolute = e.Attribute("gradientUnits")?.Value == "userSpaceOnUse";
var tileMode = ReadSpreadMethod(e);
var stops = ReadStops(e);
// TODO: check gradientTransform attribute
// TODO: use absolute
return SKShader.CreateRadialGradient(
new SKPoint(centerX, centerY),
radius,
stops.Values.ToArray(),
stops.Keys.ToArray(),
tileMode);
}
private SKShader ReadLinearGradient(XElement e)
{
var startX = ReadNumber(e.Attribute("x1"));
var startY = ReadNumber(e.Attribute("y1"));
var endX = ReadNumber(e.Attribute("x2"));
var endY = ReadNumber(e.Attribute("y2"));
var absolute = e.Attribute("gradientUnits")?.Value == "userSpaceOnUse";
var tileMode = ReadSpreadMethod(e);
var stops = ReadStops(e);
// TODO: check gradientTransform attribute
// TODO: use absolute
return SKShader.CreateLinearGradient(
new SKPoint(startX, startY),
new SKPoint(endX, endY),
stops.Values.ToArray(),
stops.Keys.ToArray(),
tileMode);
}
private static SKShaderTileMode ReadSpreadMethod(XElement e)
{
var repeat = e.Attribute("spreadMethod")?.Value;
switch (repeat)
{
case "reflect":
return SKShaderTileMode.Mirror;
case "repeat":
return SKShaderTileMode.Repeat;
case "pad":
default:
return SKShaderTileMode.Clamp;
}
}
private XElement ReadDefinition(XElement e)
{
var union = new XElement(e.Name);
union.Add(e.Elements());
union.Add(e.Attributes());
var child = ReadHref(e);
if (child != null)
{
union.Add(child.Elements());
union.Add(child.Attributes().Where(a => union.Attribute(a.Name) == null));
}
return union;
}
private XElement ReadHref(XElement e)
{
var href = e.Attribute(xlink + "href")?.Value?.Substring(1);
XElement child;
if (string.IsNullOrEmpty(href) || !defs.TryGetValue(href, out child))
{
child = null;
}
return child;
}
private SortedDictionary<float, SKColor> ReadStops(XElement e)
{
var stops = new SortedDictionary<float, SKColor>();
var ns = e.Name.Namespace;
foreach (var se in e.Elements(ns + "stop"))
{
var style = ReadStyle(se);
var offset = ReadNumber(style["offset"]);
var color = SKColors.Black;
byte alpha = 255;
string stopColor;
if (style.TryGetValue("stop-color", out stopColor))
{
// preserve alpha
if (SKColor.TryParse(stopColor, out color) && color.Alpha == 255)
alpha = color.Alpha;
}
string stopOpacity;
if (style.TryGetValue("stop-opacity", out stopOpacity))
{
alpha = (byte)(ReadNumber(stopOpacity) * 255);
}
color = color.WithAlpha(alpha);
stops[offset] = color;
}
return stops;
}
private float ReadNumber(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
return 0;
var s = raw.Trim();
var m = 1.0f;
if (unitRe.IsMatch(s))
{
if (s.EndsWith("in", StringComparison.Ordinal))
{
m = PixelsPerInch;
}
else if (s.EndsWith("cm", StringComparison.Ordinal))
{
m = PixelsPerInch / 2.54f;
}
else if (s.EndsWith("mm", StringComparison.Ordinal))
{
m = PixelsPerInch / 25.4f;
}
else if (s.EndsWith("pt", StringComparison.Ordinal))
{
m = PixelsPerInch / 72.0f;
}
else if (s.EndsWith("pc", StringComparison.Ordinal))
{
m = PixelsPerInch / 6.0f;
}
s = s.Substring(0, s.Length - 2);
}
else if (percRe.IsMatch(s))
{
s = s.Substring(0, s.Length - 1);
m = 0.01f;
}
float v;
if (!float.TryParse(s, NumberStyles.Float, icult, out v))
{
v = 0;
}
return m * v;
}
private float ReadNumber(XAttribute a) => ReadNumber(a?.Value);
private float? ReadOptionalNumber(XAttribute a) => a == null ? (float?)null : ReadNumber(a.Value);
private SKRect ReadRectangle(string s)
{
var r = new SKRect();
var p = s.Split(WS, StringSplitOptions.RemoveEmptyEntries);
if (p.Length > 0)
r.Left = ReadNumber(p[0]);
if (p.Length > 1)
r.Top = ReadNumber(p[1]);
if (p.Length > 2)
r.Right = r.Left + ReadNumber(p[2]);
if (p.Length > 3)
r.Bottom = r.Top + ReadNumber(p[3]);
return r;
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{04C4399A-6740-4733-B6B7-F968232A76C8}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SkiaSharp</RootNamespace>
<AssemblyName>SkiaSharp.Svg</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile78</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<ProjectReference Include="..\..\..\Binding\SkiaSharp.Portable\SkiaSharp.Portable.csproj">
<Project>{7aa90628-2fdd-4585-af2f-cc51cfa8b52a}</Project>
<Name>SkiaSharp.Portable</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\SkiaSharpSvgAssemblyInfo.cs" />
<Compile Include="SKSvg.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -43,6 +43,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SkiaSharp", "SkiaSharp", "{
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SkiaSharp.Views", "SkiaSharp.Views", "{F19E1537-81B2-4D4F-A69E-78DC73ACC141}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SkiaSharp.Svg", "SkiaSharp.Svg", "{F9C7D51F-468C-4E58-BB75-2317DB99C8A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Svg", "SkiaSharp.Svg\SkiaSharp.Svg\SkiaSharp.Svg.csproj", "{04C4399A-6740-4733-B6B7-F968232A76C8}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
SkiaSharp.Views.Forms\SkiaSharp.Views.Forms.Shared\SkiaSharp.Views.Forms.Shared.projitems*{1555d119-8598-4e4d-91ac-d313f94a1673}*SharedItemsImports = 4
@ -124,6 +128,10 @@ Global
{7AA90628-2FDD-4585-AF2F-CC51CFA8B52A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7AA90628-2FDD-4585-AF2F-CC51CFA8B52A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AA90628-2FDD-4585-AF2F-CC51CFA8B52A}.Release|Any CPU.Build.0 = Release|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -143,5 +151,6 @@ Global
{5180E370-A455-42BB-99F9-97BD269B8A52} = {C335869B-7CC8-4239-B4A5-8031AA9758D3}
{4588A759-3853-49B8-8A68-6C7917BE9220} = {C335869B-7CC8-4239-B4A5-8031AA9758D3}
{7AA90628-2FDD-4585-AF2F-CC51CFA8B52A} = {C335869B-7CC8-4239-B4A5-8031AA9758D3}
{04C4399A-6740-4733-B6B7-F968232A76C8} = {F9C7D51F-468C-4E58-BB75-2317DB99C8A7}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -31,6 +31,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SkiaSharp.Views", "SkiaShar
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Views.WPF", "SkiaSharp.Views\SkiaSharp.Views.WPF\SkiaSharp.Views.WPF.csproj", "{743CF830-D458-41A9-865A-F85126562015}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SkiaSharp.Svg", "SkiaSharp.Svg", "{F9C7D51F-468C-4E58-BB75-2317DB99C8A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Svg", "SkiaSharp.Svg\SkiaSharp.Svg\SkiaSharp.Svg.csproj", "{04C4399A-6740-4733-B6B7-F968232A76C8}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
SkiaSharp.Views.Forms\SkiaSharp.Views.Forms.Shared\SkiaSharp.Views.Forms.Shared.projitems*{1555d119-8598-4e4d-91ac-d313f94a1673}*SharedItemsImports = 4
@ -82,6 +86,10 @@ Global
{743CF830-D458-41A9-865A-F85126562015}.Debug|Any CPU.Build.0 = Debug|Any CPU
{743CF830-D458-41A9-865A-F85126562015}.Release|Any CPU.ActiveCfg = Release|Any CPU
{743CF830-D458-41A9-865A-F85126562015}.Release|Any CPU.Build.0 = Release|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -96,5 +104,6 @@ Global
{EB1BBDCC-FB07-40D5-8B9E-0079E2C2F2DF} = {C335869B-7CC8-4239-B4A5-8031AA9758D3}
{BAB615AA-956E-4079-B260-DD7B1F52EC7D} = {C335869B-7CC8-4239-B4A5-8031AA9758D3}
{743CF830-D458-41A9-865A-F85126562015} = {F19E1537-81B2-4D4F-A69E-78DC73ACC141}
{04C4399A-6740-4733-B6B7-F968232A76C8} = {F9C7D51F-468C-4E58-BB75-2317DB99C8A7}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -57,6 +57,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Views.WPF", "Skia
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SkiaSharp.Views.Forms", "SkiaSharp.Views.Forms", "{DCADA8CC-D50A-4BD9-B2E6-86696A43D819}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SkiaSharp.Svg", "SkiaSharp.Svg", "{F9C7D51F-468C-4E58-BB75-2317DB99C8A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Svg", "SkiaSharp.Svg\SkiaSharp.Svg\SkiaSharp.Svg.csproj", "{04C4399A-6740-4733-B6B7-F968232A76C8}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
SkiaSharp.Views.Forms\SkiaSharp.Views.Forms.Shared\SkiaSharp.Views.Forms.Shared.projitems*{1555d119-8598-4e4d-91ac-d313f94a1673}*SharedItemsImports = 4
@ -168,6 +172,10 @@ Global
{743CF830-D458-41A9-865A-F85126562015}.Debug|Any CPU.Build.0 = Debug|Any CPU
{743CF830-D458-41A9-865A-F85126562015}.Release|Any CPU.ActiveCfg = Release|Any CPU
{743CF830-D458-41A9-865A-F85126562015}.Release|Any CPU.Build.0 = Release|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04C4399A-6740-4733-B6B7-F968232A76C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -197,5 +205,6 @@ Global
{EB1BBDCC-FB07-40D5-8B9E-0079E2C2F2DF} = {C335869B-7CC8-4239-B4A5-8031AA9758D3}
{BAB615AA-956E-4079-B260-DD7B1F52EC7D} = {C335869B-7CC8-4239-B4A5-8031AA9758D3}
{743CF830-D458-41A9-865A-F85126562015} = {F19E1537-81B2-4D4F-A69E-78DC73ACC141}
{04C4399A-6740-4733-B6B7-F968232A76C8} = {F9C7D51F-468C-4E58-BB75-2317DB99C8A7}
EndGlobalSection
EndGlobal