Skip to content

✨ 文字幅計算Libを追加 - #2307

Draft
ChenCMD wants to merge 6 commits into
masterfrom
feat/text-measure-draft
Draft

ChenCMD wants to merge 6 commits into
masterfrom
feat/text-measure-draft

Conversation

@ChenCMD

@ChenCMD ChenCMD commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

文字列や静的なTextComponentの送り幅を、通常フォントとUnicodeフォント強制時の両方で計算できるようにします。lib:text/measure は文字列と太字指定を、lib:text/measure_component は装飾と文章を直接指定したtranslateを受け取り、小数の送り幅と切り上げ幅を返します。

両設定の幅は文字列を一度走査して計算します。default・uniformの未収録文字は、1 UTF-16コード単位あたり9px・太字加算0.5pxとして補完します。spaceの未収録文字、動的参照、未対応フォントや書式、計算範囲超過は失敗扱いです。成功・失敗にかかわらず作業領域と19個のTemporaryスコアを片付け、失敗時には途中の戻り値も削除します。

Minecraft 1.20.4と配布リソースパックから幅表を生成します。日本語、ラテン・ギリシャ・キリル文字、ルーン、ゲーム内文言に使う記号類と位置調整スペースを対象にし、共通値146を省略しました。表は7,935件・73,995バイトです。対象範囲外で補完値と幅が異なる文字には表の拡張が必要で、継続検査は #2312 で扱います。

幅表は lib_text_fonts: に分離し、日常的に書き換える lib: と同じ保存データに含めないようにします。core:load_once で版が変わった場合だけ、dummy引数を持つmacroから表を展開します。展開後は次のreloadまでmacroキャッシュに残りますが、表を展開しないreloadでは巨大なSNBTの解析済みcompoundを作りません。生成済み表のlint除外は、引用符が必須のキーにも警告が出るため維持し、workflowに理由を記載しています。

幅表の入力はTSB-ResourcePackの feat/bossbar-fonts のフォントです。配布フォントを変更したときは scripts/text/README.md の手順で再生成します。Assetから公開関数を使う際は、対応する公開宣言の生成が必要です。

Minecraft 1.20.4の実サーバーで53項目が成功しました。補助文字、負の小数の丸め、±65,536px、各設定の文字列内・部分間の正負オーバーフロー、途中失敗時の後片付けを含みます。補助文字とオーバーフローの一部は幅表を一時的に差し替えるfixtureで確認しています。初期処理が長いため、検証runnerのRCON待ち時間をローカルで15秒から60秒へ変更して実行しました。静的解析はエラー・警告0件、生成結果の再現性も確認しています。検証の詳細と再実行シナリオは docs/verification/text-measure.md にあります。クライアントの画素やJavaのヒープ量は測定していません。

@ChenCMD
ChenCMD force-pushed the feat/text-measure-draft branch from b0ffdef to 2891f21 Compare October 11, 2026 06:04

@EllaCoat EllaCoat left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文字幅計算 Lib のレビューです。Minecraft 1.20.4 のソース(デコンパイル)と照らし合わせながら、mcfunction を中心に読みました。

継続動作に影響する問題は見つかりませんでした。再帰する関数はどれも入力を消費してから自身を呼び出し、core/finish は成功・失敗のどちらの経路でも作業領域と一時スコアを片付けています。

軽量化の提案を 2 件、表の大きさについての相談を 1 件、テストと検証記録について 2 件、コメントしました。軽量化の効果は計測しておらず、コマンド数と件数から見積もった値です。

[テスト] シナリオで通っていない経路

  • サロゲートペア: U+10000 以上の文字が無いため、core/pair と core/pair_key.m、core/next で 2 コード単位を進める経路が実行されていません。uniform の表には絵文字が入っています。
  • 負の幅と太字: 負の幅は太字なしの 1 ケースだけです。±65536px の文字(U+F300・U+F301)、合計が負になるときの切り上げ、オーバーフローの検出(core/next と core/component/part)も通っていません。
  • 途中結果の削除: core/finish での Return の削除が効くのは、先頭の部分の結果を書いた後で、後ろの部分が失敗した場合です。失敗系のケースはどれも、収集の段階か先頭の部分で失敗しています。例えば {text:"A",extra:[{text:"한"}]} で確かめられます。
  • 失敗後の片付け: Work が片付いたかの検査は、成功したケースの後にしかありません。

