X Tutup
Skip to content

mathtext: add support for unicode mathematics fonts#31048

Closed
llohse wants to merge 25 commits intomatplotlib:text-overhaulfrom
llohse:mathtext-unicode
Closed

mathtext: add support for unicode mathematics fonts#31048
llohse wants to merge 25 commits intomatplotlib:text-overhaulfrom
llohse:mathtext-unicode

Conversation

@llohse
Copy link

@llohse llohse commented Jan 29, 2026

Add basic support for generic unicode OpenType/TrueType mathematics fonts such as STIX Two Math, Cambria Math, DejaVu Math, etc.

Currently, mathematics text rendering through mathtext in matplotlib supports a hard-coded number of fonts (configured via mathtext.fontset). Its design presumably predates the specification of mathematics alphabets in the unicode standard. While it is possible to configure custom fonts (mathtext.fontset: custom), this requires to set separate fonts for upright, italic, fraktur, double-struck, etc. variants -- which is fundamentally incompatible with the way modern mathematics fonts are designed.

Unicode defines mathematical alphanumeric symbols as unique codepoints (see https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols), in contrast to different fonts all defining different styles for the same ASCII characters/codepoints.
One relatively modern way to render mathematical formulas uses mathematics fonts such as STIX Two Math or Cambria Math, Asana Math, etc.. For LaTeX, this is implemented in the unicode-math package.
Instead of choosing a font based on the style (as it is currently done in matplotlib) to render the same codepoints, this maps alphanumeric characters to different codepoints based on the style, and render them from a single font.

Shortcomings of the status quo:

  • It is not currently possible to use modern mathematics fonts when using mathtext for rendering of mathematical equations in matplotlib. Consequently, it is difficult to configure fonts that are visually consistent and may require the use of latex rendering.
  • Font configuration may be perceived as somewhat complicated
  • The mathtext codebase and font selection logic is relatively complex and inaccessible, with all the hardcoded fonts, fixes for their quirks, and fallbacks

This change implements functionality to use any installed unicode Opentype/Truetype mathematics fonts for use in mathtext in a portable way. Currently, this can be enabled by setting the rcparams

mathtext.fontset: unicodemath
mathtext.mathfont: STIX Two Math # or any other font name that can be loaded by fontmanager

I could think of different ways to configure this, though.

Internally, I have implemented a separate class UnicodeMathFonts(TruetypeFonts) to no interfere with the existing fontsets.

Running the test currently requires STIX Two Math to be installed on the system. For that reason, I have added it to the test data. One may think about vendoring STIX Two Math or DejaVu Math via mpl-data instead.

Examples

import matplotlib.pyplot as plt
import matplotlib as mpl

mpl.rcParams['mathtext.fontset'] = 'unicodemath'

font_names = ['STIX Two Math', 'TeX Gyre Pagella Math', 'TeX Gyre Schola Math', 'Noto Sans Math', 'Latin Modern Math', 'TeX Gyre DejaVu Math']

for font in font_names:
    with mpl.rc_context({'mathtext.mathfont' : font}):
        fig = plt.figure(figsize=(3, 1.5), dpi=150)
        fig.suptitle(f'{font}')
        fig.text(.1, .2, r'$\frac{1}{2 \pi \mathrm{i}} \int_{\partial G} f(z) \mathrm{d}z = \sum_{\nu = 1}^n \left(\mathrm{res} f\right)(z_\nu)$')
        fig.text(.1, .45, r'$\bigcup_{i \in I} A_i := \left\{ x \mid \bigvee_i\left( i \in I \wedge x \in A_i\right)\right\}$')
        fig.text(.1, .7, r'$\mathbb{Q} = \left. \left(\mathbb{Z} \times \mathbb{Z} \setminus \{ 0\} \right) \right/ \sim $')
        fig.tight_layout()
        fig.savefig(f'{font}.png')
Latin Modern Math Noto Sans Math STIX Two Math TeX Gyre DejaVu Math TeX Gyre Schola Math

PR checklist

@llohse llohse marked this pull request as draft January 29, 2026 12:29
@llohse llohse marked this pull request as ready for review January 29, 2026 12:57
@QuLogic
Copy link
Member

QuLogic commented Jan 29, 2026

This is very interesting, though I have not looked into the implementation details. Do these Unicode math fonts cover symbols like integrals, etc.? The test images so far don't cover those, so I would guess not?

Also, these test images should either wait for #30161 or base the PR on it, as they all exhibit the wobbly baseline problem from the old text layout algorithm.

@llohse
Copy link
Author

llohse commented Jan 30, 2026

