Skip to content

Commit ccad5ef

Browse files
authored
Merge pull request matplotlib#26403 from QuLogic/font-typing
Update type hints for font manager and extension
2 parents 4b1508f + 94146a1 commit ccad5ef

File tree

8 files changed

+139
-97
lines changed

8 files changed

+139
-97
lines changed

ci/mypy-stubtest-allowlist.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ matplotlib.pylab.*
55
matplotlib._.*
66
matplotlib.rcsetup._listify_validator
77
matplotlib.rcsetup._validate_linestyle
8-
matplotlib.ft2font.*
8+
matplotlib.ft2font.Glyph
99
matplotlib.testing.jpl_units.*
1010
matplotlib.sphinxext.*
1111

lib/matplotlib/font_manager.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import subprocess
4141
import sys
4242
import threading
43+
from typing import Union
4344

4445
import matplotlib as mpl
4546
from matplotlib import _api, _afm, cbook, ft2font
@@ -315,7 +316,7 @@ def _fontentry_helper_repr_html(fontent):
315316
('name', str, dataclasses.field(default='')),
316317
('style', str, dataclasses.field(default='normal')),
317318
('variant', str, dataclasses.field(default='normal')),
318-
('weight', str, dataclasses.field(default='normal')),
319+
('weight', Union[str, int], dataclasses.field(default='normal')),
319320
('stretch', str, dataclasses.field(default='normal')),
320321
('size', str, dataclasses.field(default='medium')),
321322
],
@@ -464,6 +465,8 @@ def afmFontProperty(fontpath, font):
464465
465466
Parameters
466467
----------
468+
fontpath : str
469+
The filename corresponding to *font*.
467470
font : AFM
468471
The AFM font file from which information will be extracted.
469472

lib/matplotlib/font_manager.pyi

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,19 @@ X11FontDirectories: list[str]
2020
OSXFontDirectories: list[str]
2121

2222
def get_fontext_synonyms(fontext: str) -> list[str]: ...
23-
def list_fonts(directory: str, extensions: Iterable[str]): ...
23+
def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ...
2424
def win32FontDirectory() -> str: ...
2525
def _get_fontconfig_fonts() -> list[Path]: ...
2626
def findSystemFonts(
27-
fontpaths: Iterable[str] | None = ..., fontext: str = ...
27+
fontpaths: Iterable[str | os.PathLike | Path] | None = ..., fontext: str = ...
2828
) -> list[str]: ...
2929
@dataclass
3030
class FontEntry:
3131
fname: str = ...
3232
name: str = ...
3333
style: str = ...
3434
variant: str = ...
35-
weight: str = ...
35+
weight: str | int = ...
3636
stretch: str = ...
3737
size: str = ...
3838

