Skip to content

Commit b3dd35d

Browse files
story645QuLogic
andcommitted
print fontname in missing glyph warning + all fallback fonts missing glyph
catch missing glyph errors in font tests remove ft_get_char_index_or_warn Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
1 parent c16d7db commit b3dd35d

File tree

6 files changed

+78
-51
lines changed

6 files changed

+78
-51
lines changed

lib/matplotlib/_text_helpers.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
"LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"])
1313

1414

15-
def warn_on_missing_glyph(codepoint):
15+
def warn_on_missing_glyph(codepoint, font_name):
1616
_api.warn_external(
17-
"Glyph {} ({}) missing from current font.".format(
17+
"Glyph {} ({}) missing from current font {}.".format(
1818
codepoint,
19-
chr(codepoint).encode("ascii", "namereplace").decode("ascii")))
19+
chr(codepoint).encode("ascii", "namereplace").decode("ascii"),
20+
font_name))
2021
block = ("Hebrew" if 0x0590 <= codepoint <= 0x05ff else
2122
"Arabic" if 0x0600 <= codepoint <= 0x06ff else
2223
"Devanagari" if 0x0900 <= codepoint <= 0x097f else

lib/matplotlib/tests/test_backend_pdf.py

+14
Original file line numberDiff line numberDiff line change
@@ -443,3 +443,17 @@ def test_multi_font_type42():
443443

444444
fig = plt.figure()
445445
fig.text(0.15, 0.475, "There are 几个汉字 in between!")
446+
447+
448+
def test_otf_font_smoke():
449+
# checks that there's no segfault
450+
fp = fm.FontProperties(family=["Noto Sans TC"])
451+
if Path(fm.findfont(fp)).name != "NotoSansTC-Regular.otf":
452+
pytest.skip("Font may be missing")
453+
454+
plt.rc('font', family=['DejaVu Sans', "Noto Sans TC"], size=27)
455+
plt.rc('pdf', fonttype=42)
456+
457+
fig = plt.figure()
458+
fig.text(0.15, 0.475, "There are 几个汉字 in between!")
459+
fig.canvas.draw()

lib/matplotlib/tests/test_ft2font.py

+38-21
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,21 @@ def test_ft2font_positive_hinting_factor():
3030
ft2font.FT2Font(file_name, 0)
3131

3232

33-
def test_fallback_smoke():
34-
fp = fm.FontProperties(family=["WenQuanYi Zen Hei"])
35-
if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc":
36-
pytest.skip("Font wqy-zenhei.ttc may be missing")
37-
38-
fp = fm.FontProperties(family=["Noto Sans CJK JP"])
39-
if Path(fm.findfont(fp)).name != "NotoSansCJK-Regular.ttc":
40-
pytest.skip("Noto Sans CJK JP font may be missing.")
41-
33+
@pytest.mark.parametrize('family_name, file_name',
34+
[("WenQuanYi Zen Hei", "wqy-zenhei.ttc"),
35+
("Noto Sans CJK JP", "NotoSansCJK.ttc"),
36+
("Noto Sans TC", "NotoSansTC-Regular.otf")]
37+
)
38+
def test_fallback_smoke(family_name, file_name):
39+
fp = fm.FontProperties(family=[family_name])
40+
if Path(fm.findfont(fp)).name != file_name:
41+
pytest.skip(f"Font {family_name} ({file_name}) is missing")
4242
plt.rcParams['font.size'] = 20
4343
fig = plt.figure(figsize=(4.75, 1.85))
4444
fig.text(0.05, 0.45, "There are 几个汉字 in between!",
45-
family=['DejaVu Sans', "Noto Sans CJK JP"])
46-
fig.text(0.05, 0.25, "There are 几个汉字 in between!",
47-
family=['DejaVu Sans', "WenQuanYi Zen Hei"])
48-
fig.text(0.05, 0.65, "There are 几个汉字 in between!",
49-
family=["Noto Sans CJK JP"])
45+
family=['DejaVu Sans', family_name])
5046
fig.text(0.05, 0.85, "There are 几个汉字 in between!",
51-
family=["WenQuanYi Zen Hei"])
47+
family=[family_name])
5248

