2012. 10. 21
電子工作でよく使われるようになったFONTX2形式のフォント・ファイルについて、その歴史とファイル・フォーマットについて簡単に解説します。
'90年代初頭、日本IBMからDOS/V (IBM DOS J4.0/V)が発売されました。DOS/Vは、英語DOSにいくつかの日本語入出力のためのデバイス・ドライバを追加したもので、日本語表示機能を持たないIBM ATおよび互換機にハードウェアの追加なしに日本語表示を実現することができました。DOS/Vシステムは、右の図のような構成となります。なお、その名の最後に付く"V"はVGAというビデオ・サブシステムを示し、「IBM日本語DOS Version 4.0 for VGA」という意味になります。
ディスプレイ・ドライバ($DISP.SYS)は、ビデオBIOS(INT 10h)を拡張して、アプリケーションの文字出力要求を処理します。文字はビットマップ画面に描画されるため、日本語を含む任意の文字を出力することができます。これにより、「ごく一部」の英語アプリケーションはそのまま日本語を扱えるようになりました。ごく一部というのは、当時のDOSアプリケーションは、ビデオBIOSを介さずVRAMに直接書き込むものが一般的だったためです。それでも僅かな変更で日本語化できるように、仮想VRAMという機能が提供されています。しかし、2バイト文字という根本的な問題もあり、そう簡単にはいきませんでしたが。
日本語表示に必要なフォントは、フォント・ドライバ($FONT.SYS)が管理します。フォント・ドライバは、ロード時にフォント・ファイルをメモリに読み込み、システムBIOS(INT 15h)を介してフォント・サーバとして機能します。また、漢字ROMのあるマシン(PS/55など)ではそれを有効利用します。200Kバイト程度になるフォント・データはEMB領域に置かれるため、コンベンショナル・メモリを圧迫することはありませんが、これによりDOS/Vの動作条件は80286以上のCPUとなっています。
さて、当時の日本のビジネスパソコンはNEC PC-9800シリーズの独占状態で、これといって目新しいことが無かったため、物好きなハッカーたちがこれを見逃すはずがありません。DOS/VはAPI仕様が広く公開されていたこともあり、機能を拡張された互換ドライバが相次いでリリースされることになります。雨後の筍のごとくDOS/V誌が創刊されたのもこの頃です。
たとえば、ディスプレイ・ドライバでは、SVGAアダプタに対応してVGAを越えるサイズの画面で広大なテキスト表示を実現したり、グラフィック・アクセラレータを駆使した表示の高速化などが行われました。逆にポケットPC用にCGAに対応したものもありました。後に、日本IBMもこれに追随して純正のドライバを同様に拡張し、その際/Vの意味をVGAからVariableへと再定義しています。ディスプレイ・ドライバについては、私も自作して楽しんでいました(まだVectorのライブラリに残っています)。
フォント・ドライバでは、複数のフォントをロードして動的に切り替えられたり、ロード先にEMS領域や機種固有のメモリ領域を選択できるなどの拡張が行われ、柔軟な運用が可能になり、動作環境も広がっています。これはディスプレイ・ドライバほど多くはなく、IBM純正以外のドライバとしてはFONTXが主流となり、多くのフォント・ドライバもフォント・ファイルとしてFONTX形式を利用するようになりました。このため、DOS/V用として出回るフォント・ファイルもほぼFONTX形式に統一されています。
間もなく時代はWindowsへと移り、DOS/Vは終焉を迎えることになります。そして、膨大なDOSのソフトウェア資産とともにFONTX形式のフォントも忘れ去られた存在となりました。ところが、その後何年か経ち'00年代も半ばになると、電子工作界において高性能なマイコンや安価なグラフィック・ディスプレイの利用が普及してきました。それに伴い、その日本語表示のために再びFONTXファイルが利用されるようになったのです。FONTX形式はファイル・フォーマットがとても単純で、何よりフリーのフォントが多く出回っていたので、最も手軽に利用できるビットマップ・フォントといえます。このように、電子工作というマイナーな世界ではありますが、FONTX形式は再び日の目を見ることになります。
FONTXファイルは、BDFファイルなどのテキスト形式とは違い、次の図に示すようにバイナリ形式となっています。FONTXファイルには半角フォントと全角フォントの2種類があり、それらは文字コード・フラグによって識別されます。半角フォントファイルには8ビット・コードの256個のフォントイメージが格納されます。全角フォント・ファイルは、16ビット・コード(シフトJIS)ですが、65536個分の格納スペースを固定長フィールドとして確保するのは効率が悪いので、図に示すように有効な文字コードの範囲を示すコード・ブロック・テーブルが設けられ、必要なものだけ格納されます。各ブロックは文字コードの小さい順にテーブルに記述され、フォントイメージもそれにしたがって順に詰めて格納されます。
| Offset | Size | フィールド内容 |
|---|---|---|
| 0 | 6 | ファイル・シグネチャ("FONTX2") |
| 6 | 8 | フォント名 |
| 14 | 1 | フォント幅 WF(ドット) |
| 15 | 1 | フォント高さ HF(ドット) |
| 16 | 1 | 文字コード・フラグ(0:ANK) |
| 17 | ※1 | フォントイメージ (※1:フォントサイズ×256) |
| Offset | Size | フィールド内容 | |
|---|---|---|---|
| 0 | 6 | ファイル・シグネチャ("FONTX2") | |
| 6 | 8 | フォント名 | |
| 14 | 1 | フォント幅 WF (ドット) | |
| 15 | 1 | フォント高さ HF (ドット) | |
| 16 | 1 | 文字コード・フラグ (1:シフトJIS) | |
| 17 | 1 | コード・ブロック数 NB | |
| 18 | 2 | ブロック1開始コード | コード・ブロック・テーブル (リトル・エンディアン) |
| 20 | 2 | ブロック1終了コード | |
| … | … | … | |
| 14+4*NB | 2 | ブロックNB開始コード | |
| 16+4*NB | 2 | ブロックNB終了コード | |
| 18+4*NB | ※2 | フォントイメージ (※2:フォントサイズ×各ブロックのコード数の総和) | |
フォントイメージは、次の図に示すようにデータの各ビットが文字のドットに対応します。フォントの幅が8の倍数に一致しないときは、左詰めで格納されます。

