FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

mathtext: add support for unicode mathematics fonts by llohse · Pull Request #31064 · matplotlib/matplotlib · GitHub

Repository navigation

Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .otf  (1) .png  (56) .py  (6) No extension  (2) All 4 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
131 changes: 130 additions & 1 deletion lib/matplotlib/_mathtext.py
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
import matplotlib as mpl
from . import cbook
from ._mathtext_data import (
latex_to_bakoma, stix_glyph_fixes, stix_virtual_fonts, tex2uni)
latex_to_bakoma, stix_glyph_fixes, stix_virtual_fonts, tex2uni,
unicode_math_lut, greek_uppercase_domain, greek_lowercase_domain)
from .font_manager import FontProperties, findfont, get_font
from .ft2font import FT2Font, Kerning, LoadFlags

Expand Down Expand Up @@ -782,6 +783,134 @@ def get_font_constants(self) -> type[FontConstantsBase]:
return DejaVuSansFontConstants


class UnicodeMathFonts(TruetypeFonts):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

IIUC, math fonts should have tables with various layout metrics. We currently have those hard-coded in the various FontsConstantsBase subclasses, and they are likely incorrect for an arbitrary math font.

So this will likely need to parse this data out of the font and implement at least get_axis_height that was added in #31046, get_xheight maybe using #31050, and get_quad from #31110. But it is likely that you will want to refactor some of those remaining uses of the constants so that they fetch the information from the fonts as well.

llohse Feb 7, 2026 •
edited
Loading

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I fully agree. Doing this may involve some refactoring though, because the FontsConstantsBase subclass could not be determined purely from fontname but would be dynamically populated based on the loaded OpenType font.

That said, I made some experiments locally. Unfortunately, Freetype does not parse the MATH table. We could use fonttools, which is a hard dependency anyway.
There are several open questions how to map the OpenType layout metrics to the legacy TeX-inspired variables used in mathtext. Does it make sense to postpone that to a separate PR and focus on the basics here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

While I would like it to happen, I'm not sure this will be ready for 3.11. So if we're aiming for 3.12, I think it's okay to spend some time getting everything worked out in separate PRs.

"""
A font handling class for Unicode mathematics fonts.

In addition to what TruetypeFonts provides, this class:

- supports mapping alphanumeric characters (latin and greek) to different alphabet
styles (such as bold, italic, fraktur, script, double-struck, ...) defined in
the Unicode standard.

"""

def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlags):
super().__init__(default_font_prop, load_glyph_flags)
prop = mpl.rcParams['mathtext.mathfont'] # type: ignore[index]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I think this will break the caching for MathTextParser._parse_cached, but I see it already doesn't really handle any of the other mathtext.* rcParams, so I guess that's something to work out at some point.

font = findfont(prop)
self.fontmap['mathfont'] = font

_slanted_symbols = set(r"\int \oint".split())

def _get_font(self, font: str | int) -> FT2Font:
# work around, since we only populate 'mathfont' in the fontmap
if font not in ('default', 'regular'):
font = 'mathfont'

return super()._get_font(font)

def _get_glyph(self, fontname: str, font_class: str,
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
# `fontname' is one of rm cal it tt sf bf bfit default bb frak scr regular
# font_class is not currently supported

# 1) map symbol to unicode index
try:
uniindex = get_unicode_index(sym)
found_symbol = True
except ValueError:
uniindex = ord('?')
found_symbol = False
_log.warning("No TeX to Unicode mapping for %a.", sym)

if fontname in ('default', 'regular'):
slanted = False
font = self._get_font(fontname)
return font, uniindex, slanted

# 2) remap unicode index based on fontname and font_class (bf, it, ...)
# Unicode mathematical alphanumeric symbols define all (relevant) variants

# from here on: use the Math font
new_fontname = 'mathfont'

def _is_digit(codepoint: CharacterCodeType) -> bool:
return 0x30 <= codepoint <= 0x39

def _is_latin_uppercase(codepoint: CharacterCodeType) -> bool:
return 0x41 <= codepoint <= 0x5a

def _is_latin_lowercase(codepoint: CharacterCodeType) -> bool:
return 0x61 <= codepoint <= 0x7a

def _is_greek_uppercase(codepoint: CharacterCodeType) -> bool:
return codepoint in greek_uppercase_domain

def _is_greek_lowercase(codepoint: CharacterCodeType) -> bool:
return codepoint in greek_lowercase_domain

# digits, latin letters, and greek letter are mapped by separate rules
if _is_digit(uniindex):
_alphabet_map = {
'normal': 'up',
'rm': 'up',
'it': 'up', # italic digits are not available
'tt': 'tt',
'sf': 'sfup',
'bf': 'bfup',
'bfit': 'bfup', # bold italic digits are not available
'bb': 'bb',
}
alphabet = _alphabet_map.get(fontname, 'up')
elif _is_latin_uppercase(uniindex) or _is_latin_lowercase(uniindex):
_alphabet_map = {
'normal': 'it',
'rm': 'up',
'cal': 'scr',
'it': 'it',
'tt': 'tt',
'sf': 'sfup',
'bf': 'bfup',
'bfit': 'bfit',
'bb': 'bb',
'frak': 'frak',
'scr': 'scr'
}
alphabet = _alphabet_map.get(fontname, 'up')
elif _is_greek_uppercase(uniindex):
_alphabet_map = {
'normal': 'up',
'rm': 'up',
'it': 'it',
'bf': 'bfup',
'bfit': 'bfit',
}
alphabet = _alphabet_map.get(fontname, 'up')
elif _is_greek_lowercase(uniindex):
_alphabet_map = {
'normal': 'it',
'rm': 'up',
'it': 'it',
'bf': 'bfup',
'bfit': 'bfit',
}
alphabet = _alphabet_map.get(fontname, 'up')
else:
alphabet = 'up'

if alphabet != 'up':
alphabet_lut = unicode_math_lut.get(alphabet, {})
new_uniindex = alphabet_lut.get(uniindex, uniindex)
else:
new_uniindex = uniindex

slanted = (alphabet == 'it') or sym in self._slanted_symbols
font = self._get_font(new_fontname)

return font, new_uniindex, slanted


class StixFonts(UnicodeFonts):
"""
A font handling class for the STIX fonts.
Expand Down
Loading
Loading

Back | FazBrowse Home | New Git URL