[BlueLeaf1336]> PROGRAM>

.TTFからフォント名抽出

historyTOP

2004/06/16:作成

overviewTOP

CodeGuru: Retrieving the Font Name from a TTF FileにあるコードをDelphiに翻訳します。それだけです。いや、この時点では翻訳し終わってます。次に示すのがそうですが、意味を理解せずに訳しています。なのでコメントも英語のままだったり。

実際のところ、ソースを掲載する必要はないんですが、「あーDelphiで書き直したいのにできないよー」という人が稀に発生することを期待しています。で、構造体の名前とかで検索してこのページが引っかかったり引っかからなかったり。

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

参考サイトTOP

codeTOP

(*
    CodeGuru: Retrieving the Font Name from a TTF File
    http://www.codeguru.com/Cpp/G-M/gdi/fonthandlinganddetection/article.php/c3659/
*)

unit CodeGuruC3659;

interface

uses
    Windows, SysUtils, Classes;

function    GetFontNameFromFile(lpszFilePath: string): string;

implementation

type
    USHORT = Word;
    ULONG = Longword;

type
    //  -----------------------------------------
    //  This is the TTF file header
    TT_OFFSET_TABLE = packed record
        uMajorVersion : USHORT;
        uMinorVersion : USHORT;
        uNumOfTables  : USHORT;
        uSearchRange  : USHORT;
        uEntrySelector: USHORT;
        uRangeShift   : USHORT;
    end;

    //  -----------------------------------------
    //  Tables in the TTF file and their placement and name (tag)
    TT_TABLE_DIRECTORY = packed record
        szTag         : array[0..3] of char;    //  table name
        uCheckSum     : ULONG;                  //  Check sum
        uOffset       : ULONG;                  //  Offset from beginning of file
        uLength       : ULONG;                  //  length of the table in bytes
    end;

    //  -----------------------------------------
    //  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;

    //  -----------------------------------------
    //  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;

//  ---------------------------------------------------------------------------
function    GetFontNameFromFile(lpszFilePath: string): string;
var
    F: TFileStream;
    csRetVal: string;

    ttOffsetTable: TT_OFFSET_TABLE;

    Version: integer;

    tblDir: TT_TABLE_DIRECTORY;
    bFound: Boolean;
    i: integer;
    csTemp: string;

    ttNTHeader: TT_NAME_TABLE_HEADER;
    ttRecord: TT_NAME_RECORD;

    nPos: 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(ttOffsetTable, SizeOf(TT_OFFSET_TABLE));

        //  remember to rearrange bytes in the field you're going to use
        ttOffsetTable.uNumOfTables  := SWAPWORD(ttOffsetTable.uNumOfTables);
        ttOffsetTable.uMajorVersion := SWAPWORD(ttOffsetTable.uMajorVersion);
        ttOffsetTable.uMinorVersion := SWAPWORD(ttOffsetTable.uMinorVersion);

        //  check is this is a true type font and the version is 1.0
        Version := ttOffsetTable.uMajorVersion * 10 + ttOffsetTable.uMinorVersion;
        if (Version = 10) then
        begin
            bFound := false;

            for i := 0 to ttOffsetTable.uNumOfTables - 1 do
            begin
                //  the table's tag cannot exceed 4 characters
                F.Read(tblDir, SizeOf(TT_TABLE_DIRECTORY));
                csTemp := string(tblDir.szTag);
                if (LowerCase(csTemp) = 'name') then
                begin
                    //  we found our table. Rearrange order and quit the loop
                    bFound := true;
                    tblDir.uLength := SWAPLONG(tblDir.uLength);
                    tblDir.uOffset := SWAPLONG(tblDir.uOffset);
                    break;
                end;
            end;

            if (bFound) then
            begin
                //  move to offset we got from Offsets Table
                F.Seek(tblDir.uOffset, soFromBeginning);
                F.Read(ttNTHeader, SizeOf(TT_NAME_TABLE_HEADER));
                //  again, don't forget to swap bytes!
                ttNTHeader.uNRCount       := SWAPWORD(ttNTHeader.uNRCount);
                ttNTHeader.uStorageOffset := SWAPWORD(ttNTHeader.uStorageOffset);

                for i := 0 to ttNTHeader.uNRCount - 1 do
                begin
                    F.Read(ttRecord, SizeOf(TT_NAME_RECORD));
                    ttRecord.uNameID := SWAPWORD(ttRecord.uNameID);
                    //  1 says that this is the font name. 0, for example,
                    //  determines copyright info
                    if (ttRecord.uNameID = 1) then
                    begin
                        ttRecord.uStringLength := SWAPWORD(ttRecord.uStringLength);
                        ttRecord.uStringOffset := SWAPWORD(ttRecord.uStringOffset);
                        //  save file position so we can return to continue with search
                        nPos := F.Position;
                        F.Seek(tblDir.uOffset
                               + ttRecord.uStringOffset
                               + ttNTHeader.uStorageOffset,
                               soFromBeginning);

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

                        //  yes, still need to check if the font name is not empty
                        //  if it is, continue the search
                        if (csTemp <> '') then
                        begin
                            csRetVal := csTemp;
                            break;
                        end;

                        F.Seek(nPos, soFromBeginning);
                    end;
                end;

            end;
        end;
    finally
        F.Free;
    end;

    Result := csRetVal;
end;

end.

sampleTOP

ソースコードと実行ファイルです。
20040616fontstudy.zip(4,765ytes)

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

WindowsのFontsフォルダを試してみるとアルファベットフォントについては大体動いているようですが、日本語フォントは一切無視してるようです。というかこの関数を呼ぶ前の、フォントの列挙すらできてません。

それはそれとして(Screen.Fontsとかでできるし)、この関数のすばらしさはインストールしていなくてもフォントファイルさえあれば、フォント名を取得できるという点にあります。ずっとこういうのを探してたんですが、やっとみつけました。

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

EOFTOP