Entry

プログラミングメモ - TrueType フォントを少しだけ読む

2009年09月09日

ちょっと TrueType フォントの中身をゴニョゴニョする用事があるので,テストプログラムを書いてみました。需要少なそうなので,ものすごく私的なメモということで。TrueType フォントの仕様については,「TrueType Reference Manual」をご参照をば。

ソースが少し長くなってしまったので,結果から書いておきます。

C:\home\aian\work>truetype arial.ttf
scaler type  :10000
numTables    :17
searchRange  :100
entrySelector:4
rangeShift   :70

C:\home\aian\work>truetype msmincho.ttc
scaler type  :74746366
numTables    :1
searchRange  :0
entrySelector:0
rangeShift   :2

出力されている数値について気になる方は,仕様書を読んでみてください。おおむねよく取れていると思う。*.ttc フォントのマジックが仕様書に書かれていなかったもんで,これで合ってるのか不安だったんだけれども,0x74746366("ttcf")でいいらしい。

TrueType フォントの作りは割と単純で,「テーブル」と呼ばれる単位で,各種データにアクセスすることになります。ヘッダに相当する Font Directory テーブルに各テーブルへのインデックスがあるので,必要な情報を取得するには,このインデックスを使って二分探索を使いつつ取得することになる。Font Directory テーブルは,前半部(offset subtable)と後半部(table directory)に分かれていて,上の出力は前半部だけの出力です。

で,ソース。Windows 向けです。ビットをひっくり返すのに VC++ 7.0 以上では,CLR のランタイムマクロを使うようにしたんですけれど,あたしが試したのは VC++ 6.0 なので,VC++ 7.0 以上でコンパイルした場合については未検証です。

/**
 * TrueType font read test.
 */
#if _MSC_VER < 1300  // VC++ 7.0
#include <stdlib.h>
#endif

#include <iostream>
#include <fstream>

typedef unsigned char UInt8;
typedef unsigned long UInt32;
typedef unsigned short UInt16;
typedef unsigned _int64 UInt64;

////////////////////////////////////////////////////////////////////////////////

struct FontDirectorySubTable {
public:
    FontDirectorySubTable();
    virtual ~FontDirectorySubTable();
private:  // no implement
    FontDirectorySubTable(const FontDirectorySubTable& other);
    FontDirectorySubTable& operator=(const FontDirectorySubTable& rhs);
public:
    UInt32 scalerType;
    UInt16 numTables;
    UInt16 searchRange;
    UInt16 entrySelector;
    UInt16 rangeShift;
};

FontDirectorySubTable::FontDirectorySubTable()
    : scalerType(0), numTables(0), searchRange(0), entrySelector(0), rangeShift(0) {
}

FontDirectorySubTable::~FontDirectorySubTable() {
}

////////////////////////////////////////////////////////////////////////////////
class BinaryStreamReader {
public:
    BinaryStreamReader() throw ();
    BinaryStreamReader(const std::string& filepath) throw (std::runtime_error);
    virtual ~BinaryStreamReader() throw ();
public:
    /**
     * Swap hi-bytes and low-bytes.
     */
    UInt16 hton(UInt16& n) const throw ();
    UInt32 hton(UInt32& n) const throw ();
    UInt64 hton(UInt64& n) const throw ();
    /**
     * Byte read from a specified file stream. Stream byte order is regarded
     * as a big-endian representation. So, if read bytes are greater than
     * or equal to the size of UInt16, then convert them into a little-endian
     * representation.
     */
    void read(UInt8& v) throw ();
    void read(UInt16& v) throw ();
    void read(UInt32& v) throw ();
    void read(UInt64& v) throw ();
private:
    std::string filepath_;
    std::ifstream ifs_;
};

BinaryStreamReader::BinaryStreamReader() throw () {
}

BinaryStreamReader::BinaryStreamReader(const std::string& filepath)
    throw (std::runtime_error) {
    ifs_.open(filepath.c_str(), std::ios::binary | std::ios::in);
    if (!ifs_.good()) {
        throw std::runtime_error("file open error.");
    }
}

BinaryStreamReader::~BinaryStreamReader() throw () {
}

UInt16
BinaryStreamReader::hton(UInt16& n) const throw () {
#if _MSC_VER < 1300  // VC++ 7.0
    return n = (((n & 0xFF00) >> 8) |
                ((n & 0x00FF) << 8)
                );
#else
    return _byteswap_ushort(n);
#endif
}

UInt32
BinaryStreamReader::hton(UInt32& n) const throw () {
#if _MSC_VER < 1300  // VC++ 7.0
    return n = (((n & 0xFF000000) >> 24) |
                ((n & 0x00FF0000) >>  8) |
                ((n & 0x0000FF00) <<  8) |
                ((n & 0x000000FF) << 24)
                );
#else
    return _byteswap_ulong(n);
#endif
}

UInt64
BinaryStreamReader::hton(UInt64& n) const throw () {
#if _MSC_VER < 1300  // VC++ 7.0
    return n = (((n & 0xFF00000000000000) >> 56) |
                ((n & 0x00FF000000000000) >> 40) |
                ((n & 0x0000FF0000000000) >> 24) |
                ((n & 0x000000FF00000000) >>  8) |
                ((n & 0x00000000FF000000) <<  8) |
                ((n & 0x0000000000FF0000) << 24) |
                ((n & 0x000000000000FF00) << 40) |
                ((n & 0x00000000000000FF) << 56)
                );
#else
    return _byteswap_uint64(n);
#endif
}

void
BinaryStreamReader::read(UInt8& v) throw () {
    ifs_.read(reinterpret_cast<char*>(&v), sizeof(UInt8));
}

void
BinaryStreamReader::read(UInt16& v) throw () {
    ifs_.read(reinterpret_cast<char*>(&v), sizeof(UInt16));
    hton(v);
}

void
BinaryStreamReader::read(UInt32& v) throw () {
    ifs_.read(reinterpret_cast<char*>(&v), sizeof(UInt32));
    hton(v);
}

void
BinaryStreamReader::read(UInt64& v) throw () {
    ifs_.read(reinterpret_cast<char*>(&v), sizeof(UInt64));
    hton(v);
}

////////////////////////////////////////////////////////////////////////////////

int
readFontDirectory(BinaryStreamReader& bsr, FontDirectorySubTable& fdst) {
    bsr.read(fdst.scalerType);
    bsr.read(fdst.numTables);
    bsr.read(fdst.searchRange);
    bsr.read(fdst.entrySelector);
    bsr.read(fdst.rangeShift);
    return 0;
}

int
main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "arg error." << std::endl;
        return 1;
    }
    try {
        FontDirectorySubTable fdst;
        BinaryStreamReader bsr(argv[1]);
        readFontDirectory(bsr, fdst);
        std::cout << "scaler type  :" << std::hex << fdst.scalerType << std::endl;
        std::cout << "numTables    :" << std::hex << fdst.numTables << std::endl;
        std::cout << "searchRange  :" << std::hex << fdst.searchRange << std::endl;
        std::cout << "entrySelector:" << std::hex << fdst.entrySelector << std::endl;
        std::cout << "rangeShift   :" << std::hex << fdst.rangeShift << std::endl;
    } catch (...) {
        std::cerr << "error." << std::endl;
    }
    return 0;
}

ファイルはビッグエンディアンで格納されているので,IA32 のようなリトルエンディアンのアーキテクチャでは,ひっくり返して読む必要があります。これ,CPU 命令を直接使ったマクロがあったんですね。知らなかった。

毎度のことだけれども,バイナリファイルの読み書きって,きれいに書けないんだよなぁ……。どうしたもんだろ。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN