本文將介紹如何將OpenXml的actTo轉為Svg的弧線(a)
OpenXml的artTo
首先下面是一段OpenXml的arcTo弧線
<arcTo wR="152403" hR="152403" stAng="cd4" swAng="-5400000" />
假設我們當前的點是(0,0),這時候我們已知的資訊如下:
- 當前點座標:(x1,y1)=(0,0)
- 橢圓的半徑:半長軸 rx=wR=152403,半短軸 ry=hR=152403
- 起始角到結束角的夾角:起始角θ1=stAng=cd4,夾角Δθ=swAng,結束角θ2=θ1+Δθ
- 是否優(大)弧:fA=|Δθ|>Π(180°)
- 順逆時針:fS=|Δθ|>0°
目前Svg的橢圓弧線引數字串為以下:
a rx ry x-axis-rotation large-arc-flag sweep-flag x y
其中涉及到的引數:
引數 | 說明 | 備註 |
---|---|---|
rx | 橢圓半長軸 | 已知:rx=wR=152403 |
ry | 橢圓半短軸 | 已知:ry=hR=152403 |
x-axis-rotation | 橢圓相對於座標系的旋轉角度,角度數而非弧度數 | 已知:0 |
large-arc-flag | 是否優(大)弧:0否,1是 | 已知:fA=|Δθ|>Π(180°) |
sweep-flag | 繪製方向:0逆時針,1順時針 | 已知:fS=|Δθ|>0° |
x | 圓弧終點的x座標 | 未知 |
y | 圓弧終點的y座標 | 未知 |
因此實際上,我們需要求出的則是圓弧終點座標就能夠完成最終換算到Svg橢圓弧線字串了
求橢圓弧上任意一點的二維矩陣方程式
以下是我從W3C的SVG官方文件中獲取到的關於橢圓任意一點的二維矩陣方程式:
因此的存在以下兩個(開始點和終點)橢圓任意一點的二維矩陣方程式:
其中涉及到的引數:
引數 | 說明 | 備註 |
---|---|---|
(x1,y1) | 當前座標 | 已知:(0,0) |
(x2,y2) | 終點座標 | 未知 |
φ | 橢圓相對於座標系的旋轉角度 | 已知:0° |
θ1 | 起始角 | 已知:stAng |
Δθ | 起始角到結束角的夾角 | 已知:swAng |
(cx,cy) | 橢圓中心座標點 | 未知 |
fA | 是否優(大)弧 | 已知:fA=|Δθ|>Π(180°) |
fS | 繪製方向 | 已知:fS=Δθ>0° |
因此推導公式如下:
步驟1:
因為開始點的橢圓任意一點的二維矩陣方程式為
所以能夠得出兩行一列矩陣CxCy為:
步驟2:
因為終點的橢圓任意一點的二維矩陣方程式為
因此將矩陣CxCy帶入到終點點的橢圓任意一點的二維矩陣方程式:
程式碼部分
在寫程式碼之前,我們需要安裝一些所需要用到的庫,Openxml單位換算為Pixel的庫和矩陣運算用到的庫:
通過nuget包的控制檯執行以下命令:
Openxml單位換算庫
Install-Package dotnetCampus.OpenXmlUnitConverter -Version 1.5.1
矩陣運算庫
Install-Package MathNet.Numerics -Version 5.0.0-alpha02
然後正式開始我們的程式碼,我們通過WPF應用窗體來展示效果:
前端xaml程式碼:
<Window x:Class="OpenxmlActToSvgSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OpenxmlActToSvgSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Path x:Name="Path" Stroke="Blue" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Window>
後端cs程式碼:
public MainWindow()
{
InitializeComponent();
//Openxml的360° circle
const double c = 21600000d;
//circle divide 4
var cd4 = c / 4;
//<arcTo wR="152403" hR="152403" stAng="cd4" swAng="-5400000" />
var wR = 152403;
var hR = 152403;
var stAng = cd4;
var swAng = -5400000d;
StringBuilder stringPath=new StringBuilder();
var currentPoint=new Point(0, 0);
stringPath.Append($"M {currentPoint.X} {currentPoint.Y}");
ParseOpenxmlArcTo(stringPath, wR, hR, stAng, swAng, currentPoint);
this.Path.Data=Geometry.Parse(stringPath.ToString());
}
private Point ParseOpenxmlArcTo(StringBuilder stringPath, double wR, double hR, double stAng, double swAng, Point currentPoint)
{
const string comma = ",";
//將Openxml的角度轉為真實的角度
var θ1 = new Angle((int)stAng).ToRadiansValue();
var Δθ = new Angle((int)swAng).ToRadiansValue();
//旋轉角
var φ = 0d;
//是否是大弧
var isLargeArcFlag = Math.Abs(Δθ) > Math.PI;
//是否是順時針
var isClockwise = Δθ > 0;
var rx = new Emu(wR).ToPixel().Value;
var ry = new Emu(hR).ToPixel().Value;
//獲取終點座標
var pt = GetArBitraryPoint(rx, ry, swAng, stAng, φ, currentPoint);
currentPoint = pt;
// 格式如下
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
// 這裡 large-arc-flag 是 1 和 0 表示
stringPath.Append("A")
.Append(rx) //rx
.Append(comma)
.Append(ry) //ry
.Append(comma)
.Append(φ) // x-axis-rotation
.Append(comma)
.Append(isLargeArcFlag ? "1" : "0") //large-arc-flag
.Append(comma)
.Append(isClockwise ? "1" : "0") // sweep-flag
.Append(comma)
.Append(pt.X)
.Append(comma)
.Append(pt.Y)
.Append(' ');
return currentPoint;
}
/// <summary>
/// 獲取橢圓任意一點座標
/// </summary>
/// <returns></returns>
private static Point GetArBitraryPoint(double rx, double ry, double Δθ, double θ1, double φ, Point currentPoint)
{
//開始點的橢圓任意一點的二維矩陣方程式
var matrixX1Y1 = DenseMatrix.OfArray(new double[2, 1]
{
{ currentPoint.X},
{ currentPoint.Y}
});
var matrix1 = DenseMatrix.OfArray(new double[2, 2]
{
{ Math.Cos(φ),-Math.Sin(φ)},
{ Math.Sin(φ),Math.Cos(φ)}
});
var matrix2 = DenseMatrix.OfArray(new double[2, 1]
{
{ rx*Math.Cos(θ1)},
{ ry*Math.Sin(θ1)}
});
var matrixCxCy = matrixX1Y1 - (matrix1 * matrix2);
//終點的橢圓任意一點的二維矩陣方程式
var matrix3 = DenseMatrix.OfArray(new double[2, 1]
{
{ rx*Math.Cos(θ1+Δθ)},
{ ry*Math.Sin(θ1+Δθ)}
});
var matrixX2Y2 = matrix1 * matrix3 + matrixCxCy;
return new Point(matrixX2Y2.Values[0], matrixX2Y2.Values[1]);
}
效果如下:
可以看到,我們成功的繪製出我們的一條橢圓弧線,雖然很簡單,但是其實這條弧線是我取ppt形狀缺角矩形當中的一條弧線,在繪製其形狀時候,上述方法會自動根據arcTo的資料來自動判斷弧線的大小弧、順逆時針等情況的繪製
原始碼
BlogCodeSample/OpenxmlActToSvgSample at main · ZhengDaoWang/BlogCodeSample