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);
}
}
}
Xamarin版的C# SVG路径解析器的更多相关文章
- SpringMVC源码情操陶冶-AnnotationDrivenBeanDefinitionParser注解解析器
mvc:annotation-driven节点的解析器,是springmvc的核心解析器 官方注释 Open Declaration org.springframework.web.servlet.c ...
- 自制C#版3DS文件的解析器并用SharpGL显示3DS模型
自制C#版3DS文件的解析器并用SharpGL显示3DS模型 我已经重写了3ds解析器,详情在此(http://www.cnblogs.com/bitzhuwei/p/CSharpGL-2-parse ...
- Windows使用Python统一设置解析器路径
碰到的问题: .py文件放在cgi-bin文件夹下面,这个.py文件都要设置"#!python.exe路径"来告诉CGI如何找解析器解析这个.py的文件,我是想知道这个路径可否统一 ...
- TinyXML:一个优秀的C++ XML解析器
//-------------------------------------------------------------------------------------------------- ...
- dom解析器机制 web基本概念 tomcat
0 作业[cn.itcast.xml.sax.Demo2] 1)在SAX解析器中,一定要知道每方法何时执行,及SAX解析器会传入的参数含义 1 理解dom解析器机制 1)dom解析和dom4j原理 ...
- SVG 路径(path)
本文转自:https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths <path>元素是SVG基本形状中最强大的一个,它 ...
- boost之词法解析器spirit
摘要:解析器就是编译原理中的语言的词法分析器,可以按照文法规则提取字符或者单词.功能:接受扫描器的输入,并根据语法规则对输入流进行匹配,匹配成功后执行语义动作,进行输入数据的处理. C++ 程序员需要 ...
- 【swupdate文档 四】SWUpdate:使用默认解析器的语法和标记
SWUpdate:使用默认解析器的语法和标记 介绍 SWUpdate使用库"libconfig"作为镜像描述的默认解析器. 但是,可以扩展SWUpdate并添加一个自己的解析器, ...
- SpringMVC视图解析器
SpringMVC视图解析器 前言 在前一篇博客中讲了SpringMVC的Controller控制器,在这篇博客中将接着介绍一下SpringMVC视 图解析器.当我们对SpringMVC控制的资源发起 ...
随机推荐
- Confluence 6 嵌套用户组的影响
本部分说明了嵌套用户组对用户登录,权限和查看更新用户组的影响. 登录 如果用户属于一个授权的用户组或者授权用户组中的子用户组,当用户登录后,用户可以访问应用程序. 权限 如果用户属于的用户组或者用户组 ...
- IE6不兼容hover已解决
新建一个csshover.htc文件,一下是csshover.htc内容 <public:attach event="ondocumentready" onevent=&qu ...
- hpu1165 贪心
1165: 最少的需求 [贪心] 时间限制: 1 Sec 内存限制: 128 MB 提交: 12 解决: 4 状态 题目描述 小Q开了一家餐厅,最近生意非常火爆. 假设有N N 个预订信息,第i i ...
- nyoj860(01变形)
http://acm.nyist.net/JudgeOnline/problem.php?pid=860 又见01背包 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 ...
- 原生JS和jQuery版实现文件上传功能
<!doctype html> <html lang="zh"> <head> <meta charset="utf-8&quo ...
- JavaScript学习总结(十八)——JavaScript获取浏览器类型与版本
从网上找到一段使用JavaScript判断浏览器以及浏览器版本的比较好的代码,在此记录一下: 1 <script type="text/javascript"> 2 v ...
- spring boot 学习(五)SpringBoot+MyBatis(XML)+Druid
SpringBoot+MyBatis(xml)+Druid 前言 springboot集成了springJDBC与JPA,但是没有集成mybatis,所以想要使用mybatis就要自己去集成. 主要是 ...
- PHP:第三章——PHP中返回引用的函数
<?php header("Content-Type:text/html;charset=utf-8"); $i=1; function &F(){ global $ ...
- js通过class获取元素
<!doctype html> <html> <head> <meta charset="utf-8"> <meta char ...
- Android SharedPreferences一般的读写 的用法。
Android SharedPreferences一般用于轻量级的数据存储,比如用户名和密码等. package com.lixu.testsharepreferences; import andro ...