Skip to content

Commit 972b815

Browse files
committed
Directly support ^% in pgf.
Support for `^` is inspired from the support for `_` as implemented in underscore.sty, but much simpler because 1) we don't care about hyphenation, 2) we don't care about substituting a real underscore by a rule box when the current font lacks an underscore (there's no real way out if the font has no caret, anyways), and 3) we don't care about the active character ending up in tex `\commands` because the character is only made active in the context of matplotlib-provided strings, not globally.
1 parent 2716c82 commit 972b815

File tree

2 files changed

+24
-16
lines changed

2 files changed

+24
-16
lines changed

lib/matplotlib/backends/backend_pgf.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,6 @@ def _get_preamble():
8585

8686
_NO_ESCAPE = r"(?<!\\)(?:\\\\)*"
8787
_split_math = re.compile(_NO_ESCAPE + r"\$").split
88-
_replace_escapetext = functools.partial(
89-
# When the next character is an unescaped % or ^, insert a backslash.
90-
re.compile(_NO_ESCAPE + "(?=[%^])").sub, "\\\\")
9188
_replace_mathdefault = functools.partial(
9289
# Replace \mathdefault (when not preceded by an escape) by empty string.
9390
re.compile(_NO_ESCAPE + r"(\\mathdefault)").sub, "")
@@ -106,8 +103,6 @@ def _tex_escape(text):
106103
This distinguishes text-mode and math-mode by replacing the math separator
107104
``$`` with ``\(\displaystyle %s\)``. Escaped math separators (``\$``)
108105
are ignored.
109-
110-
The following characters are escaped in text segments: ``^%``
111106
"""
112107
# Sometimes, matplotlib adds the unknown command \mathdefault.
113108
# Not using \mathnormal instead since this looks odd for the latex cm font.
@@ -116,11 +111,7 @@ def _tex_escape(text):
116111
# split text into normaltext and inline math parts
117112
parts = _split_math(text)
118113
for i, s in enumerate(parts):
119-
if not i % 2:
120-
# textmode replacements
121-
s = _replace_escapetext(s)
122-
else:
123-
# mathmode replacements
114+
if i % 2: # mathmode replacements
124115
s = r"\(\displaystyle %s\)" % s
125116
parts[i] = s
126117
return "".join(parts)
@@ -168,7 +159,17 @@ def _escape_and_apply_props(s, prop):
168159
commands.append(r"\bfseries")
169160

170161
commands.append(r"\selectfont")
171-
return "".join(commands) + " " + _tex_escape(s)
162+
return (
163+
"{"
164+
+ "".join(commands)
165+
+ r"\catcode`\^=\active\def^{\ifmmode\sp\else\^{}\fi}"
166+
# It should normally be enough to set the catcode of % to 12 ("normal
167+
# character"); this works on TeXLive 2021 but not on 2018, so we just
168+
# make it active too.
169+
+ r"\catcode`\%=\active\def%{\%}"
170+
+ _tex_escape(s)
171+
+ "}"
172+
)
172173

173174

174175
def _metadata_to_str(key, value):
@@ -357,7 +358,11 @@ def _get_box_metrics(self, tex):
357358
"""
358359
# This method gets wrapped in __init__ for per-instance caching.
359360
self._stdin_writeln( # Send textbox to TeX & request metrics typeout.
360-
r"\sbox0{%s}\typeout{\the\wd0,\the\ht0,\the\dp0}" % tex)
361+
# \sbox doesn't handle catcode assignments inside its argument,
362+
# so repeat the assignment of the catcode of "^" and "%" outside.
363+
r"{\catcode`\^=\active\catcode`\%%=\active\sbox0{%s}"
364+
r"\typeout{\the\wd0,\the\ht0,\the\dp0}}"
365+
% tex)
361366
try:
362367
answer = self._expect_prompt()
363368
except LatexError as err:

lib/matplotlib/tests/test_backend_pgf.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,22 @@ def compare_figure(fname, savefig_kwargs={}, tol=0):
3535

3636
@pytest.mark.parametrize('plain_text, escaped_text', [
3737
(r'quad_sum: $\sum x_i^2$', r'quad_sum: \(\displaystyle \sum x_i^2\)'),
38-
('% not a comment', r'\% not a comment'),
39-
('^not', r'\^not'),
4038
])
4139
def test_tex_escape(plain_text, escaped_text):
4240
assert _tex_escape(plain_text) == escaped_text
4341

4442

4543
@needs_pgf_xelatex
44+
@needs_ghostscript
4645
@pytest.mark.backend('pgf')
4746
def test_tex_special_chars(tmp_path):
4847
fig = plt.figure()
49-
fig.text(.5, .5, "_^ $a_b^c$")
50-
fig.savefig(tmp_path / "test.pdf") # Should not error.
48+
fig.text(.5, .5, "%_^ $a_b^c$")
49+
buf = BytesIO()
50+
fig.savefig(buf, format="png", backend="pgf")
51+
buf.seek(0)
52+
t = plt.imread(buf)
53+
assert not (t == 1).all() # The leading "%" didn't eat up everything.
5154

5255

5356
def create_figure():

0 commit comments

Comments
 (0)