@@ -42,41 +42,44 @@ def afmFontProperty(fontpath: str, font: AFM) -> FontEntry: ...
4242
class FontProperties:
4343
def __init__(
4444
self,
45-
family: str | None = ...,
45+
family: str | Iterable[str] | None = ...,
4646
style: Literal["normal", "italic", "oblique"] | None = ...,
4747
variant: Literal["normal", "small-caps"] | None = ...,
4848
weight: int | str | None = ...,
4949
stretch: int | str | None = ...,
5050
size: float | str | None = ...,
51-
fname: str | None = ...,
51+
fname: str | os.PathLike | Path | None = ...,
5252
math_fontfamily: str | None = ...,
5353
) -> None: ...
5454
def __hash__(self) -> int: ...
5555
def __eq__(self, other: object) -> bool: ...
56-
def get_family(self) -> str: ...
56+
def get_family(self) -> list[str]: ...
5757
def get_name(self) -> str: ...
5858
def get_style(self) -> Literal["normal", "italic", "oblique"]: ...
5959
def get_variant(self) -> Literal["normal", "small-caps"]: ...
6060
def get_weight(self) -> int | str: ...
6161
def get_stretch(self) -> int | str: ...
6262
def get_size(self) -> float: ...
63-
def get_file(self) -> str: ...
63+
def get_file(self) -> str | bytes | None: ...
6464
def get_fontconfig_pattern(self) -> dict[str, list[Any]]: ...
65-
def set_family(self, family: str | Iterable[str]) -> None: ...
66-
def set_style(self, style: Literal["normal", "italic", "oblique"]) -> None: ...
67-
def set_variant(self, variant: Literal["normal", "small-caps"]) -> None: ...
68-
def set_weight(self, weight: int | str) -> None: ...
69-
def set_stretch(self, stretch: int | str) -> None: ...
70-
def set_size(self, size: float | str) -> None: ...
65+
def set_family(self, family: str | Iterable[str] | None) -> None: ...
66+
def set_style(
67+
self, style: Literal["normal", "italic", "oblique"] | None
68+
) -> None: ...
69+
def set_variant(self, variant: Literal["normal", "small-caps"] | None) -> None: ...
70+
def set_weight(self, weight: int | str | None) -> None: ...
71+
def set_stretch(self, stretch: int | str | None) -> None: ...
72+
def set_size(self, size: float | str | None) -> None: ...
7173
def set_file(self, file: str | os.PathLike | Path | None) -> None: ...
7274
def set_fontconfig_pattern(self, pattern: str) -> None: ...
7375
def get_math_fontfamily(self) -> str: ...
7476
def set_math_fontfamily(self, fontfamily: str | None) -> None: ...
7577
def copy(self) -> FontProperties: ...
76-
def set_name(self, family: str) -> None: ...
77-
def get_slant(self) -> Literal["normal", "italic", "oblique"]: ...
78-
def set_slant(self, style: Literal["normal", "italic", "oblique"]) -> None: ...
79-
def get_size_in_points(self) -> float: ...
78+
# Aliases
79+
set_name = set_family
80+
get_slant = get_style
81+
set_slant = set_style
82+
get_size_in_points = get_size
8083

8184
def json_dump(data: FontManager, filename: str | Path | os.PathLike) -> None: ...
8285
def json_load(filename: str | Path | os.PathLike) -> FontManager: ...

lib/matplotlib/ft2font.pyi

Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
# This is generated from a compiled module, and as such is very generic
2-
# This could be more specific. Docstrings for this module are light
1+
from typing import BinaryIO, Literal
32

4-
from typing import Any
3+
import numpy as np
4+
from numpy.typing import NDArray
55

6+
__freetype_build_type__: str
7+
__freetype_version__: str
68
BOLD: int
79
EXTERNAL_STREAM: int
810
FAST_GLYPHS: int
@@ -41,54 +43,83 @@ SFNT: int
4143
VERTICAL: int
4244