5349
# TODO enable fallback for other backends!
5450
for fmt in ['png', 'raw']: # ["svg", "pdf", "ps"]:
@@ -57,7 +53,8 @@ def test_fallback_smoke():
5753

5854
@pytest.mark.parametrize('family_name, file_name',
5955
[("WenQuanYi Zen Hei", "wqy-zenhei"),
60-
("Noto Sans CJK JP", "NotoSansCJK")]
56+
("Noto Sans CJK JP", "NotoSansCJK"),
57+
("Noto Sans TC", "NotoSansTC-Regular.otf")]
6158
)
6259
@check_figures_equal(extensions=["png", "pdf", "eps", "svg"])
6360
def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name):
@@ -78,25 +75,45 @@ def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name):
7875
fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font)
7976

8077

78+
@pytest.mark.parametrize("font_list",
79+
[['DejaVu Serif', 'DejaVu Sans'],
80+
['DejaVu Sans Mono']],
81+
ids=["two fonts", "one font"])
82+
def test_fallback_missing(recwarn, font_list):
83+
for family_name in font_list:
84+
if not fm.findfont(fm.FontProperties(family=family_name)):
85+
pytest.skip(f"Font {family_name} is missing")
86+
87+
fig = plt.figure()
88+
fig.text(.5, .5, "Hello 🙃 World!", family=font_list)
89+
fig.canvas.draw()
90+
assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
91+
e = "Glyph 128579 (\\N{{UPSIDE-DOWN FACE}}) missing from current font {}."
92+
assert ([(warn.message.args) for warn in recwarn] ==
93+
[(e.format(font),) for font in font_list[::-1]])
94+
95+
8196
@pytest.mark.parametrize(
8297
"family_name, file_name",
8398
[
8499
("WenQuanYi Zen Hei", "wqy-zenhei"),
85100
("Noto Sans CJK JP", "NotoSansCJK"),
101+
("Noto Sans TC", "NotoSansTC-Regular.otf")
86102
],
87103
)
88104
def test__get_fontmap(family_name, file_name):
89105
fp = fm.FontProperties(family=[family_name])
106+
90107
found_file_name = Path(fm.findfont(fp)).name
91108
if file_name not in found_file_name:
92109
pytest.skip(f"Font {family_name} ({file_name}) is missing")
93110

94111
text = "There are 几个汉字 in between!"
95-
ft = fm.get_font(
96-
fm.fontManager._find_fonts_by_props(
97-
fm.FontProperties(family=["DejaVu Sans", family_name])
98-
)
99-
)
112+
113+
# breaking this out to debug segfault
114+
font_properties = fm.FontProperties(family=["DejaVu Sans", family_name])
115+
fonts = fm.fontManager._find_fonts_by_props(font_properties)
116+
ft = fm.get_font(fonts)
100117

101118
fontmap = ft._get_fontmap(text)
102119
for char, font in fontmap.items():

lib/matplotlib/tests/test_text.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,13 @@ def test_pdf_kerning():
817817

818818
def test_unsupported_script(recwarn):
819819
fig = plt.figure()
820-
fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}")
820+
t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}")
821821
fig.canvas.draw()
822822
assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
823823
assert (
824824
[warn.message.args for warn in recwarn] ==
825-
[(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from current font.",),
825+
[(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from current font "
826+
+ f"{t.get_fontname()}.",),
826827
(r"Matplotlib currently does not support Bengali natively.",)])
827828

828829

src/ft2font.cpp

+17-25
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,13 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1,
184184
m_dirty = true;
185185
}
186186

187-
static void ft_glyph_warn(FT_ULong charcode)
187+
static void ft_glyph_warn(FT_ULong charcode, FT_String *family_name)
188188
{
189+
const char* name = (family_name != NULL)?family_name:"UNAVAILABLE";
189190
PyObject *text_helpers = NULL, *tmp = NULL;
191+
190192
if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) ||
191-
!(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "k", charcode))) {
193+
!(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "(k, s)", charcode, name))) {
192194
goto exit;
193195
}
194196
exit:
@@ -199,19 +201,6 @@ static void ft_glyph_warn(FT_ULong charcode)
199201
}
200202
}
201203

