Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a6913f3

Browse files
committed
mathtext: implement \mathnormal and distinguish between normal and it
To replicate LaTeX behaviour, distinguish between "italic" and "normal" math. In particular, digits should be set italic in the \mathit env. For `cm`, use cmti font for "it" For general UnicodeFont (stix, DejaVu, ...), maps digits to roman or italic alphabet, depending on "normal" or "it" environment.
1 parent 8137091 commit a6913f3

9 files changed

Lines changed: 385 additions & 37 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Mathtext distinguishes *italic* and *normal* font
2+
-------------------------------------------------
3+
4+
Matplotlib's lightweight TeX expression parser (``usetex=False``) now distinguishes between *italic* and *normal* math fonts to closer replicate the behaviour of LaTeX.
5+
The normal math font is selected by default in math environment (unless the rcParam ``mathtext.default`` is overwritten) but can be explicitly set with the new ``\mathnormal`` command. Italic font is selected with ``\mathit``.
6+
The main difference is that *italic* produces italic digits, whereas *normal* produces upright digits. Previously, it was not possible to typeset italic digits.
7+
Note that ``normal`` now corresponds to what used to be ``it``, whereas ``it`` now renders all characters italic.
8+
**Important**: In case the default mathematics font is overwritten by setting ``mathtext.default: it`` in ``matplotlibrc``, it must be either commented out or changed to ``mathtext.default: normal`` to preserve its behaviour. Otherwise, all alphanumeric characters, including digits, are rendered italic.
9+
10+
One difference to traditional LaTeX is that LaTeX further distinguishes between *normal* (``\mathnormal``) and *default math*, where the default uses roman digits and normal uses oldstyle digits. This distinction is no longer present with modern LaTeX engines and unicode-math nor in Matplotlib.

