diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index ac1a7d03c687..672ea2c3b003 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -173,6 +173,7 @@ import functools import logging import math +import re from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, @@ -597,7 +598,16 @@ def drange(dstart, dend, delta): def _wrap_in_tex(text): - return r"{\fontfamily{\familydefault}\selectfont " + text + "}" + p = r'([a-zA-Z]+)' + ret_text = re.sub(p, r'}$\1$\\mathdefault{', text) + + # Braces ensure symbols are not spaced like binary operators. + ret_text = ret_text.replace('-', '{-}').replace(':', '{:}') + # To not concatenate space between numbers. + ret_text = ret_text.replace(' ', r'\;') + ret_text = '$\\mathdefault{' + ret_text + '}$' + ret_text = ret_text.replace('$\\mathdefault{}$', '') + return ret_text ## date tickers and formatters ### diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index 8c83d8d6c508..296e67c4d5ff 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -352,10 +352,40 @@ 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 + # # if using chemformula + # down + # push + # down + # # 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 @@ -488,8 +518,6 @@ 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 diff --git a/lib/matplotlib/tests/baseline_images/test_usetex/eqnarray.png b/lib/matplotlib/tests/baseline_images/test_usetex/eqnarray.png new file mode 100644 index 000000000000..249f15d238dd Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_usetex/eqnarray.png differ diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 57a131cec4a0..fc5eed7f2856 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -322,13 +322,13 @@ def callable_formatting_function(dates, _): @pytest.mark.parametrize('delta, expected', [ (datetime.timedelta(weeks=52 * 200), - range(1990, 2171, 20)), + [r'$\mathdefault{%d}$' % year for year in range(1990, 2171, 20)]), (datetime.timedelta(days=30), - ['1990-01-%02d' % day for day in range(1, 32, 3)]), + [r'$\mathdefault{1990{-}01{-}%02d}$' % day for day in range(1, 32, 3)]), (datetime.timedelta(hours=20), - ['01-01 %02d' % hour for hour in range(0, 21, 2)]), + [r'$\mathdefault{01{-}01\;%02d}$' % hour for hour in range(0, 21, 2)]), (datetime.timedelta(minutes=10), - ['01 00:%02d' % minu for minu in range(0, 11)]), + [r'$\mathdefault{01\;00{:}%02d}$' % minu for minu in range(0, 11)]), ]) def test_date_formatter_usetex(delta, expected): style.use("default") @@ -341,8 +341,7 @@ def test_date_formatter_usetex(delta, expected): locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2)) formatter = mdates.AutoDateFormatter(locator, usetex=True) - assert [formatter(loc) for loc in locator()] == [ - r'{\fontfamily{\familydefault}\selectfont %s}' % s for s in expected] + assert [formatter(loc) for loc in locator()] == expected def test_drange(): @@ -645,14 +644,24 @@ def test_offset_changes(): @pytest.mark.parametrize('t_delta, expected', [ (datetime.timedelta(weeks=52 * 200), - range(1980, 2201, 20)), + ['$\\mathdefault{%d}$' % (t, ) for t in range(1980, 2201, 20)]), (datetime.timedelta(days=40), - ['Jan', '05', '09', '13', '17', '21', '25', '29', 'Feb', '05', '09']), + ['Jan', '$\\mathdefault{05}$', '$\\mathdefault{09}$', + '$\\mathdefault{13}$', '$\\mathdefault{17}$', '$\\mathdefault{21}$', + '$\\mathdefault{25}$', '$\\mathdefault{29}$', 'Feb', + '$\\mathdefault{05}$', '$\\mathdefault{09}$']), (datetime.timedelta(hours=40), - ['Jan-01', '04:00', '08:00', '12:00', '16:00', '20:00', - 'Jan-02', '04:00', '08:00', '12:00', '16:00']), + ['Jan$\\mathdefault{{-}01}$', '$\\mathdefault{04{:}00}$', + '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$', + '$\\mathdefault{16{:}00}$', '$\\mathdefault{20{:}00}$', + 'Jan$\\mathdefault{{-}02}$', '$\\mathdefault{04{:}00}$', + '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$', + '$\\mathdefault{16{:}00}$']), (datetime.timedelta(seconds=2), - ['59.5', '00:00', '00.5', '01.0', '01.5', '02.0', '02.5']), + ['$\\mathdefault{59.5}$', '$\\mathdefault{00{:}00}$', + '$\\mathdefault{00.5}$', '$\\mathdefault{01.0}$', + '$\\mathdefault{01.5}$', '$\\mathdefault{02.0}$', + '$\\mathdefault{02.5}$']), ]) def test_concise_formatter_usetex(t_delta, expected): d1 = datetime.datetime(1997, 1, 1) @@ -663,8 +672,7 @@ def test_concise_formatter_usetex(t_delta, expected): locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2)) formatter = mdates.ConciseDateFormatter(locator, usetex=True) - assert formatter.format_ticks(locator()) == [ - r'{\fontfamily{\familydefault}\selectfont %s}' % s for s in expected] + assert formatter.format_ticks(locator()) == expected def test_concise_formatter_formats(): @@ -1347,12 +1355,6 @@ def test_date_ticker_factory(span, expected_locator): assert isinstance(locator, expected_locator) -def test_usetex_newline(): - fig, ax = plt.subplots() - ax.xaxis.set_major_formatter(mdates.DateFormatter('%d/%m\n%Y')) - fig.canvas.draw() - - def test_datetime_masked(): # make sure that all-masked data falls back to the viewlim # set in convert.axisinfo.... diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index b5c1bbff641b..9b3f70526763 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -806,11 +806,26 @@ 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._get_renderer() + 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() - # Each string gets drawn twice, so the second draw results in a hit. - assert info.hits == info.misses + # 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 diff --git a/lib/matplotlib/tests/test_usetex.py b/lib/matplotlib/tests/test_usetex.py index 22309afdaf97..0f01ebaffb56 100644 --- a/lib/matplotlib/tests/test_usetex.py +++ b/lib/matplotlib/tests/test_usetex.py @@ -64,6 +64,21 @@ def test_mathdefault(): fig.canvas.draw() +@image_comparison(['eqnarray.png']) +def test_multiline_eqnarray(): + text = ( + r'\begin{eqnarray*}' + r'foo\\' + r'bar\\' + r'baz\\' + r'\end{eqnarray*}' + ) + + fig = plt.figure(figsize=(1, 1)) + fig.text(0.5, 0.5, text, usetex=True, + horizontalalignment='center', verticalalignment='center') + + @pytest.mark.parametrize("fontsize", [8, 10, 12]) def test_minus_no_descent(fontsize): # Test special-casing of minus descent in DviFont._height_depth_of, by diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index c0d37edbc3d8..e3b500f948bc 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -230,8 +230,7 @@ def _get_tex_source(cls, tex, fontsize): r"% last line's baseline.", rf"\fontsize{{{fontsize}}}{{{baselineskip}}}%", r"\ifdefined\psfrag\else\hbox{}\fi%", - rf"{{\obeylines{fontcmd} {tex}}}%", - r"\special{matplotlibbaselinemarker}%", + rf"{{{fontcmd} {tex}}}%", r"\end{document}", ]) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 356bdb36a8c0..abd5cb959839 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -334,8 +334,7 @@ def _get_layout(self, renderer): of a rotated text when necessary. """ thisx, thisy = 0.0, 0.0 - text = self.get_text() - lines = [text] if self.get_usetex() else text.split("\n") # Not empty. + lines = self.get_text().split("\n") # Ensures lines is not empty. ws = [] hs = [] pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy