動的テキスト Glyph Push 拡張

このドキュメントは、glyph_push プロトコル拡張のバージョン 1 を定義します。この拡張を使うと、デバイスにインストールされたテキストフォントにないビットマップグリフを、サーバーが送れます。対応機能の通知と受信した JSON の処理は共通のプロトコル層で実装されているため、WebSocket と MQTT/UDP のどちらのトランスポートにも同じように適用されます。

この拡張が補うのはテキストの描画だけです。それを含むメッセージのテキスト、TTS 音声、STT の意味は変えません。

1. 対応機能の通知

デバイスは、クライアントの hello メッセージで対応を通知します。

{
  "type": "hello",
  "version": 1,
  "features": {
    "mcp": true,
    "glyph_push": true
  },
  "text_font": {
    "bundle": "noto-v1",
    "charset": "common",
    "size": 20,
    "bpp": 4
  }
}

features.glyph_push は、この拡張に対応していることを示します。サーバーは、この値がない場合や false の場合を、非対応として扱わなければなりません。送信する各ペイロードの v フィールドが、拡張のバージョンを表します。

text_font オブジェクトは、デバイスにインストールされているフォントデータを正確に記述します。

フィールド型意味
bundlestringフォントバンドルを明示する識別子。グリフのメトリクス、描画の挙動、文字セット、通信上の互換性が変わると変更されます。
charsetstringインストール済みの文字セット。バージョン 1 のデバイスは basic か common を報告します。
sizenumberファームウェアが使うテキストフォントのピクセルプロファイル。
bppnumberテキストフォントのビットマップの 1 ピクセルあたりのビット数。現在は 1 か 4。

basic は、ファームウェアにリンクされているフォントです。標準の小智 assets は、assets パーティションから common フォントを読み込んだあと、common を報告します。サーバーは、ボードの型番から推測するのではなく、デバイスごとの hello メッセージの値を使わなければなりません。

OTA の assets パッケージは、テキストフォントを、サイズ、bpp、文字セット、フォントファミリーの異なるものに置き換えられます。ファームウェアは、構造的に有効な CBIN フォントであれば何でも読み込みます。パッケージが完全な text_font_meta フィールドも提供している場合、デバイスはその実行時の有効な値を通知し、グリフの送信をその値に照らして検証します。互換性のあるグリフのメタデータがない旧式またはカスタムのパッケージは、カスタムのフォント、絵文字、色、背景をそのまま使い続けますが、glyph_push: false を通知し、text_font を省略します。これにより、テーマのカスタマイズを制限せずに、互換性のないフォールバックグリフを防ぎます。

2. サーバーのグリフペイロード

サーバーは、サーバーからデバイスへの次のいずれかのメッセージに、glyph_push オブジェクトを付けられます。

  • "type": "tts" かつ "state": "sentence_start" の TTS メッセージ。
  • "type": "stt" の STT メッセージ。

例:

{
  "type": "tts",
  "state": "sentence_start",
  "text": "𠮷野家",
  "glyph_push": {
    "v": 1,
    "bundle": "noto-v1",
    "size": 20,
    "bpp": 4,
    "glyphs": [
      {
        "codepoint": 134071,
        "adv_w": 320,
        "box_w": 20,
        "box_h": 20,
        "ofs_x": 0,
        "ofs_y": 0,
        "bitmap": "<base64-encoded bitmap>"
      }
    ]
  }
}

ペイロードのヘッダーは、デバイスの対応機能と完全に一致していなければなりません。

フィールド要件
v1 でなければならない。
bundletext_font.bundle と等しくなければならない。
sizetext_font.size と等しくなければならない。
bpptext_font.bpp と等しくなければならない。
glyphs送信するグリフの一部のバッチ。最大 64 件。

各項目は、LVGL ネイティブのビットマップフォントのメトリクスを使います。

フィールド意味
codepoint1 から 0x10FFFF までの Unicode コードポイント。
adv_w小数部 4 ビットの LVGL 固定小数点単位での水平方向の送り幅(1 ピクセルは 16 単位)。
box_w, box_hビットマップの寸法(ピクセル)。どちらも 0 から 64 まで。
ofs_x, ofs_yテキストのベースラインとカーソル位置を基準にした、符号付き 16 ビットのグリフのオフセット。
bitmap非圧縮の LVGL プレーンビットマップの Base64 エンコード。

