你们应该都看见过我说的边框吧?
当你显示一个字符串,但是字符串中的某些字符不被当前字体所支持,这个时候,你将会看到系统会使用一个方框来代替这个不可显示的字符。
让我们回忆我们的例子程序,然后,我们在PaintContent函数中添加如下的代码:
在上面的代码中,我们构造了一个比较有意思的字符串:它的前三个字符是罗马字母的”ABC”,然后是西里尔的”ABC”,最后是泰文的”ABC”。
如果你执行这个程序,你会看到除了罗马字母ABC之外,其他的字母都显示为了方框,为什么?
因为系统字体(SYSTEM Font)只支持有限的几个字符集。
有人会问了,那怎样才能选择正确的字体呢?万一字符串中还包括韩文或者日文怎么办?遗憾的是,没有一个单一字体能包含Unicode字符集中的所有字符。那有什么好办法解决这个问题吗?
解决之道:字体链接(Font Linking)
字体链接可以用来将字符串打散成不同的片段,然后每个片段都可以使用一种合适的字体来显示。
IMLangFontLink2接口中的一些方法可以用来打散字符串。GetStrCodePages这个方法可以接收一个字符串,然后将字符串打散为不同的片段,在同一个片段中的所有字符都可以使用相同的字体来显示,然后,可以使用MapFont方法来创建字体。
好了,让我们来编写一个新版本的TextOut函数,这个函数使用了上面所提到的字体链接概念。我们会一步一步地编写这个函数,咱们先从下面的函数开始:
当我们搞清楚了默认字体所支持的代码页之后,我们将会调用GetStrCodePages来将字符串打散为一个一个的片段,对于每个片段,我们会创建与之对应的字体并以正确的方式,在正确的地方,来显示这个字符串片段,直至所有字符串片段都得到这样的处理。
接下来就是一些细节方面的优化工作了。
首先,什么是”正确的地方”?我们希望下一个块在前一个块停止的地方继续。为此,我们利用了 TA_UPDATECP文本对齐样式,它表示 GDI 应该在当前位置绘制文本,并将当前位置更新到绘制文本的末尾(因此,在下一个块的位置)。
因此,我们需要设置DC的当前位置,并设置文本模式为TA_UPDATECP,如下图所示:
然后,我们只是将坐标”0,0″传递给TextOut函数,因为如果文本对齐模式是TA_UPDATECP,则TextOut函数会忽略掉传递给它的坐标值,它会始终在当前位置进行绘制。
当然,我们不能像这样乱搞DC的设置。 如果调用者没有设置 TA_UPDATECP,那么调用者就不会期望我们干预当前位置。 因此,我们必须保存原始位置并在之后恢复它(以及原始文本对齐方式)。
接下来是一个改进:我们应该利用GetStrCodePages的第二个参数,它指定了我们希望使用的代码页。显然,我们应该更喜欢使用我们想要使用的字体支持的代码页,这样如果字符可以直接以该字体显示,那么我们就不应该映射替代字体。代码修改如下:
当然,你可能想知道这个神奇的pfl是从哪里来的。它来自 mlang 中的Multilanguage对象,如下图所示:
当然,我们一直忽略的所有错误都需要处理。 如果在我们已经通过几个块之后遇到错误,这确实会产生一个大问题。 我们应该做什么?
我将通过以原始字体、方框等等绘制字符串来处理错误。 我们不能擦除已经绘制的字符,也不能只绘制字符串的一半(因为我们的调用者不知道从哪里恢复)。 所以我们只用原始字体绘制并希望这样能得到最好的结果。至少它没有比字体链接之前更糟。
将所有这些改进放在一起,代码变为了下面的样子:
最后,我们可以将整个操作包装在一个辅助函数中,该函数首先尝试使用字体链接,如果失败,则仅以老式方式绘制文本。
好的,现在我们有了带有的字回调函数版本的字体链接TextOut,我们可以继续调整我们的 PaintContent函数,如下图所示:
再次运行程序,我们可以看到,显示的字符串没有再出现方框。
我没有做的一项改进是:避免每次我们想要绘制文本时都创建 IMlangFontLink2 指针。 在实际的 工程中,你可能会为每个绘图上下文(可能是每个窗口)创建一次多语言对象,然后重新使用它以避免每次要绘制字符串时都遍历整个对象创建代码路径。
总结
古老的API,古老的技术,但是,它确实能解决问题。
还是点个赞吧。
最后
Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《How to display a string without those ugly boxes》
最近我写了个东西
正如你们所知道的,拓扑梅尔智慧办公平台(Topomel Box)是一款绿色软件,主要面向经常使用电脑的朋友。它提供了各种提升办公效率的小功能,同时操作上尽可能地简单方便。
我想:你值得拥有。