274 lines
8.5 KiB
Python
274 lines
8.5 KiB
Python
|
import datetime
|
||
|
import io
|
||
|
import os
|
||
|
from pathlib import Path
|
||
|
import sys
|
||
|
from tempfile import NamedTemporaryFile
|
||
|
|
||
|
import numpy as np
|
||
|
import pytest
|
||
|
|
||
|
import matplotlib as mpl
|
||
|
from matplotlib import dviread, pyplot as plt, checkdep_usetex, rcParams
|
||
|
from matplotlib.backends.backend_pdf import PdfPages
|
||
|
from matplotlib.testing.decorators import check_figures_equal, image_comparison
|
||
|
|
||
|
|
||
|
needs_usetex = pytest.mark.skipif(
|
||
|
not checkdep_usetex(True),
|
||
|
reason="This test needs a TeX installation")
|
||
|
|
||
|
|
||
|
@image_comparison(['pdf_use14corefonts.pdf'])
|
||
|
def test_use14corefonts():
|
||
|
rcParams['pdf.use14corefonts'] = True
|
||
|
rcParams['font.family'] = 'sans-serif'
|
||
|
rcParams['font.size'] = 8
|
||
|
rcParams['font.sans-serif'] = ['Helvetica']
|
||
|
rcParams['pdf.compression'] = 0
|
||
|
|
||
|
text = '''A three-line text positioned just above a blue line
|
||
|
and containing some French characters and the euro symbol:
|
||
|
"Merci pépé pour les 10 €"'''
|
||
|
|
||
|
fig = plt.figure()
|
||
|
ax = fig.add_subplot(1, 1, 1)
|
||
|
ax.set_title('Test PDF backend with option use14corefonts=True')
|
||
|
ax.text(0.5, 0.5, text, horizontalalignment='center',
|
||
|
verticalalignment='bottom',
|
||
|
fontsize=14)
|
||
|
ax.axhline(0.5, linewidth=0.5)
|
||
|
|
||
|
|
||
|
def test_type42():
|
||
|
rcParams['pdf.fonttype'] = 42
|
||
|
|
||
|
fig = plt.figure()
|
||
|
ax = fig.add_subplot(111)
|
||
|
ax.plot([1, 2, 3])
|
||
|
fig.savefig(io.BytesIO())
|
||
|
|
||
|
|
||
|
def test_multipage_pagecount():
|
||
|
with PdfPages(io.BytesIO()) as pdf:
|
||
|
assert pdf.get_pagecount() == 0
|
||
|
fig = plt.figure()
|
||
|
ax = fig.add_subplot(111)
|
||
|
ax.plot([1, 2, 3])
|
||
|
fig.savefig(pdf, format="pdf")
|
||
|
assert pdf.get_pagecount() == 1
|
||
|
pdf.savefig()
|
||
|
assert pdf.get_pagecount() == 2
|
||
|
|
||
|
|
||
|
def test_multipage_properfinalize():
|
||
|
pdfio = io.BytesIO()
|
||
|
with PdfPages(pdfio) as pdf:
|
||
|
for i in range(10):
|
||
|
fig = plt.figure()
|
||
|
ax = fig.add_subplot(111)
|
||
|
ax.set_title('This is a long title')
|
||
|
fig.savefig(pdf, format="pdf")
|
||
|
pdfio.seek(0)
|
||
|
assert sum(b'startxref' in line for line in pdfio) == 1
|
||
|
assert sys.getsizeof(pdfio) < 40000
|
||
|
|
||
|
|
||
|
def test_multipage_keep_empty():
|
||
|
# test empty pdf files
|
||
|
# test that an empty pdf is left behind with keep_empty=True (default)
|
||
|
with NamedTemporaryFile(delete=False) as tmp:
|
||
|
with PdfPages(tmp) as pdf:
|
||
|
filename = pdf._file.fh.name
|
||
|
assert os.path.exists(filename)
|
||
|
os.remove(filename)
|
||
|
# test if an empty pdf is deleting itself afterwards with keep_empty=False
|
||
|
with PdfPages(filename, keep_empty=False) as pdf:
|
||
|
pass
|
||
|
assert not os.path.exists(filename)
|
||
|
# test pdf files with content, they should never be deleted
|
||
|
fig = plt.figure()
|
||
|
ax = fig.add_subplot(111)
|
||
|
ax.plot([1, 2, 3])
|
||
|
# test that a non-empty pdf is left behind with keep_empty=True (default)
|
||
|
with NamedTemporaryFile(delete=False) as tmp:
|
||
|
with PdfPages(tmp) as pdf:
|
||
|
filename = pdf._file.fh.name
|
||
|
pdf.savefig()
|
||
|
assert os.path.exists(filename)
|
||
|
os.remove(filename)
|
||
|
# test that a non-empty pdf is left behind with keep_empty=False
|
||
|
with NamedTemporaryFile(delete=False) as tmp:
|
||
|
with PdfPages(tmp, keep_empty=False) as pdf:
|
||
|
filename = pdf._file.fh.name
|
||
|
pdf.savefig()
|
||
|
assert os.path.exists(filename)
|
||
|
os.remove(filename)
|
||
|
|
||
|
|
||
|
def test_composite_image():
|
||
|
# Test that figures can be saved with and without combining multiple images
|
||
|
# (on a single set of axes) into a single composite image.
|
||
|
X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1))
|
||
|
Z = np.sin(Y ** 2)
|
||
|
fig = plt.figure()
|
||
|
ax = fig.add_subplot(1, 1, 1)
|
||
|
ax.set_xlim(0, 3)
|
||
|
ax.imshow(Z, extent=[0, 1, 0, 1])
|
||
|
ax.imshow(Z[::-1], extent=[2, 3, 0, 1])
|
||
|
plt.rcParams['image.composite_image'] = True
|
||
|
with PdfPages(io.BytesIO()) as pdf:
|
||
|
fig.savefig(pdf, format="pdf")
|
||
|
assert len(pdf._file._images) == 1
|
||
|
plt.rcParams['image.composite_image'] = False
|
||
|
with PdfPages(io.BytesIO()) as pdf:
|
||
|
fig.savefig(pdf, format="pdf")
|
||
|
assert len(pdf._file._images) == 2
|
||
|
|
||
|
|
||
|
def test_savefig_metadata(monkeypatch):
|
||
|
pikepdf = pytest.importorskip('pikepdf')
|
||
|
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
|
||
|
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.plot(range(5))
|
||
|
|
||
|
md = {
|
||
|
'Author': 'me',
|
||
|
'Title': 'Multipage PDF',
|
||
|
'Subject': 'Test page',
|
||
|
'Keywords': 'test,pdf,multipage',
|
||
|
'ModDate': datetime.datetime(
|
||
|
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
|
||
|
'Trapped': 'True'
|
||
|
}
|
||
|
buf = io.BytesIO()
|
||
|
fig.savefig(buf, metadata=md, format='pdf')
|
||
|
|
||
|
with pikepdf.Pdf.open(buf) as pdf:
|
||
|
info = {k: str(v) for k, v in pdf.docinfo.items()}
|
||
|
|
||
|
assert info == {
|
||
|
'/Author': 'me',
|
||
|
'/CreationDate': 'D:19700101000000Z',
|
||
|
'/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
|
||
|
'/Keywords': 'test,pdf,multipage',
|
||
|
'/ModDate': 'D:19680801000000Z',
|
||
|
'/Producer': f'Matplotlib pdf backend v{mpl.__version__}',
|
||
|
'/Subject': 'Test page',
|
||
|
'/Title': 'Multipage PDF',
|
||
|
'/Trapped': '/True',
|
||
|
}
|
||
|
|
||
|
|
||
|
def test_multipage_metadata(monkeypatch):
|
||
|
pikepdf = pytest.importorskip('pikepdf')
|
||
|
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
|
||
|
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.plot(range(5))
|
||
|
|
||
|
md = {
|
||
|
'Author': 'me',
|
||
|
'Title': 'Multipage PDF',
|
||
|
'Subject': 'Test page',
|
||
|
'Keywords': 'test,pdf,multipage',
|
||
|
'ModDate': datetime.datetime(
|
||
|
1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))),
|
||
|
'Trapped': 'True'
|
||
|
}
|
||
|
buf = io.BytesIO()
|
||
|
with PdfPages(buf, metadata=md) as pdf:
|
||
|
pdf.savefig(fig)
|
||
|
pdf.savefig(fig)
|
||
|
|
||
|
with pikepdf.Pdf.open(buf) as pdf:
|
||
|
info = {k: str(v) for k, v in pdf.docinfo.items()}
|
||
|
|
||
|
assert info == {
|
||
|
'/Author': 'me',
|
||
|
'/CreationDate': 'D:19700101000000Z',
|
||
|
'/Creator': f'Matplotlib v{mpl.__version__}, https://matplotlib.org',
|
||
|
'/Keywords': 'test,pdf,multipage',
|
||
|
'/ModDate': 'D:19680801000000Z',
|
||
|
'/Producer': f'Matplotlib pdf backend v{mpl.__version__}',
|
||
|
'/Subject': 'Test page',
|
||
|
'/Title': 'Multipage PDF',
|
||
|
'/Trapped': '/True',
|
||
|
}
|
||
|
|
||
|
|
||
|
def test_pdfpages_fspath():
|
||
|
with PdfPages(Path(os.devnull)) as pdf:
|
||
|
pdf.savefig(plt.figure())
|
||
|
|
||
|
|
||
|
@image_comparison(['hatching_legend.pdf'])
|
||
|
def test_hatching_legend():
|
||
|
"""Test for correct hatching on patches in legend"""
|
||
|
fig = plt.figure(figsize=(1, 2))
|
||
|
|
||
|
a = plt.Rectangle([0, 0], 0, 0, facecolor="green", hatch="XXXX")
|
||
|
b = plt.Rectangle([0, 0], 0, 0, facecolor="blue", hatch="XXXX")
|
||
|
|
||
|
fig.legend([a, b, a, b], ["", "", "", ""])
|
||
|
|
||
|
|
||
|
@image_comparison(['grayscale_alpha.pdf'])
|
||
|
def test_grayscale_alpha():
|
||
|
"""Masking images with NaN did not work for grayscale images"""
|
||
|
x, y = np.ogrid[-2:2:.1, -2:2:.1]
|
||
|
dd = np.exp(-(x**2 + y**2))
|
||
|
dd[dd < .1] = np.nan
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.imshow(dd, interpolation='none', cmap='gray_r')
|
||
|
ax.set_xticks([])
|
||
|
ax.set_yticks([])
|
||
|
|
||
|
|
||
|
# This tests tends to hit a TeX cache lock on AppVeyor.
|
||
|
@pytest.mark.flaky(reruns=3)
|
||
|
@needs_usetex
|
||
|
def test_missing_psfont(monkeypatch):
|
||
|
"""An error is raised if a TeX font lacks a Type-1 equivalent"""
|
||
|
def psfont(*args, **kwargs):
|
||
|
return dviread.PsFont(texname='texfont', psname='Some Font',
|
||
|
effects=None, encoding=None, filename=None)
|
||
|
|
||
|
monkeypatch.setattr(dviread.PsfontsMap, '__getitem__', psfont)
|
||
|
rcParams['text.usetex'] = True
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.text(0.5, 0.5, 'hello')
|
||
|
with NamedTemporaryFile() as tmpfile, pytest.raises(ValueError):
|
||
|
fig.savefig(tmpfile, format='pdf')
|
||
|
|
||
|
|
||
|
@pytest.mark.style('default')
|
||
|
@check_figures_equal(extensions=["pdf", "eps"])
|
||
|
def test_pdf_eps_savefig_when_color_is_none(fig_test, fig_ref):
|
||
|
ax_test = fig_test.add_subplot()
|
||
|
ax_test.set_axis_off()
|
||
|
ax_test.plot(np.sin(np.linspace(-5, 5, 100)), "v", c="none")
|
||
|
ax_ref = fig_ref.add_subplot()
|
||
|
ax_ref.set_axis_off()
|
||
|
|
||
|
|
||
|
@needs_usetex
|
||
|
def test_failing_latex(tmpdir):
|
||
|
"""Test failing latex subprocess call"""
|
||
|
path = str(tmpdir.join("tmpoutput.pdf"))
|
||
|
|
||
|
rcParams['text.usetex'] = True
|
||
|
|
||
|
# This fails with "Double subscript"
|
||
|
plt.xlabel("$22_2_2$")
|
||
|
with pytest.raises(RuntimeError):
|
||
|
plt.savefig(path)
|
||
|
|
||
|
|
||
|
def test_empty_rasterized():
|
||
|
# Check that empty figures that are rasterised save to pdf files fine
|
||
|
fig, ax = plt.subplots()
|
||
|
ax.plot([], [], rasterized=True)
|
||
|
fig.savefig(io.BytesIO(), format="pdf")
|