Skip to content
Open
15 changes: 6 additions & 9 deletions src/_backend_agg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,27 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi)

unsigned stride(width * 4);

pixBuffer = new agg::int8u[NUMBYTES];
renderingBuffer.attach(pixBuffer, width, height, stride);
pixBuffer.reset(new agg::int8u[NUMBYTES]);
renderingBuffer.attach(pixBuffer.get(), width, height, stride);
pixFmt.attach(renderingBuffer);
rendererBase.attach(pixFmt);
rendererBase.clear(_fill_color);
rendererAA.attach(rendererBase);
rendererBin.attach(rendererBase);
hatch_size = int(dpi);
hatchBuffer = new agg::int8u[hatch_size * hatch_size * 4];
hatchRenderingBuffer.attach(hatchBuffer, hatch_size, hatch_size, hatch_size * 4);
hatchBuffer.reset(new agg::int8u[hatch_size * hatch_size * 4]);
hatchRenderingBuffer.attach(hatchBuffer.get(), hatch_size, hatch_size, hatch_size * 4);
}

RendererAgg::~RendererAgg()
{
delete[] hatchBuffer;
delete[] alphaBuffer;
delete[] pixBuffer;
}

void RendererAgg::create_alpha_buffers()
{
if (!alphaBuffer) {
alphaBuffer = new agg::int8u[width * height];
alphaMaskRenderingBuffer.attach(alphaBuffer, width, height, width);
alphaBuffer.reset(new agg::int8u[width * height]);
alphaMaskRenderingBuffer.attach(alphaBuffer.get(), width, height, width);
rendererBaseAlphaMask.attach(pixfmtAlphaMask);
rendererAlphaMask.attach(rendererBaseAlphaMask);
}
Expand Down
7 changes: 4 additions & 3 deletions src/_backend_agg.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <cmath>
#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <vector>

Expand Down Expand Up @@ -206,10 +207,10 @@ class RendererAgg
double dpi;
size_t NUMBYTES; // the number of bytes in buffer

agg::int8u *pixBuffer;
std::unique_ptr<agg::int8u[]> pixBuffer;
agg::rendering_buffer renderingBuffer;

agg::int8u *alphaBuffer;
std::unique_ptr<agg::int8u[]> alphaBuffer;
agg::rendering_buffer alphaMaskRenderingBuffer;
alpha_mask_type alphaMask;
agg::pixfmt_gray8 pixfmtAlphaMask;
Expand All @@ -229,7 +230,7 @@ class RendererAgg
agg::trans_affine lastclippath_transform;

size_t hatch_size;
agg::int8u *hatchBuffer;
std::unique_ptr<agg::int8u[]> hatchBuffer;
agg::rendering_buffer hatchRenderingBuffer;

agg::rgba _fill_color;
Expand Down
2 changes: 1 addition & 1 deletion src/_backend_agg_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used())
4,
1
};
return py::buffer_info(renderer->pixBuffer, shape, strides);
return py::buffer_info(renderer->pixBuffer.get(), shape, strides);
});

py::class_<BufferRegion>(m, "BufferRegion", py::buffer_protocol())
Expand Down
8 changes: 7 additions & 1 deletion src/_c_internal_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@ mpl_GetCurrentProcessExplicitAppUserModelID(void)
PyErr_SetFromWindowsErr(hr);
throw py::error_already_set();
}
auto py_appid = py::cast(appid);
py::object py_appid;
try {
py_appid = py::cast(appid);
} catch (...) {
CoTaskMemFree(appid); // don't leak the COM-allocated string on error
throw;
}
CoTaskMemFree(appid);
return py_appid;
#else
Expand Down
4 changes: 4 additions & 0 deletions src/_qhull_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ class QhullInfo {
}
}

// Uniquely owns qh and error_file; forbid copying (Rule of Three).
QhullInfo(const QhullInfo&) = delete;
QhullInfo& operator=(const QhullInfo&) = delete;

private:
FILE* error_file;
qhT* qh;
Expand Down
40 changes: 35 additions & 5 deletions src/ft2font_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include "ft2font.h"
#include "_enums.h"

#include <memory>
#include <mutex>
#include <set>
#include <sstream>
#include <unordered_map>
Expand Down Expand Up @@ -360,6 +362,18 @@ class PyFT2Font final : public FT2Font
FT_StreamRec stream;
py::list fallbacks;

// Serializes this object's mutating Python-facing methods so that concurrent
// calls on the SAME FT2Font (under free-threading / py::mod_gil_not_used)
// cannot corrupt its FreeType glyph slot, glyph list or image buffer. It is
// taken ONLY at the wrapper boundary -- never inside FT2Font's C++ methods,
// which also run during construction and fallback recursion -- so a single
// lock is held per top-level call and it never nests (deadlock-free by
// construction). Recursive to tolerate re-entry via Python callbacks such as
// ft_glyph_warn(). Contract: one FT2Font (together with its fallback graph)
// is used by a single thread at a time; matplotlib enforces this via the
// thread-keyed cache in font_manager._get_font.
std::recursive_mutex mutex;

