diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 8a01d76c00f5..39fcedcd3862 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -182,7 +182,7 @@ jobs: export PATH="/usr/local/bin:$PATH" python -m pip install --no-build-isolation 'contourpy>=1.0.1' python -m pip install --upgrade cycler fonttools \ - packaging pyparsing python-dateutil setuptools-scm \ + packaging pyparsing python-dateutil 'setuptools-scm<10' \ -r requirements_test.txt sphinx ipython python -m pip install --upgrade pycairo 'cairocffi>=0.8' PyGObject && python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' && diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6a794bfe3f6b..86c90c77b21a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -256,7 +256,7 @@ jobs: # Preinstall build requirements to enable no-build-isolation builds. python -m pip install --upgrade $PRE \ 'contourpy>=1.0.1' cycler fonttools kiwisolver importlib_resources \ - packaging pillow 'pyparsing!=3.1.0' python-dateutil setuptools-scm \ + packaging pillow 'pyparsing!=3.1.0' python-dateutil 'setuptools-scm<10' \ 'meson-python>=0.13.1' 'pybind11>=2.13.2' \ -r requirements/testing/all.txt \ ${{ matrix.extra-requirements }} diff --git a/lib/matplotlib/_afm.py b/lib/matplotlib/_afm.py index 3d7f7a44baca..af607b0374fc 100644 --- a/lib/matplotlib/_afm.py +++ b/lib/matplotlib/_afm.py @@ -478,10 +478,18 @@ def get_angle(self) -> float: """Return the fontangle as float.""" return self._header['ItalicAngle'] + def get_ascender(self) -> float: + """Return the ascent as float.""" + return self._header['Ascender'] + def get_capheight(self) -> float: """Return the cap height as float.""" return self._header['CapHeight'] + def get_descender(self) -> float: + """Return the descent as float.""" + return self._header['Descender'] + def get_xheight(self) -> float: """Return the xheight as float.""" return self._header['XHeight'] diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 83a8566517a7..a06779b8efee 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -348,6 +348,32 @@ def get_canvas_width_height(self): # docstring inherited return self.width * 72.0, self.height * 72.0 + def _get_font_height_metrics(self, prop): + """ + Return the ascent, descent, and line gap for font described by *prop*. + + TODO: This is a temporary method until we design a proper API for the backends. + + Parameters + ---------- + prop : `.font_manager.FontProperties` + The properties describing the font to measure. + + Returns + ------- + ascent, descent, line_gap : float or None + The ascent, descent and line gap of the determined font, or None to fall + back to normal measurements. + """ + if not mpl.rcParams[self._use_afm_rc_name]: + return None, None, None + font = self._get_font_afm(prop) + scale = prop.get_size_in_points() / 1000 + a = font.get_ascender() * scale + d = -font.get_descender() * scale + g = (a + d) * 0.2 # Preserve previous line spacing of 1.2. + return a, d, g + def get_text_width_height_descent(self, s, prop, ismath): # docstring inherited if ismath == "TeX": diff --git a/lib/matplotlib/tests/baseline_images/test_backend_pdf/pdf_use14corefonts.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/pdf_use14corefonts.pdf index 5cdc2e34e25d..b7dbb9adec70 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pdf/pdf_use14corefonts.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/pdf_use14corefonts.pdf differ diff --git a/lib/matplotlib/tests/test_bbox_tight.py b/lib/matplotlib/tests/test_bbox_tight.py index 99b56bee91ba..677cdf37dd24 100644 --- a/lib/matplotlib/tests/test_bbox_tight.py +++ b/lib/matplotlib/tests/test_bbox_tight.py @@ -47,7 +47,7 @@ def test_bbox_inches_tight(text_placeholders): @image_comparison(['bbox_inches_tight_suptile_legend'], savefig_kwarg={'bbox_inches': 'tight'}, - tol=0 if platform.machine() == 'x86_64' else 0.022) + tol=0 if platform.machine() == 'x86_64' else 0.024) def test_bbox_inches_tight_suptile_legend(): plt.plot(np.arange(10), label='a straight line') plt.legend(bbox_to_anchor=(0.9, 1), loc='upper left') diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index c85cbc5e21a8..33fb8918d22a 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -224,8 +224,11 @@ def baseline_images(request, fontset, index, text): @pytest.mark.parametrize( 'fontset', ['cm', 'stix', 'stixsans', 'dejavusans', 'dejavuserif']) @pytest.mark.parametrize('baseline_images', ['mathtext'], indirect=True) -@image_comparison(baseline_images=None, style='mpl20', - tol=0.011 if platform.machine() in ('ppc64le', 's390x') else 0) +@image_comparison( + baseline_images=None, style='mpl20', + tol=(0.013 + if platform.machine() in ('ppc64le', 's390x') or platform.system() == 'Windows' + else 0)) def test_mathtext_rendering(baseline_images, fontset, index, text): mpl.rcParams['mathtext.fontset'] = fontset fig = plt.figure(figsize=(5.25, 0.75)) diff --git a/lib/matplotlib/tests/test_patheffects.py b/lib/matplotlib/tests/test_patheffects.py index d957ef2a5510..0b99a954afb3 100644 --- a/lib/matplotlib/tests/test_patheffects.py +++ b/lib/matplotlib/tests/test_patheffects.py @@ -120,7 +120,7 @@ def test_SimplePatchShadow_offset(): assert pe._offset == (4, 5) -@image_comparison(['collection'], tol=0.03, style='mpl20') +@image_comparison(['collection'], tol=0.032, style='mpl20') def test_collection(): x, y = np.meshgrid(np.linspace(0, 10, 150), np.linspace(-5, 5, 100)) data = np.sin(x) + np.cos(y) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index f6ea6673ff0f..9c6478f9c7df 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -437,22 +437,29 @@ def _get_layout(self, renderer): dpi = self.get_figure(root=True).dpi # Determine full vertical extent of font, including ascenders and descenders: if not self.get_usetex(): - font = get_font(fontManager._find_fonts_by_props(self._fontproperties)) - possible_metrics = [ - ('OS/2', 'sTypoLineGap', 'sTypoAscender', 'sTypoDescender'), - ('hhea', 'lineGap', 'ascent', 'descent') - ] - for table_name, linegap_key, ascent_key, descent_key in possible_metrics: - table = font.get_sfnt_table(table_name) - if table is None: - continue - # Rescale to font size/DPI if the metrics were available. - fontsize = self._fontproperties.get_size_in_points() - units_per_em = font.get_sfnt_table('head')['unitsPerEm'] - line_gap = table[linegap_key] / units_per_em * fontsize * dpi / 72 - min_ascent = table[ascent_key] / units_per_em * fontsize * dpi / 72 - min_descent = -table[descent_key] / units_per_em * fontsize * dpi / 72 - break + if hasattr(renderer, '_get_font_height_metrics'): + # TODO: This is a temporary internal method call (for _backend_pdf_ps to + # support AFM files) until we design a proper API for the backends. + min_ascent, min_descent, line_gap = renderer._get_font_height_metrics( + self._fontproperties) + if min_ascent is None: + font = get_font(fontManager._find_fonts_by_props(self._fontproperties)) + possible = [ + ('OS/2', 'sTypoLineGap', 'sTypoAscender', 'sTypoDescender'), + ('hhea', 'lineGap', 'ascent', 'descent') + ] + for table_name, linegap_key, ascent_key, descent_key in possible: + table = font.get_sfnt_table(table_name) + if table is None: + continue + # Rescale to font size/DPI if the metrics were available. + fontsize = self._fontproperties.get_size_in_points() + units_per_em = font.get_sfnt_table('head')['unitsPerEm'] + scale = 1 / units_per_em * fontsize * dpi / 72 + line_gap = table[linegap_key] * scale + min_ascent = table[ascent_key] * scale + min_descent = -table[descent_key] * scale + break if None in (min_ascent, min_descent): # Fallback to font measurement. _, h, min_descent = _get_text_metrics_with_cache( diff --git a/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py b/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py index 6672bd0ac3a0..5575a48d499a 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py +++ b/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py @@ -63,7 +63,7 @@ def test_curvelinear3(): l.set_clip_path(ax1.patch) -@image_comparison(['curvelinear4.png'], style='mpl20') +@image_comparison(['curvelinear4.png'], style='mpl20', tol=0.04) def test_curvelinear4(): fig = plt.figure(figsize=(5, 5)) diff --git a/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py index f49d02766421..62feaee4279a 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py @@ -78,7 +78,7 @@ def inverted(self): ax1.grid(True) -@image_comparison(['polar_box.png'], style='mpl20') +@image_comparison(['polar_box.png'], style='mpl20', tol=0.04) def test_polar_box(): plt.rcParams.update({"xtick.direction": "inout", "ytick.direction": "out"}) fig = plt.figure(figsize=(5, 5)) @@ -138,7 +138,7 @@ def test_polar_box(): ax1.grid(True) -@image_comparison(['axis_direction.png'], style='mpl20') +@image_comparison(['axis_direction.png'], style='mpl20', tol=0.04) def test_axis_direction(): fig = plt.figure(figsize=(5, 5)) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 8d2441393dde..1cff9fdbe76e 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2878,7 +2878,7 @@ def _make_triangulation_data(): @mpl3d_image_comparison(['scale3d_artists_log.png'], style='mpl20', - remove_text=False, tol=0.03) + remove_text=False, tol=0.032) def test_scale3d_artists_log(): """Test all 3D artist types with log scale.""" fig = plt.figure(figsize=(16, 12)) diff --git a/pyproject.toml b/pyproject.toml index 7fd25147eb05..2ede26597a31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ requires-python = ">=3.11" dev = [ "meson-python>=0.13.2,!=0.17.*", "pybind11>=2.13.2,!=2.13.3", - "setuptools_scm>=7", + "setuptools_scm>=7,<10", # Not required by us but setuptools_scm without a version, cso _if_ # installed, then setuptools_scm 8 requires at least this version. # Unfortunately, we can't do a sort of minimum-if-installed dependency, so @@ -75,7 +75,7 @@ requires = [ # you really need it and aren't using an sdist. "meson-python>=0.13.2,!=0.17.*", "pybind11>=2.13.2,!=2.13.3", - "setuptools_scm>=7", + "setuptools_scm>=7,<10", ] [tool.meson-python.args] diff --git a/requirements/dev/build-requirements.txt b/requirements/dev/build-requirements.txt index 4d2a098c3c4f..372a7d669fb1 100644 --- a/requirements/dev/build-requirements.txt +++ b/requirements/dev/build-requirements.txt @@ -1,3 +1,3 @@ pybind11>=2.13.2,!=2.13.3 meson-python -setuptools-scm +setuptools-scm<10 diff --git a/requirements/testing/mypy.txt b/requirements/testing/mypy.txt index 343517263f40..451a096f6a96 100644 --- a/requirements/testing/mypy.txt +++ b/requirements/testing/mypy.txt @@ -22,5 +22,5 @@ packaging>=20.0 pillow>=9 pyparsing>=3 python-dateutil>=2.7 -setuptools_scm>=7 +setuptools_scm>=7,<10 setuptools>=64