OpenType フォーマットのフォントファイルからカーニング情報を取り出したい事情があっていろいろ調べた作業ログ。
- フォントファイルをいじるときにつかう定番ライブラリ freetype 2.6 では OpenType のカーニング情報がひきだせなかったので(フォントにもよると思うけど)、libotf (0.9.13) で OTF ファイルを解析して取り出す。
- OTF のファイルフォーマットは [Microsoft Typography - OpenType Specification] (https://www.microsoft.com/typography/otspec/) にまとまってる。
- The TrueType Font File OpenType は TrueType をベースにしてるので↑にないテーブル定義とかはこのへんも見る。
- libotf の example に含まれてる
otfdump
コマンドを使うと、OTF ファイルの構造をツリー上にテキストでダンプしてくれるので↓、仕様書とくらべながら見ると理解しやすい。
(OTF
(OffsetTable
(sfnt-version 20308.21583)
(numTables 25)
(searchRange 256)
(enterSelector 4)
(rangeShift 384))
(Table 0 (tag "BASE" #x42415345)
(checkSum 6962C672) (offset #x00000430) (length: #x000001C8))
(Table 1 (tag "CFF " #x43464620)
(checkSum 0BDFCA60) (offset #x0034700C) (length: #x0080F295))
(Table 2 (tag "EBDT" #x45424454)
(checkSum E109E7DA) (offset #x00213B60) (length: #x001334AA))
(Table 3 (tag "EBLC" #x45424C43)
(checkSum 81D5AE36) (offset #x00002A58) (length: #x0001064C))
(Table 4 (tag "GPOS" #x47504F53)
(checkSum D6E4B8CC) (offset #x00036E04) (length: #x00015098))
(Table 5 (tag "GSUB" #x47535542)
...
- カーニング情報は↓らへんにはいってる。
- freetype で取り出せるのはおそらく 1 の
kern
テーブルのみ? しかも Apple 拡張フォーマットには対応してない。TTX は Apple 拡張対応してる。
libotf でカーニング情報を実際にとりだす
GPOS テーブルから取り出すのが正攻法っぽいのでそっからいく。(/Library/Fonts/ヒラギノ明朝 Pro W6.otf
には kerx
テーブルもあったけど、後方互換てきなこと?)
- Unicode から Glyph IDに変換。
-
GPOS
テーブルの ScriptList から処理対象 Glyph に対応する Script を選ぶ(←ここがよくわからんので、とりあえずリストのいちばん最初の Script にしとく。) - Script のなかから対応する LangSys を選ぶ。(←これもよくわからんけど基本 1 個しかないのでそれで。)
- LangSys の下に、対応してる Feature の FeatureList 内における index を含んだリストがあるので、それをなめてく。
-
kern
feature の lookup table index から lookup table 取得。 - lookup のなかの sub table のなかの coverage 構造体のなかに対象の Glyph があればカーニング値がふくまれてる。
- coverage 内の glyph の index が pair set の index と対応してる。
- pair set 内の pair value record をだーっとみていって、second glyph がカーニングペアの id だったらそれが実際のカーニング値。
って、書いててわけわかんなくなってきたけど、コードに直すと↓
#include <iostream>
#include "otf.h"
#include <ft2build.h>
#include FT_FREETYPE_H
int glyph_contains(OTF_Coverage *coverage, OTF_GlyphID glyph_id) {
if (coverage->CoverageFormat == 1) {
for (int i = 0; i < coverage->Count; i++) {
if (coverage->table.GlyphArray[i] == glyph_id) {
return i;
}
}
} else if (coverage->CoverageFormat == 2) {
for (int i = 0; i < coverage->Count; i++) {
OTF_RangeRecord *range = coverage->table.RangeRecord + i;
for (int j = range->Start; j <= range->End; j++) {
if (j == glyph_id) {
return j;
}
}
}
}
// ふくまれてない
return -1;
}
OTF_PairValueRecord *get_pair_set(OTF_LookupSubTableGPOS *sub_table, OTF_GlyphID left, OTF_GlyphID right) {
// printf("Coverage Format: %d, Count: %d, left: %d, right: %d\n", sub_table->Coverage.CoverageFormat,
// sub_table->Coverage.Count, glyph_contains(&sub_table->Coverage, left), glyph_contains(&sub_table->Coverage,
// right));
int pair_set_index = glyph_contains(&sub_table->Coverage, left);
if (pair_set_index < 0) {
// この sub table には left の情報無い
return NULL;
}
OTF_PairSet *pair_set = &sub_table->u.pair1.PairSet[pair_set_index];
for (int i = 0; i < pair_set->PairValueCount; i++) {
if (pair_set->PairValueRecord[i].SecondGlyph == right) {
return &pair_set->PairValueRecord[i];
}
}
// right との情報無い
return NULL;
}
int get_kerning_value(OTF *otf, OTF_GlyphString *gstring) {
char name[5];
OTF_Tag kern = OTF_tag("kern");
// OTF_Tag palt = OTF_tag("palt");
// よくわからんので最初の Script
OTF_LangSys *lang_sys = &otf->gpos->ScriptList.Script[0].DefaultLangSys;
for (int i = 0; i < lang_sys->FeatureCount; i++) {
int featureIndex = lang_sys->FeatureIndex[i];
OTF_Feature *feature = &otf->gpos->FeatureList.Feature[featureIndex];
OTF_tag_name(feature->FeatureTag, name);
// printf("%d: %d, %s\n", i, featureIndex, name);
if (feature->FeatureTag != kern) continue;
// LookupListIndex ながさ 1 なので最初のやつ。複数はいってることってあるの?
unsigned int lookupIndex = feature->LookupListIndex[0];
// printf("%i: %s, %d\n", i, name, lookupIndex);
// このへんもリストの最初だけ
OTF_PairValueRecord *value = get_pair_set(&otf->gpos->LookupList.Lookup[lookupIndex].SubTable.gpos[0], gstring->glyphs[0].glyph_id, gstring->glyphs[1].glyph_id);
if (value) {
// XAdvance 以外に値はいってることあるの?
return value->Value1.XAdvance;
}
}
return 0;
}
int main(int argc, const char *argv[]) {
OTF *otf = OTF_open("/Library/Fonts/ヒラギノ明朝 Pro W6.otf");
OTF_get_table(otf, "GPOS");
OTF_GlyphString gstring;
gstring.size = 2;
gstring.used = 2;
gstring.glyphs = (OTF_Glyph *)calloc(gstring.size, sizeof(OTF_Glyph));
gstring.glyphs[0].c = 0x3042; // あ
gstring.glyphs[1].c = 0x3062; // ぢ
OTF_drive_cmap(otf, &gstring);
printf("Left glyph=0x%04x, Right glyph=0x%04x\n", gstring.glyphs[0].glyph_id, gstring.glyphs[1].glyph_id);
int kerning_value = get_kerning_value(otf, &gstring);
printf("Kerning value=%d\n", kerning_value);
free(gstring.glyphs);
OTF_close(otf);
return 0;
}
んで、これではまだ正確にポジショニングできなくて、kern
feature の解説のとこに、
Feature interaction: If kern is activated, palt must also be activated if it exists. If palt is activated, there is no requirement that kern must also be activated.
ってあるように、palt
があるならそれもちゃんと処理しないといけない。OTF 複雑すぎ、、、