了解最新技术文章
让我们讨论一下有用的字体数据结构。为了本文的目的,我们假设字体仅由字体、大小和粗体标志组成。事实证明,即使是这种简化的字体视图在我们在软件中遇到的许多用例中也非常有用:
在我们的 PowerPoint 插件中,我们将这样的字体表示用于许多不同的目的,例如,
显示所选文本的字体设置,
将字体设置应用于选定的文本,以响应某些用户交互,
应用默认字体设置来初始化我们的加载项插入的一些文本。
这听起来很简单,但有一些实际相关的限制:
我们如何表示混合字体选择,例如,只有某些选定的文本是粗体的?
例如,当用户只想更改大小而保留粗体时,我们如何表示应用程序的字体?
为了显示,我们可以使用std::optional<SFont>
, 来std::nullopt
表示混合字体。原则上这是可行的,但即使所选文本中仅混合粗体,并且大小和名称一致,我们也只能显示“混合字体”,而无法显示大小或名称。不是地球上最好的用户界面。
对于应用程序,我们可以使用单独的函数来设置名称、大小和粗体。鉴于在大多数实际相关的软件中,用户对字体更改的请求可能会由嵌套函数调用的层次结构进行处理,因此我们的结构的复杂性将在SFont
整个调用层次结构中激增。想要添加对斜体的支持吗?添加函数调用树SetItalic(...)
。不是地球上最伟大的软件架构。
为了解决这两个问题(以及更多问题,如下所示),让我们尝试使用std::optional
inside of SFont
:
我们称其SFont
为“属性”,并称其为成员m_ostrName
,m_onSize
以及m_obBold
该属性的“方面”。方面可能是“混合的”或“未定义的”( std::nullopt
)。
这如何解决我们的显示问题?当从文本选择中选取字体方面时,我们可以为SFont
在整个选择过程中保持一致的方面填充明确定义的信息。在选择中具有不同值的方面可以用 表示std::nullopt
。当显示结果字体时,我们可以显示一致方面的值,而对于不一致方面,我们可以显示一些“混合”的表示:“Arial ... pt 粗体”,“... 12 pt”(隐式非-)粗体)或“Arial 12 pt(粗体)”对用户来说比“混合字体”更有意义。
这如何解决我们的应用问题?无论我们想要设置大小、粗体或名称,还是它们的任意组合,我们现在都会SFont
在整个调用层次结构中传递一个对象(或其引用)。我们称其为部分定义的字体。只是传递它的函数不必关心 的哪些方面SFont
实际携带值以及哪些方面是std::nullopt
。
通过部分定义的字体,我们可以做一些有趣的事情。例如,在我们的软件中,我们有分层默认设置:在确定堆叠图表中总和标签的默认字体时,我们从完全定义的全局默认字体开始,这是我们从 PowerPoint 主幻灯片中推断出来的。然后,我们应用例如图表标签的通用默认字体,最后应用总和标签的特定默认字体。除全局默认字体外,所有字体都可以部分定义,因此图表默认字体可以将字体大小设置为 10 pt,而保留名称和粗体,和标签默认字体可以将字体设置为粗体而不影响大小和粗体。姓名。我们将其简洁地写为:
为了方便这个表达式,我们定义了以下运算符重载:
您可能会争辩说,您不想让operator<<
与位移位语义无关的功能过载。您可以自由地重命名SFont::operator<<=(...)
为,例如SFont::Set(...)
,但您要以它换取表达式 (3) 的简洁性。此外,当用于operator<<
此目的时,运算符关联性存在潜在问题,我们将在即将发布的博客文章中解决该问题。
现在,当我们这样做的时候,让我们看看其他一些有用的方法SFont
。我们如何从选定的文本中选取混合字体?如果我们有一种方法来创建类似“字体联盟”的东西就好了......
我们再次使用std::optional<SFont>
,但这次它有不同的目的。正如函数名称Union(...)
所暗示的,我们喜欢根据集合论来思考属性和方面。我们的宇宙是可以用 表示的所有可能字体的集合SFont
。一个SFont
对象代表这个宇宙中的一个子集:如果所有成员都定义良好,则它代表一个单例。如果没有定义成员,则该SFont
对象代表宇宙。如果字体名称为“Arial”,大小为“12 pt”,粗体未定义,则它表示具有“Arial 12 pt non-bold”和“Arial 12 pt 粗体”两个成员的集合。
现在我们从集合论的角度思考,我们可以说,通过使某些方面未定义,方法SFont::Union(...)
扩大了 表示的子集*this
。具体来说,它消除了所有不同意的*this
方面rhs
。如果根本没有达成一致,则所得子集就是整个宇宙。但是等等,如果我们想要迭代计算 n 个单例集的并集,就像我们的文本选择示例中的情况一样,我们该如何开始呢?累积迭代需要从相应操作的单位元素开始。我们可以方便地将std::nullopt
其用作函数的标识元素Union(...)
。union 的单位元素是空集,因此您可以将 std::nullopt 视为本示例中的空集。
通过std::nullopt
充当任何累积算法的方便的通用标识元素,并且通过隐式提供的基类型X
作为std::optional<X>
范围值类型,我们可以将此方法包装到通用算法中。我们称其为tc::accumulatewithfront(...)
,因为迭代从范围的第一个元素开始,而不是从显式的开始元素开始。如果范围为空,std::nullopt
则返回。请注意,我们不再需要辅助函数Union(std::optional<SFont>&, SFont)
,因为它隐含在 的定义中tc::accumulatewithfront(...)
:
当我们想知道将一种字体应用在另一种字体上是否会产生任何影响时,作为字体子集的表示的概念SFont
也很有用:我们现在可以将这个问题表述为谓词IsSupersetOf(...)
。以下是两种结果相同的实现,尽管其中一种比另一种使用更多的内存和更多的操作:
虽然IsSupersetOf(...)
其本身在某些情况下可能已经有用,但在我们的软件中,我们需要解决一个密切相关但更棘手的问题:我们需要从一种字体相对于另一种字体提取相关信息,以便存储它,例如,作为默认值或用于快速访问。一个典型的例子是用户将任意部分定义的字体应用于某个已经具有字体的标签。我们希望避免存储任何冗余的、不必要的信息,因为这会抑制我们的分层字体组合,请参见表达式(3)。同样,您可能希望在应用到 PowerPoint 之前消除字体中任何不必要的方面(就像我们软件中的情况一样),因为对 PowerPoint 的调用可能会很昂贵。输入最小化:
注意:最小化不是设置差异!
您可能注意到该SFont::Union(...)
方法的结果实际上并不是作为参数传递的两个子集的集合并。相反,它是包含两个子集的最小集合,并且可以用我们的定义来表示SFont
。这就是本文中介绍的数据结构的美妙之处:它非常简单但非常有用。我们通过删除任何“混合”的实际值来实现简单性。出于我们的实际目的,这是一个合理的权衡。
当试图掌握 的这种稍微奇怪的行为时,我发现从 n 维空间(在我们的玩具示例中 n==3)的角度来看SFont::Union(...)
很有帮助且具有说明性,该空间由其“名称”、“大小”等方面组成SFont
”和“大胆”。完全定义的字体相当于一个点。如果字体的某一方面是“混合的”或“未定义的”,则部分定义的SFont
对象可以被视为所有可能字体的空间中的一条线。去掉另一个方面,你就剩下了一架飞机。当没有留下任何方面时,生成的SFont
对象代表整个空间。在这个空间中,operator<<(lhs, rhs)
如上面介绍的 (4) 计算 lhs 到 rhs 的投影。
如果您有任何反馈,我很高兴收到您的来信。请随时告诉我您对部分定义属性的看法!
接下来:将相同的想法应用到设计中,SLineFormat
结果并不像您想象的那么简单。
24小时免费咨询
请输入您的联系电话,座机请加区号