Skip to content

Commit 8ca754a

Browse files
committed
print fontname in missing glyph warning + all fallback fonts missing glyph
catch missing glyph errors in font tests add :filter-warning: option to plot directive
1 parent e394940 commit 8ca754a

File tree

9 files changed

+65
-30
lines changed

9 files changed

+65
-30
lines changed

galleries/users_explain/text/fonts.py

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
:include-source:
183183
:caption: The string "There are 几个汉字 in between!" rendered with 2 fonts.
184184
185+
185186
fig, ax = plt.subplots()
186187
ax.text(
187188
.5, .5, "There are 几个汉字 in between!",

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/sphinxext/plot_directive.py

+27-11
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@
7878
figure. This overwrites the caption given in the content, when the plot
7979
is generated from a file.
8080
81+
``:filter-warning:`` : str
82+
When specified, will ignore warnings that match the input string, which is
83+
a warnings filter `message regex <msg>`_ string.
84+
85+
.. _msg: https://docs.python.org/3/library/warnings.html#the-warnings-filter
86+
8187
Additionally, this directive supports all the options of the `image directive
8288
<https://docutils.sourceforge.io/docs/ref/rst/directives.html#image>`_,
8389
except for ``:target:`` (since plot will add its own target). These include
@@ -177,6 +183,7 @@
177183
import sys
178184
import textwrap
179185
import traceback
186+
import warnings
180187

181188
from docutils.parsers.rst import directives, Directive
182189
from docutils.parsers.rst.directives.images import Image
@@ -221,6 +228,10 @@ def _option_format(arg):
221228
return directives.choice(arg, ('python', 'doctest'))
222229

223230

231+
def _option_string(arg):
232+
return arg if arg.strip() else False
233+
234+
224235
def mark_plot_labels(app, document):
225236
"""
226237
To make plots referenceable, we need to move the reference from the
@@ -271,7 +282,8 @@ class PlotDirective(Directive):
271282
'context': _option_context,
272283
'nofigs': directives.flag,
273284
'caption': directives.unchanged,
274-
}
285+
'filter-warning': _option_string,
286+
}
275287

276288
def run(self):
277289
"""Run the plot directive."""
@@ -854,16 +866,20 @@ def run(arguments, content, options, state_machine, state, lineno):
854866

855867
# make figures
856868
try:
857-
results = render_figures(code=code,
858-
code_path=source_file_name,
859-
output_dir=build_dir,
860-
output_base=output_base,
861-
context=keep_context,
862-
function_name=function_name,
863-
config=config,
864-
context_reset=context_opt == 'reset',
865-
close_figs=context_opt == 'close-figs',
866-
code_includes=source_file_includes)
869+
with warnings.catch_warnings(record=True) as w:
870+
if msg := options.get('filter-warning', False):
871+
warnings.filterwarnings('ignore', message=msg)
872+
873+
results = render_figures(code=code,
874+
code_path=source_file_name,
875+
output_dir=build_dir,
876+
output_base=output_base,
877+
context=keep_context,
878+
function_name=function_name,
879+
config=config,
880+
context_reset=context_opt == 'reset',
881+
close_figs=context_opt == 'close-figs',
882+
code_includes=source_file_includes)
867883
errors = []
868884
except PlotError as err:
869885
reporter = state.memo.reporter

lib/matplotlib/tests/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from pathlib import Path
2+
import pytest
23

4+
pytest.register_assert_rewrite("matplotlib.testing")
35

46
# Check that the test directories exist.
57
if not (Path(__file__).parent / 'baseline_images').exists():

lib/matplotlib/tests/test_ft2font.py

+11
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name):
7878
fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font)
7979

8080

81+
def test_fallback_cascade_missing(recwarn):
82+
font_list = ['DejaVu Serif', 'DejaVu Sans']
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+
e = "Glyph 128579 (\\N{{UPSIDE-DOWN FACE}}) missing from current font {}."
88+
assert ([(warn.message.args) for warn in recwarn] ==
89+
[(e.format(font),) for font in font_list])
90+
91+
8192
@pytest.mark.parametrize(
8293
"family_name, file_name",
8394
[

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

pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,6 @@ ignore_messages = [
288288
"Duplicate implicit target name: \".*\".",
289289
"Duplicate explicit target name: \".*\".",
290290
]
291+
292+
[pytest]
293+
python_files = lib\matplotlib\tests\*.py

src/ft2font.cpp

+13-14
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:
@@ -207,7 +209,7 @@ ft_get_char_index_or_warn(FT_Face face, FT_ULong charcode, bool warn = true)
207209
return glyph_index;
208210
}
209211
if (warn) {
210-
ft_glyph_warn(charcode);
212+
ft_glyph_warn(charcode, face->family_name);
211213
}
212214
return 0;
213215
}
@@ -515,8 +517,6 @@ void FT2Font::set_text(
515517
char_to_font, glyph_to_font, codepoints[n], flags,
516518
charcode_error, glyph_error, false);
517519
if (!was_found) {
518-
ft_glyph_warn((FT_ULong)codepoints[n]);
519-
520520
// render missing glyph tofu
521521
// come back to top-most font
522522
ft_object_with_glyph = this;
@@ -582,7 +582,6 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
582582
bool was_found = load_char_with_fallback(ft_object_with_glyph, final_glyph_index, glyphs, char_to_font,
583583
glyph_to_font, charcode, flags, charcode_error, glyph_error, true);
584584
if (!was_found) {
585-
ft_glyph_warn(charcode);
586585
if (charcode_error) {
587586
throw_ft_error("Could not load charcode", charcode_error);
588587
}
@@ -609,7 +608,7 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool
609608

610609
bool FT2Font::get_char_fallback_index(FT_ULong charcode, int& index) const
611610
{
612-
FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
611+
FT_UInt glyph_index = ft_get_char_index_or_warn(face, charcode);
613612
if (glyph_index) {
614613
// -1 means the host has the char and we do not need to fallback
615614
index = -1;
@@ -642,17 +641,15 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
642641
FT_Error &glyph_error,
643642
bool override = false)
644643
{
645-
FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
646-
644+
FT_UInt glyph_index = ft_get_char_index_or_warn(face, charcode, false);
645+
family_names.push_back(face->family_name);
647646
if (glyph_index || override) {
648-
charcode_error = FT_Load_Glyph(face, glyph_index, flags);
649-
if (charcode_error) {
647+
if ((charcode_error=FT_Load_Glyph(face, glyph_index, flags))) {
650648
return false;
651649
}
652650

653651
FT_Glyph thisGlyph;
654-
glyph_error = FT_Get_Glyph(face->glyph, &thisGlyph);
655-
if (glyph_error) {
652+
if ((glyph_error= FT_Get_Glyph(face->glyph, &thisGlyph))){
656653
return false;
657654
}
658655

@@ -667,7 +664,6 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
667664
parent_glyphs.push_back(thisGlyph);
668665
return true;
669666
}
670-
671667
else {
672668
for (size_t i = 0; i < fallbacks.size(); ++i) {
673669
bool was_found = fallbacks[i]->load_char_with_fallback(
@@ -677,6 +673,9 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph,
677673
return true;
678674
}
679675
}
676+
for (size_t j=family_names.size()-1; j>0; --j){
677+
ft_glyph_warn(charcode, family_names[j]);
678+
}
680679
return false;
681680
}
682681
}

src/ft2font.h

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class FT2Font
144144
FT_Vector pen; /* untransformed origin */
145145
std::vector<FT_Glyph> glyphs;
146146
std::vector<FT2Font *> fallbacks;
147+
std::vector<FT_String *> family_names;
147148
std::unordered_map<FT_UInt, FT2Font *> glyph_to_font;
148149
std::unordered_map<long, FT2Font *> char_to_font;
149150
FT_BBox bbox;

0 commit comments

Comments
 (0)