test_backend_pgf.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. from io import BytesIO
  2. import os
  3. from pathlib import Path
  4. import shutil
  5. import subprocess
  6. from tempfile import TemporaryDirectory
  7. import numpy as np
  8. import pytest
  9. import platform
  10. import matplotlib as mpl
  11. import matplotlib.pyplot as plt
  12. from matplotlib.testing.compare import compare_images, ImageComparisonFailure
  13. from matplotlib.testing.decorators import image_comparison, _image_directories
  14. from matplotlib.backends.backend_pgf import PdfPages
  15. baseline_dir, result_dir = _image_directories(lambda: 'dummy func')
  16. def check_for(texsystem):
  17. with TemporaryDirectory() as tmpdir:
  18. tex_path = Path(tmpdir, "test.tex")
  19. tex_path.write_text(r"""
  20. \documentclass{minimal}
  21. \usepackage{pgf}
  22. \begin{document}
  23. \typeout{pgfversion=\pgfversion}
  24. \makeatletter
  25. \@@end
  26. """)
  27. try:
  28. subprocess.check_call(
  29. [texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir,
  30. stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
  31. except (OSError, subprocess.CalledProcessError):
  32. return False
  33. return True
  34. needs_xelatex = pytest.mark.skipif(not check_for('xelatex'),
  35. reason='xelatex + pgf is required')
  36. needs_pdflatex = pytest.mark.skipif(not check_for('pdflatex'),
  37. reason='pdflatex + pgf is required')
  38. needs_lualatex = pytest.mark.skipif(not check_for('lualatex'),
  39. reason='lualatex + pgf is required')
  40. def compare_figure(fname, savefig_kwargs={}, tol=0):
  41. actual = os.path.join(result_dir, fname)
  42. plt.savefig(actual, **savefig_kwargs)
  43. expected = os.path.join(result_dir, "expected_%s" % fname)
  44. shutil.copyfile(os.path.join(baseline_dir, fname), expected)
  45. err = compare_images(expected, actual, tol=tol)
  46. if err:
  47. raise ImageComparisonFailure(err)
  48. def create_figure():
  49. plt.figure()
  50. x = np.linspace(0, 1, 15)
  51. # line plot
  52. plt.plot(x, x ** 2, "b-")
  53. # marker
  54. plt.plot(x, 1 - x**2, "g>")
  55. # filled paths and patterns
  56. plt.fill_between([0., .4], [.4, 0.], hatch='//', facecolor="lightgray",
  57. edgecolor="red")
  58. plt.fill([3, 3, .8, .8, 3], [2, -2, -2, 0, 2], "b")
  59. # text and typesetting
  60. plt.plot([0.9], [0.5], "ro", markersize=3)
  61. plt.text(0.9, 0.5, 'unicode (ü, °, µ) and math ($\\mu_i = x_i^2$)',
  62. ha='right', fontsize=20)
  63. plt.ylabel('sans-serif, blue, $\\frac{\\sqrt{x}}{y^2}$..',
  64. family='sans-serif', color='blue')
  65. plt.xlim(0, 1)
  66. plt.ylim(0, 1)
  67. # test compiling a figure to pdf with xelatex
  68. @needs_xelatex
  69. @pytest.mark.backend('pgf')
  70. @image_comparison(['pgf_xelatex.pdf'], style='default')
  71. def test_xelatex():
  72. rc_xelatex = {'font.family': 'serif',
  73. 'pgf.rcfonts': False}
  74. mpl.rcParams.update(rc_xelatex)
  75. create_figure()
  76. # test compiling a figure to pdf with pdflatex
  77. @needs_pdflatex
  78. @pytest.mark.backend('pgf')
  79. @image_comparison(['pgf_pdflatex.pdf'], style='default')
  80. def test_pdflatex():
  81. if os.environ.get('APPVEYOR', False):
  82. pytest.xfail("pdflatex test does not work on appveyor due to missing "
  83. "LaTeX fonts")
  84. rc_pdflatex = {'font.family': 'serif',
  85. 'pgf.rcfonts': False,
  86. 'pgf.texsystem': 'pdflatex',
  87. 'pgf.preamble': ['\\usepackage[utf8x]{inputenc}',
  88. '\\usepackage[T1]{fontenc}']}
  89. mpl.rcParams.update(rc_pdflatex)
  90. create_figure()
  91. # test updating the rc parameters for each figure
  92. @needs_xelatex
  93. @needs_pdflatex
  94. @pytest.mark.style('default')
  95. @pytest.mark.backend('pgf')
  96. def test_rcupdate():
  97. rc_sets = [{'font.family': 'sans-serif',
  98. 'font.size': 30,
  99. 'figure.subplot.left': .2,
  100. 'lines.markersize': 10,
  101. 'pgf.rcfonts': False,
  102. 'pgf.texsystem': 'xelatex'},
  103. {'font.family': 'monospace',
  104. 'font.size': 10,
  105. 'figure.subplot.left': .1,
  106. 'lines.markersize': 20,
  107. 'pgf.rcfonts': False,
  108. 'pgf.texsystem': 'pdflatex',
  109. 'pgf.preamble': ['\\usepackage[utf8x]{inputenc}',
  110. '\\usepackage[T1]{fontenc}',
  111. '\\usepackage{sfmath}']}]
  112. tol = [6, 0]
  113. for i, rc_set in enumerate(rc_sets):
  114. with mpl.rc_context(rc_set):
  115. create_figure()
  116. compare_figure('pgf_rcupdate%d.pdf' % (i + 1), tol=tol[i])
  117. # test backend-side clipping, since large numbers are not supported by TeX
  118. @needs_xelatex
  119. @pytest.mark.style('default')
  120. @pytest.mark.backend('pgf')
  121. def test_pathclip():
  122. rc_xelatex = {'font.family': 'serif',
  123. 'pgf.rcfonts': False}
  124. mpl.rcParams.update(rc_xelatex)
  125. plt.figure()
  126. plt.plot([0., 1e100], [0., 1e100])
  127. plt.xlim(0, 1)
  128. plt.ylim(0, 1)
  129. # this test passes if compiling/saving to pdf works (no image comparison)
  130. plt.savefig(os.path.join(result_dir, "pgf_pathclip.pdf"))
  131. # test mixed mode rendering
  132. @needs_xelatex
  133. @pytest.mark.backend('pgf')
  134. @image_comparison(['pgf_mixedmode.pdf'], style='default',
  135. tol={'aarch64': 1.086}.get(platform.machine(), 0.0))
  136. def test_mixedmode():
  137. rc_xelatex = {'font.family': 'serif',
  138. 'pgf.rcfonts': False}
  139. mpl.rcParams.update(rc_xelatex)
  140. Y, X = np.ogrid[-1:1:40j, -1:1:40j]
  141. plt.figure()
  142. plt.pcolor(X**2 + Y**2).set_rasterized(True)
  143. # test bbox_inches clipping
  144. @needs_xelatex
  145. @pytest.mark.style('default')
  146. @pytest.mark.backend('pgf')
  147. def test_bbox_inches():
  148. rc_xelatex = {'font.family': 'serif',
  149. 'pgf.rcfonts': False}
  150. mpl.rcParams.update(rc_xelatex)
  151. Y, X = np.ogrid[-1:1:40j, -1:1:40j]
  152. fig = plt.figure()
  153. ax1 = fig.add_subplot(121)
  154. ax1.plot(range(5))
  155. ax2 = fig.add_subplot(122)
  156. ax2.plot(range(5))
  157. plt.tight_layout()
  158. bbox = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
  159. compare_figure('pgf_bbox_inches.pdf', savefig_kwargs={'bbox_inches': bbox},
  160. tol=0)
  161. @needs_pdflatex
  162. @pytest.mark.style('default')
  163. @pytest.mark.backend('pgf')
  164. def test_pdf_pages():
  165. rc_pdflatex = {
  166. 'font.family': 'serif',
  167. 'pgf.rcfonts': False,
  168. 'pgf.texsystem': 'pdflatex',
  169. }
  170. mpl.rcParams.update(rc_pdflatex)
  171. fig1 = plt.figure()
  172. ax1 = fig1.add_subplot(1, 1, 1)
  173. ax1.plot(range(5))
  174. fig1.tight_layout()
  175. fig2 = plt.figure(figsize=(3, 2))
  176. ax2 = fig2.add_subplot(1, 1, 1)
  177. ax2.plot(range(5))
  178. fig2.tight_layout()
  179. with PdfPages(os.path.join(result_dir, 'pdfpages.pdf')) as pdf:
  180. pdf.savefig(fig1)
  181. pdf.savefig(fig2)
  182. @needs_xelatex
  183. @pytest.mark.style('default')
  184. @pytest.mark.backend('pgf')
  185. def test_pdf_pages_metadata():
  186. rc_pdflatex = {
  187. 'font.family': 'serif',
  188. 'pgf.rcfonts': False,
  189. 'pgf.texsystem': 'xelatex',
  190. }
  191. mpl.rcParams.update(rc_pdflatex)
  192. fig = plt.figure()
  193. ax = fig.add_subplot(1, 1, 1)
  194. ax.plot(range(5))
  195. fig.tight_layout()
  196. md = {'author': 'me', 'title': 'Multipage PDF with pgf'}
  197. path = os.path.join(result_dir, 'pdfpages_meta.pdf')
  198. with PdfPages(path, metadata=md) as pdf:
  199. pdf.savefig(fig)
  200. pdf.savefig(fig)
  201. pdf.savefig(fig)
  202. assert pdf.get_pagecount() == 3
  203. @needs_lualatex
  204. @pytest.mark.style('default')
  205. @pytest.mark.backend('pgf')
  206. def test_pdf_pages_lualatex():
  207. rc_pdflatex = {
  208. 'font.family': 'serif',
  209. 'pgf.rcfonts': False,
  210. 'pgf.texsystem': 'lualatex'
  211. }
  212. mpl.rcParams.update(rc_pdflatex)
  213. fig = plt.figure()
  214. ax = fig.add_subplot(1, 1, 1)
  215. ax.plot(range(5))
  216. fig.tight_layout()
  217. md = {'author': 'me', 'title': 'Multipage PDF with pgf'}
  218. path = os.path.join(result_dir, 'pdfpages_lua.pdf')
  219. with PdfPages(path, metadata=md) as pdf:
  220. pdf.savefig(fig)
  221. pdf.savefig(fig)
  222. assert pdf.get_pagecount() == 2
  223. @needs_xelatex
  224. def test_tex_restart_after_error():
  225. fig = plt.figure()
  226. fig.suptitle(r"\oops")
  227. with pytest.raises(ValueError):
  228. fig.savefig(BytesIO(), format="pgf")
  229. fig = plt.figure() # start from scratch
  230. fig.suptitle(r"this is ok")
  231. fig.savefig(BytesIO(), format="pgf")
  232. @needs_xelatex
  233. def test_bbox_inches_tight(tmpdir):
  234. fig, ax = plt.subplots()
  235. ax.imshow([[0, 1], [2, 3]])
  236. fig.savefig(os.path.join(tmpdir, "test.pdf"), backend="pgf",
  237. bbox_inches="tight")