Fix tofu in Chinese figure labels, and give font.family a fallback#206
Merged
Conversation
These three lectures set Chinese text on their figures but never register a
CJK font, so matplotlib falls back to DejaVu Sans — which has no CJK coverage
— and every Chinese axis label, title and legend entry renders as blank boxes
("tofu"). Nothing errors and a valid PNG is written, so the build stays green
and the defect only shows up in the published output.
Affected labels include 密度 / KL散度 / JS散度 / 切尔诺夫熵 in
divergence_measures.md, 收益率 / 日期 / 时间 / 对数秩 / 对数规模 in
kesten_processes.md, and the subplot titles 价值函数 / $s$策略 / $\phi$策略
in jv.md.
Adds this edition's standard block to the first code cell that imports
matplotlib in each file, immediately after `import matplotlib.pyplot as plt`
so it runs before any figure is drawn:
import matplotlib as mpl
FONTPATH = "fonts/SourceHanSerifSC-SemiBold.otf"
mpl.font_manager.fontManager.addfont(FONTPATH)
plt.rcParams['font.family'] = ['Source Han Serif SC']
Matches the convention already used by the other 112 lectures here (see
aiyagari.md); the path resolves against lectures/, where
fonts/SourceHanSerifSC-SemiBold.otf is checked in. Same class of gap as the
five lectures fixed in #196 — nothing else in these files is touched.
See #205
…re lectures
Setting font.family to a single entry removes matplotlib's fallback, so any
character the CJK font lacks renders as a blank box. Source Han Serif SC follows
Adobe-GB1, which omits subscript digits (U+2080-2083), the vertical ellipsis
(U+22EE), and the lunate/symbol Greek forms (U+03F5 epsilon, U+03D5 phi) — all
of which these lectures use in figure labels.
Measured on matplotlib 3.11.1 with this repo's own font, rendering a label
containing 密钥拆分树, key₀, ⋮, ϵ and ϕ:
font.family = ['Source Han Serif SC'] -> 4 missing-glyph warnings
font.family = ['Source Han Serif SC', 'DejaVu Sans'] -> 0
Two lectures already carried the block and were silently losing characters to
it: career.md sets ylabel='ϵ' (and label='ϵ'), affine_risk_prices.md builds
label=f"... (r₁ = ...)". Both are fixed by the fallback alone.
This deviates from the single-entry dialect used elsewhere in the edition. That
is deliberate: the dialect is missing a fallback, and adding one is strictly an
improvement everywhere it appears.
✅ Deploy Preview for astonishing-narwhal-a8fc64 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
This PR fixes missing CJK glyph rendering in several lecture notebooks by ensuring the Source Han Serif SC font is registered where needed, and by adjusting Matplotlib’s font.family setting to preserve per-glyph fallback for symbols/subscripts that Source Han Serif SC does not cover.
Changes:
- Restores/adds the Matplotlib font-registration block (
addfont) indivergence_measures.md,kesten_processes.md, andjv.md. - Updates
font.familyto include a fallback ('DejaVu Sans') incareer.mdandaffine_risk_prices.md. - Standardizes the font-family list in the touched lectures to avoid “tofu”/missing-glyph substitution for non-CJK symbols.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| lectures/kesten_processes.md | Reintroduces Matplotlib font registration + font family fallback to ensure CJK and symbol glyph coverage. |
| lectures/jv.md | Restores the font registration block and adds fallback to prevent missing glyphs in figure labels. |
| lectures/divergence_measures.md | Adds the font registration block (previously absent) with a safe fallback list for glyph coverage. |
| lectures/career.md | Extends font.family from a single entry to a fallback list to avoid missing non-CJK glyphs. |
| lectures/affine_risk_prices.md | Updates the existing i18n font configuration to include a fallback family, preventing missing-glyph rendering in figures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restores the font block in
divergence_measures.md,kesten_processes.mdandjv.md, and adds the fallback tocareer.mdandaffine_risk_prices.md, which already carried the block and were silently losingϵandr₁to it.divergence_measuresis live on the published site today; the other two are queued behind the next publish. Provenance note:jvandkesten_processeslost their block to resyncs (965ad3e,da7b176), whiledivergence_measuresnever had one — its Chinese labels were hand-added after the original blanket pass.Chinese figure labels in these lectures render as tofu — blank rectangles — because the notebook never registers a CJK font. matplotlib falls back to DejaVu Sans, which has no CJK coverage, emits a
UserWarning, substitutes boxes, and writes a perfectly valid PNG. Nothing fails.Two changes, and the second is the interesting one
1. Register the font where it was missing, following this edition's existing convention and path.
2. Give
font.familya fallback. This is a deliberate deviation from the single-entry form used elsewhere in the estate, and it is a defect fix rather than a style choice.Setting
font.familyto a single entry removes matplotlib's per-glyph fallback. Source Han Serif SC follows Adobe-GB1, which omits subscript digits (U+2080–U+2083), the vertical ellipsis (U+22EE) and the symbol Greek forms (U+03F5 ϵ, U+03D5 ϕ) — characters these lectures genuinely use in labels. So the naive fix would have swapped one set of blank boxes for another.Measured on matplotlib 3.11.1 against this repo's own font file, rendering a label containing 密钥拆分树,
key₀,⋮,ϵandϕ:font.family['Source Han Serif SC']['Source Han Serif SC', 'DejaVu Sans']CJK still comes from Source Han Serif; everything outside Adobe-GB1 now falls back instead of vanishing.
Why this was invisible
The missing-glyph warning is raised inside the Jupyter kernel subprocess. Sphinx's
-Wonly escalates Sphinx-logger warnings, so CI never sees it — myst-nb renders it into the published page as stderr output instead. No CI log in the estate has ever containedfindfontorGlyph … missing.A detector now exists (
bin/check-tofuinproject-translation), validated against the live sites. It found pages that source-level analysis missed, because it reads what was actually rendered.Background
Full history, the argument against reverting to
text.usetex, and the recommendation to replace per-lecture injection with a build-level mechanism: project-translation reports/2026-07-24-cjk-font-rendering-review.md. Engine side: action-translation#182 (resync strips these blocks; the guardrail is prompt prose).🤖 Generated with Claude Code