4345
class FT2Font:
44-
ascender: Any
45-
bbox: Any
46-
descender: Any
47-
face_flags: Any
48-
family_name: Any
49-
fname: Any
50-
height: Any
51-
max_advance_height: Any
52-
max_advance_width: Any
53-
num_charmaps: Any
54-
num_faces: Any
55-
num_fixed_sizes: Any
56-
num_glyphs: Any
57-
postscript_name: Any
58-
scalable: Any
59-
style_flags: Any
60-
style_name: Any
61-
underline_position: Any
62-
underline_thickness: Any
63-
units_per_EM: Any
64-
def __init__(self, *args, **kwargs) -> None: ...
65-
def _get_fontmap(self, *args, **kwargs) -> Any: ...
66-
def clear(self, *args, **kwargs) -> Any: ...
67-
def draw_glyph_to_bitmap(self, *args, **kwargs) -> Any: ...
68-
def draw_glyphs_to_bitmap(self, *args, **kwargs) -> Any: ...
69-
def get_bitmap_offset(self, *args, **kwargs) -> Any: ...
70-
def get_char_index(self, *args, **kwargs) -> Any: ...
71-
def get_charmap(self, *args, **kwargs) -> Any: ...
72-
def get_descent(self, *args, **kwargs) -> Any: ...
73-
def get_glyph_name(self, *args, **kwargs) -> Any: ...
74-
def get_image(self, *args, **kwargs) -> Any: ...
75-
def get_kerning(self, *args, **kwargs) -> Any: ...
76-
def get_name_index(self, *args, **kwargs) -> Any: ...
77-
def get_num_glyphs(self, *args, **kwargs) -> Any: ...
78-
def get_path(self, *args, **kwargs) -> Any: ...
79-
def get_ps_font_info(self, *args, **kwargs) -> Any: ...
80-
def get_sfnt(self, *args, **kwargs) -> Any: ...
81-
def get_sfnt_table(self, *args, **kwargs) -> Any: ...
82-
def get_width_height(self, *args, **kwargs) -> Any: ...
83-
def get_xys(self, *args, **kwargs) -> Any: ...
84-
def load_char(self, *args, **kwargs) -> Any: ...
85-
def load_glyph(self, *args, **kwargs) -> Any: ...
86-
def select_charmap(self, *args, **kwargs) -> Any: ...
87-
def set_charmap(self, *args, **kwargs) -> Any: ...
88-
def set_size(self, *args, **kwargs) -> Any: ...
89-
def set_text(self, *args, **kwargs) -> Any: ...
46+
ascender: int
47+
bbox: tuple[int, int, int, int]
48+
descender: int
49+
face_flags: int
50+
family_name: str
51+
fname: str
52+
height: int
53+
max_advance_height: int
54+
max_advance_width: int
55+
num_charmaps: int
56+
num_faces: int
57+
num_fixed_sizes: int
58+
num_glyphs: int
59+
postscript_name: str
60+
scalable: bool
61+
style_flags: int
62+
style_name: str
63+
underline_position: int
64+
underline_thickness: int
65+
units_per_EM: int
9066

91-
class FT2Image:
92-
def __init__(self, *args, **kwargs) -> None: ...
93-
def draw_rect(self, *args, **kwargs) -> Any: ...
94-
def draw_rect_filled(self, *args, **kwargs) -> Any: ...
67+
def __init__(
68+
self,
69+
filename: str | BinaryIO,
70+
hinting_factor: int = ...,
71+
*,
72+
_fallback_list: list[FT2Font] | None = ...,
73+
_kerning_factor: int = ...
74+
) -> None: ...
75+
def _get_fontmap(self, string: str) -> dict[str, FT2Font]: ...
76+
def clear(self) -> None: ...
77+
def draw_glyph_to_bitmap(
78+
self, image: FT2Image, x: float, y: float, glyph: Glyph, antialiased: bool = ...
79+
) -> None: ...
80+
def draw_glyphs_to_bitmap(self, antialiased: bool = ...) -> None: ...
81+
def get_bitmap_offset(self) -> tuple[int, int]: ...
82+
def get_char_index(self, codepoint: int) -> int: ...
83+
def get_charmap(self) -> dict[int, int]: ...
84+
def get_descent(self) -> int: ...
85+
def get_glyph_name(self, index: int) -> str: ...
86+
def get_image(self) -> NDArray[np.uint8]: ...
87+
def get_kerning(self, left: int, right: int, mode: int) -> int: ...
88+
def get_name_index(self, name: str) -> int: ...
89+
def get_num_glyphs(self) -> int: ...
90+
def get_path(self) -> tuple[NDArray[np.float64], NDArray[np.int8]]: ...
91+
def get_ps_font_info(
92+
self,
93+
) -> tuple[str, str, str, str, str, int, int, int, int]: ...
94+
def get_sfnt(self) -> dict[tuple[int, int, int, int], bytes]: ...
95+
def get_sfnt_table(
96+
self, name: Literal["head", "maxp", "OS/2", "hhea", "vhea", "post", "pclt"]
97+
) -> dict[str, tuple[int, int, int, int] | tuple[int, int] | int | bytes]: ...
98+
def get_width_height(self) -> tuple[int, int]: ...
99+
def get_xys(self, antialiased: bool = ...) -> NDArray[np.float64]: ...
100+
def load_char(self, charcode: int, flags: int = ...) -> Glyph: ...
101+
def load_glyph(self, glyphindex: int, flags: int = ...) -> Glyph: ...
102+
def select_charmap(self, i: int) -> None: ...
103+
def set_charmap(self, i: int) -> None: ...
104+
def set_size(self, ptsize: float, dpi: float) -> None: ...
105+
def set_text(
106+
self, string: str, angle: float = ..., flags: int = ...
107+
) -> NDArray[np.float64]: ...
108+
109+
class FT2Image: # TODO: When updating mypy>=1.4, subclass from Buffer.
110+
def __init__(self, width: float, height: float) -> None: ...
111+
def draw_rect(self, x0: float, y0: float, x1: float, y1: float) -> None: ...
112+
def draw_rect_filled(self, x0: float, y0: float, x1: float, y1: float) -> None: ...
113+
114+
class Glyph:
115+
width: int
116+
height: int
117+
horiBearingX: int
118+
horiBearingY: int
119+
horiAdvance: int
120+
linearHoriAdvance: int
121+
vertBearingX: int
122+
vertBearingY: int
123+
vertAdvance: int
124+
125+
def bbox(self) -> tuple[int, int, int, int]: ...

