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

Appearance settings

Commit ed32a04

Browse filesBrowse files
authored
Merge pull request #19253 from anntzer/svgfontspec
Improve font spec for SVG font referencing.
2 parents 78e3b5f + 92b1d40 commit ed32a04
Copy full SHA for ed32a04

File tree

Expand file treeCollapse file tree

3 files changed

+56
-41
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+56
-41
lines changed

‎lib/matplotlib/backends/backend_svg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_svg.py
+44-30Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
from PIL import Image
1414

1515
import matplotlib as mpl
16-
from matplotlib import _api, cbook
16+
from matplotlib import _api, cbook, font_manager as fm
1717
from matplotlib.backend_bases import (
1818
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
1919
RendererBase)
2020
from matplotlib.backends.backend_mixed import MixedModeRenderer
2121
from matplotlib.colors import rgb2hex
2222
from matplotlib.dates import UTC
23-
from matplotlib.font_manager import findfont, get_font
24-
from matplotlib.ft2font import LOAD_NO_HINTING
2523
from matplotlib.mathtext import MathTextParser
2624
from matplotlib.path import Path
2725
from matplotlib import _path
@@ -93,6 +91,12 @@ def escape_attrib(s):
9391
return s
9492

9593

94+
def _quote_escape_attrib(s):
95+
return ('"' + escape_cdata(s) + '"' if '"' not in s else
96+
"'" + escape_cdata(s) + "'" if "'" not in s else
97+
'"' + escape_attrib(s) + '"')
98+
99+
96100
def short_float_fmt(x):
97101
"""
98102
Create a short string representation of a float, which is %f
@@ -158,8 +162,8 @@ def start(self, tag, attrib={}, **extra):
158162
for k, v in {**attrib, **extra}.items():
159163
if v:
160164
k = escape_cdata(k)
161-
v = escape_attrib(v)
162-
self.__write(' %s="%s"' % (k, v))
165+
v = _quote_escape_attrib(v)
166+
self.__write(' %s=%s' % (k, v))
163167
self.__open = 1
164168
return len(self.__tags) - 1
165169

@@ -261,15 +265,7 @@ def generate_transform(transform_list=[]):
261265

262266

263267
def generate_css(attrib={}):
264-
if attrib:
265-
output = StringIO()
266-
attrib = attrib.items()
267-
for k, v in attrib:
268-
k = escape_attrib(k)
269-
v = escape_attrib(v)
270-
output.write("%s:%s;" % (k, v))
271-
return output.getvalue()
272-
return ''
268+
return "; ".join(f"{k}: {v}" for k, v in attrib.items())
273269

274270

275271
_capstyle_d = {'projecting': 'square', 'butt': 'butt', 'round': 'round'}
@@ -462,8 +458,8 @@ def _make_flip_transform(self, transform):
462458
.translate(0.0, self.height))
463459

464460
def _get_font(self, prop):
465-
fname = findfont(prop)
466-
font = get_font(fname)
461+
fname = fm.findfont(prop)
462+
font = fm.get_font(fname)
467463
font.clear()
468464
size = prop.get_size_in_points()
469465
font.set_size(size, 72.0)
@@ -1106,16 +1102,23 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
11061102
style['opacity'] = short_float_fmt(alpha)
11071103

11081104
if not ismath:
1109-
font = self._get_font(prop)
1110-
font.set_text(s, 0.0, flags=LOAD_NO_HINTING)
1111-
11121105
attrib = {}
1113-
style['font-family'] = str(font.family_name)
1114-
style['font-weight'] = str(prop.get_weight()).lower()
1115-
style['font-stretch'] = str(prop.get_stretch()).lower()
1116-
style['font-style'] = prop.get_style().lower()
1117-
# Must add "px" to workaround a Firefox bug
1118-
style['font-size'] = short_float_fmt(prop.get_size()) + 'px'
1106+
1107+
font_parts = []
1108+
if prop.get_style() != 'normal':
1109+
font_parts.append(prop.get_style())
1110+
if prop.get_variant() != 'normal':
1111+
font_parts.append(prop.get_variant())
1112+
weight = fm.weight_dict[prop.get_weight()]
1113+
if weight != 400:
1114+
font_parts.append(f'{weight}')
1115+
font_parts.extend([
1116+
f'{short_float_fmt(prop.get_size())}px',
1117+
f'{prop.get_family()[0]!r}', # ensure quoting
1118+
])
1119+
style['font'] = ' '.join(font_parts)
1120+
if prop.get_stretch() != 'normal':
1121+
style['font-stretch'] = prop.get_stretch()
11191122
attrib['style'] = generate_css(style)
11201123

11211124
if mtext and (angle == 0 or mtext.get_rotation_mode() == "anchor"):
@@ -1175,11 +1178,22 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
11751178
# Sort the characters by font, and output one tspan for each.
11761179
spans = {}
11771180
for font, fontsize, thetext, new_x, new_y in glyphs:
1178-
style = generate_css({
1179-
'font-size': short_float_fmt(fontsize) + 'px',
1180-
'font-family': font.family_name,
1181-
'font-style': font.style_name.lower(),
1182-
'font-weight': font.style_name.lower()})
1181+
entry = fm.ttfFontProperty(font)
1182+
font_parts = []
1183+
if entry.style != 'normal':
1184+
font_parts.append(entry.style)
1185+
if entry.variant != 'normal':
1186+
font_parts.append(entry.variant)
1187+
if entry.weight != 400:
1188+
font_parts.append(f'{entry.weight}')
1189+
font_parts.extend([
1190+
f'{short_float_fmt(fontsize)}px',
1191+
f'{entry.name!r}', # ensure quoting
1192+
])
1193+
style = {'font': ' '.join(font_parts)}
1194+
if entry.stretch != 'normal':
1195+
style['font-stretch'] = entry.stretch
1196+
style = generate_css(style)
11831197
if thetext == 32:
11841198
thetext = 0xa0 # non-breaking space
11851199
spans.setdefault(style, []).append((new_x, -new_y, thetext))

‎lib/matplotlib/tests/test_backend_svg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_backend_svg.py
+2-4Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def test_unicode_won():
216216

217217

218218
def test_svgnone_with_data_coordinates():
219-
plt.rcParams['svg.fonttype'] = 'none'
219+
plt.rcParams.update({'svg.fonttype': 'none', 'font.stretch': 'condensed'})
220220
expected = 'Unlikely to appear by chance'
221221

222222
fig, ax = plt.subplots()
@@ -229,9 +229,7 @@ def test_svgnone_with_data_coordinates():
229229
fd.seek(0)
230230
buf = fd.read().decode()
231231

232-
assert expected in buf
233-
for prop in ["family", "weight", "stretch", "style", "size"]:
234-
assert f"font-{prop}:" in buf
232+
assert expected in buf and "condensed" in buf
235233

236234

237235
def test_gid():

‎lib/matplotlib/tests/test_mathtext.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_mathtext.py
+10-7Lines changed: 10 additions & 7 deletions
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
@@ -343,7 +345,7 @@ def test_mathtext_fallback_invalid():
343345
("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral'])])
344346
def test_mathtext_fallback(fallback, fontlist):
345347
mpl.font_manager.fontManager.addfont(
346-
os.path.join((os.path.dirname(os.path.realpath(__file__))), 'mpltest.ttf'))
348+
str(Path(__file__).resolve().parent / 'mpltest.ttf'))
347349
mpl.rcParams["svg.fonttype"] = 'none'
348350
mpl.rcParams['mathtext.fontset'] = 'custom'
349351
mpl.rcParams['mathtext.rm'] = 'mpltest'
@@ -357,12 +359,13 @@ def test_mathtext_fallback(fallback, fontlist):
357359
fig, ax = plt.subplots()
358360
fig.text(.5, .5, test_str, fontsize=40, ha='center')
359361
fig.savefig(buff, format="svg")
360-
char_fonts = [
361-
line.split("font-family:")[-1].split(";")[0]
362-
for line in str(buff.getvalue()).split(r"\n") if "tspan" in line
363-
]
362+
tspans = (ET.fromstring(buff.getvalue())
363+
.findall(".//{http://www.w3.org/2000/svg}tspan[@style]"))
364+
# Getting the last element of the style attrib is a close enough
365+
# approximation for parsing the font property.
366+
char_fonts = [shlex.split(tspan.attrib["style"])[-1] for tspan in tspans]
364367
assert char_fonts == fontlist
365-
mpl.font_manager.fontManager.ttflist = mpl.font_manager.fontManager.ttflist[:-1]
368+
mpl.font_manager.fontManager.ttflist.pop()
366369

367370

368371
def test_math_to_image(tmpdir):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.