Skip to content

Commit d14bb6b

Browse files
QuLogicmeeseeksmachine
authored andcommitted
Backport PR matplotlib#32147: Fix nested braces in mathtext \text arguments
1 parent a516b06 commit d14bb6b

2 files changed

Lines changed: 68 additions & 4 deletions

File tree

lib/matplotlib/_mathtext.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
from numpy.typing import NDArray
2525
from pyparsing import (
2626
Empty, Forward, Literal, Group, NotAny, OneOrMore, Optional,
27-
ParseBaseException, ParseExpression, ParseFatalException,
28-
ParserElement, ParseResults, QuotedString, Regex, StringEnd, ZeroOrMore,
29-
pyparsing_common, nested_expr, one_of)
27+
ParseBaseException, ParseException, ParseExpression, ParseFatalException,
28+
ParserElement, ParseResults, QuotedString, Regex, StringEnd, Token,
29+
ZeroOrMore, pyparsing_common, nested_expr, one_of)
3030

3131
import matplotlib as mpl
3232
from . import cbook
@@ -1873,6 +1873,50 @@ def raise_error(s: str, loc: int, toks: ParseResults) -> T.Any:
18731873
return Empty().set_parse_action(raise_error)
18741874

18751875

1876+
class _BracedText(Token):
1877+
r"""
1878+
Match a brace-delimited literal string, allowing nested braces.
1879+
1880+
This is similar to ``QuotedString("{", "\\", end_quote_char="}")``, except
1881+
that brace depth is tracked, so that the string does not end at the first
1882+
``}``. As in TeX, nested unescaped braces only group, and are not
1883+
rendered; a literal brace is written as ``\{`` or ``\}``. A backslash
1884+
escapes the following character, which therefore does not affect depth.
1885+
"""
1886+
1887+
_escapes = {"t": "\t", "n": "\n", "f": "\f", "r": "\r"}
1888+
1889+
def __init__(self) -> None:
1890+
super().__init__()
1891+
self.mayReturnEmpty = True
1892+
self.mayIndexError = False
1893+
1894+
def parseImpl(self, instring: str, loc: int,
1895+
do_actions: bool = True) -> tuple[int, str]:
1896+
if loc >= len(instring) or instring[loc] != "{":
1897+
raise ParseException(instring, loc, "Expected '{'", self)
1898+
chars = []
1899+
depth = 0
1900+
while loc < len(instring):
1901+
char = instring[loc]
1902+
if char == "\\" and loc + 1 < len(instring):
1903+
escaped = instring[loc + 1]
1904+
chars.append(self._escapes.get(escaped, escaped))
1905+
loc += 2
1906+
continue
1907+
loc += 1
1908+
if char == "{":
1909+
depth += 1
1910+
continue
1911+
elif char == "}":
1912+
depth -= 1
1913+
if depth == 0:
1914+
return loc, "".join(chars)
1915+
continue
1916+
chars.append(char)
1917+
raise ParseException(instring, loc, "Expected '}'", self)
1918+
1919+
18761920
class ParserState:
18771921
"""
18781922
Parser state.
@@ -2216,7 +2260,7 @@ def csnames(group: str, names: Iterable[str]) -> Regex:
22162260
r"\underset",
22172261
p.optional_group("annotation") + p.optional_group("body"))
22182262

2219-
p.text = cmd(r"\text", QuotedString('{', '\\', end_quote_char="}"))
2263+
p.text = cmd(r"\text", _BracedText())
22202264

22212265
p.substack = cmd(r"\substack",
22222266
nested_expr(opener="{", closer="}",

lib/matplotlib/tests/test_mathtext.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ def test_fontinfo():
337337
(r'$a_2_2$', r'Double subscript'),
338338
(r'$a^2_a^2$', r'Double superscript'),
339339
(r'$a = {b$', r"Expected '}'"),
340+
(r'$\text$', r'Expected \text'),
341+
(r'$\text{foo$', r'Expected \text'),
340342
],
341343
ids=[
342344
'hspace without value',
@@ -366,6 +368,8 @@ def test_fontinfo():
366368
'double subscript',
367369
'super on sub without braces',
368370
'unclosed group',
371+
'text without argument',
372+
'text with unclosed argument',
369373
]
370374
)
371375
def test_mathtext_exceptions(math, msg):
@@ -576,6 +580,22 @@ def test_mathtext_single_char_super_with_prime(expr):
576580
parser.parse(expr)
577581

578582

583+
@check_figures_equal()
584+
def test_text_nested_braces(fig_test, fig_ref):
585+
# Nested braces group as in TeX, and are not rendered (gh-32105).
586+
fig_test.text(0.1, 0.2, r"$\text{{example}}$")
587+
fig_test.text(0.1, 0.5, r"$\text{a{b}{{c}}d}$")
588+
fig_ref.text(0.1, 0.2, r"$\text{example}$")
589+
fig_ref.text(0.1, 0.5, r"$\text{abcd}$")
590+
591+
592+
@check_figures_equal()
593+
def test_text_escaped_braces(fig_test, fig_ref):
594+
# Escaped braces are still rendered as literal braces (gh-32105).
595+
fig_test.text(0.1, 0.2, r"$\text{{\{example\}}}$")
596+
fig_ref.text(0.1, 0.2, r"$\text{\{example\}}$")
597+
598+
579599
@check_figures_equal()
580600
def test_boldsymbol(fig_test, fig_ref):
581601
fig_test.text(0.1, 0.2, r"$\boldsymbol{\mathrm{abc0123\alpha}}$")

0 commit comments

Comments
 (0)