fix(plot): fall back to Noto Sans Math for symbols Noto Sans lacks, prepare 0.16.1 - #113
Merged
Merged
Conversation
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>
This was referenced Sep 28, 2026
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.
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 setsfont.familyfrom a newdefault_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 constantFONT_FAMILY_FALLBACKnames 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_propslogsfindfont: 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 genericsans-seriffirst avoids this and keepsfont.sans-serifmeaningful. 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:Textkeeps thefont.familyin 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
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.font.sans-serifand with only DejaVu Sans.ruff check,ruff format --check,npm run format:checkandnpm run spellcheckare clean.Release
__version__is 0.16.1 and the install instructions point atv0.16.1. Nothing is tagged. Merge this PR, check CI on the merged commit, then tag that commit asv0.16.1.Downstream follow-up after v0.16.1
figure_io.HOUSE_FONT_FAMILIESnames Noto Sans explicitly. On a machine without Noto Sans it therefore logsFont family 'Noto Sans' not found.for every text element. The layer can be dropped, anduse_house_fonts()can takefont.familyfromdefault_font_families().tests/test_figure_text_glyphs.pyis no longer needed to prevent boxes.🤖 Generated with Claude Code