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

Skip to content

Commit 3539117

Browse files
authored
Merge pull request #31121 from llohse/feat-mathnormal
mathtext: add mathnormal and distinguish between normal and italic family
2 parents 05b7879 + 77d4e52 commit 3539117

10 files changed

Lines changed: 414 additions & 80 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: 48 additions & 57 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
@@ -341,6 +342,9 @@ def get_sized_alternatives_for_symbol(self, fontname: str,
341342
"""
342343
return [(fontname, sym)]
343344

345+
def get_font_constants(self) -> type[FontConstantsBase]:
346+
return FontConstantsBase
347+
344348

345349
class TruetypeFonts(Fonts, metaclass=abc.ABCMeta):
346350
"""
@@ -420,7 +424,7 @@ def _get_info(self, fontname: str, font_class: str, sym: str, fontsize: float,
420424
)
421425

422426
def get_axis_height(self, fontname: str, fontsize: float, dpi: float) -> float:
423-
consts = _get_font_constants(self, fontname)
427+
consts = self.get_font_constants()
424428
if consts.axis_height is not None:
425429
return consts.axis_height * fontsize * dpi / 72
426430
else:
@@ -431,7 +435,7 @@ def get_axis_height(self, fontname: str, fontsize: float, dpi: float) -> float:
431435
return (metrics.ymax + metrics.ymin) / 2
432436

433437
def get_quad(self, fontname: str, fontsize: float, dpi: float) -> float:
434-
consts = _get_font_constants(self, fontname)
438+
consts = self.get_font_constants()
435439
if consts.quad is not None:
436440
return consts.quad * fontsize * dpi / 72
437441
else:
@@ -474,10 +478,11 @@ class BakomaFonts(TruetypeFonts):
474478
its own proprietary 8-bit encoding.
475479
"""
476480
_fontmap = {
481+
'normal': 'cmmi10',
477482
'cal': 'cmsy10',
478483
'rm': 'cmr10',
479484
'tt': 'cmtt10',
480-
'it': 'cmmi10',
485+
'it': 'cmti10',
481486
'bf': 'cmb10',
482487
'sf': 'cmss10',
483488
'ex': 'cmex10',
@@ -497,12 +502,18 @@ def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlag
497502
def _get_glyph(self, fontname: str, font_class: str,
498503
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
499504
font = None
505+
500506
if fontname in self.fontmap and sym in latex_to_bakoma:
501507
basename, num = latex_to_bakoma[sym]
502-
slanted = (basename == "cmmi10") or sym in self._slanted_symbols
508+
slanted = (basename in ("cmmi10", "cmti10")) or sym in self._slanted_symbols
503509
font = self._get_font(basename)
504510
elif len(sym) == 1:
505-
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
506517
font = self._get_font(fontname)
507518
if font is not None:
508519
num = ord(sym)
@@ -551,6 +562,9 @@ def get_sized_alternatives_for_symbol(self, fontname: str,
551562
sym: str) -> list[tuple[str, str]]:
552563
return self._size_alternatives.get(sym, [(fontname, sym)])
553564

565+
def get_font_constants(self) -> type[FontConstantsBase]:
566+
return ComputerModernFontConstants
567+
554568

555569
class UnicodeFonts(TruetypeFonts):
556570
"""
@@ -630,11 +644,14 @@ def _get_glyph(self, fontname: str, font_class: str,
630644
# Only characters in the "Letter" class should be italicized in 'it'
631645
# mode. Greek capital letters should be Roman.
632646
if found_symbol:
633-
if fontname == 'it' and uniindex < 0x10000:
647+
if fontname == 'normal' and uniindex < 0x10000:
648+
# normal mathematics font
634649
char = chr(uniindex)
635650
if (unicodedata.category(char)[0] != "L"
636651
or unicodedata.name(char).startswith("GREEK CAPITAL")):
637652
new_fontname = 'rm'
653+
else:
654+
new_fontname = 'it'
638655

639656
slanted = (new_fontname == 'it') or sym in self._slanted_symbols
640657
found_symbol = False
@@ -651,7 +668,7 @@ def _get_glyph(self, fontname: str, font_class: str,
651668

652669
if not found_symbol:
653670
if self._fallback_font:
654-
if (fontname in ('it', 'regular')
671+
if (fontname in ('it', 'regular', 'normal')
655672
and isinstance(self._fallback_font, StixFonts)):
656673
fontname = 'rm'
657674

@@ -663,7 +680,7 @@ def _get_glyph(self, fontname: str, font_class: str,
663680
return g
664681

665682
else:
666-
if (fontname in ('it', 'regular')
683+
if (fontname in ('it', 'regular', 'normal')
667684
and isinstance(self, StixFonts)):
668685
return self._get_glyph('rm', font_class, sym)
669686
_log.warning("Font %r does not have a glyph for %a [U+%x], "
@@ -741,6 +758,9 @@ class DejaVuSerifFonts(DejaVuFonts):
741758
0: 'DejaVu Serif',
742759
}
743760

761+
def get_font_constants(self) -> type[FontConstantsBase]:
762+
return DejaVuSerifFontConstants
763+
744764

745765
class DejaVuSansFonts(DejaVuFonts):
746766
"""
@@ -759,6 +779,9 @@ class DejaVuSansFonts(DejaVuFonts):
759779
0: 'DejaVu Sans',
760780
}
761781

782+
def get_font_constants(self) -> type[FontConstantsBase]:
783+
return DejaVuSansFontConstants
784+
762785

763786
class StixFonts(UnicodeFonts):
764787
"""
@@ -842,7 +865,7 @@ def _map_virtual_font(self, fontname: str, font_class: str,
842865
fontname = mpl.rcParams['mathtext.default']
843866

844867
# Fix some incorrect glyphs.
845-
if fontname in ('rm', 'it'):
868+
if fontname in ('rm', 'it', 'normal'):
846869
uniindex = stix_glyph_fixes.get(uniindex, uniindex)
847870

848871
# Handle private use area glyphs
@@ -874,6 +897,12 @@ def get_sized_alternatives_for_symbol( # type: ignore[override]
874897
alternatives = alternatives[:-1]
875898
return alternatives
876899

900+
def get_font_constants(self) -> type[FontConstantsBase]:
901+
if self._sans:
902+
return STIXSansFontConstants
903+
else:
904+
return STIXFontConstants
905+
877906

878907
class StixSansFonts(StixFonts):
879908
"""
@@ -1078,45 +1107,6 @@ class DejaVuSansFontConstants(FontConstantsBase):
10781107
axis_height = 512 / 2048
10791108

10801109

1081-
# Maps font family names to the FontConstantBase subclass to use
1082-
_font_constant_mapping = {
1083-
'DejaVu Sans': DejaVuSansFontConstants,
1084-
'DejaVu Sans Mono': DejaVuSansFontConstants,
1085-
'DejaVu Serif': DejaVuSerifFontConstants,
1086-
'cmb10': ComputerModernFontConstants,
1087-
'cmex10': ComputerModernFontConstants,
1088-
'cmmi10': ComputerModernFontConstants,
1089-
'cmr10': ComputerModernFontConstants,
1090-
'cmss10': ComputerModernFontConstants,
1091-
'cmsy10': ComputerModernFontConstants,
1092-
'cmtt10': ComputerModernFontConstants,
1093-
'STIXGeneral': STIXFontConstants,
1094-
'STIXNonUnicode': STIXFontConstants,
1095-
'STIXSizeFiveSym': STIXFontConstants,
1096-
'STIXSizeFourSym': STIXFontConstants,
1097-
'STIXSizeThreeSym': STIXFontConstants,
1098-
'STIXSizeTwoSym': STIXFontConstants,
1099-
'STIXSizeOneSym': STIXFontConstants,
1100-
# Map the fonts we used to ship, just for good measure
1101-
'Bitstream Vera Sans': DejaVuSansFontConstants,
1102-
'Bitstream Vera': DejaVuSansFontConstants,
1103-
}
1104-
1105-
1106-
def _get_font_constants(fontset: Fonts, font: str) -> type[FontConstantsBase]:
1107-
constants = _font_constant_mapping.get(fontset._get_font(font).family_name,
1108-
FontConstantsBase)
1109-
# STIX sans isn't really its own fonts, just different code points
1110-
# in the STIX fonts, so we have to detect this one separately.
1111-
if constants is STIXFontConstants and isinstance(fontset, StixSansFonts):
1112-
return STIXSansFontConstants
1113-
return constants
1114-
1115-
1116-
def _get_font_constant_set(state: ParserState) -> type[FontConstantsBase]:
1117-
return _get_font_constants(state.fontset, state.font)
1118-
1119-
11201110
class Node:
11211111
"""A node in the TeX box model."""
11221112

@@ -1896,7 +1886,7 @@ def font(self) -> str:
18961886

18971887
@font.setter
18981888
def font(self, name: str) -> None:
1899-
if name in ('rm', 'it', 'bf', 'bfit'):
1889+
if name in ('normal', 'rm', 'it', 'bf', 'bfit'):
19001890
self.font_class = name
19011891
self._font = name
19021892

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

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

20732063
_function_names = set("""
20742064
arccos csc ker min arcsin deg lg Pr arctan det lim sec arg dim
@@ -2325,7 +2315,7 @@ def non_math(self, toks: ParseResults) -> T.Any:
23252315
s = toks[0].replace(r'\$', '$')
23262316
symbols = [Char(c, self.get_state()) for c in s]
23272317
hlist = Hlist(symbols)
2328-
# We're going into math now, so set font to 'it'
2318+
# We're going into math now, so set font to 'normal'
23292319
self.push_state()
23302320
self.get_state().font = mpl.rcParams['mathtext.default']
23312321
return [hlist]
@@ -2344,13 +2334,14 @@ def _make_space(self, percentage: float) -> Kern:
23442334
# In TeX, an em (the unit usually used to measure horizontal lengths)
23452335
# is not the width of the character 'm'; it is the same in different
23462336
# font styles (e.g. roman or italic). Mathtext, however, uses 'm' in
2347-
# 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
23482338
# current font style.
2339+
# TODO: this should be read from the font file
23492340
state = self.get_state()
23502341
key = (state.font, state.fontsize, state.dpi)
23512342
width = self._em_width_cache.get(key)
23522343
if width is None:
2353-
width = state.fontset.get_quad('it', state.fontsize, state.dpi)
2344+
width = state.fontset.get_quad('normal', state.fontsize, state.dpi)
23542345
self._em_width_cache[key] = width
23552346
return Kern(width * percentage)
23562347

@@ -2649,7 +2640,7 @@ def subsuper(self, s: str, loc: int, toks: ParseResults) -> T.Any:
26492640
nucleus = Hlist([nucleus])
26502641

26512642
# Handle regular sub/superscripts
2652-
consts = _get_font_constant_set(state)
2643+
consts = state.fontset.get_font_constants()
26532644
lc_height = last_char.height
26542645
lc_baseline = 0
26552646
if self.is_dropsub(last_char):
@@ -2743,7 +2734,7 @@ def _genfrac(self, ldelim: str, rdelim: str, rule: float | None, style: _MathSty
27432734

27442735
axis_height = state.fontset.get_axis_height(
27452736
state.font, state.fontsize, state.dpi)
2746-
consts = _get_font_constant_set(state)
2737+
consts = state.fontset.get_font_constants()
27472738
x_height = state.fontset.get_xheight(state.font, state.fontsize, state.dpi)
27482739

27492740
for _ in range(style.value):

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
@@ -1131,7 +1131,7 @@ class FontManager:
11311131
# Increment this version number whenever the font cache data
11321132
# format or behavior has changed and requires an existing font
11331133
# cache files to be rebuilt.
1134-
__version__ = '3.11.0a2'
1134+
__version__ = '3.11.0a3'
11351135

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

0 commit comments

Comments
 (0)