青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆-341  評論-2670  文章-0  trackbacks-0

Uniscribe是Windows 2000以來就存在于WinAPI中的一個庫。這個庫能夠提供給我們關(guān)于字符串渲染的很多信息,譬如說哪里可以換行啦,渲染的時候字符的順序應該是什么樣子啦,還有每一個字符的大小什么的。關(guān)于Uniscribe的資料可以在http://msdn.microsoft.com/en-us/library/windows/desktop/dd374091(v=vs.85).aspx看到。

在使用Uniscribe之前,我們先看看利用Uniscribe我們可以做到什么樣的效果:

image

image

通過Uniscribe,我們可以獲得把各種不同大小的字符串混合在一起渲染的時候所需要的所有數(shù)據(jù),甚至可以再漂亮的地方換行,譬如說這里:

image

當然,渲染的部分不包含在Uniscribe里面,只不過Uniscribe告訴我們的信息可以讓我們直接計算出渲染每一小段字符串的位置。當然,這也就足夠了。下面我來介紹一下Uniscribe的幾個函數(shù)的作用。

首先,我們需要注意的是,Uniscribe一次只處理一行字符串。我們固然可以把多行字符串一次性丟給Uniscribe進行計算,但是得到的結(jié)果處理起來要困難得多。所以我們一次只給Uniscribe一行的字符串。現(xiàn)在我們需要渲染一個帶有多種格式的一行字符串。首先我們需要知道這些字符串可以被分為多少段。這在那些從右到左閱讀的文字(譬如說阿拉伯文)特別重要,而且這也是一個特別復雜的話題,在這里我就不講了,我們假設我們只處理從左到右的字符串。

于是我們第一個遇到的函數(shù)就是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
);

由于我們不處理從右到左的字符串渲染,也不處理把數(shù)字變成亂七八糟格式的效果(譬如在某些邪惡的帝國主義國家12345被表達成12,345),因此這個函數(shù)的psControl和psState參數(shù)我們都可以給NULL。這個時候我們需要首先為SCRIPT_ITEM數(shù)組分配空間。由于一個字符串的item最多就是字符數(shù)量那么多個,所以我們要先創(chuàng)建一個cInChars+1那么長的SCRIPT_ITEM數(shù)組。在調(diào)用了這個函數(shù)之后,*pcItems+1的結(jié)果就是pItems里面的有效長度了。為什么pItems的長度總是要+1呢?因為SCRIPT_ITEM里面有一個很有用的成員叫做iCharPos,這個成員告訴我們這個item是從字符串的什么地方開始的。那長度呢?自然是用下一個SCRIPT_ITEM的cCharPos去剪了。那么最后一個item怎么辦呢?所以ScriptItemize給了我們額外的一個結(jié)尾item,讓我們總是可以方便的這么減……特別的蛋疼……

好了,現(xiàn)在我們把一行字符串分成了各個item。現(xiàn)在第一個問題就來了,一行字符串里面可能有各種不同的字體的樣式,接下來怎么辦呢?我們要同時用item的邊界和樣式的邊界來切割這個字符串,讓每一個字符串的片段都完全被某個item包含,并且片段的所有字符都有一樣的樣式。這聽起來好像很復雜,我來舉個例子:

譬如我們有一個字符串長成下面這個樣子:
This parameter (foo) is optional

然后ScriptItemize告訴我們這個字符串一共分為3個片段(這個劃分當然是我胡扯的,我只是舉個例子):
This parameter
(foo)
is optional

所以,字體的樣式和ScriptItemize的結(jié)果就把這個字符串分成了下面的五段:
This
parameter
(foo)
is
optional

是不是聽起來很直觀呢?但是代碼寫起來還是比較麻煩的,不過其實說麻煩也不麻煩,只需要大約十行左右就可以搞定了。在MSDN里面,這五段的“段”叫做“run”或者是“range”。

現(xiàn)在,我們拿起一個run,送進一個叫做ScriptShape的函數(shù)里面:
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
);

這個函數(shù)可以告訴我們,這一堆wchar_t可以被如何分割成glyph。這里我們要注意的是,glyph的數(shù)量和wchar_t的數(shù)量并不相同。所以在調(diào)用這個函數(shù)的時候,我們要先猜一個長度來分配空間。MSDN告訴我們,我們可以先讓cMaxGlyphs = cChars*1.5 + 16。

在上面的參數(shù)里,SCRIPT_ANALYSIS其實就是SCRIPT_ITEM::a。由于一個run肯定是完整的屬于一個item的,因此SCRIPT_ITEM就可以直接從上一個函數(shù)的結(jié)果獲得了。然后這個函數(shù)告訴我們?nèi)齻€信息:
1、pwOutGlyphs:這個字符串一共有多少glyph組成。
2、psva:每一個glyph的屬性是什么。
3、pwLogClust:wchar_t(術(shù)語叫unicode code point)是如何跟glyph對應起來的。

