はじめに
このサイトでは、フォントファイルの内部構造を徹底的に解説し、最終的には自分でプログラムを書いてフォントを作成できるレベルまで理解を深めることを目標としています。
フォントファイル形式の基礎
主要なフォント形式
TrueType (.ttf)
- Apple & Microsoft共同開発
- 2次ベジェ曲線で輪郭を定義
- 比較的シンプルな構造
OpenType (.otf)
- TrueTypeの拡張版
- PostScript輪郭も使用可能
- 高度なタイポグラフィ機能
WOFF/WOFF2
- Web用に最適化
- TTF/OTFを圧縮
- メタデータ付き
フォントファイルの基本構造
バイナリ構造の例
0x0000: 00 01 00 00 // sfnt version (TrueType) 0x0004: 00 0D // テーブル数: 13 0x0006: 00 80 // searchRange 0x0008: 00 03 // entrySelector 0x000A: 00 60 // rangeShift 0x000C: 63 6D 61 70 // "cmap" テーブルタグ 0x0010: 00 00 12 34 // checksum 0x0014: 00 00 01 00 // offset 0x0018: 00 00 02 56 // length
TrueTypeフォント構造の詳細
グリフ輪郭の定義方法
ポイントデータ構造
struct GlyphPoint { int16_t x; // X座標 int16_t y; // Y座標 uint8_t flags; // フラグ(On/Off curve等) }; // フラグのビット定義 #define ON_CURVE_POINT 0x01 #define X_SHORT_VECTOR 0x02 #define Y_SHORT_VECTOR 0x04 #define REPEAT_FLAG 0x08 #define X_IS_SAME_OR_POS 0x10 #define Y_IS_SAME_OR_POS 0x20
TrueTypeテーブル構造
必須テーブル
テーブル名 | 用途 | 構造 |
---|---|---|
cmap |
文字コード→グリフIDマッピング | Platform ID, Encoding ID, サブテーブル |
glyf |
グリフデータ | 輪郭データ、複合グリフ情報 |
head |
フォントヘッダー | バージョン、作成日、バウンディングボックス |
hhea |
水平ヘッダー | アセンダー、ディセンダー、行間 |
hmtx |
水平メトリクス | 文字幅、左サイドベアリング |
loca |
インデックス | グリフデータのオフセット |
maxp |
最大値プロファイル | グリフ数、ポイント数の最大値 |
name |
名前テーブル | フォント名、著作権情報 |
post |
PostScript情報 | PostScript名、メトリクス |
2次ベジェ曲線の仕組み
// 2次ベジェ曲線の計算 function quadraticBezier(p0, p1, p2, t) { // B(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂ const x = Math.pow(1-t, 2) * p0.x + 2 * (1-t) * t * p1.x + Math.pow(t, 2) * p2.x; const y = Math.pow(1-t, 2) * p0.y + 2 * (1-t) * t * p1.y + Math.pow(t, 2) * p2.y; return {x, y}; }
OpenTypeフォント構造の詳細
OpenTypeの拡張機能
GSUB (Glyph Substitution)
GPOS (Glyph Positioning)
OpenTypeレイアウトテーブル
// OpenTypeレイアウトテーブルの構造 struct GSUB_Header { uint16_t majorVersion; uint16_t minorVersion; uint16_t scriptListOffset; uint16_t featureListOffset; uint16_t lookupListOffset; uint32_t featureVariationsOffset; // version 1.1 }; // フィーチャーテーブル struct Feature { uint16_t featureParams; // NULL or offset uint16_t lookupIndexCount; uint16_t lookupListIndices[]; // 可変長配列 };
CFFアウトライン(PostScript)
// 3次ベジェ曲線の計算 function cubicBezier(p0, p1, p2, p3, t) { // B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃ const x = Math.pow(1-t, 3) * p0.x + 3 * Math.pow(1-t, 2) * t * p1.x + 3 * (1-t) * Math.pow(t, 2) * p2.x + Math.pow(t, 3) * p3.x; const y = Math.pow(1-t, 3) * p0.y + 3 * Math.pow(1-t, 2) * t * p1.y + 3 * (1-t) * Math.pow(t, 2) * p2.y + Math.pow(t, 3) * p3.y; return {x, y}; }
グリフデータ構造の詳細
単純グリフ vs 複合グリフ
単純グリフ
複合グリフ
グリフデータのバイナリ構造
// 単純グリフのヘッダー struct SimpleGlyphHeader { int16_t numberOfContours; // 輪郭数(正の値) int16_t xMin; int16_t yMin; int16_t xMax; int16_t yMax; }; // 複合グリフのヘッダー struct CompositeGlyphHeader { int16_t numberOfContours; // -1(複合グリフを示す) int16_t xMin; int16_t yMin; int16_t xMax; int16_t yMax; }; // 複合グリフのコンポーネント struct CompositeComponent { uint16_t flags; uint16_t glyphIndex; // 変換マトリックスまたはオフセット値が続く };
グリフ描画の座標系
ヒンティング情報
TrueTypeインストラクション
// TrueTypeヒンティング命令の例 PUSHB[0] 5 // スタックに5をプッシュ MDRP[10] // Move Direct Relative Point PUSHB[0] 10 MIRP[01] // Move Indirect Relative Point IUP[1] // Interpolate Untouched Points
フォントメトリクスと配置
垂直メトリクス
水平メトリクス
メトリクステーブルの構造
// hmtx(水平メトリクス)テーブル struct HorizontalMetrics { uint16_t advanceWidth; // 文字送り幅 int16_t leftSideBearing; // 左サイドベアリング }; // hhea(水平ヘッダー)テーブル struct HorizontalHeader { Fixed version; FWord ascent; // アセンダー FWord descent; // ディセンダー(負の値) FWord lineGap; // 行間 uFWord advanceWidthMax; // 最大文字送り幅 FWord minLeftSideBearing; FWord minRightSideBearing; FWord xMaxExtent; int16_t caretSlopeRise; int16_t caretSlopeRun; int16_t caretOffset; int16_t reserved[4]; int16_t metricDataFormat; uint16_t numOfLongHorMetrics; };
インタラクティブ視覚化ツール
フォントファイルビューア
テーブル一覧
テーブル詳細
グリフプレビュー
グリフエディタ
ポイントリスト
ID | X | Y | Type |
---|
実践:フォント作成
最小限のTrueTypeフォントを作る
// 最小限のTrueTypeフォント生成(Python) import struct import datetime class MinimalTTF: def __init__(self): self.tables = {} def create_head_table(self): # headテーブルの作成 head = struct.pack( '>HHHHHQQhhhhhhhhhhh', 0x00010000, # version 0x00010000, # fontRevision 0, # checkSumAdjustment 0x5F0F3CF5, # magicNumber 0x0001, # flags 2048, # unitsPerEm 0, # created(後で設定) 0, # modified(後で設定) 0, # xMin 0, # yMin 1000, # xMax 1000, # yMax 0, # macStyle 7, # lowestRecPPEM 2, # fontDirectionHint 0, # indexToLocFormat 0 # glyphDataFormat ) self.tables['head'] = head def create_simple_glyph(self): # 四角形のグリフを作成 glyph_data = struct.pack( '>hhhhh', 1, # numberOfContours 100, # xMin 100, # yMin 900, # xMax 900 # yMax ) # 輪郭終点 glyph_data += struct.pack('>H', 3) # endPtsOfContours[0] # 命令長 glyph_data += struct.pack('>H', 0) # instructionLength # フラグ flags = bytes([0x01, 0x01, 0x01, 0x01]) # ON_CURVE_POINT glyph_data += flags # X座標 x_coords = struct.pack('>hhhh', 100, 900, 900, 100) glyph_data += x_coords # Y座標 y_coords = struct.pack('>hhhh', 100, 100, 900, 900) glyph_data += y_coords return glyph_data
フォント作成のベストプラクティス
1. 座標系の理解
- フォントデザインの単位(通常2048または1000)
- ベースライン基準の座標系
- 整数座標への丸め処理
2. アウトラインの最適化
- 不要なポイントの削除
- 滑らかな曲線の生成
- 極値点の配置
3. メトリクスの設定
- 適切な文字幅の設定
- ベースラインの統一
- カーニングペアの調整
フォント検証ツール
作成したフォントの検証
// フォント検証関数 function validateFont(fontData) { const errors = []; const warnings = []; // ヘッダー検証 if (fontData.head.unitsPerEm < 16 || fontData.head.unitsPerEm > 16384) { errors.push('Invalid unitsPerEm value'); } // 必須テーブルの確認 const requiredTables = ['cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', 'post']; requiredTables.forEach(table => { if (!fontData.tables[table]) { errors.push(`Missing required table: ${table}`); } }); // グリフ検証 for (let i = 0; i < fontData.numGlyphs; i++) { const glyph = fontData.glyphs[i]; if (glyph.numberOfContours < -1) { errors.push(`Invalid contour count in glyph ${i}`); } } return {errors, warnings}; }