diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index b8f054447de0..8252fb25ad39 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -29,6 +29,9 @@ matplotlib\.ticker\.LogitLocator\.nonsingular # Stdlib/Enum considered inconsistent (no fault of ours, I don't think) matplotlib\.backend_bases\._Mode\.__new__ +# pybind11 internals +matplotlib\..*\.__pybind11_native_enum__ + # 3.6 Pending deprecations matplotlib\.figure\.Figure\.set_constrained_layout matplotlib\.figure\.Figure\.set_constrained_layout_pads diff --git a/meson.build b/meson.build index 24a09821a047..f1cf20995be3 100644 --- a/meson.build +++ b/meson.build @@ -46,7 +46,7 @@ py_mod = import('python') py3 = py_mod.find_installation(pure: false) py3_dep = py3.dependency() -pybind11_dep = dependency('pybind11', version: '>=2.13.2') +pybind11_dep = dependency('pybind11', version: '>=3') subdir('extern') subdir('src') diff --git a/pyproject.toml b/pyproject.toml index 49ffd0f42478..8ee0d5ef3eb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ requires = [ # meson-python 0.17.x breaks symlinks in sdists. You can remove this pin if # you really need it and aren't using an sdist. "meson-python>=0.13.2,!=0.17.*", - "pybind11>=2.13.2,!=2.13.3", + "pybind11>=3", # setuptools_scm 10 breaks versioning in editable installs. You can remove this pin # if you're a downstream distributor just building wheels or your equivalent. "setuptools_scm>=7,<10", @@ -80,7 +80,7 @@ build = [ # Should be the same as `[build-system] requires` above. "meson-python>=0.13.1,!=0.17.*", - "pybind11>=2.13.2,!=2.13.3", + "pybind11>=3", "setuptools_scm>=7,<10", # Not required by us but setuptools_scm without a version, so _if_ # installed, then setuptools_scm 8 requires at least this version. diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 3db347854da6..7df58179fc4b 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -1,6 +1,10 @@ #include #include #include +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +#include +#endif + #include "mplutils.h" #include "py_converters.h" #include "_backend_agg.h" @@ -185,9 +189,14 @@ PyRendererAgg_draw_gouraud_triangles(RendererAgg *self, self->draw_gouraud_triangles(gc, points, colors, trans); } +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +PYBIND11_MODULE(_backend_agg, m, + py::mod_gil_not_used(), py::multiple_interpreters::per_interpreter_gil()) +#else PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used()) +#endif { - py::class_(m, "RendererAgg", py::buffer_protocol()) + py::classh(m, "RendererAgg", py::buffer_protocol()) .def(py::init(), "width"_a, "height"_a, "dpi"_a) @@ -237,7 +246,7 @@ PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used()) return py::buffer_info(renderer->pixBuffer, shape, strides); }); - py::class_(m, "BufferRegion", py::buffer_protocol()) + py::classh(m, "BufferRegion", py::buffer_protocol()) // BufferRegion is not constructible from Python, thus no py::init is added. .def("set_x", &PyBufferRegion_set_x) .def("set_y", &PyBufferRegion_set_y) diff --git a/src/_enums.h b/src/_enums.h deleted file mode 100644 index e607b93f50f2..000000000000 --- a/src/_enums.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef MPL_ENUMS_H -#define MPL_ENUMS_H - -#include - -// Extension for pybind11: Pythonic enums. -// This allows creating classes based on ``enum.*`` types. -// This code was copied from mplcairo, with some slight tweaks. -// The API is: -// -// - P11X_DECLARE_ENUM(py_name: str, py_base_cls: str, ...: {str, enum value}): -// py_name: The name to expose in the module. -// py_base_cls: The name of the enum base class to use. -// ...: The enum name/value pairs to expose. -// -// Use this macro to declare an enum and its values. -// -// - py11x::bind_enums(m: pybind11::module): -// m: The module to use to register the enum classes. -// -// Place this in PYBIND11_MODULE to register the enums declared by P11X_DECLARE_ENUM. - -// a1 includes the opening brace and a2 the closing brace. -// This definition is compatible with older compiler versions compared to -// #define P11X_ENUM_TYPE(...) decltype(std::map{std::pair __VA_ARGS__})::mapped_type -#define P11X_ENUM_TYPE(a1, a2, ...) decltype(std::pair a1, a2)::second_type - -#define P11X_CAT2(a, b) a##b -#define P11X_CAT(a, b) P11X_CAT2(a, b) - -namespace p11x { - namespace { - namespace py = pybind11; - - // Holder is (py_base_cls, [(name, value), ...]) before module init; - // converted to the Python class object after init. - auto enums = std::unordered_map{}; - - auto bind_enums(py::module mod) -> void - { - for (auto& [py_name, spec]: enums) { - auto const& [py_base_cls, pairs] = - spec.cast>(); - mod.attr(py::cast(py_name)) = spec = - py::module::import("enum").attr(py_base_cls.c_str())( - py_name, pairs, py::arg("module") = mod.attr("__name__")); - } - } - } -} - -// Immediately converting the args to a vector outside of the lambda avoids -// name collisions. -#define P11X_DECLARE_ENUM(py_name, py_base_cls, ...) \ - namespace p11x { \ - namespace { \ - [[maybe_unused]] auto const P11X_CAT(enum_placeholder_, __COUNTER__) = \ - [](auto args) { \ - py::gil_scoped_acquire gil; \ - using int_t = std::underlying_type_t; \ - auto pairs = std::vector>{}; \ - for (auto& [k, v]: args) { \ - pairs.emplace_back(k, int_t(v)); \ - } \ - p11x::enums[py_name] = pybind11::cast(std::pair{py_base_cls, pairs}); \ - return 0; \ - } (std::vector{std::pair __VA_ARGS__}); \ - } \ - } \ - namespace pybind11::detail { \ - template<> struct type_caster { \ - using type = P11X_ENUM_TYPE(__VA_ARGS__); \ - static_assert(std::is_enum_v, "Not an enum"); \ - PYBIND11_TYPE_CASTER(type, _(py_name)); \ - bool load(handle src, bool) { \ - auto cls = p11x::enums.at(py_name); \ - PyObject* tmp = nullptr; \ - if (pybind11::isinstance(src, cls) \ - && (tmp = PyNumber_Index(src.attr("value").ptr()))) { \ - auto ival = PyLong_AsLong(tmp); \ - value = decltype(value)(ival); \ - Py_DECREF(tmp); \ - return !(ival == -1 && PyErr_Occurred()); \ - } else { \ - return false; \ - } \ - } \ - static handle cast(decltype(value) obj, return_value_policy, handle) { \ - auto cls = p11x::enums.at(py_name); \ - return cls(std::underlying_type_t(obj)).inc_ref(); \ - } \ - }; \ - } - -#endif /* MPL_ENUMS_H */ diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 6b6dc61dc1c6..c151af9e92ea 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -1,5 +1,9 @@ #include +#include #include +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +#include +#endif #include @@ -287,9 +291,14 @@ calculate_rms_and_diff(py::array_t expected_image, } +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +PYBIND11_MODULE(_image, m, + py::mod_gil_not_used(), py::multiple_interpreters::per_interpreter_gil()) +#else PYBIND11_MODULE(_image, m, py::mod_gil_not_used()) +#endif { - py::enum_(m, "_InterpolationType") + py::native_enum(m, "_InterpolationType", "enum.Enum") .value("NEAREST", NEAREST) .value("BILINEAR", BILINEAR) .value("BICUBIC", BICUBIC) @@ -307,7 +316,8 @@ PYBIND11_MODULE(_image, m, py::mod_gil_not_used()) .value("SINC", SINC) .value("LANCZOS", LANCZOS) .value("BLACKMAN", BLACKMAN) - .export_values(); + .export_values() + .finalize(); m.def("resample", &image_resample, "input_array"_a, diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index 802189c428d3..d0e02151429c 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -1,5 +1,8 @@ #include #include +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +#include +#endif #include #include @@ -303,7 +306,12 @@ Py_is_sorted_and_has_non_nan(py::object obj) return result; } +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +PYBIND11_MODULE(_path, m, + py::mod_gil_not_used(), py::multiple_interpreters::per_interpreter_gil()) +#else PYBIND11_MODULE(_path, m, py::mod_gil_not_used()) +#endif { m.def("point_in_path", &Py_point_in_path, "x"_a, "y"_a, "radius"_a, "path"_a, "trans"_a); diff --git a/src/_qhull_wrapper.cpp b/src/_qhull_wrapper.cpp index 509b48ecb3e1..49632843fc53 100644 --- a/src/_qhull_wrapper.cpp +++ b/src/_qhull_wrapper.cpp @@ -7,6 +7,9 @@ */ #include #include +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +#include +#endif #ifdef _MSC_VER /* The Qhull header does not declare this as extern "C", but only MSVC seems to @@ -284,7 +287,12 @@ delaunay(const CoordArray& x, const CoordArray& y, int verbose) return delaunay_impl(npoints, x.data(), y.data(), verbose == 0); } +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +PYBIND11_MODULE(_qhull, m, + py::mod_gil_not_used(), py::multiple_interpreters::per_interpreter_gil()) +#else PYBIND11_MODULE(_qhull, m, py::mod_gil_not_used()) +#endif { m.doc() = "Computing Delaunay triangulations.\n"; diff --git a/src/ft2font.cpp b/src/ft2font.cpp index 3e53a87343a6..4edb05649021 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -16,8 +16,6 @@ #define M_PI 3.14159265358979323846264338328 #endif -FT_Library _ft2Library; - FT2Image::FT2Image(unsigned long width, unsigned long height) : m_buffer((unsigned char *)calloc(width * height, 1)), m_width(width), m_height(height) { @@ -195,9 +193,9 @@ FT2Font::~FT2Font() close(); } -void FT2Font::open(FT_Open_Args &open_args, FT_Long face_index) +void FT2Font::open(FT_Library ft2Library, FT_Open_Args &open_args, FT_Long face_index) { - FT_CHECK(FT_Open_Face, _ft2Library, &open_args, face_index, &face); + FT_CHECK(FT_Open_Face, ft2Library, &open_args, face_index, &face); if (open_args.stream != nullptr) { face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM; } diff --git a/src/ft2font.h b/src/ft2font.h index 09e028f3404c..383dc0915bbc 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -97,8 +97,6 @@ class FT2Image FT2Image &operator=(const FT2Image &); }; -extern FT_Library _ft2Library; - class FT2Font { public: @@ -107,7 +105,7 @@ class FT2Font FT2Font(std::vector &fallback_list, bool warn_if_used); virtual ~FT2Font(); - void open(FT_Open_Args &open_args, FT_Long face_index); + void open(FT_Library ft2Library, FT_Open_Args &open_args, FT_Long face_index); void close(); void clear(); void set_size(double ptsize, double dpi); diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index bef60445e26e..9cc30cffdb8f 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -1,10 +1,13 @@ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include +#include #include #include +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +#include +#endif #include "ft2font.h" -#include "_enums.h" #include #include @@ -26,13 +29,6 @@ const char *Kerning__doc__ = R"""( .. versionadded:: 3.10 )"""; -P11X_DECLARE_ENUM( - "Kerning", "Enum", - {"DEFAULT", FT_KERNING_DEFAULT}, - {"UNFITTED", FT_KERNING_UNFITTED}, - {"UNSCALED", FT_KERNING_UNSCALED}, -); - const char *FaceFlags__doc__ = R"""( Flags returned by `FT2Font.face_flags`. @@ -79,29 +75,6 @@ enum class FaceFlags : FT_Long { #undef DECLARE_FLAG }; -P11X_DECLARE_ENUM( - "FaceFlags", "Flag", - {"SCALABLE", FaceFlags::SCALABLE}, - {"FIXED_SIZES", FaceFlags::FIXED_SIZES}, - {"FIXED_WIDTH", FaceFlags::FIXED_WIDTH}, - {"SFNT", FaceFlags::SFNT}, - {"HORIZONTAL", FaceFlags::HORIZONTAL}, - {"VERTICAL", FaceFlags::VERTICAL}, - {"KERNING", FaceFlags::KERNING}, - {"FAST_GLYPHS", FaceFlags::FAST_GLYPHS}, - {"MULTIPLE_MASTERS", FaceFlags::MULTIPLE_MASTERS}, - {"GLYPH_NAMES", FaceFlags::GLYPH_NAMES}, - {"EXTERNAL_STREAM", FaceFlags::EXTERNAL_STREAM}, - {"HINTER", FaceFlags::HINTER}, - {"CID_KEYED", FaceFlags::CID_KEYED}, - {"TRICKY", FaceFlags::TRICKY}, - {"COLOR", FaceFlags::COLOR}, - {"VARIATION", FaceFlags::VARIATION}, - {"SVG", FaceFlags::SVG}, - {"SBIX", FaceFlags::SBIX}, - {"SBIX_OVERLAY", FaceFlags::SBIX_OVERLAY}, -); - const char *LoadFlags__doc__ = R"""( Flags for `FT2Font.load_char`, `FT2Font.load_glyph`, and `FT2Font.set_text`. @@ -150,36 +123,6 @@ enum class LoadFlags : FT_Int32 { #undef DECLARE_FLAG }; -P11X_DECLARE_ENUM( - "LoadFlags", "Flag", - {"DEFAULT", LoadFlags::DEFAULT}, - {"NO_SCALE", LoadFlags::NO_SCALE}, - {"NO_HINTING", LoadFlags::NO_HINTING}, - {"RENDER", LoadFlags::RENDER}, - {"NO_BITMAP", LoadFlags::NO_BITMAP}, - {"VERTICAL_LAYOUT", LoadFlags::VERTICAL_LAYOUT}, - {"FORCE_AUTOHINT", LoadFlags::FORCE_AUTOHINT}, - {"CROP_BITMAP", LoadFlags::CROP_BITMAP}, - {"PEDANTIC", LoadFlags::PEDANTIC}, - {"IGNORE_GLOBAL_ADVANCE_WIDTH", LoadFlags::IGNORE_GLOBAL_ADVANCE_WIDTH}, - {"NO_RECURSE", LoadFlags::NO_RECURSE}, - {"IGNORE_TRANSFORM", LoadFlags::IGNORE_TRANSFORM}, - {"MONOCHROME", LoadFlags::MONOCHROME}, - {"LINEAR_DESIGN", LoadFlags::LINEAR_DESIGN}, - {"NO_AUTOHINT", LoadFlags::NO_AUTOHINT}, - {"COLOR", LoadFlags::COLOR}, - {"COMPUTE_METRICS", LoadFlags::COMPUTE_METRICS}, - {"BITMAP_METRICS_ONLY", LoadFlags::BITMAP_METRICS_ONLY}, - {"NO_SVG", LoadFlags::NO_SVG}, - // These must be unique, but the others can be OR'd together; I don't know if - // there's any way to really enforce that. - {"TARGET_NORMAL", LoadFlags::TARGET_NORMAL}, - {"TARGET_LIGHT", LoadFlags::TARGET_LIGHT}, - {"TARGET_MONO", LoadFlags::TARGET_MONO}, - {"TARGET_LCD", LoadFlags::TARGET_LCD}, - {"TARGET_LCD_V", LoadFlags::TARGET_LCD_V}, -); - const char *RenderMode__doc__ = R"""( Render modes. @@ -189,16 +132,6 @@ const char *RenderMode__doc__ = R"""( .. versionadded:: 3.10 )"""; -P11X_DECLARE_ENUM( - "RenderMode", "Enum", - {"NORMAL", FT_RENDER_MODE_NORMAL}, - {"LIGHT", FT_RENDER_MODE_LIGHT}, - {"MONO", FT_RENDER_MODE_MONO}, - {"LCD", FT_RENDER_MODE_LCD}, - {"LCD_V", FT_RENDER_MODE_LCD_V}, - {"SDF", FT_RENDER_MODE_SDF}, -); - const char *StyleFlags__doc__ = R"""( Flags returned by `FT2Font.style_flags`. @@ -216,13 +149,6 @@ enum class StyleFlags : FT_Long { #undef DECLARE_FLAG }; -P11X_DECLARE_ENUM( - "StyleFlags", "Flag", - {"NORMAL", StyleFlags::NORMAL}, - {"ITALIC", StyleFlags::ITALIC}, - {"BOLD", StyleFlags::BOLD}, -); - /********************************************************************** * FT2Image * */ @@ -252,19 +178,20 @@ const char *PyFT2Image_draw_rect_filled__doc__ = R"""( * */ struct PyPositionedBitmap { + FT_Library _ft2Library; FT_Int left, top; bool owning; FT_Bitmap bitmap; - PyPositionedBitmap(FT_GlyphSlot slot) : - left{slot->bitmap_left}, top{slot->bitmap_top}, owning{true} + PyPositionedBitmap(FT_Library ft2Library, FT_GlyphSlot slot) : + _ft2Library{ft2Library}, left{slot->bitmap_left}, top{slot->bitmap_top}, owning{true} { FT_Bitmap_Init(&bitmap); FT_CHECK(FT_Bitmap_Convert, _ft2Library, &slot->bitmap, &bitmap, 1); } - PyPositionedBitmap(FT_BitmapGlyph bg) : - left{bg->left}, top{bg->top}, owning{true} + PyPositionedBitmap(FT_Library ft2Library, FT_BitmapGlyph bg) : + _ft2Library{ft2Library}, left{bg->left}, top{bg->top}, owning{true} { FT_Bitmap_Init(&bitmap); FT_CHECK(FT_Bitmap_Convert, _ft2Library, &bg->bitmap, &bitmap, 1); @@ -273,7 +200,8 @@ struct PyPositionedBitmap { PyPositionedBitmap(PyPositionedBitmap& other) = delete; // Non-copyable. PyPositionedBitmap(PyPositionedBitmap&& other) : - left{other.left}, top{other.top}, owning{true}, bitmap{other.bitmap} + _ft2Library{other._ft2Library}, left{other.left}, top{other.top}, owning{true}, + bitmap{other.bitmap} { other.owning = false; // Prevent double deletion. } @@ -473,7 +401,8 @@ const char *PyFT2Font_init__doc__ = R"""( )"""; static PyFT2Font * -PyFT2Font_init(py::object filename, std::optional hinting_factor = std::nullopt, +PyFT2Font_init(FT_Library ft2Library, py::object filename, + std::optional hinting_factor = std::nullopt, FT_Long face_index = 0, std::optional> fallback_list = std::nullopt, std::optional kerning_factor = std::nullopt, @@ -545,7 +474,7 @@ PyFT2Font_init(py::object filename, std::optional hinting_factor = std::nu self->stream.close = nullptr; } - self->open(open_args, face_index); + self->open(ft2Library, open_args, face_index); return self; } @@ -1467,28 +1396,96 @@ PyFT2Font_layout(PyFT2Font *self, std::u32string text, LoadFlags flags, return items; } -/********************************************************************** - * Deprecations - * */ - +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +PYBIND11_MODULE(ft2font, m, + py::mod_gil_not_used(), py::multiple_interpreters::per_interpreter_gil()) +#else PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) +#endif { - if (FT_Init_FreeType(&_ft2Library)) { // initialize library + FT_Library ft2Library = nullptr; + + if (FT_Init_FreeType(&ft2Library)) { // initialize library throw std::runtime_error("Could not initialize the freetype2 library"); } FT_Int major, minor, patch; char version_string[64]; - FT_Library_Version(_ft2Library, &major, &minor, &patch); + FT_Library_Version(ft2Library, &major, &minor, &patch); snprintf(version_string, sizeof(version_string), "%d.%d.%d", major, minor, patch); - p11x::bind_enums(m); - p11x::enums["Kerning"].attr("__doc__") = Kerning__doc__; - p11x::enums["LoadFlags"].attr("__doc__") = LoadFlags__doc__; - p11x::enums["RenderMode"].attr("__doc__") = RenderMode__doc__; - p11x::enums["FaceFlags"].attr("__doc__") = FaceFlags__doc__; - p11x::enums["StyleFlags"].attr("__doc__") = StyleFlags__doc__; - - py::class_(m, "FT2Image", py::is_final(), py::buffer_protocol(), + py::native_enum(m, "Kerning", "enum.Enum", Kerning__doc__) + .value("DEFAULT", FT_KERNING_DEFAULT) + .value("UNFITTED", FT_KERNING_UNFITTED) + .value("UNSCALED", FT_KERNING_UNSCALED) + .finalize(); + + py::native_enum(m, "LoadFlags", "enum.Flag", LoadFlags__doc__) + .value("DEFAULT", LoadFlags::DEFAULT) + .value("NO_SCALE", LoadFlags::NO_SCALE) + .value("NO_HINTING", LoadFlags::NO_HINTING) + .value("RENDER", LoadFlags::RENDER) + .value("NO_BITMAP", LoadFlags::NO_BITMAP) + .value("VERTICAL_LAYOUT", LoadFlags::VERTICAL_LAYOUT) + .value("FORCE_AUTOHINT", LoadFlags::FORCE_AUTOHINT) + .value("CROP_BITMAP", LoadFlags::CROP_BITMAP) + .value("PEDANTIC", LoadFlags::PEDANTIC) + .value("IGNORE_GLOBAL_ADVANCE_WIDTH", LoadFlags::IGNORE_GLOBAL_ADVANCE_WIDTH) + .value("NO_RECURSE", LoadFlags::NO_RECURSE) + .value("IGNORE_TRANSFORM", LoadFlags::IGNORE_TRANSFORM) + .value("MONOCHROME", LoadFlags::MONOCHROME) + .value("LINEAR_DESIGN", LoadFlags::LINEAR_DESIGN) + .value("NO_AUTOHINT", LoadFlags::NO_AUTOHINT) + .value("COLOR", LoadFlags::COLOR) + .value("COMPUTE_METRICS", LoadFlags::COMPUTE_METRICS) + .value("BITMAP_METRICS_ONLY", LoadFlags::BITMAP_METRICS_ONLY) + .value("NO_SVG", LoadFlags::NO_SVG) + // These must be unique, but the others can be OR'd together; I don't know if + // there's any way to really enforce that. + .value("TARGET_NORMAL", LoadFlags::TARGET_NORMAL) + .value("TARGET_LIGHT", LoadFlags::TARGET_LIGHT) + .value("TARGET_MONO", LoadFlags::TARGET_MONO) + .value("TARGET_LCD", LoadFlags::TARGET_LCD) + .value("TARGET_LCD_V", LoadFlags::TARGET_LCD_V) + .finalize(); + + py::native_enum(m, "FaceFlags", "enum.Flag", FaceFlags__doc__) + .value("SCALABLE", FaceFlags::SCALABLE) + .value("FIXED_SIZES", FaceFlags::FIXED_SIZES) + .value("FIXED_WIDTH", FaceFlags::FIXED_WIDTH) + .value("SFNT", FaceFlags::SFNT) + .value("HORIZONTAL", FaceFlags::HORIZONTAL) + .value("VERTICAL", FaceFlags::VERTICAL) + .value("KERNING", FaceFlags::KERNING) + .value("FAST_GLYPHS", FaceFlags::FAST_GLYPHS) + .value("MULTIPLE_MASTERS", FaceFlags::MULTIPLE_MASTERS) + .value("GLYPH_NAMES", FaceFlags::GLYPH_NAMES) + .value("EXTERNAL_STREAM", FaceFlags::EXTERNAL_STREAM) + .value("HINTER", FaceFlags::HINTER) + .value("CID_KEYED", FaceFlags::CID_KEYED) + .value("TRICKY", FaceFlags::TRICKY) + .value("COLOR", FaceFlags::COLOR) + .value("VARIATION", FaceFlags::VARIATION) + .value("SVG", FaceFlags::SVG) + .value("SBIX", FaceFlags::SBIX) + .value("SBIX_OVERLAY", FaceFlags::SBIX_OVERLAY) + .finalize(); + + py::native_enum(m, "RenderMode", "enum.Enum", RenderMode__doc__) + .value("NORMAL", FT_RENDER_MODE_NORMAL) + .value("LIGHT", FT_RENDER_MODE_LIGHT) + .value("MONO", FT_RENDER_MODE_MONO) + .value("LCD", FT_RENDER_MODE_LCD) + .value("LCD_V", FT_RENDER_MODE_LCD_V) + .value("SDF", FT_RENDER_MODE_SDF) + .finalize(); + + py::native_enum(m, "StyleFlags", "enum.Flag", StyleFlags__doc__) + .value("NORMAL", StyleFlags::NORMAL) + .value("ITALIC", StyleFlags::ITALIC) + .value("BOLD", StyleFlags::BOLD) + .finalize(); + + py::classh(m, "FT2Image", py::is_final(), py::buffer_protocol(), PyFT2Image__doc__) .def(py::init( [](long width, long height) { @@ -1508,7 +1505,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) return py::buffer_info(self.get_buffer(), shape, strides); }); - py::class_(m, "_PositionedBitmap", py::is_final()) + py::classh(m, "_PositionedBitmap", py::is_final()) .def_readonly("left", &PyPositionedBitmap::left) .def_readonly("top", &PyPositionedBitmap::top) .def_property_readonly( @@ -1519,7 +1516,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) }) ; - py::class_(m, "Glyph", py::is_final(), PyGlyph__doc__) + py::classh(m, "Glyph", py::is_final(), PyGlyph__doc__) .def(py::init<>([]() -> PyGlyph { // Glyph is not useful from Python, so mark it as not constructible. throw std::runtime_error("Glyph is not constructible"); @@ -1543,7 +1540,7 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) .def_property_readonly("bbox", &PyGlyph_get_bbox, "The control box of the glyph."); - py::class_(m, "LayoutItem", py::is_final()) + py::classh(m, "LayoutItem", py::is_final()) .def(py::init<>([]() -> LayoutItem { // LayoutItem is not useful from Python, so mark it as not constructible. throw std::runtime_error("LayoutItem is not constructible"); @@ -1569,13 +1566,23 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) item.glyph_index, item.x, item.y, item.prev_kern); }); - auto cls = py::class_(m, "FT2Font", py::is_final(), py::buffer_protocol(), - PyFT2Font__doc__) - .def(py::init(&PyFT2Font_init), + py::classh(m, "FT2Font", py::is_final(), py::buffer_protocol(), + PyFT2Font__doc__) + .def(py::init( + [ft2Library]( + py::object filename, + std::optional hinting_factor = std::nullopt, + FT_Long face_index = 0, + std::optional> fallback_list = std::nullopt, + std::optional kerning_factor = std::nullopt, + bool warn_if_used = false) -> PyFT2Font * + { + return PyFT2Font_init(ft2Library, filename, hinting_factor, face_index, + fallback_list, kerning_factor, warn_if_used); + }), "filename"_a, "hinting_factor"_a=py::none(), py::kw_only(), - "face_index"_a=0, - "_fallback_list"_a=py::none(), "_kerning_factor"_a=py::none(), - "_warn_if_used"_a=false, + "face_index"_a=0, "_fallback_list"_a=py::none(), + "_kerning_factor"_a=py::none(), "_warn_if_used"_a=false, PyFT2Font_init__doc__) .def("clear", &PyFT2Font::clear, PyFT2Font_clear__doc__) .def("set_size", &PyFT2Font::set_size, "ptsize"_a, "dpi"_a, @@ -1610,20 +1617,10 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) .def("get_descent", &PyFT2Font::get_descent, PyFT2Font_get_descent__doc__) .def("draw_glyphs_to_bitmap", &PyFT2Font::draw_glyphs_to_bitmap, py::kw_only(), "antialiased"_a=true, - PyFT2Font_draw_glyphs_to_bitmap__doc__); - // The generated docstring uses an unqualified "Buffer" as type hint, - // which causes an error in sphinx. This is fixed as of pybind11 - // master (since #5566) which now uses "collections.abc.Buffer"; - // restore the signature once that version is released. - { - py::options options{}; - options.disable_function_signatures(); - cls - .def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap, - "image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true, - PyFT2Font_draw_glyph_to_bitmap__doc__); - } - cls + PyFT2Font_draw_glyphs_to_bitmap__doc__) + .def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap, + "image"_a, "x"_a, "y"_a, "glyph"_a, py::kw_only(), "antialiased"_a=true, + PyFT2Font_draw_glyph_to_bitmap__doc__) .def("get_glyph_name", &PyFT2Font::get_glyph_name, "index"_a, PyFT2Font_get_glyph_name__doc__) .def("get_charmap", &PyFT2Font_get_charmap, PyFT2Font_get_charmap__doc__) @@ -1750,14 +1747,28 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) }) .def("_render_glyph", - [](PyFT2Font *self, FT_UInt idx, LoadFlags flags, FT_Render_Mode render_mode) { + [ft2Library](PyFT2Font *self, FT_UInt idx, LoadFlags flags, + FT_Render_Mode render_mode) + { auto face = self->get_face(); FT_CHECK(FT_Load_Glyph, face, idx, static_cast(flags)); FT_CHECK(FT_Render_Glyph, face->glyph, render_mode); - return PyPositionedBitmap{face->glyph}; + return PyPositionedBitmap{ft2Library, face->glyph}; }) ; + // Ensure FreeType library is closed after all instances of FT2Font are gone by + // tying a weak ref to the class itself. + (void)py::weakref( + m.attr("FT2Font"), + py::cpp_function( + [ft2Library](py::handle weakref) { + FT_Done_FreeType(ft2Library); + weakref.dec_ref(); + } + ) + ).release(); + m.attr("__freetype_version__") = version_string; m.attr("__freetype_build_type__") = FREETYPE_BUILD_TYPE; m.attr("__libraqm_version__") = raqm_version_string(); diff --git a/src/tri/_tri_wrapper.cpp b/src/tri/_tri_wrapper.cpp index 732e1af5f310..78183af75de1 100644 --- a/src/tri/_tri_wrapper.cpp +++ b/src/tri/_tri_wrapper.cpp @@ -1,10 +1,18 @@ #include "_tri.h" +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +#include +#endif using namespace pybind11::literals; +#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT +PYBIND11_MODULE(_tri, m, + py::mod_gil_not_used(), py::multiple_interpreters::per_interpreter_gil()) +#else PYBIND11_MODULE(_tri, m, py::mod_gil_not_used()) +#endif { - py::class_(m, "Triangulation", py::is_final()) + py::classh(m, "Triangulation", py::is_final()) .def(py::init(m, "TriContourGenerator", py::is_final()) + py::classh(m, "TriContourGenerator", py::is_final()) .def(py::init(), "triangulation"_a, @@ -44,7 +52,7 @@ PYBIND11_MODULE(_tri, m, py::mod_gil_not_used()) .def("create_filled_contour", &TriContourGenerator::create_filled_contour, "Create and return a filled contour."); - py::class_(m, "TrapezoidMapTriFinder", py::is_final()) + py::classh(m, "TrapezoidMapTriFinder", py::is_final()) .def(py::init(), "triangulation"_a, "Create a new C++ TrapezoidMapTriFinder object.\n"