颜色是一个大的主题,在 ECMA 376 里面用了 19 页 A4 描述了颜色,但仅是简单的描述。在 OpenXML 定义了 Scheme Color (schemeClr) 是用来表示主题的颜色,可以跟随主题的更改而更改颜色。例如我的文本设置为主题的文本颜色,那么在我更改文档主题的文本色就可以更改我的文本颜色
在 OpenXML 的颜色里面,其中 Scheme Color (a:schemeClr) 是十分强大的,可以用来作为模版发布,让用户自己一键替换主题色。也提供了给智能排版协助更换主题色的方法
在填充笔刷里,本文说的颜色是放在 SolidColorBrush 里面,也就是在 OpenXML 的 a:solidFill
里面的颜色,大概的文档代码请看下面
<a:solidFill>
<a:schemeClr val="tx1">
<a:lumMod val="65000" />
<a:lumOff val="35000" />
</a:schemeClr>
</a:solidFill>
上面代码的 a:solidFill
使用 a:schemeClr
填充,使用的值是 val="tx1"
而在 a:schemeClr
的 lumMod 和 lumOff 表示颜色转换,更多颜色转换请看 dotnet OpenXML 颜色变换
那么 val="tx1"
表示的颜色是什么?是否可以转 RGB 表示,其实这个值表示的是主题里面的 tx1 也就是 Text1 属性的颜色,需要再次去主题里面找到对应的颜色
假定如上是放在 Slide 页面里面的某个文本的颜色,代码如下
<p:sp>
<p:nvSpPr>
<p:cNvPr id="2" name="标题 1" />
<p:cNvSpPr>
<a:spLocks noGrp="1" />
</p:cNvSpPr>
<p:nvPr>
<p:ph type="ctrTitle" />
</p:nvPr>
</p:nvSpPr>
<p:spPr>
<a:xfrm>
<a:off x="568575" y="84959" />
<a:ext cx="9144000" cy="635000" />
</a:xfrm>
</p:spPr>
<p:txBody>
<a:bodyPr />
<a:lstStyle />
<a:p>
<a:r>
<a:rPr kumimoji="1" lang="zh-CN" altLang="en-US" b="1" dirty="0">
<a:solidFill>
<a:schemeClr val="tx1">
<a:lumMod val="65000" />
<a:lumOff val="35000" />
</a:schemeClr>
</a:solidFill>
<a:ea typeface="微软雅黑" panose="020B0503020204020204" charset="-122" />
</a:rPr>
<a:t>林德熙</a:t>
</a:r>
</a:p>
</p:txBody>
</p:sp>
此时想要拿到这个文本的字体的颜色,就需要先获取 Color Map 颜色表,然后找到 Color Scheme 读取实际颜色。默认的 Color Map 在 Slide Master 里面,关于 Slide Master 请看dotnet OpenXML 的 Slide Master 和 Slide Layout 是什么
但是此时在页面里面依然可以通过 ColorMapOverride 重写颜色表,因此在 OpenXML SDK 里面需要这样获取,在拿到 SlidePart, SlideLayoutPart, SlideMasterPart 三个变量,然后先判断当前页面是否有重写,有的话使用当前页面,然后再使用 SlideLayout 的
当然,如果此时的元素是放在 Slide Layout 的元素,那么就不能使用 Slide 的,大概代码如下
var masterColorMap = slideMasterPart?.SlideMaster.ColorMap;
//从当前Slide获取ColorMap
if (slidePart?.Slide.ColorMapOverride != null)
{
if (slidePart.Slide.ColorMapOverride.MasterColorMapping != null)
{
return masterColorMap;
}
if (slidePart.Slide.ColorMapOverride.OverrideColorMapping != null)
{
return slidePart.Slide.ColorMapOverride.OverrideColorMapping.ToColorMap();
}
}
//从SlideLayout获取ColorMap
if (slideLayoutPart?.SlideLayout.ColorMapOverride != null)
{
if (slideLayoutPart.SlideLayout.ColorMapOverride.MasterColorMapping != null)
{
return masterColorMap;
}
if (slideLayoutPart.SlideLayout.ColorMapOverride.OverrideColorMapping != null)
{
return slideLayoutPart.SlideLayout.ColorMapOverride.OverrideColorMapping.ToColorMap();
}
}
//从SlideMaster获取ColorMap
return masterColorMap;
上面代码的 ToColorMap 是一个扩展方法,请看下面代码
/// <summary>
/// 将<see cref="OverrideColorMapping"/>转换为<see cref="ColorMap"/>
/// </summary>
/// <param name="mapping"></param>
/// <returns></returns>
public static ColorMap ToColorMap(this OverrideColorMapping mapping)
{
return new ColorMap()
{
Accent1 = mapping.Accent1,
Accent2 = mapping.Accent2,
Accent3 = mapping.Accent3,
Accent4 = mapping.Accent4,
Accent5 = mapping.Accent5,
Accent6 = mapping.Accent6,
Background1 = mapping.Background1,
Background2 = mapping.Background2,
FollowedHyperlink = mapping.FollowedHyperlink,
Hyperlink = mapping.Hyperlink,
Text1 = mapping.Text1,
Text2 = mapping.Text2
};
}
颜色表在 Slide Master 的内容如下
<p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink" />
也就是如上面文本使用的是 tx1 的颜色,在色表可以看到 tx1="dk1"
所以此时使用的是 dk1
的颜色,这个颜色需要在主题里面找到对应的颜色
找到对应的主题的方法,在 OpenXML 里面可以使用如下方法拿到
//从当前Slide获取theme
if (slidePart?.ThemeOverridePart?.ThemeOverride?.ColorScheme != null)
{
return slidePart.ThemeOverridePart.ThemeOverride.ColorScheme;
}
//从SlideLayout获取theme
if (slideLayoutPart?.ThemeOverridePart?.ThemeOverride?.ColorScheme != null)
{
return slideLayoutPart.ThemeOverridePart.ThemeOverride.ColorScheme;
}
//从SlideMaster获取theme
return slideMasterPart?.ThemePart?.Theme?.ThemeElements?.ColorScheme;
如果是放在页面的元素,那么依次尝试获取 Slide 的主题,如果拿不到,就从 SlideLayout 获取,再获取不到就从 SlideMaster 获取。如果是 Slide Layout 的元素,那么先从 SlideLayout 获取,而不能从 Slide 获取,如果获取不到就从 SlideMaster 获取
在拿到颜色表和主题,可以使用如下方法找到对应颜色
static Color ToColor(this SchemeColor color, ColorScheme scheme, ColorMap map)
{
var value = color.Val.Value;
value = SchemeColorMap(value, map);
var schemeColor = FindSchemeColor(value, scheme);
// 忽略代码
}
而 ColorMap 是非必须的,如果没有 ColorMap 那么就不需要使用 SchemeColorMap 方法
static Color ToColor(this SchemeColor color, ColorScheme scheme, ColorMap map)
{
var value = color.Val.Value;
if (map != null)
{
value = SchemeColorMap(value, map);
}
var schemeColor = FindSchemeColor(value, scheme);
// 忽略代码
}
这里的 SchemeColorMap 方法代码如下
private static SchemeColorValues SchemeColorMap(SchemeColorValues value, ColorMap map) =>
value switch
{
SchemeColorValues.Accent1 => IndexToSchemeColor(map.Accent1),
SchemeColorValues.Accent2 => IndexToSchemeColor(map.Accent2),
SchemeColorValues.Accent3 => IndexToSchemeColor(map.Accent3),
SchemeColorValues.Accent4 => IndexToSchemeColor(map.Accent4),
SchemeColorValues.Accent5 => IndexToSchemeColor(map.Accent5),
SchemeColorValues.Accent6 => IndexToSchemeColor(map.Accent6),
SchemeColorValues.Dark1 => SchemeColorValues.Dark1,
SchemeColorValues.Dark2 => SchemeColorValues.Dark2,
SchemeColorValues.FollowedHyperlink => IndexToSchemeColor(map.FollowedHyperlink),
SchemeColorValues.Hyperlink => IndexToSchemeColor(map.Hyperlink),
SchemeColorValues.Light1 => SchemeColorValues.Light1,
SchemeColorValues.Light2 => SchemeColorValues.Light2,
SchemeColorValues.Background1 => IndexToSchemeColor(map.Background1),
SchemeColorValues.Background2 => IndexToSchemeColor(map.Background2),
SchemeColorValues.Text1 => IndexToSchemeColor(map.Text1),
SchemeColorValues.Text2 => IndexToSchemeColor(map.Text2),
_ => SchemeColorValues.Accent1,
};
private static SchemeColorValues IndexToSchemeColor(ColorSchemeIndexValues value) =>
value switch
{
ColorSchemeIndexValues.Accent1 => SchemeColorValues.Accent1,
ColorSchemeIndexValues.Accent2 => SchemeColorValues.Accent2,
ColorSchemeIndexValues.Accent3 => SchemeColorValues.Accent3,
ColorSchemeIndexValues.Accent4 => SchemeColorValues.Accent4,
ColorSchemeIndexValues.Accent5 => SchemeColorValues.Accent5,
ColorSchemeIndexValues.Accent6 => SchemeColorValues.Accent6,
ColorSchemeIndexValues.Dark1 => SchemeColorValues.Dark1,
ColorSchemeIndexValues.Dark2 => SchemeColorValues.Dark2,
ColorSchemeIndexValues.FollowedHyperlink => SchemeColorValues.FollowedHyperlink,
ColorSchemeIndexValues.Hyperlink => SchemeColorValues.Hyperlink,
ColorSchemeIndexValues.Light1 => SchemeColorValues.Light1,
ColorSchemeIndexValues.Light2 => SchemeColorValues.Light2,
_ => SchemeColorValues.Accent1,
};
这里的 FindSchemeColor 代码如下
private static Color2Type FindSchemeColor(SchemeColorValues value, ColorScheme scheme) =>
value switch
{
SchemeColorValues.Accent1 => scheme.Accent1Color,
SchemeColorValues.Accent2 => scheme.Accent2Color,
SchemeColorValues.Accent3 => scheme.Accent3Color,
SchemeColorValues.Accent4 => scheme.Accent4Color,
SchemeColorValues.Accent5 => scheme.Accent5Color,
SchemeColorValues.Accent6 => scheme.Accent6Color,
SchemeColorValues.Dark1 => scheme.Dark1Color,
SchemeColorValues.Dark2 => scheme.Dark2Color,
SchemeColorValues.FollowedHyperlink => scheme.FollowedHyperlinkColor,
SchemeColorValues.Hyperlink => scheme.Hyperlink,
SchemeColorValues.Light1 => scheme.Light1Color,
SchemeColorValues.Light2 => scheme.Light2Color,
_ => null,
};
此时拿到的只是颜色,颜色可以选的值有很多
<a:clrScheme name="Office">
<a:dk1>
<a:sysClr val="windowText" lastClr="000000" />
</a:dk1>
<a:lt1>
<a:sysClr val="window" lastClr="FFFFFF" />
</a:lt1>
<a:dk2>
<a:srgbClr val="44546A" />
</a:dk2>
<a:lt2>
<a:srgbClr val="E7E6E6" />
</a:lt2>
<a:accent1>
<a:srgbClr val="4472C4" />
</a:accent1>
<a:accent2>
<a:srgbClr val="ED7D31" />
</a:accent2>
<a:accent3>
<a:srgbClr val="A5A5A5" />
</a:accent3>
<a:accent4>
<a:srgbClr val="FFC000" />
</a:accent4>
<a:accent5>
<a:srgbClr val="5B9BD5" />
</a:accent5>
<a:accent6>
<a:srgbClr val="70AD47" />
</a:accent6>
<a:hlink>
<a:srgbClr val="0563C1" />
</a:hlink>
<a:folHlink>
<a:srgbClr val="954F72" />
</a:folHlink>
</a:clrScheme>
在这个颜色里面 dk1 的颜色是 sysClr 也就是 SystemColor 颜色,读取 lastClr 也就是 LastColor 属性就可以拿到颜色。而 SystemColor 的 val 表示的是使用系统的哪个颜色,因此是可以做到在不同的系统设置打开的文档的颜色是不同的。这部分获取的逻辑就相对复杂了,也不在本文范围
本文的 Scheme Color 可以在 ECMA 376 的 20.1.2.3.29 schemeClr (Scheme Color) 找到。如果没有 OpenXML SDK 的定义辅助,也许这里的逻辑能坑你很久
本文会经常更新,请阅读原文: https://dotnet-campus.github.io//post/dotnet-OpenXML-%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-schemeClr-%E9%A2%9C%E8%89%B2.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 lindexi (包含链接: https://dotnet-campus.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。