Repository navigation
bug: 日本語が中国語フォントで描画される(sans-serif の解決先が WenQuanYi Zen Hei になる) #161
Description
何を見つけたか
base コンテナでは、日本語が中国語のフォントで描画される。 総称ファミリの sans-serif そのものが中国語フォントへ解決される。
$ fc-match sans-serif
wqy-zenhei.ttc: "WenQuanYi Zen Hei" "Regular"
$ fc-match sans-serif:lang=ja
wqy-zenhei.ttc: "WenQuanYi Zen Hei" "Regular"
言語を ja と明示しても中国語フォントになる。 フォールバックの順序は次のとおりで、fonts-noto-cjk が明示的に入っているにもかかわらず、日本語フェイスは5番目である。
$ fc-match -s sans-serif:lang=ja | head -5
wqy-zenhei.ttc: "WenQuanYi Zen Hei" ← 中国語
ipagp.ttf: "IPAPGothic"
Loma.otf: "Loma" ← タイ語
FreeSans.ttf: "FreeSans"
NotoSansCJK-Regular.ttc: "Noto Sans CJK JP" ← ここでようやく日本語
日本語環境でよく指定される名前も、すべて中国語フォントへ落ちる。
| 指定 | 解決先(修正前) |
|---|---|
sans-serif |
WenQuanYi Zen Hei |
Noto Sans JP |
WenQuanYi Zen Hei |
Meiryo |
WenQuanYi Zen Hei |
Yu Gothic |
WenQuanYi Zen Hei |
MS PGothic |
WenQuanYi Zen Hei |
Calibri / Cambria |
WenQuanYi Zen Hei |
Arial |
Liberation Sans(欧文は正しい) |
影響は Office 文書の描画に限らない。 fontconfig は Chromium / Playwright のスクリーンショット、PDF の生成、画像の生成すべてが参照する。日本語を含むページを撮ると、中国語の字形で写る。
原因
fonts-wqy-zenhei がイメージのビルド時に入っており、その fontconfig 設定が sans-serif を WenQuanYi へ向けている。
$ ls /etc/fonts/conf.d/ | grep -iE "wqy|noto-cjk|local"
25-wqy-zenhei.conf
51-local.conf
64-wqy-zenhei.conf
70-fonts-noto-cjk.conf
向けているのは 64-wqy-zenhei.conf である。
<alias><family>sans-serif</family><prefer><family>WenQuanYi Zen Hei</family></prefer></alias>25-wqy-zenhei.conf は <match target="font"> のレンダリングヒント(antialias / hintstyle / globaladvance など)だけを持ち、エイリアスを 1 つも持たない。番号が小さいから効いているのではない。
OS 既定の 65-nonlatin.conf も sans-serif の prefer 一覧に WenQuanYi Zen Hei を含む(103 行目)。そのため、パッケージや 64-wqy-zenhei.conf を外しても日本語にはならない。実測では別の言語へ落ちる。
(64-wqy-zenhei.conf を外し fc-cache -f した状態)
$ fc-match sans-serif → Loma.otf: "Loma" ← タイ語
$ fc-match sans-serif:lang=ja → ipagp.ttf: "IPAPGothic"
containers/base/Dockerfileはfonts-noto-cjkfonts-noto-cjk-extraを明示で入れているが(22 行目)、優先順位は指定していない。/etc/fonts/local.confを置くCOPYもfc-cacheも無い- イメージの中で
LANGは未設定である。Dockerfile はlocale-gen en_US.UTF-8とupdate-localeを呼ぶだけでENV LANGを持たない(24-25 行目)。いずれにせよjaではないため、言語のヒントは効かない apt-cache rdepends --installed fonts-wqy-zenheiの Reverse Depends は空で、依存としては誰からも要求されていない。/var/log/dpkg.logではfonts-ipafont-gothic/fonts-liberation/fonts-freefont-ttf/fonts-noto-color-emoji/fonts-tlwg-loma-otf/fonts-unifontやlibatk/libasound/xkb-dataと同じ 86 パッケージのバーストで入っており、npx playwright install --with-deps chromiumが入れたものである
どこで見つけたか
volareinc/nyle-dx PR #5 の資料を LibreOffice で PDF にしたところ、埋め込まれた書体が NotoSansCJKsc(簡体字中国語のフェイス)だった。字幅が違うため、実物では1行に収まる表の見出しが2行に折り返し、「文字がはみ出している」という誤った QA 判定につながった。
LibreOffice は fontconfig とは別の照合も持つため解決先が fc-match と一致しないが、どちらも日本語フェイスを選ばない点は同じである。
直し方(検証済み)
/etc/fonts/local.conf を1つ置く。
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<!-- 総称ファミリの先頭を日本語フェイスにする -->
<alias><family>sans-serif</family><prefer><family>Noto Sans CJK JP</family></prefer></alias>
<alias><family>sans</family><prefer><family>Noto Sans CJK JP</family></prefer></alias>
<alias><family>serif</family><prefer><family>Noto Serif CJK JP</family></prefer></alias>
<alias><family>monospace</family><prefer><family>Noto Sans Mono CJK JP</family></prefer></alias>
<!-- 未導入の書体を指定されたときの受け皿。弱い結合なので実在する指定は妨げない -->
<match target="pattern">
<edit name="family" mode="append" binding="weak"><string>Noto Sans CJK JP</string></edit>
</match>
<!-- 中国語・韓国語を明示したときは、そのフェイスを保つ -->
<match target="pattern">
<test name="lang" compare="contains"><string>zh-cn</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK SC</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>ko</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK KR</string></edit>
</match>
</fontconfig>Dockerfile へは COPY --chmod=0644 fonts-local.conf /etc/fonts/local.conf を足し、fc-cache -f を1度走らせる。追加パッケージは要らない(サイズは 0)。
置き場所を /etc/fonts/local.conf から動かさない
/etc/fonts/fonts.conf は <include ignore_missing="yes">conf.d</include> の 1 行しか持たず、local.conf は conf.d/51-local.conf 経由で読まれる。つまりスロット 51 で、64 や 65 より先である。
効くのは順序が後だからではなく、<prefer> が一致した総称ファミリの直前へ挿入する形(prepend)であり、最も早く読まれた prefer が先頭に残るためである。
conf.d/99-*.conf へ置くと効かない。 同じ内容で実測した。
| 置き場所 | fc-match sans-serif |
fc-match sans-serif:lang=ja |
|---|---|---|
/etc/fonts/local.conf |
Noto Sans CJK JP | Noto Sans CJK JP |
/etc/fonts/conf.d/99-devbase-fonts.conf |
WenQuanYi Zen Hei | WenQuanYi Zen Hei |
この理由を containers/base/fonts-local.conf の先頭にもコメントで残す。
適用後の実測
| 指定 | 修正前 | 修正後 |
|---|---|---|
sans-serif |
WenQuanYi Zen Hei | Noto Sans CJK JP |
sans-serif:lang=ja |
WenQuanYi Zen Hei | Noto Sans CJK JP |
Noto Sans JP |
WenQuanYi Zen Hei | Noto Sans CJK JP |
Meiryo / Yu Gothic / MS PGothic |
WenQuanYi Zen Hei | Noto Sans CJK JP |
未導入の書体(例: Zen Kaku Gothic New) |
WenQuanYi Zen Hei | Noto Sans CJK JP |
serif |
— | Noto Serif CJK JP |
monospace |
— | Noto Sans Mono CJK JP |
欧文と他言語を壊していないことも確かめた。
| 指定 | 修正後 |
|---|---|
Arial |
Liberation Sans(metric 互換のまま) |
Times New Roman |
Liberation Serif |
Courier New |
Liberation Mono |
WenQuanYi Zen Hei(名指し) |
WenQuanYi Zen Hei(残る) |
sans-serif:lang=zh-cn |
Noto Sans CJK SC |
sans-serif:lang=ko |
Noto Sans CJK KR |
fc-match -s sans-serif:lang=ja の先頭も Noto Sans CJK JP になった。
LibreOffice と Chromium での実際の描画は、どちらも base に無いため実機未検証である(fc-match の水準まで確認した)。
併せて検討したいこと
fonts-wqy-zenheiを外す案は採らない。 rdepends が空なので削除自体はできるが、上の実測のとおり65-nonlatin.confが残るためsans-serifはタイ語や IPAPGothic へ落ちるだけで、日本語にはならない。優先順位を明示する修正が要る点は変わらず、削除は中国語のページを豆腐にするだけになるfonts-crosextra-carlito(Calibri 互換)とfonts-crosextra-caladea(Cambria 互換)が未導入で、この2つも日本語フェイスへ落ちている。合計 3.0 MB で入れられ、欧文の字幅が正しくなる(feat: base へ poppler-utils と python3 の画像・XML パッケージを足す(LibreOffice は入れない) #160 に実測がある)
修正レイヤー
現象レイヤー: Office 文書・PDF・スクリーンショットに中国語の字形が出る(volareinc/nyle-dx PR #5 の QA 誤判定)。
修正レイヤー: containers/base/Dockerfile の fontconfig の明示化。新規の containers/base/fonts-local.conf を /etc/fonts/local.conf へ置き、fc-cache -f を走らせる。
さかのぼると、原因は npx playwright install --with-deps chromium が入れる副作用のフォントを base が放置していることにある。ただし削除では直らない(OS 既定の 65-nonlatin.conf が残るため)ので、責務を持つべき場所は base の側で総称ファミリを明示することである。
採る手: 新設(containers/base/fonts-local.conf)。
回帰テストは tests/containers/ の既存の流儀(Dockerfile の文字列検査)に合わせる。
決めること
- arm64 のイメージに動作する Chromium が無い件を別の issue に切り出すか。
google-chrome-stableは amd64 のときだけ入り、arm64 に入るchromium-browserは Ubuntu の snap スタブで起動しない。npx playwright install --with-deps chromiumの直後にsudo rm -rf ~/.cacheがあり、Playwright の既定の置き場(~/.cache/ms-playwright)が消えている。現象レイヤーも修正レイヤーもフォントとは別(ブラウザの導入とキャッシュの削除)なので、本 issue には含めない。playwright-kitが実行時に導入する設計なら問題にならないため、確認したうえで起票の要否を決める
由来
volareinc/nyle-dx PR #5 / #160 / devbasex/ai-plugins #530
進行
モード: standard / 作業ツリー: .worktrees/feature/v3.7.0-base-rendering / 計画: issues/PLAN63_base-image-rendering-impl.md
- 要求と受け入れ条件 — 2026-09-22 06:49
- 作業場所の用意 — 2026-09-22 06:50
- 設計 — 2026-09-22 06:57
- 素材の収集と出典の確定
- ドキュメント再構成 — 2026-09-22 07:02
- ドキュメントレビュー — 2026-09-22 07:05
- 計画 — 2026-09-23 04:09
- 実装 — 2026-09-23 04:09
- 構造改善 — 2026-09-23 04:28
- 実装レビュー — 2026-09-23 04:40
- 完了判定 — 2026-09-23 05:16
- Pull Request — 2026-09-22 07:06
- 確定仕様化 — 2026-09-23 05:25
- 後片付け — 2026-09-23 04:06
- 配布 — 2026-09-23 06:02
- 体裁レビュー
- リリース後テスト — 2026-09-23 07:53
- 振り返り — 2026-09-23 08:08
振り返り: #212 (comment)
Activity
takemi-ohama commentedon Sep 21, 2026
「決めること」に挙がっていた arm64 の Chromium と
~/.cacheの削除は、#220 として残した。playwright-kitは実行時にuv sync+playwright install chromiumを自前で行う設計であることをdevbasex/ai-pluginsのplugins/playwright-kit/skills/playwright-kit-ops/(templates/run.sh・scripts/init_project.sh・templates/runtime-README.md)で確認した。そのため消えているブラウザのキャッシュは playwright-kit の利用者には影響しないが、ビルドが毎回取得して捨てていることと arm64 に動く Chromium が無いことは残るため、起票した。docs(PLAN63): base イメージの日本語の描画と、文書を扱う軽量の道具の要求仕様と設計 (#161, #160) (#221)
feat(PLAN63): base イメージの日本語の描画を直し、文書を扱う軽量の道具を足す (#161, #160)
fix(PLAN63): probe の実行中の異常を skip でなく失敗として知らせる (#161)
test(PLAN63): 新しい RUN の検査を RUN ブロック単位へ直す (#161)
feat(PLAN63): base イメージの日本語の描画を直し、文書を扱う軽量の道具を足す (#161, #160) (#238)
docs: PLAN63 / PLAN64 / PLAN65 / PLAN66 を確定仕様へ取り込む (#161, #160, #188, #…