8000 Merge pull request #23442 from anntzer/pgfescape · matplotlib/matplotlib@2329fd4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2329fd4

Browse files
authored
Merge pull request #23442 from anntzer/pgfescape
Remove need to detect math mode in pgf strings
2 parents 84cc898 + e56ff71 commit 2329fd4

File tree

2 files changed

+37
-49
lines changed

2 files changed

+37
-49
lines changed

lib/matplotlib/backends/backend_pgf.py

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@
3737
@_api.caching_module_getattr
3838
class __getattr__:
3939
NO_ESCAPE = _api.deprecated("3.6", obj_type="")(
40-
property(lambda self: _NO_ESCAPE))
40+
property(lambda self: r"(?<!\\)(?:\\\\)*"))
4141
re_mathsep = _api.deprecated("3.6", obj_type="")(
42-
property(lambda self: _split_math.__self__))
42+
property(lambda self: r"(?<!\\)(?:\\\\)*\$"))
4343

4444

4545
@_api.deprecated("3.6")
@@ -57,7 +57,15 @@ def get_preamble():
5757

5858
def _get_preamble():
5959
"""Prepare a LaTeX preamble based on the rcParams configuration."""
60-
preamble = [mpl.rcParams["pgf.preamble"]]
60+
preamble = [
61+
# Remove Matplotlib's custom command \mathdefault. (Not using
62+
# \mathnormal instead since this looks odd with Computer Modern.)
63+
r"\def\mathdefault#1{#1}",
64+
# Use displaystyle for all math.
65+
r"\everymath=\expandafter{\the\everymath\displaystyle}",
66+
# Allow pgf.preamble to override the above definitions.
67+
mpl.rcParams["pgf.preamble"],
68+
]
6169
if mpl.rcParams["pgf.texsystem"] != "pdflatex":
6270
preamble.append("\\usepackage{fontspec}")
6371
if mpl.rcParams["pgf.rcfonts"]:
@@ -82,16 +90,6 @@ def _get_preamble():
8290
mpl_in_to_pt = 1. / mpl_pt_to_in
8391

8492

85-
_NO_ESCAPE = r"(?<!\\)(?:\\\\)*"
86-
_split_math = re.compile(_NO_ESCAPE + r"\$").split
87-
_replace_escapetext = functools.partial(
88-
# When the next character is an unescaped % or ^, insert a backslash.
89-
re.compile(_NO_ESCAPE + "(?=[%^])").sub, "\\\\")
90-
_replace_mathdefault = functools.partial(
91-
# Replace \mathdefault (when not preceded by an escape) by empty string.
92-
re.compile(_NO_ESCAPE + r"(\\mathdefault)").sub, "")
93-
94-
9593
@_api.deprecated("3.6")
9694
def common_texification(text):
9795
return _tex_escape(text)
@@ -101,28 +99,8 @@ def _tex_escape(text):
10199
r"""
102100
Do some necessary and/or useful substitutions for texts to be included in
103101
LaTeX documents.
104-
105-
This distinguishes text-mode and math-mode by replacing the math separator
106-
``$`` with ``\(\displaystyle %s\)``. Escaped math separators (``\$``)
107-
are ignored.
108-
109-
The following characters are escaped in text segments: ``^%``
110102
"""
111-
# Sometimes, matplotlib adds the unknown command \mathdefault.
112-
# Not using \mathnormal instead since this looks odd for the latex cm font.
113-
text = _replace_mathdefault(text)
114-
text = text.replace("\N{MINUS SIGN}", r"\ensuremath{-}")
115-
# split text into normaltext and inline math parts
116-
parts = _split_math(text)
117-
for i, s in enumerate(parts):
118-
if not i % 2:
119-
# textmode replacements
120-
s = _replace_escapetext(s)
121-
else:
122-
# mathmode replacements
123-
s = r"\(\displaystyle %s\)" % s
124-
parts[i] = s
125-
return "".join(parts)
103+
return text.replace("\N{MINUS SIGN}", r"\ensuremath{-}")
126104

