diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 18bfb550c90b..8625186ba8ec 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -12,11 +12,12 @@ "LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"]) -def warn_on_missing_glyph(codepoint): +def warn_on_missing_glyph(codepoint, fontnames): _api.warn_external( - "Glyph {} ({}) missing from current font.".format( - codepoint, - chr(codepoint).encode("ascii", "namereplace").decode("ascii"))) + f"Glyph {codepoint} " + f"({chr(codepoint).encode('ascii', 'namereplace').decode('ascii')}) " + f"missing from font(s) {fontnames}.") + block = ("Hebrew" if 0x0590 <= codepoint <= 0x05ff else "Arabic" if 0x0600 <= codepoint <= 0x06ff else "Devanagari" if 0x0900 <= codepoint <= 0x097f else diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 68ce02cc366c..ae17c7a4a4e3 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -443,3 +443,19 @@ def test_multi_font_type42(): fig = plt.figure() fig.text(0.15, 0.475, "There are 几个汉字 in between!") + + +@pytest.mark.parametrize('family_name, file_name', + [("Noto Sans", "NotoSans-Regular.otf"), + ("FreeMono", "FreeMono.otf")]) +def test_otf_font_smoke(family_name, file_name): + # checks that there's no segfault + fp = fm.FontProperties(family=[family_name]) + if Path(fm.findfont(fp)).name != file_name: + pytest.skip(f"Font {family_name} may be missing") + + plt.rc('font', family=[family_name], size=27) + + fig = plt.figure() + fig.text(0.15, 0.475, "Привет мир!") + fig.savefig(io.BytesIO(), format="pdf") diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index 0139bdf41526..2e2ce673f4b8 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -30,25 +30,21 @@ def test_ft2font_positive_hinting_factor(): ft2font.FT2Font(file_name, 0) -def test_fallback_smoke(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font wqy-zenhei.ttc may be missing") - - fp = fm.FontProperties(family=["Noto Sans CJK JP"]) - if Path(fm.findfont(fp)).name != "NotoSansCJK-Regular.ttc": - pytest.skip("Noto Sans CJK JP font may be missing.") - +@pytest.mark.parametrize('family_name, file_name', + [("WenQuanYi Zen Hei", "wqy-zenhei.ttc"), + ("Noto Sans CJK JP", "NotoSansCJK.ttc"), + ("Noto Sans TC", "NotoSansTC-Regular.otf")] + ) +def test_fallback_smoke(family_name, file_name): + fp = fm.FontProperties(family=[family_name]) + if Path(fm.findfont(fp)).name != file_name: + pytest.skip(f"Font {family_name} ({file_name}) is missing") plt.rcParams['font.size'] = 20 fig = plt.figure(figsize=(4.75, 1.85)) fig.text(0.05, 0.45, "There are 几个汉字 in between!", - family=['DejaVu Sans', "Noto Sans CJK JP"]) - fig.text(0.05, 0.25, "There are 几个汉字 in between!", - family=['DejaVu Sans', "WenQuanYi Zen Hei"]) - fig.text(0.05, 0.65, "There are 几个汉字 in between!", - family=["Noto Sans CJK JP"]) + family=['DejaVu Sans', family_name]) fig.text(0.05, 0.85, "There are 几个汉字 in between!", - family=["WenQuanYi Zen Hei"]) + family=[family_name]) # TODO enable fallback for other backends! for fmt in ['png', 'raw']: # ["svg", "pdf", "ps"]: @@ -57,7 +53,8 @@ def test_fallback_smoke(): @pytest.mark.parametrize('family_name, file_name', [("WenQuanYi Zen Hei", "wqy-zenhei"), - ("Noto Sans CJK JP", "NotoSansCJK")] + ("Noto Sans CJK JP", "NotoSansCJK"), + ("Noto Sans TC", "NotoSansTC-Regular.otf")] ) @check_figures_equal(extensions=["png", "pdf", "eps", "svg"]) 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): fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font) +@pytest.mark.parametrize("font_list", + [['DejaVu Serif', 'DejaVu Sans'], + ['DejaVu Sans Mono']], + ids=["two fonts", "one font"]) +def test_fallback_missing(recwarn, font_list): + fig = plt.figure() + fig.text(.5, .5, "Hello 🙃 World!", family=font_list) + fig.canvas.draw() + assert all(isinstance(warn.message, UserWarning) for warn in recwarn) + # not sure order is guaranteed on the font listing so + assert recwarn[0].message.args[0].startswith( + "Glyph 128579 (\\N{UPSIDE-DOWN FACE}) missing from font(s)") + assert all([font in recwarn[0].message.args[0] for font in font_list]) + + @pytest.mark.parametrize( "family_name, file_name", [ ("WenQuanYi Zen Hei", "wqy-zenhei"), ("Noto Sans CJK JP", "NotoSansCJK"), + ("Noto Sans TC", "NotoSansTC-Regular.otf") ], ) def test__get_fontmap(family_name, file_name): @@ -97,7 +110,6 @@ def test__get_fontmap(family_name, file_name): fm.FontProperties(family=["DejaVu Sans", family_name]) ) ) - fontmap = ft._get_fontmap(text) for char, font in fontmap.items(): if ord(char) > 127: diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index 7222d5b00cf7..3c14357e45f3 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -817,12 +817,13 @@ def test_pdf_kerning(): def test_unsupported_script(recwarn): fig = plt.figure() - fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}") + t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}") fig.canvas.draw() assert all(isinstance(warn.message, UserWarning) for warn in recwarn) assert ( [warn.message.args for warn in recwarn] == - [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from current font.",), + [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from font(s) " + + f"{t.get_fontname()}.",), (r"Matplotlib currently does not support Bengali natively.",)]) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index fbc00dc69696..b20f224715bf 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -184,11 +185,20 @@ FT2Image::draw_rect_filled(unsigned long x0, unsigned long y0, unsigned long x1, m_dirty = true; } -static void ft_glyph_warn(FT_ULong charcode) +static void ft_glyph_warn(FT_ULong charcode, std::set family_names) { PyObject *text_helpers = NULL, *tmp = NULL; + std::set::iterator it = family_names.begin(); + std::stringstream ss; + ss<<*it; + while(++it != family_names.end()){ + ss<<", "<<*it; + } + if (!(text_helpers = PyImport_ImportModule("matplotlib._text_helpers")) || - !(tmp = PyObject_CallMethod(text_helpers, "warn_on_missing_glyph", "k", charcode))) { + !(tmp = PyObject_CallMethod(text_helpers, + "warn_on_missing_glyph", "(k, s)", + charcode, ss.str().c_str()))) { goto exit; } exit: @@ -199,19 +209,6 @@ static void ft_glyph_warn(FT_ULong charcode) } } -static FT_UInt -ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode, bool warn = true) -{ - FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); - if (glyph_index) { - return glyph_index; - } - if (warn) { - ft_glyph_warn(charcode); - } - return 0; -} - // ft_outline_decomposer should be passed to FT_Outline_Decompose. On the // first pass, vertices and codes are set to NULL, and index is simply // incremented for each vertex that should be inserted, so that it is set, at @@ -510,13 +507,13 @@ void FT2Font::set_text( FT_Pos last_advance; FT_Error charcode_error, glyph_error; + std::set glyph_seen_fonts; FT2Font *ft_object_with_glyph = this; bool was_found = load_char_with_fallback(ft_object_with_glyph, glyph_index, glyphs, char_to_font, glyph_to_font, codepoints[n], flags, - charcode_error, glyph_error, false); + charcode_error, glyph_error, glyph_seen_fonts, false); if (!was_found) { - ft_glyph_warn((FT_ULong)codepoints[n]); - + ft_glyph_warn((FT_ULong)codepoints[n], glyph_seen_fonts); // render missing glyph tofu // come back to top-most font ft_object_with_glyph = this; @@ -570,6 +567,7 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool // if this is parent FT2Font, cache will be filled in 2 ways: // 1. set_text was previously called // 2. set_text was not called and fallback was enabled + std::set glyph_seen_fonts; if (fallback && char_to_font.find(charcode) != char_to_font.end()) { ft_object = char_to_font[charcode]; // 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 FT_UInt final_glyph_index; FT_Error charcode_error, glyph_error; FT2Font *ft_object_with_glyph = this; - bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font, - glyph_to_font, charcode, flags, charcode_error, glyph_error, true); + bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, + glyphs, char_to_font, glyph_to_font, + charcode, flags, charcode_error, glyph_error, + glyph_seen_fonts, true); if (!was_found) { - ft_glyph_warn(charcode); + ft_glyph_warn(charcode, glyph_seen_fonts); if (charcode_error) { throw_ft_error("Could not load charcode", charcode_error); } @@ -592,9 +592,13 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool } ft_object = ft_object_with_glyph; } else { + //no fallback case ft_object = this; - FT_UInt glyph_index = ft_get_char_index_or_warn(face, (FT_ULong)charcode); - + FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_ULong) charcode); + if (!glyph_index){ + glyph_seen_fonts.insert((face != NULL)?face->family_name: NULL); + ft_glyph_warn((FT_ULong)charcode, glyph_seen_fonts); + } if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { throw_ft_error("Could not load charcode", error); } @@ -640,16 +644,17 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, FT_Int32 flags, FT_Error &charcode_error, FT_Error &glyph_error, + std::set &glyph_seen_fonts, bool override = false) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); + glyph_seen_fonts.insert(face->family_name); if (glyph_index || override) { charcode_error = FT_Load_Glyph(face, glyph_index, flags); if (charcode_error) { return false; } - FT_Glyph thisGlyph; glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph); if (glyph_error) { @@ -667,12 +672,12 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, parent_glyphs.push_back(thisGlyph); return true; } - else { for (size_t i = 0; i < fallbacks.size(); ++i) { bool was_found = fallbacks[i]->load_char_with_fallback( - ft_object_with_glyph, final_glyph_index, parent_glyphs, parent_char_to_font, - parent_glyph_to_font, charcode, flags, charcode_error, glyph_error, override); + ft_object_with_glyph, final_glyph_index, parent_glyphs, + parent_char_to_font, parent_glyph_to_font, charcode, flags, + charcode_error, glyph_error, glyph_seen_fonts, override); if (was_found) { return true; } @@ -721,8 +726,7 @@ FT_UInt FT2Font::get_char_index(FT_ULong charcode, bool fallback = false) ft_object = this; } - // historically, get_char_index never raises a warning - return ft_get_char_index_or_warn(ft_object->get_face(), charcode, false); + return FT_Get_Char_Index(ft_object->get_face(), charcode); } void FT2Font::get_width_height(long *width, long *height) diff --git a/src/ft2font.h b/src/ft2font.h index d566c3f9bd9d..69dafb66bdca 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -5,6 +5,7 @@ #ifndef MPL_FT2FONT_H #define MPL_FT2FONT_H #include +#include #include #include @@ -91,6 +92,7 @@ class FT2Font FT_Int32 flags, FT_Error &charcode_error, FT_Error &glyph_error, + std::set &glyph_seen_fonts, bool override); void load_glyph(FT_UInt glyph_index, FT_Int32 flags, FT2Font *&ft_object, bool fallback); void load_glyph(FT_UInt glyph_index, FT_Int32 flags); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy