Pptx的多路徑形狀轉為WPF的Path
阿新 • • 發佈:2021-07-08
本文是將演示如何解析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渲染結果對比:
我們會發現,pptx的形狀和wpf的形狀是一模一樣的,同樣的左邊線條的Path是無填充的,而右邊的矩形則是無輪廓有填充的