コンテンツにスキップ

フォントシステム

吉里吉里Z のフォントまわり (登録・名前解決・描画・検索) の総合ガイドです。 フォントの用意から登録手順、各 API の使い分けまでをここにまとめています。

全体構成

  フォントファイル (.ttf/.otf/.ttc)
    │  ①exe 埋め込み (resource/)      ②data 外だし + fonts.json (宣言・遅延)
    │  ③実行時登録 (Font.addFont 等)  ④システムフォント (Windows GDI)
    ▼
  ┌───────────────────────────────────────────────┐
  │ FontSystem (名前→ストレージ対応・遅延ロード)  │←─ fonts.json
  │ FontStream (共有オンメモリバッファ / 1フォント1部) │
  └───────────────┬───────────────────────────────┘
                  ▼
  ┌───────────────────────────────────────────────┐
  │ glyphware (統一フォントエンジン:               │
  │  FreeType + HarfBuzz + BiDi / レジストリ・検索) │
  └───┬───────────────┬───────────────┬───────────┘
      ▼               ▼               ▼
  Layer.drawText   Layer.draw     ThorVG テキスト
  (ラスタライザ     GlyphwareText  (Elements UI /
   3種切替)        (シェイピング)   layerExVector)
  • フォントのバイトデータは FontStream 共有バッファに 1 フォント 1 部だけ 読み込まれ、drawText・Elements UI・layerExVector・プラグインのすべてが同じ バッファを共有します (XP3 内フォント対応・オンメモリ)。
  • グリフ生成・シェイピング・メタデータ検索は統一フォントエンジン glyphware に集約されています。従来の GDI / FreeType ラスタライザも 互換のためそのまま残っています。

ラスタライザ

Layer.drawText の文字画像を作るエンジンは Font.rasterizer (静的プロパティ) で切り替えられます。

Windows ネイティブ版 (WINVER) SDL 版ほか
0 FreeType FreeType (既定)
1 GDI (既定) glyphware
2 glyphware -
  • 既定値は従来互換のまま (WINVER=GDI / その他=FreeType) です。
  • glyphware ラスタライザは classic とレイアウト互換 (セル送り・影・縁取り・ 下線/取消線・VS15/16 など) を保ちながら、フォールバック連鎖・カラー絵文字・ 合成 bold/italic に対応します。既知の差分: アンチエイリアス指定 off でも 常に AA 描画になります (font.hinting / font.antialiased は無視)。

フォントの用意と登録手順

① exe 埋め込み (resource/)

エンジンの resource/ フォルダに置いた .ttf/.otf はビルド時に実行ファイルへ 埋め込まれ、起動時から使用できます (既定同梱: Noto Sans JP / Roboto / Noto Emoji / elements_basic)。ゲーム側で増やすものではなく、エンジン既定 フォントの置き場所です。埋め込みフォントはストレージ名 resource://./ファイル名 でもアクセスできます。

同梱フォントは実行ファイルサイズの大半を占めます (Noto Sans JP 4.3MB / Noto Emoji 1.9MB)。ゲーム側で自前のフォントを供給する構成では、ビルド時に -DKRKRZ_EMBED_BUNDLED_FONTS=OFF を渡すとこの 2 本を埋め込まなくなり、 実行ファイルが約 6.2MB 小さくなります (Roboto / elements_basic は Elements の 既定テーマが参照するので残ります)。任意のファイルを外すには -DKRKRZ_RESOURCE_EXCLUDE="ファイル名;ファイル名" を使います。 外した場合、日本語・絵文字の描画に使うフォントはゲーム側で用意してください。

② data 外だし + fonts.json (推奨)

大きいフォント (カラー絵文字・CJK バリエーション等) は data フォルダに置き、 fonts.json で宣言します。起動時には名前だけ登録され、実ファイルは 初回使用時に遅延ロードされるため起動時間・メモリに影響しません。

