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


    本文是将演示如何解析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是无填充的,而右边的矩形则是无轮廓有填充的

    源码

    源码地址

  • 相关阅读:
    gitlab centos 安装配置运维笔记
    CentOS 7 升级内核 Kernel
    VMware 桥接模式 复制物理网络连接状态的作用
    diskpart setid value list
    怎样判断Windows的DPI大小?
    Camtasia Studio 6录制视频时鼠标闪烁的解决办法
    ssh的各个jar包作用
    MyEclipse优化技巧
    Struts详细用法
    Ajax的readyState和status属性
  • 原文地址:https://www.cnblogs.com/ryzen/p/14987788.html
Copyright © 2020-2023  润新知