Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f5d1dbe

Browse files
committed
ps: Default to using figure size as paper
We already set the bounding box to coincide with the figure size, but setting an overall paper size can sometimes cause the resulting file to be cropped by the viewer. We additionally need to pass this through to the distiller.
1 parent 412dced commit f5d1dbe

File tree

5 files changed

+63
-28
lines changed

5 files changed

+63
-28
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: 25 additions & 3 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', [
2526
('ps', False, {}),
@@ -38,7 +39,7 @@
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):
4243
mpl.rcParams.update(rcParams)
4344

4445
fig, ax = plt.subplots()
@@ -61,8 +62,10 @@ def test_savefig_to_stringio(format, use_log, rcParams, orientation):
6162
if rcParams.get("ps.useafm"):
6263
allowable_exceptions.append(mpl.MatplotlibDeprecationWarning)
6364
try:
64-
fig.savefig(s_buf, format=format, orientation=orientation)
65-
fig.savefig(b_buf, format=format, orientation=orientation)
65+
fig.savefig(s_buf, format=format, orientation=orientation,
66+
papertype=papersize)
67+
fig.savefig(b_buf, format=format, orientation=orientation,
68+
papertype=papersize)
6669
except tuple(allowable_exceptions) as exc:
6770
pytest.skip(str(exc))
6871

@@ -71,6 +74,25 @@ def test_savefig_to_stringio(format, use_log, rcParams, orientation):
7174
s_val = s_buf.getvalue().encode('ascii')
7275
b_val = b_buf.getvalue()
7376

77+
if format == 'ps':
78+
# Default figsize = (8, 6) inches = (576, 432) points = (203.2, 152.4) mm.
79+
# Landscape orientation will swap dimensions.
80+
if rcParams.get("ps.usedistiller") == "xpdf":
81+
wanted = b'%%DocumentMedia: ' + (
82+
(b'152x203mm' if orientation == 'landscape' else b'203x152mm')
83+
if papersize == 'figure' else b'Letter')
84+
assert wanted in s_val
85+
elif rcParams.get("ps.usedistiller") or rcParams.get("text.usetex"):
86+
width = b'432.0' if orientation == 'landscape' else b'576.0'
87+
wanted = (b'-dDEVICEWIDTHPOINTS=' + width if papersize == 'figure'
88+
else b'-sPAPERSIZE')
89+
assert wanted in s_val
90+
else:
91+
if papersize == 'figure':
92+
assert b'%%DocumentPaperSizes' not in s_val
93+
else:
94+
assert b'%%DocumentPaperSizes' in s_val
95+
7496
# Strip out CreationDate: ghostscript and cairo don't obey
7597
# SOURCE_DATE_EPOCH, and that environment variable is already tested in
7698
# test_determinism.

0 commit comments

Comments
 (0)