Skip to content

Commit e522abd

Browse files
authored
Merge pull request #24078 from meeseeksmachine/auto-backport-of-pr-24047-on-v3.6.x
Backport PR #24047 on branch v3.6.x (Revert #22360: Let TeX handle multiline strings itself)
2 parents 4690343 + 2e351d1 commit e522abd

File tree

8 files changed

+97
-29
lines changed

8 files changed

+97
-29
lines changed

lib/matplotlib/dates.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
import functools
174174
import logging
175175
import math
176+
import re
176177

177178
from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY,
178179
MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
@@ -597,7 +598,16 @@ def drange(dstart, dend, delta):
597598

598599

599600
def _wrap_in_tex(text):
600-
return r"{\fontfamily{\familydefault}\selectfont " + text + "}"
601+
p = r'([a-zA-Z]+)'
602+
ret_text = re.sub(p, r'}$\1$\\mathdefault{', text)
603+
604+
# Braces ensure symbols are not spaced like binary operators.
605+
ret_text = ret_text.replace('-', '{-}').replace(':', '{:}')
606+
# To not concatenate space between numbers.
607+
ret_text = ret_text.replace(' ', r'\;')
608+
ret_text = '$\\mathdefault{' + ret_text + '}$'
609+
ret_text = ret_text.replace('$\\mathdefault{}$', '')
610+
return ret_text
601611

602612

603613
## date tickers and formatters ###

