【OpenXml】Pptx的多路径形状转为WPF的Path

  • A+
所属分类:.NET技术
摘要

本文是将演示如何解析pptx文件的多路径的形状转换到WPF,绘制多个Shape的Path这是Pptx的【标注:弯曲曲线(无边框)】形状的OpenXml定义部分:

本文是将演示如何解析pptx文件的多路径的形状转换到WPF,绘制多个Shape的Path

Shape Path

这是Pptx的【标注:弯曲曲线(无边框)】形状的OpenXml定义部分:

  <callout2>     <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">       <gd name="adj1" fmla="val 18750" />       <gd name="adj2" fmla="val -8333" />       <gd name="adj3" fmla="val 18750" />       <gd name="adj4" fmla="val -16667" />       <gd name="adj5" fmla="val 112500" />       <gd name="adj6" fmla="val -46667" />     </avLst>     <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">       <gd name="y1" fmla="*/ h adj1 100000" />       <gd name="x1" fmla="*/ w adj2 100000" />       <gd name="y2" fmla="*/ h adj3 100000" />       <gd name="x2" fmla="*/ w adj4 100000" />       <gd name="y3" fmla="*/ h adj5 100000" />       <gd name="x3" fmla="*/ w adj6 100000" />     </gdLst>     <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">       <path stroke="false" extrusionOk="false">         <moveTo>           <pt x="l" y="t" />         </moveTo>         <lnTo>           <pt x="r" y="t" />         </lnTo>         <lnTo>           <pt x="r" y="b" />         </lnTo>         <lnTo>           <pt x="l" y="b" />         </lnTo>         <close />       </path>       <path fill="none" extrusionOk="false">         <moveTo>           <pt x="x1" y="y1" />         </moveTo>         <lnTo>           <pt x="x2" y="y2" />         </lnTo>         <lnTo>           <pt x="x3" y="y3" />         </lnTo>       </path>     </pathLst>   </callout2> 

然后以下OpenXml Shape Path的子属性:

属性 类型 备注
extrusionOk (3D Extrusion Allowed) bool 指定使用 3D 拉伸可能在此路径,默认false或0
fill (Path Fill) PathFillMode 路径填充模式:Norm(默认)、None、Lighten、LightenLess、Darken、DarkenLess
stroke (Path Stroke) bool 是否存在轮廓:默认false
h (Path Height) int 指定框架的高度或在路径坐标系统中应在使用的最大的 y 坐标
w (Path Width) int 指定的宽度或在路径坐标系统中应在使用的最大的 x 坐标

首先为什么是要转为多个Shape呢?因为OpenXml每条路径,都能设置是否有轮廓、填充等属性,而该属性设置只能在Shape层,而不能在Geometry层,就算是通过PathGeometry的PathFigure也只能设置IsFilled(是否填充),不能设置IsStroke(是否有轮廓)

解析Pptx形状

首先我们来创建对应Shape Path的类:

    public readonly struct ShapePath     {         public ShapePath(string path, FillMode fillMode = FillMode.Norm, bool isStroke = true)         {             Path = path;             IsStroke = isStroke;             FillMode = fillMode;             IsFilled = fillMode is not FillMode.None;         }          /// <summary>         /// 是否填充         /// </summary>         public bool IsFilled { get; }          /// <summary>         /// 是否有边框         /// </summary>         public bool IsStroke { get; }          public FillMode FillMode { get; }          /// <summary>         /// Geometry的Path         /// </summary>         public string Path { get; }     }      public enum FillMode     {         /// <summary>         ///Darken Path Fill         /// </summary>         Darken,          /// <summary>         /// Darken Path Fill Less         /// </summary>         DarkenLess,          /// <summary>         /// Lighten Path Fill         /// </summary>         Lighten,          /// <summary>         /// Lighten Path Fill Less         /// </summary>         LightenLess,          /// <summary>         /// None Path Fill         /// </summary>         None,          /// <summary>         /// Normal Path Fill         /// </summary>         Norm     } 

解析pptx形状的关键代码:

private void PptxMultiPathToGeometry(string filePath) {     if (!File.Exists(filePath) || !filePath.EndsWith(".pptx", StringComparison.OrdinalIgnoreCase))     {         MessageBox.Show("请输入正确的pptx文件路径");         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.BorderCallout2)                     {                         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 geometryPaths = GetGeometryPathFromCallout2(new Emu(width).EmuToPixel().Value, new Emu(height).EmuToPixel().Value);                                 RenderGeometry(geometryPaths);                             }                         }                    }                }          }      }    } }  

