65.9K
CodeProject 正在变化。 阅读更多。
Home

从 TTF 文件中检索字体名称

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (23投票s)

2002年5月17日

CPOL

3分钟阅读

viewsIcon

295118

downloadIcon

6517

解释如何从 TrueType 或 OpenType 文件 (.ttf) 中检索字体名称

引言

每个人都可以获取已安装字体的字体名称。但如果字体尚未安装在系统中,您又想以编程方式知道它是什么呢?当然,您可以暂时将其添加到系统字体中,然后获取其属性(嗯...但您现在将如何找到已安装的字体?)。也许您可以考虑其他方法,但我决定查找 TrueType 和 OpenType 字体文件的规范。幸运的是,微软在这方面有非常好的文章。如果您想了解更多关于它们的信息,请查看本文末尾的链接。

编写代码

由于所有让我感兴趣的(并且在大多数情况下让您感兴趣的)只是字体名称,而不是 TTF 文件中的其他属性,因此我们的代码将很简单(实际上只有一个函数)。该函数将从给定的文件中检索字体名称并将其返回给调用程序。

数据类型定义

由于 Windows 标头文件中没有定义结构(或者我没有找到它们),我们将自己创建。我们需要 4 个结构和 2 个宏(稍后我会解释它们)。

TTF 文件由几个表组成,每个表代表与其类型相关的一些数据。有些表是必需的,有些则不是。我们实际上只需要其中一个,称为“name”,例如名称表。这是存储字体信息的地方,如字体名称、版权、商标等。

//This is TTF file header
typedef struct _tagTT_OFFSET_TABLE{
    USHORT uMajorVersion;
    USHORT uMinorVersion;
    USHORT uNumOfTables;
    USHORT uSearchRange;
    USHORT uEntrySelector;
    USHORT uRangeShift;
}TT_OFFSET_TABLE;

//Tables in TTF file and there placement and name (tag)
typedef struct _tagTT_TABLE_DIRECTORY{
    char szTag[4]; //table name
    ULONG uCheckSum; //Check sum
    ULONG uOffset; //Offset from beginning of file
    ULONG uLength; //length of the table in bytes
}TT_TABLE_DIRECTORY;

//Header of names table
typedef struct _tagTT_NAME_TABLE_HEADER{
    USHORT uFSelector; //format selector. Always 0
    USHORT uNRCount; //Name Records count
    USHORT uStorageOffset; //Offset for strings storage, 
                           //from start of the table
}TT_NAME_TABLE_HEADER;

//Record in names table
typedef struct _tagTT_NAME_RECORD{
    USHORT uPlatformID;
    USHORT uEncodingID;
    USHORT uLanguageID;
    USHORT uNameID;
    USHORT uStringLength;
    USHORT uStringOffset; //from start of storage area
}TT_NAME_RECORD;

现在,剩下的就是我之前一直在谈论的宏。宏定义如下所示

#define SWAPWORD(x) MAKEWORD(HIBYTE(x), LOBYTE(x))
#define SWAPLONG(x) MAKELONG(SWAPWORD(HIWORD(x)), SWAPWORD(LOWORD(x)))

这是什么意思?我们需要这些宏的原因是 TTF 文件以 Big-Endian 格式存储,这与 Windows 系统不同,Windows 系统中的所有文件都采用 Little Endian。是的,我知道这听起来很傻,涉及到所有这些“endians” :)。例如,Motorolla 处理器使用 Big Endian,其中较高的字节首先存储,而在 Little Endian(用于 Intel 处理器)中,较高的字节是最后一个。例如,您有一个整数变量 1(长度为 4 个字节)。尝试将其保存到文件中并在任何十六进制编辑器中打开,您将看到

01 00 00 00    //Little Endian - Intel

这是 Little Endian 系统 (Intel)。但是对于 Big-Endian (Motorolla),该数字将被反向存储

00 00 00 01    //Big Endian - Motorolla

因此,这些格式是不兼容的。正如我所说,TTF 文件以 Motorolla 风格 (Big Endian) 存储。这就是为什么我们需要这两个宏来重新排列从 TrueType 字体文件中检索的变量中的字节。

读取文件

