From b28d7575c9573598475be245ec2a7e87a263c1e7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 20 Apr 2023 18:23:43 -0400 Subject: [PATCH 1/4] ps: Explicitly set language level to 3 We currently use various operators from level 2: - `<<` - `>>` - `colorimage` - `glyphshow` - `makepattern` - `rectfill` - `selectfont` - `setpagedevice` - `setpattern` and from level 3: - `ShadingType` - `shfill` - Type 42 fonts, possibly without any conditions or fallback, so we should be explicit about what we require. This is already part of the xpdf distiller at level 2, but not ghostscript. Since the distiller is optional though, we shouldn't rely on that. --- lib/matplotlib/backends/backend_ps.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 3231ed62da9c..5ca8140fd134 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -914,7 +914,8 @@ def print_figure_impl(fh): f"%%DocumentPaperSizes: {papertype}\n" f"%%Pages: 1\n", end="", file=fh) - print(f"{dsc_comments}\n" + print(f"%%LanguageLevel: 3\n" + f"{dsc_comments}\n" f"%%Orientation: {orientation.name}\n" f"{get_bbox_header(bbox)[0]}\n" f"%%EndComments\n", @@ -1024,6 +1025,7 @@ def _print_figure_tex( tmppath.write_text( f"""\ %!PS-Adobe-3.0 EPSF-3.0 +%%LanguageLevel: 3 {dsc_comments} {get_bbox_header(bbox)[0]} %%EndComments @@ -1196,7 +1198,7 @@ def xpdf_distill(tmpfile, eps=False, ptype='letter', bbox=None, rotated=False): "-dEPSCrop" if eps else "-sPAPERSIZE#%s" % ptype, tmpfile, tmppdf], _log) cbook._check_and_log_subprocess( - ["pdftops", "-paper", "match", "-level2", tmppdf, tmpps], _log) + ["pdftops", "-paper", "match", "-level3", tmppdf, tmpps], _log) shutil.move(tmpps, tmpfile) if eps: pstoeps(tmpfile) From e39d792bb3e7a83fe6d317eafb1fb596cab88980 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 20 Apr 2023 23:17:09 -0400 Subject: [PATCH 2/4] ps: Deprecate the psDefs module-level variable This is just a bunch of shortcut procedure definitions for Matplotlib's internal use; there's not much use for them externally. --- doc/api/next_api_changes/deprecations/25742-ES.rst | 5 +++++ lib/matplotlib/backends/backend_ps.py | 11 ++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/25742-ES.rst diff --git a/doc/api/next_api_changes/deprecations/25742-ES.rst b/doc/api/next_api_changes/deprecations/25742-ES.rst new file mode 100644 index 000000000000..2189a0665b65 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/25742-ES.rst @@ -0,0 +1,5 @@ +``backend_ps.psDefs`` +~~~~~~~~~~~~~~~~~~~~~ + +The ``psDefs`` module-level variable in ``backend_ps`` is deprecated with no +replacement. diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 5ca8140fd134..b0bc90ca3c34 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -49,6 +49,7 @@ class __getattr__: # module-level deprecations ps_backend_helper = _api.deprecated("3.7", obj_type="")( property(lambda self: PsBackendHelper())) + psDefs = _api.deprecated("3.8", obj_type="")(property(lambda self: _psDefs)) papersize = {'letter': (8.5, 11), @@ -921,13 +922,13 @@ def print_figure_impl(fh): f"%%EndComments\n", end="", file=fh) - Ndict = len(psDefs) + Ndict = len(_psDefs) print("%%BeginProlog", file=fh) if not mpl.rcParams['ps.useafm']: Ndict += len(ps_renderer._character_tracker.used) print("/mpldict %d dict def" % Ndict, file=fh) print("mpldict begin", file=fh) - print("\n".join(psDefs), file=fh) + print("\n".join(_psDefs), file=fh) if not mpl.rcParams['ps.useafm']: for font_path, chars \ in ps_renderer._character_tracker.used.items(): @@ -1030,9 +1031,9 @@ def _print_figure_tex( {get_bbox_header(bbox)[0]} %%EndComments %%BeginProlog -/mpldict {len(psDefs)} dict def +/mpldict {len(_psDefs)} dict def mpldict begin -{"".join(psDefs)} +{"".join(_psDefs)} end %%EndProlog mpldict begin @@ -1297,7 +1298,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False): # The usage comments use the notation of the operator summary # in the PostScript Language reference manual. -psDefs = [ +_psDefs = [ # name proc *_d* - # Note that this cannot be bound to /d, because when embedding a Type3 font # we may want to define a "d" glyph using "/d{...} d" which would locally From 7062ddabe0ff514db29c203bc3d0ea06eb7a3f31 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 21 Apr 2023 00:31:46 -0400 Subject: [PATCH 3/4] ps: Use rectclip instead of custom clipbox Since we require language level 2 now, we can use this directly. --- lib/matplotlib/backends/backend_ps.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index b0bc90ca3c34..949ec0234677 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -407,7 +407,7 @@ def _get_clip_cmd(self, gc): clip = [] rect = gc.get_clip_rectangle() if rect is not None: - clip.append("%s clipbox\n" % _nums_to_str(*rect.size, *rect.p0)) + clip.append(f"{_nums_to_str(*rect.p0, *rect.size)} rectclip\n") path, trf = gc.get_clip_path() if path is not None: key = (path, id(trf)) @@ -953,8 +953,7 @@ def print_figure_impl(fh): print("%s translate" % _nums_to_str(xo, yo), file=fh) if rotation: print("%d rotate" % rotation, file=fh) - print("%s clipbox" % _nums_to_str(width*72, height*72, 0, 0), - file=fh) + print(f"0 0 {_nums_to_str(width*72, height*72)} rectclip", file=fh) # write the figure print(self._pswriter.getvalue(), file=fh) @@ -1038,7 +1037,7 @@ def _print_figure_tex( %%EndProlog mpldict begin {_nums_to_str(xo, yo)} translate -{_nums_to_str(width*72, height*72)} 0 0 clipbox +0 0 {_nums_to_str(width*72, height*72)} rectclip {self._pswriter.getvalue()} end showpage @@ -1316,20 +1315,6 @@ def pstoeps(tmpfile, bbox=None, rotated=False): "/cl { closepath } _d", # *ce* - "/ce { closepath eofill } _d", - # w h x y *box* - - """/box { - m - 1 index 0 r - 0 exch r - neg 0 r - cl - } _d""", - # w h x y *clipbox* - - """/clipbox { - box - clip - newpath - } _d""", # wx wy llx lly urx ury *setcachedevice* - "/sc { setcachedevice } _d", ] From dec80724acf6433b9fb0b58831349873f7d3735e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 21 Apr 2023 04:27:44 -0400 Subject: [PATCH 4/4] ps: Use _nums_to_str in more places I only change places that used the exact same formatting, and not cases of, e.g., `%f` or `%g`. --- lib/matplotlib/backends/backend_ps.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 949ec0234677..92b97eef1010 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -88,8 +88,8 @@ def _get_papertype(w, h): return 'a0' -def _nums_to_str(*args): - return " ".join(f"{arg:1.3f}".rstrip("0").rstrip(".") for arg in args) +def _nums_to_str(*args, sep=" "): + return sep.join(f"{arg:1.3f}".rstrip("0").rstrip(".") for arg in args) def _move_path_to_path_or_stream(src, dst): @@ -293,16 +293,16 @@ def _is_transparent(self, rgb_or_rgba): def set_color(self, r, g, b, store=True): if (r, g, b) != self.color: - self._pswriter.write(f"{r:1.3f} setgray\n" + self._pswriter.write(f"{_nums_to_str(r)} setgray\n" if r == g == b else - f"{r:1.3f} {g:1.3f} {b:1.3f} setrgbcolor\n") + f"{_nums_to_str(r, g, b)} setrgbcolor\n") if store: self.color = (r, g, b) def set_linewidth(self, linewidth, store=True): linewidth = float(linewidth) if linewidth != self.linewidth: - self._pswriter.write("%1.3f setlinewidth\n" % linewidth) + self._pswriter.write(f"{_nums_to_str(linewidth)} setlinewidth\n") if store: self.linewidth = linewidth @@ -338,8 +338,7 @@ def set_linedash(self, offset, seq, store=True): if np.array_equal(seq, oldseq) and oldo == offset: return - self._pswriter.write(f"[{_nums_to_str(*seq)}]" - f" {_nums_to_str(offset)} setdash\n" + self._pswriter.write(f"[{_nums_to_str(*seq)}] {_nums_to_str(offset)} setdash\n" if seq is not None and len(seq) else "[] 0 setdash\n") if store: @@ -474,9 +473,9 @@ def draw_markers( ps_color = ( None if self._is_transparent(rgbFace) - else '%1.3f setgray' % rgbFace[0] + else f'{_nums_to_str(rgbFace[0])} setgray' if rgbFace[0] == rgbFace[1] == rgbFace[2] - else '%1.3f %1.3f %1.3f setrgbcolor' % rgbFace[:3]) + else f'{_nums_to_str(*rgbFace[:3])} setrgbcolor') # construct the generic marker command: @@ -582,7 +581,7 @@ def draw_tex(self, gc, x, y, s, prop, angle, *, mtext=None): w, h, bl = self.get_text_width_height_descent(s, prop, ismath="TeX") fontsize = prop.get_size_in_points() thetext = 'psmarker%d' % self.textcnt - color = '%1.3f,%1.3f,%1.3f' % gc.get_rgb()[:3] + color = _nums_to_str(*gc.get_rgb()[:3], sep=',') fontcmd = {'sans-serif': r'{\sffamily %s}', 'monospace': r'{\ttfamily %s}'}.get( mpl.rcParams['font.family'][0], r'{\rmfamily %s}') @@ -784,8 +783,8 @@ def _draw_ps(self, ps, gc, rgbFace, *, fill=True, stroke=True): if hatch: hatch_name = self.create_hatch(hatch) write("gsave\n") - write("%f %f %f " % gc.get_hatch_color()[:3]) - write("%s setpattern fill grestore\n" % hatch_name) + write(_nums_to_str(*gc.get_hatch_color()[:3])) + write(f" {hatch_name} setpattern fill grestore\n") if stroke: write("stroke\n")