Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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

Sign up
Appearance settings

Commit8f6616c

Browse files
authored
Merge pull request#23638 from tacaswell/svg_generic_fonts
FIX: correctly handle generic font families in svg text-as-text mode
2 parents3feaa5d +6e99a52 commit8f6616c

File tree

3 files changed

+114
-6
lines changed

3 files changed

+114
-6
lines changed

‎lib/matplotlib/backends/backend_svg.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,10 +1153,48 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
11531153
weight=fm.weight_dict[prop.get_weight()]
11541154
ifweight!=400:
11551155
font_parts.append(f'{weight}')
1156+
1157+
def_format_font_name(fn):
1158+
normalize_names= {
1159+
'sans':'sans-serif',
1160+
'sans serif':'sans-serif'
1161+
}
1162+
# A generic font family. We need to do two things:
1163+
# 1. list all of the configured fonts with quoted names
1164+
# 2. append the generic name unquoted
1165+
iffninfm.font_family_aliases:
1166+
# fix spelling of sans-serif
1167+
# we accept 3 ways CSS only supports 1
1168+
fn=normalize_names.get(fn,fn)
1169+
# get all of the font names and fix spelling of sans-serif
1170+
# if it comes back
1171+
aliases= [
1172+
normalize_names.get(_,_)for_in
1173+
fm.FontManager._expand_aliases(fn)
1174+
]
1175+
# make sure the generic name appears at least once
1176+
# duplicate is OK, next layer will deduplicate
1177+
aliases.append(fn)
1178+
1179+
forainaliases:
1180+
# generic font families must not be quoted
1181+
ifainfm.font_family_aliases:
1182+
yielda
1183+
# specific font families must be quoted
1184+
else:
1185+
yieldrepr(a)
1186+
# specific font families must be quoted
1187+
else:
1188+
yieldrepr(fn)
1189+
1190+
def_get_all_names(prop):
1191+
forfinprop.get_family():
1192+
yieldfrom_format_font_name(f)
1193+
11561194
font_parts.extend([
11571195
f'{_short_float_fmt(prop.get_size())}px',
1158-
# ensure quoting
1159-
f'{", ".join(repr(f)forfinprop.get_family())}',
1196+
# ensure quoting and expansion of font names
1197+
", ".join(dict.fromkeys(_get_all_names(prop)))
11601198
])
11611199
style['font']=' '.join(font_parts)
11621200
ifprop.get_stretch()!='normal':

‎lib/matplotlib/font_manager.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,9 +1345,12 @@ def findfont(self, prop, fontext='ttf', directory=None,
13451345
rc_params=tuple(tuple(mpl.rcParams[key])forkeyin [
13461346
"font.serif","font.sans-serif","font.cursive","font.fantasy",
13471347
"font.monospace"])
1348-
returnself._findfont_cached(
1348+
ret=self._findfont_cached(
13491349
prop,fontext,directory,fallback_to_default,rebuild_if_missing,
13501350
rc_params)
1351+
ifisinstance(ret,Exception):
1352+
raiseret
1353+
returnret
13511354

13521355
defget_font_names(self):
13531356
"""Return the list of available fonts."""
@@ -1496,8 +1499,11 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
14961499
returnself.findfont(default_prop,fontext,directory,
14971500
fallback_to_default=False)
14981501
else:
1499-
raiseValueError(f"Failed to find font{prop}, and fallback "
1500-
f"to the default font was disabled")
1502+
# This return instead of raise is intentional, as we wish to
1503+
# cache the resulting exception, which will not occur if it was
1504+
# actually raised.
1505+
returnValueError(f"Failed to find font{prop}, and fallback "
1506+
f"to the default font was disabled")
15011507
else:
15021508
_log.debug('findfont: Matching %s to %s (%r) with score of %f.',
15031509
prop,best_font.name,best_font.fname,best_score)
@@ -1516,7 +1522,10 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
15161522
returnself.findfont(
15171523
prop,fontext,directory,rebuild_if_missing=False)
15181524
else:
1519-
raiseValueError("No valid font could be found")
1525+
# This return instead of raise is intentional, as we wish to
1526+
# cache the resulting exception, which will not occur if it was
1527+
# actually raised.
1528+
returnValueError("No valid font could be found")
15201529

15211530
return_cached_realpath(result)
15221531

‎lib/matplotlib/tests/test_backend_svg.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,3 +527,64 @@ def test_svg_escape():
527527
fig.savefig(fd,format='svg')
528528
buf=fd.getvalue().decode()
529529
assert'<'"&>"'inbuf
530+
531+
532+
@pytest.mark.parametrize("font_str", [
533+
"'DejaVu Sans', 'WenQuanYi Zen Hei', 'Arial', sans-serif",
534+
"'DejaVu Serif', 'WenQuanYi Zen Hei', 'Times New Roman', serif",
535+
"'Arial', 'WenQuanYi Zen Hei', cursive",
536+
"'Impact', 'WenQuanYi Zen Hei', fantasy",
537+
"'DejaVu Sans Mono', 'WenQuanYi Zen Hei', 'Courier New', monospace",
538+
# These do not work because the logic to get the font metrics will not find
539+
# WenQuanYi as the fallback logic stops with the first fallback font:
540+
# "'DejaVu Sans Mono', 'Courier New', 'WenQuanYi Zen Hei', monospace",
541+
# "'DejaVu Sans', 'Arial', 'WenQuanYi Zen Hei', sans-serif",
542+
# "'DejaVu Serif', 'Times New Roman', 'WenQuanYi Zen Hei', serif",
543+
])
544+
@pytest.mark.parametrize("include_generic", [True,False])
545+
deftest_svg_font_string(font_str,include_generic):
546+
fp=fm.FontProperties(family=["WenQuanYi Zen Hei"])
547+
ifPath(fm.findfont(fp)).name!="wqy-zenhei.ttc":
548+
pytest.skip("Font may be missing")
549+
550+
explicit,*rest,generic=map(
551+
lambdax:x.strip("'"),font_str.split(", ")
552+
)
553+
size=len(generic)
554+
ifinclude_generic:
555+
rest=rest+ [generic]
556+
plt.rcParams[f"font.{generic}"]=rest
557+
plt.rcParams["font.size"]=size
558+
plt.rcParams["svg.fonttype"]="none"
559+
560+
fig,ax=plt.subplots()
561+
ifgeneric=="sans-serif":
562+
generic_options= ["sans","sans-serif","sans serif"]
563+
else:
564+
generic_options= [generic]
565+
566+
forgeneric_nameingeneric_options:
567+
# test that fallback works
568+
ax.text(0.5,0.5,"There are 几个汉字 in between!",
569+
family=[explicit,generic_name],ha="center")
570+
# test deduplication works
571+
ax.text(0.5,0.1,"There are 几个汉字 in between!",
572+
family=[explicit,*rest,generic_name],ha="center")
573+
ax.axis("off")
574+
575+
withBytesIO()asfd:
576+
fig.savefig(fd,format="svg")
577+
buf=fd.getvalue()
578+
579+
tree=xml.etree.ElementTree.fromstring(buf)
580+
ns="http://www.w3.org/2000/svg"
581+
text_count=0
582+
fortext_elementintree.findall(f".//{{{ns}}}text"):
583+
text_count+=1
584+
font_info=dict(
585+
map(lambdax:x.strip(),_.strip().split(":"))
586+
for_indict(text_element.items())["style"].split(";")
587+
)["font"]
588+
589+
assertfont_info==f"{size}px{font_str}"
590+
asserttext_count==len(ax.texts)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp