Xamarin版的C# SVG路径解析器

原文:Xamarin版的C# SVG路径解析器

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);
        }
    }
}

上一篇:Linux 添加新磁盘,在线扩充空间


下一篇:JS代码片段:一个日期离现在多久了