本文共 14578 字,大约阅读时间需要 48 分钟。
Xamarin版的C# SVG路径解析器,对SVG的Path路径进行解析,其中包括:
主程序SvgPathParser.cs,
相关接口定义:ISourceFormatter.cs,
辅助类:FormatterRocks.cs,
从接口派生的CSharpCoreGraphicsFormatter.cs。
//主程序SvgPathParser.cs:
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>//// Copyright 2012-2013 Xamarin Inc.//// This file is mostly based on the C++ code from once magnificent Moonlight// https://github.com/mono/moon/blob/master/src/xaml.cpp// Copyright 2007 Novell, Inc. (http://www.novell.com)//// Licensed under the GNU LGPL 2 license only (no "later versions")using System;using System.Collections.Generic;using System.Drawing;using System.Globalization;namespace Poupou.SvgPathConverter { public class SvgPathParser { static int i; public ISourceFormatter Formatter { get; set; } public void Parse (string svgPath, string name = null) { if (Formatter == null) throw new InvalidOperationException ("Missing formatter"); if (name == null) name = "Unnamed_" + (++i).ToString (); Parse (svgPath, name, Formatter); } static void Advance (string s, ref int pos) { if (pos >= s.Length) return; char c = s [pos]; while (!Char.IsLetterOrDigit (c) && c != '.' && c!= '-' && c != '+') { if (++pos == s.Length) return; c = s [pos]; } } static int FindNonFloat (string s, int pos) { char c = s [pos]; while ((Char.IsNumber (c) || c == '.' || c == '-' || c == '+')) { if (++pos == s.Length) return pos; c = s [pos]; } return pos; } static bool MorePointsAvailable (string s, int pos) { if (pos >= s.Length) return false; char c = s [pos]; while (Char.IsWhiteSpace (c) || c == ',') c = s [++pos]; return Char.IsDigit (c) || c == '.' || c == '-' || c == '+'; } static float GetFloat (string svg, ref int pos) { int end = FindNonFloat (svg, pos); string s = svg.Substring (pos, end - pos); float f = Single.Parse (s, CultureInfo.InvariantCulture); pos = end; return f; } static PointF GetPoint (string svg, ref int pos) { while (Char.IsWhiteSpace (svg [pos])) pos++; float x = GetFloat (svg, ref pos); while (Char.IsWhiteSpace (svg [pos])) pos++; if (svg [pos] == ',') pos++; while (Char.IsWhiteSpace (svg [pos])) pos++; float y = GetFloat (svg, ref pos); return new PointF (x, y); } static PointF MakeRelative (PointF c, PointF m) { return new PointF (m.X + c.X, m.Y + c.Y); } static void Parse (string svg, string name, ISourceFormatter formatter) { formatter.Prologue (name); PointF start; PointF cp = new PointF (0, 0); PointF cp1, cp2, cp3; PointF qbzp, cbzp; int fill_rule = 0; int pos = 0; bool cbz = false; bool qbz = false; while (pos < svg.Length) { char c = svg [pos++]; if (Char.IsWhiteSpace (c)) continue; bool relative = false; switch (c) { case 'f': case 'F': c = svg [pos++]; if (c == '0') fill_rule = 0; else if (c == '1') fill_rule = 1; else throw new FormatException (); break; case 'h': relative = true; goto case 'H'; case 'H': float x = GetFloat (svg, ref pos); if (relative) x += cp.X; cp = new PointF (x, cp.Y); formatter.LineTo (cp); cbz = qbz = false; break; case 'm': relative = true; goto case 'M'; case 'M': cp1 = GetPoint (svg, ref pos); if (relative) cp1 = MakeRelative (cp, cp1); formatter.MoveTo (cp1); start = cp = cp1; Advance (svg, ref pos); while (MorePointsAvailable (svg, pos)) { cp1 = GetPoint (svg, ref pos); if (relative) cp1 = MakeRelative (cp, cp1); formatter.LineTo (cp1); } cp = cp1; cbz = qbz = false; break; case 'l': relative = true; goto case 'L'; case 'L': while (MorePointsAvailable (svg, pos)) { cp1 = GetPoint (svg, ref pos); if (relative) cp1 = MakeRelative (cp, cp1); Advance (svg, ref pos); formatter.LineTo (cp1); cp = cp1; } cbz = qbz = false; break; case 'a': relative = true; goto case 'A'; case 'A': while (MorePointsAvailable (svg, pos)) { cp1 = GetPoint (svg, ref pos); // this is a width and height so it's not made relative to cp Advance (svg, ref pos); float angle = GetFloat (svg, ref pos); Advance (svg, ref pos); bool is_large = GetFloat (svg, ref pos) != 0.0f; Advance (svg, ref pos); bool positive_sweep = GetFloat (svg, ref pos) != 0.0f; Advance (svg, ref pos); cp2 = GetPoint (svg, ref pos); if (relative) cp2 = MakeRelative (cp, cp2); Advance (svg, ref pos); formatter.ArcTo (cp1, angle, is_large, positive_sweep, cp2, cp); cp = cp2; Advance (svg, ref pos); } qbz = false; cbz = false; break; case 'q': relative = true; goto case 'Q'; case 'Q': while (MorePointsAvailable (svg, pos)) { cp1 = GetPoint (svg, ref pos); if (relative) cp1 = MakeRelative (cp, cp1); Advance (svg, ref pos); cp2 = GetPoint (svg, ref pos); if (relative) cp2 = MakeRelative (cp, cp2); Advance (svg, ref pos); formatter.QuadCurveTo (cp1, cp2); cp = cp2; Advance (svg, ref pos); } qbz = true; qbzp = cp1; cbz = false; break; case 'c': relative = true; goto case 'C'; case 'C': while (MorePointsAvailable (svg, pos)) { cp1 = GetPoint (svg, ref pos); if (relative) cp1 = MakeRelative (cp, cp1); Advance (svg, ref pos); cp2 = GetPoint (svg, ref pos); if (relative) cp2 = MakeRelative (cp, cp2); Advance (svg, ref pos); cp3 = GetPoint (svg, ref pos); if (relative) cp3 = MakeRelative (cp, cp3); Advance (svg, ref pos); formatter.CurveTo (cp1, cp2, cp3); cp1 = cp3; } cp = cp3; cbz = true; cbzp = cp2; qbz = false; break; case 't': relative = true; goto case 'T'; case 'T': while (MorePointsAvailable (svg, pos)) { cp2 = GetPoint (svg, ref pos); if (relative) cp2 = MakeRelative (cp, cp2); if (qbz) { cp1.X = 2 * cp.X - qbzp.X; cp1.Y = 2 * cp.Y - qbzp.Y; } else { cp1 = cp; } formatter.QuadCurveTo (cp1, cp2); qbz = true; qbzp = cp1; cp = cp2; Advance (svg, ref pos); } cbz = false; break; case 's': relative = true; goto case 'S'; case 'S': while (MorePointsAvailable (svg, pos)) { cp2 = GetPoint (svg, ref pos); if (relative) cp2 = MakeRelative (cp, cp2); Advance (svg, ref pos); cp3 = GetPoint (svg, ref pos); if (relative) cp3 = MakeRelative (cp, cp3); if (cbz) { cp1.X = 2 * cp.X - cbzp.X; cp1.Y = 2 * cp.Y - cbzp.Y; } else { cp1 = cp; } formatter.CurveTo (cp1, cp2, cp3); cbz = true; cbzp = cp2; cp = cp3; Advance (svg, ref pos); } qbz = false; break; case 'v': relative = true; goto case 'V'; case 'V': float y = GetFloat (svg, ref pos); if (relative) y += cp.Y; cp = new PointF (cp.X, y); formatter.LineTo (cp); cbz = qbz = false; break; case 'z': case 'Z': formatter.ClosePath (); formatter.MoveTo (start); cp = start; cbz = qbz = false; break; default: throw new FormatException (c.ToString ()); } } formatter.Epilogue (); } }}
//相关接口定义:ISourceFormatter.cs
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>//// Copyright 2012 Xamarin Inc.//// Licensed under the GNU LGPL 2 license only (no "later versions")using System;using System.Drawing;namespace Poupou.SvgPathConverter { public interface ISourceFormatter { void Prologue (string name); void Epilogue (); void MoveTo (PointF pt); void LineTo (PointF pt); void QuadCurveTo (PointF pt1, PointF pt2); void CurveTo (PointF pt1, PointF pt2, PointF pt3); void ArcTo (PointF size, float angle, bool isLarge, bool sweep, PointF ep, PointF sp); void ClosePath (); }}//辅助类:FormatterRocks.cs
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>//// Copyright 2012 Xamarin Inc.//// This file is mostly based on the C++ code from once magnificent Moonlight// https://github.com/mono/moon/blob/master/src/moon-path.cpp// Copyright 2007-2008 Novell, Inc. (http://www.novell.com)//// Licensed under the GNU LGPL 2 license only (no "later versions")using System;using System.Drawing;using System.IO;namespace Poupou.SvgPathConverter { public static class FormatterRocks { static bool IsNearZero (float value) { return Math.Abs (value) < 0.000019; } // The SVG Arc is a bit more complex than others - and also quite different than many existing API // This implementation will use a ISourceFormatter's CurveTo method to draw the arc public static void ArcHelper (this ISourceFormatter formatter, PointF size, float anglef, bool isLarge, bool sweep, PointF endPoint, PointF startPoint) { if (IsNearZero (endPoint.X - startPoint.X) && IsNearZero (endPoint.Y - startPoint.Y)) return; // Correction of out-of-range radii, see F6.6 (step 1) if (IsNearZero (size.X) || IsNearZero (size.Y)) { // treat this as a straight line (to end point) formatter.LineTo (endPoint); return; } // Correction of out-of-range radii, see F6.6.1 (step 2) float rx = Math.Abs (size.X); float ry = Math.Abs (size.Y); // convert angle into radians double angle = anglef * Math.PI / 180.0f; // variables required for F6.3.1 double cos_phi = Math.Cos (angle); double sin_phi = Math.Sin (angle); double dx2 = (startPoint.X - endPoint.X) / 2.0; double dy2 = (startPoint.Y - endPoint.Y) / 2.0; double x1p = cos_phi * dx2 + sin_phi * dy2; double y1p = cos_phi * dy2 - sin_phi * dx2; double x1p2 = x1p * x1p; double y1p2 = y1p * y1p; float rx2 = rx * rx; float ry2 = ry * ry; // Correction of out-of-range radii, see F6.6.2 (step 4) double lambda = (x1p2 / rx2) + (y1p2 / ry2); if (lambda > 1.0) { // see F6.6.3 float lambda_root = (float) Math.Sqrt (lambda); rx *= lambda_root; ry *= lambda_root; // update rx2 and ry2 rx2 = rx * rx; ry2 = ry * ry; } double cxp, cyp, cx, cy; double c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2); // check if there is no possible solution (i.e. we can't do a square root of a negative value) if (c < 0.0) { // scale uniformly until we have a single solution (see F6.2) i.e. when c == 0.0 float scale = (float) Math.Sqrt (1.0 - c / (rx2 * ry2)); rx *= scale; ry *= scale; // update rx2 and ry2 rx2 = rx * rx; ry2 = ry * ry; // step 2 (F6.5.2) - simplified since c == 0.0 cxp = 0.0; cyp = 0.0; // step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0 cx = 0.0; cy = 0.0; } else { // complete c calculation c = Math.Sqrt (c / ((rx2 * y1p2) + (ry2 * x1p2))); // inverse sign if Fa == Fs if (isLarge == sweep) c = -c; // step 2 (F6.5.2) cxp = c * ( rx * y1p / ry); cyp = c * (-ry * x1p / rx); // step 3 (F6.5.3 first part) cx = cos_phi * cxp - sin_phi * cyp; cy = sin_phi * cxp + cos_phi * cyp; } // step 3 (F6.5.3 second part) we now have the center point of the ellipse cx += (startPoint.X + endPoint.X) / 2.0; cy += (startPoint.Y + endPoint.Y) / 2.0; // step 4 (F6.5.4) // we dont' use arccos (as per w3c doc), see http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm // note: atan2 (0.0, 1.0) == 0.0 double at = Math.Atan2 (((y1p - cyp) / ry), ((x1p - cxp) / rx)); double theta1 = (at < 0.0) ? 2.0 * Math.PI + at : at; double nat = Math.Atan2 (((-y1p - cyp) / ry), ((-x1p - cxp) / rx)); double delta_theta = (nat < at) ? 2.0 * Math.PI - at + nat : nat - at; if (sweep) { // ensure delta theta < 0 or else add 360 degrees if (delta_theta < 0.0) delta_theta += 2.0 * Math.PI; } else { // ensure delta theta > 0 or else substract 360 degrees if (delta_theta > 0.0) delta_theta -= 2.0 * Math.PI; } // add several cubic bezier to approximate the arc (smaller than 90 degrees) // we add one extra segment because we want something smaller than 90deg (i.e. not 90 itself) int segments = (int) (Math.Abs (delta_theta / Math.PI)) + 1; double delta = delta_theta / segments; // http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13) float bcp = (float) (4.0 / 3 * (1 - Math.Cos (delta / 2)) / Math.Sin (delta / 2)); double cos_phi_rx = cos_phi * rx; double cos_phi_ry = cos_phi * ry; double sin_phi_rx = sin_phi * rx; double sin_phi_ry = sin_phi * ry; double cos_theta1 = Math.Cos (theta1); double sin_theta1 = Math.Sin (theta1); PointF c1, c2; int i; for (i = 0; i < segments; ++i) { // end angle (for this segment) = current + delta double theta2 = theta1 + delta; double cos_theta2 = Math.Cos (theta2); double sin_theta2 = Math.Sin (theta2); // first control point (based on start point sx,sy) c1.X = startPoint.X - bcp * (float) (cos_phi_rx * sin_theta1 + sin_phi_ry * cos_theta1); c1.Y = startPoint.Y + bcp * (float) (cos_phi_ry * cos_theta1 - sin_phi_rx * sin_theta1); // end point (for this segment) endPoint.X = (float) (cx + (cos_phi_rx * cos_theta2 - sin_phi_ry * sin_theta2)); endPoint.Y = (float) (cy + (sin_phi_rx * cos_theta2 + cos_phi_ry * sin_theta2)); // second control point (based on end point ex,ey) c2.X = endPoint.X + bcp * (float) (cos_phi_rx * sin_theta2 + sin_phi_ry * cos_theta2); c2.Y = endPoint.Y + bcp * (float) (sin_phi_rx * sin_theta2 - cos_phi_ry * cos_theta2); formatter.CurveTo (c1, c2, endPoint); // next start point is the current end point (same for angle) startPoint = endPoint; theta1 = theta2; // avoid recomputations cos_theta1 = cos_theta2; sin_theta1 = sin_theta2; } } }}// 从接口派生的CSharpCoreGraphicsFormatter.cs
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>//// Copyright 2012-2013 Xamarin Inc.//// Licensed under the GNU LGPL 2 license only (no "later versions")using System;using System.Drawing;using System.Globalization;using System.IO;namespace Poupou.SvgPathConverter { public class CSharpCoreGraphicsFormatter : ISourceFormatter { TextWriter writer; public CSharpCoreGraphicsFormatter (TextWriter textWriter) { writer = textWriter; } public void Prologue (string name) { writer.WriteLine ("\tstatic void {0} (CGContext c)", name); writer.WriteLine ("\t{"); } public void Epilogue () { writer.WriteLine ("\t\tc.FillPath ();"); writer.WriteLine ("\t\tc.StrokePath ();"); writer.WriteLine ("\t}"); writer.WriteLine (); } public void MoveTo (PointF pt) { writer.WriteLine ("\t\tc.MoveTo ({0}f, {1}f);", pt.X.ToString (CultureInfo.InvariantCulture), pt.Y.ToString (CultureInfo.InvariantCulture)); } public void LineTo (PointF pt) { writer.WriteLine ("\t\tc.AddLineToPoint ({0}f, {1}f);", pt.X.ToString (CultureInfo.InvariantCulture), pt.Y.ToString (CultureInfo.InvariantCulture)); } public void ClosePath () { writer.WriteLine ("\t\tc.ClosePath ();"); } public void QuadCurveTo (PointF pt1, PointF pt2) { writer.WriteLine ("\t\tc.AddQuadCurveToPoint ({0}f, {1}f, {2}f, {3}f);", pt1.X.ToString (CultureInfo.InvariantCulture), pt1.Y.ToString (CultureInfo.InvariantCulture), pt2.X.ToString (CultureInfo.InvariantCulture), pt2.Y.ToString (CultureInfo.InvariantCulture)); } public void CurveTo (PointF pt1, PointF pt2, PointF pt3) { writer.WriteLine ("\t\tc.AddCurveToPoint ({0}f, {1}f, {2}f, {3}f, {4}f, {5}f);", pt1.X.ToString (CultureInfo.InvariantCulture), pt1.Y.ToString (CultureInfo.InvariantCulture), pt2.X.ToString (CultureInfo.InvariantCulture), pt2.Y.ToString (CultureInfo.InvariantCulture), pt3.X.ToString (CultureInfo.InvariantCulture), pt3.Y.ToString (CultureInfo.InvariantCulture)); } public void ArcTo (PointF size, float angle, bool isLarge, bool sweep, PointF endPoint, PointF startPoint) { this.ArcHelper (size, angle, isLarge, sweep, endPoint, startPoint); } }}转载地址:http://vtrdx.baihongyu.com/