[BlueLeaf1336]> PROGRAM>

.TTCからフォント名抽出

historyTOP

2004/10/03:作成

overviewTOP

以前に .TTFからフォント名抽出 というのを(翻訳ベースで)やりましたが、今回は参考にしたサイト(CodeGuru: Retrieving the Font Name from a TTF File)のリファレンスにあったページ The OpenType Font File を自分でちゃんと読んで、表題の通り .TTCファイルからフォント名を抽出 しようじゃないかと考えました。

誰かのあるいは自分の役に立ちますように。

参考サイトTOP

codeTOP

(*
    http://www.microsoft.com/typography/otspec/otff.htm
    The OpenType Font File
*)

unit otff;

interface

uses
    Windows, SysUtils, Classes;

procedure GetFontNameFromTTC(lpszFilePath: string; Results: TStrings);

implementation

type
    USHORT = Word;
    ULONG = Longword;
    Tag = array[0..3] of char;
    Fixed = ULONG;              //  実際は 32bit fixed-point number(16.16)

type
    //  Version 1.0 の TTC ヘッダ
    TTC_HEADER_VERSION_10 = packed record
        TTCTag      : Tag;                  //  TrueType Collection ID string: 'ttcf'
        Version     : ULONG;                //  Version of the TTC Header (1.0), 0x00010000
        numFonts    : ULONG;                //  numFonts Number of fonts in TTC
        OffsetTable : array[0..0] of ULONG; //  実際は array[0..numFonts-1] of ULONG;
                                            //  Array of offsets to the OffsetTable
                                            //  for each font from the beginning of the file
    end;

    //  Version 2.0 の TTC ヘッダ
    //  フォント名が欲しいだけで、シグネチャなんかどうでもよい。
    //  先頭の 4フィールドは共通のため、Ver.2.0 は使用しない。

    TTC_HEADER_VERSION_20 = packed record
        TTCTag      : Tag;                  //  TrueType Collection ID string: 'ttcf'
        Version     : ULONG;                //  Version of the TTC Header (2.0), 0x00020000
        numFonts    : ULONG;                //  numFonts Number of fonts in TTC
        OffsetTable : array[0..0] of ULONG; //  実際は array[0..numFonts-1] of ULONG;
                                            //  Array of offsets to the OffsetTable
                                            //  for each font from the beginning of the file
        ulDsigTag   : ULONG;                //  Tag indicating that a DSIG table exists,
                                            //  0x44534947 ('DSIG') (null if no signature)
        ulDsigLength: ULONG;                //  The length (in bytes) of the DSIG table
                                            //  (null if no signature)
        ulDsigOffset: ULONG;                //  The offset (in bytes) of the DSIG table
                                            //  from the beginning of the TTC file
                                            //  (null if no signature)
    end;

    //  Offset Table
    TTC_OFFSET_TABLE = packed record
        sfntversion  : Fixed;           //  0x00010000 for version 1.0.
        numTables    : USHORT;          //  Number of tables.
        searchRange  : USHORT;          //  (Maximum power of 2 <= numTables) x 16.
        entrySelector: USHORT;          //  Log2(maximum power of 2 <= numTables).
        rangeShift   : USHORT;          //  NumTables x 16-searchRange.
    end;

    //  Table Directory
    TTC_TABLE_DIRECTORY = packed record
        tag     : array[0..3] of char;  //  4 -byte identifier.
        checkSum: ULONG;                //  CheckSum for this table.
        offset  : ULONG;                //  Offset from beginning of TrueType font file.
        length  : ULONG;                //  Length of this table.
    end;

    //  Naming Table
    TTC_NAMING_TABLE = packed record

    end;

    //  ----------------------------------------- from CodeGuruC3659.pas
    //  Header of the names table
    TT_NAME_TABLE_HEADER = packed record
        uFSelector    : USHORT;        //  format selector. Always 0
        uNRCount      : USHORT;        //  Name Records count
        uStorageOffset: USHORT;     //  Offset for strings storage,
                                    //  from start of the table
    end;

    //  ----------------------------------------- from CodeGuruC3659.pas
    //  Records in the names table
    TT_NAME_RECORD = packed record
        uPlatformID   : USHORT;
        uEncodingID   : USHORT;
        uLanguageID   : USHORT;
        uNameID       : USHORT;
        uStringLength : USHORT;
        uStringOffset : USHORT;     //  from start of storage area
    end;

//  ---------------------------------------------------------------------------
//  #define SWAPWORD(x) MAKEWORD(HIBYTE(x), LOBYTE(x))
function    SWAPWORD(x: Word): Word;
begin
    Result := MakeWord(HiByte(x), LoByte(x));
end;

//  ---------------------------------------------------------------------------
//  #define SWAPLONG(x) MAKELONG(SWAPWORD(HIWORD(x)),SWAPWORD(LOWORD(x)))
function    SWAPLONG(x: Cardinal): Cardinal;
begin
    Result := MakeLong(SWAPWORD(HiWord(x)), SWAPWORD(LoWord(x)));
end;

//  ---------------------------------------------------------------------------
procedure GetFontNameFromTTC(lpszFilePath: string; Results: TStrings);
var
    F: TFileStream;
    ttc_header_ver10: TTC_HEADER_VERSION_10;
    FontCount: integer;
    Offsets: array[0..256] of ULONG;
    i, j: integer;
    ttcOffsetTable: TTC_OFFSET_TABLE;
    TableCount: integer;
    ttcTableDir: TTC_TABLE_DIRECTORY;
    csTemp: string;
    ttNTHeader: TT_NAME_TABLE_HEADER;
    ttRecord: TT_NAME_RECORD;
    bFound: Boolean;
    nPos, nPos2: integer;
    Buf: array[0..1024] of char; //  結局...
