X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/release/next_whats_new/mathnormal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Mathtext distinguishes *italic* and *normal* font
-------------------------------------------------

Matplotlib's lightweight TeX expression parser (``usetex=False``) now distinguishes between *italic* and *normal* math fonts to closer replicate the behaviour of LaTeX.
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``.
The main difference is that *italic* produces italic digits, whereas *normal* produces upright digits. Previously, it was not possible to typeset italic digits.
Note that ``normal`` now corresponds to what used to be ``it``, whereas ``it`` now renders all characters italic.
**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.

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.
105 changes: 48 additions & 57 deletions lib/matplotlib/_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,9 @@ def get_metrics(self, font: str, font_class: str, sym: str, fontsize: float,
----------
font : str
One of the TeX font names: "tt", "it", "rm", "cal", "sf", "bf",
"default", "regular", "bb", "frak", "scr". "default" and "regular"
are synonyms and use the non-math font.
"default", "regular", "normal", "bb", "frak", "scr". "default"
and "regular" are synonyms and use the non-math font.
"normal" denotes the normal math font.
font_class : str
One of the TeX font names (as for *font*), but **not** "bb",
"frak", or "scr". This is used to combine two font classes. The
Expand Down Expand Up @@ -341,6 +342,9 @@ def get_sized_alternatives_for_symbol(self, fontname: str,
"""
return [(fontname, sym)]

def get_font_constants(self) -> type[FontConstantsBase]:
return FontConstantsBase


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

def get_axis_height(self, fontname: str, fontsize: float, dpi: float) -> float:
consts = _get_font_constants(self, fontname)
consts = self.get_font_constants()
if consts.axis_height is not None:
return consts.axis_height * fontsize * dpi / 72
else:
Expand All @@ -431,7 +435,7 @@ def get_axis_height(self, fontname: str, fontsize: float, dpi: float) -> float:
return (metrics.ymax + metrics.ymin) / 2

def get_quad(self, fontname: str, fontsize: float, dpi: float) -> float:
consts = _get_font_constants(self, fontname)
consts = self.get_font_constants()
if consts.quad is not None:
return consts.quad * fontsize * dpi / 72
else:
Expand Down Expand Up @@ -474,10 +478,11 @@ class BakomaFonts(TruetypeFonts):
its own proprietary 8-bit encoding.
"""
_fontmap = {
'normal': 'cmmi10',
'cal': 'cmsy10',
'rm': 'cmr10',
'tt': 'cmtt10',
'it': 'cmmi10',
'it': 'cmti10',
'bf': 'cmb10',
'sf': 'cmss10',
'ex': 'cmex10',
Expand All @@ -497,12 +502,18 @@ def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlag
def _get_glyph(self, fontname: str, font_class: str,
sym: str) -> tuple[FT2Font, CharacterCodeType, bool]:
font = None

if fontname in self.fontmap and sym in latex_to_bakoma:
basename, num = latex_to_bakoma[sym]
slanted = (basename == "cmmi10") or sym in self._slanted_symbols
slanted = (basename in ("cmmi10", "cmti10")) or sym in self._slanted_symbols
font = self._get_font(basename)
elif len(sym) == 1:
slanted = (fontname == "it")
slanted = (fontname in ("it", "normal"))
if fontname == "normal" and sym.isdigit():
# use digits from cmr (roman alphabet) instead of cmm (math alphabet),
# same as LaTeX does.
fontname = "rm"
slanted = False
font = self._get_font(fontname)
if font is not None:
num = ord(sym)
Expand Down Expand Up @@ -551,6 +562,9 @@ def get_sized_alternatives_for_symbol(self, fontname: str,
sym: str) -> list[tuple[str, str]]:
return self._size_alternatives.get(sym, [(fontname, sym)])

def get_font_constants(self) -> type[FontConstantsBase]:
return ComputerModernFontConstants


class UnicodeFonts(TruetypeFonts):
"""
Expand Down Expand Up @@ -630,11 +644,14 @@ def _get_glyph(self, fontname: str, font_class: str,
# Only characters in the "Letter" class should be italicized in 'it'
# mode. Greek capital letters should be Roman.
if found_symbol:
if fontname == 'it' and uniindex < 0x10000:
if fontname == 'normal' and uniindex < 0x10000:
# normal mathematics font
char = chr(uniindex)
if (unicodedata.category(char)[0] != "L"
or unicodedata.name(char).startswith("GREEK CAPITAL")):
new_fontname = 'rm'
else:
new_fontname = 'it'

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

if not found_symbol:
if self._fallback_font:
if (fontname in ('it', 'regular')
if (fontname in ('it', 'regular', 'normal')
and isinstance(self._fallback_font, StixFonts)):
fontname = 'rm'

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

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

def get_font_constants(self) -> type[FontConstantsBase]:
return DejaVuSerifFontConstants


class DejaVuSansFonts(DejaVuFonts):
"""
Expand All @@ -759,6 +779,9 @@ class DejaVuSansFonts(DejaVuFonts):
0: 'DejaVu Sans',
}

def get_font_constants(self) -> type[FontConstantsBase]:
return DejaVuSansFontConstants


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

# Fix some incorrect glyphs.
if fontname in ('rm', 'it'):
if fontname in ('rm', 'it', 'normal'):
uniindex = stix_glyph_fixes.get(uniindex, uniindex)

# Handle private use area glyphs
Expand Down Expand Up @@ -874,6 +897,12 @@ def get_sized_alternatives_for_symbol( # type: ignore[override]
alternatives = alternatives[:-1]
return alternatives

def get_font_constants(self) -> type[FontConstantsBase]:
if self._sans:
return STIXSansFontConstants
else:
return STIXFontConstants


class StixSansFonts(StixFonts):
"""
Expand Down Expand Up @@ -1078,45 +1107,6 @@ class DejaVuSansFontConstants(FontConstantsBase):
axis_height = 512 / 2048


# Maps font family names to the FontConstantBase subclass to use
_font_constant_mapping = {
'DejaVu Sans': DejaVuSansFontConstants,
'DejaVu Sans Mono': DejaVuSansFontConstants,
'DejaVu Serif': DejaVuSerifFontConstants,
'cmb10': ComputerModernFontConstants,
'cmex10': ComputerModernFontConstants,
'cmmi10': ComputerModernFontConstants,
'cmr10': ComputerModernFontConstants,
'cmss10': ComputerModernFontConstants,
'cmsy10': ComputerModernFontConstants,
'cmtt10': ComputerModernFontConstants,
'STIXGeneral': STIXFontConstants,
'STIXNonUnicode': STIXFontConstants,
'STIXSizeFiveSym': STIXFontConstants,
'STIXSizeFourSym': STIXFontConstants,
'STIXSizeThreeSym': STIXFontConstants,
'STIXSizeTwoSym': STIXFontConstants,
'STIXSizeOneSym': STIXFontConstants,
# Map the fonts we used to ship, just for good measure
'Bitstream Vera Sans': DejaVuSansFontConstants,
'Bitstream Vera': DejaVuSansFontConstants,
}


def _get_font_constants(fontset: Fonts, font: str) -> type[FontConstantsBase]:
constants = _font_constant_mapping.get(fontset._get_font(font).family_name,
FontConstantsBase)
# STIX sans isn't really its own fonts, just different code points
# in the STIX fonts, so we have to detect this one separately.
if constants is STIXFontConstants and isinstance(fontset, StixSansFonts):
return STIXSansFontConstants
return constants


def _get_font_constant_set(state: ParserState) -> type[FontConstantsBase]:
return _get_font_constants(state.fontset, state.font)


class Node:
"""A node in the TeX box model."""

Expand Down Expand Up @@ -1896,7 +1886,7 @@ def font(self) -> str:

@font.setter
def font(self, name: str) -> None:
if name in ('rm', 'it', 'bf', 'bfit'):
if name in ('normal', 'rm', 'it', 'bf', 'bfit'):
self.font_class = name
self._font = name

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

_fontnames = set("rm cal it tt sf bf bfit "
"default bb frak scr regular".split())
"default bb frak scr regular normal".split())

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

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

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

axis_height = state.fontset.get_axis_height(
state.font, state.fontsize, state.dpi)
consts = _get_font_constant_set(state)
consts = state.fontset.get_font_constants()
x_height = state.fontset.get_xheight(state.font, state.fontsize, state.dpi)

for _ in range(style.value):
Expand Down
15 changes: 4 additions & 11 deletions lib/matplotlib/_mathtext_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,6 @@
'(' : ('cmr10', 0x28),
')' : ('cmr10', 0x29),
'+' : ('cmr10', 0x2b),
'0' : ('cmr10', 0x30),
'1' : ('cmr10', 0x31),
'2' : ('cmr10', 0x32),
'3' : ('cmr10', 0x33),
'4' : ('cmr10', 0x34),
'5' : ('cmr10', 0x35),
'6' : ('cmr10', 0x36),
'7' : ('cmr10', 0x37),
'8' : ('cmr10', 0x38),
'9' : ('cmr10', 0x39),
':' : ('cmr10', 0x3a),
';' : ('cmr10', 0x3b),
'=' : ('cmr10', 0x3d),
Expand Down Expand Up @@ -1350,7 +1340,7 @@
"\N{DOUBLE-STRUCK CAPITAL PI}"),
("\N{GREEK CAPITAL LETTER SIGMA}",
"\N{GREEK CAPITAL LETTER SIGMA}",
"it",
"rm", # not in STIX italic
"\N{DOUBLE-STRUCK N-ARY SUMMATION}"), # \Sigma (not in beta STIX fonts)
("\N{GREEK SMALL LETTER GAMMA}",
"\N{GREEK SMALL LETTER GAMMA}",
Expand Down Expand Up @@ -1778,6 +1768,9 @@
],
}

_stix_virtual_fonts['bb']['normal'] = _stix_virtual_fonts['bb']['it'] # type:ignore[call-overload]
_stix_virtual_fonts['sf']['normal'] = _stix_virtual_fonts['sf']['it'] # type:ignore[call-overload]


@overload
def _normalize_stix_fontcodes(d: _EntryTypeIn) -> _EntryTypeOut: ...
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ class FontManager:
# Increment this version number whenever the font cache data
# format or behavior has changed and requires an existing font
# cache files to be rebuilt.
__version__ = '3.11.0a2'
__version__ = '3.11.0a3'

def __init__(self, size=None, weight='normal'):
self._version = self.__version__
Expand Down
Loading
Loading
X Tutup