Skip to content

Commit c352088

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 9a7c2d8 commit c352088

File tree

5 files changed

+40
-9
lines changed

5 files changed

+40
-9
lines changed

lib/matplotlib/ft2font.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ class FT2Font(Buffer):
191191
filename: str | bytes | PathLike | BinaryIO,
192192
hinting_factor: int = ...,
193193
*,
194+
face_index: int = ...,
194195
_fallback_list: list[FT2Font] | None = ...,
195196
_kerning_factor: int | None = ...
196197
) -> None: ...
@@ -248,6 +249,8 @@ class FT2Font(Buffer):
248249
@property
249250
def face_flags(self) -> FaceFlags: ...
250251
@property
252+
def face_index(self) -> int: ...
253+
@property
251254
def family_name(self) -> str: ...
252255
@property
253256
def fname(self) -> str | bytes: ...

lib/matplotlib/tests/test_ft2font.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,24 @@ def test_ft2font_invalid_args(tmp_path):
198198
ft2font.FT2Font(file, _kerning_factor=123)
199199

200200

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

src/ft2font.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,7 @@ FT2Font::get_path(std::vector<double> &vertices, std::vector<unsigned char> &cod
207207
codes.push_back(CLOSEPOLY);
208208
}
209209

210-
FT2Font::FT2Font(FT_Open_Args &open_args,
211-
long hinting_factor_,
210+
FT2Font::FT2Font(FT_Long face_index, FT_Open_Args &open_args, long hinting_factor_,
212211
std::vector<FT2Font *> &fallback_list,
213212
FT2Font::WarnFunc warn, bool warn_if_used)
214213
: ft_glyph_warn(warn), warn_if_used(warn_if_used), image({1, 1}), face(nullptr),
@@ -217,7 +216,7 @@ FT2Font::FT2Font(FT_Open_Args &open_args,
217216
kerning_factor(0)
218217
{
219218
clear();
220-
FT_CHECK(FT_Open_Face, _ft2Library, &open_args, 0, &face);
219+
FT_CHECK(FT_Open_Face, _ft2Library, &open_args, face_index, &face);
221220
if (open_args.stream != nullptr) {
222221
face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM;
223222
}

src/ft2font.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class FT2Font
9999
typedef void (*WarnFunc)(FT_ULong charcode, std::set<FT_String*> family_names);
100100

101101
public:
102-
FT2Font(FT_Open_Args &open_args, long hinting_factor,
102+
FT2Font(FT_Long face_index, FT_Open_Args &open_args, long hinting_factor,
103103
std::vector<FT2Font *> &fallback_list,
104104
WarnFunc warn, bool warn_if_used);
105105
virtual ~FT2Font();

src/ft2font_wrapper.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ const char *PyFT2Font_init__doc__ = R"""(
426426
hinting_factor : int, optional
427427
Must be positive. Used to scale the hinting in the x-direction.
428428
429+
face_index : int, optional
430+
The index of the face in the font file to load.
431+
429432
_fallback_list : list of FT2Font, optional
430433
A list of FT2Font objects used to find missing glyphs.
431434
@@ -440,7 +443,7 @@ const char *PyFT2Font_init__doc__ = R"""(
440443
)""";
441444

442445
static PyFT2Font *
443-
PyFT2Font_init(py::object filename, long hinting_factor = 8,
446+
PyFT2Font_init(py::object filename, long hinting_factor = 8, FT_Long face_index = 0,
444447
std::optional<std::vector<PyFT2Font *>> fallback_list = std::nullopt,
445448
std::optional<int> kerning_factor = std::nullopt,
446449
bool warn_if_used = false)
@@ -456,6 +459,10 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8,
456459
kerning_factor = 0;
457460
}
458461

462+
if (face_index < 0 || face_index >= 1<<16) {
463+
throw std::range_error("face_index must be between 0 and 65535, inclusive");
464+
}
465+
459466
PyFT2Font *self = new PyFT2Font();
460467
self->x = nullptr;
461468
memset(&self->stream, 0, sizeof(FT_StreamRec));
@@ -502,8 +509,8 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8,
502509
self->stream.close = nullptr;
503510
}
504511

505-
self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn,
506-
warn_if_used);
512+
self->x = new FT2Font(face_index, open_args, hinting_factor, fallback_fonts,
513+
ft_glyph_warn, warn_if_used);
507514

508515
self->x->set_kerning_factor(*kerning_factor);
509516

@@ -1609,7 +1616,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16091616
auto cls = py::class_<PyFT2Font>(m, "FT2Font", py::is_final(), py::buffer_protocol(),
16101617
PyFT2Font__doc__)
16111618
.def(py::init(&PyFT2Font_init),
1612-
"filename"_a, "hinting_factor"_a=8, py::kw_only(),
1619+
"filename"_a, "hinting_factor"_a=8, py::kw_only(), "face_index"_a=0,
16131620
"_fallback_list"_a=py::none(), "_kerning_factor"_a=py::none(),
16141621
"_warn_if_used"_a=false,
16151622
PyFT2Font_init__doc__)
@@ -1682,8 +1689,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16821689
}, "PostScript name of the font.")
16831690
.def_property_readonly(
16841691
"num_faces", [](PyFT2Font *self) {
1685-
return self->x->get_face()->num_faces;
1692+
return self->x->get_face()->num_faces & 0xffff;
16861693
}, "Number of faces in file.")
1694+
.def_property_readonly(
1695+
"face_index", [](PyFT2Font *self) {
1696+
return self->x->get_face()->face_index;
1697+
}, "The index of the font in the file.")
16871698
.def_property_readonly(
16881699
"family_name", [](PyFT2Font *self) {
16891700
if (const char *name = self->x->get_face()->family_name) {

0 commit comments

Comments
 (0)