begin
    //  lpszFilePath is the path to our font file
    F := TFileStream.Create(lpszFilePath, fmOpenRead or fmShareDenyWrite);
    try
        //  ヘッダ情報読込
        F.Read(ttc_header_ver10, SizeOf(ttc_header_ver10));

        //  TrueType Collection かを確認
        if (ttc_header_ver10.TTCTag <> 'ttcf') then exit;

        //  バージョンを Big Endian -> Little Endian
        ttc_header_ver10.Version := SWAPLONG(ttc_header_ver10.Version);

        //  バージョン判定(不要)
        if (ttc_header_ver10.Version = $00010000) then
        begin
            //  Version 1.0
        end
        else if (ttc_header_ver10.Version = $00020000) then
        begin
            //  Version 2.0
        end;

        //  含まれるフォントの数を調べる
        ttc_header_ver10.numFonts := SWAPLONG(ttc_header_ver10.numFonts);
        FontCount := ttc_header_ver10.numFonts;

        //  各フォント情報のオフセット位置を格納する配列をクリア
        FillChar(Offsets, SizeOf(Offsets), 0);

        //  ストリームを少しだけ巻き戻し
        F.Seek(- SizeOf(ULONG), soFromCurrent);

        //  オフセット位置を読み込み
        F.Read(Offsets, SizeOf(ULONG) * FontCount);

        //  オフセット位置をエンディアン変換
        for i := 0 to FontCount - 1 do
        begin
            Offsets[i] := SWAPLONG(Offsets[i]);
        end;

        for i := 0 to FontCount - 1 do
        begin
            //  ストリームを早送り
            F.Seek(Offsets[i], soFromBeginning);

            //  オフセットテーブルを読み込む
            F.Read(ttcOffsetTable, SizeOf(ttcOffsetTable));

            //  エンディアン変換
            ttcOffsetTable.numTables := SWAPWORD(ttcOffsetTable.numTables);
            TableCount := ttcOffsetTable.numTables;

            //  [name]タグがあるかどうかフラグ
            bFound := false;

            //  ここからすぐに Table Directory がはじまる
            for j := 0 to TableCount - 1 do
            begin
                //  読込
                F.Read(ttcTableDir, SizeOf(ttcTableDir));

                //  [name]タグを検索
                if (ttcTableDir.tag = 'name') then
                begin
                    bFound := true;
                    break;
                end;
            end;

            //  見つからなかったら次
            if not bFound then continue;

            //  エンディアン変換
            ttcTableDir.checkSum := SWAPLONG(ttcTableDir.checkSum);
            ttcTableDir.offset := SWAPLONG(ttcTableDir.offset);
            ttcTableDir.length := SWAPLONG(ttcTableDir.length);

            //  現在位置記録
            nPos := F.Position;

            //  指定位置まで移動
            F.Seek(ttcTableDir.offset, soFromBeginning);

            //  ※以降はへろへろ。
            //  名称構造体読込
            F.Read(ttNTHeader, SizeOf(ttNTHeader));

            //  エンディアン変換
            ttNTHeader.uNRCount       := SWAPWORD(ttNTHeader.uNRCount);
            ttNTHeader.uStorageOffset := SWAPWORD(ttNTHeader.uStorageOffset);

            //  名称レコードの数だけ繰り返し
            for j := 0 to ttNTHeader.uNRCount - 1 do
            begin
                F.Read(ttRecord, SizeOf(TT_NAME_RECORD));
                ttRecord.uNameID := SWAPWORD(ttRecord.uNameID);

                //  4 はフルネーム
                if (ttRecord.uNameID = 6) then
                begin
                    ttRecord.uStringLength := SWAPWORD(ttRecord.uStringLength);
                    ttRecord.uStringOffset := SWAPWORD(ttRecord.uStringOffset);

                    //  現在位置記録
                    nPos2 := F.Position;
                    F.Seek(ttcTableDir.offset
                           + ttRecord.uStringOffset
                           + ttNTHeader.uStorageOffset,
                           soFromBeginning);

                    FillChar(Buf, SizeOf(Buf), 0);
                    F.Read(Buf, ttRecord.uStringLength);
                    csTemp := string(Buf);

                    //  あれば次へ
                    if (csTemp <> '') then
                    begin
                        Results.Add(String(Buf));
                        break;
                    end;
                    F.Seek(nPos2, soFromBeginning);
                end;
            end;

            //  巻き戻し
            F.Seek(nPos, soFromBeginning);
        end;

    finally
        F.Free;
    end;
end;

end.

sampleTOP

ソースコードと実行ファイルです。
20041003fontstudy.zip(178,865bytes)

フォルダ選択ボタンで適当にTrue Type Fontが入っている場所を選択すると、サブフォルダを適当に掘って.ttf/.ttcファイルを列挙し「取り出したフォント名 << ファイル名」の形でTMemoに追加していきます。

前回は「.ttf」だけでしたが、今回は「.ttf + .ttc」になりました。でも外見は全く同じです。

それから、なぜか今回の関数では、コンパイラオプションの実行時エラーで「範囲チェック」をはずさないとエラーになってしまいます。厭な感じです。

以前にDelphi6にサンプルでついているリソースエクスプローラをいじってたときにも、こんなことになったような気がしますが、バイナリファイルを解析するような処理には付き物なんでしょうか?もしくは自分がへぼすぎるんでしょうか?

あ、最後になりましたが、動いています。正しいかどうかは別として。

EOFTOP