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

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

Shape Path#

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

Copy
<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的类:

Copy
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形状的关键代码:

Copy
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:

Copy
/// <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:

Copy
/// <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; }

然后渲染到界面:

Copy
/// <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渲染结果对比:

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

源码#

源码地址

posted @   RyzenAdorer  阅读(537)  评论(1编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示
CONTENTS