根据openxml的定义算出Shape Path:

        /// <summary>         /// 获取【标注:弯曲线形】的Shape Path         /// </summary>         /// <param name="width"></param>         /// <param name="height"></param>         /// <returns></returns>         public static List<ShapePath> GetGeometryPathFromCallout2(double width, double height)         {             var (h, w, l, r, t, b, hd2, hd4, hd5, hd6, hd8, ss, hc, vc, ls, ss2, ss4, ss6, ss8, wd2, wd4, wd5, wd6, wd8, wd10, cd2, cd4, cd8) = GetFormulaProperties(width, height);             //<avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">             //  <gd name="adj1" fmla="val 18750" />             //  <gd name="adj2" fmla="val -8333" />             //  <gd name="adj3" fmla="val 18750" />             //  <gd name="adj4" fmla="val -16667" />             //  <gd name="adj5" fmla="val 112500" />             //  <gd name="adj6" fmla="val -46667" />             //</avLst>             var adj1 = 18750d;             var adj2 = -8333d;             var adj3 = 18750d;             var adj4 = -16667d;             var adj5 = 112500d;             var adj6 = -46667;              //<gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">             //  <gd name="y1" fmla="*/ h adj1 100000" />             //  <gd name="x1" fmla="*/ w adj2 100000" />             //  <gd name="y2" fmla="*/ h adj3 100000" />             //  <gd name="x2" fmla="*/ w adj4 100000" />             //  <gd name="y3" fmla="*/ h adj5 100000" />             //  <gd name="x3" fmla="*/ w adj6 100000" />             //</gdLst>              //  <gd name="y1" fmla="*/ h adj1 100000" />             var y1 = h * adj1 / 100000;             //  <gd name="x1" fmla="*/ w adj2 100000" />             var x1 = w * adj2 / 100000;             //  <gd name="y2" fmla="*/ h adj3 100000" />             var y2 = h * adj3 / 100000;             //  <gd name="x2" fmla="*/ w adj4 100000" />             var x2 = w * adj4 / 100000;             //  <gd name="y3" fmla="*/ h adj5 100000" />             var y3 = h * adj5 / 100000;             //  <gd name="x3" fmla="*/ w adj6 100000" />             var x3 = w * adj6 / 100000;              // <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">             //  <path extrusionOk="false">             //    <moveTo>             //      <pt x="l" y="t" />             //    </moveTo>             //    <lnTo>             //      <pt x="r" y="t" />             //    </lnTo>             //    <lnTo>             //      <pt x="r" y="b" />             //    </lnTo>             //    <lnTo>             //      <pt x="l" y="b" />             //    </lnTo>             //    <close />             //  </path>             //  <path fill="none" extrusionOk="false">             //    <moveTo>             //      <pt x="x1" y="y1" />             //    </moveTo>             //    <lnTo>             //      <pt x="x2" y="y2" />             //    </lnTo>             //    <lnTo>             //      <pt x="x3" y="y3" />             //    </lnTo>             //  </path>             //</pathLst>              var pathLst = new List<ShapePath>();              //  <path stroke="false" extrusionOk="false">             //    <moveTo>             //      <pt x="l" y="t" />             //    </moveTo>             var currentPoint = new EmuPoint(l, t);             var stringPath = new StringBuilder();             stringPath.Append($"M {EmuToPixelString(currentPoint.X)},{EmuToPixelString(currentPoint.Y)} ");             //    <lnTo>             //      <pt x="r" y="t" />             //    </lnTo>             currentPoint = LineToToString(stringPath, r, t);             //    <lnTo>             //      <pt x="r" y="b" />             //    </lnTo>             currentPoint = LineToToString(stringPath, r, b);             //    <lnTo>             //      <pt x="l" y="b" />             //    </lnTo>             currentPoint = LineToToString(stringPath, l, b);             //    <close />             stringPath.Append("z ");              pathLst.Add(new ShapePath(stringPath.ToString(),isStroke:false));                //  <path fill="none" extrusionOk="false">             //    <moveTo>             //      <pt x="x1" y="y1" />             //    </moveTo>             stringPath.Clear();             currentPoint = new EmuPoint(x1, y1);             stringPath.Append($"M {EmuToPixelString(currentPoint.X)},{EmuToPixelString(currentPoint.Y)} ");             //    <lnTo>             //      <pt x="x2" y="y2" />             //    </lnTo>             currentPoint = LineToToString(stringPath, x2, y2);             //    <lnTo>             //      <pt x="x3" y="y3" />             //    </lnTo>             _ = LineToToString(stringPath, x3, y3);              pathLst.Add(new ShapePath(stringPath.ToString(), FillMode.None));               return pathLst;          } 

将解析好的Shape Path转为WPF的形状Path:

         /// <summary>         /// 将解析好的Shape Path转为Path的形状集合         /// </summary>         /// <param name="geometryPaths"></param>         /// <returns></returns>         private List<System.Windows.Shapes.Path> CreatePathLst(List<ShapePath> geometryPaths)         {             var pathLst = new List<System.Windows.Shapes.Path>();             foreach (var geometryPath in geometryPaths)             {                 var geometry = Geometry.Parse(geometryPath.Path);                 var path = new System.Windows.Shapes.Path                 {                     Data = geometry,                     Fill = geometryPath.IsFilled ? new SolidColorBrush(Color.FromRgb(68, 114, 196)) : null,                     Stroke = geometryPath.IsStroke ? new SolidColorBrush(Color.FromRgb(47, 82, 143)) : null,                 };                 pathLst.Add(path);             }             return pathLst;         } 

然后渲染到界面:

        /// <summary>         /// 渲染形状到界面         /// </summary>         /// <param name="geometryPaths"></param>         private void RenderGeometry(List<ShapePath> geometryPaths)         {             if (PathGrid.Children.Count > 0)             {                 PathGrid.Children.Clear();             }             var pathLst = CreatePathLst(geometryPaths);             foreach (var path in pathLst)             {                 PathGrid.Children.Add(path);             }         } 

效果演示

pptx和WPF渲染结果对比:

【OpenXml】Pptx的多路径形状转为WPF的Path

我们会发现,pptx的形状和wpf的形状是一模一样的,同样的左边线条的Path是无填充的,而右边的矩形则是无轮廓有填充的

源码

源码地址