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

Skip to content

ps: Add option to use figure size as paper size #26479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/api/next_api_changes/behavior/26479-ES.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
PostScript paper type adds option to use figure size
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The :rc:`ps.papertype` rcParam can now be set to ``'figure'``, which will use
a paper size that corresponds exactly with the size of the figure that is being
saved.
49 changes: 28 additions & 21 deletions lib/matplotlib/backends/backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ def _print_ps(
if papertype is None:
papertype = mpl.rcParams['ps.papersize']
papertype = papertype.lower()
_api.check_in_list(['auto', *papersize], papertype=papertype)
_api.check_in_list(['figure', 'auto', *papersize], papertype=papertype)

orientation = _api.check_getitem(
_Orientation, orientation=orientation.lower())
Expand Down Expand Up @@ -873,24 +873,16 @@ def _print_figure(
width, height = self.figure.get_size_inches()
if papertype == 'auto':
_api.warn_deprecated("3.8", name="papertype='auto'",
addendum="Pass an explicit paper type, or omit the "
"*papertype* argument entirely.")
addendum="Pass an explicit paper type, 'figure', or "
"omit the *papertype* argument entirely.")
papertype = _get_papertype(*orientation.swap_if_landscape((width, height)))

if is_eps:
if is_eps or papertype == 'figure':
paper_width, paper_height = width, height
else:
paper_width, paper_height = orientation.swap_if_landscape(
papersize[papertype])

if mpl.rcParams['ps.usedistiller']:
# distillers improperly clip eps files if pagesize is too small
if width > paper_width or height > paper_height:
papertype = _get_papertype(
*orientation.swap_if_landscape((width, height)))
paper_width, paper_height = orientation.swap_if_landscape(
papersize[papertype])

# center the figure on the paper
xo = 72 * 0.5 * (paper_width - width)
yo = 72 * 0.5 * (paper_height - height)
Expand Down Expand Up @@ -921,10 +913,10 @@ def print_figure_impl(fh):
if is_eps:
print("%!PS-Adobe-3.0 EPSF-3.0", file=fh)
else:
print(f"%!PS-Adobe-3.0\n"
f"%%DocumentPaperSizes: {papertype}\n"
f"%%Pages: 1\n",
end="", file=fh)
print("%!PS-Adobe-3.0", file=fh)
if papertype != 'figure':
print(f"%%DocumentPaperSizes: {papertype}", file=fh)
print("%%Pages: 1", file=fh)
print(f"%%LanguageLevel: 3\n"
f"{dsc_comments}\n"
f"%%Orientation: {orientation.name}\n"
Expand Down Expand Up @@ -1061,7 +1053,7 @@ def _print_figure_tex(
# set the paper size to the figure size if is_eps. The
# resulting ps file has the given size with correct bounding
# box so that there is no need to call 'pstoeps'
if is_eps:
if is_eps or papertype == 'figure':
paper_width, paper_height = orientation.swap_if_landscape(
self.figure.get_size_inches())
else:
Expand Down Expand Up @@ -1160,17 +1152,22 @@ def gs_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False):
"""

if eps:
paper_option = "-dEPSCrop"
paper_option = ["-dEPSCrop"]
elif ptype == "figure":
# The bbox will have its lower-left corner at (0, 0), so upper-right
# corner corresponds with paper size.
paper_option = [f"-dDEVICEWIDTHPOINTS={bbox[2]}",
f"-dDEVICEHEIGHTPOINTS={bbox[3]}"]
else:
paper_option = "-sPAPERSIZE=%s" % ptype
paper_option = [f"-sPAPERSIZE={ptype}"]

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

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

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

if eps:
paper_option = ["-dEPSCrop"]
elif ptype == "figure":
# The bbox will have its lower-left corner at (0, 0), so upper-right
# corner corresponds with paper size.
paper_option = [f"-dDEVICEWIDTHPOINTS#{bbox[2]}",
f"-dDEVICEHEIGHTPOINTS#{bbox[3]}"]
else:
paper_option = [f"-sPAPERSIZE#{ptype}"]

with TemporaryDirectory() as tmpdir:
tmppdf = pathlib.Path(tmpdir, "tmp.pdf")
tmpps = pathlib.Path(tmpdir, "tmp.ps")
Expand All @@ -1208,7 +1215,7 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False):
"-sAutoRotatePages#None",
"-sGrayImageFilter#FlateEncode",
"-sColorImageFilter#FlateEncode",
"-dEPSCrop" if eps else "-sPAPERSIZE#%s" % ptype,
*paper_option,
tmpfile, tmppdf], _log)
cbook._check_and_log_subprocess(
["pdftops", "-paper", "match", "-level3", tmppdf, tmpps], _log)
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/mpl-data/matplotlibrc
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@
#tk.window_focus: False # Maintain shell focus for TkAgg

### ps backend params
#ps.papersize: letter # {letter, legal, ledger, A0-A10, B0-B10}
#ps.papersize: letter # {figure, letter, legal, ledger, A0-A10, B0-B10}
#ps.useafm: False # use AFM fonts, results in small files
#ps.usedistiller: False # {ghostscript, xpdf, None}
# Experimental: may produce smaller files.
Expand Down
6 changes: 3 additions & 3 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,13 @@ def validate_ps_distiller(s):
def _validate_papersize(s):
# Re-inline this validator when the 'auto' deprecation expires.
s = ValidateInStrings("ps.papersize",
["auto", "letter", "legal", "ledger",
["figure", "auto", "letter", "legal", "ledger",
*[f"{ab}{i}" for ab in "ab" for i in range(11)]],
ignorecase=True)(s)
if s == "auto":
_api.warn_deprecated("3.8", name="ps.papersize='auto'",
addendum="Pass an explicit paper type, or omit the "
"*ps.papersize* rcParam entirely.")
addendum="Pass an explicit paper type, figure, or omit "
"the *ps.papersize* rcParam entirely.")
return s


Expand Down
44 changes: 39 additions & 5 deletions lib/matplotlib/tests/test_backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

# This tests tends to hit a TeX cache lock on AppVeyor.
@pytest.mark.flaky(reruns=3)
@pytest.mark.parametrize('papersize', ['letter', 'figure'])
@pytest.mark.parametrize('orientation', ['portrait', 'landscape'])
@pytest.mark.parametrize('format, use_log, rcParams', [
('ps', False, {}),
Expand All @@ -38,7 +39,19 @@
'eps afm',
'eps with usetex'
])
def test_savefig_to_stringio(format, use_log, rcParams, orientation):
def test_savefig_to_stringio(format, use_log, rcParams, orientation, papersize):
if rcParams.get("ps.usedistiller") == "ghostscript":
try:
mpl._get_executable_info("gs")
except mpl.ExecutableNotFoundError as exc:
pytest.skip(str(exc))
elif rcParams.get("ps.userdistiller") == "xpdf":
try:
mpl._get_executable_info("gs") # Effectively checks for ps2pdf.
mpl._get_executable_info("pdftops")
except mpl.ExecutableNotFoundError as exc:
pytest.skip(str(exc))

mpl.rcParams.update(rcParams)

fig, ax = plt.subplots()
Expand All @@ -54,15 +67,15 @@ def test_savefig_to_stringio(format, use_log, rcParams, orientation):
title += " \N{MINUS SIGN}\N{EURO SIGN}"
ax.set_title(title)
allowable_exceptions = []
if rcParams.get("ps.usedistiller"):
allowable_exceptions.append(mpl.ExecutableNotFoundError)
if rcParams.get("text.usetex"):
allowable_exceptions.append(RuntimeError)
if rcParams.get("ps.useafm"):
allowable_exceptions.append(mpl.MatplotlibDeprecationWarning)
try:
fig.savefig(s_buf, format=format, orientation=orientation)
fig.savefig(b_buf, format=format, orientation=orientation)
fig.savefig(s_buf, format=format, orientation=orientation,
papertype=papersize)
fig.savefig(b_buf, format=format, orientation=orientation,
papertype=papersize)
except tuple(allowable_exceptions) as exc:
pytest.skip(str(exc))

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

if format == 'ps':
# Default figsize = (8, 6) inches = (576, 432) points = (203.2, 152.4) mm.
# Landscape orientation will swap dimensions.
if rcParams.get("ps.usedistiller") == "xpdf":
# Some versions specifically show letter/203x152, but not all,
# so we can only use this simpler test.
if papersize == 'figure':
assert b'letter' not in s_val.lower()
else:
assert b'letter' in s_val.lower()
elif rcParams.get("ps.usedistiller") or rcParams.get("text.usetex"):
width = b'432.0' if orientation == 'landscape' else b'576.0'
wanted = (b'-dDEVICEWIDTHPOINTS=' + width if papersize == 'figure'
else b'-sPAPERSIZE')
assert wanted in s_val
else:
if papersize == 'figure':
assert b'%%DocumentPaperSizes' not in s_val
else:
assert b'%%DocumentPaperSizes' in s_val

# Strip out CreationDate: ghostscript and cairo don't obey
# SOURCE_DATE_EPOCH, and that environment variable is already tested in
# test_determinism.
Expand Down