8000 Merge pull request #22360 from anntzer/multilinetex · matplotlib/matplotlib@2fe38b5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2fe38b5

Browse files
authored
Merge pull request #22360 from anntzer/multilinetex
Let TeX handle multiline strings itself.
2 parents 68d2dc2 + 3aaf81b commit 2fe38b5

File tree

4 files changed

+39
-85
lines changed

4 files changed

+39
-85
lines changed

lib/matplotlib/dviread.py

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -291,40 +291,11 @@ def _read(self):
291291
Read one page from the file. Return True if successful,
292292
False if there were no more pages.
293293
"""
294-
# Pages appear to start with the sequence
295-
# bop (begin of page)
296-
# xxx comment
297-
# <push, ..., pop> # if using chemformula
298-
# down
299-
# push
300-
# down
301-
# <push, push, xxx, right, xxx, pop, pop> # if using xcolor
302-
# down
303-
# push
304-
# down (possibly multiple)
305-
# push <= here, v is the baseline position.
306-
# etc.
307-
# (dviasm is useful to explore this structure.)
308-
# Thus, we use the vertical position at the first time the stack depth
309-
# reaches 3, while at least three "downs" have been executed (excluding
310-
# those popped out (corresponding to the chemformula preamble)), as the
311-
# baseline (the "down" count is necessary to handle xcolor).
312-
down_stack = [0]
313294
self._baseline_v = None
314295
while True:
315296
byte = self.file.read(1)[0]
316297
self._dtable[byte](self, byte)
317298
name = self._dtable[byte].__name__
318-
if name == "_push":
319-
down_stack.append(down_stack[-1])
320-
elif name == "_pop":
321-
down_stack.pop()
322-
elif name == "_down":
323-
down_stack[-1] += 1
324-
if (self._baseline_v is None
325-
and len(getattr(self, "stack", [])) == 3
326-
and down_stack[-1] >= 4):
327-
self._baseline_v = self.v
328299
if byte == 140: # end of page
329300
return True
330301
if self.state is _dvistate.post_post: # end of file
@@ -457,6 +428,8 @@ def _fnt_num(self, new_f):
457428
@_dispatch(min=239, max=242, args=('ulen1',))
458429
def _xxx(self, datalen):
459430
special = self.file.read(datalen)
431+
if special == b'matplotlibbaselinemarker':
432+
self._baseline_v = self.v
460433
_log.debug(
461434
'Dvi._xxx: encountered special: %s',
462435
''.join([chr(ch) if 32 <= ch < 127 else '<%02x>' % ch

lib/matplotlib/tests/test_text.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -790,26 +790,11 @@ def test_metrics_cache():
790790

791791
fig = plt.figure()
792792
fig.text(.3, .5, "foo\nbar")
793+
fig.text(.5, .5, "foo\nbar")
793794
fig.text(.3, .5, "foo\nbar", usetex=True)
794795
fig.text(.5, .5, "foo\nbar", usetex=True)
795796
fig.canvas.draw()
796-
renderer = fig._cachedRenderer
797-
ys = {} # mapping of strings to where they were drawn in y with draw_tex.
798-
799-
def call(*args, **kwargs):
800-
renderer, x, y, s, *_ = args
801-
ys.setdefault(s, set()).add(y)
802-
803-
renderer.draw_tex = call
804-
fig.canvas.draw()
805-
assert [*ys] == ["foo", "bar"]
806-
# Check that both TeX strings were drawn with the same y-position for both
807-
# single-line substrings. Previously, there used to be an incorrect cache
808-
# collision with the non-TeX string (drawn first here) whose metrics would
809-
# get incorrectly reused by the first TeX string.
810-
assert len(ys["foo"]) == len(ys["bar"]) == 1
811797

812798
info = mpl.text._get_text_metrics_with_cache_impl.cache_info()
813-
# Every string gets a miss for the first layouting (extents), then a hit
814-
# when drawing, but "foo\nbar" gets two hits as it's drawn twice.
815-
assert info.hits > info.misses
799+
# Each string gets drawn twice, so the second draw results in a hit.
800+
assert info.hits == info.misses

lib/matplotlib/texmanager.py

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,9 @@ def get_basefile(self, tex, fontsize, dpi=None):
161161
"""
162162
Return a filename based on a hash of the string, fontsize, and dpi.
163163
"""
164-
s = ''.join([tex, self.get_font_config(), '%f' % fontsize,
165-
self.get_custom_preamble(), str(dpi or '')])
164+
src = self._get_tex_source(tex, fontsize) + str(dpi)
166165
return os.path.join(
167-
self.texcache, hashlib.md5(s.encode('utf-8')).hexdigest())
166+
self.texcache, hashlib.md5(src.encode('utf-8')).hexdigest())
168167

169168
def get_font_preamble(self):
170169
"""
@@ -176,26 +175,44 @@ def get_custom_preamble(self):
176175
"""Return a string containing user additions to the tex preamble."""
177176
return rcParams['text.latex.preamble']
178177

