本文来告诉大家,在 OpenXML 里面的 Geometry 的如 gdLst 和 ahLst 和 pathLst 等里面参数的公式的参数含义
这部分内容放在 ECMA-376 的 20.1.10.55 章文档里面,本文只是将文档里面的内容翻译一下
在使用 OpenXML 读取形状时,会看到有些形状的定义内容如下
<avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
<gd name="adj1" fmla="val 50000" />
</avLst>
<gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
<gd name="x2" fmla="*/ w adj1 100000" />
<gd name="x1" fmla="+/ l x2 2" />
<gd name="x3" fmla="+/ r x2 2" />
<gd name="y3" fmla="*/ h 3 4" />
</gdLst>
<ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
<ahXY gdRefX="adj1" minX="-2147483647" maxX="2147483647">
<pos x="x2" y="vc" />
</ahXY>
</ahLst>
<pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
<path fill="none">
<moveTo>
<pt x="l" y="t" />
</moveTo>
<cubicBezTo>
<pt x="x1" y="t" />
<pt x="x2" y="hd4" />
<pt x="x2" y="vc" />
</cubicBezTo>
<cubicBezTo>
<pt x="x2" y="y3" />
<pt x="x3" y="b" />
<pt x="r" y="b" />
</cubicBezTo>
</path>
</pathLst>
或者是采用预设形状,定义如下
<p:sp>
<p:nvSpPr>
<!-- 忽略代码 -->
</p:nvSpPr>
<p:spPr>
<a:xfrm>
<!-- 忽略代码 -->
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst />
</a:prstGeom>
</p:spPr>
<!-- 忽略代码 -->
</p:sp>
对于预设形状来说,需要通过预设形状的文档,也就是 ECMA-376 的 ECMA-376, Third Edition, Part 1 - Fundamentals And Markup Language Reference
的 PresetShapeDefinitions.xml
文档里面,获取到预设形状的定义。展开的结果也和上文相同,预设的定义允许被具体的元素的定义覆盖,规则和 WPF 的资源优先级相同
我将本文的计算规则逻辑放到 DocumentFormat.OpenXml.Flatten 库里面,在 GitHub 上完全开源: https://github.com/dotnet-campus/DocumentFormat.OpenXml.Extensions
如果想要绘制形状的 Path 几何图形,就需要计算此形状里面的 Path 的各个值。如上面代码,可以看到都采用的是公式的方式进行计算,如 gd 的内容如下
<gd name="adj1" fmla="val 50000" />
以上表示了在 avLst 也就是 AdjustValueList 调整点的参数,以上的 gd 也就是 OpenXML SDK 的 ShapeGuide 类型,这里面的 name 就是 adj1 换句话说就是变量名为 adj1 的值。此 adj1 变量将会在接下来的公式里面使用。而 fmla 就是 ShapeGuide 的 Formula 公式内容,通过如下代码可以获取到公式
private void Foo(ShapeGuide shapeGuide)
{
var formula = shapeGuide.Formula;
}
可以看到以上的 val 50000
字符串就是公式的内容,以上的 val 表示常量,也就是相当于 adj1 = val 50000
也就是 adj1 变量的值就是 50000 的常量
而后续在 gdLst 也就是 ShapeGuideList 类型里面,将会在如下代码使用到 adj1 变量
<gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
<gd name="x2" fmla="*/ w adj1 100000" />
<gd name="x1" fmla="+/ l x2 2" />
<gd name="x3" fmla="+/ r x2 2" />
<gd name="y3" fmla="*/ h 3 4" />
</gdLst>
此时在 gd name="x2" fmla="*/ w adj1 100000"
里面通过计算,拿到 x2 变量的值,以上使用了 */
这个符号,其实在 OpenXML 里面的公式用的是逆波兰表达的公式,大概的意思就是 */
运算符要求后续传入三个参数,假定这三个参数是 a b c 三个,那么计算的方法是 (a * b) / c
拿到值
通过不断代入公式可以拿到对应的变量,从而计算出 Path 里面的内容。但以上有一部分公式使用了常量,如下面代码
<moveTo>
<pt x="l" y="t" />
</moveTo>
上面代码的 pt x="l" y="t"
的 l 和 t 都是常量,在文档里面都有定义
下面将告诉大家计算的符号的含义,以及常量的值
3cd4
表示三分之四的圆,以上的 c 就是 Circle 圆的意思,而 d 就是除法的意思, 相当于 3 * 圆 / 4
的值
以上的圆使用的是 180° 的表示,也就是以上常量的值等于 3cd4 = 3 x 360° / 4 = 270°
通过 Office Open XML 的测量单位 可以拿到角度对应的值是 16200000.0 的常量值。在 OpenXML 里面使用 60000 表示 360° 的圆
以此可以了解到以下的对圆的计算值
3cd4 = 3 x 360° / 4 = 270° = 16200000 Degree
3cd8 = 3 x 360° / 8 = 135° = 8100000 Degree
5cd8 = 5 x 360° / 8 = 225° = 13500000 Degree
7cd8 = 7 x 360° / 8 = 315° = 18900000 Degree
cd2 = 360° / 2 = 180° = 10800000 Degree
cd4 = 360° / 4 = 90° = 5400000 Degree
cd8 = 360° / 8 = 45° = 2700000 Degree
t
也就是 Shape Top Edge 的含义,表示上边缘,等价于常量 0 的值。原因是 OpenXML 的形状采用的坐标系和 DirectX 的坐标系相同,左上角是 0,0 点,从上到下 y 的值不断加大。从左到右 x 的值加大
b
也就是 Shape Bottom Edge 的含义,等价于常量 h 的值
这是形状的下边缘,因为形状的上边缘被认为是 0 点,因此下边缘就是形状的高度
关于常量 h 的值,请看下文
h
也就是 Shape Height 的含义,表示形状的高度,需要通过形状的属性拿到形状的高度才能了解此值
hd2
表示的是高度除以 2 的值,以上的 h 是 高度 而 d 表示的是除以,相当于如下公式
*/ h 1.0 2.0
以上代码的 */
公式内容请参阅下文,而 h 表示的是宽度
以此可以了解如下的几个常量的计算
hd2 = */ h 1.0 2.0 = height / 2
hd4 = */ h 1.0 4.0 = height / 4
hd5 = */ h 1.0 5.0 = height / 5
hd6 = */ h 1.0 6.0 = height / 6
hd8 = */ h 1.0 8.0 = height / 8
vc
也就是 Vertical Center of Shape 的含义,表示垂直的中心,相当于高度的一半,使用如下公式
*/ h 1.0 2.0
以上代码的 */
公式内容请参阅下文,而 h 表示的是宽度
l
也就是 Shape Left Edge 的含义,表示左边缘的值,等价于常量 0 的值。原因是 OpenXML 的形状采用的坐标系和 DirectX 的坐标系相同,左上角是 0,0 点,从上到下 y 的值不断加大。从左到右 x 的值加大
r
也就是 Shape Right Edge 的含义,表示右边缘的值,等价于常量 w 的值。也就是右边缘的值和形状的宽度相同,因为形状的左边缘是 0 的值,因此形状的右边的值就和形状的宽度相同
关于 w 请看下文
w
也就是 Shape Width 形状宽度的含义,需要通过形状的属性拿到形状的高度才能了解此值
wd2
表示形状宽度的一半,以上的 w 是 宽度 而 d 表示的是除以,相当于如下公式
*/ w 1.0 2.0
以此可以了解如下的几个常量的计算
wd2 = */ w 1.0 2.0 = width / 2
wd4 = */ w 1.0 4.0 = width / 4
wd5 = */ w 1.0 5.0 = width / 5
wd6 = */ w 1.0 6.0 = width / 6
wd8 = */ w 1.0 8.0 = width / 8
wd10 = */ w 1.0 10.0 = width / 10
hc
也就是 Horizontal Center 的含义,表示水平的中心点,相当于宽度的一半,计算的公式如下
*/ w 1.0 2.0
以上代码的 */
公式内容请参阅下文,而 w 表示的是宽度
ls
也就是 Longest Side of Shape 的含义,表示宽度或高度里面最长的一边,等价以下公式
max w h
也就是返回宽度或高度的最大值
ss
也就是 Shortest Side of Shape 的含义,表示宽度或高度里面最短的一边,等价以下公式
min w h
也就是返回宽度或高度的最小值
ssd2
表示的是 ss 除以 2 的值,也就是获取宽度或高度的最小值除以 2 的值,以上 d 表示的是除以,使用如下公式
*/ ss 1.0 2.0
以此可以了解如下的几个常量的计算
ssd2 = */ ss 1.0 2.0 = Shortest Side / 2
ssd4 = */ ss 1.0 4.0 = Shortest Side / 4
ssd6 = */ ss 1.0 6.0 = Shortest Side / 6
ssd8 = */ ss 1.0 8.0 = Shortest Side / 8
符号
而形状的计算符号定义在 ECMA 376 的 20.1.9.11 章文档
含义如下,以下的 x 和 y 和 z 表示传入的三个参数的值,如 fmla="*/ x y z"
的实际文档的值是 fmla="*/ 1 2 3"
也就是表示 x = 1 ,y = 2 ,z = 3 的值
Multiply Divide Formula
乘除公式使用 */
表示,要求传入三个参数
"*/ x y z" = ((x * y) / z)
Add Subtract Formula
加减公式使用 +-
表示,要求传入三个参数
"+- x y z" = ((x + y) - z)
Add Divide Formula
加除公式使用 +/
表示,要求传入三个参数
"+/ x y z" = ((x + y) / z)
If Else Formula
条件判断使用 ?:
符号表示,和 C# 里面的 ?:
逻辑相同,需要传入三个参数,假定参数是 x y z 三个参数,判断是如果传入的 x 大于 0 那么则是 true 代码如下
"?: x y z" = x > 0 ? y : z
if (x > 0)
{
return y;
}
else
{
return z;
}
Absolute Value Formula
绝对值公式使用 abs
表示,需要传入一个参数,计算方法如下
abs x = Math.Abs(x)
ArcTan Formula
表示三角函数的 arctan2
公式,计算方法如下
at2 x y = arctan(y / x) = Atan2(y, x)
而 Atan2 等的定义如下
/// <summary>
/// OpenXml 三角函数的Sin函数:sin x y = (x * sin( y )) = (x * Math.Sin(y))
/// </summary>
/// <param name="x">ppt的数值</param>
/// <param name="y">ppt表示角度的值</param>
/// <returns></returns>
public static double Sin(double x, int y)
{
var angle = GetAngle(y);
return x * Math.Sin(angle);
}
/// <summary>
/// OpenXml ATan2函数:at2 x y = arctan(y / x)
/// </summary>
/// <param name="x">笛卡尔平面的x坐标</param>
/// <param name="y">笛卡尔平面的y坐标</param>
/// <returns>Emu单位的角度值</returns>
/// 计算符号定义在 ECMA 376 的 20.1.9.11 章文档
public static double ATan2(double x, double y)
{
var radians = System.Math.Atan2(y, x);
var angle = radians * 180 / System.Math.PI;
return angle * 60000;
}
/// <summary>
/// OpenXml 三角函数的Cos函数:cos x y = (x * cos( y )) = (x * Math.Cos(y))
/// </summary>
/// <param name="x">ppt的数值</param>
/// <param name="y">ppt表示角度的值</param>
/// <returns></returns>
public static double Cos(double x, int y)
{
var angle = GetAngle(y);
return x * Math.Cos(angle);
}
/// <summary>
/// OpenXml 三角函数的Tan函数:tan x y = (x * tan( y )) = (x * Math.Tan(y))
/// </summary>
/// <param name="x">ppt的数值</param>
/// <param name="y">ppt表示角度的值</param>
/// <returns></returns>
public static double Tan(double x, int y)
{
var angle = GetAngle(y);
return x * Math.Tan(angle);
}
/// <summary>
/// ppt的值转为角度
/// </summary>
/// <param name="value">ppt表示角度的值</param>
/// <returns></returns>
private static double GetAngle(int value)
{
var degree = value / 60000.0; // [Office Open XML 的测量单位](https://blog.lindexi.com/post/Office-Open-XML-%E7%9A%84%E6%B5%8B%E9%87%8F%E5%8D%95%E4%BD%8D.html )
var angle = degree * Math.PI / 180;
return angle;
}
Cosine ArcTan Formula
表示三角函数的两次计算 cat2
公式,计算方法如下
cat2 x y z = (x*(cos(arctan(z / y))) = (x * Cos(Math.Atan2(z, y)))
Cosine Formula
表示三角函数的 cos
公式,计算方法如下
cos x y = (x * cos( y )) = (x * Cos(y))
Sine ArcTan Formula
表示三角函数的 sat2
公式,计算方法如下
sat2 x y z = (x*sin(arctan(z / y))) = (x * Sin(Atan2(z, y)))
Sine Formula
表示三角函数的 sin
公式,计算方法如下
sin x y = (x * sin( y )) = (x * Sin(y))
Tangent Formula
表示三角函数的 tan
公式,计算方法如下
tan x y = (x * tan( y )) = (x * Tan(y))
Maximum Value Formula
表示两个数里面最大的一个值,使用 max
公式,计算方法如下
max x y = Math.Max(x, y)
Minimum Value Formula
表示两个数里面最小的一个值,使用 min
公式,计算方法如下
min x y = Math.Min(x, y)
Modulo Formula
表示 mod
公式,计算方法如下
mod x y z = sqrt(x^2 + b^2 + c^2) = Math.Sqrt(x * x + y * y + z * z)
Pin To Formula
表示 pin
公式,计算方法如下
pin x y z =
if (y < x)
{
return x;
}
else if (y > z)
{
return z;
}
else
{
return y;
}
Square Root Formula
表示 sqrt
公式,计算方法如下
sqrt x = Math.Sqrt(x)
Literal Value Formula
表示一个常量的值,相当于 var
的定义,表示的是 val
公式,将会返回对应的值
如 val x
就是返回 x 的值
如需要转换为 SVG 的字符串,请看 dotnet OpenXML 让 PathLst 自定义形状转 SVG 路径格式的 Geometry 内容
更多请看 Office 使用 OpenXML SDK 解析文档博客目录
感谢
感谢 Ryzen 提供的公式和代码
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-OpenXML-SDK-%E5%BD%A2%E7%8A%B6%E5%87%A0%E4%BD%95-Geometry-%E7%9A%84%E8%AE%A1%E7%AE%97%E5%85%AC%E5%BC%8F%E5%90%AB%E4%B9%89.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利