何を見つけたか
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-cjk fonts-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 の水準まで確認した)。
併せて検討したいこと
修正レイヤー
現象レイヤー: 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
振り返り: #212 (comment)
何を見つけたか
base コンテナでは、日本語が中国語のフォントで描画される。 総称ファミリの
sans-serifそのものが中国語フォントへ解決される。言語を
jaと明示しても中国語フォントになる。 フォールバックの順序は次のとおりで、fonts-noto-cjkが明示的に入っているにもかかわらず、日本語フェイスは5番目である。日本語環境でよく指定される名前も、すべて中国語フォントへ落ちる。
sans-serifNoto Sans JPMeiryoYu GothicMS PGothicCalibri/CambriaArial影響は Office 文書の描画に限らない。 fontconfig は Chromium / Playwright のスクリーンショット、PDF の生成、画像の生成すべてが参照する。日本語を含むページを撮ると、中国語の字形で写る。
原因
fonts-wqy-zenheiがイメージのビルド時に入っており、その fontconfig 設定がsans-serifを WenQuanYi へ向けている。向けているのは
64-wqy-zenhei.confである。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を外しても日本語にはならない。実測では別の言語へ落ちる。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-dxPR #5 の資料を LibreOffice で PDF にしたところ、埋め込まれた書体がNotoSansCJKsc(簡体字中国語のフェイス)だった。字幅が違うため、実物では1行に収まる表の見出しが2行に折り返し、「文字がはみ出している」という誤った QA 判定につながった。LibreOffice は fontconfig とは別の照合も持つため解決先が
fc-matchと一致しないが、どちらも日本語フェイスを選ばない点は同じである。直し方(検証済み)
/etc/fonts/local.confを1つ置く。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-seriffc-match sans-serif:lang=ja/etc/fonts/local.conf/etc/fonts/conf.d/99-devbase-fonts.confこの理由を
containers/base/fonts-local.confの先頭にもコメントで残す。適用後の実測
sans-serifsans-serif:lang=jaNoto Sans JPMeiryo/Yu Gothic/MS PGothicZen Kaku Gothic New)serifmonospace欧文と他言語を壊していないことも確かめた。
ArialTimes New RomanCourier NewWenQuanYi Zen Hei(名指し)sans-serif:lang=zh-cnsans-serif:lang=kofc-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-dxPR #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 の文字列検査)に合わせる。決めること
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-dxPR #5 / #160 /devbasex/ai-plugins#530進行
モード: standard / 作業ツリー:
.worktrees/feature/v3.7.0-base-rendering/ 計画:issues/PLAN63_base-image-rendering-impl.md振り返り: #212 (comment)