Skip to content

bug: 日本語が中国語フォントで描画される(sans-serif の解決先が WenQuanYi Zen Hei になる) #161

Description

@takemi-ohama

何を見つけたか

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.confsans-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/Dockerfilefonts-noto-cjk fonts-noto-cjk-extra を明示で入れているが(22 行目)、優先順位は指定していない/etc/fonts/local.conf を置く COPYfc-cache も無い
  • イメージの中で LANG は未設定である。Dockerfile は locale-gen en_US.UTF-8update-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-unifontlibatk / 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.confconf.d/51-local.conf 経由で読まれる。つまりスロット 51 で、6465 よりである。

効くのは順序が後だからではなく、<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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions