Skip to content

Commit dbebe96

Browse files
authored
Merge pull request #26989 from story645/warning-fontname
MNT: print fontname in missing glyph warning
2 parents 3ecb148 + 15c44a5 commit dbebe96

File tree

6 files changed

+88
-52
lines changed

6 files changed

+88
-52
lines changed

lib/matplotlib/_text_helpers.py

+5-4
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, fontnames):
1616
_api.warn_external(
17-
"Glyph {} ({}) missing from current font.".format(
18-
codepoint,
19-
chr(codepoint).encode("ascii", "namereplace").decode("ascii")))
17+
f"Glyph {codepoint} "
18+
f"({chr(codepoint).encode('ascii', 'namereplace').decode('ascii')}) "
19+
f"missing from font(s) {fontnames}.")
20+
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

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

444444
fig = plt.figure()
445445
fig.text(0.15, 0.475, "There are 几个汉字 in between!")
446+
447+
448+
@pytest.mark.parametrize('family_name, file_name',
449+
[("Noto Sans", "NotoSans-Regular.otf"),
450+
("FreeMono", "FreeMono.otf")])
451+
def test_otf_font_smoke(family_name, file_name):
452+
# checks that there's no segfault
453+
fp = fm.FontProperties(family=[family_name])
454+
if Path(fm.findfont(fp)).name != file_name:
455+
pytest.skip(f"Font {family_name} may be missing")
456+
457+
plt.rc('font', family=[family_name], size=27)
458+
459+
fig = plt.figure()
460+
fig.text(0.15, 0.475, "Привет мир!")
461+
fig.savefig(io.BytesIO(), format="pdf")

lib/matplotlib/tests/test_ft2font.py

+29-17
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,11 +75,27 @@ 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+
fig = plt.figure()
84+
fig.text(.5, .5, "Hello 🙃 World!", family=font_list)
85+
fig.canvas.draw()
86+
assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
87+
# not sure order is guaranteed on the font listing so
88+
assert recwarn[0].message.args[0].startswith(
89+
"Glyph 128579 (\\N{UPSIDE-DOWN FACE}) missing from font(s)")
90+
assert all([font in recwarn[0].message.args[0] for font in font_list])
91+
92+
8193
@pytest.mark.parametrize(
8294
"family_name, file_name",
8395
[
8496
("WenQuanYi Zen Hei", "wqy-zenhei"),
8597
("Noto Sans CJK JP", "NotoSansCJK"),
98+
("Noto Sans TC", "NotoSansTC-Regular.otf")
8699
],
87100
)
88101
def test__get_fontmap(family_name, file_name):
@@ -97,7 +110,6 @@ def test__get_fontmap(family_name, file_name):
97110
fm.FontProperties(family=["DejaVu Sans", family_name])
98111
)
99112
)
100-
101113
fontmap = ft._get_fontmap(text)
102114
for char, font in fontmap.items():
103115
if ord(char) > 127:

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 font(s) "
826+
+ f"{t.get_fontname()}.",),
826827
(r"Matplotlib currently does not support Bengali natively.",)])
827828

828829

src/ft2font.cpp

+33-29
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <algorithm>
66
#include <iterator>
7+
#include <set>
78
#include <sstream>
89
#include <stdexcept>
910
#include <string>
@@ -184,11 +185,20 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1,
184185
m_dirty = true;
185186
}
186187