{
  "version": 1,
  "fonts": [
    { "file": "fonts/notocoloremoji.ttf",
      "family": "Noto Color Emoji",
      "subfamily": "Regular",
      "aliases": ["Noto Color Emoji Regular"],
      "weight": 400,
      "scripts": ["Zsye"],
      "flags": ["emoji", "color"],
      "ranges": [[9728, 9983], [126976, 129791]] }
  ]
}
  • fonts.jsondata フォルダ (プロジェクトルート) 直下に置きます。
  • file 以外はすべて任意です。subfamily / fullName / postScriptName / weight (100-900) / width (1-9) / italic / faceIndex / languages / aliases / flags (emoji color monospace) / ranges (収録コード ポイント区間) を宣言できます。
  • バリアブルフォントinstance (fvar named instance 名) または axes (軸値の直書き) で「名前 = 特定の軸設定」を宣言できます。同じファイルを別名 + 別軸で複数宣言できます (下の「バリアブルフォント」節)。

{ "file": "myfont.ttf", "family": "MyFont SemiBold", "instance": "SemiBold" }
{ "file": "myfont.ttf", "family": "MyFont Narrow",   "axes": { "wdth": 75 } }
- 宣言した scripts / ranges / スタイルは Font.queryFontsフォントファイルを開かずに検索へ使います。 - 生成ツールで自動生成できます (要 pip install fonttools):

python tools/fontgen/gen_fonts_json.py \
    --fonts-dir <データ>/fonts --root <データ> --out <データ>/fonts.json

生成器は family を純粋な family 名にし、classic ラスタライザの face 名規約 (「family subfamily」連結、例 "Noto Color Emoji Regular") を aliases に 出力するため、どちらの名前でも解決できます。

