Pptx的形狀轉為WPF的Geometry

RyzenAdorer發表於2021-06-08

本文是將演示如何解析pptx檔案的形狀到WPF當中,並且繪製顯示出來

安裝Openxml sdk

首先,我們先安裝nuget的openxml sdk,下面兩種方式都可以安裝:

  • nuget包管理器控制檯:
Install-Package DocumentFormat.OpenXml -Version 2.13.0
  • csproj引用:
<PackageReference Include="DocumentFormat.OpenXml" Version="2.13.0" />

解析Pptx

我打算解析pptx中的五邊形來作為演示效果,直接上程式碼:

MainWindow.xaml:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition  Height="2*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBlock Text="pptx的檔案路徑:" VerticalAlignment="Center" FontSize="15" Margin="10"/>
            <TextBox x:Name="FilePathText"  Height="50" Width="300" Margin="0,0,10,0" TextWrapping="Wrap"/>
            <Button x:Name="Button" Content="解析PPT" Click="Button_OnClick" Width="120" Height="40"/>
        </StackPanel>
        <Path x:Name="Path" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Stroke="Blue"/>
    </Grid>

MainWindow.xaml.cs:

        private void PptxToGeometry(string filePath)
        {
            if (!File.Exists(filePath) || !filePath.EndsWith(".pptx", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }
            using (var presentationDocument = PresentationDocument.Open(filePath, false))
            {
                var presentationPart = presentationDocument.PresentationPart;
                var presentation = presentationPart?.Presentation;
                var slideIdList = presentation?.SlideIdList;
                if (slideIdList == null)
                {
                    return;
                }
                foreach (var slideId in slideIdList.ChildElements.OfType<SlideId>())
                {
                    var slidePart = (SlidePart)presentationPart.GetPartById(slideId.RelationshipId);
                    var slide = slidePart.Slide;
                    foreach (var shapeProperties in slide.Descendants<ShapeProperties>())
                    {
                        var presetGeometry = shapeProperties.GetFirstChild<PresetGeometry>();
                        if (presetGeometry != null && presetGeometry.Preset.HasValue)
                        {
                            if (presetGeometry.Preset == ShapeTypeValues.Pentagon)
                            {
                                var transform2D = shapeProperties.GetFirstChild<Transform2D>();
                                var extents = transform2D?.GetFirstChild<Extents>();
                                if (extents != null)
                                {
                                    var width = extents.Cx;
                                    var height = extents.Cy;
                                    if (width.HasValue && height.HasValue)
                                    {
                                        var points = GetPentagonPoints(width.Value.EmuToPixel(), height.Value.EmuToPixel());
                                        RenderGeometry(points);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 獲取五邊形頂點座標
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <returns></returns>
        /// 該五邊形定義出自ECMA-376-Fifth-Edition-Part-1-Fundamentals-And-Markup-Language-Reference
        /// \OfficeOpenXML-DrawingMLGeometries文件的presetShapeDefinitions.xml
        private List<Point> GetPentagonPoints(double width, double height)
        {
            var properties = new FormulaProperties(width, height);

            //<avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
            //  <gd name="hf" fmla="val 105146" />
            //  <gd name="vf" fmla="val 110557" />
            //</avLst>
            var hf = 105146d;
            var vf = 110557d;

            //<gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
            //  <gd name="swd2" fmla="*/ wd2 hf 100000" />
            //  <gd name="shd2" fmla="*/ hd2 vf 100000" />
            //  <gd name="svc" fmla="*/ vc  vf 100000" />
            //  <gd name="dx1" fmla="cos swd2 1080000" />
            //  <gd name="dx2" fmla="cos swd2 18360000" />
            //  <gd name="dy1" fmla="sin shd2 1080000" />
            //  <gd name="dy2" fmla="sin shd2 18360000" />
            //  <gd name="x1" fmla="+- hc 0 dx1" />
            //  <gd name="x2" fmla="+- hc 0 dx2" />
            //  <gd name="x3" fmla="+- hc dx2 0" />
            //  <gd name="x4" fmla="+- hc dx1 0" />
            //  <gd name="y1" fmla="+- svc 0 dy1" />
            //  <gd name="y2" fmla="+- svc 0 dy2" />
            //  <gd name="it" fmla="*/ y1 dx2 dx1" />
            //</gdLst>

            //  <gd name="swd2" fmla="*/ wd2 hf 100000" />
            var swd2 = properties.wd2 * hf / 100000;
            //  <gd name="shd2" fmla="*/ hd2 vf 100000" />
            var shd2 = properties.hd2 * vf / 100000;
            //  <gd name="svc" fmla="*/ vc  vf 100000" />
            var svc = properties.vc * vf / 100000;
            //  <gd name="dx1" fmla="cos swd2 1080000" />
            var dx1 = Cos(swd2, 1080000);
            //  <gd name="dx2" fmla="cos swd2 18360000" />
            var dx2 = Cos(swd2, 18360000);
            //  <gd name="dy1" fmla="sin shd2 1080000" />
            var dy1 = Sin(shd2, 1080000);
            //  <gd name="dy2" fmla="sin shd2 18360000" />
            var dy2 = Sin(shd2, 18360000);
            //  <gd name="x1" fmla="+- hc 0 dx1" />
            var x1 = properties.hc - dx1;
            //  <gd name="x2" fmla="+- hc 0 dx2" />
            var x2 = properties.hc - dx2;
            //  <gd name="x3" fmla="+- hc dx2 0" />
            var x3 = properties.hc + dx2;
            //  <gd name="x4" fmla="+- hc dx1 0" />
            var x4 = properties.hc + dx1;
            //  <gd name="y1" fmla="+- svc 0 dy1" />
            var y1 = svc - dy1;
            //  <gd name="y2" fmla="+- svc 0 dy2" />
            var y2 = svc - dy2;
            //  <gd name="it" fmla="*/ y1 dx2 dx1" />

            // <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
            //  <path>
            //    <moveTo>
            //      <pt x="x1" y="y1" />
            //    </moveTo>
            //    <lnTo>
            //      <pt x="hc" y="t" />
            //    </lnTo>
            //    <lnTo>
            //      <pt x="x4" y="y1" />
            //    </lnTo>
            //    <lnTo>
            //      <pt x="x3" y="y2" />
            //    </lnTo>
            //    <lnTo>
            //      <pt x="x2" y="y2" />
            //    </lnTo>
            //    <close />
            //  </path>
            //</pathLst>
            var points = new List<Point>(5)
            {
                new Point(x1, y1),
                new Point(properties.hc,properties.t),
                new Point(x4, y1),
                new Point(x3, y2),
                new Point(x2, y2),
            };
            return points;
        }

        private void RenderGeometry(List<Point> points)
        {
            if (points.Count > 0)
            {
                var streamGeometry = new StreamGeometry();
                using var context = streamGeometry.Open();
                context.BeginFigure(points[0], true, true);
                context.PolyLineTo(points, true, true);
                this.Path.Data = streamGeometry;
            }
        }

        private void Button_OnClick(object sender, RoutedEventArgs e)
        {
            var filePath = @"C:\Users\Ryzen\Desktop\測驗\五邊形.pptx";
            if (!string.IsNullOrEmpty(FilePathText.Text))
            {
                filePath = FilePathText.Text.Trim();
            }
            PptxToGeometry(filePath);

        }

ShapeGeometryHelper.cs:

public static class ShapeGeometryHelper
    {

        public readonly struct FormulaProperties
        {
            public readonly double t;
            public readonly double h;
            public readonly double w;
            public readonly double hd2;
            public readonly double wd2;
            public readonly double vc;
            public readonly double hc;

            public FormulaProperties(double width, double height)
            {
                t = 0;
                w = width;
                h = height;
                hd2 = h / 2;
                wd2 = w / 2;
                vc = height / 2;
                hc = width / 2;
            }
        }


        public static long EmuToPixel(this long eum)
        { 
            const long defaultDpi = 96;
            return eum / 914400 * defaultDpi;
        }


        /// <summary>
        /// OpenXml 三角函式的Sin函式:sin x y = (x * sin( y )) = (x * Math.Sin(y))
        /// </summary>
        /// <param name="x">ppt的數值</param>
        /// <param name="y">ppt表示角度的值</param>
        /// <returns></returns>
        public static double Sin(double x, int y)
        {
            var angle = GetAngle(y);
            return x * Math.Sin(angle);
        }

        /// <summary>
        /// OpenXml 三角函式的Cos函式:cos x y = (x * cos( y )) = (x * Math.Cos(y))
        /// </summary>
        /// <param name="x">ppt的數值</param>
        /// <param name="y">ppt表示角度的值</param>
        /// <returns></returns>
        public static double Cos(double x, int y)
        {
            var angle = GetAngle(y);
            return x * Math.Cos(angle);
        }

        /// <summary>
        /// OpenXml 三角函式的Tan函式:tan x y = (x * tan( y )) = (x * Math.Tan(y))
        /// </summary>
        /// <param name="x">ppt的數值</param>
        /// <param name="y">ppt表示角度的值</param>
        /// <returns></returns>
        public static double Tan(double x, int y)
        {
            var angle = GetAngle(y);
            return x * Math.Tan(angle);
        }

        /// <summary>
        /// ppt的值轉為角度
        /// </summary>
        /// <param name="value">ppt表示角度的值</param>
        /// <returns></returns>
        private static double GetAngle(int value)
        {
            var degree = value / 60000.0;
            var angle = degree * Math.PI / 180;
            return angle;
        }
    }

效果如下:

原始碼

BlogCodeSample/PptPolygonToWPFGeometry at main · ZhengDaoWang/BlogCodeSample

參考

C# dontet Office Open XML Unit Converter

C# dotnet 使用 OpenXml 解析 PPT 元素的座標和寬度高度

C# dotnet 使用 OpenXml 解析 PPT 檔案

相關文章