C++利用Uniscribe举办文字自动换行的计较和渲染
副标题#e#
Uniscribe是Windows 2000以来就存在于WinAPI中的一个库。这个库可以或许提供应我们关于字符串渲染的许多信息,譬如说那边可以换行啦,渲染的时候字符的顺序应该是什么样子啦,尚有每一个字符的巨细什么的。关于Uniscribe的资料可以在http://msdn.microsoft.com/en-us/library/windows/desktop/dd374091(v=vs.85).aspx看到。
在利用Uniscribe之前,我们先看看操作Uniscribe我们可以做到什么样的结果:
本栏目
通过Uniscribe,我们可以得到把各类差异巨细的字符串殽杂在一起渲染的时候所需要的所有数据,甚至可以再大度的处所换行,譬如说这里:
#p#副标题#e#
虽然,渲染的部门不包括在Uniscribe内里,只不外Uniscribe汇报我们的信息可以让我们直接计较出渲染每一小段字符串的位置。虽然,这也就足够了。下面我来先容一下Uniscribe的几个函数的浸染。
首先,我们需要留意的是,Uniscribe一次只处理惩罚一行字符串。我们当然可以把多行字符串一次性丢给Uniscribe举办计较,可是获得的功效处理惩罚起来要坚苦得多。所以我们一次只给Uniscribe一行的字符串。此刻我们需要渲染一个带有多种名目标一行字符串。首先我们需要知道这些字符串可以被分为几多段。这在那些从右到左阅读的文字(譬如说阿拉伯文)出格重要,并且这也是一个出格巨大的话题,在这里我就不讲了,我们假设我们只处理惩罚从左到右的字符串。
于是我们第一个碰着的函数就是ScriptItemize。
HRESULT ScriptItemize( _In_ const WCHAR *pwcInChars, _In_ int cInChars, _In_ int cMaxItems, _In_opt_ const SCRIPT_CONTROL *psControl, _In_opt_ const SCRIPT_STATE *psState, _Out_ SCRIPT_ITEM *pItems, _Out_ int *pcItems );
由于我们不处理惩罚从右到左的字符串渲染,也不处理惩罚把数字酿成参差不齐名目标结果(譬如在某些邪恶的帝国主义国度12345被表告竣12,345),因此这个函数的psControl和psState参数我们都可以给NULL。这个时候我们需要首先为SCRIPT_ITEM数组分派空间。由于一个字符串的item最多就是字符数量那么多个,所以我们要先建设一个cInChars+1那么长的SCRIPT_ITEM数组。在挪用了这个函数之后,*pcItems+1的功效就是pItems内里的有效长度了。为什么pItems的长度老是要+1呢?因为SCRIPT_ITEM内里有一个很有用的成员叫做iCharPos,这个成员汇报我们这个item是从字符串的什么处所开始的。那长度呢?自然是用下一个SCRIPT_ITEM的cCharPos去剪了。那么最后一个item怎么办呢?所以ScriptItemize给了我们特另外一个末了item,让我们老是可以利便的这么减……出格的蛋疼……
好了,此刻我们把一行字符串分成了各个item。此刻第一个问题就来了,一行字符串内里大概有各类差异的字体的样式,接下来怎么办呢?我们要同时用item的界线和样式的界线来切割这个字符串,让每一个字符串的片断都完全被某个item包括,而且片断的所有字符都有一样的样式。这听起来仿佛很巨大,我来举个例子:
譬如我们有一个字符串长成下面这个样子:
This parameter (foo) is optional
然后ScriptItemize汇报我们这个字符串一共分为3个片断(这个分别虽然是我胡扯的,我只是举个例子):
This parameter
(foo)
is optional
所以,字体的样式和ScriptItemize的功效就把这个字符串分成了下面的五段:
This
parameter
(foo)
is
optional
是不是听起来很直观呢?可是代码写起来照旧较量贫苦的,不外其实说贫苦也不贫苦,只需要约莫十行阁下就可以搞定了。在MSDN内里,这五段的“段”叫做“run”可能是“range”。
此刻,我们拿起一个run,送进一个叫做ScriptShape的函数内里:
HRESULT ScriptShape( _In_ HDC hdc, _Inout_ SCRIPT_CACHE *psc, _In_ const WCHAR *pwcChars, _In_ int cChars, _In_ int cMaxGlyphs, _Inout_ SCRIPT_ANALYSIS *psa, _Out_ WORD *pwOutGlyphs, _Out_ WORD *pwLogClust, _Out_ SCRIPT_VISATTR *psva, _Out_ int *pcGlyphs );
这个函数可以汇报我们,这一堆wchar_t可以被如何支解成glyph。这里我们要留意的是,glyph的数量和wchar_t的数量并不沟通。所以在挪用这个函数的时候,我们要先猜一个长度来分派空间。MSDN汇报我们,我们可以先让cMaxGlyphs = cChars*1.5 + 16。
在上面的参数里,SCRIPT_ANALYSIS其实就是SCRIPT_ITEM::a。由于一个run必定是完整的属于一个item的,因此SCRIPT_ITEM就可以直接从上一个函数的功效得到了。然后这个函数汇报我们三个信息:
1、pwOutGlyphs:这个字符串一共有几多glyph构成。
2、psva:每一个glyph的属性是什么。
3、pwLogClust:wchar_t(术语叫unicode code point)是如何跟glyph对应起来的。
#p#分页标题#e#
在这里表明一下glyph是什么意思。glyph其实就是字体内里的一个“图”。一个看起来像一个字符的对象,有大概由多个glyph构成,譬如说“á”,其实就占用了两个wchar_t,同时这两个wchar_t具有两个glyph(a和上面的小点)。并且这两个wchar_t在渲染的时候必需被渲染在一起,因此他们至少应该属于同一个range,鼠标在文本框选中的时候,这两个wchar_t必需作为一个整体(后头这些信息可以由ScriptBreak函数给出)。虽然尚有1个wchar_t对多个glyph的环境,可是我此刻一下子找不到。
不只如此,尚有两个wchar_t对一个glyph的环境,譬如说这些字“ ”。固然wchar_t的范畴是0到65536,但这并不代表utf-16只有6万多个字符(实际上是60多万),所以wchar_t其实也是变长的。可是utf-16的编码设计的很好,当我们拿到一个wchar_t的时候,我们通过阅读他们的数字就可以知道这个wchar_t是只有一个code point的、照旧那些两个code point的字的第一个可能是第二个,跟我们以前碰着的MBCS(char/ANSI)完全差异。
因此wchar_t和glyph的对应干系很巨大,大概是一对多、多对一、一对一可能多对多。所以pwLogClust这个数组就出格的重要。MSDN内里有一个例子:
譬如说我们的一个7个wchar_t的字符串被分成4组glyph,对应干系如下:
字符:| c1u1 | c2u1 | c3u1 c3u2 c3u3 | c4u1 c4u2 |
图案:| c1g1 | c2g1 c2g2 c2g3 | c3g1 | c4g1 c4g2 c4g3 |
上面的意思是,第二个字符c2u2被渲染成了3个glyph:c2g1、c2g2和c2g3,而c3u1、c3u2和c3u3三个字符责备归并成了一个glyph:c3g1。这种环境下,pwLogClust[cChars]的内容就是下面这个样子的:
| 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。这个布局我们临时只体贴下面几个成员:
#p#分页标题#e#
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 <GacUI.h> #include <usp10.h> #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 false;} bool operator==(const GOFFSET&, const GOFFSET&){return false;} bool operator!=(const GOFFSET&, const GOFFSET&){return false;} bool operator==(const SCRIPT_LOGATTR&, const SCRIPT_LOGATTR&){return false;} bool operator!=(const SCRIPT_LOGATTR&, const SCRIPT_LOGATTR&){return false;} namespace test { /*********************************************************************** DocumentFragment ***********************************************************************/ class DocumentFragment : public Object { public: bool paragraph; WString font; bool bold; Color color; int size; WString text; Ptr<WinFont> fontObject; DocumentFragment() :paragraph(false) ,bold(false) ,size(0) { } DocumentFragment(Ptr<DocumentFragment> prototype, const WString& _text) :paragraph(prototype->paragraph) ,font(prototype->font) ,bold(prototype->bold) ,color(prototype->color) ,size(prototype->size) ,fontObject(prototype->fontObject) ,text(_text) { } WString GetFingerPrint() { return font+L":"+(bold?L"B":L"N")+L":"+itow(size); } }; int ConvertHex(wchar_t c) { if(L'a'<=c && c<=L'f') return c-L'a'+10; if(L'A'<=c && c<=L'F') return c-L'A'+10; if(L'0'<=c && c<=L'9') return c-L'0'; return 0; } Color ConvertColor(const WString& colorString) { return Color( ConvertHex(colorString[1])*16+ConvertHex(colorString[2]), ConvertHex(colorString[3])*16+ConvertHex(colorString[4]), ConvertHex(colorString[5])*16+ConvertHex(colorString[6]) ); } // 本栏目更多出色内容:http://www.bianceng.cn/Programming/cplus/ void BuildDocumentFragments(const WString& fileName, List<Ptr<DocumentFragment>>& fragments) { fragments.Clear(); WString rawDocument; { FileStream fileStream(fileName, FileStream::ReadOnly); Utf8Decoder decoder; DecoderStream decoderStream(fileStream, decoder); StreamReader reader(decoderStream); rawDocument=reader.ReadToEnd(); } Regex regex(L"<(<tag>s)>(<font>[^:]+):(<bold>[^:]+):(<color>[^:]+):(<size>[^:]+):(<text>/.*?)<//s>|<(<tag>p)/ />"); RegexMatch::List matches; regex.Search(rawDocument, matches); for(int i=0;i<matches.Count();i++) { Ptr<RegexMatch> match=matches[i]; Ptr<DocumentFragment> fragment=new DocumentFragment; fragments.Add(fragment); if(match->Groups()[L"tag"][0].Value()==L"p") { fragment->paragraph=true; } else { WString font=match->Groups()[L"tag"][0].Value(); WString bold=match->Groups()[L"bold"][0].Value(); WString color=match->Groups()[L"color"][0].Value(); WString size=match->Groups()[L"size"][0].Value(); WString text=match->Groups()[L"text"][0].Value(); fragment->font=font; fragment->bold=bold==L"true"; fragment->size=wtoi(size); fragment->color=ConvertColor(color); fragment->text=text; } } } /*********************************************************************** ScriptFragment ***********************************************************************/ struct GlyphData { Array<WORD> glyphs; Array<SCRIPT_VISATTR> glyphVisattrs; Array<int> glyphAdvances; Array<GOFFSET> glyphOffsets; Array<WORD> charCluster; ABC runAbc; GlyphData() { memset(&runAbc, 0, sizeof(runAbc)); } void ClearUniscribeData(int glyphCount, int length) { glyphs.Resize(glyphCount); glyphVisattrs.Resize(glyphCount); glyphAdvances.Resize(glyphCount); glyphOffsets.Resize(glyphCount); charCluster.Resize(length); memset(&runAbc, 0, sizeof(runAbc)); } bool BuildUniscribeData(WinDC* dc, DocumentFragment* documentFragment, SCRIPT_ITEM* scriptItem, SCRIPT_CACHE& scriptCache, const wchar_t* runText, int length) { int glyphCount=glyphs.Count(); bool resizeGlyphData=false; if(glyphCount==0) { glyphCount=(int)(1.5*length+16); resizeGlyphData=true; } { // generate shape information WinDC* dcParameter=0; if(resizeGlyphData) { glyphs.Resize(glyphCount); glyphVisattrs.Resize(glyphCount); charCluster.Resize(length); } while(true) { int availableGlyphCount=0; HRESULT hr=ScriptShape( (dcParameter?dcParameter->GetHandle():NULL), &scriptCache, runText, length, glyphCount, &scriptItem->a, &glyphs[0], &charCluster[0], &glyphVisattrs[0], &availableGlyphCount ); if(hr==0) { glyphCount=availableGlyphCount; break; } else if(hr==E_PENDING) { dcParameter=dc; } else if(hr==E_OUTOFMEMORY) { if(resizeGlyphData) { glyphCount+=length; } else { goto BUILD_UNISCRIBE_DATA_FAILED; } } else { goto BUILD_UNISCRIBE_DATA_FAILED; } } if(resizeGlyphData) { glyphs.Resize(glyphCount); glyphVisattrs.Resize(glyphCount); } } { // generate place information WinDC* dcParameter=0; if(resizeGlyphData) { glyphAdvances.Resize(glyphCount); glyphOffsets.Resize(glyphCount); } while(true) { HRESULT hr=ScriptPlace( (dcParameter?dcParameter->GetHandle():NULL), &scriptCache, &glyphs[0], glyphCount, &glyphVisattrs[0], &scriptItem->a, &glyphAdvances[0], &glyphOffsets[0], &runAbc ); if(hr==0) { break; } else if(hr==E_PENDING) { dcParameter=dc; } else { goto BUILD_UNISCRIBE_DATA_FAILED; } } } return true; BUILD_UNISCRIBE_DATA_FAILED: return false; } }; class ScriptRun : public Object { public: DocumentFragment* documentFragment; SCRIPT_ITEM* scriptItem; int start; int length; const wchar_t* runText; SCRIPT_CACHE scriptCache; Array<SCRIPT_LOGATTR> charLogattrs; int advance; GlyphData wholeGlyph; GlyphData tempGlyph; ScriptRun() :documentFragment(0) ,scriptItem(0) ,start(0) ,length(0) ,scriptCache(0) ,advance(0) { } ~ScriptRun() { ClearUniscribeData(); } void ClearUniscribeData() { if(scriptCache) { ScriptFreeCache(&scriptCache); scriptCache=0; } charLogattrs.Resize(0); advance=0; wholeGlyph.ClearUniscribeData(0, 0); tempGlyph.ClearUniscribeData(0, 0); } bool BuildUniscribeData(WinDC* dc) { ClearUniscribeData(); { // generate break information charLogattrs.Resize(length); HRESULT hr=ScriptBreak( runText, length, &scriptItem->a, &charLogattrs[0] ); if(hr!=0) { goto BUILD_UNISCRIBE_DATA_FAILED; } } dc->SetFont(documentFragment->fontObject); if(!wholeGlyph.BuildUniscribeData(dc, documentFragment, scriptItem, scriptCache, runText, length)) { goto BUILD_UNISCRIBE_DATA_FAILED; } tempGlyph.ClearUniscribeData(wholeGlyph.glyphs.Count(), length); advance=wholeGlyph.runAbc.abcA+wholeGlyph.runAbc.abcB+wholeGlyph.runAbc.abcC; return true; BUILD_UNISCRIBE_DATA_FAILED: ClearUniscribeData(); return false; } int SumWidth(int charStart, int charLength) { int cluster=wholeGlyph.charCluster[charStart]; int nextCluster =charStart+charLength==length ?wholeGlyph.glyphs.Count() :wholeGlyph.charCluster[charStart+charLength]; int width=0; for(int i=cluster;i<nextCluster;i++) { width+=wholeGlyph.glyphAdvances[i]; } return width; } void SearchForLineBreak(int tempStart, int maxWidth, bool firstRun, int& charLength, int& charAdvances) { int width=0; charLength=0; charAdvances=0; for(int i=tempStart;i<=length;) { if(i==length || charLogattrs[i].fSoftBreak==TRUE) { if(width<=maxWidth || (firstRun && charLength==0)) { charLength=i-tempStart; charAdvances=width; } else { return; } } if(i==length) break; int cluster=wholeGlyph.charCluster[i]; int clusterLength=1; while(i+clusterLength<length) { if(wholeGlyph.charCluster[i+clusterLength]==cluster) { clusterLength++; } else { break; } } int nextCluster =i+clusterLength==length ?wholeGlyph.glyphs.Count() :wholeGlyph.charCluster[i+clusterLength]; for(int j=cluster;j<nextCluster;j++) { width+=wholeGlyph.glyphAdvances[j]; } i+=clusterLength; } } bool BuildUniscribeDataTemp(WinDC* dc, int tempStart, int tempLength) { return tempGlyph.BuildUniscribeData(dc, documentFragment, scriptItem, scriptCache, runText+tempStart, tempLength); } }; class ScriptLine : public Object { public: List<Ptr<DocumentFragment>> documentFragments; WString lineText; Array<SCRIPT_ITEM> scriptItems; List<Ptr<ScriptRun>> scriptRuns; void CLearUniscribeData() { scriptItems.Resize(0); scriptRuns.Clear(); } bool BuildUniscribeData(WinDC* dc) { lineText=L""; CLearUniscribeData(); FOREACH(Ptr<DocumentFragment>, fragment, documentFragments.Wrap()) { lineText+=fragment->text; } if(lineText!=L"") { { // itemize a line scriptItems.Resize(lineText.Length()+2); int scriptItemCount=0; HRESULT hr=ScriptItemize( lineText.Buffer(), lineText.Length(), scriptItems.Count()-1, NULL, NULL, &scriptItems[0], &scriptItemCount ); if(hr!=0) { goto BUILD_UNISCRIBE_DATA_FAILED; } scriptItems.Resize(scriptItemCount+1); } { // use item and document fragment information to produce runs // one item is constructed by one or more runs // characters in each run contains the same style int fragmentIndex=0; int fragmentStart=0; for(int i=0;i<scriptItems.Count()-1;i++) { SCRIPT_ITEM* scriptItem=&scriptItems[i]; int start=scriptItem[0].iCharPos; int length=scriptItem[1].iCharPos-scriptItem[0].iCharPos; int currentStart=start; while(currentStart<start+length) { DocumentFragment* fragment=0; int itemRemainLength=length-(currentStart-start); int fragmentRemainLength=0; while(true) { fragment=documentFragments[fragmentIndex].Obj(); fragmentRemainLength=fragment->text.Length()-(currentStart-fragmentStart); if(fragmentRemainLength<=0) { fragmentStart+=fragment->text.Length(); fragmentIndex++; } else { break; } } int shortLength=itemRemainLength<fragmentRemainLength?itemRemainLength:fragmentRemainLength; Ptr<ScriptRun> run=new ScriptRun; run->documentFragment=fragment; run->scriptItem=scriptItem; run->start=currentStart; run->length=shortLength; run->runText=lineText.Buffer()+currentStart; scriptRuns.Add(run); currentStart+=shortLength; } } // for each run, generate shape information FOREACH(Ptr<ScriptRun>, run, scriptRuns.Wrap()) { if(!run->BuildUniscribeData(dc)) { goto BUILD_UNISCRIBE_DATA_FAILED; } } } } return true; BUILD_UNISCRIBE_DATA_FAILED: CLearUniscribeData(); return false; } }; class ScriptParagraph : public Object { public: List<Ptr<ScriptLine>> lines; }; class ScriptDocument : public Object { public: List<Ptr<ScriptParagraph>> paragraphs; }; Ptr<ScriptDocument> BuildScriptParagraphs(List<Ptr<DocumentFragment>>& fragments) { Ptr<ScriptDocument> document=new ScriptDocument; document->paragraphs.Clear(); Regex regex(L"\r\n"); Ptr<ScriptParagraph> currentParagraph; Ptr<ScriptLine> currentLine; Dictionary<WString, Ptr<WinFont>> fonts; FOREACH(Ptr<DocumentFragment>, fragment, fragments.Wrap()) { WString fragmentFingerPrint=fragment->GetFingerPrint(); int index=fonts.Keys().IndexOf(fragmentFingerPrint); if(index==-1) { fragment->fontObject=new WinFont(fragment->font, fragment->size, 0, 0, 0, (fragment->bold?FW_BOLD:FW_NORMAL), false, false, false, true); fonts.Add(fragmentFingerPrint, fragment->fontObject); } else { fragment->fontObject=fonts.Values()[index]; } if(!currentParagraph) { currentParagraph=new ScriptParagraph; document->paragraphs.Add(currentParagraph); } if(fragment->paragraph) { currentParagraph=0; currentLine=0; } else { RegexMatch::List matches; regex.Split(fragment->text, true, matches); for(int i=0;i<matches.Count();i++) { Ptr<RegexMatch> match=matches[i]; if(i>0) { currentLine=0; } if(!currentLine) { currentLine=new ScriptLine; currentParagraph->lines.Add(currentLine); } currentLine->documentFragments.Add(new DocumentFragment(fragment, match->Result().Value())); } } } HDC hdc=CreateCompatibleDC(NULL); WinProxyDC dc; dc.Initialize(hdc); FOREACH(Ptr<ScriptParagraph>, paragraph, document->paragraphs.Wrap()) { FOREACH(Ptr<ScriptLine>, line, paragraph->lines.Wrap()) { line->BuildUniscribeData(&dc); } } DeleteDC(hdc); return document; } /*********************************************************************** TestWindow ***********************************************************************/ class TestWindow : public GuiWindow { protected: Ptr<ScriptDocument> document; Ptr<WinFont> messageFont; void element_Rendering(GuiGraphicsComposition* composition, GuiGDIElementEventArgs& arguments) { WinDC* dc=arguments.dc; Rect bounds=arguments.bounds; if(document) { int x=bounds.Left()+10; int y=bounds.Top()+10; int w=bounds.Width()-20; int h=bounds.Height()-10; int cx=0; int cy=0; const int lineDistance=5; const int paragraphDistance=10; FOREACH(Ptr<ScriptParagraph>, paragraph, document->paragraphs.Wrap()) { if(cy>=h) break; FOREACH(Ptr<ScriptLine>, line, paragraph->lines.Wrap()) { if(line->scriptRuns.Count()==0) { // if this line doesn't contains any run, skip and render a blank line cy+=line->documentFragments[0]->size+lineDistance; } else { // render this line into linces with auto line wrapping int startRun=0; int startRunOffset=0; int lastRun=0; int lastRunOffset=0; int currentWidth=0; while(startRun<line->scriptRuns.Count()) { int currentWidth=0; bool firstRun=true; // search for a range to fit in the given width for(int i=startRun;i<line->scriptRuns.Count();i++) { int charLength=0; int charAdvances=0; ScriptRun* run=line->scriptRuns[i].Obj(); run->SearchForLineBreak(lastRunOffset, w-currentWidth, firstRun, charLength, charAdvances); firstRun=false; if(charLength==run->length-lastRunOffset) { lastRun=i+1; lastRunOffset=0; currentWidth+=charAdvances; } else { lastRun=i; lastRunOffset=lastRunOffset+charLength; break; } } // if the range is empty, than this should be the end of line, ignore it if(startRun<lastRun || (startRun==lastRun && startRunOffset<lastRunOffset)) { // calculate the max line height in this range; int maxHeight=0; for(int i=startRun;i<=lastRun && i<line->scriptRuns.Count();i++) { int size=line->scriptRuns[i]->documentFragment->size; if(maxHeight<size) { maxHeight=size; } } // render all runs inside this range for(int i=startRun;i<=lastRun && i<line->scriptRuns.Count();i++) { ScriptRun* run=line->scriptRuns[i].Obj(); int start=i==startRun?startRunOffset:0; int end=i==lastRun?lastRunOffset:run->length; int length=end-start; Color color=run->documentFragment->color; dc->SetFont(run->documentFragment->fontObject); dc->SetTextColor(RGB(color.r, color.g, color.b)); dc->DrawBuffer(x+cx, y+cy+(maxHeight-run->documentFragment->size), run->runText+start, length); cx+=run->SumWidth(start, length); } cx=0; cy+=maxHeight+lineDistance; } startRun=lastRun; startRunOffset=lastRunOffset; } } } cy+=paragraphDistance; } } else { dc->SetFont(messageFont); WString message=L"Initializing uniscribe data..."; SIZE size=dc->MeasureString(message); int x=bounds.Left()+(bounds.Width()-size.cx)/2; int y=bounds.Top()+(bounds.Height()-size.cy)/2; dc->DrawString(x, y, message); } } public: TestWindow() :GuiWindow(GetCurrentTheme()->CreateWindowStyle()) { SetText(L"GacUISrc Test Application"); SetClientSize(Size(640, 480)); GetBoundsComposition()->SetPreferredMinSize(Size(320, 240)); MoveToScreenCenter(); { GuiGDIElement* element=GuiGDIElement::Create(); element->Rendering.AttachMethod(this, &TestWindow::element_Rendering); GuiBoundsComposition* composition=new GuiBoundsComposition; composition->SetOwnedElement(element); composition->SetAlignmentToParent(Margin(0, 0, 0, 0)); GetContainerComposition()->AddChild(composition); messageFont=new WinFont(L"Segoe UI", 56, 0, 0, 0,FW_NORMAL, false, false, false, true); } GetApplication()->InvokeAsync([=]() { List<Ptr<DocumentFragment>> fragments; BuildDocumentFragments(L"..\\GacUISrcCodepackedTest\\Resources\\document.txt", fragments); Ptr<ScriptDocument> scriptDocument=BuildScriptParagraphs(fragments); GetApplication()->InvokeInMainThreadAndWait([=]() { document=scriptDocument; }); }); } }; } using namespace test; void GuiMain() { TestWindow window; GetApplication()->Run(&window); }