lib/matplotlib/dviread.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -352,10 +352,40 @@ def _read(self):
352352
Read one page from the file. Return True if successful,
353353
False if there were no more pages.
354354
"""
355+
# Pages appear to start with the sequence
356+
# bop (begin of page)
357+
# xxx comment
358+
# <push, ..., pop> # if using chemformula
359+
# down
360+
# push
361+
# down
362+
# <push, push, xxx, right, xxx, pop, pop> # if using xcolor
363+
# down
364+
# push
365+
# down (possibly multiple)
366+
# push <= here, v is the baseline position.
367+
# etc.
368+
# (dviasm is useful to explore this structure.)
369+
# Thus, we use the vertical position at the first time the stack depth
370+
# reaches 3, while at least three "downs" have been executed (excluding
371+
# those popped out (corresponding to the chemformula preamble)), as the
372+
# baseline (the "down" count is necessary to handle xcolor).
373+
down_stack = [0]
355374
self._baseline_v = None
356375
while True:
357376
byte = self.file.read(1)[0]
358377
self._dtable[byte](self, byte)
378+
name = self._dtable[byte].__name__
379+
if name == "_push":
380+
down_stack.append(down_stack[-1])
381+
elif name == "_pop":
382+
down_stack.pop()
383+
elif name == "_down":
384+
down_stack[-1] += 1
385+
if (self._baseline_v is None
386+
and len(getattr(self, "stack", [])) == 3
387+
and down_stack[-1] >= 4):
388+
self._baseline_v = self.v
359389
if byte == 140: # end of page
360390
return True
361391
if self.state is _dvistate.post_post: # end of file
@@ -488,8 +518,6 @@ def _fnt_num(self, new_f):
488518
@_dispatch(min=239, max=242, args=('ulen1',))
489519
def _xxx(self, datalen):
490520
special = self.file.read(datalen)
491-
if special == b'matplotlibbaselinemarker':
492-
self._baseline_v = self.v
493521
_log.debug(
494522
'Dvi._xxx: encountered special: %s',
495523
''.join([chr(ch) if 32 <= ch < 127 else '<%02x>' % ch

lib/matplotlib/tests/test_dates.py

+21-19
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,13 @@ def callable_formatting_function(dates, _):
322322

323323
@pytest.mark.parametrize('delta, expected', [
324324
(datetime.timedelta(weeks=52 * 200),
325-
range(1990, 2171, 20)),
325+
[r'$\mathdefault{%d}$' % year for year in range(1990, 2171, 20)]),
326326
(datetime.timedelta(days=30),
327-
['1990-01-%02d' % day for day in range(1, 32, 3)]),
327+
[r'$\mathdefault{1990{-}01{-}%02d}$' % day for day in range(1, 32, 3)]),
328328
(datetime.timedelta(hours=20),
329-
['01-01 %02d' % hour for hour in range(0, 21, 2)]),
329+
[r'$\mathdefault{01{-}01\;%02d}$' % hour for hour in range(0, 21, 2)]),
330330
(datetime.timedelta(minutes=10),
331-
['01 00:%02d' % minu for minu in range(0, 11)]),
331+
[r'$\mathdefault{01\;00{:}%02d}$' % minu for minu in range(0, 11)]),
332332
])
333333
def test_date_formatter_usetex(delta, expected):
334334
style.use("default")
@@ -341,8 +341,7 @@ def test_date_formatter_usetex(delta, expected):
341341
locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
342342

343343
formatter = mdates.AutoDateFormatter(locator, usetex=True)
344-
assert [formatter(loc) for loc in locator()] == [
345-
r'{\fontfamily{\familydefault}\selectfont %s}' % s for s in expected]
344+
assert [formatter(loc) for loc in locator()] == expected
346345

347346

348347
def test_drange():
@@ -645,14 +644,24 @@ def test_offset_changes():
645644

646645
@pytest.mark.parametrize('t_delta, expected', [
647646
(datetime.timedelta(weeks=52 * 200),
648-
range(1980, 2201, 20)),
647+
['$\\mathdefault{%d}$' % (t, ) for t in range(1980, 2201, 20)]),
649648
(datetime.timedelta(days=40),
650-
['Jan', '05', '09', '13', '17', '21', '25', '29', 'Feb', '05', '09']),
649+
['Jan', '$\\mathdefault{05}$', '$\\mathdefault{09}$',
650+
'$\\mathdefault{13}$', '$\\mathdefault{17}$', '$\\mathdefault{21}$',
651+
'$\\mathdefault{25}$', '$\\mathdefault{29}$', 'Feb',
652+
'$\\mathdefault{05}$', '$\\mathdefault{09}$']),
651653
(datetime.timedelta(hours=40),
652-
['Jan-01', '04:00', '08:00', '12:00', '16:00', '20:00',
653-
'Jan-02', '04:00', '08:00', '12:00', '16:00']),
654+
['Jan$\\mathdefault{{-}01}$', '$\\mathdefault{04{:}00}$',
655+
'$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$',
656+
'$\\mathdefault{16{:}00}$', '$\\mathdefault{20{:}00}$',
657+
'Jan$\\mathdefault{{-}02}$', '$\\mathdefault{04{:}00}$',
658+
'$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$',
659+
'$\\mathdefault{16{:}00}$']),
654660
(datetime.timedelta(seconds=2),
655-
['59.5', '00:00', '00.5', '01.0', '01.5', '02.0', '02.5']),
661+
['$\\mathdefault{59.5}$', '$\\mathdefault{00{:}00}$',
662+
'$\\mathdefault{00.5}$', '$\\mathdefault{01.0}$',
663+
'$\\mathdefault{01.5}$', '$\\mathdefault{02.0}$',
664+
'$\\mathdefault{02.5}$']),
656665
])
657666
def test_concise_formatter_usetex(t_delta, expected):
658667
d1 = datetime.datetime(1997, 1, 1)
@@ -663,8 +672,7 @@ def test_concise_formatter_usetex(t_delta, expected):
663672
locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
664673

665674
formatter = mdates.ConciseDateFormatter(locator, usetex=True)
666-
assert formatter.format_ticks(locator()) == [
667-
r'{\fontfamily{\familydefault}\selectfont %s}' % s for s in expected]
675+
assert formatter.format_ticks(locator()) == expected
668676

669677

670678
def test_concise_formatter_formats():
@@ -1347,12 +1355,6 @@ def test_date_ticker_factory(span, expected_locator):
13471355
assert isinstance(locator, expected_locator)
13481356

13491357

1350-
def test_usetex_newline():
1351-
fig, ax = plt.subplots()
1352-
ax.xaxis.set_major_formatter(mdates.DateFormatter('%d/%m\n%Y'))
1353-
fig.canvas.draw()
1354-
1355-
13561358
def test_datetime_masked():
13571359
# make sure that all-masked data falls back to the viewlim
13581360
# set in convert.axisinfo....

lib/matplotlib/tests/test_text.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -806,11 +806,26 @@ def test_metrics_cache():
806806

807807
fig = plt.figure()
808808
fig.text(.3, .5, "foo\nbar")
809-
fig.text(.5, .5, "foo\nbar")
810809
fig.text(.3, .5, "foo\nbar", usetex=True)
811810
fig.text(.5, .5, "foo\nbar", usetex=True)
812811
fig.canvas.draw()
812+
renderer = fig._get_renderer()
813+
ys = {} # mapping of strings to where they were drawn in y with draw_tex.
814+
815+
def call(*args, **kwargs):
816+
renderer, x, y, s, *_ = args
817+
ys.setdefault(s, set()).add(y)
818+
819+
renderer.draw_tex = call
820+
fig.canvas.draw()
821+
assert [*ys] == ["foo", "bar"]
822+
# Check that both TeX strings were drawn with the same y-position for both
823+
# single-line substrings. Previously, there used to be an incorrect cache
824+
# collision with the non-TeX string (drawn first here) whose metrics would
825+
# get incorrectly reused by the first TeX string.
826+
assert len(ys["foo"]) == len(ys["bar"]) == 1
813827

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

lib/matplotlib/tests/test_usetex.py

+15
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ def test_mathdefault():
6464
fig.canvas.draw()
6565

6666

67+
@image_comparison(['eqnarray.png'])
68+
def test_multiline_eqnarray():
69+
text = (
70+
r'\begin{eqnarray*}'
71+
r'foo\\'
72+
r'bar\\'
73+
r'baz\\'
74+
r'\end{eqnarray*}'
75+
)
76+
77+
fig = plt.figure(figsize=(1, 1))
78+
fig.text(0.5, 0.5, text, usetex=True,
79+
horizontalalignment='center', verticalalignment='center')
80+
81+
6782
@pytest.mark.parametrize("fontsize", [8, 10, 12])
6883
def test_minus_no_descent(fontsize):
6984
# Test special-casing of minus descent in DviFont._height_depth_of, by

lib/matplotlib/texmanager.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,7 @@ def _get_tex_source(cls, tex, fontsize):
230230
r"% last line's baseline.",
231231
rf"\fontsize{{{fontsize}}}{{{baselineskip}}}%",
232232
r"\ifdefined\psfrag\else\hbox{}\fi%",
233-
rf"{{\obeylines{fontcmd} {tex}}}%",
234-
r"\special{matplotlibbaselinemarker}%",
233+
rf"{{{fontcmd} {tex}}}%",
235234
r"\end{document}",
236235
])
237236

lib/matplotlib/text.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,7 @@ def _get_layout(self, renderer):
302302
of a rotated text when necessary.
303303
"""
304304
thisx, thisy = 0.0, 0.0
305-
text = self.get_text()
306-
lines = [text] if self.get_usetex() else text.split("\n") # Not empty.
305+
lines = self.get_text().split("\n") # Ensures lines is not empty.
307306

308307
ws = []
309308
hs = []

0 commit comments

Comments
 (0)