179-
def _get_preamble(self):
178+
def _get_tex_source(self, tex, fontsize):
179+
"""Return the complete TeX source for processing a TeX string."""
180+
self.get_font_config() # Updates self._font_preamble.
181+
baselineskip = 1.25 * fontsize
182+
fontcmd = (r'\sffamily' if self._font_family == 'sans-serif' else
183+
r'\ttfamily' if self._font_family == 'monospace' else
184+
r'\rmfamily')
180185
return "\n".join([
181186
r"\documentclass{article}",
182-
# Pass-through \mathdefault, which is used in non-usetex mode to
183-
# use the default text font but was historically suppressed in
184-
# usetex mode.
187+
r"% Pass-through \mathdefault, which is used in non-usetex mode",
188+
r"% to use the default text font but was historically suppressed",
189+
r"% in usetex mode.",
185190
r"\newcommand{\mathdefault}[1]{#1}",
186191
self._font_preamble,
187192
r"\usepackage[utf8]{inputenc}",
188193
r"\DeclareUnicodeCharacter{2212}{\ensuremath{-}}",
189-
# geometry is loaded before the custom preamble as convert_psfrags
190-
# relies on a custom preamble to change the geometry.
194+
r"% geometry is loaded before the custom preamble as ",
195+
r"% convert_psfrags relies on a custom preamble to change the ",
196+
r"% geometry.",
191197
r"\usepackage[papersize=72in, margin=1in]{geometry}",
192198
self.get_custom_preamble(),
193-
# Use `underscore` package to take care of underscores in text
194-
# The [strings] option allows to use underscores in file names
199+
r"% Use `underscore` package to take care of underscores in text.",
200+
r"% The [strings] option allows to use underscores in file names.",
195201
_usepackage_if_not_loaded("underscore", option="strings"),
196-
# Custom packages (e.g. newtxtext) may already have loaded textcomp
197-
# with different options.
202+
r"% Custom packages (e.g. newtxtext) may already have loaded ",
203+
r"% textcomp with different options.",
198204
_usepackage_if_not_loaded("textcomp"),
205+
r"\pagestyle{empty}",
206+
r"\begin{document}",
207+
r"% The empty hbox ensures that a page is printed even for empty",
208+
r"% inputs, except when using psfrag which gets confused by it.",
209+
r"% matplotlibbaselinemarker is used by dviread to detect the",
210+
r"% last line's baseline.",
211+
rf"\fontsize{{{fontsize}}}{{{baselineskip}}}%",
212+
r"\ifdefined\psfrag\else\hbox{}\fi%",
213+
rf"{{\obeylines{fontcmd} {tex}}}%",
214+
r"\special{matplotlibbaselinemarker}%",
215+
r"\end{document}",
199216
])
200217

201218
def make_tex(self, tex, fontsize):
@@ -204,30 +221,8 @@ def make_tex(self, tex, fontsize):
204221
205222
Return the file name.
206223
"""
207-
basefile = self.get_basefile(tex, fontsize)
208-
texfile = '%s.tex' % basefile
209-
fontcmd = (r'\sffamily' if self._font_family == 'sans-serif' else
210-
r'\ttfamily' if self._font_family == 'monospace' else
211-
r'\rmfamily')
212-
tex_template = r"""
213-
%(preamble)s
214-
\pagestyle{empty}
215 10545 -
\begin{document}
216-
%% The empty hbox ensures that a page is printed even for empty inputs, except
217-
%% when using psfrag which gets confused by it.
218-
\fontsize{%(fontsize)f}{%(baselineskip)f}%%
219-
\ifdefined\psfrag\else\hbox{}\fi%%
220-
{%(fontcmd)s %(tex)s}
221-
\end{document}
222-
"""
223-
Path(texfile).write_text(tex_template % {
224-
"preamble": self._get_preamble(),
225-
"fontsize": fontsize,
226-
"baselineskip": fontsize * 1.25,
227-
"fontcmd": fontcmd,
228-
"tex": tex,
229-
}, encoding="utf-8")
230-
224+
texfile = self.get_basefile(tex, fontsize) + ".tex"
225+
Path(texfile).write_text(self._get_tex_source(tex, fontsize))
231226
return texfile
232227

233228
def _run_checked_subprocess(self, command, tex, *, cwd=None):

lib/matplotlib/text.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,8 @@ def _get_layout(self, renderer):
296296
of a rotated text when necessary.
297297
"""
298298
thisx, thisy = 0.0, 0.0
299-
lines = self.get_text().split("\n") # Ensures lines is not empty.
299+
text = self.get_text()
300+
lines = [text] if self.get_usetex() else text.split("\n") # Not empty.
300301

301302
ws = []
302303
hs = []

0 commit comments

Comments
 (0)
0