TTC indexes, the easy way...

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. :-)


Please consider a donation to keep this archive running, maintained and free of advertising.
Donate €20 or more to receive an offline copy of the whole archive including all images.

go to newer or older post, or back to index or month or day