lib/matplotlib/_mathtext.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,9 @@ def get_metrics(self, font: str, font_class: str, sym: str, fontsize: float,
269269
----------
270270
font : str
271271
One of the TeX font names: "tt", "it", "rm", "cal", "sf", "bf",
272-
"default", "regular", "bb", "frak", "scr". "default" and "regular"
273-
are synonyms and use the non-math font.
272+
"default", "regular", "normal", "bb", "frak", "scr". "default"
273+
and "regular" are synonyms and use the non-math font.
274+
"normal" denotes the normal math font.
274275
font_class : str
275276
One of the TeX font names (as for *font*), but **not** "bb",
276277
"frak", or "scr". This is used to combine two font classes. The
@@ -477,10 +478,11 @@ class BakomaFonts(TruetypeFonts):
477478
its own proprietary 8-bit encoding.
478479
"""
479480
_fontmap = {
481+
'normal': 'cmmi10',
480482
'cal': 'cmsy10',
481483
'rm': 'cmr10',
482484
'tt': 'cmtt10',
483-
'it': 'cmmi10',
485+
'it': 'cmti10',
484486
'bf': 'cmb10',
485487
'sf': 'cmss10',
486488
'ex': 'cmex10',
@@ -500,12 +502,18 @@ def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlag
500502
def _get_glyph(self, fontname: str, font_class: str,
501503
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
502504
font = None
505+
503506
if fontname in self.fontmap and sym in latex_to_bakoma:
504507
basename, num = latex_to_bakoma[sym]
505-
slanted = (basename == "cmmi10") or sym in self._slanted_symbols
508+
slanted = (basename in ("cmmi10", "cmti10")) or sym in self._slanted_symbols
506509
font = self._get_font(basename)
507510
elif len(sym) == 1:
508-
slanted = (fontname == "it")
511+
slanted = (fontname in ("it", "normal"))
512+
if fontname == "normal" and sym.isdigit():
513+
# use digits from cmr (roman alphabet) instead of cmm (math alphabet),
514+
# same as LaTeX does.
515+
fontname = "rm"
516+
slanted = False
509517
font = self._get_font(fontname)
510518
if font is not None:
511519
num = ord(sym)
@@ -636,11 +644,14 @@ def _get_glyph(self, fontname: str, font_class: str,
636644
# Only characters in the "Letter" class should be italicized in 'it'
637645
# mode. Greek capital letters should be Roman.
638646
if found_symbol:
639-
if fontname == 'it' and uniindex < 0x10000:
647+
if fontname == 'normal' and uniindex < 0x10000:
648+
# normal mathematics font
640649
char = chr(uniindex)
641650
if (unicodedata.category(char)[0] != "L"
642651
or unicodedata.name(char).startswith("GREEK CAPITAL")):
643652
new_fontname = 'rm'
653+
else:
654+
new_fontname = 'it'
644655

645656
slanted = (new_fontname == 'it') or sym in self._slanted_symbols
646657
found_symbol = False
@@ -657,7 +668,7 @@ def _get_glyph(self, fontname: str, font_class: str,
657668

658669
if not found_symbol:
659670
if self._fallback_font:
660-
if (fontname in ('it', 'regular')
671+
if (fontname in ('it', 'regular', 'normal')
661672
and isinstance(self._fallback_font, StixFonts)):
662673
fontname = 'rm'
663674

@@ -669,7 +680,7 @@ def _get_glyph(self, fontname: str, font_class: str,
669680
return g
670681

671682
else:
672-
if (fontname in ('it', 'regular')
683+
if (fontname in ('it', 'regular', 'normal')
673684
and isinstance(self, StixFonts)):
674685
return self._get_glyph('rm', font_class, sym)
675686
_log.warning("Font %r does not have a glyph for %a [U+%x], "
@@ -854,7 +865,7 @@ def _map_virtual_font(self, fontname: str, font_class: str,
854865
fontname = mpl.rcParams['mathtext.default']
855866

856867
# Fix some incorrect glyphs.
857-
if fontname in ('rm', 'it'):
868+
if fontname in ('rm', 'it', 'normal'):
858869
uniindex = stix_glyph_fixes.get(uniindex, uniindex)
859870

860871
# Handle private use area glyphs
@@ -1875,7 +1886,7 @@ def font(self) -> str:
18751886

18761887
@font.setter
18771888
def font(self, name: str) -> None:
1878-
if name in ('rm', 'it', 'bf', 'bfit'):
1889+
if name in ('normal', 'rm', 'it', 'bf', 'bfit'):
18791890
self.font_class = name
18801891
self._font = name
18811892

@@ -2047,7 +2058,7 @@ class _MathStyle(enum.Enum):
20472058
_dropsub_symbols = set(r'\int \oint \iint \oiint \iiint \oiiint \iiiint'.split())
20482059

20492060
_fontnames = set("rm cal it tt sf bf bfit "
2050-
"default bb frak scr regular".split())
2061+
"default bb frak scr regular normal".split())
20512062

20522063
_function_names = set("""
20532064
arccos csc ker min arcsin deg lg Pr arctan det lim sec arg dim
@@ -2304,7 +2315,7 @@ def non_math(self, toks: ParseResults) -> T.Any:
23042315
s = toks[0].replace(r'\$', '$')
23052316
symbols = [Char(c, self.get_state()) for c in s]
23062317
hlist = Hlist(symbols)
2307-
# We're going into math now, so set font to 'it'
2318+
# We're going into math now, so set font to 'normal'
23082319
self.push_state()
23092320
self.get_state().font = mpl.rcParams['mathtext.default']
23102321
return [hlist]
@@ -2323,13 +2334,14 @@ def _make_space(self, percentage: float) -> Kern:
23232334
# In TeX, an em (the unit usually used to measure horizontal lengths)
23242335
# is not the width of the character 'm'; it is the same in different
23252336
# font styles (e.g. roman or italic). Mathtext, however, uses 'm' in
2326-
# the italic style so that horizontal spaces don't depend on the
2337+
# the normal style so that horizontal spaces don't depend on the
23272338
# current font style.
2339+
# TODO: this should be read from the font file
23282340
state = self.get_state()
23292341
key = (state.font, state.fontsize, state.dpi)
23302342
width = self._em_width_cache.get(key)
23312343
if width is None:
2332-
width = state.fontset.get_quad('it', state.fontsize, state.dpi)
2344+
width = state.fontset.get_quad('normal', state.fontsize, state.dpi)
23332345
self._em_width_cache[key] = width
23342346
return Kern(width * percentage)
23352347

lib/matplotlib/_mathtext_data.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,6 @@
161161
'(' : ('cmr10', 0x28),
162162
')' : ('cmr10', 0x29),
163163
'+' : ('cmr10', 0x2b),
164-
'0' : ('cmr10', 0x30),
165-
'1' : ('cmr10', 0x31),
166-
'2' : ('cmr10', 0x32),
167-
'3' : ('cmr10', 0x33),
168-
'4' : ('cmr10', 0x34),
169-
'5' : ('cmr10', 0x35),
170-
'6' : ('cmr10', 0x36),
171-
'7' : ('cmr10', 0x37),
172-
'8' : ('cmr10', 0x38),
173-
'9' : ('cmr10', 0x39),
174164
':' : ('cmr10', 0x3a),
175165
';' : ('cmr10', 0x3b),
176166
'=' : ('cmr10', 0x3d),
@@ -1350,7 +1340,7 @@
13501340
"\N{DOUBLE-STRUCK CAPITAL PI}"),
13511341
("\N{GREEK CAPITAL LETTER SIGMA}",
13521342
"\N{GREEK CAPITAL LETTER SIGMA}",
1353-
"it",
1343+
"rm", # not in STIX italic
13541344
"\N{DOUBLE-STRUCK N-ARY SUMMATION}"), # \Sigma (not in beta STIX fonts)
13551345
("\N{GREEK SMALL LETTER GAMMA}",
13561346
"\N{GREEK SMALL LETTER GAMMA}",
@@ -1778,6 +1768,9 @@
17781768
],
17791769
}
17801770

1771+
_stix_virtual_fonts['bb']['normal'] = _stix_virtual_fonts['bb']['it'] # type:ignore[call-overload]
1772+
_stix_virtual_fonts['sf']['normal'] = _stix_virtual_fonts['sf']['it'] # type:ignore[call-overload]
1773+
17811774

17821775
@overload
17831776
def _normalize_stix_fontcodes(d: _EntryTypeIn) -> _EntryTypeOut: ...

lib/matplotlib/font_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,7 @@ class FontManager:
11341134
# Increment this version number whenever the font cache data
11351135
# format or behavior has changed and requires an existing font
11361136
# cache files to be rebuilt.
1137-
__version__ = '3.11.0a2'
1137+
__version__ = '3.11.0a3'
11381138

11391139
def __init__(self, size=None, weight='normal'):
11401140
self._version = self.__version__

0 commit comments

Comments
 (0)