デコード後のビットマップの長さは、次の値と正確に一致しなければなりません。

ceil(box_w * box_h * bpp / 8)

ビットマップは、対応する Noto フルバンドルの CBIN フォントと同じ、プレーンでストライドが 0 のレイアウトでなければなりません。サーバーは、リクエスト時に無関係なフォントをラスタライズするのではなく、その CBIN プロファイルからビットマップとメトリクスを直接取り出して転送するべきです。

1 つのペイロードに含まれる、デコード後のビットマップの長さの合計は 64 KiB を超えてはなりません。ヘッダー、グリフ、ビットマップのどれか 1 つでも無効だと、デバイスはグリフのペイロード全体を拒否しますが、メッセージのテキストはインストール済みのフォントで表示します。PSRAM を搭載したデバイスは、それ以前のメッセージでキャッシュしたフォールバックグリフも使えます。

3. サーバーの選択アルゴリズム

glyph_push: true を通知する接続ごとに、サーバーは次のようにするべきです。

  1. text_font.bundle が示すフルフォントバンドルを特定する。
  2. text_font.size と text_font.bpp に合う CBIN プロファイルを選ぶ。
  3. メッセージのテキストを Unicode のコードポイントにデコードする。
  4. 制御文字、重複、text_font.charset にすでに含まれるコードポイントを取り除く。
  5. 残ったグリフをフルバンドルから取り出す。
  6. メッセージごとの上限を適用し、テキストメッセージに glyph_push オブジェクトを 1 つ付ける。
  7. 不足しているグリフがなければ glyph_push を省略する。

インストール済みのテキストフォントは、動的なフォールバックフォントより先に検索されます。したがって、送信したグリフは不足しているコードポイントを埋めるだけで、basic や common のグリフを上書きしません。

フルフォントバンドルは、サーバープロセス内のすべてのデバイス接続で共有できます。接続ごとの処理は、対応機能のタプル (bundle, charset, size, bpp) を使って、不足しているグリフと読み込むプロファイルを選ぶことだけです。

4. デバイスのキャッシュの挙動

1 つのメッセージに含まれるグリフはすべて先に挿入され、そのあとフォールバックフォントを 1 回だけ再構築します。デバイスがグリフ 1 つごとにフォントを再構築することはありません。

PSRAM を初期化済みのデバイスでは:

  • ビットマップ、cmap、ディスクリプタ、キャッシュ項目の領域は PSRAM に確保されます。
  • グリフはメッセージをまたいで保持されます。
  • キャッシュには最大 256 グリフ、デコード後のビットマップデータで 64 KiB まで保持できます。
  • 上限を超えると、挿入または更新が最も古い項目から削除されます。

PSRAM のないデバイスでは:

  • 領域は内蔵 RAM を使います。
  • 現在のメッセージのグリフのバッチだけを保持します。
  • 次のテキストメッセージが、前のバッチを置き換えるか消去します。

この違いはプロトコルには影響しません。サーバーは、デバイスが PSRAM を持つかどうかを知らなくても、各メッセージに必要なグリフを送れます。

5. 互換性とバージョン管理

次のいずれかに当てはまるとき、サーバーはグリフを送ってはなりません。

  • features.glyph_push がない、またはサーバーが対応していない。
  • 通知されたバンドルをサーバーが持っていない。
  • 通知された size と bpp に合うフルフォントのプロファイルがない。
  • グリフデータがバージョン 1 の検証ルールを満たせない。

フォールバックは自動です。glyph_push のないメッセージと、グリフのペイロードが拒否されたメッセージは、どちらも通常どおり処理されます。

フォントジェネレーターがメトリクス、ビットマップのレイアウト、元のフォント、文字セット、描画の挙動を変更するときは、新しい明示的なバンドル識別子を発行してください。size と bpp がたまたま一致していても、あるバンドルのグリフを別のバンドルの識別子で配信してはいけません。

6. セキュリティ要件

グリフのペイロードは、信頼できないネットワーク入力です。実装は、稼働中のフォントを変更する前にペイロード全体を検証し、項目数とデコード後のサイズの両方に上限を設け、Base64 のデコード後の長さを確認し、無効なコードポイントやメトリクスを拒否しなければなりません。サーバーも、メッセージごとの自身の処理量に上限を設け、通知された charset がすでに含んでいるグリフを送らないようにするべきです。

出典:78/xiaozhi-esp32 の docs/glyph-push.md