diff --git a/doc/api/next_api_changes/behavior/22950-AL.rst b/doc/api/next_api_changes/behavior/22950-AL.rst new file mode 100644 index 000000000000..bd4484936e93 --- /dev/null +++ b/doc/api/next_api_changes/behavior/22950-AL.rst @@ -0,0 +1,3 @@ +``$\doteq \doteqdot \dotminus \dotplus \dots$`` are now surrounded by extra space +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... because they are correctly treated as relational or binary operators. diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 5e6ef958d452..ac7d4d813793 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -8,6 +8,7 @@ import functools import logging import os +import re import types import unicodedata @@ -1648,7 +1649,8 @@ class _MathStyle(enum.Enum): \cdot \bigtriangledown \bigcirc \cap \triangleleft \dagger \cup \triangleright \ddagger - \uplus \lhd \amalg'''.split()) + \uplus \lhd \amalg + \dotplus \dotminus'''.split()) _relation_symbols = set(r''' = < > : @@ -1661,7 +1663,7 @@ class _MathStyle(enum.Enum): \sqsubset \sqsupset \neq \smile \sqsubseteq \sqsupseteq \doteq \frown \in \ni \propto \vdash - \dashv \dots \dotplus \doteqdot'''.split()) + \dashv \dots \doteqdot'''.split()) _arrow_symbols = set(r''' \leftarrow \longleftarrow \uparrow @@ -1717,24 +1719,36 @@ def set_names_and_parse_actions(): # Root definitions. + # In TeX parlance, a csname is a control sequence name (a "\foo"). + def csnames(group, names): + ends_with_alpha = [] + ends_with_nonalpha = [] + for name in names: + if name[-1].isalpha(): + ends_with_alpha.append(name) + else: + ends_with_nonalpha.append(name) + return Regex(r"\\(?P<{}>(?:{})(?![A-Za-z]){})".format( + group, + "|".join(map(re.escape, ends_with_alpha)), + "".join(f"|{s}" for s in map(re.escape, ends_with_nonalpha)), + )) + p.float_literal = Regex(r"[-+]?([0-9]+\.?[0-9]*|\.[0-9]+)") p.space = oneOf(self._space_widths)("space") p.style_literal = oneOf( [str(e.value) for e in self._MathStyle])("style_literal") - p.single_symbol = Regex( - r"([a-zA-Z0-9 +\-*/<>=:,.;!\?&'@()\[\]|%s])|(\\[%%${}\[\]_|])" % - "\U00000080-\U0001ffff" # unicode range - )("sym") - p.accentprefixed = "\\" + oneOf(self._accentprefixed)("sym") - p.symbol_name = ( - oneOf([rf"\{sym}" for sym in tex2uni])("sym") - + Regex("(?=[^A-Za-z]|$)").leaveWhitespace()) - p.symbol = (p.single_symbol | p.symbol_name).leaveWhitespace() + p.symbol = Regex( + r"[a-zA-Z0-9 +\-*/<>=:,.;!\?&'@()\[\]|\U00000080-\U0001ffff]" + r"|\\[%${}\[\]_|]" + + r"|\\(?:{})(?![A-Za-z])".format( + "|".join(map(re.escape, tex2uni))) + )("sym").leaveWhitespace() p.unknown_symbol = Regex(r"\\[A-Za-z]*")("name") - p.font = "\\" + oneOf(self._fontnames)("font") + p.font = csnames("font", self._fontnames) p.start_group = ( Optional(r"\math" + oneOf(self._fontnames)("font")) + "{") p.end_group = Literal("}") @@ -1771,11 +1785,10 @@ def set_names_and_parse_actions(): p.customspace <<= cmd(r"\hspace", "{" + p.float_literal("space") + "}") p.accent <<= ( - "\\" - + oneOf([*self._accent_map, *self._wide_accents])("accent") + csnames("accent", [*self._accent_map, *self._wide_accents]) - p.placeable("sym")) - p.function <<= "\\" + oneOf(self._function_names)("name") + p.function <<= csnames("name", self._function_names) p.operatorname <<= cmd( r"\operatorname", "{" + ZeroOrMore(p.simple | p.unknown_symbol)("name") + "}") @@ -1816,10 +1829,8 @@ def set_names_and_parse_actions(): p.optional_group("annotation") + p.optional_group("body")) p.placeable <<= ( - p.accentprefixed # Must be before accent so named symbols that are - # prefixed with an accent name work - | p.accent # Must be before symbol as all accents are symbols - | p.symbol # Must be third to catch all named symbols and single + p.accent # Must be before symbol as all accents are symbols + | p.symbol # Must be second to catch all named symbols and single # chars not in a group | p.function | p.operatorname @@ -2015,8 +2026,6 @@ def symbol(self, s, loc, toks): return [Hlist([char, self._make_space(0.2)], do_kern=True)] return [char] - accentprefixed = symbol - def unknown_symbol(self, s, loc, toks): raise ParseFatalException(s, loc, f"Unknown symbol: {toks['name']}") @@ -2045,12 +2054,6 @@ def unknown_symbol(self, s, loc, toks): _wide_accents = set(r"widehat widetilde widebar".split()) - # make a lambda and call it to get the namespace right - _accentprefixed = (lambda am: [ - p for p in tex2uni - if any(p.startswith(a) and a != p for a in am) - ])(set(_accent_map)) - def accent(self, s, loc, toks): state = self.get_state() thickness = state.get_current_underline_thickness() diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.pdf deleted file mode 100644 index 8b8bc68dfd5b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.png deleted file mode 100644 index 9ed5a18bf57b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.svg deleted file mode 100644 index f423ee824f38..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_77.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.pdf deleted file mode 100644 index 94ca3d3c6d94..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.png deleted file mode 100644 index 82e0821cd3ea..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.svg deleted file mode 100644 index 98902d769804..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_77.svg +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.pdf deleted file mode 100644 index d6fc89ad9f25..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.png deleted file mode 100644 index b3892b25c4ee..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.svg deleted file mode 100644 index 3c9961669b69..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_77.svg +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.pdf deleted file mode 100644 index 3966ee6ddd59..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.png deleted file mode 100644 index 9ed5a18bf57b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.svg deleted file mode 100644 index f423ee824f38..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_77.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.pdf deleted file mode 100644 index 98512f54330d..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.png deleted file mode 100644 index 9ed5a18bf57b..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.png and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.svg deleted file mode 100644 index f423ee824f38..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_77.svg +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index d6e16d247f1b..f733c6c18806 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -16,7 +16,7 @@ # If test is removed, use None as placeholder math_tests = [ r'$a+b+\dot s+\dot{s}+\ldots$', - r'$x \doteq y$', + r'$x\hspace{-0.2}\doteq\hspace{-0.2}y$', r'\$100.00 $\alpha \_$', r'$\frac{\$100.00}{y}$', r'$x y$', @@ -104,12 +104,12 @@ r'$\mathring{A} \AA$', r'$M \, M \thinspace M \/ M \> M \: M \; M \ M \enspace M \quad M \qquad M \! M$', r'$\Cap$ $\Cup$ $\leftharpoonup$ $\barwedge$ $\rightharpoonup$', - r'$\dotplus$ $\doteq$ $\doteqdot$ $\ddots$', + r'$\hspace{-0.2}\dotplus\hspace{-0.2}$ $\hspace{-0.2}\doteq\hspace{-0.2}$ $\hspace{-0.2}\doteqdot\hspace{-0.2}$ $\ddots$', r'$xyz^kx_kx^py^{p-2} d_i^jb_jc_kd x^j_i E^0 E^0_u$', # github issue #4873 r'${xyz}^k{x}_{k}{x}^{p}{y}^{p-2} {d}_{i}^{j}{b}_{j}{c}_{k}{d} {x}^{j}_{i}{E}^{0}{E}^0_u$', r'${\int}_x^x x\oint_x^x x\int_{X}^{X}x\int_x x \int^x x \int_{x} x\int^{x}{\int}_{x} x{\int}^{x}_{x}x$', r'testing$^{123}$', - ' '.join('$\\' + p + '$' for p in sorted(_mathtext.Parser._accentprefixed)), + None, r'$6-2$; $-2$; $ -2$; ${-2}$; ${ -2}$; $20^{+3}_{-2}$', r'$\overline{\omega}^x \frac{1}{2}_0^x$', # github issue #5444 r'$,$ $.$ $1{,}234{, }567{ , }890$ and $1,234,567,890$', # github issue 5799 @@ -246,6 +246,19 @@ def test_mathfont_rendering(baseline_images, fontset, index, text): horizontalalignment='center', verticalalignment='center') +@check_figures_equal(extensions=["png"]) +def test_short_long_accents(fig_test, fig_ref): + acc_map = _mathtext.Parser._accent_map + short_accs = [s for s in acc_map if len(s) == 1] + corresponding_long_accs = [] + for s in short_accs: + l, = [l for l in acc_map if len(l) > 1 and acc_map[l] == acc_map[s]] + corresponding_long_accs.append(l) + fig_test.text(0, .5, "$" + "".join(rf"\{s}a" for s in short_accs) + "$") + fig_ref.text( + 0, .5, "$" + "".join(fr"\{l} a" for l in corresponding_long_accs) + "$") + + def test_fontinfo(): fontpath = mpl.font_manager.findfont("DejaVu Sans") font = mpl.ft2font.FT2Font(fontpath) @@ -258,6 +271,8 @@ def test_fontinfo(): [ (r'$\hspace{}$', r'Expected \hspace{space}'), (r'$\hspace{foo}$', r'Expected \hspace{space}'), + (r'$\sinx$', r'Unknown symbol: \sinx'), + (r'$\dotx$', r'Unknown symbol: \dotx'), (r'$\frac$', r'Expected \frac{num}{den}'), (r'$\frac{}{}$', r'Expected \frac{num}{den}'), (r'$\binom$', r'Expected \binom{num}{den}'), @@ -288,6 +303,8 @@ def test_fontinfo(): ids=[ 'hspace without value', 'hspace with invalid value', + 'function without space', + 'accent without space', 'frac without parameters', 'frac with empty parameters', 'binom without parameters',