Comment thread TheSkyBlessing/data/lib/functions/text/core/measure.mcfunction Outdated
Comment thread TheSkyBlessing/data/lib/functions/text/_index.d.mcfunction Outdated
Comment thread TheSkyBlessing/data/lib/functions/text/core/tables.mcfunction Outdated
Comment thread docs/verification/text-measure.md
data remove storage lib:text Work.UniformGlyph
execute store success score $TextValid Temporary run function lib:text/core/glyph.m with storage lib:text Work
execute unless score $TextValid Temporary matches 1 run return fail
execute unless data storage lib:text Work.Glyph unless data storage lib:text Work.UniformGlyph run function lib:text/core/pair

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WANT] 日本語の文字でも毎回 2 コード単位のキーで引き直す

146 を省いたことで、ひらがな・カタカナ・漢字・和文の句読点は、default と uniform のどちらの表にも無くなりました。そのため日本語の文字は、文字列の末尾を除いて毎回この行で core/pair に入り、2 コード単位のキーで引き直します。表にある 2 コード単位のキーは補助文字(絵文字)の 13 件だけなので、日本語の文字ではこの検索は必ず外れます。

1 文字あたりの負担は次のとおりです。

表にある文字 表に無い文字
実行するコマンド 約 46 約 60
マクロの展開 glyph.m 1 回 glyph.m 2 回、pair_key.m 1 回

マクロの展開結果のキャッシュは関数ごとに 8 件で、LRU で捨てられます(1.20.4 の MacroFunction)。n 文字の日本語では、glyph.m を 1 文字のキーで n 回、2 文字のキーで n-1 回展開します。そのため、重複の無い 5 文字以上の文字列は、同じ文字列を測り直しても毎回すべて解析し直します。例えば「ボスが出現した」では、13 回の展開がすべて解析し直しになります。2 文字のキーで引かなければ、8 文字まではキャッシュに収まる計算です。

結果は正しいので、影響は時間だけです。測る頻度が低ければ問題になりませんが、ボスバーなどで tick ごとに測る場合は無視できなくなります。

改善案として、値 146 を補助文字の先頭の目印に使う方法があります。uniform の表は 146 を省いているので、今は表から 146 が返ることがありません。

  • load の際に、表にある補助文字の先頭のコード単位(D83C・D83D・D83E の 3 種類)を、値 146 で uniform の表へ入れる。サロゲートは単独では関数ファイルに書けないので、表にある絵文字から set string ... 0 1 で切り出し、マクロでキーにする。
  • この行を、UniformGlyph が 146 のときだけ pair を呼ぶ形にする。
execute if data storage lib:text Work{UniformGlyph:146} run function lib:text/core/pair

日本語の文字も、表にある文字と同じく 1 文字あたりのマクロの展開が 1 回で済み、今と同じく 146 で補われます。表に無い補助文字は、pair で見つからなければ目印の 146 が残るので、今と同じく 2 コード単位分の 146 になります。

なお今の経路では、表に無い文字の直後が " か \ だと、pair_key.m でエスケープが外れたキーができ、2 回目の glyph.m の展開が構文エラーになります。関数の中のエラーは表示されず、結果も 146 で補われるので正しいです。上の案にすれば、この経路も通らなくなります。

Comment on lines +7 to +8
function lib:text/core/tables.m {_:""}
data modify storage lib_text_fonts: Version set value "45ce851d4a644def5c8df7be1b9cf495e7dab15301250bdc6fad15eec4078029"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WANT] 表の展開に失敗しても気付けない

tables.m のマクロ行に構文の誤りがあると、7 行目の展開は失敗します。それでも 8 行目で Version が書かれ、以降の /reload では 6 行目で戻るので、表の内容が変わるまで展開し直しません。

この失敗はどこにも表示されません。load の関数は出力を抑えた送信元で実行され、入れ子の関数のエラーはログにも出ません。tables.m は lint の対象外で、マクロ行は /reload でも解析されません。その結果、新しいワールドでは表が空のまま、既存のワールドでは古い表のまま、measure は 146 で補って成功を返します。新しいワールドでシナリオを実行すれば、最初のステップの tableLoaded で気付けます。

起きるのは生成した表が壊れていた場合だけなので、優先度は低めです。直すなら、展開の前に Version を消し、execute store success で展開の成否を受け取って、成功したときだけ書く形が考えられます。展開に失敗すると 0 が入ります。あわせて Version が無いときに measure を失敗させれば、壊れた表を 9px の成功ではなく失敗として受け取れます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants