Skip to content

Commit 93ca470

Browse files
committed
Implement text shaping with libraqm
1 parent 64ebd00 commit 93ca470

File tree

3 files changed

+65
-65
lines changed

3 files changed

+65
-65
lines changed

lib/matplotlib/_text_helpers.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,6 @@ def warn_on_missing_glyph(codepoint, fontnames):
2525
f"({chr(codepoint).encode('ascii', 'namereplace').decode('ascii')}) "
2626
f"missing from font(s) {fontnames}.")
2727

28-
block = ("Hebrew" if 0x0590 <= codepoint <= 0x05ff else
29-
"Arabic" if 0x0600 <= codepoint <= 0x06ff else
30-
"Devanagari" if 0x0900 <= codepoint <= 0x097f else
31-
"Bengali" if 0x0980 <= codepoint <= 0x09ff else
32-
"Gurmukhi" if 0x0a00 <= codepoint <= 0x0a7f else
33-
"Gujarati" if 0x0a80 <= codepoint <= 0x0aff else
34-
"Oriya" if 0x0b00 <= codepoint <= 0x0b7f else
35-
"Tamil" if 0x0b80 <= codepoint <= 0x0bff else
36-
"Telugu" if 0x0c00 <= codepoint <= 0x0c7f else
37-
"Kannada" if 0x0c80 <= codepoint <= 0x0cff else
38-
"Malayalam" if 0x0d00 <= codepoint <= 0x0d7f else
39-
"Sinhala" if 0x0d80 <= codepoint <= 0x0dff else
40-
None)
41-
if block:
42-
_api.warn_external(
43-
f"Matplotlib currently does not support {block} natively.")
44-
4528

