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',