From 86983e4f42969962351fe13a44e2957621d8510d Mon Sep 17 00:00:00 2001 From: Colton Lathrop Date: Wed, 6 Nov 2019 12:54:26 -0600 Subject: [PATCH 1/8] Log a warning if selected font weight differs from requested --- .../next_api_changes/behavior/30272-ES.rst | 2 ++ lib/matplotlib/font_manager.py | 14 +++++++-- lib/matplotlib/font_manager.pyi | 10 +++---- lib/matplotlib/tests/test_font_manager.py | 29 ++++++++++++++++++- 4 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/30272-ES.rst 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/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/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 From 347dceb1dd2edfa395c553c866ff62e842dbb56a Mon Sep 17 00:00:00 2001 From: David Stansby Date: Mon, 14 Jul 2025 09:44:32 +0100 Subject: [PATCH 2/8] Only error if redirect is different --- doc/sphinxext/redirect_from.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/sphinxext/redirect_from.py b/doc/sphinxext/redirect_from.py index 37b56373a3bf..5402c9a83f55 100644 --- a/doc/sphinxext/redirect_from.py +++ b/doc/sphinxext/redirect_from.py @@ -94,7 +94,8 @@ 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]}") From ef45c7bf2b9f0a08b2d185b428ab5226918f0a10 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Mon, 14 Jul 2025 12:55:11 +0100 Subject: [PATCH 3/8] Note where redirect is trying to go --- doc/sphinxext/redirect_from.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/sphinxext/redirect_from.py b/doc/sphinxext/redirect_from.py index 5402c9a83f55..329352b3a3c8 100644 --- a/doc/sphinxext/redirect_from.py +++ b/doc/sphinxext/redirect_from.py @@ -94,11 +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 - and domain.redirects[redirected_reldoc] != current_doc): + 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 [] From 7d113022f862a441938a78b10cb60697bdceecf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 00:07:22 +0000 Subject: [PATCH 4/8] Bump the actions group with 2 updates Bumps the actions group with 2 updates: [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) and [cygwin/cygwin-install-action](https://github.com/cygwin/cygwin-install-action). Updates `pypa/cibuildwheel` from 3.0.0 to 3.0.1 - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/5f22145df44122af0f5a201f93cf0207171beca7...95d2f3a92fbf80abe066b09418bbf128a8923df2) Updates `cygwin/cygwin-install-action` from 5 to 6 - [Release notes](https://github.com/cygwin/cygwin-install-action/releases) - [Commits](https://github.com/cygwin/cygwin-install-action/compare/f61179d72284ceddc397ed07ddb444d82bf9e559...f2009323764960f80959895c7bc3bb30210afe4d) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-version: 3.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: cygwin/cygwin-install-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 10 +++++----- .github/workflows/cygwin.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) 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 From 6019c466a29475bde678d971f665c4abd588b006 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 15 Jul 2025 09:41:00 +0100 Subject: [PATCH 5/8] Replace deprecated imports --- lib/matplotlib/_api/__init__.pyi | 2 +- lib/matplotlib/axis.pyi | 2 +- lib/matplotlib/cbook.pyi | 2 +- lib/matplotlib/dviread.pyi | 2 +- lib/matplotlib/sankey.pyi | 2 +- lib/matplotlib/tests/test_api.py | 2 +- lib/matplotlib/typing.py | 3 ++- pyproject.toml | 1 + 8 files changed, 9 insertions(+), 7 deletions(-) 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/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/typing.py b/lib/matplotlib/typing.py index e3719235cdb8..c6e62cd472b5 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 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", From eef03343123c92c79ab3fdf3c1ed2e5e2f4878cc Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 15 Jul 2025 14:01:55 +0100 Subject: [PATCH 6/8] Fix link to pango --- doc/devel/MEP/MEP14.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b77ba11f535543e0ab7f859ea125f4e292c2fc8d Mon Sep 17 00:00:00 2001 From: jocelynvj Date: Tue, 15 Jul 2025 14:36:02 +0100 Subject: [PATCH 7/8] fix broken configobj link --- doc/users/prev_whats_new/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2e6533287b7a8ce374c618d96f0a4508e571702a Mon Sep 17 00:00:00 2001 From: ZPyrolink <73246085+ZPyrolink@users.noreply.github.com> Date: Tue, 15 Jul 2025 22:22:54 +0200 Subject: [PATCH 8/8] [TYP] Add more literals to MarkerType (#30261) * Update mpl.typing.MarkerType * Revert superfluous changes * Moved import * Remove unnecessary import Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> * Correct integers values on MarkerType --------- Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/typing.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index c6e62cd472b5..270da6bcd5e0 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -70,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`. """