From 94146a111fb32ba4f9d1f48e76e3a3280cfff553 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 27 Jul 2023 20:32:56 -0400 Subject: [PATCH] Update type hints for font manager and extension --- ci/mypy-stubtest-allowlist.txt | 2 +- lib/matplotlib/font_manager.py | 5 +- lib/matplotlib/font_manager.pyi | 37 +++--- lib/matplotlib/ft2font.pyi | 137 +++++++++++++--------- lib/matplotlib/tests/test_font_manager.py | 8 +- lib/matplotlib/tests/test_ft2font.py | 4 +- lib/matplotlib/tests/test_mathtext.py | 2 +- src/ft2font_wrapper.cpp | 41 ++++--- 8 files changed, 139 insertions(+), 97 deletions(-) diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index a6ff0554cc2b..f9c7d000b08e 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -5,7 +5,7 @@ matplotlib.pylab.* matplotlib._.* matplotlib.rcsetup._listify_validator matplotlib.rcsetup._validate_linestyle -matplotlib.ft2font.* +matplotlib.ft2font.Glyph matplotlib.testing.jpl_units.* matplotlib.sphinxext.* diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index e9ac9369b86f..0d23521d6493 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -40,6 +40,7 @@ import subprocess import sys import threading +from typing import Union import matplotlib as mpl from matplotlib import _api, _afm, cbook, ft2font @@ -315,7 +316,7 @@ def _fontentry_helper_repr_html(fontent): ('name', str, dataclasses.field(default='')), ('style', str, dataclasses.field(default='normal')), ('variant', str, dataclasses.field(default='normal')), - ('weight', str, dataclasses.field(default='normal')), + ('weight', Union[str, int], dataclasses.field(default='normal')), ('stretch', str, dataclasses.field(default='normal')), ('size', str, dataclasses.field(default='medium')), ], @@ -464,6 +465,8 @@ def afmFontProperty(fontpath, font): Parameters ---------- + fontpath : str + The filename corresponding to *font*. font : AFM The AFM font file from which information will be extracted. diff --git a/lib/matplotlib/font_manager.pyi b/lib/matplotlib/font_manager.pyi index 92b78ae2212d..e5213230a0ca 100644 --- a/lib/matplotlib/font_manager.pyi +++ b/lib/matplotlib/font_manager.pyi @@ -20,11 +20,11 @@ X11FontDirectories: list[str] OSXFontDirectories: list[str] def get_fontext_synonyms(fontext: str) -> list[str]: ... -def list_fonts(directory: str, extensions: Iterable[str]): ... +def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ... def win32FontDirectory() -> str: ... def _get_fontconfig_fonts() -> list[Path]: ... def findSystemFonts( - fontpaths: Iterable[str] | None = ..., fontext: str = ... + fontpaths: Iterable[str | os.PathLike | Path] | None = ..., fontext: str = ... ) -> list[str]: ... @dataclass class FontEntry: @@ -32,7 +32,7 @@ class FontEntry: name: str = ... style: str = ... variant: str = ... - weight: str = ... + weight: str | int = ... stretch: str = ... size: str = ... @@ -42,41 +42,44 @@ def afmFontProperty(fontpath: str, font: AFM) -> FontEntry: ... class FontProperties: def __init__( self, - family: str | None = ..., + family: str | Iterable[str] | None = ..., style: Literal["normal", "italic", "oblique"] | None = ..., variant: Literal["normal", "small-caps"] | None = ..., weight: int | str | None = ..., stretch: int | str | None = ..., size: float | str | None = ..., - fname: str | None = ..., + fname: str | os.PathLike | Path | None = ..., math_fontfamily: str | None = ..., ) -> None: ... def __hash__(self) -> int: ... def __eq__(self, other: object) -> bool: ... - def get_family(self) -> str: ... + def get_family(self) -> list[str]: ... def get_name(self) -> str: ... def get_style(self) -> Literal["normal", "italic", "oblique"]: ... def get_variant(self) -> Literal["normal", "small-caps"]: ... def get_weight(self) -> int | str: ... def get_stretch(self) -> int | str: ... def get_size(self) -> float: ... - def get_file(self) -> str: ... + def get_file(self) -> str | bytes | None: ... def get_fontconfig_pattern(self) -> dict[str, list[Any]]: ... - def set_family(self, family: str | Iterable[str]) -> None: ... - def set_style(self, style: Literal["normal", "italic", "oblique"]) -> None: ... - def set_variant(self, variant: Literal["normal", "small-caps"]) -> None: ... - def set_weight(self, weight: int | str) -> None: ... - def set_stretch(self, stretch: int | str) -> None: ... - def set_size(self, size: float | str) -> None: ... + def set_family(self, family: str | Iterable[str] | None) -> None: ... + def set_style( + self, style: Literal["normal", "italic", "oblique"] | None + ) -> None: ... + def set_variant(self, variant: Literal["normal", "small-caps"] | None) -> None: ... + def set_weight(self, weight: int | str | None) -> None: ... + def set_stretch(self, stretch: int | str | None) -> None: ... + def set_size(self, size: float | str | None) -> None: ... def set_file(self, file: str | os.PathLike | Path | None) -> None: ... def set_fontconfig_pattern(self, pattern: str) -> None: ... def get_math_fontfamily(self) -> str: ... def set_math_fontfamily(self, fontfamily: str | None) -> None: ... def copy(self) -> FontProperties: ... - def set_name(self, family: str) -> None: ... - def get_slant(self) -> Literal["normal", "italic", "oblique"]: ... - def set_slant(self, style: Literal["normal", "italic", "oblique"]) -> None: ... - def get_size_in_points(self) -> float: ... + # Aliases + set_name = set_family + get_slant = get_style + set_slant = set_style + get_size_in_points = get_size def json_dump(data: FontManager, filename: str | Path | os.PathLike) -> None: ... def json_load(filename: str | Path | os.PathLike) -> FontManager: ... diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi index b4c30ef5ad8c..fb74fd676f5b 100644 --- a/lib/matplotlib/ft2font.pyi +++ b/lib/matplotlib/ft2font.pyi @@ -1,8 +1,10 @@ -# This is generated from a compiled module, and as such is very generic -# This could be more specific. Docstrings for this module are light +from typing import BinaryIO, Literal -from typing import Any +import numpy as np +from numpy.typing import NDArray +__freetype_build_type__: str +__freetype_version__: str BOLD: int EXTERNAL_STREAM: int FAST_GLYPHS: int @@ -41,54 +43,83 @@ SFNT: int VERTICAL: int class FT2Font: - ascender: Any - bbox: Any - descender: Any - face_flags: Any - family_name: Any - fname: Any - height: Any - max_advance_height: Any - max_advance_width: Any - num_charmaps: Any - num_faces: Any - num_fixed_sizes: Any - num_glyphs: Any - postscript_name: Any - scalable: Any - style_flags: Any - style_name: Any - underline_position: Any - underline_thickness: Any - units_per_EM: Any - def __init__(self, *args, **kwargs) -> None: ... - def _get_fontmap(self, *args, **kwargs) -> Any: ... - def clear(self, *args, **kwargs) -> Any: ... - def draw_glyph_to_bitmap(self, *args, **kwargs) -> Any: ... - def draw_glyphs_to_bitmap(self, *args, **kwargs) -> Any: ... - def get_bitmap_offset(self, *args, **kwargs) -> Any: ... - def get_char_index(self, *args, **kwargs) -> Any: ... - def get_charmap(self, *args, **kwargs) -> Any: ... - def get_descent(self, *args, **kwargs) -> Any: ... - def get_glyph_name(self, *args, **kwargs) -> Any: ... - def get_image(self, *args, **kwargs) -> Any: ... - def get_kerning(self, *args, **kwargs) -> Any: ... - def get_name_index(self, *args, **kwargs) -> Any: ... - def get_num_glyphs(self, *args, **kwargs) -> Any: ... - def get_path(self, *args, **kwargs) -> Any: ... - def get_ps_font_info(self, *args, **kwargs) -> Any: ... - def get_sfnt(self, *args, **kwargs) -> Any: ... - def get_sfnt_table(self, *args, **kwargs) -> Any: ... - def get_width_height(self, *args, **kwargs) -> Any: ... - def get_xys(self, *args, **kwargs) -> Any: ... - def load_char(self, *args, **kwargs) -> Any: ... - def load_glyph(self, *args, **kwargs) -> Any: ... - def select_charmap(self, *args, **kwargs) -> Any: ... - def set_charmap(self, *args, **kwargs) -> Any: ... - def set_size(self, *args, **kwargs) -> Any: ... - def set_text(self, *args, **kwargs) -> Any: ... + ascender: int + bbox: tuple[int, int, int, int] + descender: int + face_flags: int + family_name: str + fname: str + height: int + max_advance_height: int + max_advance_width: int + num_charmaps: int + num_faces: int + num_fixed_sizes: int + num_glyphs: int + postscript_name: str + scalable: bool + style_flags: int + style_name: str + underline_position: int + underline_thickness: int + units_per_EM: int -class FT2Image: - def __init__(self, *args, **kwargs) -> None: ... - def draw_rect(self, *args, **kwargs) -> Any: ... - def draw_rect_filled(self, *args, **kwargs) -> Any: ... + def __init__( + self, + filename: str | BinaryIO, + hinting_factor: int = ..., + *, + _fallback_list: list[FT2Font] | None = ..., + _kerning_factor: int = ... + ) -> None: ... + def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ... + def clear(self) -> None: ... + def draw_glyph_to_bitmap( + self, image: FT2Image, x: float, y: float, glyph: Glyph, antialiased: bool = ... + ) -> None: ... + def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ... + def get_bitmap_offset(self) -> tuple[int, int]: ... + def get_char_index(self, codepoint: int) -> int: ... + def get_charmap(self) -> dict[int, int]: ... + def get_descent(self) -> int: ... + def get_glyph_name(self, index: int) -> str: ... + def get_image(self) -> NDArray[np.uint8]: ... + def get_kerning(self, left: int, right: int, mode: int) -> int: ... + def get_name_index(self, name: str) -> int: ... + def get_num_glyphs(self) -> int: ... + def get_path(self) -> tuple[NDArray[np.float64], NDArray[np.int8]]: ... + def get_ps_font_info( + self, + ) -> tuple[str, str, str, str, str, int, int, int, int]: ... + def get_sfnt(self) -> dict[tuple[int, int, int, int], bytes]: ... + def get_sfnt_table( + self, name: Literal["head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt"] + ) -> dict[str, tuple[int, int, int, int] | tuple[int, int] | int | bytes]: ... + def get_width_height(self) -> tuple[int, int]: ... + def get_xys(self, antialiased: bool = ...) -> NDArray[np.float64]: ... + def load_char(self, charcode: int, flags: int = ...) -> Glyph: ... + def load_glyph(self, glyphindex: int, flags: int = ...) -> Glyph: ... + def select_charmap(self, i: int) -> None: ... + def set_charmap(self, i: int) -> None: ... + def set_size(self, ptsize: float, dpi: float) -> None: ... + def set_text( + self, string: str, angle: float = ..., flags: int = ... + ) -> NDArray[np.float64]: ... + +class FT2Image: # TODO: When updating mypy>=1.4, subclass from Buffer. + def __init__(self, width: float, height: float) -> None: ... + def draw_rect(self, x0: float, y0: float, x1: float, y1: float) -> None: ... + def draw_rect_filled(self, x0: float, y0: float, x1: float, y1: float) -> None: ... + +class Glyph: + width: int + height: int + horiBearingX: int + horiBearingY: int + horiAdvance: int + linearHoriAdvance: int + vertBearingX: int + vertBearingY: int + vertAdvance: int + + def bbox(self) -> tuple[int, int, int, int]: ... diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 34dd32d944e8..ec901452ee20 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -25,11 +25,11 @@ def test_font_priority(): with rc_context(rc={ 'font.sans-serif': ['cmmi10', 'Bitstream Vera Sans']}): - font = findfont(FontProperties(family=["sans-serif"])) - assert Path(font).name == 'cmmi10.ttf' + fontfile = findfont(FontProperties(family=["sans-serif"])) + assert Path(fontfile).name == 'cmmi10.ttf' # Smoketest get_charmap, which isn't used internally anymore - font = get_font(font) + font = get_font(fontfile) cmap = font.get_charmap() assert len(cmap) == 131 assert cmap[8729] == 30 @@ -148,7 +148,7 @@ def test_find_invalid(tmpdir): # Not really public, but get_font doesn't expose non-filename constructor. from matplotlib.ft2font import FT2Font with pytest.raises(TypeError, match='font file or a binary-mode file'): - FT2Font(StringIO()) + FT2Font(StringIO()) # type: ignore[arg-type] @pytest.mark.skipif(sys.platform != 'linux' or not has_fclist, diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index 9ef7127d6685..0139bdf41526 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -14,12 +14,12 @@ def test_fallback_errors(): with pytest.raises(TypeError, match="Fallback list must be a list"): # failing to be a list will fail before the 0 - ft2font.FT2Font(file_name, _fallback_list=(0,)) + ft2font.FT2Font(file_name, _fallback_list=(0,)) # type: ignore[arg-type] with pytest.raises( TypeError, match="Fallback fonts must be FT2Font objects." ): - ft2font.FT2Font(file_name, _fallback_list=[0]) + ft2font.FT2Font(file_name, _fallback_list=[0]) # type: ignore[list-item] def test_ft2font_positive_hinting_factor(): diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index f281b1e47412..54f417002ce4 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -182,7 +182,7 @@ font_tests: list[None | str] = [] for fonts, chars in font_test_specs: if fonts is None: - font_tests.extend([None] * chars) # type: ignore + font_tests.extend([None] * chars) else: wrapper = ''.join([ ' '.join(fonts), diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 8f37b5c7d9ad..f3298e9e7745 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -331,46 +331,51 @@ const char *PyFT2Font_init__doc__ = "Parameters\n" "----------\n" "filename : str or file-like\n" - " The source of the font data in a format (ttf or ttc) that FreeType can read\n\n" + " The source of the font data in a format (ttf or ttc) that FreeType can read\n" + "\n" "hinting_factor : int, optional\n" " Must be positive. Used to scale the hinting in the x-direction\n" "_fallback_list : list of FT2Font, optional\n" - " A list of FT2Font objects used to find missing glyphs.\n\n" + " A list of FT2Font objects used to find missing glyphs.\n" + "\n" " .. warning::\n" - " This API is both private and provisional: do not use it directly\n\n" + " This API is both private and provisional: do not use it directly\n" + "\n" "_kerning_factor : int, optional\n" - " Used to adjust the degree of kerning.\n\n" + " Used to adjust the degree of kerning.\n" + "\n" " .. warning::\n" - " This API is private: do not use it directly\n\n" + " This API is private: do not use it directly\n" + "\n" "Attributes\n" "----------\n" - "num_faces\n" + "num_faces : int\n" " Number of faces in file.\n" "face_flags, style_flags : int\n" " Face and style flags; see the ft2font constants.\n" - "num_glyphs\n" + "num_glyphs : int\n" " Number of glyphs in the face.\n" - "family_name, style_name\n" + "family_name, style_name : str\n" " Face family and style name.\n" - "num_fixed_sizes\n" + "num_fixed_sizes : int\n" " Number of bitmap in the face.\n" - "scalable\n" + "scalable : bool\n" " Whether face is scalable; attributes after this one are only\n" " defined for scalable faces.\n" - "bbox\n" + "bbox : tuple[int, int, int, int]\n" " Face global bounding box (xmin, ymin, xmax, ymax).\n" - "units_per_EM\n" + "units_per_EM : int\n" " Number of font units covered by the EM.\n" - "ascender, descender\n" + "ascender, descender : int\n" " Ascender and descender in 26.6 units.\n" - "height\n" + "height : int\n" " Height in 26.6 units; used to compute a default line spacing\n" " (baseline-to-baseline distance).\n" - "max_advance_width, max_advance_height\n" + "max_advance_width, max_advance_height : int\n" " Maximum horizontal and vertical cursor advance for all glyphs.\n" - "underline_position, underline_thickness\n" + "underline_position, underline_thickness : int\n" " Vertical position and thickness of the underline bar.\n" - "postscript_name\n" + "postscript_name : str\n" " PostScript name of the font.\n"; static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds) @@ -627,7 +632,7 @@ static PyObject *PyFT2Font_get_fontmap(PyFT2Font *self, PyObject *args, PyObject const char *PyFT2Font_set_text__doc__ = - "set_text(self, string, angle, flags=32)\n" + "set_text(self, string, angle=0.0, flags=32)\n" "--\n\n" "Set the text *string* and *angle*.\n" "*flags* can be a bitwise-or of the LOAD_XXX constants;\n"