202-
static FT_UInt
203-
ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode, bool warn = true)
204-
{
205-
FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
206-
if (glyph_index) {
207-
return glyph_index;
208-
}
209-
if (warn) {
210-
ft_glyph_warn(charcode);
211-
}
212-
return 0;
213-
}
214-
215204
// ft_outline_decomposer should be passed to FT_Outline_Decompose. On the
216205
// first pass, vertices and codes are set to NULL, and index is simply
217206
// incremented for each vertex that should be inserted, so that it is set, at
@@ -515,8 +504,6 @@ void FT2Font::set_text(
515504
char_to_font, glyph_to_font, codepoints[n], flags,
516505
charcode_error, glyph_error, false);
517506
if (!was_found) {
518-
ft_glyph_warn((FT_ULong)codepoints[n]);
519-
520507
// render missing glyph tofu
521508
// come back to top-most font
522509
ft_object_with_glyph = this;
@@ -582,7 +569,7 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
582569
bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font,
583570
glyph_to_font, charcode, flags, charcode_error, glyph_error, true);
584571
if (!was_found) {
585-
ft_glyph_warn(charcode);
572+
ft_glyph_warn((FT_ULong)(charcode), face->family_name);
586573
if (charcode_error) {
587574
throw_ft_error("Could not load charcode", charcode_error);
588575
}
@@ -593,8 +580,10 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
593580
ft_object = ft_object_with_glyph;
594581
} else {
595582
ft_object = this;
596-
FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode);
597-
583+
FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_ULong) charcode);
584+
if (!glyph_index){
585+
ft_glyph_warn((FT_ULong)charcode, face->family_name);
586+
}
598587
if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) {
599588
throw_ft_error("Could not load charcode", error);
600589
}
@@ -644,15 +633,16 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
644633
{
645634
FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
646635

636+
family_names.insert(face->family_name);
647637
if (glyph_index || override) {
638+
648639
charcode_error = FT_Load_Glyph(face, glyph_index, flags);
649640
if (charcode_error) {
650641
return false;
651642
}
652-
653643
FT_Glyph thisGlyph;
654644
glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph);
655-
if (glyph_error) {
645+
if (glyph_error){
656646
return false;
657647
}
658648

@@ -667,7 +657,6 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
667657
parent_glyphs.push_back(thisGlyph);
668658
return true;
669659
}
670-
671660
else {
672661
for (size_t i = 0; i < fallbacks.size(); ++i) {
673662
bool was_found = fallbacks[i]->load_char_with_fallback(
@@ -677,6 +666,10 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
677666
return true;
678667
}
679668
}
669+
for (auto name:family_names){
670+
ft_glyph_warn(charcode, name);
671+
}
672+
family_names = {}; // erase list of missing
680673
return false;
681674
}
682675
}
@@ -721,8 +714,7 @@ FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false)
721714
ft_object = this;
722715
}
723716

724-
// historically, get_char_index never raises a warning
725-
return ft_get_char_index_or_warn(ft_object->get_face(), charcode, false);
717+
return FT_Get_Char_Index(ft_object->get_face(), charcode);
726718
}
727719

728720
void FT2Font::get_width_height(long *width, long *height)

src/ft2font.h

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef MPL_FT2FONT_H
66
#define MPL_FT2FONT_H
77
#include <vector>
8+
#include <set>
89
#include <stdint.h>
910
#include <unordered_map>
1011

@@ -144,6 +145,7 @@ class FT2Font
144145
FT_Vector pen; /* untransformed origin */
145146
std::vector<FT_Glyph> glyphs;
146147
std::vector<FT2Font *> fallbacks;
148+
std::set<FT_String*> family_names;
147149
std::unordered_map<FT_UInt, FT2Font *> glyph_to_font;
148150
std::unordered_map<long, FT2Font *> char_to_font;
149151
FT_BBox bbox;

0 commit comments

Comments
 (0)