127105

128106
@_api.deprecated("3.6")
@@ -167,7 +145,17 @@ def _escape_and_apply_props(s, prop):
167145
commands.append(r"\bfseries")
168146

169147
commands.append(r"\selectfont")
170-
return "".join(commands) + " " + _tex_escape(s)
148+
return (
149+
"{"
150+
+ "".join(commands)
151+
+ r"\catcode`\^=\active\def^{\ifmmode\sp\else\^{}\fi}"
152+
# It should normally be enough to set the catcode of % to 12 ("normal
153+
# character"); this works on TeXLive 2021 but not on 2018, so we just
154+
# make it active too.
155+
+ r"\catcode`\%=\active\def%{\%}"
156+
+ _tex_escape(s)
157+
+ "}"
158+
)
171159

172160

173161
def _metadata_to_str(key, value):
@@ -349,7 +337,11 @@ def _get_box_metrics(self, tex):
349337
"""
350338
# This method gets wrapped in __init__ for per-instance caching.
351339
self._stdin_writeln( # Send textbox to TeX & request metrics typeout.
352-
r"\sbox0{%s}\typeout{\the\wd0,\the\ht0,\the\dp0}" % tex)
340+
# \sbox doesn't handle catcode assignments inside its argument,
341+
# so repeat the assignment of the catcode of "^" and "%" outside.
342+
r"{\catcode`\^=\active\catcode`\%%=\active\sbox0{%s}"
343+
r"\typeout{\the\wd0,\the\ht0,\the\dp0}}"
344+
% tex)
353345
try:
354346
answer = self._expect_prompt()
355347
except LatexError as err:

lib/matplotlib/tests/test_backend_pgf.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import matplotlib.pyplot as plt
1212
from matplotlib.testing import _has_tex_package, _check_for_pgf
1313
from matplotlib.testing.compare import compare_images, ImageComparisonFailure
14-
from matplotlib.backends.backend_pgf import PdfPages, _tex_escape
14+
from matplotlib.backends.backend_pgf import PdfPages
1515
from matplotlib.testing.decorators import (
1616
_image_directories, check_figures_equal, image_comparison)
1717
from matplotlib.testing._markers import (
@@ -33,21 +33,17 @@ def compare_figure(fname, savefig_kwargs={}, tol=0):
3333
raise ImageComparisonFailure(err)
3434

3535

36-
@pytest.mark.parametrize('plain_text, escaped_text', [
37-
(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'),
40-
])
41-
def test_tex_escape(plain_text, escaped_text):
42-
assert _tex_escape(plain_text) == escaped_text
43-
44-
4536
@needs_pgf_xelatex
37+
@needs_ghostscript
4638
@pytest.mark.backend('pgf')
4739
def test_tex_special_chars(tmp_path):
4840
fig = plt.figure()
49-
fig.text(.5, .5, "_^ $a_b^c$")
50-
fig.savefig(tmp_path / "test.pdf") # Should not error.
41+
fig.text(.5, .5, "%_^ $a_b^c$")
42+
buf = BytesIO()
43+
fig.savefig(buf, format="png", backend="pgf")
44+
buf.seek(0)
45+
t = plt.imread(buf)
46+
assert not (t == 1).all() # The leading "%" didn't eat up everything.
5147

5248

5349
def create_figure():
@@ -99,7 +95,7 @@ def test_xelatex():
9995
@pytest.mark.skipif(not _has_tex_package('ucs'), reason='needs ucs.sty')
10096
@pytest.mark.backend('pgf')
10197
@image_comparison(['pgf_pdflatex.pdf'], style='default',
102-
tol=11.7 if _old_gs_version else 0)
98+
tol=11.71 if _old_gs_version else 0)
10399
def test_pdflatex():
104100
if os.environ.get('APPVEYOR'):
105101
pytest.xfail("pdflatex test does not work on appveyor due to missing "

0 commit comments

Comments
 (0)
0