~PyFT2Font()
{
// Because destructors are called from subclass up to base class, we need to
Expand Down Expand Up @@ -503,7 +517,9 @@ PyFT2Font_init(py::object filename, std::optional<long> hinting_factor = std::nu
std::back_inserter(fallback_fonts));
}

auto self = new PyFT2Font(fallback_fonts, warn_if_used);
// Own the object locally so it is released if any step below throws (e.g. a
// corrupt font makes self->open() fail); pybind11 adopts it only on success.
auto self = std::make_unique<PyFT2Font>(fallback_fonts, warn_if_used);
self->set_kerning_factor(*kerning_factor);

if (fallback_list) {
Expand All @@ -517,7 +533,7 @@ PyFT2Font_init(py::object filename, std::optional<long> hinting_factor = std::nu
self->stream.base = nullptr;
self->stream.size = 0x7fffffff; // Unknown size.
self->stream.pos = 0;
self->stream.descriptor.pointer = self;
self->stream.descriptor.pointer = self.get();
self->stream.read = &read_from_file_callback;
FT_Open_Args open_args;
memset((void *)&open_args, 0, sizeof(FT_Open_Args));
Expand Down Expand Up @@ -547,7 +563,7 @@ PyFT2Font_init(py::object filename, std::optional<long> hinting_factor = std::nu

self->open(open_args, face_index);

return self;
return self.release();
}

static py::object
Expand Down Expand Up @@ -687,6 +703,7 @@ PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0
std::optional<std::vector<std::string>> features = std::nullopt,
std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr)
{
std::scoped_lock lock{self->mutex};
std::vector<double> xys;

FT2Font::LanguageType languages;
Expand Down Expand Up @@ -745,6 +762,7 @@ static PyGlyph *
PyFT2Font_load_char(PyFT2Font *self, long charcode,
LoadFlags flags = LoadFlags::FORCE_AUTOHINT)
{
std::scoped_lock lock{self->mutex};
bool fallback = true;
FT2Font *ft_object = nullptr;

Expand Down Expand Up @@ -783,6 +801,7 @@ static PyGlyph *
PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index,
LoadFlags flags = LoadFlags::FORCE_AUTOHINT)
{
std::scoped_lock lock{self->mutex};
self->load_glyph(glyph_index, static_cast<FT_Int32>(flags));

return PyGlyph_from_FT2Font(self);
Expand Down Expand Up @@ -885,6 +904,7 @@ PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image,
int xd, int yd,
PyGlyph *glyph, bool antialiased = true)
{
std::scoped_lock lock{self->mutex};
self->draw_glyph_to_bitmap(
py::array_t<uint8_t, py::array::c_style>{image},
xd, yd, glyph->glyphInd, antialiased);
Expand Down Expand Up @@ -1409,6 +1429,7 @@ PyFT2Font_layout(PyFT2Font *self, std::u32string text, LoadFlags flags,
std::optional<std::vector<std::string>> features = std::nullopt,
std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr)
{
std::scoped_lock lock{self->mutex};
const auto load_flags = static_cast<FT_Int32>(flags);

FT2Font::LanguageType languages;
Expand Down Expand Up @@ -1577,7 +1598,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
"_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("clear",
[](PyFT2Font& self) {
std::scoped_lock lock{self.mutex};
self.clear();
},
PyFT2Font_clear__doc__)
.def("set_size", &PyFT2Font::set_size, "ptsize"_a, "dpi"_a,
PyFT2Font_set_size__doc__)
.def("_set_transform", &PyFT2Font::_set_transform, "matrix"_a, "delta"_a,
Expand Down Expand Up @@ -1608,7 +1634,11 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
.def("get_bitmap_offset", &PyFT2Font::get_bitmap_offset,
PyFT2Font_get_bitmap_offset__doc__)
.def("get_descent", &PyFT2Font::get_descent, PyFT2Font_get_descent__doc__)
.def("draw_glyphs_to_bitmap", &PyFT2Font::draw_glyphs_to_bitmap,
.def("draw_glyphs_to_bitmap",
[](PyFT2Font& self, bool antialiased) {
std::scoped_lock lock{self.mutex};
self.draw_glyphs_to_bitmap(antialiased);
},
py::kw_only(), "antialiased"_a=true,
PyFT2Font_draw_glyphs_to_bitmap__doc__);
// The generated docstring uses an unqualified "Buffer" as type hint,
Expand Down
Loading