Skip to content

fix(plot): fall back to Noto Sans Math for symbols Noto Sans lacks, prepare 0.16.1 - #113

Merged
frankbuckley merged 3 commits into
mainfrom
dev/claude/plot-symbol-fallback
Sep 28, 2026
Merged

frankbuckley merged 3 commits into
mainfrom
dev/claude/plot-symbol-fallback

Conversation

@frankbuckley

Copy link
Copy Markdown
Member

Note

Drafted by a LLM-based AI tool (Claude Code/Opus 5.5).

Closes #112.

Under the 0.16.0 default style, a literal symbol that Noto Sans lacks draws as a missing-glyph box in titles, axis labels, legend entries and tick labels. This covers →, ≈, ≤, ≥, ✓ and every other code point from U+2190 to U+27FF except U+2212 and U+25CC. It was found while adopting 0.16.0 in dseinternational/vocabulary-growth#370. This PR fixes the style, documents the gap and the mathtext workaround in the 0.16.0 upgrade notes, and prepares 0.16.1 without publishing it.

Style change

  • set_matplotlib_default_style() now sets font.family from a new default_font_families(): ["sans-serif", "Noto Sans Math", "DejaVu Sans"], or ["sans-serif", "DejaVu Sans"] where Noto Sans Math is not installed.
  • DEFAULT_STYLE_DICT["font.family"] is ["sans-serif", "DejaVu Sans"], which is safe on any machine because DejaVu Sans ships with matplotlib. The new constant FONT_FAMILY_FALLBACK names it.

Figures without such symbols are unchanged. A two-panel figure with a suptitle, mathtext titles, legends and a 9 pt annotation gives a pixel-identical PNG under both styles.

Why this list differs from the one proposed in #112

#112 proposed [FONT_FAMILY_DEFAULT, FONT_FAMILY_MATH, "DejaVu Sans"]. When a named family is missing, FontManager._find_fonts_by_props logs findfont: Font family %r not found. on every call, without caching, and matplotlib calls it for every text layout. Two simple figures produced 1,780 such lines. With Noto Sans named explicitly, a machine without it logs that for every text element. This includes CI runners, which install no fonts. Keeping the generic sans-serif first avoids this and keeps font.sans-serif meaningful. The "keep the previous look" recipe in the 0.16.0 notes depends on that. For the same reason, Noto Sans Math is listed only when it is installed.

Why the two-family attempt in vocabulary-growth did not work

That PR reported that font.family = ["sans-serif", "Noto Sans Math"] still drew boxes. Under matplotlib 3.11.2 that list does work on the Agg, SVG, PDF and PS backends, and the PNG output was checked visually. It fails reproducibly in two cases:

  • it is set after the text is created, because each Text keeps the font.family in effect when it was created;
  • set_matplotlib_default_style() runs again afterwards and resets it.

vocabulary-growth applies the style inside package code (for example comparison.py), so the second case is the likely cause. The 0.16.0 notes now state both conditions.

Known side effect

Noto Sans Math has a single regular face and DejaVu Sans has no medium weight, while the style uses medium and bold. matplotlib therefore logs findfont: Failed to find font weight …, now using 400. once per weight and size, which is three lines per process for a typical figure. This is documented in the 0.16.1 notes. It affects only the weight of fallback symbols.

Verification

  • A new test draws the symbols on the Agg, SVG and PDF backends. It fails on any missing-glyph warning, or on any findfont "not found" log line. It adapts the guards in vocabulary-growth and feat(reports): upgrade research utils to 0.16.0 and use Noto Sans and Noto Sans Math throughout language-reading-predictors#694.
  • The test fails against the 0.16.0 behaviour. It passes on simulated machines without the Noto fonts, both through the Windows fallback in font.sans-serif and with only DejaVu Sans.
  • The full suite passes on Windows: 1,166 passed and 13 skipped. ruff check, ruff format --check, npm run format:check and npm run spellcheck are clean.

Release

__version__ is 0.16.1 and the install instructions point at v0.16.1. Nothing is tagged. Merge this PR, check CI on the merged commit, then tag that commit as v0.16.1.

Downstream follow-up after v0.16.1

  • language-reading-predictors: figure_io.HOUSE_FONT_FAMILIES names Noto Sans explicitly. On a machine without Noto Sans it therefore logs Font family 'Noto Sans' not found. for every text element. The layer can be dropped, and use_house_fonts() can take font.family from default_font_families().
  • vocabulary-growth: labels already rewritten as mathtext keep rendering. tests/test_figure_text_glyphs.py is no longer needed to prevent boxes.

🤖 Generated with Claude Code

frankbuckley and others added 3 commits September 27, 2026 21:39
Noto Sans has no arrows, mathematical operators, technical symbols,
geometric shapes or dingbats, and matplotlib falls back glyph by glyph
only across the families named in font.family. The generic "sans-serif"
resolves to a single font, so under 0.16.0 a literal → or ≤ in plain
text drew as a missing-glyph box.

set_matplotlib_default_style() now sets font.family from the new
default_font_families(): "sans-serif", then Noto Sans Math, then
DejaVu Sans (FONT_FAMILY_FALLBACK). Noto Sans Math is listed only when
installed, because matplotlib logs a warning each time it lays out
text with a named family it cannot find (about 890 lines for a simple
figure). The list keeps the generic "sans-serif" first for the same
reason, so machines without Noto Sans still fall back silently through
font.sans-serif. DEFAULT_STYLE_DICT carries the always-safe
["sans-serif", "DejaVu Sans"].

Figures without such symbols render pixel-identically. The tests draw
the symbols on the Agg, SVG and PDF backends and fail on any
missing-glyph warning or any "family not found" log line.

Closes #112

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a "Symbols in plain text" section to the 0.16.0 upgrade notes:
which symbols Noto Sans lacks, why matplotlib does not fall back under
the generic "sans-serif" family, the mathtext workaround with a table
of commands, and how to set the fallback list by hand (after applying
the style, before creating the figure). Correct the statement that
text falls back to DejaVu Sans without the fonts: it falls back
silently through font.sans-serif, and only math uses DejaVu Sans.

Add 0.16.1 upgrade notes and describe the fallback in the README and
the agent guidance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump __version__ to 0.16.1 and point the install instructions at the
v0.16.1 tag. The version change prepares the release, it does not
publish it: merge, check CI on the merged commit, then tag that commit
as v0.16.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plot style: fall back to Noto Sans Math for symbols Noto Sans lacks

1 participant