Skip to content

Commit 6f19037

Browse files
committed
Prepare for font fallback.
1 parent 7f5a6ef commit 6f19037

File tree

4 files changed

+119
-86
lines changed

4 files changed

+119
-86
lines changed

ext/_macros.h

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
} \
3232
} (void)0
3333

34+
#define CAIRO_CHECK_SET_USER_DATA_NEW(set_user_data, obj, key, user_data) \
35+
CAIRO_CHECK_SET_USER_DATA( \
36+
set_user_data, obj, key, new decltype(user_data)(user_data), \
37+
[](void* ptr) { delete static_cast<decltype(key)*>(ptr); })
38+
3439
#define FT_CHECK(func, ...) { \
3540
if (auto const& error_ = func(__VA_ARGS__)) { \
3641
THROW_ERROR(#func, mplcairo::detail::ft_errors.at(error_)); \

ext/_mplcairo.cpp

+29-39
Original file line numberDiff line numberDiff line change
@@ -246,16 +246,14 @@ GraphicsContextRenderer::GraphicsContextRenderer(
246246
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
247247
// May already have been set by cr_from_fileformat_args.
248248
if (!cairo_get_user_data(cr, &detail::REFS_KEY)) {
249-
CAIRO_CHECK_SET_USER_DATA(
250-
cairo_set_user_data, cr, &detail::REFS_KEY,
251-
new std::vector<py::object>{},
252-
[](void* data) -> void {
253-
delete static_cast<std::vector<py::object>*>(data);
254-
});
249+
CAIRO_CHECK_SET_USER_DATA_NEW(
250+
cairo_set_user_data, cr, &detail::REFS_KEY, std::vector<py::object>{});
255251
}
256-
CAIRO_CHECK_SET_USER_DATA(
252+
CAIRO_CHECK_SET_USER_DATA_NEW(
257253
cairo_set_user_data, cr, &detail::STATE_KEY,
258-
(new std::stack<AdditionalState>{{{
254+
// Unfortunately cairo_set_user_data doesn't have stack semantics wrt.
255+
// cairo_save/cairo_restore, so we must take care of that ourselves.
256+
(std::stack<AdditionalState>{{{
259257
/* width */ width,
260258
/* height */ height,
261259
/* dpi */ dpi,
@@ -269,19 +267,15 @@ GraphicsContextRenderer::GraphicsContextRenderer(
269267
/* sketch */ {},
270268
/* snap */ true, // Defaults to None, i.e. True for us.
271269
/* url */ {}
272-
}}}),
273-
[](void* data) -> void {
274-
// Just calling operator delete would not invoke the destructor.
275-
delete static_cast<std::stack<AdditionalState>*>(data);
276-
});
270+
}}}));
277271
}
278272

279273
GraphicsContextRenderer::~GraphicsContextRenderer()
280274
{
281275
if (detail::FONT_CACHE.size() > 64) { // font_manager._get_font cache size.
282-
for (auto& [pathspec, font_face]: detail::FONT_CACHE) {
276+
for (auto& [pathspec, face]: detail::FONT_CACHE) {
283277
(void)pathspec;
284-
cairo_font_face_destroy(font_face);
278+
cairo_font_face_destroy(face);
285279
}
286280
detail::FONT_CACHE.clear(); // Naive cache mechanism.
287281
}
@@ -346,9 +340,8 @@ cairo_t* GraphicsContextRenderer::cr_from_pycairo_ctx(
346340
cairo_get_matrix(cr, mtx);
347341
auto const& [sx, sy] = device_scales;
348342
mtx->x0 *= sx; mtx->y0 *= sy;
349-
CAIRO_CHECK_SET_USER_DATA(
350-
cairo_set_user_data, cr, &detail::INIT_MATRIX_KEY, mtx,
351-
[](void* data) -> void { delete static_cast<cairo_matrix_t*>(data); });
343+
CAIRO_CHECK_SET_USER_DATA_NEW(
344+
cairo_set_user_data, cr, &detail::INIT_MATRIX_KEY, mtx);
352345
return cr;
353346
}
354347

@@ -415,12 +408,9 @@ cairo_t* GraphicsContextRenderer::cr_from_fileformat_args(
415408
cairo_surface_set_fallback_resolution(surface, dpi, dpi);
416409
auto const& cr = cairo_create(surface);
417410
cairo_surface_destroy(surface);
418-
CAIRO_CHECK_SET_USER_DATA(
411+
CAIRO_CHECK_SET_USER_DATA_NEW(
419412
cairo_set_user_data, cr, &detail::REFS_KEY,
420-
new std::vector<py::object>{{write}},
421-
[](void* data) -> void {
422-
delete static_cast<std::vector<py::object>*>(data);
423-
});
413+
std::vector<py::object>{{write}});
424414
if (type == StreamSurfaceType::EPS) {
425415
// If cairo was built without PS support, we'd already have errored above.
426416
detail::cairo_ps_surface_set_eps(surface, true);
@@ -1365,13 +1355,9 @@ void GraphicsContextRenderer::draw_path_collection(
13651355

13661356
maybe_multithread(cr_, n, [&](cairo_t* ctx, int start, int stop) {
13671357
if (ctx != cr_) {
1368-
CAIRO_CHECK_SET_USER_DATA(
1358+
CAIRO_CHECK_SET_USER_DATA_NEW(
13691359
cairo_set_user_data, ctx, &detail::STATE_KEY,
1370-
(new std::stack<AdditionalState>{{get_additional_state()}}),
1371-
[](void* data) -> void {
1372-
// Just calling operator delete would not invoke the destructor.
1373-
delete static_cast<std::stack<AdditionalState>*>(data);
1374-
});
1360+
std::stack<AdditionalState>{{get_additional_state()}});
13751361
}
13761362
auto cache = PatternCache{simplify_threshold};
13771363
for (auto i = start; i < stop; ++i) {
@@ -1582,9 +1568,8 @@ void GraphicsContextRenderer::draw_text(
15821568
}
15831569
mb.draw(*this, x, y, angle);
15841570
} else {
1585-
auto const& font_face = font_face_from_prop(prop);
1586-
cairo_set_font_face(cr_, font_face);
1587-
cairo_font_face_destroy(font_face);
1571+
auto const& faces = font_faces_from_prop(prop);
1572+
cairo_set_font_face(cr_, faces[0]);
15881573
auto const& font_size =
15891574
points_to_pixels(prop.attr("get_size_in_points")().cast<double>());
15901575
cairo_set_font_size(cr_, font_size);
@@ -1621,6 +1606,9 @@ void GraphicsContextRenderer::draw_text(
16211606
cr_, s.c_str(), s.size(),
16221607
gac.glyphs, gac.num_glyphs,
16231608
gac.clusters, gac.num_clusters, gac.cluster_flags);
1609+
for (auto const& face: faces) {
1610+
cairo_font_face_destroy(face);
1611+
}
16241612
}
16251613
}
16261614

@@ -1640,9 +1628,8 @@ GraphicsContextRenderer::get_text_width_height_descent(
16401628
.cast<std::tuple<double, double, double>>();
16411629
} else {
16421630
cairo_save(cr_);
1643-
auto const& font_face = font_face_from_prop(prop);
1644-
cairo_set_font_face(cr_, font_face);
1645-
cairo_font_face_destroy(font_face);
1631+
auto const& faces = font_faces_from_prop(prop);
1632+
cairo_set_font_face(cr_, faces[0]);
16461633
auto const& font_size =
16471634
points_to_pixels(prop.attr("get_size_in_points")().cast<double>());
16481635
cairo_set_font_size(cr_, font_size);
@@ -1651,6 +1638,9 @@ GraphicsContextRenderer::get_text_width_height_descent(
16511638
auto const& gac = text_to_glyphs_and_clusters(cr_, s);
16521639
cairo_glyph_extents(cr_, gac.glyphs, gac.num_glyphs, &extents);
16531640
cairo_restore(cr_);
1641+
for (auto const& face: faces) {
1642+
cairo_font_face_destroy(face);
1643+
}
16541644
return {
16551645
// Max of inked portion and of current point advance (to also take
16561646
// whitespace into account).
@@ -1808,17 +1798,17 @@ void MathtextBackend::draw(
18081798
cairo_translate(cr, x, y);
18091799
cairo_rotate(cr, -angle * std::acos(-1) / 180);
18101800
for (auto const& glyph: glyphs_) {
1811-
auto const& font_face = font_face_from_path(glyph.path);
1812-
cairo_set_font_face(cr, font_face);
1813-
cairo_font_face_destroy(font_face);
1801+
auto const& face = font_face_from_path(glyph.path);
1802+
cairo_set_font_face(cr, face);
1803+
cairo_font_face_destroy(face);
18141804
auto const& size = glyph.size * dpi / 72;
18151805
auto const& mtx = cairo_matrix_t{
18161806
size * glyph.extend, 0, -size * glyph.slant * glyph.extend, size, 0, 0};
18171807
cairo_set_font_matrix(cr, &mtx);
18181808
adjust_font_options(cr);
18191809
auto ft_face =
18201810
static_cast<FT_Face>(
1821-
cairo_font_face_get_user_data(font_face, &detail::FT_KEY));
1811+
cairo_font_face_get_user_data(face, &detail::FT_KEY));
18221812
auto index = FT_UInt{};
18231813
std::visit(overloaded {
18241814
[&](char32_t codepoint) {

ext/_util.cpp

+82-46
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ cairo_user_data_key_t const REFS_KEY{},
5959
INIT_MATRIX_KEY{},
6060
FT_KEY{},
6161
FEATURES_KEY{},
62+
LANGS_KEY{},
6263
VARIATIONS_KEY{},
6364
IS_COLOR_FONT_KEY{};
6465
py::object RC_PARAMS{},
@@ -720,26 +721,64 @@ py::array image_surface_to_buffer(cairo_surface_t* surface) {
720721
}
721722
}
722723

723-
cairo_font_face_t* font_face_from_path(std::string pathspec)
724+
auto parse_pathspec(std::string pathspec)
724725
{
725-
auto& font_face = detail::FONT_CACHE[pathspec];
726-
if (!font_face) {
726+
struct parse_t {
727+
std::string path;
728+
int face_index;
729+
std::vector<std::string> features;
730+
std::vector<std::tuple<std::string, int, int>> langs;
731+
std::string variations;
732+
};
727733
auto match = std::smatch{};
728734
if (!std::regex_match(pathspec, match,
729735
std::regex{"(.*?)(#(\\d+))?(\\|([^|]*))?(\\|(.*))?"})) {
730736
throw std::runtime_error{
731737
"Failed to parse pathspec {}"_format(pathspec).cast<std::string>()};
732738
}
733-
auto const& path = match.str(1);
734-
auto const& face_index = std::atoi(match.str(3).c_str()); // 0 if absent.
739+
auto parse = parse_t{
740+
match.str(1),
741+
std::atoi(match.str(3).c_str()), // 0 if absent.
742+
{}, {}, // Fill below.
743+
match.str(7)
744+
};
735745
auto const& features_s = match.str(5);
736-
auto const& variations = match.str(7);
746+
if (features_s.size()) {
747+
auto const& comma = std::regex{","};
748+
auto toks = std::sregex_token_iterator{
749+
features_s.begin(), features_s.end(), comma, -1};
750+
while (toks != std::sregex_token_iterator{}) {
751+
auto const& tok = std::string{toks->str()};
752+
if (std::regex_match(
753+
tok,
754+
match,
755+
std::regex{"^language(?:\\[(\\d+)?(?::(\\d+))?\\])?=(.*)$"})) {
756+
auto const& start = std::atoi(match.str(1).c_str()); // 0 if absent.
757+
auto const& stop =
758+
match[2].matched ? std::atoi(match.str(2).c_str()) : -1;
759+
auto const& lang = match.str(3);
760+
parse.langs.emplace_back(lang, start, stop);
761+
} else {
762+
parse.features.emplace_back(tok);
763+
}
764+
++toks;
765+
}
766+
}
767+
return parse;
768+
}
769+
770+
cairo_font_face_t* font_face_from_path(std::string pathspec)
771+
{
772+
auto& font_face = detail::FONT_CACHE[pathspec];
773+
if (!font_face) {
774+
auto parsed = parse_pathspec(pathspec);
737775
FT_Face ft_face;
738776
if (auto const& error =
739-
FT_New_Face(detail::ft_library, path.c_str(), face_index, &ft_face)) {
777+
FT_New_Face(
778+
detail::ft_library, parsed.path.c_str(), parsed.face_index, &ft_face)) {
740779
if (error == FT_Err_Cannot_Open_Resource) {
741780
// Throw the exception that Python would throw...
742-
py::module::import("builtins").attr("open")(path);
781+
py::module::import("builtins").attr("open")(parsed.path);
743782
if (PyErr_Occurred()) { // ... if possible.
744783
throw py::error_already_set{};
745784
}
@@ -755,28 +794,16 @@ cairo_font_face_t* font_face_from_path(std::string pathspec)
755794
font_face, cairo_font_face_destroy};
756795
CAIRO_CHECK_SET_USER_DATA(
757796
cairo_font_face_set_user_data, font_face, &detail::FT_KEY, ft_face,
758-
[](void* ptr) -> void {
759-
FT_CHECK(FT_Done_Face, reinterpret_cast<FT_Face>(ptr));
760-
});
761-
auto const& comma = std::regex{","};
762-
auto const& features = new std::vector<std::string>(
763-
features_s.size()
764-
? std::sregex_token_iterator{
765-
features_s.begin(), features_s.end(), comma, -1}
766-
: std::sregex_token_iterator{},
767-
std::sregex_token_iterator{});
768-
CAIRO_CHECK_SET_USER_DATA(
769-
cairo_font_face_set_user_data,
770-
font_face, &detail::FEATURES_KEY, features,
771-
[](void* ptr) -> void {
772-
delete static_cast<std::vector<std::string>*>(ptr);
773-
});
774-
CAIRO_CHECK_SET_USER_DATA(
775-
cairo_font_face_set_user_data,
776-
font_face, &detail::VARIATIONS_KEY, new std::string(variations),
777-
[](void* ptr) -> void {
778-
delete static_cast<std::string*>(ptr);
779-
});
797+
[](void* ptr) { FT_CHECK(FT_Done_Face, reinterpret_cast<FT_Face>(ptr)); });
798+
CAIRO_CHECK_SET_USER_DATA_NEW(
799+
cairo_font_face_set_user_data, font_face, &detail::FEATURES_KEY,
800+
parsed.features);
801+
CAIRO_CHECK_SET_USER_DATA_NEW(
802+
cairo_font_face_set_user_data, font_face, &detail::LANGS_KEY,
803+
parsed.langs);
804+
CAIRO_CHECK_SET_USER_DATA_NEW(
805+
cairo_font_face_set_user_data, font_face, &detail::VARIATIONS_KEY,
806+
parsed.variations);
780807
// Color fonts need special handling due to cairo#404 and raqm#123; see
781808
// corresponding sections of the code.
782809
if (FT_IS_SFNT(ft_face)) {
@@ -811,14 +838,25 @@ cairo_font_face_t* font_face_from_path(py::object path) {
811838
.cast<std::string>());
812839
}
813840

814-
cairo_font_face_t* font_face_from_prop(py::object prop)
841+
std::vector<cairo_font_face_t*> font_faces_from_prop(py::object prop)
815842
{
816843
// It is probably not worth implementing an additional layer of caching here
817844
// as findfont already has its cache and object equality needs would also
818845
// need to go through Python anyways.
819-
auto const& path =
820-
py::module::import("matplotlib.font_manager").attr("findfont")(prop);
821-
return font_face_from_path(path);
846+
auto const& fm =
847+
py::module::import("matplotlib.font_manager").attr("fontManager");
848+
if (py::hasattr(fm, "_find_fonts_by_props")) {
849+
auto const& paths =
850+
fm.attr("_find_fonts_by_props")(prop).cast<std::vector<std::string>>();
851+
auto fonts = std::vector<cairo_font_face_t*>{};
852+
for (auto const& path: paths) {
853+
fonts.push_back(font_face_from_path(path));
854+
}
855+
return fonts;
856+
} else {
857+
auto const& path = fm.attr("findfont")(prop);
858+
return {font_face_from_path(path)};
859+
}
822860
}
823861

824862
long get_hinting_flag()
@@ -869,6 +907,9 @@ void warn_on_missing_glyph(std::string s)
869907
1);
870908
}
871909

910+
// TODO: Implement font fallback per
911+
// https://www.mail-archive.com/[email protected]/msg04131.html
912+
// https://tex.stackexchange.com/questions/520034/fallback-for-harfbuzz-fonts#comment1315285_520048
872913
GlyphsAndClusters text_to_glyphs_and_clusters(cairo_t* cr, std::string s)
873914
{
874915
auto const& scaled_font = cairo_get_scaled_font(cr);
@@ -893,18 +934,13 @@ GlyphsAndClusters text_to_glyphs_and_clusters(cairo_t* cr, std::string s)
893934
*static_cast<std::vector<std::string>*>(
894935
cairo_font_face_get_user_data(
895936
cairo_get_font_face(cr), &detail::FEATURES_KEY))) {
896-
auto match = std::smatch{};
897-
if (std::regex_match(
898-
feature, match,
899-
std::regex{"^language(?:\\[(\\d+)?(?::(\\d+))?\\])?=(.*)$"})) {
900-
auto const& start = std::atoi(match.str(1).c_str()); // 0 if absent.
901-
auto const& stop = match[2].matched
902-
? std::atoi(match.str(2).c_str()) : s.size();
903-
auto const& lang = match.str(3);
904-
TRUE_CHECK(raqm::set_language, rq, lang.c_str(), start, stop - start);
905-
} else {
906-
TRUE_CHECK(raqm::add_font_feature, rq, feature.c_str(), -1);
907-
}
937+
TRUE_CHECK(raqm::add_font_feature, rq, feature.c_str(), -1);
938+
}
939+
for (auto const& [lang, start, stop]:
940+
*static_cast<std::vector<std::tuple<std::string, int, int>>*>(
941+
cairo_font_face_get_user_data(
942+
cairo_get_font_face(cr), &detail::LANGS_KEY))) {
943+
TRUE_CHECK(raqm::set_language, rq, lang.c_str(), start, stop);
908944
}
909945
TRUE_CHECK(raqm::layout, rq);
910946
auto num_glyphs = size_t{};

ext/_util.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ extern cairo_user_data_key_t const
104104
INIT_MATRIX_KEY, // cairo_t -> cairo_matrix_t.
105105
FT_KEY, // cairo_font_face_t -> FT_Face.
106106
FEATURES_KEY, // cairo_font_face_t -> OpenType features.
107+
LANGS_KEY, // cairo_font_face_t -> languages.
108+
VARIATIONS_KEY, // cairo_font_face_t -> OpenType variations.
107109
IS_COLOR_FONT_KEY; // cairo_font_face_t -> non-null if a color font.
108110
extern py::object RC_PARAMS;
109111
extern py::object PIXEL_MARKER;
@@ -182,7 +184,7 @@ void fill_and_stroke_exact(
182184
py::array image_surface_to_buffer(cairo_surface_t* surface);
183185
cairo_font_face_t* font_face_from_path(std::string path);
184186
cairo_font_face_t* font_face_from_path(py::object path);
185-
cairo_font_face_t* font_face_from_prop(py::object prop);
187+
std::vector<cairo_font_face_t*> font_faces_from_prop(py::object prop);
186188
long get_hinting_flag();
187189
void adjust_font_options(cairo_t* cr);
188190
void warn_on_missing_glyph(std::string s);

0 commit comments

Comments
 (0)