ガイド 第23

日本語タイポグラフィ

BudouX vs titleBreak・punct fallback woff

@event/session-card@event/visuals

カードのタイトル / 役職 / 氏名の改行は、ブラウザの text-wrap: balance + word-break: auto-phrase 任せをやめて@event/session-cardengine/text.ts が決定論的に解く。CJK は辞書ヒューリスティックで文節途中に割れることがあり(例「巨額の資金調」/「達、…」)、改行位置が実行のたびにブレるためである。ここで実装されているのは BudouX ではなく、良い境界 + 行長均しの DP だ。BudouX / bundled-woff は @event/visuals未実装の計画(deferred)として JSDoc に明記されている(後述)。

visualWidth — 幅の通貨(CJK=1.0 / ASCII=0.55 em)

px 実測を使わず、visualWidth(text) が CJK を 1.0 em、ASCII を 0.55 em として概算する。server / client / satori のどこでも同じ答えが出る(決定論)。maxPerLine = widthBudget / (fontSize * mult) が「その fontSize で 1 行に入る visualWidth 上限」になる。

candidateCuts — 改行境界のペナルティ表

candidateCuts(text) が改行可能位置を品質ペナルティ付きで列挙する(低いほど良い):

境界penalty判定に使う定数
スペース区切り0isSpace(prev)
句読点直後2PUNCT_AFTER = 、。,.・…〜~:://
閉じ括弧直後3CLOSE_AFTER = ))」』〉》〕}]>
開き括弧直前3OPEN_BRACKETS
文節境界(辞書なし・最後の手段)9上記いずれでもない CJK 境界

禁則も同時に守る: 行頭に閉じ括弧 / 句読点 / 中黒 / 長音 / 小書き仮名を置かない(LINE_START_FORBIDDEN)、開き括弧で行を終えない(OPEN_BRACKETS が prev)、英単語(ASCII)の途中で割らない(isAsciiWord 連続)。許可する境界品質の上限は 2 段: PENALTY_CLEAN = 3(スペース / 句読点 / 括弧まで)と PENALTY_PHRASE = 9(文節も許可)。

breakTitleLines — 行長均し DP

breakTitleLines(text, maxPerLine, maxLines, maxPenalty) が本体。1 行に収まれば [text]maxPenalty 以下の境界で割り切れなければ null(呼び出し側でフォント縮小へ)。DP は各分割の raggedness((maxPerLine - w) 2)+ 境界ペナルティ × PEN_W(36)** を最小化する。最終行は短くても罰しない。

fitTitleLayout — フォント × 改行の確定

import { fitTitleLayout, visualWidth } from "@event/session-card";

const { fontSize, lines } = fitTitleLayout(title, widthBudget, baseFontSize, 3);
// 優先順:
//   1. baseFontSize で 1 行に入るならそのまま
//   2. clean 境界(<=3)で maxLines 行に割れる最大フォント(1→minScale=0.5 を step 0.04 で下降)
//   3. clean 不可 → phrase 境界(9)も許可して同じ探索
//   4. 極端な長文 → 面積 fit(floor(maxLines * widthBudget * slack / (visualWidth * mult)))で必ず収める

「2 行に小さく詰める / 切る」のではなく行数を増やして読めるフォントを保つのが方針。slack=1.08 は letter-spacing -0.05em + palt 文字詰めで実幅が visualWidth の約 0.9 倍になるぶんの余裕。

役職 / 氏名の fit

  • fitRole — clean → phrase の 2 パス。予約高 fsRoleMax × 1.25 × 3 行 を超えない最大フォントを採る。goodBreakSegments(clean cut で割った最長セグメント)が縮小の下限を決め、「代表取締役」を「代表取 / 締役」と語中で割らない
  • fitSpeakerName — 1 行優先(floor = 基準 0.42 倍まで縮小して 1 行維持)。それでも入らない長名だけ splitNameForWrap(中黒・スペースの中央寄り境界)で 2 行に折る。英語名は forcedNameSize で 1 サイズに統一、CJK 名は個別 fit
  • hasCJK / CJK_RE/[぀-ヿ㐀-鿿ヲ-゚]/)で言語を判定し、overrideForLang は手動上書き(cardRoles 等)をページ言語と一致するときだけ採用する。pickLang は en 空欄で ja へフォールバック

タイトル前処理として stripPoweredBy(協賛クレジットを落として改行崩落を防ぐ)と parseManualLines\n 明示改行を行配列に解す)も同居する。

BudouX / bundled-woff は未実装(deferred)

章題の「BudouX」「punct fallback woff」は現状コードには無い@event/visuals/index.ts の JSDoc に「フォントファイル同梱(bundled-woff-first loader)と BudouX 文節分割(ZWSP 挿入)は Satori/OG 経路で必要になった時点で追加する。重い依存(budoux 等)は今は入れない」と明記されている。参照実装として ivs-cms の survey/fonts.ts(fontsourceWoff)/ jpSegment.ts(segmentJa)が挙がるが、移植は保留。つまり DOM カード = titleBreak DP(実装済)/ Satori・OG の BudouX + 同梱 woff = 設計のみ、という線引きで読むこと。