本文来和大家聊聊在 PPT 形状使用了 Style 样式的颜色画刷读取方法

在开始之前,期望大家已了解如何在 dotnet 应用里面读取 PPT 文件,如果还不了解读取方法,请参阅 C# dotnet 使用 OpenXml 解析 PPT 文件

本文核心是来和大家聊聊 ECMA 376 文档里面,第 20.1.4.2.19 章的 lnRef (Line Reference) 内容里面没有提到的知识

在 Office 的 PowerPoint 添加默认的形状,在没有更改形状的填充和轮廓,形状使用的是默认的样式,如以下的默认矩形定义

<p:sp>
<p:nvSpPr>
<p:cNvPr id="6" name="矩形 1" />
<p:cNvSpPr />
<p:nvPr />
</p:nvSpPr>
<p:spPr>
<a:xfrm>
<a:off x="3640346" y="1595887" />
<a:ext cx="3804249" cy="3071004" />
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst />
</a:prstGeom>
<a:ln w="76200" />
</p:spPr>
<p:style>
<a:lnRef idx="2">
<a:schemeClr val="accent1">
<a:shade val="50000" />
</a:schemeClr>
</a:lnRef>
<a:fillRef idx="1">
<a:schemeClr val="accent1" />
</a:fillRef>
<a:effectRef idx="0">
<a:schemeClr val="accent1" />
</a:effectRef>
<a:fontRef idx="minor">
<a:schemeClr val="lt1" />
</a:fontRef>
</p:style>
<p:txBody/>
</p:sp>

在 OpenXML 里面,通过 a:ln 表示 Outline 轮廓,也就是咱 WPF 形状元素的边框。包括定义了边框粗细和颜色画刷等

但是从上面文档内容可以看到,只是定义了边框的粗细,没有定义颜色。这就需要从 <p:style> 样式里面读取线条的样式。从 <a:lnRef idx="2"> 可以读取到采用的是序号为 2 的线条样式,这里有一个细节是给定的序号也许会超过定义,解决方法请看 dotnet OpenXML 读取形状轮廓线条样式序号超过主题样式列表数

接着读取 <a:schemeClr val="accent1"> 的内容,用来覆盖作为实际的颜色

下面我将给大家演示如何在 WPF 中读取 PPT 的形状 Style 边框颜色和在界面里面将此显示出来

先加上基础的库引用,以下代码写在 csproj 上,可在本文末尾找到全部源代码

  <ItemGroup>
<PackageReference Include="dotnetCampus.OpenXmlUnitConverter" Version="1.4.1"/>
<PackageReference Include="DocumentFormat.OpenXml" Version="2.13.1" />
</ItemGroup>

接着读取包含上文的形状的 PPT 文档

            var file = new FileInfo("Test.pptx");

            using var presentationDocument = PresentationDocument.Open(file.FullName, false);

获取页面里面的形状,如以下代码

            var slide = presentationDocument.PresentationPart!.SlideParts.First().Slide;
var shape = slide.CommonSlideData!.ShapeTree!.GetFirstChild<Shape>()!;

先从 ShapeProperties 里面获取形状的边框粗细,如下面代码

            ShapeProperties shapeProperties = shape.ShapeProperties!;
// 虽然这个形状有轮廓,但是定义是 `<a:ln w="76200" />` 只有宽度,没有颜色
Outline outline = shapeProperties.GetFirstChild<Outline>()!;
Debug.Assert(outline.GetFirstChild<SchemeColor>() is null);
var outlineWidth = new Emu(outline.Width!);

以上代码拿到的 outlineWidth 就是形状的边框粗细。此形状有轮廓,但是定义是 <a:ln w="76200" /> 只有宽度,没有颜色。颜色需要在 Style 里面读取。在 PPTX 文件里面的定义如下

    <a:lnRef idx="2">
<a:schemeClr val="accent1">
<a:shade val="50000" />
</a:schemeClr>
</a:lnRef>

获取方法是先读取形状的样式,接着读取线条引用,请看代码

            // 实际的颜色应该从 `<a:lnRef idx="2">` 拿到
var shapeStyle = shape.ShapeStyle!;
var lineReference = shapeStyle.LineReference!;

拿到 LineReference 就可以读取里层的颜色,如下面代码

            // 读取里层的颜色
var schemeColor = lineReference.GetFirstChild<SchemeColor>()!;

此颜色是 SchemeColor 颜色,按照 dotnet OpenXML 如何获取 schemeClr 颜色 文档的方法进行读取,读取时用到的辅助方法本文就不列出,还请参阅以上引用博客。当然,本文所有源代码都可以获取到,还请不用担心细节

以下是将 SchemeColor 转换为 System.Windows.Media.Color 的方法

            var colorMap = slide.GetColorMap()!;
var colorScheme = slide.GetColorScheme()!;
var value = schemeColor.Val!.Value; // accent1
value = ColorHelper.SchemeColorMap(value, colorMap);
var actualColor = ColorHelper.FindSchemeColor(value, colorScheme)!; // <a:srgbClr val="5B9BD5"/>
RgbColorModelHex rgbColorModelHex = actualColor.RgbColorModelHex!;
var color = (Color) ColorConverter.ConvertFromString($"#{rgbColorModelHex.Val!.Value}");

接下来按照其他形状的解析方法,读取坐标和宽度高度,在界面显示出来

            // 获取坐标
var offset = shapeProperties.Transform2D!.Offset!;
var x = new Emu(offset.X!);
var y = new Emu(offset.Y!);
var extents = shapeProperties.Transform2D.Extents!;
var width = new Emu(extents.Cx!);
var height = new Emu(extents.Cy!); // 创建元素
var rectangle = new Rectangle()
{
Margin = new Thickness(x.ToPixel().Value, y.ToPixel().Value, 0, 0),
Width = width.ToPixel().Value,
Height = height.ToPixel().Value,
StrokeThickness = outlineWidth.ToPixel().Value,
Stroke = new SolidColorBrush(color)
}; Canvas.Children.Add(rectangle);

以上代码的 Canvas 是在 XAML 定义的,代码如下

  <Grid>
<Canvas x:Name="Canvas">
</Canvas>
</Grid>

运行之后的效果如下

可以看到颜色其实有些差距,原因是以上使用的 SchemeColor 没有加上颜色特效,在 PPTX 文件定义的颜色代码如下

      <a:schemeClr val="accent1">
<a:shade val="50000" />
</a:schemeClr>

通过 dotnet OpenXML 颜色变换 文档可以了解到 Shade 是让颜色变暗,使用如下代码加上特效

            var shade = schemeColor.GetFirstChild<Shade>()!;// 让颜色变暗
color = ColorTransform.HandleShade(color, shade);

此时的效果如下

本文以上所有测试文件和代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin d06378705fcc1a1ff19ea7d1f2544757fb0777c7

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 Pptx 文件夹

虽然可以看到在 WPF 上的形状的边框颜色和在 PPT 上的形状颜色是相同的,然而以上逻辑却有漏洞在于以上是重新被定义了颜色。其实在 OpenXML 里面,按照的寻找属性的规则和 WPF 的资源是相同的逻辑,按照最近原则读取。也就是说读取顺序如下

  • 形状的 a:ln 定义的颜色
  • 形状的样式的 a:lnRef 引用的主题的颜色
  • 形状继承的样式

以上的测试文档是属于在形状的 a:ln 没有定义的颜色,而在形状的样式的 a:lnRef 里面定义的颜色,而且形状引用样式里面使用的是 <a:schemeClr val="phClr" /> 占位符颜色

如果在形状的 a:ln 和形状的样式的 a:lnRef 没有定义的颜色,只有在形状的样式的 a:lnRef 引用的主题的颜色,那么以上逻辑是不符合 OpenXML 定义的。或者说在形状的 a:ln 没有定义的颜色,而在形状的样式的 a:lnRef 里面有定义颜色,但是形状的样式的 a:lnRef 引用的主题的颜色不是 phClr (PlaceholderColor, a color used in theme definitions which means "use the color of the style")的颜色

如以下的文档内容

Slide1.xml:

<p:sp>
<p:style>
<a:lnRef idx="2">
<a:schemeClr val="accent1" />
</a:lnRef>
</p:style>
</p:sp> Theme1: <a:lnStyleLst>
<a:ln w="6350" cap="flat" cmpd="sng" algn="ctr">
</a:ln>
<a:ln w="12700" cap="flat" cmpd="sng" algn="ctr">
<a:solidFill>
<a:srgbClr val="999999" />
</a:solidFill>
<a:prstDash val="solid" />
<a:miter lim="800000" />
</a:ln>
<a:ln w="19050" cap="flat" cmpd="sng" algn="ctr">
</a:ln>
</a:lnStyleLst>

通过 <a:lnRef idx="2"> 可以了解到应该读取的是 LineStyleList 的第二项(从1开始)的颜色

和第一份文档不同的是,以上文档定义的是 <a:srgbClr val="999999" /> 而不是 <a:schemeClr val="phClr" /> 颜色。也就是说在 Slide1.xml 定义的 <a:schemeClr val="accent1" /> 需要被忽略

变更之后的逻辑如下,先读取引用,参阅 dotnet OpenXML 读取形状轮廓线条样式序号超过主题样式列表数 文档的写法

            var lineStyle = lineReference.Index!.Value;// idx="2"
lineStyle--;
// 获取主题
var slidePart = slide.SlidePart!;
var themeOverride = slidePart.ThemeOverridePart?.ThemeOverride
?? slidePart.SlideLayoutPart!.ThemeOverridePart?.ThemeOverride;
FormatScheme? formatScheme = themeOverride?.FormatScheme;
if (formatScheme is null)
{
formatScheme = slidePart.SlideLayoutPart!.SlideMasterPart!.ThemePart!.Theme!.ThemeElements!.FormatScheme;
}
var lineStyleList = formatScheme!.LineStyleList;
var outlineList = lineStyleList!.Elements<Outline>().ToList(); // 其实,别用 ToList 的好,这里只是简化逻辑
Outline themeOutline;
if (lineStyle > outlineList.Count)
{
themeOutline = outlineList[^1];
}
else
{
themeOutline = outlineList[(int)lineStyle];
}

此时读取到的 themeOutline 就是如下的 OpenXML 文档内容

        <a:ln w="12700" cap="flat" cmpd="sng" algn="ctr">
<a:solidFill>
<a:srgbClr val="999999" />
</a:solidFill>
<a:prstDash val="solid" />
<a:miter lim="800000" />
</a:ln>

先获取是否有定义 solidFill 的内容,如果有,那么继续获取里层颜色

var solidFill = themeOutline.GetFirstChild<SolidFill>()!;

在以上的文档里面,是存在 SolidFill 内容的,因此上面代码就不判断空了

获取里层的颜色,如果是 srgbClr (对应 OpenXML 的 RgbColorModelHex 类型)的话,那么计算颜色即可

            var solidFill = themeOutline.GetFirstChild<SolidFill>()!;
var colorModelHex = solidFill.GetFirstChild<RgbColorModelHex>();
if (colorModelHex is not null)
{
// <a:srgbClr val="999999" />
color = (Color)ColorConverter.ConvertFromString($"#{colorModelHex.Val!.Value}");
}

而如果是读取到 SchemeColor 而且是 PhColor 方式的颜色,那么依然按照上文的方式读取形状样式里面的 LineReference 的颜色

    <a:lnRef idx="2">
<a:schemeClr val="accent1">
<a:shade val="50000" />
</a:schemeClr>
</a:lnRef>

读取的逻辑如下

            var solidFill = themeOutline.GetFirstChild<SolidFill>()!;
