8000 Merge pull request #26479 from QuLogic/ps-papersize-figure · matplotlib/matplotlib@3ab6e1f · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 3ab6e1f

Browse files
authored
Merge pull request #26479 from QuLogic/ps-papersize-figure
ps: Add option to use figure size as paper size
2 parents fb3a97a + 96b26fe commit 3ab6e1f

File tree

5 files changed

+77
-30
lines changed

5 files changed

+77
-30
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
PostScript paper type adds option to use figure size
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The :rc:`ps.papertype` rcParam can now be set to ``'figure'``, which will use
5+
a paper size that corresponds exactly with the size of the figure that is being
6+
saved.

lib/matplotlib/backends/backend_ps.py

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ def _print_ps(
841841
if papertype is None:
842842
papertype = mpl.rcParams['ps.papersize']
843843
papertype = papertype.lower()
844-
_api.check_in_list(['auto', *papersize], papertype=papertype)
844+
_api.check_in_list(['figure', 'auto', *papersize], papertype=papertype)
845845

846846
orientation = _api.check_getitem(
847847
_Orientation, orientation=orientation.lower())
@@ -873,24 +873,16 @@ def _print_figure(
873873
width, height = self.figure.get_size_inches()
874874
if papertype == 'auto':
875875
_api.warn_deprecated("3.8", name="papertype='auto'",
876-
addendum="Pass an explicit paper type, or omit the "
877-
"*papertype* argument entirely.")
876+
addendum="Pass an explicit paper type, 'figure', or "
877+
"omit the *papertype* argument entirely.")
878878
papertype = _get_papertype(*orientation.swap_if_landscape((width, height)))
879879

880-
if is_eps:
880+
if is_eps or papertype == 'figure':
881881
paper_width, paper_height = width, height
882882
else:
883883
paper_width, paper_height = orientation.swap_if_landscape(
884884
papersize[papertype])
885885

886-
if mpl.rcParams['ps.usedistiller']:
887-
# distillers improperly clip eps files if pagesize is too small
888-
if width > paper_width or height > paper_height:
889-
papertype = _get_papertype(
890-
*orientation.swap_if_landscape((width, height)))
891-
paper_width, paper_height = orientation.swap_if_landscape(
892-
papersize[papertype])
893-
894886
# center the figure on the paper
895887
xo = 72 * 0.5 * (paper_width - width)
896888
yo = 72 * 0.5 * (paper_height - height)
@@ -921,10 +913,10 @@ def print_figure_impl(fh):
921913
if is_eps:
922914
print("%!PS-Adobe-3.0 EPSF-3.0", file=fh)
923915
else:
924-
print(f"%!PS-Adobe-3.0\n"
925-
f"%%DocumentPaperSizes: {papertype}\n"
926-
f"%%Pages: 1\n",
927-
end="", file=fh)
916+
print("%!PS-Adobe-3.0", file=fh)
917+
if papertype != 'figure':
918+
print(f"%%DocumentPaperSizes: {papertype}", file=fh)
919+
print("%%Pages: 1", file=fh)
928920
print(f"%%LanguageLevel: 3\n"
929921
f"{dsc_comments}\n"
930922
f"%%Orientation: {orientation.name}\n"
@@ -1061,7 +1053,7 @@ def _print_figure_tex(
10611053
# set the paper size to the figure size if is_eps. The
10621054
# resulting ps file has the given size with correct bounding
10631055
# box so that there is no need to call 'pstoeps'
1064-
if is_eps:
1056+
if is_eps or papertype == 'figure':
10651057
paper_width, paper_height = orientation.swap_if_landscape(
10661058
self.figure.get_size_inches())
10671059
else:
@@ -1160,17 +1152,22 @@ def gs_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False):
11601152
"""
11611153

11621154
if eps:
1163-
paper_option = "-dEPSCrop"
1155+
paper_option = ["-dEPSCrop"]
1156+
elif ptype == "figure":
1157+
# The bbox will have its lower-left corner at (0, 0), so upper-right
1158+
# corner corresponds with paper size.
1159+
paper_option = [f"-dDEVICEWIDTHPOINTS={bbox[2]}",
1160+
f"-dDEVICEHEIGHTPOINTS={bbox[3]}"]
11641161
else:
1165-
paper_option = "-sPAPERSIZE=%s" % ptype
1162+
paper_option = [f"-sPAPERSIZE={ptype}"]
11661163

11671164
psfile = tmpfile + '.ps'
11681165
dpi = mpl.rcParams['ps.distiller.res']
11691166

11701167
cbook._check_and_log_subprocess(
11711168
[mpl._get_executable_info("gs").executable,
11721169
"-dBATCH", "-dNOPAUSE", "-r%d" % dpi, "-sDEVICE=ps2write",
1173-
paper_option, "-sOutputFile=%s" % psfile, tmpfile],
1170+
*paper_option, f"-sOutputFile={psfile}", tmpfile],
11741171
_log)
11751172

11761173
os.remove(tmpfile)
@@ -1196,6 +1193,16 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False):
11961193
mpl._get_executable_info("gs") # Effectively checks for ps2pdf.
11971194
mpl._get_executable_info("pdftops")
11981195

1196+
if eps:
1197+
paper_option = ["-dEPSCrop"]
1198+
elif ptype == "figure":
1199+
# The bbox will have its lower-left corner at (0, 0), so upper-right
1200+
# corner corresponds with paper size.
1201+
paper_option = [f"-dDEVICEWIDTHPOINTS#{bbox[2]}",
1202+
f"-dDEVICEHEIGHTPOINTS#{bbox[3]}"]
1203+
else:
1204+
paper_option = [f"-sPAPERSIZE#{ptype}"]
1205+
11991206
with TemporaryDirectory() as tmpdir:
12001207
tmppdf = pathlib.Path(tmpdir, "tmp.pdf")
12011208
tmpps = pathlib.Path(tmpdir, "tmp.ps")
@@ -1208,7 +1215,7 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False):
12081215
"-sAutoRotatePages#None",
12091216
"-sGrayImageFilter#FlateEncode",
12101217
"-sColorImageFilter#FlateEncode",
1211-
"-dEPSCrop" if eps else "-sPAPERSIZE#%s" % ptype,
1218+
*paper_option,
12121219
tmpfile, tmppdf], _log)
12131220
cbook._check_and_log_subprocess(
12141221
["pdftops", "-paper", "match", "-level3", tmppdf, tmpps], _log)

lib/matplotlib/mpl-data/matplotlibrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@
709709
#tk.window_focus: False # Maintain shell focus for TkAgg
710710

711711
### ps backend params
712-
#ps.papersize: letter # {letter, legal, ledger, A0-A10, B0-B10}
712+
#ps.papersize: letter # {figure, letter, legal, ledger, A0-A10, B0-B10}
713713
#ps.useafm: False # use AFM fonts, results in small files
714714
#ps.usedistiller: False # {ghostscript, xpdf, None}
715715
# Experimental: may produce smaller files.

lib/matplotlib/rcsetup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -441,13 +441,13 @@ def validate_ps_distiller(s):
441441
def _validate_papersize(s):
442442
# Re-inline this validator when the 'auto' deprecation expires.
443443
s = ValidateInStrings("ps.papersize",
444-
["auto", "letter", "legal", "ledger",
444+
["figure", "auto", "letter", "legal", "ledger",
445445
*[f"{ab}{i}" for ab in "ab" for i in range(11)]],
446446
ignorecase=True)(s)
447447
if s == "auto":
448448
_api.warn_deprecated("3.8", name="ps.papersize='auto'",
449-
addendum="Pass an explicit paper type, or omit the "
450-
"*ps.papersize* rcParam entirely.")
449+
addendum="Pass an explicit paper type, figure, or omit "
450+
"the *ps.papersize* rcParam entirely.")
451451
return s
452452

453453

lib/matplotlib/tests/test_backend_ps.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
# This tests tends to hit a TeX cache lock on AppVeyor.
2222
@pytest.mark.flaky(reruns=3)
23+
@pytest.mark.parametrize('papersize', ['letter', 'figure'])
2324
@pytest.mark.parametrize('orientation', ['portrait', 'landscape'])
2425
@pytest.mark.parametrize('format, use_log, rcParams', [
8000 2526
('ps', False, {}),
@@ -38,7 +39,19 @@
3839
'eps afm',
3940
'eps with usetex'
4041
])
41-
def test_savefig_to_stringio(format, use_log, rcParams, orientation):
42+
def test_savefig_to_stringio(format, use_log, rcParams, orientation, papersize):
43+
if rcParams.get("ps.usedistiller") == "ghostscript":
44+
try:
45+
mpl._get_executable_info("gs")
46+
except mpl.ExecutableNotFoundError as exc:
47+
pytest.skip(str(exc))
48+
elif rcParams.get("ps.userdistiller") == "xpdf":
49+
try:
50+
mpl._get_executable_info("gs") # Effectively checks for ps2pdf.
51+
mpl._get_executable_info("pdftops")
52+
except mpl.ExecutableNotFoundError as exc:
53+
pytest.skip(str(exc))
54+
4255
mpl.rcParams.update(rcParams)
4356

4457
fig, ax = plt.subplots()
@@ -54,15 +67,15 @@ def test_savefig_to_stringio(format, use_log, rcParams, orientation):
5467
title += " \N{MINUS SIGN}\N{EURO SIGN}"
5568
ax.set_title(title)
5669
allowable_exceptions = []
57-
if rcParams.get("ps.usedistiller"):
58-
allowable_exceptions.append(mpl.ExecutableNotFoundError)
5970
if rcParams.get("text.usetex"):
6071
allowable_exceptions.append(RuntimeError)
6172
if rcParams.get("ps.useafm"):
6273
allowable_exceptions.append(mpl.MatplotlibDeprecationWarning)
6374
try:
64-
fig.savefig(s_buf, format=format, orientation=orientation)
65-
fig.savefig(b_buf, format=format, orientation=orientation)
75+
fig.savefig(s_buf, format=format, orientation=orientation,
76+
papertype=papersize)
77+
fig.savefig(b_buf, format=format, orientation=orientation,
78+
papertype=papersize)
6679
except tuple(allowable_exceptions) as exc:
6780
pytest.skip(str(exc))
6881

@@ -71,6 +84,27 @@ def test_savefig_to_stringio(format, use_log, rcParams, orientation):
7184
s_val = s_buf.getvalue().encode('ascii')
7285
b_val = b_buf.getvalue()
7386

87+
if format == 'ps':
88+
# Default figsize = (8, 6) inches = (576, 432) points = (203.2, 152.4) mm.
89+
# Landscape orientation will swap dimensions.
90+
if rcParams.get("ps.usedistiller") == "xpdf":
91+
# Some versions specifically show letter/203x152, but not all,
92+
# so we can only use this simpler test.
93+
if papersize == 'figure':
94+
assert b'letter' not in s_val.lower()
95+
else:
96+
assert b'letter' in s_val.lower()
97+
elif rcParams.get("ps.usedistiller") or rcParams.get("text.usetex"):
98+
width = b'432.0' if orientation == 'landscape' else b'576.0'
99+
wanted = (b'-dDEVICEWIDTHPOINTS=' + width if papersize == 'figure'
100+
else b'-sPAPERSIZE')
101+
assert wanted in s_val
102+
else:
103+
if papersize == 'figure':
104+
assert b'%%DocumentPaperSizes' not in s_val
105+
else:
106+
assert b'%%DocumentPaperSizes' in s_val
107+
74108
# Strip out CreationDate: ghostscript and cairo don't obey
75109
# SOURCE_DATE_EPOCH, and that environment variable is already tested in
76110
# test_determinism.

0 commit comments

Comments
 (0)
0