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 阅读(3042)
评论(0) 编辑 收藏 引用 所属分类:
symbian 开发