diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 872550018f3f..cd96fb92c084 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -45,7 +45,7 @@ import matplotlib as mpl from matplotlib import ( _api, backend_tools as tools, cbook, colors, docstring, textpath, - tight_bbox, transforms, widgets, get_backend, is_interactive, rcParams) + transforms, widgets, get_backend, is_interactive, rcParams) from matplotlib._pylab_helpers import Gcf from matplotlib.backend_managers import ToolManager from matplotlib.cbook import _setattr_cm @@ -2273,11 +2273,7 @@ def print_figure( if not cbook._str_equal(color, "auto"): stack.enter_context(self.figure._cm_set(**{prop: color})) - if bbox_inches is None: - bbox_inches = rcParams['savefig.bbox'] - - if (self.figure.get_constrained_layout() or - bbox_inches == "tight"): + if self.figure.get_constrained_layout(): # we need to trigger a draw before printing to make sure # CL works. "tight" also needs a draw to get the right # locations: @@ -2289,22 +2285,6 @@ def print_figure( with getattr(renderer, "_draw_disabled", nullcontext)(): self.figure.draw(renderer) - if bbox_inches: - if bbox_inches == "tight": - bbox_inches = self.figure.get_tightbbox( - renderer, bbox_extra_artists=bbox_extra_artists) - if pad_inches is None: - pad_inches = rcParams['savefig.pad_inches'] - bbox_inches = bbox_inches.padded(pad_inches) - - # call adjust_bbox to save only the given area - restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches, - canvas.fixed_dpi) - - _bbox_inches_restore = (bbox_inches, restore_bbox) - else: - _bbox_inches_restore = None - # we have already done CL above, so turn it off: stack.enter_context(self.figure._cm_set(constrained_layout=False)) try: @@ -2316,12 +2296,19 @@ def print_figure( facecolor=facecolor, edgecolor=edgecolor, orientation=orientation, - bbox_inches_restore=_bbox_inches_restore, **kwargs) + if bbox_inches is None: + bbox_inches = rcParams["savefig.bbox"] + if bbox_inches == "tight": + bbox_inches = self.figure.get_tightbbox( + self.figure._cachedRenderer, + bbox_extra_artists=bbox_extra_artists) + if pad_inches is None: + pad_inches = rcParams["savefig.pad_inches"] + bbox_inches = bbox_inches.padded(pad_inches) + if bbox_inches: + canvas.adjust_bbox(filename, bbox_inches) finally: - if bbox_inches and restore_bbox: - restore_bbox() - self.figure.set_canvas(self) return result diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 74f1d911b561..ad34e8e7e4c9 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -562,6 +562,13 @@ def print_tif(self, filename_or_obj, *, pil_kwargs=None): print_tiff = print_tif + def adjust_bbox(self, filename, bbox_inches): + bbox = self.figure.dpi_scale_trans.transform_bbox(bbox_inches) + h = self.figure.bbox.height + img = Image.open(filename) + img = img.crop((bbox.x0, h - bbox.y1, bbox.x1, h - bbox.y0)) + img.save(filename, format=img.format) # TODO: also copy metadata + @_Backend.export class _BackendAgg(_Backend): diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 2eb6f090db26..bdf0974e8536 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2719,6 +2719,32 @@ def draw(self): self.figure.draw_no_output() return super().draw() + def adjust_bbox(self, filename, bbox_inches): + bbox = self.figure.dpi_scale_trans.transform_bbox(bbox_inches) + # What about PdfPages? + with open(filename, "a+b") as file: + file.seek(0) + buf = file.read() + page_pos = file.tell() + pageid = self.figure._cachedRenderer.file.pageList[-1].id + page_match = re.search( + rb"(?s)%d 0 obj\n.*?/MediaBox.*?\nendobj\n" % pageid, buf) + file.write(re.sub(br"/MediaBox \[[^]]*\]", b"/MediaBox %s" + % pdfRepr(bbox.extents.tolist()), page_match[0])) + startxref_pos = file.tell() + file.write(b"xref\n") + file.write(b"0 1\n0000000000 65535 f \n") + file.write(b"%d 1\n%010d 00000 n \n" % (pageid, page_pos)) + file.write(b"trailer\n") + trailer_match = re.search( + rb"(?s)trailer\n<< (.*) >>\nstartxref\n(\d+)\n%%EOF", + buf[buf.rfind(b"trailer\n"):]) + file.write( + b"<< %s /Prev %s >>\n" % (trailer_match[1], trailer_match[2])) + file.write(b"startxref\n") + file.write(b"%d\n" % startxref_pos) + file.write(b"%%EOF\n") + FigureManagerPdf = FigureManagerBase diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index a1a11061b978..cbd156f7fdea 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -6,6 +6,7 @@ import itertools import logging import os +import pathlib import re import uuid @@ -1345,6 +1346,17 @@ def draw(self): self.figure.draw_no_output() return super().draw() + def adjust_bbox(self, filename, bbox_inches): + bbox = self.figure.dpi_scale_trans.transform_bbox(bbox_inches) + buf = pathlib.Path(filename).read_text() + w, h, x, y = map(short_float_fmt, [ + bbox.width, bbox.height, + bbox.x0, self.figure.bbox.height - bbox.y1]) + buf = re.sub('width="[^"]*"', f'width="{w}pt"', buf, 1) + buf = re.sub('height="[^"]*"', f'height="{h}pt"', buf, 1) + buf = re.sub('viewBox="[^"]*"', f'viewBox="{x} {y} {w} {h}"', buf, 1) + pathlib.Path(filename).write_text(buf) + FigureManagerSVG = FigureManagerBase