Skip to content

Commit 4345d23

Browse files
committed
Improve font spec for SVG font referencing.
This replaces e.g. `"font-family:DejaVu Sans;font-size:12px;font-style:book;font-weight:book;"` by `"font: 400 12px 'DejaVu Sans'"`. Note that the previous font weight was plain wrong...
1 parent f2c5b37 commit 4345d23

File tree

2 files changed

+33
-15
lines changed

2 files changed

+33
-15
lines changed

lib/matplotlib/backends/backend_svg.py

+23-8
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from matplotlib.backends.backend_mixed import MixedModeRenderer
2222
from matplotlib.colors import rgb2hex
2323
from matplotlib.dates import UTC
24-
from matplotlib.font_manager import findfont, get_font
24+
from matplotlib.font_manager import findfont, get_font, ttfFontProperty
2525
from matplotlib.ft2font import LOAD_NO_HINTING
2626
from matplotlib.mathtext import MathTextParser
2727
from matplotlib.path import Path
@@ -94,6 +94,12 @@ def escape_attrib(s):
9494
return s
9595

9696

97+
def _quote_escape_attrib(s):
98+
return ('"' + escape_cdata(s) + '"' if '"' not in s else
99+
"'" + escape_cdata(s) + "'" if "'" not in s else
100+
'"' + escape_attrib(s) + '"')
101+
102+
97103
def short_float_fmt(x):
98104
"""
99105
Create a short string representation of a float, which is %f
@@ -159,8 +165,8 @@ def start(self, tag, attrib={}, **extra):
159165
for k, v in sorted({**attrib, **extra}.items()):
160166
if v:
161167
k = escape_cdata(k)
162-
v = escape_attrib(v)
163-
self.__write(' %s="%s"' % (k, v))
168+
v = _quote_escape_attrib(v)
169+
self.__write(' %s=%s' % (k, v))
164170
self.__open = 1
165171
return len(self.__tags) - 1
166172

@@ -1197,11 +1203,20 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
11971203
# Sort the characters by font, and output one tspan for each.
11981204
spans = OrderedDict()
11991205
for font, fontsize, thetext, new_x, new_y in glyphs:
1200-
style = generate_css({
1201-
'font-size': short_float_fmt(fontsize) + 'px',
1202-
'font-family': font.family_name,
1203-
'font-style': font.style_name.lower(),
1204-
'font-weight': font.style_name.lower()})
1206+
entry = ttfFontProperty(font)
1207+
font_parts = ['font:']
1208+
if entry.style != 'normal':
1209+
font_parts.append(entry.style)
1210+
if entry.variant != 'normal':
1211+
font_parts.append(entry.variant)
1212+
font_parts.extend([
1213+
f'{entry.weight}',
1214+
f'{short_float_fmt(fontsize)}px',
1215+
f'{entry.name!r}', # ensure quoting
1216+
])
1217+
if entry.stretch != 'normal':
1218+
font_parts.extend(['; font-stretch:', entry.stretch])
1219+
style = ' '.join(font_parts)
12051220
if thetext == 32:
12061221
thetext = 0xa0 # non-breaking space
12071222
spans.setdefault(style, []).append((new_x, -new_y, thetext))

lib/matplotlib/tests/test_mathtext.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import io
2-
import os
2+
from pathlib import Path
33
import re
4+
import shlex
5+
from xml.etree import ElementTree as ET
46

57
import numpy as np
68
import pytest
@@ -328,7 +330,7 @@ def test_mathtext_fallback_to_cm_invalid():
328330
("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral'])])
329331
def test_mathtext_fallback(fallback, fontlist):
330332
mpl.font_manager.fontManager.addfont(
331-
os.path.join((os.path.dirname(os.path.realpath(__file__))), 'mpltest.ttf'))
333+
str(Path(__file__).resolve().parent / 'mpltest.ttf'))
332334
mpl.rcParams["svg.fonttype"] = 'none'
333335
mpl.rcParams['mathtext.fontset'] = 'custom'
334336
mpl.rcParams['mathtext.rm'] = 'mpltest'
@@ -342,12 +344,13 @@ def test_mathtext_fallback(fallback, fontlist):
342344
fig, ax = plt.subplots()
343345
fig.text(.5, .5, test_str, fontsize=40, ha='center')
344346
fig.savefig(buff, format="svg")
345-
char_fonts = [
346-
line.split("font-family:")[-1].split(";")[0]
347-
for line in str(buff.getvalue()).split(r"\n") if "tspan" in line
348-
]
347+
tspans = (ET.fromstring(buff.getvalue())
348+
.findall(".//{http://www.w3.org/2000/svg}tspan[@style]"))
349+
# Getting the last element of the style attrib is a close enough
350+
# approximation for parsing the font property.
351+
char_fonts = [shlex.split(tspan.attrib["style"])[-1] for tspan in tspans]
349352
assert char_fonts == fontlist
350-
mpl.font_manager.fontManager.ttflist = mpl.font_manager.fontManager.ttflist[:-1]
353+
mpl.font_manager.fontManager.ttflist.pop()
351354

352355

353356
def test_math_to_image(tmpdir):

0 commit comments

Comments
 (0)