From a3c14cf407684a4a286df24bacda45d9af2e9af1 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 29 Mar 2025 16:21:16 +0100 Subject: [PATCH 01/12] Rework mapping of dvi glyph indices to freetype indices. In 89a7e19, an API for converting "dvi glyph indices" (as stored in a dvi file) to FreeType-compatible keys (either "indices into the native charmap" or "glyph names") was introduced. It was intended that end users (i.e., backends) would check the type of `text.glyph_name_or_index` ((A) int or (B) str) and load the glyph accordingly ((A) `FT_Set_Charmap(native_cmap); FT_Load_Char(index);` or (B) `FT_Load_Glyph(FT_Get_Name_Index(name));`); however, with the future introduction of {xe,lua}tex support, this kind of type checking becomes inconvenient, because {xe,lua}tex's "dvi glyph indices", which are directly equal to FreeType glyph indices (i.e. they would be loaded with `FT_Load_Glyph(index);`), would normally also be converted to ints. This PR introduces a new API (`Text.index`) that performs this mapping (via the new `DviFont._index_dvi_to_freetype`), always mapping to FreeType glyph indices (i.e. one can always just call `FT_Load_Glyph` on the result). To do so, in case (A) it loads itself the native charmap (something the end user needed to do by themselves previously) and performs the cmap-to-index conversion (`FT_Get_Char_Index`) previously implicit in `FT_Load_Char`; in case (B) it performs itself the name-to-index conversion (`FT_Get_Name_Index`). When {xe,lua}tex support is introduced in the future, `_index_dvi_to_freetype` will just return the index as is. The old APIs are not deprecated yet, as other changes will occur for {xe,lua}tex support (e.g. font_effects will go away and be replaced by `.font.effects`, as {xe,lua}tex don't store that info in the pdftexmap entry), and grouping all API changes together seems nicer (to only require a single adaptation step by the API consumer). --- lib/matplotlib/dviread.py | 54 +++++++++++++++++++++++++++++--------- lib/matplotlib/dviread.pyi | 2 ++ lib/matplotlib/textpath.py | 12 +-------- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index c1d1a93f55bf..a588979f5fad 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -31,7 +31,7 @@ import numpy as np -from matplotlib import _api, cbook +from matplotlib import _api, cbook, font_manager _log = logging.getLogger(__name__) @@ -71,8 +71,8 @@ class Text(namedtuple('Text', 'x y font glyph width')): *glyph*, and *width* attributes are kept public for back-compatibility, but users wanting to draw the glyph themselves are encouraged to instead load the font specified by `font_path` at `font_size`, warp it with the - effects specified by `font_effects`, and load the glyph specified by - `glyph_name_or_index`. + effects specified by `font_effects`, and load the glyph at the FreeType + glyph `index`. """ def _get_pdftexmap_entry(self): @@ -105,6 +105,14 @@ def font_effects(self): return self._get_pdftexmap_entry().effects @property + def index(self): + """ + The FreeType index of this glyph (that can be passed to FT_Load_Glyph). + """ + # See DviFont._index_dvi_to_freetype for details on the index mapping. + return self.font._index_dvi_to_freetype(self.glyph) + + @property # To be deprecated together with font_size, font_effects. def glyph_name_or_index(self): """ Either the glyph name or the native charmap glyph index. @@ -579,7 +587,7 @@ class DviFont: Size of the font in Adobe points, converted from the slightly smaller TeX points. """ - __slots__ = ('texname', 'size', '_scale', '_vf', '_tfm') + __slots__ = ('texname', 'size', '_scale', '_vf', '_tfm', '_encoding') def __init__(self, scale, tfm, texname, vf): _api.check_isinstance(bytes, texname=texname) @@ -588,6 +596,7 @@ def __init__(self, scale, tfm, texname, vf): self.texname = texname self._vf = vf self.size = scale * (72.0 / (72.27 * 2**16)) + self._encoding = None widths = _api.deprecated("3.11")(property(lambda self: [ (1000 * self._tfm.width.get(char, 0)) >> 20 @@ -630,6 +639,33 @@ def _height_depth_of(self, char): hd[-1] = 0 return hd + def _index_dvi_to_freetype(self, idx): + """Convert dvi glyph indices to FreeType ones.""" + # Glyphs indices stored in the dvi file map to FreeType glyph indices + # (i.e., which can be passed to FT_Load_Glyph) in various ways: + # - if pdftex.map specifies an ".enc" file for the font, that file maps + # dvi indices to Adobe glyph names, which can then be converted to + # FreeType glyph indices with FT_Get_Name_Index. + # - if no ".enc" file is specified, then the font must be a Type 1 + # font, and dvi indices directly index into the font's CharStrings + # vector. + # - (xetex & luatex, currently unsupported, can also declare "native + # fonts", for which dvi indices are equal to FreeType indices.) + if self._encoding is None: + psfont = PsfontsMap(find_tex_file("pdftex.map"))[self.texname] + if psfont.filename is None: + raise ValueError("No usable font file found for {} ({}); " + "the font may lack a Type-1 version" + .format(psfont.psname.decode("ascii"), + psfont.texname.decode("ascii"))) + face = font_manager.get_font(psfont.filename) + if psfont.encoding: + self._encoding = [face.get_name_index(name) + for name in _parse_enc(psfont.encoding)] + else: + self._encoding = face._get_type1_encoding_vector() + return self._encoding[idx] + class Vf(Dvi): r""" @@ -1023,8 +1059,7 @@ def _parse_enc(path): Returns ------- list - The nth entry of the list is the PostScript glyph name of the nth - glyph. + The nth list item is the PostScript glyph name of the nth glyph. """ no_comments = re.sub("%.*", "", Path(path).read_text(encoding="ascii")) array = re.search(r"(?s)\[(.*)\]", no_comments).group(1) @@ -1156,12 +1191,7 @@ def _print_fields(*args): face = FT2Font(fontpath) _print_fields("x", "y", "glyph", "chr", "w") for text in group: - if psfont.encoding: - glyph_name = _parse_enc(psfont.encoding)[text.glyph] - else: - encoding_vector = face._get_type1_encoding_vector() - glyph_name = face.get_glyph_name(encoding_vector[text.glyph]) - glyph_str = fontTools.agl.toUnicode(glyph_name) + glyph_str = fontTools.agl.toUnicode(face.get_glyph_name(text.index)) _print_fields(text.x, text.y, text.glyph, glyph_str, text.width) if page.boxes: print("--- BOXES ---") diff --git a/lib/matplotlib/dviread.pyi b/lib/matplotlib/dviread.pyi index f8d8f979fd8c..41799c083218 100644 --- a/lib/matplotlib/dviread.pyi +++ b/lib/matplotlib/dviread.pyi @@ -41,6 +41,8 @@ class Text(NamedTuple): @property def font_effects(self) -> dict[str, float]: ... @property + def index(self) -> int: ... # type: ignore[override] + @property def glyph_name_or_index(self) -> int | str: ... class Dvi: diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index 35adfdd77899..b57597ded363 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -239,17 +239,7 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, if char_id not in glyph_map: font.clear() font.set_size(self.FONT_SCALE, self.DPI) - glyph_name_or_index = text.glyph_name_or_index - if isinstance(glyph_name_or_index, str): - index = font.get_name_index(glyph_name_or_index) - elif isinstance(glyph_name_or_index, int): - if font not in t1_encodings: - t1_encodings[font] = font._get_type1_encoding_vector() - index = t1_encodings[font][glyph_name_or_index] - else: # Should not occur. - raise TypeError(f"Glyph spec of unexpected type: " - f"{glyph_name_or_index!r}") - font.load_glyph(index, flags=LoadFlags.TARGET_LIGHT) + font.load_glyph(text.index, flags=LoadFlags.TARGET_LIGHT) glyph_map_new[char_id] = font.get_path() glyph_ids.append(char_id) From 4e75193f315d9885371b227a1d2a6af0ed8a3c5f Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 6 May 2025 09:43:44 +0100 Subject: [PATCH 02/12] Expire deprecation of nth_coord arguments --- doc/api/next_api_changes/removals/30015-DS.rst | 9 +++++++++ lib/mpl_toolkits/axisartist/axislines.py | 15 ++++----------- .../axisartist/grid_helper_curvelinear.py | 4 ++-- 3 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 doc/api/next_api_changes/removals/30015-DS.rst diff --git a/doc/api/next_api_changes/removals/30015-DS.rst b/doc/api/next_api_changes/removals/30015-DS.rst new file mode 100644 index 000000000000..e5f17518a9f3 --- /dev/null +++ b/doc/api/next_api_changes/removals/30015-DS.rst @@ -0,0 +1,9 @@ +*nth_coord* parameter to axisartist helpers for fixed axis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Helper APIs in `.axisartist` for generating a "fixed" axis on rectilinear axes +(`.FixedAxisArtistHelperRectilinear`) no longer take a *nth_coord* parameter. +That parameter is entirely inferred from the (required) *loc* parameter. + +For curvilinear axes, the *nth_coord* parameter remains supported (it affects +the *ticks*, not the axis position itself), but it is now keyword-only. diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index c0379f11b8d4..c921ea597cb4 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -120,8 +120,7 @@ def _to_xy(self, values, const): class _FixedAxisArtistHelperBase(_AxisArtistHelperBase): """Helper class for a fixed (in the axes coordinate) axis.""" - @_api.delete_parameter("3.9", "nth_coord") - def __init__(self, loc, nth_coord=None): + def __init__(self, loc): """``nth_coord = 0``: x-axis; ``nth_coord = 1``: y-axis.""" super().__init__(_api.check_getitem( {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc)) @@ -171,12 +170,7 @@ def get_line(self, axes): class FixedAxisArtistHelperRectilinear(_FixedAxisArtistHelperBase): - @_api.delete_parameter("3.9", "nth_coord") - def __init__(self, axes, loc, nth_coord=None): - """ - nth_coord = along which coordinate value varies - in 2D, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis - """ + def __init__(self, axes, loc): super().__init__(loc) self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] @@ -311,10 +305,9 @@ def __init__(self, axes): super().__init__() self.axes = axes - @_api.delete_parameter( - "3.9", "nth_coord", addendum="'nth_coord' is now inferred from 'loc'.") def new_fixed_axis( - self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): + self, loc, *, axis_direction=None, offset=None, axes=None + ): if axes is None: _api.warn_external( "'new_fixed_axis' explicitly requires the axes keyword.") diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index 02c96d58b7f7..5084a54f57d6 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -316,9 +316,9 @@ def update_grid_finder(self, aux_trans=None, **kwargs): self.grid_finder.update(**kwargs) self._old_limits = None # Force revalidation. - @_api.make_keyword_only("3.9", "nth_coord") def new_fixed_axis( - self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): + self, loc, *, axis_direction=None, offset=None, axes=None, nth_coord=None + ): if axes is None: axes = self.axes if axis_direction is None: From 96ac88fbdc302efe9bf2d1e8fef0289ecc872407 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 6 May 2025 09:45:14 +0100 Subject: [PATCH 03/12] Remove deprecated get_tick_iterator() --- doc/api/next_api_changes/removals/30014-DS.rst | 4 ++++ .../axisartist/grid_helper_curvelinear.py | 11 ----------- 2 files changed, 4 insertions(+), 11 deletions(-) create mode 100644 doc/api/next_api_changes/removals/30014-DS.rst diff --git a/doc/api/next_api_changes/removals/30014-DS.rst b/doc/api/next_api_changes/removals/30014-DS.rst new file mode 100644 index 000000000000..d13737f17e40 --- /dev/null +++ b/doc/api/next_api_changes/removals/30014-DS.rst @@ -0,0 +1,4 @@ +``GridHelperCurveLinear.get_tick_iterator`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... is removed with no replacement. diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index 02c96d58b7f7..135677eb7c21 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -351,14 +351,3 @@ def get_gridlines(self, which="major", axis="both"): if axis in ["both", "y"]: grid_lines.extend([gl.T for gl in self._grid_info["lat"]["lines"]]) return grid_lines - - @_api.deprecated("3.9") - def get_tick_iterator(self, nth_coord, axis_side, minor=False): - angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side] - lon_or_lat = ["lon", "lat"][nth_coord] - if not minor: # major ticks - for tick in self._grid_info[lon_or_lat]["ticks"][axis_side]: - yield *tick["loc"], angle_tangent, tick["label"] - else: - for tick in self._grid_info[lon_or_lat]["ticks"][axis_side]: - yield *tick["loc"], angle_tangent, "" From b4d532f52d83d84cb3232765f921e346b4f81844 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 7 May 2025 02:47:46 -0400 Subject: [PATCH 04/12] Remove unused `_api` import The combination of #30014 and #30015 made this import redundant, but as each PR was indepenently tested and reviewed, nothing was evident until both were merged. --- lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index fa5636a98f7c..1e27b3f571f3 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -7,7 +7,6 @@ import numpy as np import matplotlib as mpl -from matplotlib import _api from matplotlib.path import Path from matplotlib.transforms import Affine2D, Bbox, IdentityTransform from .axislines import ( From c004e49f66531b1d6674cdd3d851cb984006002c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 13 Feb 2025 10:21:37 +0100 Subject: [PATCH 05/12] Micro-optimize _to_rgba_no_colorcycle. This patch speeds up conversions of `#rgba`-type formats by between 25% and 40% (while shortening the implementation), although real benefits should be limited because of caching in to_rgba. --- lib/matplotlib/colors.py | 49 ++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index e3c3b39e8bb2..9bd808074c1f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -372,40 +372,31 @@ def _to_rgba_no_colorcycle(c, alpha=None): # This may turn c into a non-string, so we check again below. c = _colors_full_map[c] except KeyError: - if len(orig_c) != 1: + if len(c) != 1: try: c = _colors_full_map[c.lower()] except KeyError: pass if isinstance(c, str): - # hex color in #rrggbb format. - match = re.match(r"\A#[a-fA-F0-9]{6}\Z", c) - if match: - return (tuple(int(n, 16) / 255 - for n in [c[1:3], c[3:5], c[5:7]]) - + (alpha if alpha is not None else 1.,)) - # hex color in #rgb format, shorthand for #rrggbb. - match = re.match(r"\A#[a-fA-F0-9]{3}\Z", c) - if match: - return (tuple(int(n, 16) / 255 - for n in [c[1]*2, c[2]*2, c[3]*2]) - + (alpha if alpha is not None else 1.,)) - # hex color with alpha in #rrggbbaa format. - match = re.match(r"\A#[a-fA-F0-9]{8}\Z", c) - if match: - color = [int(n, 16) / 255 - for n in [c[1:3], c[3:5], c[5:7], c[7:9]]] - if alpha is not None: - color[-1] = alpha - return tuple(color) - # hex color with alpha in #rgba format, shorthand for #rrggbbaa. - match = re.match(r"\A#[a-fA-F0-9]{4}\Z", c) - if match: - color = [int(n, 16) / 255 - for n in [c[1]*2, c[2]*2, c[3]*2, c[4]*2]] - if alpha is not None: - color[-1] = alpha - return tuple(color) + if re.fullmatch("#[a-fA-F0-9]+", c): + if len(c) == 7: # #rrggbb hex format. + return (*[n / 0xff for n in bytes.fromhex(c[1:])], + alpha if alpha is not None else 1.) + elif len(c) == 4: # #rgb hex format, shorthand for #rrggbb. + return (*[int(n, 16) / 0xf for n in c[1:]], + alpha if alpha is not None else 1.) + elif len(c) == 9: # #rrggbbaa hex format. + color = [n / 0xff for n in bytes.fromhex(c[1:])] + if alpha is not None: + color[-1] = alpha + return tuple(color) + elif len(c) == 5: # #rgba hex format, shorthand for #rrggbbaa. + color = [int(n, 16) / 0xf for n in c[1:]] + if alpha is not None: + color[-1] = alpha + return tuple(color) + else: + raise ValueError(f"Invalid hex color specifier: {orig_c!r}") # string gray. try: c = float(c) From a93e68121ded69104fe19410c49ee7d7fe7f76f3 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 8 May 2025 13:38:08 +0200 Subject: [PATCH 06/12] Make PdfFile font-related attributes private. They are clearly intended for internal use, and we may want to change their internal representation in the future to support xetex/luatex (which expose more font types). --- .../deprecations/30027-AL.rst | 3 ++ lib/matplotlib/backends/backend_pdf.py | 35 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/30027-AL.rst diff --git a/doc/api/next_api_changes/deprecations/30027-AL.rst b/doc/api/next_api_changes/deprecations/30027-AL.rst new file mode 100644 index 000000000000..1cbd0340fda6 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/30027-AL.rst @@ -0,0 +1,3 @@ +``PdfFile.fontNames``, ``PdfFile.dviFontNames``, ``PdfFile.type1Descriptors`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... are deprecated with no replacement. diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 4c6bb266e09e..0ab5a65f0b75 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -719,11 +719,11 @@ def __init__(self, filename, metadata=None): self.infoDict = _create_pdf_info_dict('pdf', metadata or {}) - self.fontNames = {} # maps filenames to internal font names self._internal_font_seq = (Name(f'F{i}') for i in itertools.count(1)) - self.dviFontInfo = {} # maps dvi font names to embedding information + self._fontNames = {} # maps filenames to internal font names + self._dviFontInfo = {} # maps dvi font names to embedding information # differently encoded Type-1 fonts may share the same descriptor - self.type1Descriptors = {} + self._type1Descriptors = {} self._character_tracker = _backend_pdf_ps.CharacterTracker() self.alphaStates = {} # maps alpha values to graphics state objects @@ -765,6 +765,11 @@ def __init__(self, filename, metadata=None): 'ProcSet': procsets} self.writeObject(self.resourceObject, resources) + fontNames = _api.deprecated("3.11")(property(lambda self: self._fontNames)) + dviFontNames = _api.deprecated("3.11")(property(lambda self: self._dviFontNames)) + type1Descriptors = _api.deprecated("3.11")( + property(lambda self: self._type1Descriptors)) + def newPage(self, width, height): self.endStream() @@ -894,7 +899,7 @@ def _write_annotations(self): def fontName(self, fontprop): """ Select a font based on fontprop and return a name suitable for - Op.selectfont. If fontprop is a string, it will be interpreted + ``Op.selectfont``. If fontprop is a string, it will be interpreted as the filename of the font. """ @@ -908,12 +913,12 @@ def fontName(self, fontprop): filenames = _fontManager._find_fonts_by_props(fontprop) first_Fx = None for fname in filenames: - Fx = self.fontNames.get(fname) + Fx = self._fontNames.get(fname) if not first_Fx: first_Fx = Fx if Fx is None: Fx = next(self._internal_font_seq) - self.fontNames[fname] = Fx + self._fontNames[fname] = Fx _log.debug('Assigning font %s = %r', Fx, fname) if not first_Fx: first_Fx = Fx @@ -925,11 +930,11 @@ def fontName(self, fontprop): def dviFontName(self, dvifont): """ Given a dvi font object, return a name suitable for Op.selectfont. - This registers the font information in ``self.dviFontInfo`` if not yet - registered. + This registers the font information internally (in ``_dviFontInfo``) if + not yet registered. """ - dvi_info = self.dviFontInfo.get(dvifont.texname) + dvi_info = self._dviFontInfo.get(dvifont.texname) if dvi_info is not None: return dvi_info.pdfname @@ -943,7 +948,7 @@ def dviFontName(self, dvifont): pdfname = next(self._internal_font_seq) _log.debug('Assigning font %s = %s (dvi)', pdfname, dvifont.texname) - self.dviFontInfo[dvifont.texname] = types.SimpleNamespace( + self._dviFontInfo[dvifont.texname] = types.SimpleNamespace( dvifont=dvifont, pdfname=pdfname, fontfile=psfont.filename, @@ -954,12 +959,12 @@ def dviFontName(self, dvifont): def writeFonts(self): fonts = {} - for dviname, info in sorted(self.dviFontInfo.items()): + for dviname, info in sorted(self._dviFontInfo.items()): Fx = info.pdfname _log.debug('Embedding Type-1 font %s from dvi.', dviname) fonts[Fx] = self._embedTeXFont(info) - for filename in sorted(self.fontNames): - Fx = self.fontNames[filename] + for filename in sorted(self._fontNames): + Fx = self._fontNames[filename] _log.debug('Embedding font %s.', filename) if filename.endswith('.afm'): # from pdf.use14corefonts @@ -1039,10 +1044,10 @@ def _embedTeXFont(self, fontinfo): # existing descriptor for this font. effects = (fontinfo.effects.get('slant', 0.0), fontinfo.effects.get('extend', 1.0)) - fontdesc = self.type1Descriptors.get((fontinfo.fontfile, effects)) + fontdesc = self._type1Descriptors.get((fontinfo.fontfile, effects)) if fontdesc is None: fontdesc = self.createType1Descriptor(t1font, fontinfo.fontfile) - self.type1Descriptors[(fontinfo.fontfile, effects)] = fontdesc + self._type1Descriptors[(fontinfo.fontfile, effects)] = fontdesc fontdict['FontDescriptor'] = fontdesc self.writeObject(fontdictObject, fontdict) From ac5225483f9b016739607ad541ea8abcb8cb9c2c Mon Sep 17 00:00:00 2001 From: ellie Date: Fri, 9 May 2025 01:06:30 +0100 Subject: [PATCH 07/12] Update diagram in subplots_adjust documentation to clarify parameter meaning --- doc/_embedded_plots/figure_subplots_adjust.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/doc/_embedded_plots/figure_subplots_adjust.py b/doc/_embedded_plots/figure_subplots_adjust.py index b4b8d7d32a3d..d3de95393bfe 100644 --- a/doc/_embedded_plots/figure_subplots_adjust.py +++ b/doc/_embedded_plots/figure_subplots_adjust.py @@ -1,28 +1,30 @@ import matplotlib.pyplot as plt - def arrow(p1, p2, **props): - axs[0, 0].annotate( - "", p1, p2, xycoords='figure fraction', + overlay.annotate( + "", p1, p2, xycoords='figure fraction', arrowprops=dict(arrowstyle="<->", shrinkA=0, shrinkB=0, **props)) - fig, axs = plt.subplots(2, 2, figsize=(6.5, 4)) fig.set_facecolor('lightblue') fig.subplots_adjust(0.1, 0.1, 0.9, 0.9, 0.4, 0.4) + +overlay = fig.add_axes([0, 0, 1, 1], zorder=100) +overlay.axis("off") + for ax in axs.flat: ax.set(xticks=[], yticks=[]) arrow((0, 0.75), (0.1, 0.75)) # left -arrow((0.435, 0.75), (0.565, 0.75)) # wspace -arrow((0.9, 0.75), (1, 0.75)) # right +arrow((0.435, 0.25), (0.565, 0.25)) # wspace +arrow((0.1, 0.8), (1, 0.8)) # right fig.text(0.05, 0.7, "left", ha="center") -fig.text(0.5, 0.7, "wspace", ha="center") -fig.text(0.95, 0.7, "right", ha="center") +fig.text(0.5, 0.3, "wspace", ha="center") +fig.text(0.95, 0.83, "right", ha="center") -arrow((0.25, 0), (0.25, 0.1)) # bottom +arrow((0.75, 0), (0.75, 0.1)) # bottom arrow((0.25, 0.435), (0.25, 0.565)) # hspace -arrow((0.25, 0.9), (0.25, 1)) # top -fig.text(0.28, 0.05, "bottom", va="center") +arrow((0.80, 0.1), (0.8, 1)) # top +fig.text(0.65, 0.05, "bottom", va="center") fig.text(0.28, 0.5, "hspace", va="center") -fig.text(0.28, 0.95, "top", va="center") +fig.text(0.75, 0.95, "top", va="center") From dd7f51b00956680ac768fea5fa51261c803f1548 Mon Sep 17 00:00:00 2001 From: ellie Date: Fri, 9 May 2025 14:53:49 +0100 Subject: [PATCH 08/12] Corrected subplots_adjust diagram --- doc/_embedded_plots/figure_subplots_adjust.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/_embedded_plots/figure_subplots_adjust.py b/doc/_embedded_plots/figure_subplots_adjust.py index d3de95393bfe..01afb74c9c3e 100644 --- a/doc/_embedded_plots/figure_subplots_adjust.py +++ b/doc/_embedded_plots/figure_subplots_adjust.py @@ -1,5 +1,6 @@ import matplotlib.pyplot as plt + def arrow(p1, p2, **props): overlay.annotate( "", p1, p2, xycoords='figure fraction', @@ -17,14 +18,14 @@ def arrow(p1, p2, **props): arrow((0, 0.75), (0.1, 0.75)) # left arrow((0.435, 0.25), (0.565, 0.25)) # wspace -arrow((0.1, 0.8), (1, 0.8)) # right +arrow((0, 0.8), (0.9, 0.8)) # right fig.text(0.05, 0.7, "left", ha="center") fig.text(0.5, 0.3, "wspace", ha="center") -fig.text(0.95, 0.83, "right", ha="center") +fig.text(0.05, 0.83, "right", ha="center") arrow((0.75, 0), (0.75, 0.1)) # bottom arrow((0.25, 0.435), (0.25, 0.565)) # hspace -arrow((0.80, 0.1), (0.8, 1)) # top +arrow((0.80, 0), (0.8, 0.9)) # top fig.text(0.65, 0.05, "bottom", va="center") fig.text(0.28, 0.5, "hspace", va="center") -fig.text(0.75, 0.95, "top", va="center") +fig.text(0.82, 0.05, "top", va="center") From 785140be92e09f900751e5e4ffc8546acd768d82 Mon Sep 17 00:00:00 2001 From: ellie Date: Fri, 9 May 2025 16:50:04 +0100 Subject: [PATCH 09/12] Addressed diagram feedback and simplified annotation calls --- doc/_embedded_plots/figure_subplots_adjust.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/doc/_embedded_plots/figure_subplots_adjust.py b/doc/_embedded_plots/figure_subplots_adjust.py index 01afb74c9c3e..6f99a3febcdc 100644 --- a/doc/_embedded_plots/figure_subplots_adjust.py +++ b/doc/_embedded_plots/figure_subplots_adjust.py @@ -1,31 +1,34 @@ import matplotlib.pyplot as plt -def arrow(p1, p2, **props): - overlay.annotate( - "", p1, p2, xycoords='figure fraction', - arrowprops=dict(arrowstyle="<->", shrinkA=0, shrinkB=0, **props)) - fig, axs = plt.subplots(2, 2, figsize=(6.5, 4)) fig.set_facecolor('lightblue') fig.subplots_adjust(0.1, 0.1, 0.9, 0.9, 0.4, 0.4) overlay = fig.add_axes([0, 0, 1, 1], zorder=100) overlay.axis("off") +xycoords='figure fraction' +arrowprops=dict(arrowstyle="<->", shrinkA=0, shrinkB=0) for ax in axs.flat: ax.set(xticks=[], yticks=[]) -arrow((0, 0.75), (0.1, 0.75)) # left -arrow((0.435, 0.25), (0.565, 0.25)) # wspace -arrow((0, 0.8), (0.9, 0.8)) # right +overlay.annotate("", (0, 0.75), (0.1, 0.75), + xycoords=xycoords, arrowprops=arrowprops) # left +overlay.annotate("", (0.435, 0.25), (0.565, 0.25), + xycoords=xycoords, arrowprops=arrowprops) # wspace +overlay.annotate("", (0, 0.8), (0.9, 0.8), + xycoords=xycoords, arrowprops=arrowprops) # right fig.text(0.05, 0.7, "left", ha="center") fig.text(0.5, 0.3, "wspace", ha="center") fig.text(0.05, 0.83, "right", ha="center") -arrow((0.75, 0), (0.75, 0.1)) # bottom -arrow((0.25, 0.435), (0.25, 0.565)) # hspace -arrow((0.80, 0), (0.8, 0.9)) # top +overlay.annotate("", (0.75, 0), (0.75, 0.1), + xycoords=xycoords, arrowprops=arrowprops) # bottom +overlay.annotate("", (0.25, 0.435), (0.25, 0.565), + xycoords=xycoords, arrowprops=arrowprops) # hspace +overlay.annotate("", (0.8, 0), (0.8, 0.9), + xycoords=xycoords, arrowprops=arrowprops) # top fig.text(0.65, 0.05, "bottom", va="center") fig.text(0.28, 0.5, "hspace", va="center") fig.text(0.82, 0.05, "top", va="center") From da6b32e02b66832556d2e668fed1392d94f965ba Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 9 May 2025 20:49:12 +0100 Subject: [PATCH 10/12] Enable linting of .pyi files --- lib/matplotlib/__init__.pyi | 2 ++ lib/matplotlib/_enums.pyi | 1 - lib/matplotlib/axes/_axes.pyi | 2 -- lib/matplotlib/colorizer.pyi | 1 - lib/matplotlib/colors.pyi | 8 ++++---- lib/matplotlib/contour.pyi | 2 +- lib/matplotlib/table.pyi | 2 +- lib/matplotlib/text.pyi | 2 +- pyproject.toml | 3 +-- 9 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi index 88058ffd7def..07019109f406 100644 --- a/lib/matplotlib/__init__.pyi +++ b/lib/matplotlib/__init__.pyi @@ -26,6 +26,8 @@ __all__ = [ "interactive", "is_interactive", "colormaps", + "multivar_colormaps", + "bivar_colormaps", "color_sequences", ] diff --git a/lib/matplotlib/_enums.pyi b/lib/matplotlib/_enums.pyi index 714e6cfe03fa..3ff7e208c398 100644 --- a/lib/matplotlib/_enums.pyi +++ b/lib/matplotlib/_enums.pyi @@ -1,4 +1,3 @@ -from typing import cast from enum import Enum diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index a23a0b27f01b..c3eb28d2f095 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -2,7 +2,6 @@ from matplotlib.axes._base import _AxesBase from matplotlib.axes._secondary_axes import SecondaryAxis from matplotlib.artist import Artist -from matplotlib.backend_bases import RendererBase from matplotlib.collections import ( Collection, FillBetweenPolyCollection, @@ -32,7 +31,6 @@ import matplotlib.table as mtable import matplotlib.stackplot as mstack import matplotlib.streamplot as mstream -import datetime import PIL.Image from collections.abc import Callable, Iterable, Sequence from typing import Any, Literal, overload diff --git a/lib/matplotlib/colorizer.pyi b/lib/matplotlib/colorizer.pyi index 8fcce3e5d63b..f35ebe5295e4 100644 --- a/lib/matplotlib/colorizer.pyi +++ b/lib/matplotlib/colorizer.pyi @@ -1,6 +1,5 @@ from matplotlib import cbook, colorbar, colors, artist -from typing import overload import numpy as np from numpy.typing import ArrayLike diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 3e761c949068..eadd759bcaa3 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -196,8 +196,8 @@ class BivarColormap: M: int n_variates: int def __init__( - self, N: int = ..., M: int | None = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., - origin: Sequence[float] = ..., name: str = ... + self, N: int = ..., M: int | None = ..., shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... ) -> None: ... @overload def __call__( @@ -245,8 +245,8 @@ class SegmentedBivarColormap(BivarColormap): class BivarColormapFromImage(BivarColormap): def __init__( - self, lut: np.ndarray, shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., - origin: Sequence[float] = ..., name: str = ... + self, lut: np.ndarray, shape: Literal['square', 'circle', 'ignore', 'circleignore'] = ..., + origin: Sequence[float] = ..., name: str = ... ) -> None: ... class Normalize: diff --git a/lib/matplotlib/contour.pyi b/lib/matplotlib/contour.pyi index 7400fac50993..2a89d6016170 100644 --- a/lib/matplotlib/contour.pyi +++ b/lib/matplotlib/contour.pyi @@ -1,7 +1,7 @@ import matplotlib.cm as cm from matplotlib.artist import Artist from matplotlib.axes import Axes -from matplotlib.collections import Collection, PathCollection +from matplotlib.collections import Collection from matplotlib.colorizer import Colorizer, ColorizingArtist from matplotlib.colors import Colormap, Normalize from matplotlib.path import Path diff --git a/lib/matplotlib/table.pyi b/lib/matplotlib/table.pyi index 07d2427f66dc..167d98d3c4cb 100644 --- a/lib/matplotlib/table.pyi +++ b/lib/matplotlib/table.pyi @@ -8,7 +8,7 @@ from .transforms import Bbox from .typing import ColorType from collections.abc import Sequence -from typing import Any, Literal, TYPE_CHECKING +from typing import Any, Literal from pandas import DataFrame diff --git a/lib/matplotlib/text.pyi b/lib/matplotlib/text.pyi index 9cdfd9596a7d..41c7b761ae32 100644 --- a/lib/matplotlib/text.pyi +++ b/lib/matplotlib/text.pyi @@ -14,7 +14,7 @@ from .transforms import ( Transform, ) -from collections.abc import Callable, Iterable +from collections.abc import Iterable from typing import Any, Literal from .typing import ColorType, CoordsType diff --git a/pyproject.toml b/pyproject.toml index dc951375bba2..81a1c32baf34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,8 +99,6 @@ exclude = [ "tools/gh_api.py", ".tox", ".eggs", - # TODO: fix .pyi files - "*.pyi", # TODO: fix .ipynb files "*.ipynb" ] @@ -173,6 +171,7 @@ external = [ convention = "numpy" [tool.ruff.lint.per-file-ignores] +"*.pyi" = ["E501"] "doc/conf.py" = ["E402"] "galleries/examples/animation/frame_grabbing_sgskip.py" = ["E402"] "galleries/examples/images_contours_and_fields/tricontour_demo.py" = ["E201"] From 03fa1ffb1b26733f109ae10ac67c8432f48ec320 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 10 May 2025 05:03:10 -0400 Subject: [PATCH 11/12] Remove meson-python pinning (#30035) Version 0.18 should restore handling of symlinks: https://github.com/mesonbuild/meson-python/pull/728 --- pyproject.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 81a1c32baf34..70b078a73d27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ requires-python = ">=3.11" [project.optional-dependencies] # Should be a copy of the build dependencies below. dev = [ - "meson-python>=0.13.1,<0.17.0", + "meson-python>=0.13.1,!=0.17.*", "pybind11>=2.13.2,!=2.13.3", "setuptools_scm>=7", # Not required by us but setuptools_scm without a version, cso _if_ @@ -70,7 +70,9 @@ dev = [ build-backend = "mesonpy" # Also keep in sync with optional dependencies above. requires = [ - "meson-python>=0.13.1,<0.17.0", + # meson-python 0.17.x breaks symlinks in sdists. You can remove this pin if + # you really need it and aren't using an sdist. + "meson-python>=0.13.1,!=0.17.*", "pybind11>=2.13.2,!=2.13.3", "setuptools_scm>=7", ] From 206c51a65d48dcbc78fc51707234734a3a9c876f Mon Sep 17 00:00:00 2001 From: Barbier--Darnal Joseph Date: Sun, 11 May 2025 08:58:43 +0200 Subject: [PATCH 12/12] update top message matplotlibrc file --- lib/matplotlib/mpl-data/matplotlibrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 72117abf7317..acb131c82e6c 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -1,6 +1,6 @@ #### MATPLOTLIBRC FORMAT -## NOTE FOR END USERS: DO NOT EDIT THIS FILE! +## DO NOT EDIT THIS FILE, MAKE A COPY FIRST ## ## This is a sample Matplotlib configuration file - you can find a copy ## of it on your system in site-packages/matplotlib/mpl-data/matplotlibrc