var colorModelHex = solidFill.GetFirstChild<RgbColorModelHex>();
if (colorModelHex is not null)
{
}
else
{
var themeSchemeColor = solidFill.GetFirstChild<SchemeColor>()!;
if (themeSchemeColor.Val!.Value == SchemeColorValues.PhColor)
{
// 读取里层的颜色
var schemeColor = lineReference.GetFirstChild<SchemeColor>()!;
// 读取 SchemeColor 方法请参阅如下文档
// [dotnet OpenXML 如何获取 schemeClr 颜色](https://blog.lindexi.com/post/dotnet-OpenXML-%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-schemeClr-%E9%A2%9C%E8%89%B2.html )
var colorMap = slide.GetColorMap()!;
var colorScheme = slide.GetColorScheme()!;
var value = schemeColor.Val!.Value; // accent1
value = ColorHelper.SchemeColorMap(value, colorMap);
var actualColor = ColorHelper.FindSchemeColor(value, colorScheme)!; // <a:srgbClr val="5B9BD5"/>
RgbColorModelHex rgbColorModelHex = actualColor.RgbColorModelHex!;
color = (Color)ColorConverter.ConvertFromString($"#{rgbColorModelHex.Val!.Value}");
// 根据 [dotnet OpenXML 颜色变换](https://blog.lindexi.com/post/dotnet-OpenXML-%E9%A2%9C%E8%89%B2%E5%8F%98%E6%8D%A2.html ) 进行修改颜色
var shade = schemeColor.GetFirstChild<Shade>()!;// 让颜色变暗
color = ColorTransform.HandleShade(color, shade);
}
}

此时更换文档,执行的界面如下

本文以上更新的测试文件和代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 868ad6c1a39853764167053e319b68a6db0a2c6b

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 Pptx 文件夹

更多的特殊逻辑:

如果在 Theme 里面定义的 LineStyleList 里面定义的轮廓没有设置颜色,如下面的 OpenXML 文档

      <a:lnStyleLst>
<a:ln w="6350" cap="flat" cmpd="sng" algn="ctr">
<a:solidFill>
<a:schemeClr val="phClr" />
</a:solidFill>
<a:prstDash val="solid" />
<a:miter lim="800000" />
</a:ln>
<a:ln w="12700" cap="flat" cmpd="sng" algn="ctr">
<a:prstDash val="solid" />
<a:miter lim="800000" />
</a:ln>
<a:ln w="19050" cap="flat" cmpd="sng" algn="ctr">
<a:solidFill>
<a:schemeClr val="phClr" />
</a:solidFill>
<a:prstDash val="solid" />
<a:miter lim="800000" />
</a:ln>
</a:lnStyleLst>

在 Slide1.xml 里面的形状定义和上文的相同,引用了第二项的主题,如下面的 OpenXML 文档

    <a:lnRef idx="2">
<a:schemeClr val="accent1">
<a:shade val="50000" />
</a:schemeClr>
</a:lnRef>

此时在 PPT 的运行效果就是没有边框,也就是说在 a:lnRef 定义的 <a:schemeClr val="accent1"> 颜色仅仅只是用来作为 PhColor 的替换

更多请看 Office 使用 OpenXML SDK 解析文档博客目录