同梱例: コアデモの src/core/data/fonts には Noto Color Emoji に加えて RTL 用の Noto Sans Arabic / Noto Sans Hebrew を同梱しています (SDL 版含む全プラットフォームでアラビア語/ヘブライ語が同一表示)。他の スクリプトも Noto ファミリ (https://notofonts.github.io/) から取得すると 埋め込みの Noto Sans JP / Roboto と見た目が揃います (候補: Noto Sans Thai / Devanagari / KR / SC / TC など。詳細は src/core/data/fonts/README.md)。

③ 実行時登録 (スクリプト)

  • Font.addFont(storage) — フォントファイルを 即時ロードして登録し、収録フェイス名の配列を返します。
  • Font.registerFontFile(storage[, family])family を指定すると ファイルを開かない遅延登録 (fonts.json 1 エントリ 相当) になります。省略時は addFont と同じ即時ロードです。
Font.addFont("mygame_font.ttf");                       // 即時ロード
Font.registerFontFile("fonts/big_cjk.ttf", "MyCJK");   // 遅延 (初回使用時に読む)

④ システムフォント (Windows ネイティブ版)

WINVER では OS にインストール済みのフォントをフォント名だけで使えます (GDI 名前解決)。glyphware 系の経路 (rasterizer=2 / drawShapedText / Elements) でも "メイリオ" "MS PGothic" 等の名前がそのまま解決され、 TTC のフェイス番号も正しく選択されます。addFont プラグインで登録した 埋め込みフォントも同様に名前で使えます。

フォント名の規約と解決順序

Font.facefontKey に書いた名前 (カンマ区切りでフォールバック連鎖) は、次の順で解決されます:

  1. fonts.json / registerFontFile の宣言名 (family / aliases) → ストレージ
  2. ストレージパス (fonts/foo.ttfresource://./… など実在するもの)
  3. (WINVER) インストール済み GDI フォント名 (addFont 登録分を含む)
  4. 解決できない名前は既定フェイスへフォールバック

classic (FreeType) の face 名は「family subfamily」連結 (例 "Noto Sans JP Regular") である点に注意してください。fonts.json 生成器が連結名を alias に 出すので、通常はどちらの表記でも動きます。

絵文字

Font.emojiMode (0=無効 / 1=モノクロ / 2=カラー) で絵文字グリフの扱いを選びます。

  • カラー絵文字フォント (既定 "Noto Color Emoji") は fonts.json の外だし宣言が 前提です。未配置の場合は同梱モノクロ絵文字 (Noto Emoji) へ自動フォール バックします (豆腐にはなりません)。
  • 既定フォント名は Font.emojiFaceName / Font.colorEmojiFaceName で変更可能。
  • WINVER の既定ラスタライザ (GDI) は絵文字グリフを持たないため、 Font.rasterizer = 0 (FreeType) または = 2 (glyphware) にして使います。
  • VS15 (テキスト表示) / VS16 (絵文字表示) のバリエーションセレクタに対応します。

フォント検索とメタデータ

登録済みフォントは Font.queryFonts で リッチ検索できます (条件はすべて省略可、結果はランク順の辞書配列):

// カラー絵文字フォントを探す
var r = Font.queryFonts(%[ containsText : "😀", color : 1 ]);
// 日本語を収録した太字を探す
var r = Font.queryFonts(%[ containsText : "あ", weight : 700 ]);

単一フォントの SFNT メタデータは Font.getFontInfo で取得できます (family / subfamily / weight / color 等)。fonts.json で 宣言済みの情報はフォントを開かずに応答します。

バリアブルフォント (可変軸)

可変軸 (fvar) を持つフォントは、TJS のフォントパラメータから軸を指定できます。 効くのは glyphware 経路Layer.drawShapedText 系 (常時) と、Font.rasterizer = 2 のときの Layer.drawText — のみで、旧 FreeType / GDI ラスタライザでは無視されます (起動後 1 回警告が出ます)。

Font.rasterizer = 2;                     // drawText で使う場合
layer.font.weight = 700;                 // wght 軸 (100-900、void で解除)
layer.font.variations = "wdth=87.5";     // 汎用軸指定 (複数は "wght=700,wdth=75")
var axes = Font.getVarAxes("MyFont");    // 利用できる軸の一覧
Font.defaultUseVarStyle = true;          // bold/italic を軸で表現 (オプトイン)
  • Font.weightwght 軸として効き、 Font.variationswght を明示した場合は そちらが優先です。適用されるのはフォールバック連鎖の各 face が実際に持つ 同名軸だけなので、日本語フォント + 絵文字フォントのような軸構成の違う連鎖でも 安全です。
  • fonts.jsoninstance / axes 宣言で「名前 = 特定の軸設定」を作れます (上の fonts.json 節)。名前を指すだけで軸込みのフォントが使えるため、 スクリプト側で weight / variations に触れる必要がありません。
  • Font.defaultUseVarStyle を真にすると、 bold / italic を軸 (wght=700 / slnt=-10 / ital=1) で表現できるフォントでは 合成ボールド / イタリックの代わりに軸を使います (既定は偽)。
  • 軸の照会は Font.getVarAxes / Font.getFontInfo (axes / namedInstances) で行えます。
  • #tag=val サフィックス表記: フォント名に #tag=val[,tag=val...] を 後置すると、その名前が指すフォントの軸インスタンスを表します (例 "MyFont#wght=700""MyFont#wght=700,wdth=75")。 Font.face のフォールバック連鎖の各要素、 Elements 画面 JSON の "font" (label / text_area とも。折り返し計算も同じ インスタンス) で一様に使えます。Font.variations がフォント全体 (連鎖の 全 face) に効くのに対し、こちらは連鎖の要素単位で効きます。
  • 無指定時の既定は wght=400 相当: 可変フォントを wght 未指定で参照した 場合、fvar の既定インスタンスではなく wght=400 で表示されます (CSS の font-weight 既定と同じ規則)。既定が Thin の VF (Noto VF 等) も、VF 1 本の 登録だけで無指定が Regular 相当に読めます。既定を変えたいときは Font.setDefaultVariations で 名前単位に登録します (明示指定・fonts.json 宣言が常に勝ちます)。

多言語シェイピング描画 (glyphware)

Layer.drawText は従来どおり「1 文字ずつのセル送り」ですが、 Layer.drawShapedTextHarfBuzz シェイピング + BiDi による本格的な多言語 1 行描画を行います:

  • アラビア文字の連結・合字・カーニング、ヘブライ/アラビアの右→左 (BiDi)
  • コードポイント単位のフォールバック連鎖 (Font.face カンマ区切り)
  • カラー絵文字、合成 bold/italic、下線/取消線、回転 (angle)
  • 描画属性は Font オブジェクト 1 個で渡します (void = レイヤ自身の font / 文字列 = face のみ差し替え)

Layer.drawShapedTextArea は 矩形内への簡易折り返し描画です。\n の明示改行、英語等のワード単位 折り返し、日本語の文字単位折り返し (行頭/行末の簡易禁則付き)、行揃え (左/中央/右)、行間調整に対応します。

count 引数 (drawShapedText / drawShapedTextArea 共通) に 0 以上を渡すと 先頭 count 「文字」だけを描画でき、タイプライタ表示に使えます。この 「文字」は描画時に一塊として扱われるクラスタ単位 (合字・結合文字・ 絵文字 ZWJ シーケンスで 1) で、総数は Layer.shapedTextCount で取得します。 全文をシェイピング/折り返し確定してから先頭部分だけを描くため、表示途中で 字形や折り返し位置が変化 (リフロー) しません。

計測は Layer.measureShapedText で行います (インク境界と ascent/descent、クラスタ数を返します)。

動作サンプル: コアデモギャラリー (src/core/data) の 「多言語シェイピング (glyphware)」シーンで、アラビア語/ヘブライ語 (RTL)・ BiDi 混在・絵文字混在・計測・矩形内折り返し・タイプライタ表示 (RTL 混在文の 自動再生 — RTL 区間が論理順で 1 クラスタずつ現れます) の実例を確認できます。

縦組み (日本語縦書き)

Layer.drawVerticalTextArea は 矩形へ縦組み (縦書き) で本文を流し込みます。横組みの drawShapedTextArea とは別経路で、日本語組版規則 (JLReq) のアキ量表に従って 組みます:

  • 和文は正立・欧文/数字は横倒しに組み分け (orientation で全正立/全横倒しにも変更可)
  • 縦字形 (vert / vrt2) と縦アドバンス (vmtx / VORG) を使うので、 括弧・長音符・句読点が縦向きの字形になる
  • 約物 (句読点・括弧類) の詰め、和欧間のアキ
  • 行頭行末禁則、追い込み / 追い出し、行末揃え、句読点のぶら下げ (任意)

列の長さは矩形の height、列の送りは「フォントサイズ + lineSpacing」です。 既定は右から左 (1 列目が矩形の右端) で、verticalLr で左から右にできます。 矩形に入りきらない列は描画されません。フォント指定 (font 引数) と色の扱いは drawShapedText と共通なので、横組みと同じ Font オブジェクトで見た目が揃います。

var opt = %[ "hanging" => true, "letterSpacing" => 0.05 ];
// (x, y, width, height, text, color, font, count, lineSpacing, options)
var r = layer.drawVerticalTextArea(20, 20, 400, 560, text, 0x000000,
                                   font, -1, 6, opt);
// r.lines = 組んだ列数 / r.width = 使った幅 / r.count = 描いたクラスタ数

options の全キーと既定値は Layer.drawVerticalTextArea を 参照してください (orientation / verticalLr / punctuation / latinGap / hanging / justify / letterSpacing)。

count はタイプライタ表示用で、横組みと同じく行分割を全文で確定してから 制限を掛けるので途中でリフローしません。ただし数える単位は「描画される文字」で、 欧文の単語間空白は数えないため Layer.shapedTextCount の値とは 一致しないことがあります。総数は戻り値の totalCount を使ってください。

描画せずに列数や必要な幅だけ知りたいときは Layer.measureVerticalTextArea を使います (引数から x / y / color を除いたもので、戻り値は同じ)。

未対応: ルビ・縦中横・圏点・割注・字取り、段組、下線 / 打ち消し線、 Font.angle。現状の対応範囲は本文の組版のみです。

動作サンプル: コアデモギャラリー (src/core/data) の 「縦組み (drawVerticalTextArea)」シーンで、本文の流し込み・options のキー切替 (ぶら下げ / 行末揃え / 約物の詰め / 列送りの向き / 字間)・orientation による 和欧の組み分け・同じ本文を横組みで並べた対比・タイプライタ表示 (count 制限) の 実例を確認できます。

UI 系 (Elements) と layerExVector のフォント

  • Elements ダイアログ (ダイアログ) のテキストは glyphware で 描画されます。テーマフォントには埋め込みフォント (Roboto / Noto Sans JP / Noto Emoji) が自動登録され、追加フォントは ElementsDialog.registerFont(family, storage[, weight, slant, stretch]) / ElementsDialog.registerFontDir(dir) で登録します。ストレージパス (XP3 内・ resource:// 含む) をそのまま渡せます。registerFontDir登録できた 本数を返すので、if (ElementsDialog.registerFontDir(dir) == 0) { ... } で 「パッケージしたらフォントが 1 本も無い」事故を検知できます (0 本のときは エンジンも警告ログを出します)。XP3 に固めたフォントを指すときは "data.xp3>font/" のようにアーカイブを明示してください (addAutoPath でのマウントはファイル検索用で、ディレクトリ列挙はできません)。 OS のパス ("D:/game/font/") をそのまま渡すと 0 本になります。多言語 UI で表示言語ごとに フォント (JP/TC/SC 等) を自動で切り替えるには ElementsDialog.fontLanguages (言語連動フォント置換) を使います (ダイアログ の「言語連動フォント置換」節参照)。
  • 矩形への流し込みは折り返しロジックも共有します。ダイアログの text_area ウィジェットは上記 drawShapedTextArea と同じ折り返し・ 行頭行末禁則・クラスタ単位の文字送りを通るため、同じ本文・同じ幅なら レイヤ描画と Elements で改行位置が一致します (従来からある text_box は互換のため素朴な折り返しのまま)。
  • richtext プラグイン (RichText) の組版は minikin ですが、フォントの実体取得とグリフのラスタライズは本体の glyphware を通ります。 face とフォントのバイト列を本体と共有するため、Layer.drawText や Elements と 同じフォント・同じ見た目になります ( 可変フォントの軸を使う場合だけは、 共有 face の状態を汚さないよう専用の face が開かれます )。
  • layerExVector プラグイン (GdiPlus.loadFont(storage, name)) も同じ エンジンを共有します。resource://./notosansjp-regular.otf のように 本体埋め込みフォントを指定でき、フォントを同梱しなくてもアウトライン 文字を描画できます。
  • どの経路も同一フォントは FontStream の共有バッファ 1 部を使うため、 複数箇所で同じフォントを使ってもメモリは増えません。

プラグインからの利用 (C++)

tp_stub にフォントサービス API (TVPCreateFontStream / TVPFontAcquireFace / TVPFontShapeLine / TVPFontQueryFaces ほか) が公開されており、プラグインも 共有バッファ・グリフ供給・シェイピング・検索を利用できます。詳細はエンジン リポジトリの src/core/doc/FontEngine.md (内部実装ノート) を参照してください。

トラブルシューティング

  • フォント名を指定したのに既定フォントで描かれる — 名前が未登録です。 Font.getFontInfo(名前) が void を返すか確認し、fonts.json の宣言名 / aliases、または Font.addFont の戻り値のフェイス名を使ってください。
  • カラー絵文字が白黒になる — カラー絵文字フォントが未配置 (fonts.json 未宣言) だとモノクロへフォールバックします。Font.queryFonts(%[color:1]) で配置状況を確認できます。
  • WINVER で絵文字が出ない — 既定ラスタライザ (GDI) は絵文字非対応です。 Font.rasterizer = 2 (または 0) にしてください。
  • "MS Gothic" の幅が経路によって違う — FreeType の名前解決は英名 TTC で 別フェイス (PGothic) に化けることがあります。実 family 名 ("MS ゴシック" 等) を使うと全経路で一致します。

関連資料