Traktor/myenv/Lib/site-packages/matplotlib/tests/test_backend_pgf.py
2024-05-23 01:57:24 +02:00

409 lines
13 KiB
Python

import datetime
from io import BytesIO
import os
import shutil
import numpy as np
from packaging.version import parse as parse_version
import pytest
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.testing import _has_tex_package, _check_for_pgf
from matplotlib.testing.exceptions import ImageComparisonFailure
from matplotlib.testing.compare import compare_images
from matplotlib.backends.backend_pgf import PdfPages
from matplotlib.testing.decorators import (
_image_directories, check_figures_equal, image_comparison)
from matplotlib.testing._markers import (
needs_ghostscript, needs_pgf_lualatex, needs_pgf_pdflatex,
needs_pgf_xelatex)
baseline_dir, result_dir = _image_directories(lambda: 'dummy func')
def compare_figure(fname, savefig_kwargs={}, tol=0):
actual = os.path.join(result_dir, fname)
plt.savefig(actual, **savefig_kwargs)
expected = os.path.join(result_dir, "expected_%s" % fname)
shutil.copyfile(os.path.join(baseline_dir, fname), expected)
err = compare_images(expected, actual, tol=tol)
if err:
raise ImageComparisonFailure(err)
@needs_pgf_xelatex
@needs_ghostscript
@pytest.mark.backend('pgf')
def test_tex_special_chars(tmp_path):
fig = plt.figure()
fig.text(.5, .5, "%_^ $a_b^c$")
buf = BytesIO()
fig.savefig(buf, format="png", backend="pgf")
buf.seek(0)
t = plt.imread(buf)
assert not (t == 1).all() # The leading "%" didn't eat up everything.
def create_figure():
plt.figure()
x = np.linspace(0, 1, 15)
# line plot
plt.plot(x, x ** 2, "b-")
# marker
plt.plot(x, 1 - x**2, "g>")
# filled paths and patterns
plt.fill_between([0., .4], [.4, 0.], hatch='//', facecolor="lightgray",
edgecolor="red")
plt.fill([3, 3, .8, .8, 3], [2, -2, -2, 0, 2], "b")
# text and typesetting
plt.plot([0.9], [0.5], "ro", markersize=3)
plt.text(0.9, 0.5, 'unicode (ü, °, \N{Section Sign}) and math ($\\mu_i = x_i^2$)',
ha='right', fontsize=20)
plt.ylabel('sans-serif, blue, $\\frac{\\sqrt{x}}{y^2}$..',
family='sans-serif', color='blue')
plt.text(1, 1, 'should be clipped as default clip_box is Axes bbox',
fontsize=20, clip_on=True)
plt.xlim(0, 1)
plt.ylim(0, 1)
# test compiling a figure to pdf with xelatex
@needs_pgf_xelatex
@pytest.mark.backend('pgf')
@image_comparison(['pgf_xelatex.pdf'], style='default')
def test_xelatex():
rc_xelatex = {'font.family': 'serif',
'pgf.rcfonts': False}
mpl.rcParams.update(rc_xelatex)
create_figure()
try:
_old_gs_version = \
mpl._get_executable_info('gs').version < parse_version('9.50')
except mpl.ExecutableNotFoundError:
_old_gs_version = True
# test compiling a figure to pdf with pdflatex
@needs_pgf_pdflatex
@pytest.mark.skipif(not _has_tex_package('type1ec'), reason='needs type1ec.sty')
@pytest.mark.skipif(not _has_tex_package('ucs'), reason='needs ucs.sty')
@pytest.mark.backend('pgf')
@image_comparison(['pgf_pdflatex.pdf'], style='default',
tol=11.71 if _old_gs_version else 0)
def test_pdflatex():
rc_pdflatex = {'font.family': 'serif',
'pgf.rcfonts': False,
'pgf.texsystem': 'pdflatex',
'pgf.preamble': ('\\usepackage[utf8x]{inputenc}'
'\\usepackage[T1]{fontenc}')}
mpl.rcParams.update(rc_pdflatex)
create_figure()
# test updating the rc parameters for each figure
@needs_pgf_xelatex
@needs_pgf_pdflatex
@mpl.style.context('default')
@pytest.mark.backend('pgf')
def test_rcupdate():
rc_sets = [{'font.family': 'sans-serif',
'font.size': 30,
'figure.subplot.left': .2,
'lines.markersize': 10,
'pgf.rcfonts': False,
'pgf.texsystem': 'xelatex'},
{'font.family': 'monospace',
'font.size': 10,
'figure.subplot.left': .1,
'lines.markersize': 20,
'pgf.rcfonts': False,
'pgf.texsystem': 'pdflatex',
'pgf.preamble': ('\\usepackage[utf8x]{inputenc}'
'\\usepackage[T1]{fontenc}'
'\\usepackage{sfmath}')}]
tol = [0, 13.2] if _old_gs_version else [0, 0]
for i, rc_set in enumerate(rc_sets):
with mpl.rc_context(rc_set):
for substring, pkg in [('sfmath', 'sfmath'), ('utf8x', 'ucs')]:
if (substring in mpl.rcParams['pgf.preamble']
and not _has_tex_package(pkg)):
pytest.skip(f'needs {pkg}.sty')
create_figure()
compare_figure(f'pgf_rcupdate{i + 1}.pdf', tol=tol[i])
# test backend-side clipping, since large numbers are not supported by TeX
@needs_pgf_xelatex
@mpl.style.context('default')
@pytest.mark.backend('pgf')
def test_pathclip():
np.random.seed(19680801)
mpl.rcParams.update({'font.family': 'serif', 'pgf.rcfonts': False})
fig, axs = plt.subplots(1, 2)
axs[0].plot([0., 1e100], [0., 1e100])
axs[0].set_xlim(0, 1)
axs[0].set_ylim(0, 1)
axs[1].scatter([0, 1], [1, 1])
axs[1].hist(np.random.normal(size=1000), bins=20, range=[-10, 10])
axs[1].set_xscale('log')
fig.savefig(BytesIO(), format="pdf") # No image comparison.
# test mixed mode rendering
@needs_pgf_xelatex
@pytest.mark.backend('pgf')
@image_comparison(['pgf_mixedmode.pdf'], style='default')
def test_mixedmode():
mpl.rcParams.update({'font.family': 'serif', 'pgf.rcfonts': False})
Y, X = np.ogrid[-1:1:40j, -1:1:40j]
plt.pcolor(X**2 + Y**2).set_rasterized(True)
# test bbox_inches clipping
@needs_pgf_xelatex
@mpl.style.context('default')
@pytest.mark.backend('pgf')
def test_bbox_inches():
mpl.rcParams.update({'font.family': 'serif', 'pgf.rcfonts': False})
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.plot(range(5))
ax2.plot(range(5))
plt.tight_layout()
bbox = ax1.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
compare_figure('pgf_bbox_inches.pdf', savefig_kwargs={'bbox_inches': bbox},
tol=0)
@mpl.style.context('default')
@pytest.mark.backend('pgf')
@pytest.mark.parametrize('system', [
pytest.param('lualatex', marks=[needs_pgf_lualatex]),
pytest.param('pdflatex', marks=[needs_pgf_pdflatex]),
pytest.param('xelatex', marks=[needs_pgf_xelatex]),
])
def test_pdf_pages(system):
rc_pdflatex = {
'font.family': 'serif',
'pgf.rcfonts': False,
'pgf.texsystem': system,
}
mpl.rcParams.update(rc_pdflatex)
fig1, ax1 = plt.subplots()
ax1.plot(range(5))
fig1.tight_layout()
fig2, ax2 = plt.subplots(figsize=(3, 2))
ax2.plot(range(5))
fig2.tight_layout()
path = os.path.join(result_dir, f'pdfpages_{system}.pdf')
md = {
'Author': 'me',
'Title': 'Multipage PDF with pgf',
'Subject': 'Test page',
'Keywords': 'test,pdf,multipage',
'ModDate': datetime.datetime(
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
'Trapped': 'Unknown'
}
with PdfPages(path, metadata=md) as pdf:
pdf.savefig(fig1)
pdf.savefig(fig2)
pdf.savefig(fig1)
assert pdf.get_pagecount() == 3
@mpl.style.context('default')
@pytest.mark.backend('pgf')
@pytest.mark.parametrize('system', [
pytest.param('lualatex', marks=[needs_pgf_lualatex]),
pytest.param('pdflatex', marks=[needs_pgf_pdflatex]),
pytest.param('xelatex', marks=[needs_pgf_xelatex]),
])
def test_pdf_pages_metadata_check(monkeypatch, system):
# Basically the same as test_pdf_pages, but we keep it separate to leave
# pikepdf as an optional dependency.
pikepdf = pytest.importorskip('pikepdf')
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
mpl.rcParams.update({'pgf.texsystem': system})
fig, ax = plt.subplots()
ax.plot(range(5))
md = {
'Author': 'me',
'Title': 'Multipage PDF with pgf',
'Subject': 'Test page',
'Keywords': 'test,pdf,multipage',
'ModDate': datetime.datetime(
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
'Trapped': 'True'
}
path = os.path.join(result_dir, f'pdfpages_meta_check_{system}.pdf')
with PdfPages(path, metadata=md) as pdf:
pdf.savefig(fig)
with pikepdf.Pdf.open(path) as pdf:
info = {k: str(v) for k, v in pdf.docinfo.items()}
# Not set by us, so don't bother checking.
if '/PTEX.FullBanner' in info:
del info['/PTEX.FullBanner']
if '/PTEX.Fullbanner' in info:
del info['/PTEX.Fullbanner']
# Some LaTeX engines ignore this setting, and state themselves as producer.
producer = info.pop('/Producer')
assert producer == f'Matplotlib pgf backend v{mpl.__version__}' or (
system == 'lualatex' and 'LuaTeX' in producer)
assert info == {
'/Author': 'me',
'/CreationDate': 'D:19700101000000Z',
'/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
'/Keywords': 'test,pdf,multipage',
'/ModDate': 'D:19680801000000Z',
'/Subject': 'Test page',
'/Title': 'Multipage PDF with pgf',
'/Trapped': '/True',
}
@needs_pgf_xelatex
def test_multipage_keep_empty(tmp_path):
# test empty pdf files
# an empty pdf is left behind with keep_empty unset
fn = tmp_path / "a.pdf"
with pytest.warns(mpl.MatplotlibDeprecationWarning), PdfPages(fn) as pdf:
pass
assert fn.exists()
# an empty pdf is left behind with keep_empty=True
fn = tmp_path / "b.pdf"
with pytest.warns(mpl.MatplotlibDeprecationWarning), \
PdfPages(fn, keep_empty=True) as pdf:
pass
assert fn.exists()
# an empty pdf deletes itself afterwards with keep_empty=False
fn = tmp_path / "c.pdf"
with PdfPages(fn, keep_empty=False) as pdf:
pass
assert not fn.exists()
# test pdf files with content, they should never be deleted
# a non-empty pdf is left behind with keep_empty unset
fn = tmp_path / "d.pdf"
with PdfPages(fn) as pdf:
pdf.savefig(plt.figure())
assert fn.exists()
# a non-empty pdf is left behind with keep_empty=True
fn = tmp_path / "e.pdf"
with pytest.warns(mpl.MatplotlibDeprecationWarning), \
PdfPages(fn, keep_empty=True) as pdf:
pdf.savefig(plt.figure())
assert fn.exists()
# a non-empty pdf is left behind with keep_empty=False
fn = tmp_path / "f.pdf"
with PdfPages(fn, keep_empty=False) as pdf:
pdf.savefig(plt.figure())
assert fn.exists()
@needs_pgf_xelatex
def test_tex_restart_after_error():
fig = plt.figure()
fig.suptitle(r"\oops")
with pytest.raises(ValueError):
fig.savefig(BytesIO(), format="pgf")
fig = plt.figure() # start from scratch
fig.suptitle(r"this is ok")
fig.savefig(BytesIO(), format="pgf")
@needs_pgf_xelatex
def test_bbox_inches_tight():
fig, ax = plt.subplots()
ax.imshow([[0, 1], [2, 3]])
fig.savefig(BytesIO(), format="pdf", backend="pgf", bbox_inches="tight")
@needs_pgf_xelatex
@needs_ghostscript
def test_png_transparency(): # Actually, also just testing that png works.
buf = BytesIO()
plt.figure().savefig(buf, format="png", backend="pgf", transparent=True)
buf.seek(0)
t = plt.imread(buf)
assert (t[..., 3] == 0).all() # fully transparent.
@needs_pgf_xelatex
def test_unknown_font(caplog):
with caplog.at_level("WARNING"):
mpl.rcParams["font.family"] = "this-font-does-not-exist"
plt.figtext(.5, .5, "hello, world")
plt.savefig(BytesIO(), format="pgf")
assert "Ignoring unknown font: this-font-does-not-exist" in [
r.getMessage() for r in caplog.records]
@check_figures_equal(extensions=["pdf"])
@pytest.mark.parametrize("texsystem", ("pdflatex", "xelatex", "lualatex"))
@pytest.mark.backend("pgf")
def test_minus_signs_with_tex(fig_test, fig_ref, texsystem):
if not _check_for_pgf(texsystem):
pytest.skip(texsystem + ' + pgf is required')
mpl.rcParams["pgf.texsystem"] = texsystem
fig_test.text(.5, .5, "$-1$")
fig_ref.text(.5, .5, "$\N{MINUS SIGN}1$")
@pytest.mark.backend("pgf")
def test_sketch_params():
fig, ax = plt.subplots(figsize=(3, 3))
ax.set_xticks([])
ax.set_yticks([])
ax.set_frame_on(False)
handle, = ax.plot([0, 1])
handle.set_sketch_params(scale=5, length=30, randomness=42)
with BytesIO() as fd:
fig.savefig(fd, format='pgf')
buf = fd.getvalue().decode()
baseline = r"""\pgfpathmoveto{\pgfqpoint{0.375000in}{0.300000in}}%
\pgfpathlineto{\pgfqpoint{2.700000in}{2.700000in}}%
\usepgfmodule{decorations}%
\usepgflibrary{decorations.pathmorphing}%
\pgfkeys{/pgf/decoration/.cd, """ \
r"""segment length = 0.150000in, amplitude = 0.100000in}%
\pgfmathsetseed{42}%
\pgfdecoratecurrentpath{random steps}%
\pgfusepath{stroke}%"""
# \pgfdecoratecurrentpath must be after the path definition and before the
# path is used (\pgfusepath)
assert baseline in buf