Skip to content

Commit 5cfa9b0

Browse files
committed
Expose face index when loading fonts
This enables loading a non-initial font from collections (`.ttc` files). Currently exposed for `FT2Font`, only.
1 parent a6ac58f commit 5cfa9b0

File tree

5 files changed

+40
-7
lines changed

5 files changed

+40
-7
lines changed

lib/matplotlib/ft2font.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class FT2Font(Buffer):
198198
filename: str | bytes | PathLike | BinaryIO,
199199
hinting_factor: int = ...,
200200
*,
201+
face_index: int = ...,
201202
_fallback_list: list[FT2Font] | None = ...,
202203
_kerning_factor: int | None = ...
203204
) -> None: ...
@@ -261,6 +262,8 @@ class FT2Font(Buffer):
261262
@property
262263
def face_flags(self) -> FaceFlags: ...
263264
@property
265+
def face_index(self) -> int: ...
266+
@property
264267
def family_name(self) -> str: ...
265268
@property
266269
def fname(self) -> str | bytes: ...

lib/matplotlib/tests/test_ft2font.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,25 @@ def test_ft2font_invalid_args(tmp_path):
199199
ft2font.FT2Font(file, _kerning_factor=123)
200200

201201

202+
@pytest.mark.parametrize('name, size, skippable',
203+
[('DejaVu Sans', 1, False), ('WenQuanYi Zen Hei', 3, True)])
204+
def test_ft2font_face_index(name, size, skippable):
205+
try:
206+
file = fm.findfont(name, fallback_to_default=False)
207+
except ValueError:
208+
if skippable:
209+
pytest.skip(r'Font {name} may be missing')
210+
raise
211+
for index in range(size):
212+
font = ft2font.FT2Font(file, face_index=index)
213+
assert font.num_faces >= size
214+
assert font.face_index == index
215+
with pytest.raises(ValueError, match='must be between'): # out of bounds for spec
216+
ft2font.FT2Font(file, face_index=0x1ffff)
217+
with pytest.raises(RuntimeError, match='invalid argument'): # invalid for this font
218+
ft2font.FT2Font(file, face_index=0xff)
219+
220+
202221
def test_ft2font_clear():
203222
file = fm.findfont('DejaVu Sans')
204223
font = ft2font.FT2Font(file)

src/ft2font.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ FT2Font::~FT2Font()
222222
close();
223223
}
224224

225-
void FT2Font::open(FT_Open_Args &open_args)
225+
void FT2Font::open(FT_Long face_index, FT_Open_Args &open_args)
226226
{
227-
FT_CHECK(FT_Open_Face, _ft2Library, &open_args, 0, &face);
227+
FT_CHECK(FT_Open_Face, _ft2Library, &open_args, face_index, &face);
228228
if (open_args.stream != nullptr) {
229229
face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM;
230230
}

src/ft2font.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class FT2Font
107107
FT2Font(long hinting_factor, std::vector<FT2Font *> &fallback_list,
108108
bool warn_if_used);
109109
virtual ~FT2Font();
110-
void open(FT_Open_Args &open_args);
110+
void open(FT_Long face_index, FT_Open_Args &open_args);
111111
void close();
112112
void clear();
113113
void set_size(double ptsize, double dpi);

src/ft2font_wrapper.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,9 @@ const char *PyFT2Font_init__doc__ = R"""(
430430
hinting_factor : int, optional
431431
Must be positive. Used to scale the hinting in the x-direction.
432432
433+
face_index : int, optional
434+
The index of the face in the font file to load.
435+
433436
_fallback_list : list of FT2Font, optional
434437
A list of FT2Font objects used to find missing glyphs.
435438
@@ -444,7 +447,7 @@ const char *PyFT2Font_init__doc__ = R"""(
444447
)""";
445448

446449
static PyFT2Font *
447-
PyFT2Font_init(py::object filename, long hinting_factor = 8,
450+
PyFT2Font_init(py::object filename, long hinting_factor = 8, FT_Long face_index = 0,
448451
std::optional<std::vector<PyFT2Font *>> fallback_list = std::nullopt,
449452
std::optional<int> kerning_factor = std::nullopt,
450453
bool warn_if_used = false)
@@ -460,6 +463,10 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8,
460463
kerning_factor = 0;
461464
}
462465

466+
if (face_index < 0 || face_index > 0xffff) {
467+
throw std::range_error("face_index must be between 0 and 65535, inclusive");
468+
}
469+
463470
std::vector<FT2Font *> fallback_fonts;
464471
if (fallback_list) {
465472
// go through fallbacks to add them to our lists
@@ -509,7 +516,7 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8,
509516
self->stream.close = nullptr;
510517
}
511518

512-
self->open(open_args);
519+
self->open(face_index, open_args);
513520

514521
return self;
515522
}
@@ -1546,7 +1553,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15461553
auto cls = py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
15471554
PyFT2Font__doc__)
15481555
.def(py::init(&PyFT2Font_init),
1549-
"filename"_a, "hinting_factor"_a=8, py::kw_only(),
1556+
"filename"_a, "hinting_factor"_a=8, py::kw_only(), "face_index"_a=0,
15501557
"_fallback_list"_a=py::none(), "_kerning_factor"_a=py::none(),
15511558
"_warn_if_used"_a=false,
15521559
PyFT2Font_init__doc__)
@@ -1622,8 +1629,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16221629
}, "PostScript name of the font.")
16231630
.def_property_readonly(
16241631
"num_faces", [](PyFT2Font *self) {
1625-
return self->get_face()->num_faces;
1632+
return self->get_face()->num_faces & 0xffff;
16261633
}, "Number of faces in file.")
1634+
.def_property_readonly(
1635+
"face_index", [](PyFT2Font *self) {
1636+
return self->get_face()->face_index;
1637+
}, "The index of the font in the file.")
16271638
.def_property_readonly(
16281639
"family_name", [](PyFT2Font *self) {
16291640
if (const char *name = self->get_face()->family_name) {

0 commit comments

Comments
 (0)