diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index fececb0dfc40..15ec0e405400 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -141,7 +141,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.14 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -155,7 +155,7 @@ jobs: --upgrade --pre --only-binary=:all: contourpy numpy pillow - name: Build wheels for CPython 3.13 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -164,7 +164,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -172,7 +172,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -180,7 +180,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 4a5b79c0538e..a52343c5d22c 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -84,7 +84,7 @@ jobs: fetch-depth: 0 persist-credentials: false - - uses: cygwin/cygwin-install-action@f61179d72284ceddc397ed07ddb444d82bf9e559 # v5 + - uses: cygwin/cygwin-install-action@f2009323764960f80959895c7bc3bb30210afe4d # v6 with: packages: >- ccache gcc-g++ gdb git graphviz libcairo-devel libffi-devel diff --git a/doc/api/next_api_changes/behavior/30272-ES.rst b/doc/api/next_api_changes/behavior/30272-ES.rst new file mode 100644 index 000000000000..5a03f9bc7972 --- /dev/null +++ b/doc/api/next_api_changes/behavior/30272-ES.rst @@ -0,0 +1,2 @@ +``font_manager.findfont`` logs if selected font weight does not match requested +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/devel/MEP/MEP14.rst b/doc/devel/MEP/MEP14.rst index 2c696adf8a58..d79d3c2d3115 100644 --- a/doc/devel/MEP/MEP14.rst +++ b/doc/devel/MEP/MEP14.rst @@ -78,7 +78,7 @@ number of other projects: - `Microsoft DirectWrite`_ - `Apple Core Text`_ -.. _pango: https://pango.gnome.org +.. _pango: https://github.com/GNOME/pango .. _harfbuzz: https://github.com/harfbuzz/harfbuzz .. _QtTextLayout: https://doc.qt.io/archives/qt-4.8/qtextlayout.html .. _Microsoft DirectWrite: https://docs.microsoft.com/en-ca/windows/win32/directwrite/introducing-directwrite diff --git a/doc/sphinxext/redirect_from.py b/doc/sphinxext/redirect_from.py index 37b56373a3bf..329352b3a3c8 100644 --- a/doc/sphinxext/redirect_from.py +++ b/doc/sphinxext/redirect_from.py @@ -94,10 +94,15 @@ def run(self): domain = self.env.get_domain('redirect_from') current_doc = self.env.path2doc(self.state.document.current_source) redirected_reldoc, _ = self.env.relfn2path(redirected_doc, current_doc) - if redirected_reldoc in domain.redirects: + if ( + redirected_reldoc in domain.redirects + and domain.redirects[redirected_reldoc] != current_doc + ): raise ValueError( f"{redirected_reldoc} is already noted as redirecting to " - f"{domain.redirects[redirected_reldoc]}") + f"{domain.redirects[redirected_reldoc]}\n" + f"Cannot also redirect it to {current_doc}" + ) domain.redirects[redirected_reldoc] = current_doc return [] diff --git a/doc/users/prev_whats_new/changelog.rst b/doc/users/prev_whats_new/changelog.rst index 8f505e4fdd37..93fd4df2200a 100644 --- a/doc/users/prev_whats_new/changelog.rst +++ b/doc/users/prev_whats_new/changelog.rst @@ -1689,7 +1689,7 @@ recent changes, please refer to the :doc:`/users/release_notes`. required by the experimental traited config and are somewhat out of date. If needed, install them independently, see http://code.enthought.com/pages/traits.html and - http://www.voidspace.org.uk/python/configobj.html + https://configobj.readthedocs.io/en/latest/ 2008-12-12 Added support to assign labels to histograms of multiple data. - MM diff --git a/lib/matplotlib/_api/__init__.pyi b/lib/matplotlib/_api/__init__.pyi index 9bf67110bb54..c8ea814fc13d 100644 --- a/lib/matplotlib/_api/__init__.pyi +++ b/lib/matplotlib/_api/__init__.pyi @@ -1,6 +1,6 @@ from collections.abc import Callable, Generator, Iterable, Mapping, Sequence from typing import Any, TypeVar, overload -from typing_extensions import Self # < Py 3.11 +from typing import Self from numpy.typing import NDArray diff --git a/lib/matplotlib/axis.pyi b/lib/matplotlib/axis.pyi index 6119b946fd7b..4bcfb1e1cfb7 100644 --- a/lib/matplotlib/axis.pyi +++ b/lib/matplotlib/axis.pyi @@ -1,7 +1,7 @@ from collections.abc import Callable, Iterable, Sequence import datetime from typing import Any, Literal, overload -from typing_extensions import Self # < Py 3.11 +from typing import Self import numpy as np from numpy.typing import ArrayLike diff --git a/lib/matplotlib/cbook.pyi b/lib/matplotlib/cbook.pyi index 6c2d9c303eb2..ad14841463e8 100644 --- a/lib/matplotlib/cbook.pyi +++ b/lib/matplotlib/cbook.pyi @@ -14,10 +14,10 @@ from typing import ( Generic, IO, Literal, - Sequence, TypeVar, overload, ) +from collections.abc import Sequence _T = TypeVar("_T") diff --git a/lib/matplotlib/dviread.pyi b/lib/matplotlib/dviread.pyi index 12a9215b5308..82c0238d39d1 100644 --- a/lib/matplotlib/dviread.pyi +++ b/lib/matplotlib/dviread.pyi @@ -6,7 +6,7 @@ from enum import Enum from collections.abc import Generator from typing import NamedTuple -from typing_extensions import Self # < Py 3.11 +from typing import Self class _dvistate(Enum): pre = ... diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 2db98b75ab2e..ab6b495631de 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -35,7 +35,7 @@ from io import BytesIO import json import logging -from numbers import Number +from numbers import Integral import os from pathlib import Path import plistlib @@ -172,6 +172,10 @@ ] +def _normalize_weight(weight): + return weight if isinstance(weight, Integral) else weight_dict[weight] + + def get_fontext_synonyms(fontext): """ Return a list of file extensions that are synonyms for @@ -1256,8 +1260,8 @@ def score_weight(self, weight1, weight2): # exact match of the weight names, e.g. weight1 == weight2 == "regular" if cbook._str_equal(weight1, weight2): return 0.0 - w1 = weight1 if isinstance(weight1, Number) else weight_dict[weight1] - w2 = weight2 if isinstance(weight2, Number) else weight_dict[weight2] + w1 = _normalize_weight(weight1) + w2 = _normalize_weight(weight2) return 0.95 * (abs(w1 - w2) / 1000) + 0.05 def score_size(self, size1, size2): @@ -1480,6 +1484,10 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default, best_font = font if score == 0: break + if best_font is not None and (_normalize_weight(prop.get_weight()) != + _normalize_weight(best_font.weight)): + _log.warning('findfont: Failed to find font weight %s, now using %s.', + prop.get_weight(), best_font.weight) if best_font is None or best_score >= 10.0: if fallback_to_default: diff --git a/lib/matplotlib/font_manager.pyi b/lib/matplotlib/font_manager.pyi index c64ddea3e073..e865f67384cd 100644 --- a/lib/matplotlib/font_manager.pyi +++ b/lib/matplotlib/font_manager.pyi @@ -1,14 +1,13 @@ +from collections.abc import Iterable from dataclasses import dataclass +from numbers import Integral import os +from pathlib import Path +from typing import Any, Literal from matplotlib._afm import AFM from matplotlib import ft2font -from pathlib import Path - -from collections.abc import Iterable -from typing import Any, Literal - font_scalings: dict[str | None, float] stretch_dict: dict[str, int] weight_dict: dict[str, int] @@ -19,6 +18,7 @@ MSUserFontDirectories: list[str] X11FontDirectories: list[str] OSXFontDirectories: list[str] +def _normalize_weight(weight: str | Integral) -> Integral: ... def get_fontext_synonyms(fontext: str) -> list[str]: ... def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ... def win32FontDirectory() -> str: ... diff --git a/lib/matplotlib/sankey.pyi b/lib/matplotlib/sankey.pyi index 33565b998a9c..083d590559ca 100644 --- a/lib/matplotlib/sankey.pyi +++ b/lib/matplotlib/sankey.pyi @@ -2,7 +2,7 @@ from matplotlib.axes import Axes from collections.abc import Callable, Iterable from typing import Any -from typing_extensions import Self # < Py 3.11 +from typing import Self import numpy as np diff --git a/lib/matplotlib/tests/test_api.py b/lib/matplotlib/tests/test_api.py index f04604c14cce..58e7986bfce6 100644 --- a/lib/matplotlib/tests/test_api.py +++ b/lib/matplotlib/tests/test_api.py @@ -13,7 +13,7 @@ if typing.TYPE_CHECKING: - from typing_extensions import Self + from typing import Self T = TypeVar('T') diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 97ee8672b1d4..24421b8e30b3 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -15,7 +15,8 @@ from matplotlib.font_manager import ( findfont, findSystemFonts, FontEntry, FontProperties, fontManager, json_dump, json_load, get_font, is_opentype_cff_font, - MSUserFontDirectories, _get_fontconfig_fonts, ttfFontProperty) + MSUserFontDirectories, ttfFontProperty, + _get_fontconfig_fonts, _normalize_weight) from matplotlib import cbook, ft2font, pyplot as plt, rc_context, figure as mfigure from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing @@ -407,3 +408,29 @@ def test_fontproperties_init_deprecation(): # Since this case is not covered by docs, I've refrained from jumping # extra hoops to detect this possible API misuse. FontProperties(family="serif-24:style=oblique:weight=bold") + + +def test_normalize_weights(): + assert _normalize_weight(300) == 300 # passthrough + assert _normalize_weight('ultralight') == 100 + assert _normalize_weight('light') == 200 + assert _normalize_weight('normal') == 400 + assert _normalize_weight('regular') == 400 + assert _normalize_weight('book') == 400 + assert _normalize_weight('medium') == 500 + assert _normalize_weight('roman') == 500 + assert _normalize_weight('semibold') == 600 + assert _normalize_weight('demibold') == 600 + assert _normalize_weight('demi') == 600 + assert _normalize_weight('bold') == 700 + assert _normalize_weight('heavy') == 800 + assert _normalize_weight('extra bold') == 800 + assert _normalize_weight('black') == 900 + with pytest.raises(KeyError): + _normalize_weight('invalid') + + +def test_font_match_warning(caplog): + findfont(FontProperties(family=["DejaVu Sans"], weight=750)) + logs = [rec.message for rec in caplog.records] + assert 'findfont: Failed to find font weight 750, now using 700.' in logs diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index e3719235cdb8..270da6bcd5e0 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -12,7 +12,8 @@ """ from collections.abc import Hashable, Sequence import pathlib -from typing import Any, Callable, Literal, TypeAlias, TypeVar, Union +from typing import Any, Literal, TypeAlias, TypeVar, Union +from collections.abc import Callable from . import path from ._enums import JoinStyle, CapStyle @@ -69,7 +70,16 @@ ) """See :doc:`/gallery/lines_bars_and_markers/markevery_demo`.""" -MarkerType: TypeAlias = str | path.Path | MarkerStyle +MarkerType: TypeAlias = ( + path.Path | MarkerStyle | str | # str required for "$...$" marker + Literal[ + ".", ",", "o", "v", "^", "<", ">", + "1", "2", "3", "4", "8", "s", "p", + "P", "*", "h", "H", "+", "x", "X", + "D", "d", "|", "_", "none", " ", + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + ] | list[tuple[int, int]] | tuple[int, Literal[0, 1, 2], int] +) """ Marker specification. See :doc:`/gallery/lines_bars_and_markers/marker_reference`. """ diff --git a/pyproject.toml b/pyproject.toml index b580feff930e..b06a5bcc5740 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,6 +140,7 @@ select = [ "E", "F", "W", + "UP035", # The following error codes require the preview mode to be enabled. "E201", "E202",