Introduction
Everyone can get a font name of one of installed fonts. But what if the font is still not installed in the system and you want to know what is that, programmatically? Of course, you can temporary add it to system fonts and get its properties then (hmm... but how you will find now what was the font installed?). Well, maybe you can think about other ways, but I decided to look for specification of the TrueType and OpenType fonts file. Fortunately, Microsoft has very good articles on these files. If you want to know more about them, look at the end of this article for links.
Writing the Code
Since all that interested me (and you in most cases) is only the font name and not other properties in the TTF file, our code is going to be simple (actually only one function). The function will retrieve font name from a given file and return it to the calling program.
Data Types Definition
Since there are no structures defined in Windows header files (or I didn't find them), we are going to make our own. We need 4 structures and 2 macros (I will explain later, about them).
A TTF file consists of several tables, each table represent some data, regarding of its type. Some tables are required, some are not. We actually need only one of them, called "name
", e.g. names table. This is the place where the font information is stored, like font name, copyright, trademark and more.
typedef struct _tagTT_OFFSET_TABLE{
USHORT uMajorVersion;
USHORT uMinorVersion;
USHORT uNumOfTables;
USHORT uSearchRange;
USHORT uEntrySelector;
USHORT uRangeShift;
}TT_OFFSET_TABLE;
typedef struct _tagTT_TABLE_DIRECTORY{
char szTag[4];
ULONG uCheckSum;
ULONG uOffset;
ULONG uLength;
}TT_TABLE_DIRECTORY;
typedef struct _tagTT_NAME_TABLE_HEADER{
USHORT uFSelector;
USHORT uNRCount;
USHORT uStorageOffset;
}TT_NAME_TABLE_HEADER;
typedef struct _tagTT_NAME_RECORD{
USHORT uPlatformID;
USHORT uEncodingID;
USHORT uLanguageID;
USHORT uNameID;
USHORT uStringLength;
USHORT uStringOffset;
}TT_NAME_RECORD;
Macros
Now, the only thing left is macros I was talking before. The macros definition looks like:
#define SWAPWORD(x) MAKEWORD(HIBYTE(x), LOBYTE(x))
#define SWAPLONG(x) MAKELONG(SWAPWORD(HIWORD(x)), SWAPWORD(LOWORD(x)))
Now what is that? The reason we need those macros is that TTF files are stored in Big-Endian format, unlike in Windows systems, where all files are in Little Endian. Yeah, I know it sounds silly with all those "endians" :). Big Endian is used by Motorolla processors for example, where the higher byte is stored first, while in Little Endian (for Intel processors) the higher byte is the last. For example, you have an integer variable 1
(which is 4 bytes long). Try to save it to file and open in any hexadecimal editor, you will see:
01 00 00 00 //Little Endian - Intel
This is Little Endian system (Intel). But for Big-Endian (Motorolla), the number will be stored vise versa:
00 00 00 01 //Big Endian - Motorolla
So these formats are incompatible. And TTF file as I said, is stored in Motorolla style (Big Endian). That's why we need those 2 macros to rearrange bytes in variables retrieved from TrueType font file.
Reading the File
Now we are prepared to read the TTF file. So let's get started.
First of all, we need to read the file header (TT_OFFSET_TABLE
structure):
CFile f;
CString csRetVal;
if(f.Open(lpszFilePath, CFile::modeRead|CFile::shareDenyWrite)){
TT_OFFSET_TABLE ttOffsetTable;
f.Read(&ttOffsetTable, sizeof(TT_OFFSET_TABLE));
ttOffsetTable.uNumOfTables = SWAPWORD(ttOffsetTable.uNumOfTables);
ttOffsetTable.uMajorVersion = SWAPWORD(ttOffsetTable.uMajorVersion);
ttOffsetTable.uMinorVersion = SWAPWORD(ttOffsetTable.uMinorVersion);
if(ttOffsetTable.uMajorVersion != 1 || ttOffsetTable.uMinorVersion != 0)
return csRetVal;
Right after the file header goes Offsets
Table. You can find here an offset interesting in your table, "name
" in our case.
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();
strncpy(csTemp.GetBuffer(4), tblDir.szTag, 4);
csTemp.ReleaseBuffer();
if(csTemp.CompareNoCase(_T("name")) == 0){
bFound = TRUE;
tblDir.uLength = SWAPLONG(tblDir.uLength);
tblDir.uOffset = SWAPLONG(tblDir.uOffset);
break;
}
}
We finally found the names table, so let's read its header:
if(bFound){
f.Seek(tblDir.uOffset, CFile::begin);
TT_NAME_TABLE_HEADER ttNTHeader;
f.Read(&ttNTHeader, sizeof(TT_NAME_TABLE_HEADER));
ttNTHeader.uNRCount = SWAPWORD(ttNTHeader.uNRCount);
ttNTHeader.uStorageOffset = SWAPWORD(ttNTHeader.uStorageOffset);
TT_NAME_RECORD ttRecord;
bFound = FALSE;
Right after the Names Table header, go records in it. So we need to run through all records to find information interesting to us - font name.
for(int i=0; i<ttNTHeader.uNRCount; i++){
f.Read(&ttRecord, sizeof(TT_NAME_RECORD));
ttRecord.uNameID = SWAPWORD(ttRecord.uNameID);
if(ttRecord.uNameID == 1){
ttRecord.uStringLength = SWAPWORD(ttRecord.uStringLength);
ttRecord.uStringOffset = SWAPWORD(ttRecord.uStringOffset);
int nPos = f.GetPosition();
f.Seek(tblDir.uOffset + ttRecord.uStringOffset +
ttNTHeader.uStorageOffset, CFile::begin);
TCHAR lpszNameBuf = csTemp.GetBuffer(ttRecord.uStringLength + 1);
ZeroMemory(lpszNameBuf, ttRecord.uStringLength + 1);
f.Read(lpszNameBuf, ttRecord.uStringLength);
csTemp.ReleaseBuffer();
if(csTemp.GetLength() > 0){
csRetVal = csTemp;
break;
}
f.Seek(nPos, CFile::begin);
}
}
That's all! Now we can return csRetVal
containing our font name.
You can download the full working function and use in your code. I also included a demo project with the same function, but customized a bit, so it also returned copyright and trademark information.
If you want to continue with TTF files, you can look at Microsoft's specification on them. But remember that deeper you are going to TTF, more differences between TrueType and OpenType you may find. Anyway, below are the links to articles about TTF.
References