|
9 | 9 |
|
10 | 10 | #include "ft2font.h" |
11 | 11 |
|
| 12 | +#include <memory> |
| 13 | +#include <mutex> |
12 | 14 | #include <set> |
13 | 15 | #include <sstream> |
14 | 16 | #include <unordered_map> |
@@ -288,6 +290,18 @@ class PyFT2Font final : public FT2Font |
288 | 290 | FT_StreamRec stream; |
289 | 291 | py::list fallbacks; |
290 | 292 |
|
| 293 | + // Serializes this object's mutating Python-facing methods so that concurrent |
| 294 | + // calls on the SAME FT2Font (under free-threading / py::mod_gil_not_used) |
| 295 | + // cannot corrupt its FreeType glyph slot, glyph list or image buffer. It is |
| 296 | + // taken ONLY at the wrapper boundary -- never inside FT2Font's C++ methods, |
| 297 | + // which also run during construction and fallback recursion -- so a single |
| 298 | + // lock is held per top-level call and it never nests (deadlock-free by |
| 299 | + // construction). Recursive to tolerate re-entry via Python callbacks such as |
| 300 | + // ft_glyph_warn(). Contract: one FT2Font (together with its fallback graph) |
| 301 | + // is used by a single thread at a time; matplotlib enforces this via the |
| 302 | + // thread-keyed cache in font_manager._get_font. |
| 303 | + std::recursive_mutex mutex; |
| 304 | + |
291 | 305 | ~PyFT2Font() |
292 | 306 | { |
293 | 307 | // Because destructors are called from subclass up to base class, we need to |
@@ -432,7 +446,9 @@ PyFT2Font_init(FT_Library ft2Library, py::object filename, |
432 | 446 | std::back_inserter(fallback_fonts)); |
433 | 447 | } |
434 | 448 |
|
435 | | - auto self = new PyFT2Font(fallback_fonts, warn_if_used); |
| 449 | + // Own the object locally so it is released if any step below throws (e.g. a |
| 450 | + // corrupt font makes self->open() fail); pybind11 adopts it only on success. |
| 451 | + auto self = std::make_unique<PyFT2Font>(fallback_fonts, warn_if_used); |
436 | 452 | self->set_kerning_factor(*kerning_factor); |
437 | 453 |
|
438 | 454 | if (fallback_list) { |
@@ -476,7 +492,7 @@ PyFT2Font_init(FT_Library ft2Library, py::object filename, |
476 | 492 |
|
477 | 493 | self->open(ft2Library, open_args, face_index); |
478 | 494 |
|
479 | | - return self; |
| 495 | + return self.release(); |
480 | 496 | } |
481 | 497 |
|
482 | 498 | static py::object |
@@ -616,6 +632,7 @@ PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0 |
616 | 632 | std::optional<std::vector<std::string>> features = std::nullopt, |
617 | 633 | std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr) |
618 | 634 | { |
| 635 | + std::scoped_lock lock{self->mutex}; |
619 | 636 | std::vector<double> xys; |
620 | 637 |
|
621 | 638 | FT2Font::LanguageType languages; |
@@ -674,6 +691,7 @@ static PyGlyph * |
674 | 691 | PyFT2Font_load_char(PyFT2Font *self, long charcode, |
675 | 692 | LoadFlags flags = LoadFlags::FORCE_AUTOHINT) |
676 | 693 | { |
| 694 | + std::scoped_lock lock{self->mutex}; |
677 | 695 | bool fallback = true; |
678 | 696 | FT2Font *ft_object = nullptr; |
679 | 697 |
|
@@ -712,6 +730,7 @@ static PyGlyph * |
712 | 730 | PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index, |
713 | 731 | LoadFlags flags = LoadFlags::FORCE_AUTOHINT) |
714 | 732 | { |
| 733 | + std::scoped_lock lock{self->mutex}; |
715 | 734 | self->load_glyph(glyph_index, static_cast<FT_Int32>(flags)); |
716 | 735 |
|
717 | 736 | return PyGlyph_from_FT2Font(self); |
@@ -814,6 +833,7 @@ PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image, |
814 | 833 | int xd, int yd, |
815 | 834 | PyGlyph *glyph, bool antialiased = true) |
816 | 835 | { |
| 836 | + std::scoped_lock lock{self->mutex}; |
817 | 837 | self->draw_glyph_to_bitmap( |
818 | 838 | py::array_t<uint8_t, py::array::c_style>{image}, |
819 | 839 | xd, yd, glyph->glyphInd, antialiased); |
@@ -1338,6 +1358,7 @@ PyFT2Font_layout(PyFT2Font *self, std::u32string text, LoadFlags flags, |
1338 | 1358 | std::optional<std::vector<std::string>> features = std::nullopt, |
1339 | 1359 | std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr) |
1340 | 1360 | { |
| 1361 | + std::scoped_lock lock{self->mutex}; |
1341 | 1362 | const auto load_flags = static_cast<FT_Int32>(flags); |
1342 | 1363 |
|
1343 | 1364 | FT2Font::LanguageType languages; |
@@ -1584,7 +1605,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) |
1584 | 1605 | "face_index"_a=0, "_fallback_list"_a=py::none(), |
1585 | 1606 | "_kerning_factor"_a=py::none(), "_warn_if_used"_a=false, |
1586 | 1607 | PyFT2Font_init__doc__) |
1587 | | - .def("clear", &PyFT2Font::clear, PyFT2Font_clear__doc__) |
| 1608 | + .def("clear", |
| 1609 | + [](PyFT2Font& self) { |
| 1610 | + std::scoped_lock lock{self.mutex}; |
| 1611 | + self.clear(); |
| 1612 | + }, |
| 1613 | + PyFT2Font_clear__doc__) |
1588 | 1614 | .def("set_size", &PyFT2Font::set_size, "ptsize"_a, "dpi"_a, |
1589 | 1615 | PyFT2Font_set_size__doc__) |
1590 | 1616 | .def("_set_transform", &PyFT2Font::_set_transform, "matrix"_a, "delta"_a, |
@@ -1615,7 +1641,11 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) |
1615 | 1641 | .def("get_bitmap_offset", &PyFT2Font::get_bitmap_offset, |
1616 | 1642 | PyFT2Font_get_bitmap_offset__doc__) |
1617 | 1643 | .def("get_descent", &PyFT2Font::get_descent, PyFT2Font_get_descent__doc__) |
1618 | | - .def("draw_glyphs_to_bitmap", &PyFT2Font::draw_glyphs_to_bitmap, |
| 1644 | + .def("draw_glyphs_to_bitmap", |
| 1645 | + [](PyFT2Font& self, bool antialiased) { |
| 1646 | + std::scoped_lock lock{self.mutex}; |
| 1647 | + self.draw_glyphs_to_bitmap(antialiased); |
| 1648 | + }, |
1619 | 1649 | py::kw_only(), "antialiased"_a=true, |
1620 | 1650 | PyFT2Font_draw_glyphs_to_bitmap__doc__) |
1621 | 1651 | .def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap, |
|
0 commit comments