4629
def layout(string, font, *, kern_mode=Kerning.DEFAULT):
4730
"""

lib/matplotlib/tests/test_text.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -817,18 +817,6 @@ def test_pdf_kerning():
817817
plt.figtext(0.1, 0.5, "ATATATATATATATATATA", size=30)
818818

819819

820-
def test_unsupported_script(recwarn):
821-
fig = plt.figure()
822-
t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}")
823-
fig.canvas.draw()
824-
assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
825-
assert (
826-
[warn.message.args for warn in recwarn] ==
827-
[(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from font(s) "
828-
+ f"{t.get_fontname()}.",),
829-
(r"Matplotlib currently does not support Bengali natively.",)])
830-
831-
832820
# See gh-26152 for more information on this xfail
833821
@pytest.mark.xfail(pyparsing_version.release == (3, 1, 0),
834822
reason="Error messages are incorrect with pyparsing 3.1.0")

src/ft2font.cpp

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@ FT2Font::FT2Font(FT_Open_Args &open_args,
271271
throw_ft_error("Can not load face", error);
272272
}
273273

274+
// This allows us to get back to our data if we need it, though it makes a pointer
275+
// loop, so don't set a free-function for it.
276+
face->generic.data = this;
277+
face->generic.finalizer = nullptr;
278+
274279
// set a default fontsize 12 pt at 72dpi
275280
error = FT_Set_Char_Size(face, 12 * 64, 0, 72 * (unsigned int)hinting_factor, 72);
276281
if (error) {
@@ -417,62 +422,86 @@ void FT2Font::set_text(
417422
bbox.xMin = bbox.yMin = 32000;
418423
bbox.xMax = bbox.yMax = -32000;
419424

420-
FT_UInt previous = 0;
421-
FT2Font *previous_ft_object = nullptr;
425+
auto rq = raqm_create();
426+
if (!rq) {
427+
throw std::runtime_error("failed to compute text layout");
428+
}
429+
[[maybe_unused]] auto const& rq_cleanup =
430+
std::unique_ptr<std::remove_pointer_t<raqm_t>, decltype(&raqm_destroy)>(
431+
rq, raqm_destroy);
422432

423-
for (auto codepoint : text) {
424-
FT_UInt glyph_index = 0;
425-
FT_BBox glyph_bbox;
426-
FT_Pos last_advance;
433+
if (!raqm_set_text(rq, reinterpret_cast<const uint32_t *>(text.data()), text.size())) {
434+
throw std::runtime_error("failed to set text for layout");
435+
}
427436

428-
FT_Error charcode_error, glyph_error;
429-
std::set<FT_String*> glyph_seen_fonts;
430-
FT2Font *ft_object_with_glyph = this;
431-
bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs,
432-
char_to_font, glyph_to_font, codepoint, flags,
433-
charcode_error, glyph_error, glyph_seen_fonts, false);
434-
if (!was_found) {
435-
ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts);
436-
// render missing glyph tofu
437-
// come back to top-most font
438-
ft_object_with_glyph = this;
439-
char_to_font[codepoint] = ft_object_with_glyph;
440-
glyph_to_font[glyph_index] = ft_object_with_glyph;
441-
ft_object_with_glyph->load_glyph(glyph_index, flags, ft_object_with_glyph, false);
442-
} else if (ft_object_with_glyph->warn_if_used) {
443-
ft_glyph_warn((FT_ULong)codepoint, glyph_seen_fonts);
444-
}
437+
if (!raqm_set_freetype_face(rq, face)) {
438+
throw std::runtime_error("failed to set text face for layout");
439+
}
440+
441+
if (!raqm_set_freetype_load_flags(rq, flags)) {
442+
throw std::runtime_error("failed to set text flags for layout");
443+
}
444+
445+
std::set<FT_String*> glyph_seen_fonts;
446+
glyph_seen_fonts.insert(face->family_name);
447+
448+
if (!raqm_layout(rq)) {
449+
throw std::runtime_error("failed to layout text");
450+
}
445451

446-
// retrieve kerning distance and move pen position
447-
if ((ft_object_with_glyph == previous_ft_object) && // if both fonts are the same
448-
ft_object_with_glyph->has_kerning() && // if the font knows how to kern
449-
previous && glyph_index // and we really have 2 glyphs
450-
) {
451-
FT_Vector delta;
452-
pen.x += ft_object_with_glyph->get_kerning(previous, glyph_index, FT_KERNING_DEFAULT, delta);
452+
453+
size_t num_glyphs = 0;
454+
auto const& rq_glyphs = raqm_get_glyphs(rq, &num_glyphs);
455+
456+
for (size_t i = 0; i < num_glyphs; i++) {
457+
auto const& rglyph = rq_glyphs[i];
458+
459+
// Warn for missing glyphs.
460+
if (rglyph.index == 0) {
461+
ft_glyph_warn(text[rglyph.cluster], glyph_seen_fonts);
462+
continue;
463+
}
464+
FT2Font *wrapped_font = static_cast<FT2Font *>(rglyph.ftface->generic.data);
465+
if (wrapped_font->warn_if_used) {
466+
ft_glyph_warn(text[rglyph.cluster], glyph_seen_fonts);
453467
}
454468

455469
// extract glyph image and store it in our table
456-
FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1];
470+
FT_Error error;
471+
error = FT_Load_Glyph(rglyph.ftface, rglyph.index, flags);
472+
if (error) {
473+
throw std::runtime_error("failed to load glyph");
474+
}
475+
FT_Glyph thisGlyph;
476+
error = FT_Get_Glyph(rglyph.ftface->glyph, &thisGlyph);
477+
if (error) {
478+
throw std::runtime_error("failed to get glyph");
479+
}
480+
481+
pen.x += rglyph.x_offset;
482+
pen.y += rglyph.y_offset;
457483

458-
last_advance = ft_object_with_glyph->get_face()->glyph->advance.x;
459484
FT_Glyph_Transform(thisGlyph, nullptr, &pen);
460485
FT_Glyph_Transform(thisGlyph, &matrix, nullptr);
461486
xys.push_back(pen.x);
462487
xys.push_back(pen.y);
463488

489+
FT_BBox glyph_bbox;
464490
FT_Glyph_Get_CBox(thisGlyph, FT_GLYPH_BBOX_SUBPIXELS, &glyph_bbox);
465491

466492
bbox.xMin = std::min(bbox.xMin, glyph_bbox.xMin);
467493
bbox.xMax = std::max(bbox.xMax, glyph_bbox.xMax);
468494
bbox.yMin = std::min(bbox.yMin, glyph_bbox.yMin);
469495
bbox.yMax = std::max(bbox.yMax, glyph_bbox.yMax);
470496

471-
pen.x += last_advance;
472-
473-
previous = glyph_index;
474-
previous_ft_object = ft_object_with_glyph;
497+
if ((flags & FT_LOAD_NO_HINTING) != 0) {
498+
pen.x += rglyph.x_advance - rglyph.x_offset;
499+
} else {
500+
pen.x += hinting_factor * rglyph.x_advance - rglyph.x_offset;
501+
}
502+
pen.y += rglyph.y_advance - rglyph.y_offset;
475503

504+
glyphs.push_back(thisGlyph);
476505
}
477506

478507
FT_Vector_Transform(&pen, &matrix);

0 commit comments

Comments
 (0)