现在我们已准备好读取 TTF 文件。所以让我们开始吧。

首先,我们需要读取文件头 (TT_OFFSET_TABLE 结构)

CFile f;
CString csRetVal;

//lpszFilePath is the path to our font file
if(f.Open(lpszFilePath, CFile::modeRead|CFile::shareDenyWrite)){

    //define and read file header
    TT_OFFSET_TABLE ttOffsetTable;
    f.Read(&ttOffsetTable, sizeof(TT_OFFSET_TABLE));

    //remember to rearrange bytes in the field you gonna use
    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)
        return csRetVal;

文件头之后紧接着是 Offsets 表。您可以在此处找到感兴趣的表偏移量,在本例中为“name”。

    TT_TABLE_DIRECTORY tblDir;
    BOOL bFound = FALSE;
    CString csTemp;

    for(int i=0; i< ttOffsetTable.uNumOfTables; i++){
        f.Read(&tblDir, sizeof(TT_TABLE_DIRECTORY));
        csTemp.Empty();

        //table's tag cannot exceed 4 characters
        strncpy(csTemp.GetBuffer(4), tblDir.szTag, 4);
        csTemp.ReleaseBuffer();
        if(csTemp.CompareNoCase(_T("name")) == 0){
            //we found our table. Rearrange order and quit the loop
            bFound = TRUE;
            tblDir.uLength = SWAPLONG(tblDir.uLength);
            tblDir.uOffset = SWAPLONG(tblDir.uOffset);
            break;
        }
    }

我们终于找到了名称表,所以让我们读取它的标头

    if(bFound){
        //move to offset we got from Offsets Table
        f.Seek(tblDir.uOffset, CFile::begin);
        TT_NAME_TABLE_HEADER ttNTHeader;
        f.Read(&ttNTHeader, sizeof(TT_NAME_TABLE_HEADER));

        //again, don't forget to swap bytes!
        ttNTHeader.uNRCount = SWAPWORD(ttNTHeader.uNRCount);
        ttNTHeader.uStorageOffset = SWAPWORD(ttNTHeader.uStorageOffset);
        TT_NAME_RECORD ttRecord;
        bFound = FALSE;

在名称表标头之后,表中会插入记录。因此,我们需要遍历所有记录以找到我们感兴趣的信息 - 字体名称。

    for(int i=0; i<ttNTHeader.uNRCount; i++){
    f.Read(&ttRecord, sizeof(TT_NAME_RECORD));
    ttRecord.uNameID = SWAPWORD(ttRecord.uNameID);

    //1 says that this is font name. 0 for example determines copyright info
    if(ttRecord.uNameID == 1){
        ttRecord.uStringLength = SWAPWORD(ttRecord.uStringLength);
        ttRecord.uStringOffset = SWAPWORD(ttRecord.uStringOffset);

        //save file position, so we can return to continue with search
        int nPos = f.GetPosition();
        f.Seek(tblDir.uOffset + ttRecord.uStringOffset + 
                 ttNTHeader.uStorageOffset, CFile::begin);

        //bug fix: see the post by SimonSays to read more about it
        TCHAR lpszNameBuf = csTemp.GetBuffer(ttRecord.uStringLength + 1);
        ZeroMemory(lpszNameBuf, ttRecord.uStringLength + 1);
        f.Read(lpszNameBuf, ttRecord.uStringLength);
        csTemp.ReleaseBuffer();

        //yes, still need to check if the font name is not empty
        //if it is, continue the search
        if(csTemp.GetLength() > 0){
            csRetVal = csTemp;
            break;
        }
        f.Seek(nPos, CFile::begin);
    }
}

就这样!现在我们可以返回包含字体名称的 csRetVal

您可以下载完整的工作函数并在您的代码中使用。我还包括了一个演示项目,其中包含相同的函数,但稍作自定义,因此它还返回了版权和商标信息。

如果您想继续使用 TTF 文件,可以查看微软关于它们的规范。但请记住,您深入了解 TTF 的程度越深,就可能找到 TrueType 和 OpenType 之间的更多差异。无论如何,下面是关于 TTF 的文章的链接。

参考文献

© . All rights reserved.