Skip to content

Commit ea51d36

Browse files
committed
Implement font fallback for libraqm
1 parent b183d30 commit ea51d36

File tree

2 files changed

+75
-5
lines changed

2 files changed

+75
-5
lines changed

lib/matplotlib/tests/test_text.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,16 @@ def test_complex_shaping():
121121
text = (
122122
'Arabic: \N{Arabic Letter REH}\N{Arabic FATHA}\N{Arabic Letter QAF}'
123123
'\N{Arabic SUKUN}\N{Arabic Letter MEEM}')
124-
fig = plt.figure(figsize=(3, 1))
125-
fig.text(0.5, 0.5, text, size=32, ha='center', va='center')
124+
math_signs = '\N{N-ary Product}\N{N-ary Coproduct}\N{N-ary summation}\N{Integral}'
125+
text = math_signs + text + math_signs
126+
fig = plt.figure(figsize=(6, 2))
127+
fig.text(0.5, 0.75, text, size=32, ha='center', va='center')
128+
# Also check fallback behaviour:
129+
# - English should use cmr10
130+
# - Math signs should use DejaVu Sans Display (and thus be larger than the rest)
131+
# - Arabic should use DejaVu Sans
132+
fig.text(0.5, 0.25, text, size=32, ha='center', va='center',
133+
family=['cmr10', 'DejaVu Sans Display', 'DejaVu Sans'])
126134

127135

128136
@image_comparison(['multiline'])

src/ft2font.cpp

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <algorithm>
77
#include <cstdio>
88
#include <iterator>
9+
#include <map>
910
#include <set>
1011
#include <stdexcept>
1112
#include <string>
@@ -360,13 +361,74 @@ void FT2Font::set_text(
360361
throw std::runtime_error("failed to set text flags for layout");
361362
}
362363

363-
std::set<FT_String*> glyph_seen_fonts;
364-
glyph_seen_fonts.insert(face->family_name);
365-
366364
if (!raqm_layout(rq)) {
367365
throw std::runtime_error("failed to layout text");
368366
}
369367

368+
std::vector<std::pair<size_t, const FT_Face&>> face_substitutions;
369+
std::set<FT_String*> glyph_seen_fonts;
370+
glyph_seen_fonts.insert(face->family_name);
371+
372+
// Attempt to use fallback fonts if necessary.
373+
for (auto const& fallback : fallbacks) {
374+
size_t num_glyphs = 0;
375+
auto const& rq_glyphs = raqm_get_glyphs(rq, &num_glyphs);
376+
bool new_fallback_used = false;
377+
378+
// Sort clusters, as RTL text will be returned in display, not saurce, order.
379+
std::map<decltype(raqm_glyph_t::cluster), bool> cluster_missing;
380+
for (size_t i = 0; i < num_glyphs; i++) {
381+
auto const& rglyph = rq_glyphs[i];
382+
383+
// Sometimes multiple glyphs are necessary for a single cluster; if any are
384+
// not found, we want to "poison" the whole set and keep them missing.
385+
cluster_missing[rglyph.cluster] |= (rglyph.index == 0);
386+
}
387+
388+
for (auto it = cluster_missing.cbegin(); it != cluster_missing.cend(); ) {
389+
auto [cluster, missing] = *it;
390+
++it; // Early change so we can access the next cluster below.
391+
if (missing) {
392+
auto next = (it != cluster_missing.cend()) ? it->first : text.size();
393+
for (auto i = cluster; i < next; i++) {
394+
face_substitutions.emplace_back(i, fallback->face);
395+
}
396+
new_fallback_used = true;
397+
}
398+
}
399+
400+
if (new_fallback_used) {
401+
// If a fallback was used, then re-attempt the layout with the new fonts.
402+
if (!fallback->warn_if_used) {
403+
glyph_seen_fonts.insert(fallback->face->family_name);
404+
}
405+
406+
raqm_clear_contents(rq);
407+
if (!raqm_set_text(rq,
408+
reinterpret_cast<const uint32_t *>(text.data()),
409+
text.size()))
410+
{
411+
throw std::runtime_error("failed to set text for layout");
412+
}
413+
if (!raqm_set_freetype_face(rq, face)) {
414+
throw std::runtime_error("failed to set text face for layout");
415+
}
416+
for (auto [cluster, fallback] : face_substitutions) {
417+
raqm_set_freetype_face_range(rq, fallback, cluster, 1);
418+
}
419+
if (!raqm_set_freetype_load_flags(rq, flags)) {
420+
throw std::runtime_error("failed to set text flags for layout");
421+
}
422+
423+
if (!raqm_layout(rq)) {
424+
throw std::runtime_error("failed to layout text");
425+
}
426+
} else {
427+
// If we never used a fallback, then we're good to go with the existing
428+
// layout we have already made.
429+
break;
430+
}
431+
}
370432

371433
size_t num_glyphs = 0;
372434
auto const& rq_glyphs = raqm_get_glyphs(rq, &num_glyphs);

0 commit comments

Comments
 (0)