在這里解釋一下glyph是什么意思。glyph其實就是字體里面的一個“圖”。一個看起來像一個字符的東西,有可能由多個glyph組成,譬如說“á”,其實就占用了兩個wchar_t,同時這兩個wchar_t具有兩個glyph(a和上面的小點)。而且這兩個wchar_t在渲染的時候必須被渲染在一起,因此他們至少應該屬于同一個range,鼠標在文本框選中的時候,這兩個wchar_t必須作為一個整體(后面這些信息可以由ScriptBreak函數(shù)給出)。當然還有1個wchar_t對多個glyph的情況,但是我現(xiàn)在一下子找不到。

不僅如此,還有兩個wchar_t對一個glyph的情況,譬如說這些字“? ”。雖然wchar_t的范圍是0到65536,但這并不代表utf-16只有6萬多個字符(實際上是60多萬),所以wchar_t其實也是變長的。但是utf-16的編碼設計的很好,當我們拿到一個wchar_t的時候,我們通過閱讀他們的數(shù)字就可以知道這個wchar_t是只有一個code point的、還是那些兩個code point的字的第一個或者是第二個,跟我們以前遇到的MBCS(char/ANSI)完全不同。

因此wchar_t和glyph的對應關(guān)系很復雜,可能是一對多、多對一、一對一或者多對多。所以pwLogClust這個數(shù)組就特別的重要。MSDN里面有一個例子:

譬如說我們的一個7個wchar_t的字符串被分成4組glyph,對應關(guān)系如下:
字符:| 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]的內(nèi)容就是下面這個樣子的:
| 0 | 1 | 4 4 4 | 5 5 |

連續(xù)的數(shù)字相同的幾個clust說明這些wchar_t是被歸到一起的,而且這一組wchar_t的第一個glyph的的序號就是pwLogClust的內(nèi)容了。那么這一組wchar_t究竟有多少個glyph呢?當然就要看下一組wchar_t的第一個glyph在哪了。

為什么我們需要這些信息呢?因為字符串的長度是按照glyph的長度來計算的!而且接下來我們要介紹的函數(shù)ScriptPlace會真的給我們每一個glyph的長度。因此我們在計算換行的時候,我們只能在每一組glyph并且ScriptBreak告訴我們可以換行的那個地方換行,所以當我們拿出一段完整的不會被換行的一個run的子集的時候,我們要在渲染的時候計算長度,就要特別小心glyph和wchar_t的對應關(guān)系。因為我們渲染的是一串wchar_t,但是我們的長度是按照glyph計算的,這個對應關(guān)系要是亂掉了,要么計算出錯,要么渲染的字符選錯,總之是很麻煩的。那么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大小的函數(shù)了。其中pwGlyphs就是我們剛剛從ScriptShape函數(shù)拿到的pwOutGlyphs,而psa還是那個psa,psva也還是那個psva。接下來的piAdvance數(shù)組告訴我們每一個glyph的長度,pGoffset這個是每一個glyph的偏移量(還記得“á”上面的那個小點嗎),pABC是整一個run的長度。至于ABC的三個長度我們并不用管,因為我們需要的是pABC里面三個長度的和。而且這個和跟piAdvance的所有數(shù)字加起來一樣。

現(xiàn)在我們拿到了所有g(shù)lyph的尺寸信息,和他們的分組情況,最后就是知道字符串的一些屬性了,譬如說在哪里可以換行。為什么要知道這些呢?譬如說我們有一個字符串叫做
c:\ThisIsAFolder\ThisIsAFile.txt

然后我們渲染字符串的位置可以容納下“c:\ThisIsAFolder\”,卻不能容納完整的“c:\ThisIsAFolder\ThisIsAFile”。這個時候,ScriptBreak函數(shù)就可以告訴我們,一個優(yōu)美的換行可以在斜杠“\”的后面產(chǎn)生。讓我們來看看這個ScriptBreak函數(shù)的真面目:
HRESULT ScriptBreak(
  _In_   const WCHAR *pwcChars,
  _In_   int cChars,
  _In_   const SCRIPT_ANALYSIS *psa,
  _Out_  SCRIPT_LOGATTR *psla
);

這個函數(shù)告訴我們每一個wchar_t對應的SCRIPT_LOGATTR。這個結(jié)構(gòu)我們暫時只關(guān)心下面幾個成員:
1、fSoftBreak:可以被換行的位置。譬如說上面那個美妙的換行在“\”處,就是因為接下來的ThisIsAFile的第一個字符“T”的fSoftBreak是TRUE。
2、fCharStop和fWordStop:告訴我們每一個wchar_t是不是char或者word的第一個code point(參考那些一個字有兩個wchar_t那么長的? )。

