test_usetex.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. from tempfile import TemporaryFile
  2. import numpy as np
  3. from packaging.version import parse as parse_version
  4. import pytest
  5. import matplotlib as mpl
  6. from matplotlib import dviread
  7. from matplotlib.testing import _has_tex_package
  8. from matplotlib.testing.decorators import check_figures_equal, image_comparison
  9. from matplotlib.testing._markers import needs_usetex
  10. import matplotlib.pyplot as plt
  11. pytestmark = needs_usetex
  12. @image_comparison(
  13. baseline_images=['test_usetex'],
  14. extensions=['pdf', 'png'],
  15. style="mpl20")
  16. def test_usetex():
  17. mpl.rcParams['text.usetex'] = True
  18. fig, ax = plt.subplots()
  19. kwargs = {"verticalalignment": "baseline", "size": 24,
  20. "bbox": dict(pad=0, edgecolor="k", facecolor="none")}
  21. ax.text(0.2, 0.7,
  22. # the \LaTeX macro exercises character sizing and placement,
  23. # \left[ ... \right\} draw some variable-height characters,
  24. # \sqrt and \frac draw horizontal rules, \mathrm changes the font
  25. r'\LaTeX\ $\left[\int\limits_e^{2e}'
  26. r'\sqrt\frac{\log^3 x}{x}\,\mathrm{d}x \right\}$',
  27. **kwargs)
  28. ax.text(0.2, 0.3, "lg", **kwargs)
  29. ax.text(0.4, 0.3, r"$\frac{1}{2}\pi$", **kwargs)
  30. ax.text(0.6, 0.3, "$p^{3^A}$", **kwargs)
  31. ax.text(0.8, 0.3, "$p_{3_2}$", **kwargs)
  32. for x in {t.get_position()[0] for t in ax.texts}:
  33. ax.axvline(x)
  34. for y in {t.get_position()[1] for t in ax.texts}:
  35. ax.axhline(y)
  36. ax.set_axis_off()
  37. @check_figures_equal()
  38. def test_empty(fig_test, fig_ref):
  39. mpl.rcParams['text.usetex'] = True
  40. fig_test.text(.5, .5, "% a comment")
  41. @check_figures_equal()
  42. def test_unicode_minus(fig_test, fig_ref):
  43. mpl.rcParams['text.usetex'] = True
  44. fig_test.text(.5, .5, "$-$")
  45. fig_ref.text(.5, .5, "\N{MINUS SIGN}")
  46. def test_mathdefault():
  47. plt.rcParams["axes.formatter.use_mathtext"] = True
  48. fig = plt.figure()
  49. fig.add_subplot().set_xlim(-1, 1)
  50. # Check that \mathdefault commands generated by tickers don't cause
  51. # problems when later switching usetex on.
  52. mpl.rcParams['text.usetex'] = True
  53. fig.canvas.draw()
  54. @image_comparison(['eqnarray.png'])
  55. def test_multiline_eqnarray():
  56. text = (
  57. r'\begin{eqnarray*}'
  58. r'foo\\'
  59. r'bar\\'
  60. r'baz\\'
  61. r'\end{eqnarray*}'
  62. )
  63. fig = plt.figure(figsize=(1, 1))
  64. fig.text(0.5, 0.5, text, usetex=True,
  65. horizontalalignment='center', verticalalignment='center')
  66. @pytest.mark.parametrize("fontsize", [8, 10, 12])
  67. def test_minus_no_descent(fontsize):
  68. # Test special-casing of minus descent in DviFont._height_depth_of, by
  69. # checking that overdrawing a 1 and a -1 results in an overall height
  70. # equivalent to drawing either of them separately.
  71. mpl.style.use("mpl20")
  72. mpl.rcParams['font.size'] = fontsize
  73. heights = {}
  74. fig = plt.figure()
  75. for vals in [(1,), (-1,), (-1, 1)]:
  76. fig.clear()
  77. for x in vals:
  78. fig.text(.5, .5, f"${x}$", usetex=True)
  79. fig.canvas.draw()
  80. # The following counts the number of non-fully-blank pixel rows.
  81. heights[vals] = ((np.array(fig.canvas.buffer_rgba())[..., 0] != 255)
  82. .any(axis=1).sum())
  83. assert len({*heights.values()}) == 1
  84. @pytest.mark.parametrize('pkg', ['xcolor', 'chemformula'])
  85. def test_usetex_packages(pkg):
  86. if not _has_tex_package(pkg):
  87. pytest.skip(f'{pkg} is not available')
  88. mpl.rcParams['text.usetex'] = True
  89. fig = plt.figure()
  90. text = fig.text(0.5, 0.5, "Some text 0123456789")
  91. fig.canvas.draw()
  92. mpl.rcParams['text.latex.preamble'] = (
  93. r'\PassOptionsToPackage{dvipsnames}{xcolor}\usepackage{%s}' % pkg)
  94. fig = plt.figure()
  95. text2 = fig.text(0.5, 0.5, "Some text 0123456789")
  96. fig.canvas.draw()
  97. np.testing.assert_array_equal(text2.get_window_extent(),
  98. text.get_window_extent())
  99. @pytest.mark.parametrize(
  100. "preamble",
  101. [r"\usepackage[full]{textcomp}", r"\usepackage{underscore}"],
  102. )
  103. def test_latex_pkg_already_loaded(preamble):
  104. plt.rcParams["text.latex.preamble"] = preamble
  105. fig = plt.figure()
  106. fig.text(.5, .5, "hello, world", usetex=True)
  107. fig.canvas.draw()
  108. def test_usetex_with_underscore():
  109. plt.rcParams["text.usetex"] = True
  110. df = {"a_b": range(5)[::-1], "c": range(5)}
  111. fig, ax = plt.subplots()
  112. ax.plot("c", "a_b", data=df)
  113. ax.legend()
  114. ax.text(0, 0, "foo_bar", usetex=True)
  115. plt.draw()
  116. @pytest.mark.flaky(reruns=3) # Tends to hit a TeX cache lock on AppVeyor.
  117. @pytest.mark.parametrize("fmt", ["pdf", "svg"])
  118. def test_missing_psfont(fmt, monkeypatch):
  119. """An error is raised if a TeX font lacks a Type-1 equivalent"""
  120. monkeypatch.setattr(
  121. dviread.PsfontsMap, '__getitem__',
  122. lambda self, k: dviread.PsFont(
  123. texname=b'texfont', psname=b'Some Font',
  124. effects=None, encoding=None, filename=None))
  125. mpl.rcParams['text.usetex'] = True
  126. fig, ax = plt.subplots()
  127. ax.text(0.5, 0.5, 'hello')
  128. with TemporaryFile() as tmpfile, pytest.raises(ValueError):
  129. fig.savefig(tmpfile, format=fmt)
  130. try:
  131. _old_gs_version = mpl._get_executable_info('gs').version < parse_version('9.55')
  132. except mpl.ExecutableNotFoundError:
  133. _old_gs_version = True
  134. @image_comparison(baseline_images=['rotation'], extensions=['eps', 'pdf', 'png', 'svg'],
  135. style='mpl20', tol=3.91 if _old_gs_version else 0)
  136. def test_rotation():
  137. mpl.rcParams['text.usetex'] = True
  138. fig = plt.figure()
  139. ax = fig.add_axes([0, 0, 1, 1])
  140. ax.set(xlim=[-0.5, 5], xticks=[], ylim=[-0.5, 3], yticks=[], frame_on=False)
  141. text = {val: val[0] for val in ['top', 'center', 'bottom', 'left', 'right']}
  142. text['baseline'] = 'B'
  143. text['center_baseline'] = 'C'
  144. for i, va in enumerate(['top', 'center', 'bottom', 'baseline', 'center_baseline']):
  145. for j, ha in enumerate(['left', 'center', 'right']):
  146. for k, angle in enumerate([0, 90, 180, 270]):
  147. k //= 2
  148. x = i + k / 2
  149. y = j + k / 2
  150. ax.plot(x, y, '+', c=f'C{k}', markersize=20, markeredgewidth=0.5)
  151. # 'My' checks full height letters plus descenders.
  152. ax.text(x, y, f"$\\mathrm{{My {text[ha]}{text[va]} {angle}}}$",
  153. rotation=angle, horizontalalignment=ha, verticalalignment=va)