187-
static void ft_glyph_warn(FT_ULong charcode)
188+
static void ft_glyph_warn(FT_ULong charcode, std::set<FT_String*> family_names)
188189
{
189190
PyObject *text_helpers = NULL, *tmp = NULL;
191+
std::set<FT_String*>::iterator it = family_names.begin();
192+
std::stringstream ss;
193+
ss<<*it;
194+
while(++it != family_names.end()){
195+
ss<<", "<<*it;
196+
}
197+
190198
if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) ||
191-
!(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "k", charcode))) {
199+
!(tmp = PyObject_CallMethod(text_helpers,
200+
"warn_on_missing_glyph", "(k, s)",
201+
charcode, ss.str().c_str()))) {
192202
goto exit;
193203
}
194204
exit:
@@ -199,19 +209,6 @@ static void ft_glyph_warn(FT_ULong charcode)
199209
}
200210
}
201211

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-
215212
// ft_outline_decomposer should be passed to FT_Outline_Decompose. On the
216213
// first pass, vertices and codes are set to NULL, and index is simply
217214
// incremented for each vertex that should be inserted, so that it is set, at
@@ -510,13 +507,13 @@ void FT2Font::set_text(
510507
FT_Pos last_advance;
511508

512509
FT_Error charcode_error, glyph_error;
510+
std::set<FT_String*> glyph_seen_fonts;
513511
FT2Font *ft_object_with_glyph = this;
514512
bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs,
515513
char_to_font, glyph_to_font, codepoints[n], flags,
516-
charcode_error, glyph_error, false);
514+
charcode_error, glyph_error, glyph_seen_fonts, false);
517515
if (!was_found) {
518-
ft_glyph_warn((FT_ULong)codepoints[n]);
519-
516+
ft_glyph_warn((FT_ULong)codepoints[n], glyph_seen_fonts);
520517
// render missing glyph tofu
521518
// come back to top-most font
522519
ft_object_with_glyph = this;
@@ -570,6 +567,7 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
570567
// if this is parent FT2Font, cache will be filled in 2 ways:
571568
// 1. set_text was previously called
572569
// 2. set_text was not called and fallback was enabled
570+
std::set <FT_String *> glyph_seen_fonts;
573571
if (fallback && char_to_font.find(charcode) != char_to_font.end()) {
574572
ft_object = char_to_font[charcode];
575573
// since it will be assigned to ft_object anyway
@@ -579,10 +577,12 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
579577
FT_UInt final_glyph_index;
580578
FT_Error charcode_error, glyph_error;
581579
FT2Font *ft_object_with_glyph = this;
582-
bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font,
583-
glyph_to_font, charcode, flags, charcode_error, glyph_error, true);
580+
bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index,
581+
glyphs, char_to_font, glyph_to_font,
582+
charcode, flags, charcode_error, glyph_error,
583+
glyph_seen_fonts, true);
584584
if (!was_found) {
585-
ft_glyph_warn(charcode);
585+
ft_glyph_warn(charcode, glyph_seen_fonts);
586586
if (charcode_error) {
587587
throw_ft_error("Could not load charcode", charcode_error);
588588
}
@@ -592,9 +592,13 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
592592
}
593593
ft_object = ft_object_with_glyph;
594594
} else {
595+
//no fallback case
595596
ft_object = this;
596-
FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode);
597-
597+
FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_ULong) charcode);
598+
if (!glyph_index){
599+
glyph_seen_fonts.insert((face != NULL)?face->family_name: NULL);
600+
ft_glyph_warn((FT_ULong)charcode, glyph_seen_fonts);
601+
}
598602
if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) {
599603
throw_ft_error("Could not load charcode", error);
600604
}
@@ -640,16 +644,17 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
640644
FT_Int32 flags,
641645
FT_Error &charcode_error,
642646
FT_Error &glyph_error,
647+
std::set<FT_String*> &glyph_seen_fonts,
643648
bool override = false)
644649
{
645650
FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
651+
glyph_seen_fonts.insert(face->family_name);
646652

647653
if (glyph_index || override) {
648654
charcode_error = FT_Load_Glyph(face, glyph_index, flags);
649655
if (charcode_error) {
650656
return false;
651657
}
652-
653658
FT_Glyph thisGlyph;
654659
glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph);
655660
if (glyph_error) {
@@ -667,12 +672,12 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
667672
parent_glyphs.push_back(thisGlyph);
668673
return true;
669674
}
670-
671675
else {
672676
for (size_t i = 0; i < fallbacks.size(); ++i) {
673677
bool was_found = fallbacks[i]->load_char_with_fallback(
674-
ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font,
675-
parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, override);
678+
ft_object_with_glyph, final_glyph_index, parent_glyphs,
679+
parent_char_to_font, parent_glyph_to_font, charcode, flags,
680+
charcode_error, glyph_error, glyph_seen_fonts, override);
676681
if (was_found) {
677682
return true;
678683
}
@@ -721,8 +726,7 @@ FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false)
721726
ft_object = this;
722727
}
723728

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

728732
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

@@ -91,6 +92,7 @@ class FT2Font
9192
FT_Int32 flags,
9293
FT_Error &charcode_error,
9394
FT_Error &glyph_error,
95+
std::set<FT_String*> &glyph_seen_fonts,
9496
bool override);
9597
void load_glyph(FT_UInt glyph_index, FT_Int32 flags, FT2Font *&ft_object, bool fallback);
9698
void load_glyph(FT_UInt glyph_index, FT_Int32 flags);

0 commit comments

Comments
 (0)