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

Skip to content

Commit da76485

Browse files
ahmed5145claude
andcommitted
Fix mathtext spacing of operators at expression boundaries
Relation, binary and arrow operators are "spaced symbols": mathtext puts a space on each side of them. TeX, however, discards the glue at the boundaries of a math list, so an operator at the very start or end of an expression (or of a {...} group) is not spaced on that side -- e.g. in "a$=b$" there is no space before the "=", only after it. mathtext instead spaced both sides, and only binary operators were special-cased at the start of the string. Decide the left and right spacing of a spaced symbol independently, and drop the space on any side that abuts the start/end of the expression or an opening/closing delimiter. The lookahead for the next character now skips over the source token (which may be a multi-character command such as "\doteq") instead of just loc + 1. This restores the simpler \ddots test that had to be worked around with negative \hspace, and adds a check_figures_equal regression test. Closes #23315 Co-Authored-By: Claude Opus 4.8 <[email protected]>
1 parent ff01737 commit da76485

2 files changed

Lines changed: 42 additions & 12 deletions

File tree

lib/matplotlib/_mathtext.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2452,21 +2452,33 @@ def symbol(self, s: str, loc: int,
24522452
"Unknown symbol: %s" % c) from err
24532453

24542454
if c in self._spaced_symbols:
2455-
# iterate until we find previous character, needed for cases
2456-
# such as $=-2$, ${ -2}$, $ -2$, or $ -2$.
2455+
# Find the previous and next characters (skipping spaces), needed
2456+
# for cases such as $=-2$, ${ -2}$, $ -2$, or $ -2$. loc points
2457+
# at the start of this symbol, so the next character is looked for
2458+
# after its source token, which may be a multi-character command
2459+
# such as "\doteq".
24572460
prev_char = next((c for c in s[:loc][::-1] if c != ' '), '')
2458-
# Binary operators at start of string should not be spaced
2459-
# Also, operators in sub- or superscripts should not be spaced
2461+
next_char = next(
2462+
(c for c in s[loc + len(toks["sym"]):] if c != ' '), '')
2463+
# A binary operator acting as a unary operator -- at the start of
2464+
# the string, or following another operator or an opening delimiter
2465+
# -- is not spaced. Operators in sub- or superscripts are not
2466+
# spaced either.
24602467
if (self._needs_space_after_subsuper or (
24612468
c in self._binary_operators and (
2462-
len(s[:loc].split()) == 0 or prev_char in {
2463-
'{', *self._left_delims, *self._relation_symbols}))):
2469+
prev_char in {'', '{', *self._left_delims,
2470+
*self._relation_symbols}))):
24642471
return [char]
2465-
else:
2466-
return [Hlist([self._make_space(0.2),
2467-
char,
2468-
self._make_space(0.2)],
2469-
do_kern=True)]
2472+
# Otherwise a spaced symbol gets a space on each side, except on a
2473+
# side where it abuts the start or end of the (sub-)expression: TeX
2474+
# discards the glue at the boundaries of a math list, so e.g. in
2475+
# "a$=b$" there is no space before the "=" (gh-23315).
2476+
left_space = prev_char not in {'', '{', *self._left_delims}
2477+
right_space = next_char not in {'', '}', *self._right_delims}
2478+
return [Hlist([*([self._make_space(0.2)] if left_space else []),
2479+
char,
2480+
*([self._make_space(0.2)] if right_space else [])],
2481+
do_kern=True)]
24702482
elif c in self._punctuation_symbols:
24712483
prev_char = next((c for c in s[:loc][::-1] if c != ' '), '')
24722484
next_char = next((c for c in s[loc + 1:] if c != ' '), '')

lib/matplotlib/tests/test_mathtext.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
r'$\mathring{A} \AA$',
115115
r'$M \, M \thinspace M \/ M \> M \: M \; M \ M \enspace M \quad M \qquad M \! M$',
116116
r'$\Cap$ $\Cup$ $\leftharpoonup$ $\barwedge$ $\rightharpoonup$',
117-
r'$\hspace{-0.2}\dotplus\hspace{-0.2}$ $\hspace{-0.2}\doteq\hspace{-0.2}$ $\hspace{-0.2}\doteqdot\hspace{-0.2}$ $\ddots$',
117+
r'$\dotplus$ $\doteq$ $\doteqdot$ $\ddots$', # github issue #23315
118118
r'$xyz^kx_kx^py^{p-2} d_i^jb_jc_kd x^j_i E^0 E^0_u$', # github issue #4873
119119
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$',
120120
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$',
@@ -596,6 +596,24 @@ def test_text_escaped_braces(fig_test, fig_ref):
596596
fig_ref.text(0.1, 0.2, r"$\text{\{example\}}$")
597597

598598

599+
@check_figures_equal()
600+
def test_spaced_operator_at_boundary(fig_test, fig_ref):
601+
# A spaced operator (relation, binary, or arrow) at the start or end of a
602+
# math expression is not spaced on the boundary side, matching TeX, which
603+
# discards glue at the boundaries of a math list (gh-23315). The reference
604+
# cancels the would-be boundary space with a matching negative \hspace.
605+
fig_test.text(0.5, 0.1, r"$=b$") # relation at start
606+
fig_test.text(0.5, 0.3, r"$b=$") # relation at end
607+
fig_test.text(0.5, 0.5, r"$\doteq$") # named relation, both ends
608+
fig_test.text(0.5, 0.7, r"$b\doteq$") # named relation at end
609+
fig_test.text(0.5, 0.9, r"$\leftharpoonup$") # arrow, both ends
610+
fig_ref.text(0.5, 0.1, r"$\hspace{-0.2}=b$")
611+
fig_ref.text(0.5, 0.3, r"$b=\hspace{-0.2}$")
612+
fig_ref.text(0.5, 0.5, r"$\hspace{-0.2}\doteq\hspace{-0.2}$")
613+
fig_ref.text(0.5, 0.7, r"$b\doteq\hspace{-0.2}$")
614+
fig_ref.text(0.5, 0.9, r"$\hspace{-0.2}\leftharpoonup\hspace{-0.2}$")
615+
616+
599617
@check_figures_equal()
600618
def test_boldsymbol(fig_test, fig_ref):
601619
fig_test.text(0.1, 0.2, r"$\boldsymbol{\mathrm{abc0123\alpha}}$")

0 commit comments

Comments
 (0)