This is very interesting, though I have not looked into the implementation details. Do these Unicode math fonts cover symbols like integrals, etc.? The test images so far don't cover those, so I would guess not?

Thanks. Absolutely. They include all glyphs that are relevant for mathematics typesetting. "Modern" LaTeX typestting with unicode-math exclusively uses glyphs and metadata from the otf font files. In the future, one may consider to read the MATH table instead of using hard-coded FontConstant values.
Please take a look at the example images that I provided in the PR description.
I would welcome any advice on how to lay out the tests. In particular, they require at least one mathematics font to be present. Does it make sense to vendor STIX Two Math, either in mpl-data or in the test data? Alternatively, DejaVu Math (since DejaVu seems to be the default in matplotlib).

Also, these test images should either wait for #30161 or base the PR on it, as they all exhibit the wobbly baseline problem from the old text layout algorithm.

Makes sense. I'll try to rebase to it.

@llohse
Copy link
Author

llohse commented Jan 30, 2026

@QuLogic I merged the changes into a new branch based on #30161 here: https://github.com/llohse/matplotlib/tree/mathtext-unicode-textoverhaul

Should I update this PR somehow?

@llohse llohse changed the base branch from main to text-overhaul February 3, 2026 08:20
dependabot bot and others added 6 commits February 3, 2026 09:22
Bumps the actions group with 2 updates in the / directory: [github/codeql-action](https://github.com/github/codeql-action) and [actions/cache](https://github.com/actions/cache).


Updates `github/codeql-action` from 4.31.9 to 4.31.10
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@5d4e8d1...cdefb33)

Updates `actions/cache` from 5.0.1 to 5.0.2
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@9255dc7...8b402f5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/cache
  dependency-version: 5.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
The concept of alias priority was deprecated in 71d725f and has been
removed since then (making normalize_kwargs maintain kwarg order).
For more consistency and similarity with RadioButtons
doronbehar and others added 19 commits February 3, 2026 09:22
Share all the trivial functions which are identical without any further
modifications.
This is generally frowned upon (though we don't actually change it, so
it's safe), but more importantly, Sphinx tries to put all the values
into the docstring, which just looks bad.
The new version appears to be causing some issues for our missing
reference checks, so reverting back to the old implementation should buy
us some time to fix things.
* FIX: Handle AxesWidget cleanup after failed init

* Apply code review suggestions

* Update lib/matplotlib/tests/test_widgets.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Remove __del__ method

* Remove AxesWidget.__del__ from widgets.pyi

---------

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
If Pillow is old enough to not support a feature _at all_, then it will
warn about it. If you're running the whole test suite, then something
else seems to cause pytest to treat it as benign. But if you run only
`test_agg.py`, then our `-Werror` setting will cause it to fail test
collection.
Directly store property alias information in `{"alias": "propety", ...}`
format, which greatly speeds up normalize_kwargs.

In order to not worry about two different dict formats (the new one, and
the old one, which was `{"property": ["alias", ...], ...}`) in
normalize_kwargs, deprecate support for directly passing an alias map --
callers should always pass an artist (or artist type) directly, which
hides the detail of the format of the alias map.  Also deprecate passing
`alias_mapping=None`, which doesn't really seem needed (unlike
`kw=None`, which is regularly used).

Whether the format of aliases passed to `_api.define_aliases` should
also be changed to match the new format is a separate question that
doesn't have to be addressed here (it can be changed later, being fully
private anyways).
…inplot (matplotlib#30752)

* Improving error message for width and position type mismatch in violinplot

* Improving error message for width and position type mismatch in violinplot

* Fix violin plot statistics in test data

* Trigger CI pipeline

* Trigger CI pipeline

* Update lib/matplotlib/axes/_axes.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update lib/matplotlib/axes/_axes.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update lib/matplotlib/tests/test_axes.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Store original widths parameter before conversion for accurate type validation
- Improve pytest match strings with proper line continuation formatting
- Enhanced error messages provide clear examples for correct usage

* Update lib/matplotlib/axes/_axes.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update lib/matplotlib/axes/_axes.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Remove low-information comment

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com>
The code uses dt=0.01 as time step but the comment said 0.02
This page has not been updated since June 2020.  Getting it up to
date would require a lot of manual intervention in the mailmap, so
I propose to do it in batches.  This change adds new contributors
up to 0b61e32.
Adds support for generic unicode OpenType/TrueType mathematics fonts such as STIX Two Math, Cambria Math,
@llohse
Copy link
Author

llohse commented Feb 3, 2026

... I messed up by changing the target branch. Apologies.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

X Tutup