From 336028f4d521a23d9ea2de88b1faf82eddfa5b52 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 30 Sep 2021 11:14:15 +0200 Subject: [PATCH] Privatize various internal APIs of backend_pgf. ... and merge the now private get_preamble and get_fontspec together. --- .../deprecations/21962-AL.rst | 5 + lib/matplotlib/backends/backend_pgf.py | 313 +++++++++--------- lib/matplotlib/tests/test_backend_pgf.py | 6 +- 3 files changed, 169 insertions(+), 155 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/21962-AL.rst diff --git a/doc/api/next_api_changes/deprecations/21962-AL.rst b/doc/api/next_api_changes/deprecations/21962-AL.rst new file mode 100644 index 000000000000..e5ddab747b48 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/21962-AL.rst @@ -0,0 +1,5 @@ +``backend_pgf`` +~~~~~~~~~~~~~~~ +The following API elements have been deprecated with no +replacement: ``NO_ESCAPE``, ``re_mathsep``, ``get_fontspec``, ``get_preamble``, +``common_texification``, ``writeln``. diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 334cd6a05f16..d0341bc36e89 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -36,58 +36,69 @@ # which is not recognized by TeX. -def get_fontspec(): - """Build fontspec preamble from rc.""" - latex_fontspec = [] - texcommand = mpl.rcParams["pgf.texsystem"] - - if texcommand != "pdflatex": - latex_fontspec.append("\\usepackage{fontspec}") +@_api.caching_module_getattr +class __getattr__: + NO_ESCAPE = _api.deprecated("3.6", obj_type="")( + property(lambda self: _NO_ESCAPE)) + re_mathsep = _api.deprecated("3.6", obj_type="")( + property(lambda self: _split_math.__self__)) - if texcommand != "pdflatex" and mpl.rcParams["pgf.rcfonts"]: - families = ["serif", "sans\\-serif", "monospace"] - commands = ["setmainfont", "setsansfont", "setmonofont"] - for family, command in zip(families, commands): - # 1) Forward slashes also work on Windows, so don't mess with - # backslashes. 2) The dirname needs to include a separator. - path = pathlib.Path(fm.findfont(family)) - latex_fontspec.append(r"\%s{%s}[Path=\detokenize{%s}]" % ( - command, path.name, path.parent.as_posix() + "/")) - return "\n".join(latex_fontspec) +@_api.deprecated("3.6") +def get_fontspec(): + """Build fontspec preamble from rc.""" + with mpl.rc_context({"pgf.preamble": ""}): + return _get_preamble() +@_api.deprecated("3.6") def get_preamble(): """Get LaTeX preamble from rc.""" return mpl.rcParams["pgf.preamble"] -############################################################################### -# This almost made me cry!!! -# In the end, it's better to use only one unit for all coordinates, since the +def _get_preamble(): + """Prepare a LaTeX preamble based on the rcParams configuration.""" + preamble = [mpl.rcParams["pgf.preamble"]] + if mpl.rcParams["pgf.texsystem"] != "pdflatex": + preamble.append("\\usepackage{fontspec}") + if mpl.rcParams["pgf.rcfonts"]: + families = ["serif", "sans\\-serif", "monospace"] + commands = ["setmainfont", "setsansfont", "setmonofont"] + for family, command in zip(families, commands): + # 1) Forward slashes also work on Windows, so don't mess with + # backslashes. 2) The dirname needs to include a separator. + path = pathlib.Path(fm.findfont(family)) + preamble.append(r"\%s{%s}[Path=\detokenize{%s/}]" % ( + command, path.name, path.parent.as_posix())) + return "\n".join(preamble) + + +# It's better to use only one unit for all coordinates, since the # arithmetic in latex seems to produce inaccurate conversions. latex_pt_to_in = 1. / 72.27 latex_in_to_pt = 1. / latex_pt_to_in mpl_pt_to_in = 1. / 72. mpl_in_to_pt = 1. / mpl_pt_to_in -############################################################################### -# helper functions - -NO_ESCAPE = r"(? 3 else 1.0 if has_fill: - writeln(self.fh, - r"\definecolor{currentfill}{rgb}{%f,%f,%f}" - % tuple(rgbFace[:3])) - writeln(self.fh, r"\pgfsetfillcolor{currentfill}") + _writeln(self.fh, + r"\definecolor{currentfill}{rgb}{%f,%f,%f}" + % tuple(rgbFace[:3])) + _writeln(self.fh, r"\pgfsetfillcolor{currentfill}") if has_fill and fillopacity != 1.0: - writeln(self.fh, r"\pgfsetfillopacity{%f}" % fillopacity) + _writeln(self.fh, r"\pgfsetfillopacity{%f}" % fillopacity) # linewidth and color lw = gc.get_linewidth() * mpl_pt_to_in * latex_in_to_pt stroke_rgba = gc.get_rgb() - writeln(self.fh, r"\pgfsetlinewidth{%fpt}" % lw) - writeln(self.fh, - r"\definecolor{currentstroke}{rgb}{%f,%f,%f}" - % stroke_rgba[:3]) - writeln(self.fh, r"\pgfsetstrokecolor{currentstroke}") + _writeln(self.fh, r"\pgfsetlinewidth{%fpt}" % lw) + _writeln(self.fh, + r"\definecolor{currentstroke}{rgb}{%f,%f,%f}" + % stroke_rgba[:3]) + _writeln(self.fh, r"\pgfsetstrokecolor{currentstroke}") if strokeopacity != 1.0: - writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % strokeopacity) + _writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % strokeopacity) # line style dash_offset, dash_list = gc.get_dashes() if dash_list is None: - writeln(self.fh, r"\pgfsetdash{}{0pt}") + _writeln(self.fh, r"\pgfsetdash{}{0pt}") else: - writeln(self.fh, - r"\pgfsetdash{%s}{%fpt}" - % ("".join(r"{%fpt}" % dash for dash in dash_list), - dash_offset)) + _writeln(self.fh, + r"\pgfsetdash{%s}{%fpt}" + % ("".join(r"{%fpt}" % dash for dash in dash_list), + dash_offset)) def _print_pgf_path(self, gc, path, transform, rgbFace=None): f = 1. / self.dpi @@ -573,32 +586,32 @@ def _print_pgf_path(self, gc, path, transform, rgbFace=None): for points, code in path.iter_segments(transform, clip=clip): if code == Path.MOVETO: x, y = tuple(points) - writeln(self.fh, - r"\pgfpathmoveto{\pgfqpoint{%fin}{%fin}}" % - (f * x, f * y)) + _writeln(self.fh, + r"\pgfpathmoveto{\pgfqpoint{%fin}{%fin}}" % + (f * x, f * y)) elif code == Path.CLOSEPOLY: - writeln(self.fh, r"\pgfpathclose") + _writeln(self.fh, r"\pgfpathclose") elif code == Path.LINETO: x, y = tuple(points) - writeln(self.fh, - r"\pgfpathlineto{\pgfqpoint{%fin}{%fin}}" % - (f * x, f * y)) + _writeln(self.fh, + r"\pgfpathlineto{\pgfqpoint{%fin}{%fin}}" % + (f * x, f * y)) elif code == Path.CURVE3: cx, cy, px, py = tuple(points) coords = cx * f, cy * f, px * f, py * f - writeln(self.fh, - r"\pgfpathquadraticcurveto" - r"{\pgfqpoint{%fin}{%fin}}{\pgfqpoint{%fin}{%fin}}" - % coords) + _writeln(self.fh, + r"\pgfpathquadraticcurveto" + r"{\pgfqpoint{%fin}{%fin}}{\pgfqpoint{%fin}{%fin}}" + % coords) elif code == Path.CURVE4: c1x, c1y, c2x, c2y, px, py = tuple(points) coords = c1x * f, c1y * f, c2x * f, c2y * f, px * f, py * f - writeln(self.fh, - r"\pgfpathcurveto" - r"{\pgfqpoint{%fin}{%fin}}" - r"{\pgfqpoint{%fin}{%fin}}" - r"{\pgfqpoint{%fin}{%fin}}" - % coords) + _writeln(self.fh, + r"\pgfpathcurveto" + r"{\pgfqpoint{%fin}{%fin}}" + r"{\pgfqpoint{%fin}{%fin}}" + r"{\pgfqpoint{%fin}{%fin}}" + % coords) # apply pgf decorators sketch_params = gc.get_sketch_params() if gc else None @@ -616,13 +629,13 @@ def _print_pgf_path(self, gc, path, transform, rgbFace=None): length *= 0.5 scale *= 2 # PGF guarantees that repeated loading is a no-op - writeln(self.fh, r"\usepgfmodule{decorations}") - writeln(self.fh, r"\usepgflibrary{decorations.pathmorphing}") - writeln(self.fh, r"\pgfkeys{/pgf/decoration/.cd, " - f"segment length = {(length * f):f}in, " - f"amplitude = {(scale * f):f}in}}") - writeln(self.fh, f"\\pgfmathsetseed{{{int(randomness)}}}") - writeln(self.fh, r"\pgfdecoratecurrentpath{random steps}") + _writeln(self.fh, r"\usepgfmodule{decorations}") + _writeln(self.fh, r"\usepgflibrary{decorations.pathmorphing}") + _writeln(self.fh, r"\pgfkeys{/pgf/decoration/.cd, " + f"segment length = {(length * f):f}in, " + f"amplitude = {(scale * f):f}in}}") + _writeln(self.fh, f"\\pgfmathsetseed{{{int(randomness)}}}") + _writeln(self.fh, r"\pgfdecoratecurrentpath{random steps}") def _pgf_path_draw(self, stroke=True, fill=False): actions = [] @@ -630,7 +643,7 @@ def _pgf_path_draw(self, stroke=True, fill=False): actions.append("stroke") if fill: actions.append("fill") - writeln(self.fh, r"\pgfusepath{%s}" % ",".join(actions)) + _writeln(self.fh, r"\pgfusepath{%s}" % ",".join(actions)) def option_scale_image(self): # docstring inherited @@ -659,27 +672,27 @@ def draw_image(self, gc, x, y, im, transform=None): self.image_counter += 1 # reference the image in the pgf picture - writeln(self.fh, r"\begin{pgfscope}") + _writeln(self.fh, r"\begin{pgfscope}") self._print_pgf_clip(gc) f = 1. / self.dpi # from display coords to inch if transform is None: - writeln(self.fh, - r"\pgfsys@transformshift{%fin}{%fin}" % (x * f, y * f)) + _writeln(self.fh, + r"\pgfsys@transformshift{%fin}{%fin}" % (x * f, y * f)) w, h = w * f, h * f else: tr1, tr2, tr3, tr4, tr5, tr6 = transform.frozen().to_values() - writeln(self.fh, - r"\pgfsys@transformcm{%f}{%f}{%f}{%f}{%fin}{%fin}" % - (tr1 * f, tr2 * f, tr3 * f, tr4 * f, - (tr5 + x) * f, (tr6 + y) * f)) + _writeln(self.fh, + r"\pgfsys@transformcm{%f}{%f}{%f}{%f}{%fin}{%fin}" % + (tr1 * f, tr2 * f, tr3 * f, tr4 * f, + (tr5 + x) * f, (tr6 + y) * f)) w = h = 1 # scale is already included in the transform interp = str(transform is None).lower() # interpolation in PDF reader - writeln(self.fh, - r"\pgftext[left,bottom]" - r"{%s[interpolate=%s,width=%fin,height=%fin]{%s}}" % - (_get_image_inclusion_command(), - interp, w, h, fname_img)) - writeln(self.fh, r"\end{pgfscope}") + _writeln(self.fh, + r"\pgftext[left,bottom]" + r"{%s[interpolate=%s,width=%fin,height=%fin]{%s}}" % + (_get_image_inclusion_command(), + interp, w, h, fname_img)) + _writeln(self.fh, r"\end{pgfscope}") def draw_tex(self, gc, x, y, s, prop, angle, ismath="TeX", mtext=None): # docstring inherited @@ -691,16 +704,16 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # prepare string for tex s = _escape_and_apply_props(s, prop) - writeln(self.fh, r"\begin{pgfscope}") + _writeln(self.fh, r"\begin{pgfscope}") alpha = gc.get_alpha() if alpha != 1.0: - writeln(self.fh, r"\pgfsetfillopacity{%f}" % alpha) - writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % alpha) + _writeln(self.fh, r"\pgfsetfillopacity{%f}" % alpha) + _writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % alpha) rgb = tuple(gc.get_rgb())[:3] - writeln(self.fh, r"\definecolor{textcolor}{rgb}{%f,%f,%f}" % rgb) - writeln(self.fh, r"\pgfsetstrokecolor{textcolor}") - writeln(self.fh, r"\pgfsetfillcolor{textcolor}") + _writeln(self.fh, r"\definecolor{textcolor}{rgb}{%f,%f,%f}" % rgb) + _writeln(self.fh, r"\pgfsetstrokecolor{textcolor}") + _writeln(self.fh, r"\pgfsetfillcolor{textcolor}") s = r"\color{textcolor}" + s dpi = self.figure.dpi @@ -729,8 +742,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): if angle != 0: text_args.append("rotate=%f" % angle) - writeln(self.fh, r"\pgftext[%s]{%s}" % (",".join(text_args), s)) - writeln(self.fh, r"\end{pgfscope}") + _writeln(self.fh, r"\pgftext[%s]{%s}" % (",".join(text_args), s)) + _writeln(self.fh, r"\end{pgfscope}") def get_text_width_height_descent(self, s, prop, ismath): # docstring inherited @@ -816,9 +829,7 @@ def _print_pgf_to_fh(self, fh, *, bbox_inches_restore=None): # append the preamble used by the backend as a comment for debugging header_info_preamble = ["%% Matplotlib used the following preamble"] - for line in get_preamble().splitlines(): - header_info_preamble.append("%% " + line) - for line in get_fontspec().splitlines(): + for line in _get_preamble().splitlines(): header_info_preamble.append("%% " + line) header_info_preamble.append("%%") header_info_preamble = "\n".join(header_info_preamble) @@ -831,22 +842,22 @@ def _print_pgf_to_fh(self, fh, *, bbox_inches_restore=None): fh.write(header_text) fh.write(header_info_preamble) fh.write("\n") - writeln(fh, r"\begingroup") - writeln(fh, r"\makeatletter") - writeln(fh, r"\begin{pgfpicture}") - writeln(fh, - r"\pgfpathrectangle{\pgfpointorigin}{\pgfqpoint{%fin}{%fin}}" - % (w, h)) - writeln(fh, r"\pgfusepath{use as bounding box, clip}") + _writeln(fh, r"\begingroup") + _writeln(fh, r"\makeatletter") + _writeln(fh, r"\begin{pgfpicture}") + _writeln(fh, + r"\pgfpathrectangle{\pgfpointorigin}{\pgfqpoint{%fin}{%fin}}" + % (w, h)) + _writeln(fh, r"\pgfusepath{use as bounding box, clip}") renderer = MixedModeRenderer(self.figure, w, h, dpi, RendererPgf(self.figure, fh), bbox_inches_restore=bbox_inches_restore) self.figure.draw(renderer) # end the pgfpicture environment - writeln(fh, r"\end{pgfpicture}") - writeln(fh, r"\makeatother") - writeln(fh, r"\endgroup") + _writeln(fh, r"\end{pgfpicture}") + _writeln(fh, r"\makeatother") + _writeln(fh, r"\endgroup") def print_pgf(self, fname_or_fh, **kwargs): """ @@ -877,8 +888,7 @@ def print_pdf(self, fname_or_fh, *, metadata=None, **kwargs): r"\documentclass[12pt]{minimal}", r"\usepackage[papersize={%fin,%fin}, margin=0in]{geometry}" % (w, h), - get_preamble(), - get_fontspec(), + _get_preamble(), r"\usepackage{pgf}", r"\begin{document}", r"\centering", @@ -989,8 +999,7 @@ def _write_header(self, width_inches, height_inches): r"\documentclass[12pt]{minimal}", r"\usepackage[papersize={%fin,%fin}, margin=0in]{geometry}" % (width_inches, height_inches), - get_preamble(), - get_fontspec(), + _get_preamble(), r"\usepackage{pgf}", r"\setlength{\parindent}{0pt}", r"\begin{document}%", diff --git a/lib/matplotlib/tests/test_backend_pgf.py b/lib/matplotlib/tests/test_backend_pgf.py index 9b5b0b28ee3f..db094f1d0a8d 100644 --- a/lib/matplotlib/tests/test_backend_pgf.py +++ b/lib/matplotlib/tests/test_backend_pgf.py @@ -11,7 +11,7 @@ import matplotlib.pyplot as plt from matplotlib.testing import _has_tex_package, _check_for_pgf from matplotlib.testing.compare import compare_images, ImageComparisonFailure -from matplotlib.backends.backend_pgf import PdfPages, common_texification +from matplotlib.backends.backend_pgf import PdfPages, _tex_escape from matplotlib.testing.decorators import (_image_directories, check_figures_equal, image_comparison) @@ -73,8 +73,8 @@ def create_figure(): ('% not a comment', r'\% not a comment'), ('^not', r'\^not'), ]) -def test_common_texification(plain_text, escaped_text): - assert common_texification(plain_text) == escaped_text +def test_tex_escape(plain_text, escaped_text): + assert _tex_escape(plain_text) == escaped_text # test compiling a figure to pdf with xelatex