Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -243,7 +243,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::classh<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 @@ -164,7 +164,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 @@ -133,6 +133,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
9 changes: 8 additions & 1 deletion src/ft2font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <cstdio>
#include <iterator>
#include <map>
#include <mutex>
#include <new>
#include <set>
#include <stdexcept>
#include <string>
Expand All @@ -17,8 +19,13 @@
#endif

FT2Image::FT2Image(unsigned long width, unsigned long height)
: m_buffer((unsigned char *)calloc(width * height, 1)), m_width(width), m_height(height)
// calloc(width, height) performs the width*height overflow check internally
// and returns nullptr on overflow or allocation failure.
: m_buffer((unsigned char *)calloc(width, height)), m_width(width), m_height(height)
{
if (!m_buffer && width != 0 && height != 0) {
throw std::bad_alloc();
}
}

FT2Image::~FT2Image()
Expand Down
40 changes: 35 additions & 5 deletions src/ft2font_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include "ft2font.h"
#include "mplutils.h"

#include <memory>
#include <mutex>
#include <set>
#include <sstream>
#include <unordered_map>
Expand Down Expand Up @@ -291,6 +293,18 @@ class PyFT2Font final : public FT2Font
py::list fallbacks;
bool from_path = false;

// 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 @@ -434,7 +448,9 @@ PyFT2Font_init(FT_Library ft2Library, py::object filename,
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 @@ -452,7 +468,7 @@ PyFT2Font_init(FT_Library ft2Library, py::object filename,
// file we opened, and nullptr for a caller-owned one.
auto stream_font_via_python = [&](FT_Stream_CloseFunc close) {
self->stream.size = 0x7fffffff; // Unknown size.
self->stream.descriptor.pointer = self;
self->stream.descriptor.pointer = self.get();
self->stream.read = &read_from_file_callback;
self->stream.close = close;
open_args.flags = FT_OPEN_STREAM;
Expand Down Expand Up @@ -516,7 +532,7 @@ PyFT2Font_init(FT_Library ft2Library, py::object filename,

self->open(ft2Library, open_args, face_index);

return self;
return self.release();
}

static py::object
Expand Down Expand Up @@ -656,6 +672,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 = std::visit(overloaded {
[](FT2Font::LanguageType languages) {
Expand Down Expand Up @@ -709,6 +726,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 @@ -747,6 +765,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 @@ -849,6 +868,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 @@ -1373,6 +1393,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 = std::visit(overloaded {
Expand Down Expand Up @@ -1615,7 +1636,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
"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("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 @@ -1646,7 +1672,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__)
.def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap,
Expand Down
Loading