dotnet OpenXML 读取 PPT 形状边框定义在 Style 的颜色画刷的更多相关文章

  1. dotnet OpenXML 读取 PPT 内嵌 ole 格式 Excel 表格的信息

    在 Office 中,可以在 PPT 里面插入表格,插入表格有好多不同的方法,对应 OpenXML 文档存储的更多不同的方式.本文来介绍如何读取 PPT 内嵌 ole 格式的 xls+ 表格的方法 在 ...

  2. dotnet OpenXML 转换 PathFillModeValues 为颜色特效

    在 OpenXml 预设形状,有一些形状设置了 PathFillModeValues 枚举,此枚举提供了亮暗的蒙层特效.具体的特效是让形状选择一个画刷,在画刷上加上特效.如立体几何 Cube 形状,在 ...

  3. C# 添加、删除、读取Word形状(基于Spire.Cloud.Word.SDK)

    本文介绍调用Spire.Cloud.Word.SDK提供的接口shapesApi来操作Word形状,包括添加形状AddShape(),添加形状时,可设置形状类型.颜色.大小.位置.倾斜.轮廓.文本环绕 ...

  4. java使用poi读取ppt文件和poi读取excel、word示例

    java使用poi读取ppt文件和poi读取excel.word示例 http://www.jb51.net/article/48092.htm

  5. OpenXml读取word内容(二)

    注意事项 上一篇已经说明,这次就不一一说了,直接来正文: word内容 相关代码 方法1 static void Main(string[] args) { string wordPathStr = ...

  6. OpenXml读取word内容(一)

    OpenXml读取word内容注意事项 1.使用OpenXml读取word内容,word后缀必须是".docx":如果word后缀是".doc"需要转成&quo ...

  7. OpenXml读取word内容注意事项

    OpenXml读取word内容注意事项 1.使用OpenXml读取word内容,word后缀必须是".docx":如果word后缀是".doc"需要转成&quo ...

  8. Java 将 PPT 形状(表格、文本框、心形、图表等)保存成图片

    MS PowerPoint中的表格.文本框.心形.图表.图片等均可以称为形状,将这些形状保存成图片,便可分类储存,方便日后查找,再利用. 本文将介绍如何使用 Spire.Presentation fo ...

  9. WPF 10天修炼 第八天 - 形状、画刷和变换

    图形 在WPF中使用绘图最简单的就是使用Shape类.Shape类继承自FrameworkElement,是一个专门用来绘图的类.Shape类中年派生的类有直线.矩形.多边形和圆形等. System. ...

  10. WPF 动画(形状、画刷)

    一:形状 在WPF用户界面中,可以通过形状(Shape)来绘制直线.椭圆.矩形及一些多边形的类.通过这些基本的图像,组合成为复杂的图形. Shape类中,主要的形状有Rectangle(),Ellip ...

随机推荐

  1. Sealos 云开发:Laf 出嫁了,与 Sealos 正式结合!

    千呼万唤始出来,Laf 云开发最近已正式与 Sealos 融合,入住 Sealos!大家可以登录 Sealos 公有云 体验和使用,现在正式介绍一下 Sealos 云开发. Sealos 云开发是什么 ...

  2. uni-app 地图全解析+事件监听

    最近找到了一篇uni-app的地图解决方案精品文章,这里分享给大家,希望对大家有所帮助 转载地址:https://blog.csdn.net/cplvfx/article/details/111447 ...

  3. 记录-VueJs中如何使用Teleport组件

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 在DOM结构相对比较复杂,层级嵌套比较深的组件内,需要根据相对应的模块业务处理一些逻辑,该逻辑属于当前组件 但是从整个页面应用的视图上看, ...

  4. el-select的简单封装,使其返回值中包含key,value,obj 三种值

    常规的el-select中change事件返回值,只有key返回,业务上有些需求有需要获取到value值,所以简单的封装了一下,使返回中包含key,value,obj三个值,基本上可以满足大部分的需求 ...

  5. 深入浅出Java 23种设计模式,最全PDF版本终于开放下载了!!(文末有福利)

    写在前面 在「 冰河技术 」微信公众号中[设计模式专题]更新完毕已有一段时间了.不少小伙伴在我微信上留言说:冰河,你能不能把[设计模式专题]的文章汇总成PDF文档呢?一直没有时间整理,最近在公众号后台 ...

  6. Java AES CBC模式 加密和解密

    import org.apache.tomcat.util.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.s ...

  7. 聊聊大模型"打字机"效果的背后技术——SSE

    SSE:Server Sent Event:服务器发送事件. Server-Sent Events(SSE)是一种由服务器向客户端推送实时数据的技术.它是构建基于事件的.服务器到客户端的通信的一种方法 ...

  8. ET介绍——为什么使用C# .net core做服务端?

    为什么使用C# .net core做服务端? 游戏服务端从早期的单服到分布式,开发越来越复杂,对稳定性,开发效率要求越来越高.开发语言的选择也逐步发生了变化,C 到 C++ 到 C++ + PYTHO ...

  9. #Kruskal重构树,主席树,倍增#洛谷 4197 Peaks

    题目传送门 分析 首先第\(k\)大一般都是主席树 问题是困难值的限制有点束手无措 又是\(\text{Kruskal重构树}\) 将困难值为边权跑最小生成树,重新建树,实际上让困难值跳到尽量大的位置 ...

  10. #环#nssl 1487 图

    题目 在一个\(n\)个节点\(n\)条边的连通图中, 每条边的权值为两个端点的权值的和. 已知各边权值,求各点权值 (保证环的大小一定是奇数) 分析 考虑断掉环的某一条边,设根节点的答案为\(ax+ ...