フォントの気持ちになるですよ。
……正確にはフォントというより、フォントレンダリングソフトウェアの気持ちになってフォントを読んでみよう、という感じでごぜーますが。
OpenType Font File
OpenType specification - Typography | Microsoft Docs
このOpenTypeの仕様をみながら読んでいきます。
特に、OpenTypeフォントファイルの仕様についての次のページを参考にします。
OpenType font file - Typography | Microsoft Docs
最初にデータ型について書かれています。
特に説明を要するものもなさそうですが、固定小数点数を10進数に直すには、整数として読み出し、2小数部分のビット数で割ると実際の値になるようです。
さて、初めから読んでいきます。OpenTypeフォーマットはバイトオーダがビッグエンディアンなので直感的ですね。
最初に来るのが0x00010000だから(TTC Headerでなく)Offset Tableなので、TTCでない単一のフォントでかつTrueTypeアウトラインのものだとわかります。
Table Record
Type |
Name |
Description |
ULONG |
tag |
テーブルを特定するための4文字の識別子 |
ULONG |
checkSum |
テーブルのチェックサム |
ULONG |
offset |
テーブルのフォントファイルの先頭からのオフセット |
ULONG |
length |
テーブルの長さ |
直後にくるのがTable Recordで、これがOffset TableのnumTables……つまり19個並んでいるはずです。読みだすと次のようになっていました。
チェックサムの確認は面倒なのでしませんでしたが、offsetとlengthから計算できる値で計算方法は仕様を参照。
tag |
checkSum |
offset |
length |
'FFTM' |
0x61D6EAF7 |
0x013C (316) |
0x001C (28) |
'GDEF' |
0x005C0024 |
0x0158 (344) |
0x0028 (40) |
'GPOS' |
0xA176C2E4 |
0x0180 (384) |
0x00D0 (208) |
'GSUB' |
0x9966BC6F |
0x0250 (592) |
0x0076 (118) |
'OS/2' |
0x79FC780F |
0x02C8 (712) |
0x0060 (96) |
'cmap' |
0xD6E8B4E8 |
0x0328 (808) |
0x0182 (386) |
'cvt ' |
0x0F5312F5 |
0x04AC (1196) |
0x0048 (72) |
'fpgm' |
0x53B42FA7 |
0x04F4 (1268) |
0x0265 (613) |
'gasp' |
0x00160023 |
0x075C (1884) |
0x0010 (16) |
'glyf' |
0xF470EA57 |
0x076C (1900) |
0x1164 (4452) |
'head' |
0x0D957EF6 |
0x18D0 (6352) |
0x0036 (54) |
'hhea' |
0x0EF404F9 |
0x1908 (6408) |
0x0024 (36) |
'hmtx' |
0x9AC30ABF |
0x192C (6444) |
0x00B4 (180) |
'loca' |
0x7CD081CC |
0x19E0 (6624) |
0x005C (92) |
'maxp' |
0x014700FD |
0x1A3C (6716) |
0x0020 (32) |
'name' |
0x8B41D5BC |
0x1A5C (6748) |
0x06A4 (1700) |
'post' |
0xE1EBCFD6 |
0x2100 (8448) |
0x0103 (259) |
'prep' |
0x7730038F |
0x2204 (8708) |
0x015A (346) |
'webf' |
0x62B657D9 |
0x2360 (9056) |
0x0006 (6) |
必ずしもあるテーブルについのoffset+lengthが次のテーブルのoffsetとなっていないのは、テーブルが4バイト境界にアラインメントされるためで、lengthには実際のテーブルの長さが記録されるのだそうです。実際のテーブルから次の4バイト境界までの隙間は0で埋められます。
'FFTM'はFontForge Time StampでFontForgeが出力するデータ、'webf'は不明ですがおそらくWebfontの略か、それぞれ独自テーブルなので無視します。
さて、OpenType tableのリストとその位置が得られたので、見ていきます。
どういう順番で見てくのがいいんでしょうか。とりあえずheadから行きます。
'head' - Font Header Table
フォントヘッダだそうです。開始オフセットが0x18D0なのでそこから見ていきます。54バイトなので短い。
Type |
Name |
Value |
備考 |
USHORT |
majorVersion |
0x0001 |
1固定 |
USHORT |
minorVersion |
0x0000 |
0固定 |
Fixed |
fontRevision |
0x00010F5C |
1.06くらい |
ULONG |
checkSumAdjustment |
0xEC47630E |
|
ULONG |
magicNumber |
0x5F0F3CF5 |
0x5F0F3CF5固定 |
USHORT |
flags |
0x001F |
ベースラインがy=0; Left sidebearing point at x=0; Instructions may depend on point size; Force ppem to integer values for all internal scaler math; Instructions may alter advance width |
USHORT |
unitsPerEm |
0x0800 |
2048 |
LONGDATETIME |
created |
0x00000000D3FF1335 |
2016-09-14T14:46:13+00:00 mac時間らしい |
LONGDATETIME |
modified |
0x00000000D3FF1335 |
2016-09-14T14:46:13+00:00 |
SHORT |
xMin |
0xFF8F |
-113 バウンディングボックスのx最小値 |
SHORT |
yMin |
0xFE14 |
-492 同y最小値 |
SHORT |
xMax |
0x06D5 |
1749 同x最大値 |
SHORT |
yMax |
0x061F |
1567 同y最大値 |
USHORT |
macStyle |
0x0000 |
なし |
USHORT |
lowestRecPPEM |
0x0008 |
最小可読サイズ。8px |
SHORT |
fontDirectionHint |
0x0002 |
2固定。廃止 |
SHORT |
indexToLocFormat |
0x0000 |
short offsets |
SHORT |
glyphDataFormat |
0x0000 |
0固定 |
offsetが0x02C8なのでそこから読みます。
OS/2 - OS/2 and Windows metrics table specification - Typography | Microsoft Docs (Version 5)
実質Windows専用なんですかね。
はじめ2バイトのversionが0x0004であるからこれがVersion 4のOS/2テーブルということが分かります。
OS/2 - OS/2 and Windows metrics table specification (version 4) - Typography | Microsoft Docs (Version 4)
Type |
Name of Entry |
Value |
comment |
USHORT |
version |
0x0004 |
4 |
SHORT |
xAvgCharWidth |
0x0387 |
903; 幅が0でないグリフの幅の算術平均 |
USHORT |
usWeightClass |
0x0190 |
400 = Normal (Regular) |
USHORT |
usWidthClass |
0x0005 |
5 = Medium (normal) |
USHORT |
fsType |
0x0004 |
4 = 表示・印刷用にフォント埋め込み可(編集不可) |
SHORT |
ySubscriptXSize |
0x059A |
1434; 下付き文字の大きさの目安 |
SHORT |
ySubscriptYSize |
0x0533 |
1331 |
SHORT |
ySubscriptXOffset |
0x0000 |
0; 下付き文字の位置の目安 |
SHORT |
ySubscriptYOffset |
0x011F |
287 |
SHORT |
ySuperscriptXSize |
0x059A |
1434; 上付き文字の大きさの目安 |
SHORT |
ySuperscriptYSize |
0x0533 |
1331 |
SHORT |
ySuperscriptXOffset |
0x0000 |
0; 上付き文字の位置の目安 |
SHORT |
ySuperscriptYOffset |
0x03D1 |
977 |
SHORT |
yStrikeoutSize |
0x0066 |
102; 打消線の幅 |
SHORT |
yStrikeoutPosition |
0x0200 |
512; 打消線の上側の位置 |
SHORT |
sFamilyClass |
0x0802 |
2050 |
BYTE |
panose[10] |
0x020B0502040504020204 |
|
ULONG |
ulUnicodeRange1 |
0xE00002FF |
Basic Latin, Latin-1 Supplement, Latin Extended-A, Latin Extended-B, IPA Extensions, Phonetic Extensions, Phonetic Extensions Supplement, Spacing Modifier Letters, Modifier Tone Letters, Combining Diacritical Marks, Combining Diacritical Marks Supplement, Greek and Coptic, Cyrillic, Cyrillic Supplement, Cyrillic Extended-A, Cyrillic Extended-B, Latin Extended Additional, Latin Extended-C, Latin Extended-D, Greek Extended, General Punctuation, Supplemental Punctuation |
ULONG |
ulUnicodeRange2 |
0x00000000 |
|
ULONG |
ulUnicodeRange3 |
0x00000000 |
|
ULONG |
ulUnicodeRange4 |
0x00000000 |
|
CHAR |
achVendID[4] |
0x474F4F47 |
'GOOG'; 大文字IDはMicrosoftに登録しないと使えないらしい |
USHORT |
fsSelection |
0x0040 |
REGULAR |
USHORT |
usFirstCharIndex |
0x000D |
CR…フォント内の最小のUnicode index |
USHORT |
usLastCharIndex |
0x25FC |
'◼'…フォント内の最大のUnicode index |
SHORT |
sTypoAscender |
0x088D |
2189; typographic ascender; 'hhea'のAscenderとは異なる |
SHORT |
sTypoDescender |
0xFDA8 |
-600; typographic descender; 'hhea'のDescenderとは異なる |
SHORT |
sTypoLineGap |
0x0000 |
0; typographic line gap; 'hhea'のLineGapとは異なる |
USHORT |
usWinAscent |
0x088D |
2189; ascender metric for Windows |
USHORT |
usWinDescent |
0x0258 |
600; descender metric for Windows |
ULONG |
ulCodePageRange1 |
0x2000019F |
サポートするコードページ: Latin 1, Latin 2: Eastern Europe, Cyrillic, Greek, Turkish, Windows Baltic, Vietnamese, Macintosh Character Set (US Roman), |
ULONG |
ulCodePageRange2 |
0xDFD70000 |
IBM Greek, MS-DOS Russian, MS-DOS Nordic, MS-DOS Canadian French, MS-DOS Icelandic, MS-DOS Portuguese, IBM Turkish, IBM Cyrillic; primarily Russian, Latin 2, MS-DOS Baltic, Greek; former 437 G, WE/Latin 1 |
SHORT |
sxHeight |
0x044A |
1098; エックスハイト |
SHORT |
sCapHeight |
0x0000 |
0; キャップハイトあるいは大文字Hの高さ |
USHORT |
usDefaultChar |
0x0000 |
0 |
USHORT |
usBreakChar |
0x0020 |
' ' スペース |
USHORT |
usMaxContext |
0x0002 |
2 = ペアカーニングや2文字までを対象したフィーチャしかない |
Windows用のソフトでは、sTypoAscender, sTypoDescender, sTypoLineGapを利用して標準のline-spaceingを計算するのが推奨されてるらしいです。
'hhea' - Horizontal Header Table
offsetが0x1908なのでそこから読んでいきます。
hhea - Horizontal header table specification - Typography | Microsoft Docs
Type |
Name of Entry |
Value |
comment |
USHORT |
majorVersion |
0x0001 |
1固定 |
USHORT |
minorVersion |
0x0000 |
0固定 |
FWORD |
Ascender |
0x088D |
2189; Typographic ascent |
FWORD |
Descender |
0xFDA8 |
-600; Typographic descent |
FWORD |
LineGap |
0x0000 |
0; Typographic line gap |
UFWORD |
advanceWidthMax |
0x077B |
1915; Maximum advance width value in 'hmtx' |
FWORD |
minLeftSideBearing |
0xFF8F |
-113; Minimum left sidebearing value in 'hmtx' |
FWORD |
minRightSideBearing |
0xFFA8 |
-88 |
FWORD |
xMaxExtent |
0x06D5 |
1749; Max(lsb + (xMax - xMin)) |
SHORT |
caretSlopeRise |
0x0001 |
1 for vertical |
SHORT |
caretSlopeRun |
0x0000 |
0 for vertical |
SHORT |
caretOffset |
0x0000 |
0 for non-slanted fonts |
SHORT |
(reserved) |
0x0000 |
0固定 |
SHORT |
(reserved) |
0x0000 |
0固定 |
SHORT |
(reserved) |
0x0000 |
0固定 |
SHORT |
(reserved) |
0x0000 |
0固定 |
SHORT |
metricDataFormat |
0x0000 |
0固定 |
USHORT |
numberOfHMetrics |
0x002D |
45; 'hmtx'におけるhMetricエントリの数 |
ascender, descender, linegapはMac用。とはいえ'OS/2'にあるものと一致してますね。
'maxp' - Maximum Profile
offsetが0x1A3Cなのでそこから読みます。
maxp - Maximum Profile table specification - Typography | Microsoft Docs
最初が0x00010000なのでversion 1.0であることがわかります。それに従って読んでいくと……
Type |
Name |
value |
comment |
Fixed |
Table version number |
0x00010000 |
version 1.0 |
USHORT |
numGlyphs |
0x002D |
45; グリフの数 |
USHORT |
maxPoints |
0x0029 |
41; Maximum points in a non-composite glyph. |
USHORT |
maxContours |
0x0002 |
2; Maximum contours in a non-composite glyph. |
USHORT |
maxCompositePoints |
0x0000 |
0; Maximum points in a composite glyph. |
USHORT |
maxCompositeContours |
0x0000 |
0; Maximum contours in a composite glyph. |
USHORT |
maxZones |
0x0002 |
instructions use the twilight zone (Z0); 大体は2 |
USHORT |
maxTwilightPoints |
0x0001 |
1; Maximum points used in Z0. |
USHORT |
maxStorage |
0x0002 |
2; Number of Storage Area locations. |
USHORT |
maxFunctionDefs |
0x0016 |
22; Number of FDEFs |
USHORT |
maxInstructionDefs |
0x0000 |
0; Number of IDEFs. |
USHORT |
maxStackElements |
0x0100 |
256; Maximum stack depth. |
USHORT |
maxSizeOfInstructions |
0x00D0 |
208; Maximum byte count for glyph instructions. |
USHORT |
maxComponentElements |
0x0000 |
0; Maximum number of components referenced at “top level” for any composite glyph. |
USHORT |
maxComponentDepth |
0x0000 |
0; Maximum levels of recursion |
'cmap' - Character To Glyph Index Mapping Table
offsetが0x0328からなのでそこから読みます。
cmap - Character To Glyph Index Mapping Table - Typography | Microsoft Docs
文字コードを実際のグリフの内部コード(Glyph IDもしくはGID)にマッピングするテーブルです。
cmap Header
Type |
Name |
value |
comment |
USHORT |
version |
0x0000 |
0 |
USHORT |
numTables |
0x0003 |
3; エンコーディングテーブルの数 |
Encoding Record
つぎに来るのがencoding recordの列で、cmapヘッダから3つあることがわかります。レコードの形式は次の様になっています。
Type |
Name |
備考 |
USHORT |
platformID |
|
USHORT |
encodingID |
|
ULONG |
offset |
サブテーブルへのオフセット |
platformIDやencodingIDは'name'テーブルのものと同じらしい。さらにサブテーブルがあるようですね。
これに従って読んでいくと……
よく見てみると、UnicodeとWindowsのレコードのoffsetが同一です。サブテーブルは実際には2つだけ含まれているということでしょう。
0x0328 + 0x001C = 0x0344 から読みます。というか前と連続してますね。
先頭が0x0004なのでFormat 4サブテーブルのようです。
Type |
Name |
value |
備考 |
USHORT |
format |
0x0004 |
Format number 4 |
USHORT |
length |
0x0060 |
96; このsubtableの長さ |
USHORT |
language |
0x0000 |
Mac用以外では0 |
USHORT |
segCountX2 |
0x0014 |
20; 2×segCount= 20 よって segCount=10 |
USHORT |
searchRange |
0x0010 |
16; segCount以上で最小の2の冪乗数 |
USHORT |
entrySelector |
0x0003 |
3; log2(searchRange/2) |
USHORT |
rangeShift |
0x0004 |
4; 2×segCount - searchRange = 20 - 16 = 4 |
USHORT |
endCount[segCount] |
0x0000 |
End characterCode for each segment |
|
|
0x000D |
|
|
|
0x0020 |
|
|
|
0x007A |
|
|
|
0x00A0 |
|
|
|
0x200A |
|
|
|
0x202F |
|
|
|
0x205F |
|
|
|
0x25FC |
|
|
|
0xFFFF |
last=0xFFFF |
USHORT |
reservedPad |
0x0000 |
0固定 |
USHORT |
startCount[segCount] |
0x0000 |
Start character code for each segment |
|
|
0x000D |
|
|
|
0x0020 |
|
|
|
0x0061 |
|
|
|
0x00A0 |
|
|
|
0x2000 |
|
|
|
0x202F |
|
|
|
0x205F |
|
|
|
0x25FC |
|
|
|
0xFFFF |
|
SHORT |
idDelta[segCount] |
0x0001 |
Delta for all character codes in segment |
|
|
0xFFF5 |
|
|
|
0xFFE3 |
|
|
|
0xFFA3 |
|
|
|
0xFF7E |
|
|
|
0xE01F |
|
|
|
0xDFFB |
|
|
|
0xDFCC |
|
|
|
0xDA30 |
|
|
|
0x0001 |
|
USHORT |
idRangeOffset[segCount] |
0x0000 |
0; Offsets into glyphIdArray or 0 |
|
|
0x0000 |
|
|
|
0x0000 |
|
|
|
0x0000 |
|
|
|
0x0000 |
|
|
|
0x0000 |
|
|
|
0x0000 |
|
|
|
0x0000 |
|
|
|
0x0000 |
|
|
|
0x0000 |
|
USHORT |
glyphIdArray[ ] |
なし |
|
segCountが分からないとendCount以降が読み込めないので、先にsegCountを読んで配列の個数を確定させてから読んでいく必要があります。
さて、このformat 4は文字コードとglyph indexが連続して対応してる場合にコンパクトに記録できる方式のようです。
まとめます。
index |
endCount |
startCount |
idDelta |
idRangeOffset |
0 |
0x0000 |
0x0000 |
0x0001 (1) |
0x0000 |
1 |
0x000D |
0x000D |
0xFFF5 (-11) |
0x0000 |
2 |
0x0020 |
0x0020 |
0xFFE3 (-29) |
0x0000 |
3 |
0x007A |
0x0061 |
0xFFA3 (-93) |
0x0000 |
4 |
0x00A0 |
0x00A0 |
0xFF7E (-130) |
0x0000 |
5 |
0x200A |
0x2000 |
0xE01F (-8161) |
0x0000 |
6 |
0x202F |
0x202F |
0xDFFB (-8197) |
0x0000 |
7 |
0x205F |
0x205F |
0xDFCC (-8244) |
0x0000 |
8 |
0x25FC |
0x25FC |
0xDA30 (-9680) |
0x0000 |
9 |
0xFFFF |
0xFFFF |
0x0001 (1) |
0x0000 |
さて、Unicodeスカラ値とグリフの対応を見てみましょう。今回欲しいのは“cat”(0x0063, 0x0061, 0x0074)なので、それに対応するものを探しましょう。
これらはendCount順にソートされています。endCountを順に見ていくと、4番目(index = 3)の0x007Aで初めてc (0x0063), a (0x0061), t (0x0074)の値以上になります。
そのためそれに対応するstartCountを見ると0x0061となっていて、c, a, tはいずれもそれ以上であり、したがってマッピングが定義されていることが分かります。
そこで、対応するidDeltaとidRangeOffset (ここでは0なので気にしない)からGlyph IDを計算します。ここでは、文字コードにidDelta = -93を加えると(=93を引くと)求めるGlyph IDになります。
文字 |
文字コード |
Glyph ID |
c |
0x0063 |
6 |
a |
0x0061 |
4 |
t |
0x0074 |
23 |
0x0328 + 0x007C = 0x03A4 から。
0x0000で始まっているのでFormat 0だとわかります。それに従って読んでいくと……
Type |
Name |
value |
備考 |
USHORT |
format |
0x0000 |
Format 0 |
USHORT |
length |
0x0106 |
262; このsubtableの長さ |
USHORT |
language |
0x0000 |
0; the subtable is not language-specific |
BYTE |
glyphIdArray[256] |
以下参照 |
|
参照元のcmapのEncoding Recordからわかるように文字コードがMac Romanなので、lengthには言語非依存の0がセットされています。
Format 0は0~255の文字コードに一対一で対応を記述していくというシンプルなもので、対応がない文字コードの部分は0で埋められます。文字コードが255を超えるものについては記述することができません。今回はアルファベット小文字だけなのでこの範囲に収まっていますね。
glyphIdArrayは次の様になります。尚、glyph indexの値が0なものは省いています。
char code |
glyph index |
備考 |
0 |
0x01 |
NUL |
8 |
0x01 |
BS |
9 |
0x02 |
HT |
13 |
0x02 |
CR |
29 |
0x01 |
GS |
32 |
0x03 |
SP ' ' |
97 |
0x04 |
'a' |
98 |
0x05 |
'b' |
99 |
0x06 |
'c' |
100 |
0x07 |
'd' |
101 |
0x08 |
'e' |
102 |
0x09 |
'f' |
103 |
0x0A |
'g' |
104 |
0x0B |
'h' |
105 |
0x0C |
'i' |
106 |
0x0D |
'j' |
107 |
0x0E |
'k' |
108 |
0x0F |
'l' |
109 |
0x10 |
'm' |
110 |
0x11 |
'n' |
111 |
0x12 |
'o' |
112 |
0x13 |
'p' |
113 |
0x14 |
'q' |
114 |
0x15 |
'r' |
115 |
0x16 |
's' |
116 |
0x17 |
't' |
117 |
0x18 |
'u' |
118 |
0x19 |
'v' |
119 |
0x1A |
'w' |
120 |
0x1B |
'x' |
121 |
0x1C |
'y' |
122 |
0x1D |
'z' |
202 |
0x1E |
NBSP |
このサブテーブル、読んだはいいですけど特に使わなさそうですね……当然ですが上で求めたWindows用のものと一致しています。
'hmtx' - Horizontal Metrics
offsetが0x192Cなのでそこから読みます。
hmtx - Horizontal metrix table specification - Typography | Microsoft Docs
グリフ幅と左サイドベアリングが記録されているようです。
Field |
Type |
hMetrics |
longHorMetric[numberOfHMetrics] |
leftSideBearing |
SHORT[ ] |
'hhea'のnumberOfHMetricsが45であることから、45個hMetricsデータが連なってるはずです。
という訳で順番に読んでいきます。なおlongHorMetricは
- USHORT advanceWidth;
- SHORT lsb;
からなる構造体です。(lsbは左サイドベアリング)
hMetrics
index |
advanceWidth |
lsb |
0 |
0x02EC |
0x0044 |
1 |
0x0000 |
0x0000 |
2 |
0x0414 |
0x0000 |
3 |
0x0214 |
0x0000 |
4 |
0x047D |
0x005E |
5 |
0x04EC |
0x00AE |
6 |
0x03D7 |
0x0071 |
7 |
0x04EC |
0x0071 |
8 |
0x0483 |
0x0071 |
9 |
0x02C1 |
0x001F |
10 |
0x04EC |
0x0071 |
11 |
0x04F2 |
0x00AE |
12 |
0x0210 |
0x00A0 |
13 |
0x0210 |
0xFF8F |
14 |
0x0446 |
0x00AE |
15 |
0x0210 |
0x00AE |
16 |
0x077B |
0x00AE |
17 |
0x04F2 |
0x00AE |
18 |
0x04D7 |
0x0071 |
19 |
0x04EC |
0x00AE |
20 |
0x04EC |
0x0071 |
21 |
0x034E |
0x00AE |
22 |
0x03D5 |
0x0068 |
23 |
0x02E3 |
0x0021 |
24 |
0x04F2 |
0x00A2 |
25 |
0x0410 |
0x0000 |
26 |
0x064A |
0x0017 |
27 |
0x043B |
0x0025 |
28 |
0x0414 |
0x0002 |
29 |
0x03C3 |
0x0050 |
30 |
0x0214 |
0x0000 |
31 |
0x030F |
0x0000 |
32 |
0x061F |
0x0000 |
33 |
0x030F |
0x0000 |
34 |
0x061F |
0x0000 |
35 |
0x020A |
0x0000 |
36 |
0x0187 |
0x0000 |
37 |
0x0105 |
0x0000 |
38 |
0x0105 |
0x0000 |
39 |
0x00C3 |
0x0000 |
40 |
0x0139 |
0x0000 |
41 |
0x0057 |
0x0000 |
42 |
0x0139 |
0x0000 |
43 |
0x0187 |
0x0000 |
44 |
0x044C |
0x0000 |
あれ、これでこのテーブル終わりのようです。leftSideBearingフィールドの入る余地がないですね。
leftSideBearingの個数は'maxp'のnumGlyphsを使って numGlyphs - numberOfHMetrics で計算されるらしいので、 45 - 45 = 0 なのであってます。主に等幅のグリフのためのものだとか。
"cat"の3文字についてまとめます。
文字 |
Glyph ID |
advanceWidth |
lsb |
c |
6 |
0x03D7 (983) |
0x0071 (113) |
a |
4 |
0x047D (1149) |
0x005E (94) |
t |
23 |
0x02E3 (739) |
0x0021 (33) |
'post' - PostScript Table
offsetは0x2100なのでそこから読みます。
post — PostScript Table specification - Typography | Microsoft Docs
PostScriptプリンタのために必要な情報を記録するところとのことで、グリフ名とかが格納されているようです。
ヘッダ
Type |
Name |
value |
備考 |
Fixed |
Version |
0x00020000 |
version 2.0 |
Fixed |
italicAngle |
0x00000000 |
0.0; 斜体の角度[°](12時の方向から反時計回り) |
FWord |
underlinePosition |
0xFF66 |
-154; 下線の上端がベースラインのどれだけ上にくるか |
FWord |
underlineThickness |
0x0066 |
102; 下線の太さ |
ULONG |
isFixedPitch |
0x00000000 |
the font is proportionally spaced |
ULONG |
minMemType42 |
0x00000000 |
Minimum memory usage when an OpenType font is downloaded. |
ULONG |
maxMemType42 |
0x00000000 |
Maximum memory usage when an OpenType font is downloaded. |
ULONG |
minMemType1 |
0x00000000 |
Minimum memory usage when an OpenType font is downloaded as a Type 1 font. |
ULONG |
maxMemType1 |
0x00000000 |
Maximum memory usage when an OpenType font is downloaded as a Type 1 font. |
postテーブルのVersion 2.0はPostScriptグリフ名を含みます。これは、'post' Format 1の258個の標準的なMacintosh glyph namesに含まれないものだけを文字列としてフォント内に持つというもののようです。
Type |
Name |
value |
備考 |
USHORT |
numberOfGlyphs |
0x002D |
45; グリフの個数('maxp'のnumGlyphsと同じ) |
USHORT |
glyphNameIndex[numGlyphs] |
以下参照 |
オフセットでなく何番目かという順序 |
CHAR |
names[numberNewGlyphs] |
以下参照 |
Glyph names (a Pascal string) |
glyphNameIndexは、0~257ならばMacintosh Standard Orderのglyph indexとしてそれを参照し、258~65535であれば258を引いた数をindexとしてnamesからその順番にあたる文字列を読みだします。namesはPascal文字列の羅列になっています。Pascal文字列というのは第1バイトが“文字列が何バイトか”を示し、直後に文字列が示された分だけ続くというもので、null終端はされません。
index |
glyphNameIndex |
name (括弧内はstandard Macintosh setから) |
0 |
0x0000 |
(.notdef) |
1 |
0x0102 |
glyph1 |
2 |
0x0103 |
uni000D |
3 |
0x0003 |
(space) |
4 |
0x0044 |
(a) |
5 |
0x0045 |
(b) |
6 |
0x0046 |
(c) |
7 |
0x0047 |
(d) |
8 |
0x0048 |
(e) |
9 |
0x0049 |
(f) |
10 |
0x004A |
(g) |
11 |
0x004B |
(h) |
12 |
0x004C |
(i) |
13 |
0x004D |
(j) |
14 |
0x004E |
(k) |
15 |
0x004F |
(l) |
16 |
0x0050 |
(m) |
17 |
0x0051 |
(n) |
18 |
0x0052 |
(o) |
19 |
0x0053 |
(p) |
20 |
0x0054 |
(q) |
21 |
0x0055 |
(r) |
22 |
0x0056 |
(s) |
23 |
0x0057 |
(t) |
24 |
0x0058 |
(u) |
25 |
0x0059 |
(v) |
26 |
0x005A |
(w) |
27 |
0x005B |
(x) |
28 |
0x005C |
(y) |
29 |
0x005D |
(z) |
30 |
0x0104 |
uni00A0 |
31 |
0x0105 |
uni2000 |
32 |
0x0106 |
uni2001 |
33 |
0x0107 |
uni2002 |
34 |
0x0108 |
uni2003 |
35 |
0x0109 |
uni2004 |
36 |
0x010A |
uni2005 |
37 |
0x010B |
uni2006 |
38 |
0x010C |
uni2007 |
39 |
0x010D |
uni2008 |
40 |
0x010E |
uni2009 |
41 |
0x010F |
uni200A |
42 |
0x0110 |
uni202F |
43 |
0x0111 |
uni205F |
44 |
0x0112 |
uni25CF |
PostScript名が得られたはいいんですけど、何に使うんでしょうか…。
さて、これで必須テーブルは読みだせました。
次はOpenType Layout用のテーブルを読んでみましょうか、短いし……。
'GDEF'
offsetは0x0158。
GDEF — Glyph Definition Table - Typography | Microsoft Docs
グリフの属するクラスを定義したり、リガチャのグリフのキャレット位置を指定したりするテーブルの様です。
最初2バイトが0x0001、次が0x0000なのでGDEF HeaderのVersion 1.0であることがわかります。
GDEF Header (version 1.0)
Type |
Name |
value |
備考 |
USHORT |
MajorVersion |
0x0001 |
1 |
USHORT |
MinorVersion |
0x0000 |
0 |
Offset |
GlyphClassDef |
0x000E |
14; Offset to class definition table for glyph type |
Offset |
AttachList |
0x0000 |
NULL; Offset to list of glyphs with attachment points |
Offset |
LigCaretList |
0x0018 |
24; Offset to list of positioning points for ligature carets |
Offset |
MarkAttachClassDef |
0x0020 |
32; Offset to class definition table for mark attachment type |
offsetはGDEFヘッダ開始位置からのバイト数。NULLはテーブルがないということのようです。
次にここから参照されているテーブルを読んでいきます。
OpenType layout common table formats - Typography | Microsoft Docs
テーブル構造はこれを参考にします。
Glyph Class Definition Table
フォントのグリフがBase glyph, Ligature glyph, Mark glyph, Componentのどのクラスに属するかを指定するテーブルの様です。
Class Definition Table
0x0002から始まるのでFormat 2だとわかります。
Type |
Name |
value |
備考 |
USHORT |
ClassFormat |
0x0002 |
Format 2 |
USHORT |
ClassRangeCount |
0x0001 |
ClassRangeRecordの数 |
struct |
ClassRangeRecord[ClassRangeCount] |
下参照 |
ClassRangeRecordの列 |
ClassRangeRecord
Type |
Name |
value |
備考 |
GlyphID |
Start |
0x0001 |
この範囲の最初のGlyphID |
GlyphID |
End |
0x002C |
この範囲の最後のGlyphID |
USHORT |
Class |
0x0001 |
Base glyph (single character, spacing glyph) |
GID 0は.notdefなので、それ以外は全部Base glyphってことですね。
Ligature Caret List Table
フォントに含まれる合字についてのキャレットの位置を指定するテーブルの様です。
LigCaretList table
Type |
Name |
value |
備考 |
Offset |
Coverage |
0x0004 |
4; Coverage tableへのオフセット |
USHORT |
LigGlyphCount |
0x0000 |
0; 合字グリフの数 |
Offset |
LigGlyph[LigGlyphCount] |
なし |
Coverage Table
Type |
Name |
value |
備考 |
USHORT |
CoverageFormat |
0x0002 |
Format 2 |
USHORT |
RangeCount |
0x0000 |
0; glyph rangeの数 |
struct |
RangeRecord[RangeCount] |
なし |
何も入ってないですね。まあ当然か…。
Mark Attachment Class Definition Table
mark glyphの属するクラスを定義するテーブルだそうです。
Class Definition Table
0x0001から始まるのでFormat 1です。
Type |
Name |
value |
備考 |
USHORT |
ClassFormat |
0x0001 |
Format 1 |
GlyphID |
StartGlyph |
0x0000 |
ClassValueArrayの最初のGlyphID |
USHORT |
GlyphCount |
0x0001 |
ClassValueArrayの大きさ |
USHORT |
ClassValueArray[GlyphCount] |
0x0000 |
0 |
GID 0 = .notdefだけ指定されてますね。class 0って何でしょうか。
'GPOS'
offsetは0x0180
GPOS — Glyph Positioning Table - Typography | Microsoft Docs
カーニングの調整、合成記号がどこにつくかの指定など、条件つきのグリフの位置調整を指定するテーブルです。
GPOS Header
0x00010000から始まっているのでversion 1.0だとわかります。それに従って読むと……
Value |
Type |
value |
Description |
USHORT |
MajorVersion |
0x0001 |
1 |
USHORT |
MinorVersion |
0x0000 |
0 |
Offset |
ScriptList |
0x000A |
GPOSテーブル先頭からScriptListテーブルへのオフセット |
Offset |
FeatureList |
0x0042 |
GPOSテーブル先頭からFeatureListテーブルへのオフセット |
Offset |
LookupList |
0x0050 |
GPOSテーブル先頭からLookupListテーブルへのオフセット |
ScriptList table
Type |
Name |
value |
Description |
USHORT |
ScriptCount |
0x0003 |
3; ScriptRecordsの個数 |
struct |
ScriptRecord[ScriptCount] |
以下 |
ScriptRecordの配列 |
ScriptRecordは4バイトのTag scriptTagとoffset Scriptからなる構造体なので、
ScriptTag |
Script |
0x6379726C (cyrl) |
0x0014 |
0x6772656B (grek) |
0x0020 |
0x6C61746E (latn) |
0x002C |
です。ScriptはScriptList先頭からScriptテーブルへのオフセットになっています。
とりあえずcyrlに対応するScriptテーブルを見てみましょう。
Type |
Name |
value |
備考 |
Offset |
DefaultLangSys |
0x0004 |
DefaultLangSysテーブルへのオフセット |
USHORT |
LangSysCount |
0x0000 |
LangSysRecordの個数(DefaultLangSysは除く) |
struct |
LangSysRecord[LangSysCount] |
なし |
DefaultLangSysはLangSysテーブルへのオフセットになっています。
なので、このDefaultLangSysは0x01BAからLangSysテーブルの構造で読んでいきます。
Type |
Name |
value |
備考 |
Offset |
LookupOrder |
0x0000 |
0 (NULL)固定 |
USHORT |
ReqFeatureIndex |
0xFFFF |
= no required features |
USHORT |
FeatureCount |
0x0001 |
1; FeatureIndexの数 |
USHORT |
FeatureIndex[FeatureCount] |
0x0000 |
0; FeatureListのインデックスの配列 |
となっています。
同様にScriptテーブルを読んでいくと次のようになります。
tag |
cyrl |
grek |
latn |
Scriptテーブル開始オフセット |
0x019E |
0x01AA |
0x01B6 |
DefaultLangSys |
0x0004 |
0x0004 |
0x0004 |
LangSysCount |
0x0000 |
0x0000 |
0x0000 |
LangSysRecord |
なし |
なし |
なし |
DefalultLangSysの中身 |
|
|
LookupOrder |
0x0000 |
0x0000 |
0x0000 |
ReqFeatureIndex |
0xFFFF |
0xFFFF |
0xFFFF |
FeatureCount |
0x0001 (1) |
0x0001 (1) |
0x0001 (1) |
FeatureIndex[] |
0x0000 (0, ) |
0x0000 (0, ) |
0x0000 (0, ) |
どの書字系でも同じみたいですね。デフォルト言語システムしかなく、必須フィーチャはなし、使われるフィーチャは0番のものの1つだけの様です。
FeatureList table
0x01C2から。
Type |
Name |
value |
備考 |
USHORT |
FeatureCount |
0x0001 |
1; FeatureRecordの個数 |
struct |
FeatureRecord[FeatureCount] |
以下 |
FeatureRecordの配列 |
FeatureRecordは
Type |
Name |
value |
備考 |
Tag |
FeatureTag |
0x6B65726E |
kern; フィーチャを特定する4バイトタグ |
Offset |
Feature |
0x0008 |
FeatureList先頭からFeatureテーブルへのオフセット |
Featureテーブルは
Type |
Name |
value |
備考 |
Offset |
FeatureParams |
0x0000 |
0 (NULL)固定 |
USHORT |
LookupCount |
0x0001 |
1; LookupListIndexの個数 |
USHORT |
LookupListIndex[LookupCount] |
0x0000 |
0,; LookupListのインデックスの配列 |
LookupList table
0x01D0から。
Type |
Name |
value |
備考 |
USHORT |
LookupCount |
0x0001 |
Number of lookups in this table |
Offset |
Lookup[LookupCount] |
0x0004 |
Lookupテーブルへのオフセットの配列(LookupList先頭から) |
Lookup table (0x01D4~)
Type |
Name |
value |
備考 |
USHORT |
LookupType |
0x0002 |
Pair adjustment (GPOS) |
USHORT |
LookupFlag |
0x0008 |
IgnoreMarks |
USHORT |
SubTableCount |
0x0001 |
SubTableの個数 |
Offset |
SubTable[SubTableCount] |
0x0008 |
SubTableへのオフセットの配列(Lookupテーブル先頭から) |
unit16 |
MarkFilteringSet |
なし |
UseMarkFilteringSetフラグがオフなので、なし |
SubTableで参照されるのはLookup Subtableです。0x01DC~は0x0001で始まっているのでLookup Type 2: Pair Adjustment Positioning Subtable (format 1)であることがわかります。
PairPosFormat1 subtable: Adjustments for glyph pairs
Type |
Name |
value |
備考 |
USHORT |
PosFormat |
0x0001 |
Format 1 |
Offset |
Coverage |
0x0062 |
Coverageテーブルへのオフセット(PairPos subtableの先頭から) |
USHORT |
ValueFormat1 |
0x0004 |
XAdvance; Defines the types of data in ValueRecord1 |
USHORT |
ValueFormat2 |
0x0000 |
なし; Defines the types of data in ValueRecord2 |
USHORT |
PairSetCount |
0x0007 |
7; PairSetテーブルの個数 |
Offset |
PairSetOffset[PairSetCount] |
0x0018 |
PairSetテーブルへのオフセットの配列(PairPos subtableの先頭から, coverage index順) |
|
|
0x0018 |
|
|
|
0x002E |
|
|
|
0x0018 |
|
|
|
0x0018 |
|
|
|
0x0044 |
|
|
|
0x002E |
|
0x23E~のCoverage tableは0x0001で始まっていることからformat 1だとわかります。
CoverageFormat1 table: Individual glyph indices
Type |
Name |
value |
備考 |
USHORT |
CoverageFormat |
0x0001 |
Format 1 |
USHORT |
GlyphCount |
0x0007 |
GlyphArrayのグリフ数 |
GlyphID |
GlyphArray[GlyphCount] |
0x0005 |
GlyphIDの配列 |
|
|
0x0008 |
|
|
|
0x000E |
|
|
|
0x0012 |
|
|
|
0x0013 |
|
|
|
0x0015 |
|
|
|
0x001B |
|
coverage tableに6, 4, 23が含まれていないので、“cat”には関係ないことになります。
PairSetテーブルは
PairSet table
Type |
Name |
備考 |
USHORT |
PairValueCount |
PairValueRecordの数 |
struct |
PairValueRecord[PairValueCount] |
PairValueRecordの列(2番目のグリフのGlyphIDの順) |
PairValueRecordは
Type |
Name |
備考 |
GlyphID |
SecondGlyph |
ペアの2番目のグリフのGlyphID |
ValueRecord |
Value1 |
ペアの1番目のグリフに対するPositioning data |
ValueRecord |
Value2 |
ペアの2番目のグリフに対するPositioning data |
ですが、ここではValueFormat2 = 0なのでValue2は空になるようです。
なので、まとめて見ていくと、
index |
coverage index |
PairSetOffset |
PairSet |
0 |
0x0005 |
0x0018 |
(A) |
1 |
0x0008 |
0x0018 |
(A) |
2 |
0x000E |
0x002E |
(B) |
3 |
0x0012 |
0x0018 |
(A) |
4 |
0x0013 |
0x0018 |
(A) |
5 |
0x0015 |
0x0044 |
(C) |
6 |
0x001B |
0x002E |
(A) |
PairSet (A)
PairValueCount |
PairValueRecord |
|
0x0005 |
SecondGlyph |
Value1 |
|
0x0019 |
0xFFEC (-20) |
|
0x001A |
0xFFEC (-20) |
|
0x001B |
0xFFEC (-20) |
|
0x001C |
0xFFEC (-20) |
|
0x001D |
0xFFF6 (-10) |
PairSet (B)
PairValueCount |
PairValueRecord |
|
0x0005 |
SecondGlyph |
Value1 |
|
0x0006 |
0xFFEC (-20) |
|
0x0007 |
0xFFEC (-20) |
|
0x0008 |
0xFFEC (-20) |
|
0x0012 |
0xFFEC (-20) |
|
0x0014 |
0xFFEC (-20) |
PairSet (C)
PairValueCount |
PairValueRecord |
|
0x0007 |
SecondGlyph |
Value1 |
|
0x0004 |
0xFFEC (-20) |
|
0x0006 |
0xFFEC (-20) |
|
0x0007 |
0xFFEC (-20) |
|
0x0008 |
0xFFEC (-20) |
|
0x000A |
0xFFF6 (-10) |
|
0x0012 |
0xFFEC (-20) |
|
0x0014 |
0xFFEC (-20) |
……となるようです。
GPOSはおしまいですが、結局“cat”には関係ありませんでした。
'GSUB'
offsetは0x0250。
GSUB — Glyph Substitution Table - Typography | Microsoft Docs
合字や、アラビア文字などにおける独立・語頭・語中・語末形の切り替えなど、特定の条件でグリフを置き換えることを指定するテーブルです。
GSUBを見ていくと、初めの部分0x40位がGPOSと全く同じバイナリをしているんですね。
GSUB Header
0x00010000から始まっているのでversion 1.0だとわかります。それに従って読むと……
Value |
Type |
value |
Description |
USHORT |
MajorVersion |
0x0001 |
1 |
USHORT |
MinorVersion |
0x0000 |
0 |
Offset |
ScriptList |
0x000A |
GSUBテーブル先頭からScriptListテーブルへのオフセット |
Offset |
FeatureList |
0x0042 |
GSUBテーブル先頭からFeatureListテーブルへのオフセット |
Offset |
LookupList |
0x0050 |
GSUBテーブル先頭からLookupListテーブルへのオフセット |
ScriptList table
Type |
Name |
value |
Description |
USHORT |
ScriptCount |
0x0003 |
3; ScriptRecordsの個数 |
struct |
ScriptRecord[ScriptCount] |
以下 |
ScriptRecordの配列 |
ScriptRecordは4バイトのTag scriptTagとoffset Scriptからなる構造体なので、
ScriptTag |
Script |
0x6379726C (cyrl) |
0x0014 |
0x6772656B (grek) |
0x0020 |
0x6C61746E (latn) |
0x002C |
です。ScriptはScriptList先頭からScriptテーブルへのオフセットになっています。
Scriptテーブルを読んでいくと次のようになります。
tag |
cyrl |
grek |
latn |
Scriptテーブル開始アドレス |
0x026E |
0x027A |
0x0286 |
DefaultLangSys |
0x0004 |
0x0004 |
0x0004 |
LangSysCount |
0x0000 |
0x0000 |
0x0000 |
LangSysRecord |
なし |
なし |
なし |
DefalultLangSysの中身 |
|
|
LookupOrder |
0x0000 |
0x0000 |
0x0000 |
ReqFeatureIndex |
0xFFFF |
0xFFFF |
0xFFFF |
FeatureCount |
0x0001 (1) |
0x0001 (1) |
0x0001 (1) |
FeatureIndex[] |
0x0000 (0, ) |
0x0000 (0, ) |
0x0000 (0, ) |
どの書字系でも同じみたいですね。デフォルト言語システムしかなく、必須フィーチャはなし、使われるフィーチャは0番のものの1つだけの様です。ここまではGPOSとバイナリ単位で一緒です。
FeatureList table
0x0292から。
Type |
Name |
value |
備考 |
USHORT |
FeatureCount |
0x0001 |
1; FeatureRecordの個数 |
struct |
FeatureRecord[FeatureCount] |
以下 |
FeatureRecordの配列 |
FeatureRecordは
Type |
Name |
value |
備考 |
Tag |
FeatureTag |
0x63636D70 |
ccmp; フィーチャを特定する4バイトタグ |
Offset |
Feature |
0x0008 |
FeatureList先頭からFeatureテーブルへのオフセット |
Featureテーブルは
Type |
Name |
value |
備考 |
Offset |
FeatureParams |
0x0000 |
0 (NULL)固定 |
USHORT |
LookupCount |
0x0001 |
1; LookupListIndexの個数 |
USHORT |
LookupListIndex[LookupCount] |
0x0000 |
0,; LookupListのインデックスの配列 |
LookupList table
0x02A0から。
Type |
Name |
value |
備考 |
USHORT |
LookupCount |
0x0001 |
Number of lookups in this table |
Offset |
Lookup[LookupCount] |
0x0004 |
Lookupテーブルへのオフセットの配列(LookupList先頭から) |
Lookup table (0x02A4~)
Type |
Name |
value |
備考 |
USHORT |
LookupType |
0x0006 |
Chaining Context (format 6.1 6.2 6.3) |
USHORT |
LookupFlag |
0x0000 |
IgnoreMarks |
USHORT |
SubTableCount |
0x0001 |
SubTableの個数 |
Offset |
SubTable[SubTableCount] |
0x0008 |
SubTableへのオフセットの配列(Lookupテーブル先頭から) |
unit16 |
MarkFilteringSet |
なし |
UseMarkFilteringSetフラグがオフなので、なし |
0x02AC~
0x0003で始まっているのでChaining Context Substitution Format 3: Coverage-based Chaining Context Glyph Substitutionだとわかります。
ChainContextSubstFormat3 subtable: Coverage-based chaining context glyph substitution
Type |
Name |
value |
備考 |
USHORT |
SubstFormat |
0x0003 |
Format 3 |
USHORT |
BacktrackGlyphCount |
0x0000 |
Number of glyphs in the backtracking sequence |
Offset |
Coverage[BacktrackGlyphCount] |
なし |
Array of offsets to coverage tables in backtracking sequence, in glyph sequence order |
USHORT |
InputGlyphCount |
0x0001 |
Number of glyphs in input sequence |
Offset |
Coverage[InputGlyphCount] |
0x000E |
Array of offsets to coverage tables in input sequence, in glyph sequence order |
USHORT |
LookaheadGlyphCount |
0x0001 |
Number of glyphs in lookahead sequence |
Offset |
Coverage[LookaheadGlyphCount] |
0x0016 |
Array of offsets to coverage tables in lookahead sequence, in glyph sequence order |
USHORT |
SubstCount |
0x0000 |
Number of SubstLookupRecords |
struct |
SubstLookupRecord[SubstCount] |
なし |
Array of SubstLookupRecords, in design order |
0x02BA
CoverageFormat1 table: Individual glyph indices
Type |
Name |
value |
Description |
USHORT |
CoverageFormat |
0x0001 |
Format identifier-format = 1 |
USHORT |
GlyphCount |
0x0002 |
Number of glyphs in the GlyphArray |
GlyphID |
GlyphArray[GlyphCount] |
0x000C |
Array of GlyphIDs-in numerical order |
|
|
0x000D |
|
0x02C2
CoverageFormat1 table: Individual glyph indices
Type |
Name |
value |
Description |
USHORT |
CoverageFormat |
0x0001 |
Format identifier-format = 1 |
USHORT |
GlyphCount |
0x0000 |
Number of glyphs in the GlyphArray |
GlyphID |
GlyphArray[GlyphCount] |
なし |
Array of GlyphIDs-in numerical order |
ASCIIの小文字アルファベット以外取り除いてるのでGSUBは残ってないのではと思いますが、何でしょうね。
GSUBは“cat”には関係ない様です。
次はTrueTypeのテーブルを見ていきます。
'gasp' — Grid-fitting And Scan-conversion Procedure Table
offsetは0x075C。
gasp — Grid-fitting And Scan-conversion Procedure Table - Typography | Microsoft Docs
PPEMサイズの違いによりラスタライザの描画品質を向上させるためのフラグ設定等が書かれているようです。
Type |
Name |
value |
備考 |
USHORT |
version |
0x0001 |
1固定 |
USHORT |
numRanges |
0x0003 |
3 |
GASPRANGE |
gaspRange[numRanges] |
下参照 |
|
GASPRANGEは
Type |
Name |
Description |
USHORT |
rangeMaxPPEM |
範囲の上限[PPEM] |
USHORT |
rangeGaspBehavior |
TrueTypeラスタライザの動作を指定するフラグ |
だそうで、読むと、
rangeMaxPPEM |
rangeGaspBehavior |
上限 |
flags |
0x0008 |
0x000A |
8 ppem |
GASP_DOGRAY, GASP_SYMMETRIC_SMOOTHING |
0x000E |
0x0007 |
14 ppem |
GASP_GRIDFIT, GASP_DOGRAY, GASP_SYMMETRIC_GRIDFIT |
0xFFFF |
0x000F |
65535 ppem |
GASP_GRIDFIT, GASP_DOGRAY, GASP_SYMMETRIC_GRIDFIT, GASP_SYMMETRIC_SMOOTHING |
らしいです。PPEMサイズの違いにより描画品質を向上させるためのフラグ設定が書かれているようですね。今回は関係なし。
'cvt ' - Control Value Table
offsetは0x04AC。
cvt - Control value table - Typography | Microsoft Docs
TrueType instructionから参照される値がつまってるようです。
FWORDの値が並んでいます。
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
0x |
0x0000 |
0x044A |
0x05B6 |
0x00A2 |
0x00E5 |
0x006A |
0x007D |
0x008C |
0x0091 |
0x0095 |
1x |
0x009A |
0x009D |
0x0054 |
0x00B4 |
0x00D3 |
0x0081 |
0x008F |
0x0094 |
0x00AA |
0x00AE |
2x |
0x00B4 |
0x00B8 |
0x00BE |
0x00C2 |
0x006B |
0x00A6 |
0x00BA |
0x0084 |
0x00B2 |
0x008A |
3x |
0x0077 |
0x007B |
0x00AC |
0x00B0 |
0x0044 |
0x0511 |
|
|
|
|
'fpgm' - Font Program
offsetは0x04F4。
fpgm - Font program table - Typography | Microsoft Docs
BYTE[ n ]
最初に一回だけ実行されるプログラムだそうで、TrueType命令によって関数定義を行っているようです。
また、TrueType命令については、最近のMac OSからは単純に無視される様です。高DPI環境には不要ということでしょうか。今回描画に必要でないので省略します。
16進数の羅列じゃ分からないので逆
アセンブルしましょうかね。
The Instruction Set
このページに従って読んでいきます。
/* initial stack|b|a */
PUSHB[000] 0
FDEF /* 関数0の定義開始 */
PUSHB[000] 0
SZP0 /* zp0 := 0 */
MPPEM
PUSHB[000] 76
LT /* ppem < 76 */
IF /* if ppem < 76 */
PUSHB[000] 74
SROUND /* set round_state. period: 1px, phase: 0, threshold: 6/8 * period = 3/4px */
EIF
PUSHB[000] 0
SWAP /* stack|b|←0|a
MIAP[1] /* Move Indirect Absolute Point. round the distance and look at the control value cut-in;
rp0 := point 0, rp1 := point 0. stack|b| */
RTG /* round_state := grid */
PUSHB[000] 6
CALL /* call function 6, check if ClearType grayscale hinting is enabled */
IF /* if ClearType grayscale hinting is enabled */
RTDG /* round_state := double grid */
EIF
MPPEM
PUSHB[000] 76
LT
IF /* if ppem < 76 */
RDTG /* round_state := down to grid */
EIF
DUP /* stack|←b|b */
MDRP[10100] /* rp0 := point b,
do not keep distance greater than or equal to minimum distance,
round the distance, gray distance */
PUSHB[000] 1 /* stack|b|←1 */
SZP0 /* zp0 := 1 */
MDAP[0] /* Move Direct Absolute Point, rp0 := b, rp1 := b; do not round the value */
RTG /* round_state := grid */
ENDF
PUSHB[000] 1
FDEF /* function 1 */
DUP
MDRP[11010]
PUSHB[000] 12
CALL /* call function 12 */
ENDF
/* initial stack|c|b|a */
/* final stack|cvt[b] (if a>ppem) or c (otherwise)| */
/* a>ppem?cvt[b]:c */
PUSHB[000] 2
FDEF /* function 2 */
MPPEM /* stack|c|b|a|←ppem */
GT
IF /* if a > ppem, stack|c|b */
RCVT /* stack|c|←cvt[b] */
SWAP /* stack|←cvt[b]|c */
EIF
POP /* stack|cvt[b] (if a>ppem), stack|c (otherwise) */
ENDF
/* initial stack|a */
/* final stack|max(1.0,round(a)) */
PUSHB[000] 3
FDEF /* function 3 */
ROUND[01] /* round value, distance type: Black, stack|←round(a) */
RTG /* round_state := grid */
DUP /* stack|←round(a)|round(a) */
PUSHB[000] 64
LT
IF /* if round(a) < 64(1.0), stack|round(a) */
POP /* stack| →round(a) */
PUSHB[000] 64 /* stack|←64(1.0) */
EIF
ENDF
PUSHB[000] 4
FDEF /* fuction 4 */
PUSHB[000] 6
CALL /* call fuction 6 */
IF
POP
SWAP
POP
ROFF
IF
MDRP[11101]
ELSE
MDRP[01101]
EIF
ELSE
MPPEM
GT
IF
IF
MIRP[11101]
ELSE
MIRP[1101]
EIF
ELSE
SWAP
POP
PUSHB[000] 5
CALL /* call fuction 5 */
IF
PUSHB[000] 70
SROUND
EIF
IF
MDRP[11101]
ELSE
MDRP[01101]
EIF
EIF
EIF
RTG
ENDF
PUSHB[000] 5
FDEF /* function 5 */
GFV
NOT
AND
ENDF
/* check if ClearType Grayscale Hinting is enabled */
/* stack|a */
PUSHB[000] 6
FDEF /* function 6 */
PUSHB[001] 34 1 /*stack|a|←34|1
GETINFO /* stack|a|34|←getinfo(get engine version) */
LT
IF /* if 34 < getinfo(get engine version) i.e. ClearType selector can be used*/
PUSHB[000] 32 /* stack|a|←32 */
GETINFO /* stack|a|←getinfo(HINTING FOR GRAYSCALE) */
NOT /* ? */
NOT /* ? */
ELSE
PUSHB[000] 0 /* stack|a|←0 */
EIF
ENDF
PUSHB[000] 7
FDEF /* function 7 */
PUSHB[001] 36 1
GETINFO
LT
IF
PUSHB[000] 64
GETINFO
NOT
NOT
ELSE
PUSHB[000] 0
EIF
ENDF
PUSHB[000] 8
FDEF /* function 8 */
SRP2
SRP1
DUP
IP
MDAP[1]
ENDF
PUSHB[000] 9
FDEF /* function 9 */
DUP
RDTG
PUSHB[000] 6
CALL /* call function 6 */
IF
MDRP[00100]
ELSE
MDRP[01101]
EIF
DUP
PUSHB[000] 3
CINDEX
MD[0]
SWAP
DUP
PUSHB[000] 4
MINDEX
MD[1]
PUSHB[000] 0
LT
IF
ROLL
NEG
ROLL
SUB
DUP
PUSHB[000] 0
LT
IF
SHPIX
ELSE
POP
POP
EIF
ELSE
ROLL
ROLL
SUB
DUP
PUSHB[000] 0
GT
IF
SHPIX
ELSE
POP
POP
EIF
EIF
RTG
ENDF
PUSHB[000] 10
FDEF /* function 10 */
PUSHB[000] 6
CALL /* call function 6 */
IF
POP
SRP0
ELSE
SRP0
POP
EIF
ENDF
PUSHB[000] 11
FDEF /* function 11 */
DUP
MDRP[10010]
PUSHB[000] 12
CALL /* call function 12 */
ENDF
PUSHB[000] 12
FDEF /* function 12 */
DUP
MDAP[1]
PUSHB[000] 7
CALL /* call function 7 */
NOT
IF
DUP
DUP
GC[1]
SWAP
GC[0]
SUB
ROUND[10]
DUP
IF
DUP
ABS
DIV
SHPIX
ELSE
POP
POP
EIF
ELSE
POP
EIF
ENDF
PUSHB[000] 13
FDEF /* function 13 */
SRP2
SRP1
DUP
DUP
IP
MDAP[1]
DUP
ROLL
DUP
GC[1]
ROLL
GC[0]
SUB
SWAP
ROLL
DUP
ROLL
SWAP
MD[1]
PUSHB[000] 0
LT
IF
SWAP
PUSHB[000] 0
GT
IF
PUSHB[000] 64
SHPIX
ELSE
POP
EIF
ELSE
SWAP
PUSHB[000] 0
LT
IF
PUSHB[000] 64
NEG
SHPIX
ELSE
POP
EIF
EIF
ENDF
PUSHB[000] 14
FDEF /* function 14 */
PUSHB[000] 6
CALL /* call function 6 */
IF
RTDG
MDRP[10110]
RTG
POP
POP
ELSE
DUP
MDRP[10110]
ROLL
MPPEM
GT
IF
DUP
ROLL
SWAP
MD[0]
DUP
PUSHB[000] 0
NEQ
IF
SHPIX
ELSE
POP
POP
EIF
ELSE
POP
POP
EIF
EIF
ENDF
PUSHB[000] 15
FDEF /* function 15 */
SWAP
DUP
MDRP[10110]
DUP
MDAP[1]
PUSH[000] 7
CALL /* call function 7 */
NOT
IF
SWAP
DUP
IF
MPPEM
GTEQ
ELSE
POP
PUSH[000] 1
EIF
IF
ROLL
PUSH[000] 4
MINDEX
MD[0]
SWAP
ROLL
SWAP
DUP
ROLL
MD[0]
ROLL
SWAP
SUB
SHPIX
ELSE
POP
POP
POP
POP
EIF
ELSE
POP
POP
POP
POP
POP
EIF
ENDF
PUSHB[000] 16
FDEF /* function 16 */
DUP
MDRP[11010]
PUSHB[000] 18
CALL /* call function 18 */
ENDF
PUSHB[000] 17
FDEF /* function 17 */
DUP
MDRP[10010]
PUSHB[000] 18
CALL /* call function 18 */
ENDF
PUSHB[000] 18
FDEF /* function 18 */
DUP
MDAP[1]
PUSHB[000] 7
CALL
NOT
IF
DUP
DUP
GC[1]
SWAP
GC[0]
SUB
ROUND[10]
ROLL
DUP
GC[1]
SWAP
GC[0]
SWAP
SUB
ROUND[10]
ADD
DUP
IF
DUP
ABS
DIV
SHPIX
ELSE
POP
POP
EIF
ELSE
POP
POP
EIF
ENDF
PUSHB[000] 19
FDEF /* function 19 */
DUP
ROLL
DUP
ROLL
SDPVTL[1]
DUP
PUSHB[000] 3
CINDEX
MD[1]
ABS
SWAP
ROLL
SPVTL[1]
PUSHB[000] 32
LT
IF
ALIGNRP
ELSE
MDRP[00000]
EIF
ENDF
/* initial stack| */
PUSHB[000] 20
FDEF /* function 20 */
PUSHB[011] 0 64 1 64 /* stack|←0|64|1|64 */
WS /* storage[1] := 64(1.0) */
WS /* storage[0] := 64(1.0) */
SVTCA[1] /* Set freedom and projection Vectors To the x Axis */
MPPEM /* stack|←ppem_x */
PUSHW[000] 4096 /* stack|ppem_x|←4096(64.0) */
MUL /* stack|←ppem_x*64.0 */
SVTCA[0] /* Set freedom and projection Vectors To the y Axis */
MPPEM /* stack|ppem_x*64.0|←ppem_y */
PUSHW[000] 4096 /* stack|ppem_x*64.0|ppem_y|←4096(64.0) */
MUL /* stack|ppem_x*64.0|←ppem_y*64.0 */
DUP /* stack|ppem_x*64.0|←ppem_y*64.0|ppem_y*64.0 */
ROLL /* stack|←ppem_y*64.0|ppem_y*64.0|ppem_x*64.0 */
DUP /* stack|ppem_y*64.0|ppem_y*64.0|←ppem_x*64.0|ppem_x*64.0 */
ROLL /* stack|ppem_y*64.0|←ppem_x*64.0|ppem_x*64.0|ppem_y*64.0 */
NEQ
IF /* if ppem_x*64.0 != ppem_y*64.0 */
DUP /* stack|ppem_y*64.0|←ppem_x*64.0|ppem_x*64.0 */
ROLL /* stack|←ppem_x*64.0|ppem_x*64.0|ppem_y*64.0 */
DUP /* stack|ppem_x*64.0|ppem_x*64.0|←ppem_y*64.0|ppem_y*64.0 */
ROLL /* stack|ppem_x*64.0|←ppem_y*64.0|ppem_y*64.0|ppem_x*64.0 */
GT
IF /* if ppem_y*64.0 > ppem_x*64.0 */
SWAP /* stack|←ppem_y*64.0|ppem_x*64.0 */
DIV /* stack|←ppem_y*64.0/(ppem_x*64.0) */
DUP /* stack|←ppem_y*64.0/(ppem_x*64.0)|ppem_y*64.0/(ppem_x*64.0) */
PUSHB[000] 0
SWAP /* stack|ppem_y*64.0/(ppem_x*64.0)|←0|ppem_y*64.0/(ppem_x*64.0) */
WS /* storage[0] := ppem_y*64.0/(ppem_x*64.0)
ELSE
DIV /* stack|←ppem_x*64.0/(ppem_y*64.0) */
DUP /* stack|←ppem_x*64.0/(ppem_y*64.0)|ppem_x*64.0/(ppem_y*64.0) */
PUSHB[000] 1
SWAP /* stack|ppem_x*64.0/(ppem_y*64.0)|←1|ppem_x*64.0/(ppem_y*64.0) */
WS /* storage[1] := ppem_x*64.0/(ppem_y*64.0) */
EIF /* stack|ratioppem=ppem_y*64.0/(ppem_x*64.0) (if ppem_y*64.0 > ppem_x*64.0) or ppem_x*64.0/(ppem_y*64.0) (otherwise) */
dest:
DUP /* stack|←ratioppem|ratioppem */
PUSHB[000] 64
GT
IF /* if ratioppem>64(1.0), stack|ratioppem */
PUSHB[010] 0 32 0 /* stack|ratioppem|←0|32|0 */
RS /* stack|ratioppem|0|32|←storage[0] */
MUL /* stack|ratioppem|0|←0.5*storage[0] */
WS /* storage[0] := 0.5*storage[0]
PUSHB[010] 1 32 1 /* stack|ratioppem|←1|32|1 */
RS /* stack|ratioppem|1|32|←storage[1] */
MUL /* stack|ratioppem|1|←0.5*storage[1] */
WS /* storage[1] := 0.5*storage[1] */
PUSHB[000] 32 /* stack|ratioppem|←32 */
MUL /* stack|←ratioppem*0.5 */
PUSHB[000] 25 /* stack|ratioppem*0.5|←25 */
NEG /* stack|ratioppem*0.5|←-25 */
JMPR /* jump to dest: */
POP
EIF
ELSE
POP /* stack|ppem_y*64.0| →ppem_x*64.0 */
POP /* stack| →ppem_y*64.0 */
EIF
ENDF
/* function 21 */
/* initial stack ... c b a | (stack top) */
/* final stack ..... c b*storage[0] a*storage[1] */
PUSHB[000] 21 /* c b a 21 */
FDEF /* c b a */
PUSHB[000] 1 /* c b a 1 */
RS /* c b a storage[1] */
MUL /* c b a*storage[1] */
SWAP /* c a*storage[1] b */
PUSHB[000] 0 /* c a*storage[1] b 0 */
RS /* c a*storage[1] b storage[0] */
MUL /* c a*storage[1] b*storage[0] */
SWAP /* c b*storage[0] a*storage[1] */
ENDF
長々と逆アセンブルしてきたはいいんですけど、Fontforgeとかでもできますので……。
関数定義だけが並んでいますね。
'prep' - Control Value Program
offsetは0x2204。
prep - Control value program table - Typography | Microsoft Docs
フォントサイズや変換行列などが変わる度に呼び出されるプログラムらしいです。グリフ固有の処理はできません。TrueType命令が並んでいます。
PUSHW[000] 0x01FF
SCANCTRL /* dropout control for all sizes */
PUSHB[000] 1
SCANTYPE /* rules 1 and 3 are invoked (dropout control scan conversion excluding stubs) */
/* rule1: ピクセル中心がアウトライン上または内部にあるとき、そのピクセルはonとなる */
/* rule2: 縦か横に隣接する2つのピクセル中心がon-Transitionとoff-Transitionの
2つのcontourによって交差され、かつどちらのピクセルもrule1によってonに
なっていない場合、左または下のピクセルをonにする */
/* rule3: 2つのcontourが両方向に他のスキャンラインと継続して交差する場合のみRule2をonにする */
SVTCA[0] /* set freedom and projection vectors to the y-axis */
MPPEM /* stack|←ppem */
PUSHB[000] 8 /* stack|ppem|←8 */
LT
IF /* if ppem < 8, stack| */
PUSHB[001] 1 1
INSTCTRL /* inhibit grid-fitting. any parameters set in the CVT program should be ignored
when instructions associated with glyphs are executed */
EIF
PUSHB[001] 0x46 6
CALL /* call function 6, stack|0x46(~1.09)|←func6(), check if ClearType Grayscale Hinting is enabled */
IF /* if ClearType Grayscale Hinting is enabled */
POP /* stack| →0x46 */
PUSHB[000] 0x10 /* stack|←0x10(~0.25) */
EIF
MPPEM /* stack|0x46or0x10|←ppem */
PUSHB[000] 20 /* stack|0x46or0x10|ppem|←20 */
GT
IF /* if ppem > 20, stack|70or16 */
POP /* stack| →0x46or0x10 */
PUSHB[000] 0x80 /* stack|←0x80(~2.0) */
EIF
SCVTCI /* set control value table cut-in, stack| →0x46(~1.09)or0x10(~0.25)or0x80(~2.0) */
PUSHB[000] 6
CALL /* call function 6, stack|←func6(), check if ClearType Grayscale Hinting is enabled */
NOT
IF /* if ClearType Grayscale Hinting is NOT enabled */
SVTCA[0] /* Set the projection vector and freedom vector to the y-axis */
PUSHB[000] 3 /* stack|←3 */
DUP /* stack|←3|3 */
RCVT /* Read Control Value Table entry 3, stack|3|←cvt[3](=0x00A2 ~2.53) */
PUSHB[000] 3
CALL /* call function 3, stack|3|←round(2.53125)~3? */
WCVTP /* Write Control Value Table in Pixel units, cvt[3] := round(2.53125), stack| */
PUSHB[000] 4 /* stack|←4 */
DUP /* stack|←4|4 */
RCVT /* Read Control Value Table entry 4, stack|4|←cvt[4](=0x00E5 ~3.58) */
PUSHB[010] 3 18 2 /* stack|4|cvt[4]|←3|18|2 */
CALL /* call function 2, stack|4|←18>ppem?cvt[3](round(2.53125)):cvt[4](~3.58) */
PUSHB[000] 3
CALL /* call function 3, stack|4|←round(18>ppem?cvt[3](round(2.53125)):cvt[4](~3.58)) */
WCVTP /* cvt[4] := round(18>ppem?cvt[3]:cvt[4](~3.58)), stack| */
PUSHB[000] 5 /* stack|←5 */
DUP /* stack|←5|5 */
RCVT /* stack|5|←cvt[5](=0x006A ~1.66) */
PUSHB[010] 4 18 2 /* stack|5|cvt[5](~1.66)|←4|18|2 */
CALL /* call function 2, stack|5|←18>ppem?cvt[4]:cvt[5] */
PUSHB[000] 3
CALL /* call function 3, stack|5|←round(18>ppem?cvt[4]:cvt[5]) */
WCVTP /* cvt[5] := round(18>ppem?cvt[4]:cvt[5]), stack| */
PUSHB[000] 6 /* stack|←6 */
DUP /* stack|←6|6 */
RCVT /* stack|6|←cvt[6](=0x007D ~1.95) */
PUSHB[010] 5 66 2 /* stack|6|cvt[6](~1.95)←5|66|2 */
CALL /* call function 2, stack|6|←66>ppem?cvt[5]:cvt[6] */
PUSHB[000] 3
CALL /* call function 3, stack|6|←round(66>ppem?cvt[5]:cvt[6]) */
WCVTP /* cvt[6] := round(66>ppem?cvt[5]:cvt[6]) */
PUSHB[000] 7 /* stack|←7 */
DUP /* stack|←7|7 */
RCVT /* stack|7|←cvt[7](=0x008C ~2.19) */
PUSHB[010] 6 44 2 /* stack|7|←cvt[7](~2.19)|←6|44|2 */
CALL /* call function 2, stack|7|←44>ppem?cvt[6]:cvt[7] */
PUSHB[000] 3
CALL /* call function 3, stack|7|←round(44>ppem?cvt[6]:cvt[7]) */
WCVTP /* cvt[7] := round(44>ppem?cvt[6]:cvt[7]) */
PUSHB[000] 8 /* stack|←8 */
DUP /* stack|←8|8 */
RCVT /* stack|8|←cvt[8](=0x0091 ~2.27) */
PUSHB[010] 7 43 2 /* stack|8|cvt[8](~2.27)|←7|43|2 */
CALL /* call function 2, stack|8|←43>ppem?cvt[7]:cvt[8] */
PUSHB[000] 3
CALL /* call function 3, stack|8|←round(43>ppem?cvt[7]:cvt[8]) */
WCVTP /* cvt[8] := round(43>ppem?cvt[7]:cvt[8]) */
PUSHB[000] 9 /* stack|←9 */
DUP /* stack|←9|9 */
RCVT /* stack|9|←cvt[9](=0x0095 ~2.33) */
PUSHB[010] 8 28 2 /* stack|9|cvt[9](~2.33)|←8|28|2 */
CALL /* call function 2, stack|9|←28>ppem?cvt[8]:cvt[9] */
PUSHB[000] 3
CALL /* call function 3, stack|9|←round(28>ppem?cvt[8]:cvt[9]) */
WCVTP /* cvt[9] := round(28>ppem?cvt[8]:cvt[9]) */
PUSHB[000] 10 /* stack|←10 */
DUP /* stack|←10|10 */
RCVT /* stack|10|←cvt[10](=0x009A ~2.41) */
PUSHB[010] 9 27 2 /* stack|10|cvt[10](~2.41)|←9|27|2 */
CALL /* call function 2, stack|10|←27>ppem?cvt[9]:cvt[10] */
PUSHB[000] 3
CALL /* call function 3, stack|10|←round(27>ppem?cvt[9]:cvt[10]) */
WCVTP /* cvt[10] := round(27>ppem?cvt[9]:cvt[10]) */
PUSHB[000] 11 /* stack|←11 */
DUP /* stack|←11|11 */
RCVT /* stack|11|←cvt[11](=0x009D ~2.45) */
PUSHB[010] 10 26 2 /* stack|11|cvt[11](~2.45)|←10|26|2 */
CALL /* call function 2, stack|11|←26>ppem?cvt[10]:cvt[11] */
PUSHB[000] 3
CALL /* call function 3, stack|11|←round(26>ppem?cvt[10]:cvt[11]) */
WCVTP /* cvt[11] := round(26>ppem?cvt[10]:cvt[11]) */
PUSHB[000] 12 /* stack|←12 */
DUP /* stack|←12|12 */
RCVT /* stack|12|←cvt[12](=0x0054 ~1.31) */
PUSHB[010] 11 33 2 /* stack|12|cvt[12](~1.31)|←11|33|2 */
CALL /* call function 2, stack|12|←33>ppem?cvt[11]:cvt[12] */
PUSHB[000] 3
CALL /* call function 3, stack|12|←round(33>ppem?cvt[11]:cvt[12]) */
WCVTP /* cvt[12] := round(33>ppem?cvt[11]:cvt[12]) */
SVTCA[1] /* Set the projection vector and freedom vector to the x-axis */
PUSHB[000] 13 /* stack|←13 */
DUP /* stack|←13|13 */
RCVT /* stack|13|←cvt[13](=0x00B4 ~2.81) */
PUSHB[000] 3
CALL /* call function 3, stack|13|←round(cvt[13]) */
WCVTP /* cvt[13] := round(cvt[13]) */
PUSHB[000] 14 /* stack|←14 */
DUP /* stack|←14|14 */
RCVT /* stack|14|←cvt[14](=0x00D3 ~3.30) */
PUSHB[010] 13 39 2 /* stack|14|cvt[14](~3.30)|←13|39|2 */
CALL /* call function 2, stack|14|←39>ppem?cvt[13]:cvt[14] */
PUSHB[001] 3 0x46 /* stack|14|39>ppem?cvt[13]:cvt[14]|←3|0b01000110 */
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|14|←round(39>ppem?cvt[13]:cvt[14]) */
WCVTP /* cvt[14] := round(39>ppem?cvt[13]:cvt[14]) */
PUSHB[000] 15
DUP /* stack|←15|15 */
RCVT /* stack|15|←cvt[15](=0x0081 ~2.02) */
PUSHB[010] 14 29 2 /* stack|15|cvt[15]|←14|29|2 */
CALL /* call function 2, stack|15|←29>ppem?cvt[14]:cvt[15] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|15|←round(29>ppem?cvt[14]:cvt[15]) */
WCVTP /* cvt[15] := round(29>ppem?cvt[14]:cvt[15]) */
PUSHB[000] 16
DUP /* stack|←16|16 */
RCVT /* stack|16|←cvt[16](0x008F ~2.23) */
PUSHB[010] 15 43 2 /* stack|16|cvt[16]|←15|43|2 */
CALL /* call function 2, stack|16|←43>ppem?cvt[15]:cvt[16] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|16|←round(43>ppem?cvt[15]:cvt[16]) */
WCVTP /* cvt[16] := round(43>ppem?cvt[15]:cvt[16]) */
PUSHB[000] 17
DUP /* stack|←17|17 */
RCVT /* stack|17|←cvt[17](0x0094 ~2.31) */
PUSHB[010] 16 42 2 /* stack|17|cvt[17]|←16|42|2 */
CALL /* call function 2, stack|17|←42>ppem?cvt[16]:cvt[17] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|17|←round(42>ppem?cvt[16]:cvt[17]) */
WCVTP /* cvt[17] := round(42>ppem?cvt[16]:cvt[17]) */
PUSHB[000] 18
DUP /* stack|←18|18 */
RCVT /* stack|18|←cvt[18](=0x00AA ~2.66) */
PUSHB[010] 17 37 2 /* stack|18|cvt[18]|←17|37|2 */
CALL /* call function 2, stack|18|←37>ppem?cvt[17]:cvt[18] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|18|←round(37>ppem?cvt[17]:cvt[18]) */
WCVTP /* cvt[18] := round(37>ppem?cvt[17]:cvt[18]) */
PUSHB[000] 19
DUP /* stack|←19|19 */
RCVT /* stack|19|←cvt[19](=0x00AE ~2.72) */
PUSHB[010] 18 36 2 /* stack|19|cvt[19]|←18|36|2 */
CALL /* call function 2, stack|19|←36>ppem?cvt[18]:cvt[19] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|19|←round(36>ppem?cvt[18]:cvt[19]) */
WCVTP /* cvt[19] := round(36>ppem?cvt[18]:cvt[19]) */
PUSHB[000] 20
DUP /* stack|←20|20 */
RCVT /* stack|20|←cvt[20](=0x00B4 ~2.81) */
PUSHB[010] 19 35 2 /* stack|20|cvt[20]|←19|35|2 */
CALL /* call function 2, stack|20|←35>ppem?cvt[19]:cvt[20] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|20|←round(35>ppem?cvt[19]:cvt[20]) */
WCVTP /* cvt[20] := round(35>ppem?cvt[19]:cvt[20]) */
PUSHB[000] 21
DUP /* stack|←21|21 */
RCVT /* stack|21|←cvt[21](=0x00B8 ~2.88) */
PUSHB[010] 20 34 2 /* stack|21|cvt[21]|←20|34|2 */
CALL /* call function 2, stack|21|←34>ppem?cvt[20]:cvt[21] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|21|←round(34>ppem?cvt[20]:cvt[21]) */
WCVTP /* cvt[21] := round(34>ppem?cvt[20]:cvt[21]) */
PUSHB[000] 22
DUP /* stack|←22|22 */
RCVT /* stack|22|←cvt[22](=0x00BE ~2.97) */
PUSHB[010] 21 33 2 /* stack|22|cvt[22]|←21|33|2 */
CALL /* call function 2, stack|22|←33>ppem?cvt[21]:cvt[22] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|22|←round(33>ppem?cvt[21]:cvt[22]) */
WCVTP /* cvt[22] := round(33>ppem?cvt[21]:cvt[22]) */
PUSHB[000] 23
DUP /* stack|←23|23 */
RCVT /* stack|23|←cvt[23](=0x00C2 ~3.03) */
PUSHB[010] 22 32 2 /* stack|23|cvt[23]|←22|32|2 */
CALL /* call function 2, stack|23|←32>ppem?cvt[22]:cvt[23] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|23|←round(32>ppem?cvt[22]:cvt[23]) */
WCVTP /* cvt[23] :=
PUSHB[000] 24
DUP /* stack|←24|24 */
RCVT /* stack|24|←cvt[24](=0x006B ~1.67) */
PUSHB[010] 23 17 2 /* stack|24|cvt[24]|←23|17|2 */
CALL /* call function 2, stack|24|←17>ppem?cvt[23]:cvt[24] */
PUSHB[001] 3 0x46
SROUND /* round_state := period: 1 pixel, phase: 0, threshold: 2/8 * period = 1/4 pixel */
CALL /* call function 3, stack|24|←round(17>ppem?cvt[23]:cvt[24]) */
WCVTP /* cvt[24] := round(17>ppem?cvt[23]:cvt[24]) */
EIF
PUSHB[000] 20
CALL /* call function 20 */
SVTCA[0]
関数2, 3, 6, 20を呼び出していますね。先ほど見たfpgmで定義されてるものを参照してるのですね。
'loca' - Index to Location
offsetは0x19E0。
loca - Index-to-location - Typography | Microsoft Docs
このテーブルには2種類の形式がありますが、headテーブルのindexToLocFormat = 0であることからshort版であることがわかります。
indexToLoc table short version
Type |
Name |
説明 |
USHORT |
offsets[n] |
2で割ったローカルオフセット n = numGlyphs('maxp') + 1 |
maxpテーブルのnumGlyphs = 45なので、USHORT型が46個並んだ配列となっているはずです。
index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
0o00 |
0x0000 |
0x002C |
0x002C |
0x002C |
0x002C |
0x00A4 |
0x010E |
0x0154 |
0o10 |
0x01BA |
0x021E |
0x0272 |
0x02EC |
0x033C |
0x0376 |
0x03C2 |
0x03FE |
0o20 |
0x0418 |
0x0482 |
0x04CC |
0x051A |
0x0580 |
0x05E6 |
0x0622 |
0x0688 |
0o30 |
0x06EC |
0x073A |
0x0766 |
0x0806 |
0x0838 |
0x0878 |
0x08A6 |
0x08A6 |
0o40 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08A6 |
0o50 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08A6 |
0x08B2 |
|
|
'glyf' - Glyf Data
offsetは0x076C。
glyf - Glyf data table - Typography | Microsoft Docs
これにはグリフのデータが並んでいます。実際に描画される文字図形の本体ですね。フォントファイルの大部分を占めています。
locaテーブルに書かれているGlyphIDに対応するオフセットにアクセスするとそのグリフが得られるようです。
さすがに全部順番に読んでいくのは無理なので、今回描こうとしている“cat”の三文字について見ていきましょうか。
cmapテーブルから、c, a, tのGlyph IDはそれぞれ6, 4, 23であることがわかります。locaテーブルから対応するオフセットを求めると……
文字 |
glyph ID |
offset |
アドレス |
c |
6 |
0x021C |
0x0988 - 0x0A14 |
a |
4 |
0x0058 |
0x07C4 - 0x08B4 |
t |
23 |
0x0D10 |
0x147C - 0x1544 |
となるので、それぞれを見ていきましょう。
'c' 0x0988 - 0x0A14
ヘッダー
Type |
Name |
value |
備考 |
SHORT |
numberOfContours |
0x0001 |
1; >= 0 なのでsingle glyph |
SHORT |
xMin |
0x0071 |
Minimum x for coordinate data. |
SHORT |
yMin |
0xFFEC |
Minimum y for coordinate data. |
SHORT |
xMax |
0x0393 |
Maximum x for coordinate data. |
SHORT |
yMax |
0x045E |
Maximum y for coordinate data. |
numberOfCoutours >= 0 で単体のグリフなので、次のデータが来ます。
Simple Glyph Description
Type |
Name |
value |
Description |
USHORT |
endPtsOfContours[n] |
0x0016 |
22; Array of last points of each contour; n = numberOfCoutours |
USHORT |
instructionLength |
0x003D |
61; instructionsのバイト数 |
BYTE |
instructions[n] |
以下 |
Array of instructions for each glyph; n is the number of instructions. |
BYTE |
flags[n] |
以下 |
Array of flags for each coordinate in outline; n is the number of flags. |
BYTE or SHORT |
xCoordinates[ ] |
以下 |
First coordinates relative to (0,0); others are relative to previous point. |
BYTE or SHORT |
yCoordinates[ ] |
以下 |
First coordinates relative to (0,0); others are relative to previous point. |
TrueType命令 (0x0996~0x9D3)
SVTCA[0] /* set freedom and projection vectors to the y-axis */
PUSHB[010] 20 0 0
CALL /* call function 0, stack| */
PUSHB[001] 15 10
MIRP[01001] /* Move Indirect Relative Point 16, cvt[10]
Keep distance greater than or equal to minimum distance,
Do not round the distance and do not look at the control value cut-in,
Black distance type */
PUSHB[010] 3 1 0
CALL /* call function 0 */
PUSHB[001] 9 11
MIRP[01001] /* Move Indirect Relative Point 9, cvt[11]
Keep distance greater than or equal to minimum distance,
Do not round the distance and do not look at the control value cut-in,
Black distance type */
SVTCA[1] /* set freedom and projection vectors to the x-axis */
PUSHB[000] 23
MDAP[1] /* Move Direct Absolute Point 23, round the value */
PUSHB[000] 0
MDRP[10110] /* Move Direct Relative Point 0, rp0 := point 0,
do not keep distance greater than or equal to minimum distance,
round the distance, White distance type; rp1: = rp0, rp2 := 0 */
PUSHB[001] 12 21
MIRP[01001] /* Move Indirect Relative Point 12, cvt[21]
Keep distance greater than or equal to minimum distance,
Do not round the distance and do not look at the control value cut-in,
Black distance type */
PUSHB[001] 24 1 /* stack|←24|1 */ /*解析飽きた*/
CALL /* call function 1 */
SVTCA[0]
PUSHB[001] 15 20
SRP1
SRP2
PUSHB[000] 18
IP
PUSHB[000] 9
SRP1
PUSHB[010] 0 7 17
IP
IP
IP
PUSHB[000] 3
SRP2
PUSHB[000] 6
IP
IUP[0]
IUP[1]
関数0, 1を呼んでいるようですが…。
制御点列とその座標
flagsの配列の大きさは最初から確定できないので順番に読んでいく必要があるようです。インデックスがendPtsOfContoursの最大の値に等しくなるまで読んでいきます。
flagには、x,yがそれぞれBYTE(さらに正か負)かSHORTか前の値と同じかについての情報が含まれているので、それに従ってまず各制御点のx座標を、次にy座標を読みだしていきます。記録されている座標の値は前の座標との差分になっていて、記録スペースを圧縮することができるようになっているようです。
1バイト正数を+1b, 1バイト負数を-1b, 2バイト整数を2b, 前と変化なしをsameとします。
idx |
flag |
On Curve |
x |
y |
Δx |
Δy |
x座標 |
y座標 |
0 |
0x13 (0b00010011) |
○ |
+1b |
2b |
+113 |
+543 |
113 |
543 |
1 |
0x10 (0b00010000) |
|
same |
2b |
0 |
+276 |
113 |
819 |
2 |
0x00 (0b00000000) |
|
2b |
2b |
+267 |
+299 |
380 |
1118 |
3 |
0x33 (0b00110011) |
○ |
+1b |
same |
+247 |
0 |
627 |
1118 |
4 |
0x32 (0b00110010) |
|
+1b |
same |
+80 |
0 |
707 |
1118 |
5 |
0x16 (0b00010110) |
|
+1b |
-1b |
+157 |
-33 |
864 |
1085 |
6 |
0x17 (0b00010111) |
○ |
+1b |
-1b |
+51 |
-26 |
915 |
1059 |
7 |
0x07 (0b00000111) |
○ |
-1b |
-1b |
-55 |
-150 |
860 |
909 |
8 |
0x26 (0b00100110) |
|
-1b |
+1b |
-139 |
+52 |
721 |
961 |
9 |
0x23 (0b00100011) |
○ |
-1b |
same |
-98 |
0 |
623 |
961 |
10 |
0x22 (0b00100010) |
|
-1b |
same |
-166 |
0 |
457 |
961 |
11 |
0x06 (0b00000110) |
|
-1b |
-1b |
-158 |
-209 |
299 |
752 |
12 |
0x15 (0b00010101) |
○ |
same |
-1b |
0 |
-207 |
299 |
545 |
13 |
0x14 (0b00010100) |
|
same |
-1b |
0 |
-199 |
299 |
346 |
14 |
0x16 (0b00010110) |
|
+1b |
-1b |
+158 |
-211 |
457 |
135 |
15 |
0x33 (0b00110011) |
○ |
+1b |
same |
+155 |
0 |
612 |
135 |
16 |
0x32 (0b00110010) |
|
+1b |
same |
+145 |
0 |
757 |
135 |
17 |
0x37 (0b00110111) |
○ |
+1b |
+1b |
+140 |
+64 |
897 |
199 |
18 |
0x15 (0b00010101) |
○ |
same |
-1b |
0 |
-160 |
897 |
39 |
19 |
0x06 (0b00000110) |
|
-1b |
-1b |
-114 |
-59 |
783 |
-20 |
20 |
0x23 (0b00100011) |
○ |
-1b |
same |
-169 |
0 |
614 |
-20 |
21 |
0x22 (0b00100010) |
|
-1b |
same |
-237 |
0 |
377 |
-20 |
22 |
0x00 (0b00000000) |
|
2b |
2b |
-264 |
+291 |
113 |
271 |
座標の単位はFUNITです。
とりあえず制御点列が得られたので、プロットしていきましょう。簡
単のため、今回は10 FUNITを1mmとします。このフォントはEMが2048 FUNITなので、EMスクエアは204.8mmとなる計算です。
ここでは、黒い点がon-curve point、赤い×がoff-curve pointです。
それっぽいですね。
'a' 0x07C4 - 0x08B4
同様に読んでいきます。
ヘッダー
Type |
Name |
value |
備考 |
SHORT |
numberOfContours |
0x0002 |
2; >= 0 なのでsingle glyph |
SHORT |
xMin |
0x005E |
Minimum x for coordinate data. |
SHORT |
yMin |
0xFFEC |
Minimum y for coordinate data. |
SHORT |
xMax |
0x03D7 |
Maximum x for coordinate data. |
SHORT |
yMax |
0x045C |
Maximum y for coordinate data. |
numberOfCoutours >= 0 なので、つまり合成グリフでないので、次のテーブルが後続します。
Simple Glyph Description
Type |
Name |
value |
Description |
USHORT |
endPtsOfContours[n] |
0x001A |
26; Array of last points of each contour; n = numberOCoutours |
|
|
0x0025 |
37 |
USHORT |
instructionLength |
0x007B |
123; instructionsのバイト数 |
BYTE |
instructions[n] |
以下 |
TrueType命令 |
BYTE |
flags[n] |
以下 |
このフラグに従って座標データを読む |
BYTE or SHORT |
xCoordinates[ ] |
以下 |
x座標データ差分 |
BYTE or SHORT |
yCoordinates[ ] |
以下 |
y座標データ差分 |
TrueType命令: 0x07D4~0x084F
SVTCA[0]
PUSHB[010] 19 0 0
CALL /* call function 0 */
PUSHB[010] 24 0 0
CALL /* call function 0 */
PUSHB[001] 30 8
MIRP[01001]
PUSHB[010] 14 1 0
CALL /* call function 0 */
PUSHB[001] 7 8
MIRP[01001]
PUSHB[100] 2 35 24 14 13
CALL /* call function 13 */
PUSHB[001] 2 6
MIRP[01001]
SVTCA[1]
PUSHB[000] 38
MDAP[1]
PUSHB[000] 0
MDRP[10110]
PUSHB[001] 27 21
MIRP[01001]
PUSHB[000] 27
SRP0
PUSHB[001] 33 1
CALL /* call function 1 */
PUSHB[000] 3
SHP[0]
PUSHB[001] 17 13
MIRP[01001]
PUSHB[000] 17
SRP0
PUSHB[001] 19 15
MIRP[01001]
PUSHB[000] 19
MDAP[1]
PUSHB[001] 39 1
CALL /* call function 1 */
PUSHB[001] 27 0
SRP1
SRP2
PUSHB[001] 10 11
IP
IP
PUSHB[000] 33
SRP1
PUSHB[010] 14 24 7
IP
IP
IP
PUSHB[000] 19
SRP2
PUSHB[000] 21
IP
SVTCA[0]
PUSHB[001] 2 24
SRP1
SRP2
PUSHB[000] 20
IP
PUSHB[000] 7
SRP1
PUSHB[000] 10
IP
PUSHB[000] 14
SRP2
PUSHB[000] 11
IP
IUP[0]
IUP[1]
関数0, 1, 13を呼んでいるようです。
制御点列とその座標
idx |
flag |
On Curve |
x |
y |
Δx |
Δy |
x座標 |
y座標 |
0 |
0x13 (0b00010011) |
○ |
+1b |
2b |
+94 |
+305 |
94 |
305 |
1 |
0x10 (0b00010000) |
|
same |
2b |
0 |
+334 |
94 |
639 |
2 |
0x25 (0b00100101) |
○ |
2b |
+1b |
+527 |
+16 |
621 |
655 |
3 |
0x37 (0b00110111) |
○ |
+1b |
+1b |
+186 |
+7 |
807 |
662 |
4 |
0x35 (0b00110101) |
○ |
same |
+1b |
0 |
+65 |
807 |
727 |
5 |
0x34 (0b00110100) |
|
same |
+1b |
0 |
+125 |
807 |
852 |
6 |
0x26 (0b00100110) |
|
-1b |
+1b |
-108 |
+119 |
699 |
971 |
7 |
0x23 (0b00100011) |
○ |
-1b |
same |
-119 |
0 |
580 |
971 |
8 |
0x22 (0b00100010) |
|
-1b |
same |
-87 |
0 |
493 |
971 |
9 |
0x06 (0b00000110) |
|
-1b |
-1b |
-155 |
-52 |
338 |
919 |
10 |
0x07 (0b00000111) |
○ |
-1b |
-1b |
-68 |
-32 |
270 |
887 |
11 |
0x27 (0b00100111) |
○ |
-1b |
+1b |
-55 |
+135 |
215 |
1022 |
12 |
0x3E (0b00111110) |
|
+1b |
+1b |
+83 |
+44 |
298 |
1066 |
13 |
0x01 |
|
+1b |
+1b |
+196 |
+50 |
494 |
1116 |
14 |
0x33 (0b00110011) |
○ |
+1b |
same |
+96 |
0 |
590 |
1116 |
15 |
0x32 (0b00110010) |
|
+1b |
same |
+199 |
0 |
789 |
1116 |
16 |
0x16 (0b00010110) |
|
+1b |
-1b |
+194 |
-176 |
983 |
940 |
17 |
0x15 (0b00010101) |
○ |
same |
-1b |
0 |
-192 |
983 |
748 |
18 |
0x11 (0b00010001) |
○ |
same |
2b |
0 |
-748 |
983 |
0 |
19 |
0x23 (0b00100011) |
○ |
-1b |
same |
-131 |
0 |
852 |
0 |
20 |
0x27 (0b00100111) |
○ |
-1b |
+1b |
-35 |
+156 |
817 |
156 |
21 |
0x23 (0b00100011) |
○ |
-1b |
same |
-8 |
0 |
809 |
156 |
22 |
0x0E (0b00001110) |
|
-1b |
-1b |
-82 |
-103 |
727 |
53 |
23 |
0x01 |
|
-1b |
-1b |
-163 |
-73 |
564 |
-20 |
24 |
0x23 (0b00100011) |
○ |
-1b |
same |
-124 |
0 |
440 |
-20 |
25 |
0x22 (0b00100010) |
|
-1b |
same |
-162 |
0 |
278 |
-20 |
26 |
0x26 (0b00100110) |
|
-1b |
+1b |
-184 |
+170 |
94 |
150 |
27 |
0x37 (0b00110111) |
○ |
+1b |
+1b |
+187 |
+153 |
281 |
303 |
28 |
0x14 (0b00010100) |
|
same |
-1b |
0 |
-86 |
281 |
217 |
29 |
0x16 (0b00010110) |
|
+1b |
-1b |
+105 |
-92 |
386 |
125 |
30 |
0x33 (0b00110011) |
○ |
+1b |
same |
+95 |
0 |
481 |
125 |
31 |
0x32 (0b00110010) |
|
+1b |
same |
+151 |
0 |
632 |
125 |
32 |
0x36 (0b00110110) |
|
+1b |
+1b |
+173 |
+163 |
805 |
288 |
33 |
0x3D (0b00111101) |
○ |
same |
+1b |
0 |
+150 |
805 |
438 |
34 |
0x01 |
○ |
same |
+1b |
0 |
+99 |
805 |
537 |
35 |
0x07 (0b00000111) |
○ |
-1b |
-1b |
-162 |
-7 |
643 |
530 |
36 |
0x0E (0b00001110) |
|
-1b |
-1b |
-189 |
-7 |
454 |
523 |
37 |
0x01 |
|
-1b |
-1b |
-173 |
106 |
281 |
417 |
制御点の0-26と27-37が別々のcontourになります。
cと同様にaの制御点列もプロットしていきましょう。'hmtx'で見たようにcは幅983なので、aは制御点のx座標に983を加えた場所にプロットします。
't' 0x147C - 0x1544
これも同様に読んでいきます。
ヘッダー
Type |
Name |
value |
備考 |
SHORT |
numberOfContours |
0x0001 |
1; >= 0 なのでsingle glyph |
SHORT |
xMin |
0x0021 |
Minimum x for coordinate data. |
SHORT |
yMin |
0xFFEC |
Minimum y for coordinate data. |
SHORT |
xMax |
0x02B6 |
Maximum x for coordinate data. |
SHORT |
yMax |
0x0546 |
Maximum y for coordinate data. |
numberOfCoutours >= 0 なので、つまり合成グリフでないので、次のテーブルが後続します。
Simple Glyph Description
Type |
Name |
value |
Description |
USHORT |
endPtsOfContours[n] |
0x0016 |
22; Array of last points of each contour; n = numberOCoutours |
USHORT |
instructionLength |
0x007A |
122; instructionsのバイト数 |
BYTE |
instructions[n] |
以下 |
TrueType命令 |
BYTE |
flags[n] |
以下 |
このフラグに従って座標データを読む |
BYTE or SHORT |
xCoordinates[ ] |
以下 |
x座標データ差分 |
BYTE or SHORT |
yCoordinates[ ] |
以下 |
y座標データ差分 |
TrueType命令 (0x148A-0x1504)
SVTCA[0]
PUSHB[010] 19 0 0
CALL /* call function 0 */
PUSHB[001] 12 8
MIRP[01001]
PUSHB[010] 5 1 0
CALL /* call function 0 */
PUSHB[001] 8 7
MIRP[01001]
PUSHB[000] 0
SHP[0]
PUSHB[010] 5 8 10
CALL /* call function 10 */
PUSHB[011] 64 5 4 9
CALL /* call function 9 */
PUSHB[010] 2 1 0
CALL /* call function 0 */
SVTCA[1]
PUSHB[000] 23
MDAP[1]
PUSHB[000] 21
MDRP[10110]
PUSHB[001] 9 13
MIRP[01001]
PUSHB[000] 4
SHP[0]
PUSHB[010] 9 21 10
CALL /* call function 10 */
PUSHB[011] 64 9 7 9
CALL /* call function 9 */
PUSHB[000] 15
SHP[0]
PUSHB[010] 21 9 10
CALL /* call function 10 */
PUSHB[011] 64 21 0 9
CALL /* call function 9 */
PUSHB[000] 9
SRP0
PUSHB[001] 3 24
MIRP[01001]
PUSHB[000] 3
MDAP[1]
PUSHB[001] 24 1
CALL /* call function 1 */
PUSHB[001] 3 21
SRP1
SRP2
PUSHB[000] 2
IP
SVTCA[0]
PUSHB[001] 12 19
SRP1
SRP2
PUSHB[000] 16
IP
PUSHB[000] 8
SRP1
PUSHB[000] 15
IP
PUSHB[000] 5
SRP2
PUSHB[000] 1
IP
IUP[0]
IUP[1]
関数0, 1, 9, 10を呼んでいます。
制御点列とその座標
idx |
flag |
On Curve |
x |
y |
Δx |
Δy |
x座標 |
y座標 |
0 |
0x13 (0b00010011) |
○ |
+1b |
2b |
+33 |
+958 |
33 |
958 |
1 |
0x35 (0b00110101) |
○ |
same |
+1b |
0 |
+86 |
33 |
1044 |
2 |
0x3F (0b00111111) |
○ |
+1b |
+1b |
+157 |
+72 |
190 |
1116 |
3 |
0x01 |
○ |
+1b |
+1b |
+72 |
+234 |
262 |
1350 |
4 |
0x33 (0b00110011) |
○ |
+1b |
same |
+107 |
0 |
369 |
1350 |
5 |
0x15 (0b00010101) |
○ |
same |
-1b |
0 |
-252 |
369 |
1098 |
6 |
0x21 (0b00100001) |
○ |
2b |
same |
+317 |
0 |
686 |
1098 |
7 |
0x15 (0b00010101) |
○ |
same |
-1b |
0 |
-140 |
686 |
958 |
8 |
0x21 (0b00100001) |
○ |
2b |
same |
-317 |
0 |
369 |
958 |
9 |
0x11 (0b00010001) |
○ |
same |
2b |
0 |
-634 |
369 |
324 |
10 |
0x14 (0b00010100) |
|
same |
-1b |
0 |
-95 |
369 |
229 |
11 |
0x16 (0b00010110) |
|
+1b |
-1b |
+91 |
-102 |
460 |
127 |
12 |
0x33 (0b00110011) |
○ |
+1b |
same |
+81 |
0 |
541 |
127 |
13 |
0x32 (0b00110010) |
|
+1b |
same |
+35 |
0 |
576 |
127 |
14 |
0x36 (0b00110110) |
|
+1b |
+1b |
+94 |
+14 |
670 |
141 |
15 |
0x37 (0b00110111) |
○ |
+1b |
+1b |
+24 |
+9 |
694 |
150 |
16 |
0x15 (0b00010101) |
○ |
same |
-1b |
0 |
-138 |
694 |
12 |
17 |
0x0E (0b00001110) |
|
-1b |
-1b |
-25 |
-11 |
669 |
1 |
18 |
0x01 |
|
-1b |
-1b |
-105 |
-21 |
564 |
-20 |
19 |
0x23 (0b00100011) |
○ |
-1b |
same |
-54 |
0 |
510 |
-20 |
20 |
0x20 (0b00100000) |
|
2b |
same |
-322 |
0 |
188 |
-20 |
21 |
0x19 (0b00011001) |
○ |
same |
2b |
0 |
+339 |
188 |
319 |
22 |
0x01 |
○ |
same |
2b |
0 |
+639 |
188 |
958 |
また、tについてもプロットしていきましょう。cとaの幅 = 983 + 1149をx座標に加えた位置にプロットします。
アウトライン描画
さて、これら制御点列からアウトラインを描いていきましょう。
on-curve pointはその名前の通りグリフの輪郭(=contour)の上にある点です。最終的なアウトラインはon-curve pointを通ります。
一方でoff-curve pointは曲線の曲がり方を制御する点で、最終的なアウトラインは普通この点を通りません。
on-curve pointが2つ連続すると直線になります。連続するon-curve pointを黒線で結びます。
tは大体直線でできていますね。
次に、曲線を求めていかないといけないのですが、まず準備として制御点列を線で結びます。
そして、連続するoff-curve pointの中点にon-curve pointを打っていきます。
こうすることで、曲線の部分は2次ベジエ(Bézier)曲線の集まりとみなせます。
さて、2次ベジエを描いていきましょう。2次ベジエは3つの制御点からなる曲線で、両端の点を通り真ん中の点の方に近づくカーブを描きます。ここでは、on-curve・off-curve・on-curveの3点の並び(●-×-●の並び)が2次ベジエ曲線です。
今回は、ド・カステリョ(De Casteljau)のアルゴリズムの考え方によって、ベジエ曲線を半分半分に繰り返し分割していくことで求めて行きたいと思います。
まず、2次ベジエの隣り合う制御点の中点同士を結びます。
次にその中点を求めるとそれがon-curve pointとなります。
さて、分割されたベジエ曲線は次の図の●-+-●の並びになります。
間隔が十分に狭いところを除いて、さらに繰り返し分割していきます。
制御点を結んだ線がどんどん求める形に近づいていきますね。
もう一回分割します。
も、もういいかな……さらに分割してもいいですが面倒なのと狭くてうまく描けないのであとはフリーハンドで黒い点をなめらかに結んでいきます。
完成です! 良さそうですね! これをさらに、制御点を順番にたどっていったときの右手側を塗りつぶすとフォントのレンダリング結果になります。
さて、実際にPhotoshopでフォントを表示したものを重ねてみます。
いい感じに一致してますね!
感想
- こうやって見ていくと自然に仕様書とにらめっこすることになり、フォントファイルについての理解が深まりますね。
マゾい暇な人にお勧めします。
- こんなことするより仕様に従ってフォントを読み込むソフトウェア作ったほうが生産性があるかもしれません。
- データを格納する際に、容量が小さくなるような工夫が随所にあるのがわかります。オフセット指定によって内容が同一なものは複数記録する必要がなかったり、座標は差分のみを記録することで容量を節約したり、ランレングス圧縮的な要素もあります。
- フォントヒンティングに関するTrueType命令の動作を把握することについては、フォントから制御点の座標を読み出す以上に面倒そうなので途中で投げました。詳しい人に動作を教わりたいです。
- もうやらない。