| 0 | 1 | 4 4 4 | 5 5 |
连续的数字相同的几个clust说明这些wchar_t是被归到一起的,而且这一组wchar_t的第一个glyph的的序号就是pwLogClust的内容了。那么这一组wchar_t究竟有多少个glyph呢?当然就要看下一组wchar_t的第一个glyph在哪了。
为什么我们需要这些信息呢?因为字符串的长度是按照glyph的长度来计算的!而且接下来我们要介绍的函数ScriptPlace会真的给我们每一个glyph的长度。因此我们在计算换行的时候,我们只能在每一组glyph并且ScriptBreak告诉我们可以换行的那个地方换行,所以当我们拿出一段完整的不会被换行的一个run的子集的时候,我们要在渲染的时候计算长度,就要特别小心glyph和wchar_t的对应关系。因为我们渲染的是一串wchar_t,但是我们的长度是按照glyph计算的,这个对应关系要是乱掉了,要么计算出错,要么渲染的字符选错,总之是很麻烦的。那么ScriptPlace究竟长什么样子呢:
HRESULT ScriptPlace(
_In_ HDC hdc,
_Inout_ SCRIPT_CACHE *psc,
_In_ const WORD *pwGlyphs,
_In_ int cGlyphs,
_In_ const SCRIPT_VISATTR *psva,
_Inout_ SCRIPT_ANALYSIS *psa,
_Out_ int *piAdvance,
_Out_ GOFFSET *pGoffset,
_Out_ ABC *pABC
);
这就是那个传说中的帮我们计算glyph大小的函数了。其中pwGlyphs就是我们刚刚从ScriptShape函数拿到的pwOutGlyphs,而psa还是那个psa,psva也还是那个psva。接下来的piAdvance数组告诉我们每一个glyph的长度,pGoffset这个是每一个glyph的偏移量(还记得“á”上面的那个小点吗),pABC是整一个run的长度。至于ABC的三个长度我们并不用管,因为我们需要的是pABC里面三个长度的和。而且这个和跟piAdvance的所有数字加起来一样。
现在我们拿到了所有glyph的尺寸信息,和他们的分组情况,最后就是知道字符串的一些属性了,譬如说在哪里可以换行。为什么要知道这些呢?譬如说我们有一个字符串叫做
c:\ThisIsAFolder\ThisIsAFile.txt
然后我们渲染字符串的位置可以容纳下“c:\ThisIsAFolder\”,却不能容纳完整的“c:\ThisIsAFolder\ThisIsAFile”。这个时候,ScriptBreak函数就可以告诉我们,一个优美的换行可以在斜杠“\”的后面产生。让我们来看看这个ScriptBreak函数的真面目:
HRESULT ScriptBreak(
_In_ const WCHAR *pwcChars,
_In_ int cChars,
_In_ const SCRIPT_ANALYSIS *psa,
_Out_ SCRIPT_LOGATTR *psla
);
这个函数告诉我们每一个wchar_t对应的SCRIPT_LOGATTR。这个结构我们暂时只关心下面几个成员:
1、fSoftBreak:可以被换行的位置。譬如说上面那个美妙的换行在“\”处,就是因为接下来的ThisIsAFile的第一个字符“T”的fSoftBreak是TRUE。
2、fCharStop和fWordStop:告诉我们每一个wchar_t是不是char或者word的第一个code point(参考那些一个字有两个wchar_t那么长的 )。
现在我们距离大功告成已经很近了。我们在渲染的时候,就一个run一个run的渲染。当我们发现一行剩余的空间不够容纳一个完整的run的时候,我们就可以用ScriptBreak告诉我们的信息,把这个run看成若干个可以被切开的段,然后用ScriptPlace告诉我们的piAdvance算出每一个切开的小段落的长度,然后尽可能多的完整渲染这些段。
上面这段话虽然很简单,但是实际上需要注意的事情特别多,譬如说那个复杂的wchar_t和glyph的关系。我们通过piAdvance计算出可以一次性渲染的glyph有多少个,再把通过ScriptShape告诉我们的pwLogClust把这些glyph换算成对应wchar_t的范围。最后再把他们送进TextOut函数里,如果你用的是GDI的话。每次渲染完一些glyph,x坐标就要偏移他们的piAdvances的和。
如果把上面这些事情全部做完的话,我们就已经完整的渲染出一行带有复杂结构的文字了。
=========================================================
最后我贴上这个程序的代码。这个程序使用GacUI编写,中间的部分使用GDI进行渲染。由于这只是个临时代码,会从codeplex上删掉,所以把代码留在这里,给有需要的人阅读。
代码里面用到的这个叫document.txt的文件,可以在GacUI的Codeplex页面上下载代码后,在(\Libraries\GacUI\GacUISrc\GacUISrcCodepackedTest\Resources\document.txt)找到
#include
#include
#pragma comment(lib, "usp10.lib")
using namespace vl::collections;
using namespace vl::stream;
using namespace vl::regex;
using namespace vl::presentation::windows;
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsGDIRenderer();
}
/***********************************************************************
Uniscribe
***********************************************************************/
bool operator==(const SCRIPT_ITEM&, const SCRIPT_ITEM&){return false;}
bool operator!=(const SCRIPT_ITEM&, const SCRIPT_ITEM&){return false;}
bool operator==(const SCRIPT_VISATTR&, const SCRIPT_VISATTR&){return false;}
bool operator!=(const SCRIPT_VISATTR&, const SCRIPT_VISATTR&){return