by Michael S. Kaplan, published on 2007/07/07 10:01 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2007/07/07/3746794.aspx
Obviously a follow-on to TTC indexes, the hard way..., this post provides the code that Sergey Malkin put together to work with .TTC files, and more importantly with the individual fonts thereof.
And I think I'll save some of his helpful functions byte swapping, too -- for future forays in this area like that thing I did with the names.... :-)
Here is the code:
#include <stdio.h>
#include <windows.h>
USHORT ReadUshort(BYTE* p) {
return ((USHORT)p[0] << 8)+
((USHORT)p[1] );
}
DWORD ReadDword(BYTE* p) {
return ((LONG)p[0] << 24 )+
((LONG)p[1] << 16 )+
((LONG)p[2] << 8 )+
((LONG)p[3] );
}|
DWORD ReadTag(BYTE* p) {
return ((LONG)p[3] << 24 )+
((LONG)p[2] << 16 )+
((LONG)p[1] << 8 )+
((LONG)p[0] );
}
void WriteDword(BYTE* p, DWORD dw) {
p[0] = (BYTE)((dw >> 24 ) & 0xFF);
p[1] = (BYTE)((dw >> 16 ) & 0xFF);
p[2] = (BYTE)((dw >> 8 ) & 0xFF);
p[3] = (BYTE)((dw ) & 0xFF);
}
DWORD RoundUpToDword(DWORD val) {
return (val + 3) & ~3;
}
#define TTC_FILE 0x66637474
const DWORD SizeOfFixedHeader = 12;
const DWORD OffsetOfTableCount = 4;
const DWORD SizeOfTableEntry = 16;
const DWORD OffsetOfTableTag = 0;
const DWORD OffsetOfTableChecksum = 4;
const DWORD OffsetOfTableOffset = 8;
const DWORD OffsetOfTableLength = 12;
HRESULT ExtractFontDataFromTTC(
HDC hdc,
__out DWORD* pcbFontDataSize,
__deref_out_bcount(*pcbFontDataLength) void** ppvFontData
) {
*ppvFontData = NULL;
*pcbFontDataSize = 0;
// Check if font is really in ttc
if (GetFontData(hdc, TTC_FILE, 0, NULL, 0) == GDI_ERROR) {
return GetLastError();
}
// 1. Read number of tables in the font (ushort value at offset 2)
USHORT nTables;
BYTE UshortBuf[2];
if (GetFontData(hdc, 0, 4, UshortBuf, 2) == GDI_ERROR) {
return GetLastError();
}
nTables = ReadUshort(UshortBuf);
// 2. Calculate memory needed for the whole font header and read it into buffer
DWORD cbHeaderSize = SizeOfFixedHeader + nTables * SizeOfTableEntry;
BYTE* pbFontHeader = (BYTE*)malloc(cbHeaderSize);
if (!pbFontHeader) {
return E_OUTOFMEMORY;
}
if (GetFontData(hdc, 0, 0, pbFontHeader, cbHeaderSize) == GDI_ERROR) {
free(pbFontHeader);
return GetLastError();
}
// 3. Go through tables and calculate total font size.
// Don't forget that tables should be padded to 4-byte
// boundaries, so length should be rounded up to dword.
DWORD cbFontSize = cbHeaderSize;
for(int i = 0; i < nTables; i++) {
DWORD cbTableLength = ReadDword(pbFontHeader +
SizeOfFixedHeader +
i * SizeOfTableEntry +
OffsetOfTableLength
);
if (i < nTables - 1) {
cbFontSize += RoundUpToDword(cbTableLength);
} else {
cbFontSize += cbTableLength;
}
}
// 4. Copying header into target buffer. Offsets are incorrect,
// we will patch them with correct values while copying data.
BYTE* pbFontData = (BYTE*)malloc(cbFontSize);
if (!pbFontData) {
free(pbFontHeader);
return E_OUTOFMEMORY;
}
memcpy(pbFontData, pbFontHeader, cbHeaderSize);
// 5. Get table data from GDI, write it into known place
// inside target buffer and fix offset value.
DWORD dwRunningOffset = cbHeaderSize;
for(int i = 0; i < nTables; i++) {
BYTE* pEntryData = pbFontHeader +
SizeOfFixedHeader +
i * SizeOfTableEntry;
DWORD dwTableTag = ReadTag(pEntryData + OffsetOfTableTag);
DWORD cbTableLength = ReadDword(pEntryData + OffsetOfTableLength);
// Write new offset for this table.
WriteDword(pbFontData+
SizeOfFixedHeader +
i * SizeOfTableEntry +
OffsetOfTableOffset,
dwRunningOffset
);
//Get font data from GDI and place it into target buffer
if (GetFontData(hdc, dwTableTag, 0, pbFontData + dwRunningOffset, cbTableLength) == GDI_ERROR) {
free(pbFontHeader);
return GetLastError();
}
dwRunningOffset += cbTableLength;
// Pad tables (except last) with zero's
if (i < nTables - 1) {
while (dwRunningOffset % 4 != 0) {
pbFontData[dwRunningOffset] = 0;
++dwRunningOffset;
}
}
}
free(pbFontHeader);
*ppvFontData = pbFontData;
*pcbFontDataSize = cbFontSize;
return S_OK;
}
int main(int argc, WCHAR* argv[]) {
HDC hdc = CreateCompatibleDC(0);
LOGFONT lf = {
12,
0,
0,
0,
FW_NORMAL,
false,
false,
false,
ANSI_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE};
wcscpy_s(lf.lfFaceName, sizeof(lf.lfFaceName)/sizeof(WCHAR), L"MS Gothic");
HFONT oldfont = (HFONT)SelectObject(hdc, CreateFontIndirect(&lf));
void* pvFontData;
DWORD dwFontDataSize;
if (FAILED(ExtractFontDataFromTTC(hdc, &dwFontDataSize, &pvFontData))) {
return -1;
}
printf("Font extracted: %i bytes in size", dwFontDataSize);
FILE* file = fopen("font.ttf","wb");
fwrite(pvFontData, sizeof(BYTE), dwFontDataSize, file);
fclose(file);
free(pvFontData);
return 0;
}
Very cool!
This post brought to you by ख़ (U+0959, a.k.a. DEVANAGARI LETTER KHHA)
# Igor on 7 Jul 2007 11:04 PM:
Well Michael, how about using some assembler instead of all that casting, shifting and masking?
__declspec(naked) DWORD ReadDWord(LPBYTE p)
{
__asm {
mov edx, dword ptr [esp + 4]
mov eax, dword ptr [edx]
bswap eax
ret
}
}
__declspec(naked) void WriteDWord(LPBYTE p, DWORD dw)
{
__asm {
mov edx, dword ptr [esp + 4]
mov eax, dword ptr [esp + 8]
bswap eax
mov dword ptr [edx], eax
ret
}
}
It is limited to 32-bit x86 (486 and up) but it is a lot cleaner and easier to read.
# Michael S. Kaplan on 8 Jul 2007 1:23 AM:
I don't know that it is more readable specifically, and if it won't run on x64 then over half of my Windows machines won't like it much. :-)
# Michael S. Kaplan on 8 Jul 2007 1:25 AM:
Also, I explained here where I gave up on inline assembler in most cases. :-)