現(xiàn)在我們距離大功告成已經(jīng)很近了。我們在渲染的時候,就一個run一個run的渲染。當我們發(fā)現(xiàn)一行剩余的空間不夠容納一個完整的run的時候,我們就可以用ScriptBreak告訴我們的信息,把這個run看成若干個可以被切開的段,然后用ScriptPlace告訴我們的piAdvance算出每一個切開的小段落的長度,然后盡可能多的完整渲染這些段。

上面這段話雖然很簡單,但是實際上需要注意的事情特別多,譬如說那個復雜的wchar_t和glyph的關(guān)系。我們通過piAdvance計算出可以一次性渲染的glyph有多少個,再把通過ScriptShape告訴我們的pwLogClust把這些glyph換算成對應wchar_t的范圍。最后再把他們送進TextOut函數(shù)里,如果你用的是GDI的話。每次渲染完一些glyph,x坐標就要偏移他們的piAdvances的和。

如果把上面這些事情全部做完的話,我們就已經(jīng)完整的渲染出一行帶有復雜結(jié)構(gòu)的文字了。

=========================================================

最后我貼上這個程序的代碼。這個程序使用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])
            );
    }

    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);
}

posted on 2012-11-06 06:34 陳梓瀚(vczh) 閱讀(5170) 評論(5)  編輯 收藏 引用 所屬分類: C++2DGacUI

評論:
# re: C++使用Uniscribe進行文字自動換行的計算和渲染 2012-11-06 20:31 | 陳昱(CY)
文字果然好復雜。。。  回復  更多評論
  
# re: C++使用Uniscribe進行文字自動換行的計算和渲染 2012-11-07 00:00 | iunkown
好厲害。文本排版果然很復雜  回復  更多評論
  
# re: C++使用Uniscribe進行文字自動換行的計算和渲染[未登錄] 2012-11-08 06:08 | megax
我覺得排版這東西還得自己來做, 記得Uniscribe的主要目的是繪制復雜字形(帶有連筆)的, 排版應該是副產(chǎn)物:-), 現(xiàn)在應該被directwrite代替了吧!  回復  更多評論
  
# re: C++使用Uniscribe進行文字自動換行的計算和渲染 2012-11-09 07:36 | 陳梓瀚(vczh)
@megax
沒有這些信息自己根本不可能做好的。排版的目的太多了,譬如說正確的順序顯示阿拉伯文字啦。舉個例子,假設abcde是LTR,ABCDE是RTL,那么字符串ABCDE abcde會被顯示為abcde EDCBA。這種事情都要自己來那太他媽麻煩了,所以讓uniscribe先把這些亂七八糟的東西先算好,然后自己來確定文字的pixel位置就行了。  回復  更多評論
  
