【Openxml】將Openxml的橢圓弧線arcTo轉為Svg的橢圓弧線

RyzenAdorer發表於2021-08-26

本文將介紹如何將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

參考

Implementation Notes — SVG 2

【OpenXml】Pptx的形狀轉為WPF的Geometry - RyzenAdorer - 部落格園

dotnet OpenXML SDK 形狀幾何 Geometry 的計算公式含義

相關文章