Symbian程序動態加載TTF字體使用小結
圖片看不到,這次文檔已上傳到了百度文庫,需要的可以去百度文庫查看完整版 http://wenku.baidu.com/view/c9ac1f1f650e52ea551898eb.html
Symbian手機支持的字體文件,有gdr字體文件、bdf字體文件和ttf字體文件,相對來說ttf字體用的更為普遍一些,他是由Apple公司和微軟公司聯合發起的一套標準,在Symbian手機目前所知有兩種使用ttf字體文件的方法:一種是實現基于ECom插件形式的光柵化插件方法,該方法是靜態加載,每次選擇一個字體后,需要重啟手機才能生效,網上有開源的FontRouter可以作為參考;另一種是通過CFbsTypefaceStore或由應用程序框架控制環境動態加載字體文件,該種方法可以實時生效,沒錯的話Go瀏覽器就是通過這種方法實現的。本文就是對在Symbian手機上動態加載TTF字體并使用的整理小結。
字體使用效果截圖演示
因為后續內容比較枯燥無味,為了增加本文的可讀性,先將字體使用的效果截圖如下
方正胖娃字體

華康少女字體

方正柳體

方正準圓字體

字體文件加載使用流程
動態加載ttf字體文件和使用的方法,其實也是可以通用于gdr等字體的,具體的加載使用步驟流程可以參考nokia官方的wiki文檔,地址見下面鏈接
http://wiki.forum.nokia.com/index.php/Custom_font
步驟1:準備好需要使用的TTF字體文件
可以去網絡上下載你想要在手機上顯示的ttf字體文件(比如http://www.font5.com.cn/index.html),也可以使用Windows操作系統自帶的字體,可以通過“控制面板”、“字體”或者直接到"C:\Windows\Fonts"目錄下尋找你需要的字體文件。
步驟2:將準備好的TTF字體文件拷貝到手機中
拷貝的路徑由你自己選擇,可以手機存儲或者TF卡上,由于TTF字體文件本身都比較大,所以本文試驗時將其放在TF卡上(“e:\Data\Fonts\”)。
步驟3:在Draw()函數中加載并使用這個字體文件
具體代碼如下:
CFont* myNewFont = NULL;
TFileName iFileName;
iFileName.Copy(_L("e:\\Data\\Fonts\\abc.ttf"));
TInt static fontID = 0;
iCoeEnv->ScreenDevice()->AddFile(iFontFile,
fontID);
TBuf<KMaxTypefaceNameLength>
aTypefaceName;
TTypefaceSupport myTypefaceSupport;
iCoeEnv->ScreenDevice()->TypefaceSupport(myTypefaceSupport,0);//通常是新增加的在序列里面是第一個
aTypefaceName.Copy(myTypefaceSupport.iTypeface.iName.Des());
TFontSpec myFontSpec;
myFontSpec.iTypeface.iName =
aTypefaceName;
TPoint pixelPoint(16,16);//字體大小模式
myFontSpec.iHeight =
iCoeEnv->ScreenDevice()->PixelsToTwips(pixelPoint).iY;
iCoeEnv->ScreenDevice()->GetNearestFontToDesignHeightInTwips(myNewFont,
myFontSpec);
gc.UseFont(myNewFont);
gc.SetPenColor(KRgbBlack);
gc.DrawText(_L("hello"),TPoint(5,20));
步驟4:卸載這個字體
卸載代碼如下:
iCoeEnv->ScreenDevice()->ReleaseFont(myFont);
iCoeEnv->ScreenDevice()->RemoveFile(aid);
字體動態加載的問題討論
雖然整個操作步驟如上述簡單流程,但是實際實現時你會發現很多意想不到的問題,我在具體操作時曾一度不相信通過上述方法真能實現所謂的動態加載,下面就對實際使用中遇到的幾個問題進行闡釋。
如何正確獲得要用的字體
通常情況下,通過AddFile或者InstallFile加載的字體,位于字體表的首項,所以可以通過以下代碼獲取新加載的字體
TTypefaceSupport myTypefaceSupport;
iCoeEnv->ScreenDevice()->TypefaceSupport(myTypefaceSupport,0);//通常是新增加的在序列里面是第一個
但是有時候也并非都是在首項(存在偶然因素),特別是當選中的這個ttf字體已經有加載的情況下,想繼續加載另一個非同文件名的ttf字體時,系統會報錯(-11,KErrAlreadyExists),這個就使得需要使用的字體不位于字體表的首項成為必然了,這個時候用戶選擇的僅僅是ttf字體文件,而ttf字體文件的文件名是可以任意修改的,所以程序無法靠字體文件名來識別,那該如何正確獲得要用的字體呢?
慶幸ttf字體文件有一個唯一識別的字體名(fontname),通過這個特性就可以通過字體名來選擇需要使用的字體。至于如何去獲取這個唯一識別的字體名,就要去了解下ttf字體文件的結構信息,慶幸網絡上有Windows平臺上的實現代碼,具體詳見http://www.codeproject.com/KB/GDI/fontnamefromfile.aspx,我就偷梁換柱,將其改成如下代碼,用以在Symbian上實現從字體文件獲取字體名。具體的ttf文件結構可以通過百度或google搜索得到,因為很復雜,這里就不做贅述了。
#define MAKEWORD(a, b) ((TText16)(((TText8)(a)) |
((TText16)((TText8)(b))) << 8))
#define MAKELONG(a, b) ((TInt32)(((TText16)(a)) |
((TUint32)((TText16)(b))) << 16))
#define LOBYTE(w) ((TText8)(w))
#define HIBYTE(w) ((TText8)(((TText16)(w) >> 8)
& 0xFF))
#define LOWORD(l) ((TText16)(l))
#define HIWORD(l) ((TText16)(((TUint32)(l) >>
16) & 0xFFFF))
#define SWAPWORD(x) MAKEWORD(HIBYTE(x), LOBYTE(x))
#define SWAPLONG(x) MAKELONG(SWAPWORD(HIWORD(x)),
SWAPWORD(LOWORD(x)))
typedef struct _tagTT_OFFSET_TABLE
{
TText16 uMajorVersion;
TText16 uMinorVersion;
TText16 uNumOfTables;
TText16 uSearchRange;
TText16 uEntrySelector;
TText16 uRangeShift;
}TT_OFFSET_TABLE;
typedef struct _tagTT_TABLE_DIRECTORY
{
char szTag[4]; //table name
TUint32 uCheckSum; //Check sum
TUint32 uOffset; //Offset from beginning of file
TUint32 uLength; //length of the table in bytes
}TT_TABLE_DIRECTORY;
typedef struct _tagTT_NAME_TABLE_HEADER
{
TText16 uFSelector; //format selector. Always 0
TText16 uNRCount; //Name Records count
TText16 uStorageOffset; //Offset for strings storage, from start
of the table
}TT_NAME_TABLE_HEADER;
typedef struct _tagTT_NAME_RECORD
{
TText16 uPlatformID;
TText16 uEncodingID;
TText16 uLanguageID;
TText16 uNameID;
TText16 uStringLength;
TText16 uStringOffset; //from start of storage area
}TT_NAME_RECORD;
TInt GetFontNameFromFile(const TDesC16
&aFontFile, TDes16 &aFontName)
{
RFs vFs;
RFile vFile;
TFileName vFileTemp;
TBuf8<56> vBufTemp;
TInt vErr = vFs.Connect();
if(KErrNone != vErr)
{
return -1;
}
vErr = vFile.Open(vFs, aFontFile, EFileRead|EFileShareAny);
if(KErrNone != vErr)
{
vFs.Close();
return -1;
}
else
{
TT_OFFSET_TABLE ttOffsetTable;
TPtr8 vPtrttOffsetTable((TUint8 *)&ttOffsetTable,
sizeof(TT_OFFSET_TABLE));
vErr = vFile.Read(vPtrttOffsetTable, sizeof(TT_OFFSET_TABLE));
if(KErrNone != vErr)
{
vFile.Close();
vFs.Close();
return -1;
}
ttOffsetTable.uNumOfTables =
SWAPWORD(ttOffsetTable.uNumOfTables);
ttOffsetTable.uMajorVersion = SWAPWORD(ttOffsetTable.uMajorVersion);
ttOffsetTable.uMinorVersion = SWAPWORD(ttOffsetTable.uMinorVersion);
//check is this is a true type font and the version is 1.0
if(ttOffsetTable.uMajorVersion != 1 || ttOffsetTable.uMinorVersion != 0)
{
vFile.Close();
vFs.Close();
return -1;
}
TT_TABLE_DIRECTORY tblDir;
TPtr8 vPtr8tblDir((TUint8*)&tblDir, sizeof(TT_TABLE_DIRECTORY));
TBool bFound = EFalse;
for(TInt i = 0; i < ttOffsetTable.uNumOfTables; i++)
{
vErr = vFile.Read(vPtr8tblDir, sizeof(TT_TABLE_DIRECTORY));
if(KErrNone != vErr)
{
vFile.Close();
vFs.Close();
return -1;
}
else
{
vBufTemp.Copy((TUint8*)tblDir.szTag, 4);
if(vBufTemp.Compare(_L8("name"))
== 0)
{
bFound = ETrue;
tblDir.uLength =
SWAPLONG(tblDir.uLength);
tblDir.uOffset =
SWAPLONG(tblDir.uOffset);
break;
}
}
}
if(bFound)
{
TInt vDataTemp = tblDir.uOffset;
vErr = vFile.Seek(ESeekStart, vDataTemp);
if(KErrNone != vErr)
{
vFile.Close();
vFs.Close();
return -1;
}
TT_NAME_TABLE_HEADER ttNTHeader;
TPtr8 vPtr8ttNTHeader((TUint8*)&ttNTHeader,
sizeof(TT_NAME_TABLE_HEADER));
vErr = vFile.Read(vPtr8ttNTHeader, sizeof(TT_NAME_TABLE_HEADER));
if(KErrNone != vErr)
{
vFile.Close();
vFs.Close();
return -1;
}
ttNTHeader.uNRCount = SWAPWORD(ttNTHeader.uNRCount);
ttNTHeader.uStorageOffset = SWAPWORD(ttNTHeader.uStorageOffset);
TT_NAME_RECORD ttRecord;
TPtr8 vPtr8ttRecord((TUint8*)&ttRecord, sizeof(TT_NAME_RECORD));
bFound = EFalse;
for(TInt j = 0; j < ttNTHeader.uNRCount; j++)
{
vErr =
vFile.Read(vPtr8ttRecord, sizeof(TT_NAME_RECORD));
if(KErrNone != vErr)
{
vFile.Close();
vFs.Close();
return -1;
}
ttRecord.uNameID =
SWAPWORD(ttRecord.uNameID);
if(ttRecord.uNameID == 1)
{
ttRecord.uStringLength =
SWAPWORD(ttRecord.uStringLength);
ttRecord.uStringOffset =
SWAPWORD(ttRecord.uStringOffset);
TInt nPos = 0;
vErr =
vFile.Seek(ESeekCurrent, nPos);
if(KErrNone != vErr)
{
vFile.Close();
vFs.Close();
return -1;
}
vDataTemp = tblDir.uOffset
+ ttRecord.uStringOffset + ttNTHeader.uStorageOffset;
vErr =
vFile.Seek(ESeekStart, vDataTemp);
if(KErrNone != vErr)
{
vFile.Close();
vFs.Close();
return -1;
}
//bug fix: see the post by
SimonSays to read more about it
HBufC8* vNameTemp =
HBufC8::New(ttRecord.uStringLength + 1);
TPtr8
vPtrFontName(vNameTemp->Des());
vErr =
vFile.Read(vPtrFontName, ttRecord.uStringLength);
if(KErrNone != vErr)
{
delete vNameTemp;
vNameTemp = NULL;
vFile.Close();
vFs.Close();
return -1;
}
if(vPtrFontName.Length()
> 0)
{
aFontName.Copy(vPtrFontName);
delete vNameTemp;
vNameTemp = NULL;
break;
}
vErr =
vFile.Seek(ESeekStart, nPos);
if(KErrNone != vErr)
{
delete vNameTemp;
vNameTemp = NULL;
vFile.Close();
vFs.Close();
return -1;
}
delete vNameTemp;
vNameTemp = NULL;
}
}
}
}
vFile.Close();
vFs.Close();
return 0;
}
通過上述實現,獲取了字體文件的字體名之后,我們就可以不用TypefaceSupport這種枚舉的方法了,直接設定TFontSpec的屬性然后通過調用GetNearestFont…相關函數來獲取需要的字體。
目前發現的還有一個問題,就是存在一種很特殊的情況,有時候調用了RemoveFile卸載了字體文件,但是字體文件卻還是處于打開或者在用的狀態,這個時候,通過上述的GetFontNameFromFile是無法取到字體名的,這種情況下,有時候就只能通過重啟手機,來將字體文件從內存中解綁。
正確加載并使用了字體卻顯示不出來
字體正確的加載并選擇使用了,但是通過這個選擇的字體,在UI上DrawText的時候居然沒有繪制出來,不得不讓人懷疑這種方法是否可行。最后經過試驗發現,假如在一個局部函數內對同一字體分別調用AddFile或者InstallFile方法,然后繪制結束后調用RemoveFile方法,那么就是顯示不出繪制的東西。所以在A函數內加載字體(AddFile或者InstallFile),甚至在A函數內使用字體,但是就是不能在A函數內卸載剛剛添加的字體。
那既然加載的字體卸載起來這么麻煩,我干脆就不卸載了可以嗎,就好比程序在堆棧上申請的內容在程序退出的時候,系統會將程序所擁有的堆棧上申請的內容釋放掉,但是字體文件就是個特例,因為Symbian OS是通過C/S架構來實現的,程序加載只是通知OS內核中的CFbsServer去加載字體,假如不人為卸載,那么CFbsServer不會自己去卸載字體,而且程序一旦退出,就只有通過手機重啟的方法來卸載字體了。
所以起初設計Demo代碼采用了加載一個字體,將對應的fontID放到一個CArray隊列里,最后程序退出的時候,在類的析構函數中再一一卸載已經加載的字體。這種方法適合在一個程序中同時加載多個ttf字體。假如用戶的程序中始終只需要加載一個ttf字體,那么就可以通過在A函數中,進行加載新字體之前,先把已加載的字體卸載掉,然后再加載新的字體并使用。這樣的情況下,就不會造成用加載的字體繪制文本內容時顯示空白的問題。
其它還有一些問題,目前就不做整理了。
posted on 2011-02-21 10:28
frank.sunny 閱讀(3057)
評論(0) 編輯 收藏 引用 所屬分類:
symbian 開發