Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4ece8ab

Browse filesBrowse files
story645QuLogictacaswell
committed
prints font name in missing glyph warning
lists all fallback fonts if glyph missing in all fonts removed ft_get_char_index_or_warn added a test for otf fonts on pdf + some windows fonts to tests Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com> Co-authored-by: Thomas A Caswell <tcaswell@gmail.com>
1 parent 309981d commit 4ece8ab
Copy full SHA for 4ece8ab

File tree

6 files changed

+89
-52
lines changed
Filter options

6 files changed

+89
-52
lines changed

‎lib/matplotlib/_text_helpers.py

Copy file name to clipboardExpand all lines: lib/matplotlib/_text_helpers.py
+5-4Lines changed: 5 additions & 4 deletions
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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_backend_pdf.py
+16Lines changed: 16 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_ft2font.py
+29-17Lines changed: 29 additions & 17 deletions
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

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_text.py
+3-2Lines changed: 3 additions & 2 deletions
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

Copy file name to clipboardExpand all lines: src/ft2font.cpp
+34-29Lines changed: 34 additions & 29 deletions
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,19 +644,21 @@ 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) {
654+
648655
charcode_error = FT_Load_Glyph(face, glyph_index, flags);
649656
if (charcode_error) {
650657
return false;
651658
}
652-
653659
FT_Glyph thisGlyph;
654660
glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph);
655-
if (glyph_error) {
661+
if (glyph_error){
656662
return false;
657663
}
658664

@@ -667,12 +673,12 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
667673
parent_glyphs.push_back(thisGlyph);
668674
return true;
669675
}
670-
671676
else {
672677
for (size_t i = 0; i < fallbacks.size(); ++i) {
673678
bool was_found = fallbacks[i]->load_char_with_fallback(
674679
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);
680+
parent_glyph_to_font, charcode, flags, charcode_error, glyph_error,
681+
glyph_seen_fonts, override);
676682
if (was_found) {
677683
return true;
678684
}
@@ -721,8 +727,7 @@ FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false)
721727
ft_object = this;
722728
}
723729

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

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

‎src/ft2font.h

Copy file name to clipboardExpand all lines: src/ft2font.h
+2Lines changed: 2 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.