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

Skip to content

Let TeX handle multiline strings itself. #22360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 2 additions & 29 deletions lib/matplotlib/dviread.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,40 +291,11 @@ def _read(self):
Read one page from the file. Return True if successful,
False if there were no more pages.
"""
# Pages appear to start with the sequence
# bop (begin of page)
# xxx comment
# <push, ..., pop> # if using chemformula
# down
# push
# down
# <push, push, xxx, right, xxx, pop, pop> # if using xcolor
# down
# push
# down (possibly multiple)
# push <= here, v is the baseline position.
# etc.
# (dviasm is useful to explore this structure.)
# Thus, we use the vertical position at the first time the stack depth
# reaches 3, while at least three "downs" have been executed (excluding
# those popped out (corresponding to the chemformula preamble)), as the
# baseline (the "down" count is necessary to handle xcolor).
down_stack = [0]
self._baseline_v = None
while True:
byte = self.file.read(1)[0]
self._dtable[byte](self, byte)
name = self._dtable[byte].__name__
if name == "_push":
down_stack.append(down_stack[-1])
elif name == "_pop":
down_stack.pop()
elif name == "_down":
down_stack[-1] += 1
if (self._baseline_v is None
and len(getattr(self, "stack", [])) == 3
and down_stack[-1] >= 4):
self._baseline_v = self.v
if byte == 140: # end of page
return True
if self.state is _dvistate.post_post: # end of file
Expand Down Expand Up @@ -457,6 +428,8 @@ def _fnt_num(self, new_f):
@_dispatch(min=239, max=242, args=('ulen1',))
def _xxx(self, datalen):
special = self.file.read(datalen)
if special == b'matplotlibbaselinemarker':
self._baseline_v = self.v
_log.debug(
'Dvi._xxx: encountered special: %s',
''.join([chr(ch) if 32 <= ch < 127 else '<%02x>' % ch
Expand Down
21 changes: 3 additions & 18 deletions lib/matplotlib/tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,26 +776,11 @@ def test_metrics_cache():

fig = plt.figure()
fig.text(.3, .5, "foo\nbar")
fig.text(.5, .5, "foo\nbar")
fig.text(.3, .5, "foo\nbar", usetex=True)
fig.text(.5, .5, "foo\nbar", usetex=True)
fig.canvas.draw()
renderer = fig._cachedRenderer
ys = {} # mapping of strings to where they were drawn in y with draw_tex.

def call(*args, **kwargs):
renderer, x, y, s, *_ = args
ys.setdefault(s, set()).add(y)

renderer.draw_tex = call
fig.canvas.draw()
assert [*ys] == ["foo", "bar"]
# Check that both TeX strings were drawn with the same y-position for both
# single-line substrings. Previously, there used to be an incorrect cache
# collision with the non-TeX string (drawn first here) whose metrics would
# get incorrectly reused by the first TeX string.
assert len(ys["foo"]) == len(ys["bar"]) == 1

info = mpl.text._get_text_metrics_with_cache_impl.cache_info()
# Every string gets a miss for the first layouting (extents), then a hit
# when drawing, but "foo\nbar" gets two hits as it's drawn twice.
assert info.hits > info.misses
# Each string gets drawn twice, so the second draw results in a hit.
assert info.hits == info.misses
69 changes: 32 additions & 37 deletions lib/matplotlib/texmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,9 @@ def get_basefile(self, tex, fontsize, dpi=None):
"""
Return a filename based on a hash of the string, fontsize, and dpi.
"""
s = ''.join([tex, self.get_font_config(), '%f' % fontsize,
self.get_custom_preamble(), str(dpi or '')])
src = self._get_tex_source(tex, fontsize) + str(dpi)
return os.path.join(
self.texcache, hashlib.md5(s.encode('utf-8')).hexdigest())
self.texcache, hashlib.md5(src.encode('utf-8')).hexdigest())

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

def _get_preamble(self):
def _get_tex_source(self, tex, fontsize):
"""Return the complete TeX source for processing a TeX string."""
self.get_font_config() # Updates self._font_preamble.
baselineskip = 1.25 * fontsize
fontcmd = (r'\sffamily' if self._font_family == 'sans-serif' else
r'\ttfamily' if self._font_family == 'monospace' else
r'\rmfamily')
return "\n".join([
r"\documentclass{article}",
# Pass-through \mathdefault, which is used in non-usetex mode to
# use the default text font but was historically suppressed in
# usetex mode.
r"% Pass-through \mathdefault, which is used in non-usetex mode",
r"% to use the default text font but was historically suppressed",
r"% in usetex mode.",
r"\newcommand{\mathdefault}[1]{#1}",
self._font_preamble,
r"\usepackage[utf8]{inputenc}",
r"\DeclareUnicodeCharacter{2212}{\ensuremath{-}}",
# geometry is loaded before the custom preamble as convert_psfrags
# relies on a custom preamble to change the geometry.
r"% geometry is loaded before the custom preamble as ",
r"% convert_psfrags relies on a custom preamble to change the ",
r"% geometry.",
r"\usepackage[papersize=72in, margin=1in]{geometry}",
self.get_custom_preamble(),
# Use `underscore` package to take care of underscores in text
# The [strings] option allows to use underscores in file names
r"% Use `underscore` package to take care of underscores in text.",
r"% The [strings] option allows to use underscores in file names.",
_usepackage_if_not_loaded("underscore", option="strings"),
# Custom packages (e.g. newtxtext) may already have loaded textcomp
# with different options.
r"% Custom packages (e.g. newtxtext) may already have loaded ",
r"% textcomp with different options.",
_usepackage_if_not_loaded("textcomp"),
r"\pagestyle{empty}",
r"\begin{document}",
r"% The empty hbox ensures that a page is printed even for empty",
r"% inputs, except when using psfrag which gets confused by it.",
r"% matplotlibbaselinemarker is used by dviread to detect the",
r"% last line's baseline.",
rf"\fontsize{{{fontsize}}}{{{baselineskip}}}%",
r"\ifdefined\psfrag\else\hbox{}\fi%",
rf"{{\obeylines{fontcmd} {tex}}}%",
r"\special{matplotlibbaselinemarker}%",
r"\end{document}",
])

def make_tex(self, tex, fontsize):
Expand All @@ -204,30 +221,8 @@ def make_tex(self, tex, fontsize):

Return the file name.
"""
basefile = self.get_basefile(tex, fontsize)
texfile = '%s.tex' % basefile
fontcmd = (r'\sffamily' if self._font_family == 'sans-serif' else
r'\ttfamily' if self._font_family == 'monospace' else
r'\rmfamily')
tex_template = r"""
%(preamble)s
\pagestyle{empty}
\begin{document}
%% The empty hbox ensures that a page is printed even for empty inputs, except
%% when using psfrag which gets confused by it.
\fontsize{%(fontsize)f}{%(baselineskip)f}%%
\ifdefined\psfrag\else\hbox{}\fi%%
{%(fontcmd)s %(tex)s}
\end{document}
"""
Path(texfile).write_text(tex_template % {
"preamble": self._get_preamble(),
"fontsize": fontsize,
"baselineskip": fontsize * 1.25,
"fontcmd": fontcmd,
"tex": tex,
}, encoding="utf-8")

texfile = self.get_basefile(tex, fontsize) + ".tex"
Path(texfile).write_text(self._get_tex_source(tex, fontsize))
return texfile

def _run_checked_subprocess(self, command, tex, *, cwd=None):
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ def _get_layout(self, renderer):
of a rotated text when necessary.
"""
thisx, thisy = 0.0, 0.0
lines = self.get_text().split("\n") # Ensures lines is not empty.
text = self.get_text()
lines = [text] if self.get_usetex() else text.split("\n") # Not empty.

ws = []
hs = []
Expand Down