LoginSignup
32

More than 5 years have passed since last update.

OpenType フォントのカーニング情報をぶっこぬく

Posted at

OpenType フォーマットのフォントファイルからカーニング情報を取り出したい事情があっていろいろ調べた作業ログ。

  • フォントファイルをいじるときにつかう定番ライブラリ freetype 2.6 では OpenType のカーニング情報がひきだせなかったので(フォントにもよると思うけど)、libotf (0.9.13) で OTF ファイルを解析して取り出す。
  • OTF のファイルフォーマットは Microsoft Typography - OpenType Specification にまとまってる。
  • 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)
...

libotf でカーニング情報を実際にとりだす

GPOS テーブルから取り出すのが正攻法っぽいのでそっからいく。(/Library/Fonts/ヒラギノ明朝 Pro W6.otf には kerx テーブルもあったけど、後方互換てきなこと?)

  1. Unicode から Glyph IDに変換。
  2. GPOS テーブルの ScriptList から処理対象 Glyph に対応する Script を選ぶ(←ここがよくわからんので、とりあえずリストのいちばん最初の Script にしとく。)
  3. Script のなかから対応する LangSys を選ぶ。(←これもよくわからんけど基本 1 個しかないのでそれで。)
  4. LangSys の下に、対応してる Feature の FeatureList 内における index を含んだリストがあるので、それをなめてく。
  5. kern feature の lookup table index から lookup table 取得。
  6. lookup のなかの sub table のなかの coverage 構造体のなかに対象の Glyph があればカーニング値がふくまれてる。
  7. coverage 内の glyph の index が pair set の index と対応してる。
  8. 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 複雑すぎ、、、

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32