lib/matplotlib/tests/test_font_manager.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ def test_font_priority():
2525
with rc_context(rc={
2626
'font.sans-serif':
2727
['cmmi10', 'Bitstream Vera Sans']}):
28-
font = findfont(FontProperties(family=["sans-serif"]))
29-
assert Path(font).name == 'cmmi10.ttf'
28+
fontfile = findfont(FontProperties(family=["sans-serif"]))
29+
assert Path(fontfile).name == 'cmmi10.ttf'
3030

3131
# Smoketest get_charmap, which isn't used internally anymore
32-
font = get_font(font)
32+
font = get_font(fontfile)
3333
cmap = font.get_charmap()
3434
assert len(cmap) == 131
3535
assert cmap[8729] == 30
@@ -148,7 +148,7 @@ def test_find_invalid(tmpdir):
148148
# Not really public, but get_font doesn't expose non-filename constructor.
149149
from matplotlib.ft2font import FT2Font
150150
with pytest.raises(TypeError, match='font file or a binary-mode file'):
151-
FT2Font(StringIO())
151+
FT2Font(StringIO()) # type: ignore[arg-type]
152152

153153

154154
@pytest.mark.skipif(sys.platform != 'linux' or not has_fclist,

lib/matplotlib/tests/test_ft2font.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ def test_fallback_errors():
1414

1515
with pytest.raises(TypeError, match="Fallback list must be a list"):
1616
# failing to be a list will fail before the 0
17-
ft2font.FT2Font(file_name, _fallback_list=(0,))
17+
ft2font.FT2Font(file_name, _fallback_list=(0,)) # type: ignore[arg-type]
1818

1919
with pytest.raises(
2020
TypeError, match="Fallback fonts must be FT2Font objects."
2121
):
22-
ft2font.FT2Font(file_name, _fallback_list=[0])
22+
ft2font.FT2Font(file_name, _fallback_list=[0]) # type: ignore[list-item]
2323

2424

2525
def test_ft2font_positive_hinting_factor():

lib/matplotlib/tests/test_mathtext.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
font_tests: list[None | str] = []
188188
for fonts, chars in font_test_specs:
189189
if fonts is None:
190-
font_tests.extend([None] * chars) # type: ignore
190+
font_tests.extend([None] * chars)
191191
else:
192192
wrapper = ''.join([
193193
' '.join(fonts),

src/ft2font_wrapper.cpp

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -331,46 +331,51 @@ const char *PyFT2Font_init__doc__ =
331331
"Parameters\n"
332332
"----------\n"
333333
"filename : str or file-like\n"
334-
" The source of the font data in a format (ttf or ttc) that FreeType can read\n\n"
334+
" The source of the font data in a format (ttf or ttc) that FreeType can read\n"
335+
"\n"
335336
"hinting_factor : int, optional\n"
336337
" Must be positive. Used to scale the hinting in the x-direction\n"
337338
"_fallback_list : list of FT2Font, optional\n"
338-
" A list of FT2Font objects used to find missing glyphs.\n\n"
339+
" A list of FT2Font objects used to find missing glyphs.\n"
340+
"\n"
339341
" .. warning::\n"
340-
" This API is both private and provisional: do not use it directly\n\n"
342+
" This API is both private and provisional: do not use it directly\n"
343+
"\n"
341344
"_kerning_factor : int, optional\n"
342-
" Used to adjust the degree of kerning.\n\n"
345+
" Used to adjust the degree of kerning.\n"
346+
"\n"
343347
" .. warning::\n"
344-
" This API is private: do not use it directly\n\n"
348+
" This API is private: do not use it directly\n"
349+
"\n"
345350
"Attributes\n"
346351
"----------\n"
347-
"num_faces\n"
352+
"num_faces : int\n"
348353
" Number of faces in file.\n"
349354
"face_flags, style_flags : int\n"
350355
" Face and style flags; see the ft2font constants.\n"
351-
"num_glyphs\n"
356+
"num_glyphs : int\n"
352357
" Number of glyphs in the face.\n"
353-
"family_name, style_name\n"
358+
"family_name, style_name : str\n"
354359
" Face family and style name.\n"
355-
"num_fixed_sizes\n"
360+
"num_fixed_sizes : int\n"
356361
" Number of bitmap in the face.\n"
357-
"scalable\n"
362+
"scalable : bool\n"
358363
" Whether face is scalable; attributes after this one are only\n"
359364
" defined for scalable faces.\n"
360-
"bbox\n"
365+
"bbox : tuple[int, int, int, int]\n"
361366
" Face global bounding box (xmin, ymin, xmax, ymax).\n"
362-
"units_per_EM\n"
367+
"units_per_EM : int\n"
363368
" Number of font units covered by the EM.\n"
364-
"ascender, descender\n"
369+
"ascender, descender : int\n"
365370
" Ascender and descender in 26.6 units.\n"
366-
"height\n"
371+
"height : int\n"
367372
" Height in 26.6 units; used to compute a default line spacing\n"
368373
" (baseline-to-baseline distance).\n"
369-
"max_advance_width, max_advance_height\n"
374+
"max_advance_width, max_advance_height : int\n"
370375
" Maximum horizontal and vertical cursor advance for all glyphs.\n"
371-
"underline_position, underline_thickness\n"
376+
"underline_position, underline_thickness : int\n"
372377
" Vertical position and thickness of the underline bar.\n"
373-
"postscript_name\n"
378+
"postscript_name : str\n"
374379
" PostScript name of the font.\n";
375380

376381
static int PyFT2Font_init(PyFT2Font *self, PyObject *args, PyObject *kwds)
@@ -627,7 +632,7 @@ static PyObject *PyFT2Font_get_fontmap(PyFT2Font *self, PyObject *args, PyObject
627632

628633

629634
const char *PyFT2Font_set_text__doc__ =
630-
"set_text(self, string, angle, flags=32)\n"
635+
"set_text(self, string, angle=0.0, flags=32)\n"
631636
"--\n\n"
632637
"Set the text *string* and *angle*.\n"
633638
"*flags* can be a bitwise-or of the LOAD_XXX constants;\n"

0 commit comments

Comments
 (0)