フォント・イメージのサイズは、1文字あたり(WF + 7) / 8 * HF [バイト] となります。それぞれの文字コードに対応するフォントイメージのファイル先頭からの位置は、次のようになります。
FONTXファイルの編集には専用ツールが必要になりますが、なぜかWindows用のフォント・エディタが存在しません。Windows時代にはFONTX自体が不要になってしまったため当然と言えばそうですが、FONTXを使う以上は無いと困るのでFONTXエディタを作ってみました。
例として下のリストに、メモリ上に置かれたFONTXファイルから指定された文字コードのフォントを得る関数を示します。引数にはFONTXデータと文字コードを指定し、指定された文字コードに対応するフォントイメージを指すポインタを返します。文字コードが無効なときはヌルポインタを返します。
const uint8_t* get_font ( /* Returns pointer to the font image (NULL:invalid code) */ const uint8_t* font, /* Pointer to the FONTX file stored on the memory */ uint16_t code /* Character code */ ) { unsigned int nc, bc, sb, eb; uint32_t fsz; const uint8_t *cblk; fsz = (font[14] + 7) / 8 * font[15]; /* Get font size */ if (font[16] == 0) { /* Single byte code font */ if (code < 0x100) return &font[17 + code * fsz]; } else { /* Double byte code font */ cblk = &font[18]; nc = 0; /* Code block table */ bc = font[17]; while (bc--) { sb = cblk[0] + cblk[1] * 0x100; /* Get range of the code block */ eb = cblk[2] + cblk[3] * 0x100; if (code >= sb && code <= eb) { /* Check if in the code block */ nc += code - sb; /* Number of codes from top of the block */ return &font[18 + 4 * font[17] + nc * fsz]; } nc += eb - sb + 1; /* Number of codes in the previous blocks */ cblk += 4; /* Next code block */ } } return 0; /* Invalid code */ }