# re: C++使用Uniscribe進行文字自動換行的計算和渲染 2013-08-18 22:06 | bombless
Uniscribe那個東西好像在《Windows編程默示錄》里面教過……  回復  更多評論
  
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            99av国产精品欲麻豆| 欧美aⅴ一区二区三区视频| 亚洲精品国产精品国产自| 久久综合给合久久狠狠色| 在线观看一区视频| 亚洲国产裸拍裸体视频在线观看乱了中文 | 亚洲欧美日韩国产另类专区| 亚洲一区国产精品| 国内精品久久久久久| 欧美成人资源| 欧美小视频在线观看| 久久精品毛片| 欧美黑人一区二区三区| 亚洲性人人天天夜夜摸| 欧美一级二区| 亚洲精品社区| 午夜精品网站| 日韩午夜高潮| 亚洲欧美一级二级三级| 亚洲成人直播| 亚洲一二三级电影| 亚洲国产第一页| 亚洲视屏在线播放| 亚洲国产片色| 亚洲一区综合| 亚洲人成亚洲人成在线观看| 亚洲一区二区在线播放| 亚洲第一页中文字幕| 中国女人久久久| 亚洲国产精品一区二区第一页| 一区二区三区产品免费精品久久75 | 欧美三级午夜理伦三级中文幕| 久久黄色小说| 欧美午夜无遮挡| 亚洲第一搞黄网站| 国内久久婷婷综合| 亚洲一区免费网站| 夜夜精品视频| 欧美 日韩 国产 一区| 欧美一区二区三区久久精品茉莉花| 老司机67194精品线观看| 久久av二区| 国产精品久久久久永久免费观看 | 一本色道婷婷久久欧美| 久久人人爽国产| 久久久久综合| 国产伦精品一区二区三区免费 | 99re66热这里只有精品4| 欧美亚洲在线| 欧美中文在线免费| 国产精品一区二区久久久| 99精品欧美一区| 日韩一级大片在线| 欧美国产日韩一区| 欧美成人中文| 亚洲娇小video精品| 久久蜜桃精品| 麻豆freexxxx性91精品| 激情五月婷婷综合| 久久久久国产精品一区三寸| 久久国产精品一区二区三区四区| 国产精品免费在线| 亚洲免费婷婷| 久久久久久久网站| 国内精品久久久久影院优| 久久精品免费电影| 欧美 日韩 国产精品免费观看| 一区免费观看视频| 另类av导航| 亚洲欧洲日产国产综合网| 亚洲精品孕妇| 国产精品v欧美精品∨日韩| av成人免费观看| 午夜伦理片一区| 国产一区二区三区在线播放免费观看 | 亚洲性视频h| 国产精品一区二区男女羞羞无遮挡| 亚洲伊人网站| 久久综合色婷婷| 亚洲精品社区| 国产精品区免费视频| 欧美在线高清| 亚洲国产精品美女| 亚洲欧美综合| 在线观看成人一级片| 欧美日韩国产天堂| 亚洲欧美在线免费观看| 欧美成人a视频| 中文国产一区| 伊人激情综合| 欧美揉bbbbb揉bbbbb| 性欧美精品高清| 亚洲人午夜精品| 伊人激情综合| 欧美日韩在线不卡一区| 欧美在线你懂的| 亚洲欧洲另类| 久久人人爽爽爽人久久久| 亚洲久久在线| 韩日视频一区| 欧美视频成人| 久久手机免费观看| 亚洲一区二区动漫| 亚洲第一主播视频| 欧美国产先锋| 久久激情五月丁香伊人| 亚洲国产视频a| 久久蜜桃av一区精品变态类天堂| 日韩手机在线导航| 激情婷婷久久| 国产亚洲精品自拍| 欧美性猛交xxxx免费看久久久| 久久夜色精品国产| 小处雏高清一区二区三区| 一本久久综合亚洲鲁鲁五月天| 免费日本视频一区| 久久精品国产亚洲aⅴ| 中文在线不卡视频| 亚洲人精品午夜| 在线不卡中文字幕播放| 国产情人综合久久777777| 欧美日韩美女一区二区| 你懂的亚洲视频| 久久天堂国产精品| 久久国产精品亚洲77777| 亚洲一区在线播放| 一区二区三区高清在线| 最新成人av在线| 亚洲高清不卡一区| 欧美高清视频在线播放| 久久久噜噜噜久噜久久| 欧美在线|欧美| 欧美一区二区国产| 欧美一区二区福利在线| 亚洲免费网站| 性色av一区二区三区红粉影视| 在线中文字幕不卡| 亚洲视频在线观看免费| 一区二区三区www| 亚洲图片欧美一区| 亚洲视频久久| 亚洲网在线观看| 亚洲免费一在线| 欧美一区二区三区四区在线观看| 午夜亚洲福利| 久久久免费精品视频| 久久综合精品国产一区二区三区| 久久青青草综合| 欧美成人免费一级人片100| 欧美激情日韩| 日韩午夜激情| 亚洲小说欧美另类婷婷| 午夜日韩在线| 玖玖精品视频| 欧美激情在线| 国产精品视频一区二区三区| 国产欧美日韩不卡免费| 好吊成人免视频| 日韩亚洲一区二区| 午夜精品在线| 欧美电影在线观看| av成人激情| 久久精品欧美日韩| 欧美成人一品| 欧美日韩黄色大片| 国产亚洲成av人在线观看导航| 亚洲第一精品电影| 亚洲在线观看免费| 久久天天躁狠狠躁夜夜爽蜜月| 亚洲第一久久影院| 亚洲一级电影| 免费成人你懂的| 国产精品久久久久久久一区探花| 国产精品一级在线| 欧美国产91| 一本久道久久综合婷婷鲸鱼| 一区二区精品在线| 久久免费99精品久久久久久| 99视频一区二区三区| 久久国产主播精品| 欧美日韩视频在线观看一区二区三区| 国产欧美 在线欧美| 91久久久久久国产精品| 欧美在线综合| 日韩系列欧美系列| 可以看av的网站久久看| 国产精品免费一区二区三区观看| 亚洲国产精品久久91精品| 亚洲欧美视频在线观看| 亚洲电影免费观看高清| 性色一区二区三区| 欧美婷婷久久| 亚洲精品小视频| 久久综合九色九九| 亚洲女优在线| 国产精品扒开腿做爽爽爽视频| …久久精品99久久香蕉国产| 欧美一区日本一区韩国一区| 亚洲人被黑人高潮完整版| 在线视频日韩精品|