Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Improve font spec for SVG font referencing. #19253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 44 additions & 30 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
from PIL import Image

import matplotlib as mpl
from matplotlib import _api, cbook
from matplotlib import _api, cbook, font_manager as fm
from matplotlib.backend_bases import (
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
RendererBase, _no_output_draw)
from matplotlib.backends.backend_mixed import MixedModeRenderer
from matplotlib.colors import rgb2hex
from matplotlib.dates import UTC
from matplotlib.font_manager import findfont, get_font
from matplotlib.ft2font import LOAD_NO_HINTING
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
from matplotlib import _path
Expand Down Expand Up @@ -94,6 +92,12 @@ def escape_attrib(s):
return s


def _quote_escape_attrib(s):
return ('"' + escape_cdata(s) + '"' if '"' not in s else
"'" + escape_cdata(s) + "'" if "'" not in s else
'"' + escape_attrib(s) + '"')


def short_float_fmt(x):
"""
Create a short string representation of a float, which is %f
Expand Down Expand Up @@ -159,8 +163,8 @@ def start(self, tag, attrib={}, **extra):
for k, v in {**attrib, **extra}.items():
if v:
k = escape_cdata(k)
v = escape_attrib(v)
self.__write(' %s="%s"' % (k, v))
v = _quote_escape_attrib(v)
self.__write(' %s=%s' % (k, v))
self.__open = 1
return len(self.__tags) - 1

Expand Down Expand Up @@ -262,15 +266,7 @@ def generate_transform(transform_list=[]):


def generate_css(attrib={}):
if attrib:
output = StringIO()
attrib = attrib.items()
for k, v in attrib:
k = escape_attrib(k)
v = escape_attrib(v)
output.write("%s:%s;" % (k, v))
return output.getvalue()
return ''
return "; ".join(f"{k}: {v}" for k, v in attrib.items())


_capstyle_d = {'projecting': 'square', 'butt': 'butt', 'round': 'round'}
Expand Down Expand Up @@ -464,8 +460,8 @@ def _make_flip_transform(self, transform):
.translate(0.0, self.height))

def _get_font(self, prop):
fname = findfont(prop)
font = get_font(fname)
fname = fm.findfont(prop)
font = fm.get_font(fname)
font.clear()
size = prop.get_size_in_points()
font.set_size(size, 72.0)
Expand Down Expand Up @@ -1128,16 +1124,23 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
style['opacity'] = short_float_fmt(alpha)

if not ismath:
font = self._get_font(prop)
font.set_text(s, 0.0, flags=LOAD_NO_HINTING)

attrib = {}
style['font-family'] = str(font.family_name)
style['font-weight'] = str(prop.get_weight()).lower()
style['font-stretch'] = str(prop.get_stretch()).lower()
style['font-style'] = prop.get_style().lower()
# Must add "px" to workaround a Firefox bug
style['font-size'] = short_float_fmt(prop.get_size()) + 'px'

font_parts = []
if prop.get_style() != 'normal':
font_parts.append(prop.get_style())
if prop.get_variant() != 'normal':
font_parts.append(prop.get_variant())
weight = fm.weight_dict[prop.get_weight()]
if weight != 400:
font_parts.append(f'{weight}')
font_parts.extend([
f'{short_float_fmt(prop.get_size())}px',
f'{prop.get_family()[0]!r}', # ensure quoting
])
style['font'] = ' '.join(font_parts)
if prop.get_stretch() != 'normal':
style['font-stretch'] = prop.get_stretch()
attrib['style'] = generate_css(style)

if mtext and (angle == 0 or mtext.get_rotation_mode() == "anchor"):
Expand Down Expand Up @@ -1197,11 +1200,22 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
# Sort the characters by font, and output one tspan for each.
spans = OrderedDict()
for font, fontsize, thetext, new_x, new_y in glyphs:
style = generate_css({
'font-size': short_float_fmt(fontsize) + 'px',
'font-family': font.family_name,
'font-style': font.style_name.lower(),
'font-weight': font.style_name.lower()})
entry = fm.ttfFontProperty(font)
font_parts = []
if entry.style != 'normal':
font_parts.append(entry.style)
if entry.variant != 'normal':
font_parts.append(entry.variant)
if entry.weight != 400:
font_parts.append(f'{entry.weight}')
font_parts.extend([
f'{short_float_fmt(fontsize)}px',
f'{entry.name!r}', # ensure quoting
])
style = {'font': ' '.join(font_parts)}
if entry.stretch != 'normal':
style['font-stretch'] = entry.stretch
style = generate_css(style)
if thetext == 32:
thetext = 0xa0 # non-breaking space
spans.setdefault(style, []).append((new_x, -new_y, thetext))
Expand Down
6 changes: 2 additions & 4 deletions lib/matplotlib/tests/test_backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def test_unicode_won():


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

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

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


def test_gid():
Expand Down
17 changes: 10 additions & 7 deletions lib/matplotlib/tests/test_mathtext.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import io
import os
from pathlib import Path
import re
import shlex
from xml.etree import ElementTree as ET

import numpy as np
import pytest
Expand Down Expand Up @@ -349,7 +351,7 @@ def test_mathtext_fallback_to_cm_invalid():
("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral'])])
def test_mathtext_fallback(fallback, fontlist):
mpl.font_manager.fontManager.addfont(
os.path.join((os.path.dirname(os.path.realpath(__file__))), 'mpltest.ttf'))
str(Path(__file__).resolve().parent / 'mpltest.ttf'))
mpl.rcParams["svg.fonttype"] = 'none'
mpl.rcParams['mathtext.fontset'] = 'custom'
mpl.rcParams['mathtext.rm'] = 'mpltest'
Expand All @@ -363,12 +365,13 @@ def test_mathtext_fallback(fallback, fontlist):
fig, ax = plt.subplots()
fig.text(.5, .5, test_str, fontsize=40, ha='center')
fig.savefig(buff, format="svg")
char_fonts = [
line.split("font-family:")[-1].split(";")[0]
for line in str(buff.getvalue()).split(r"\n") if "tspan" in line
]
tspans = (ET.fromstring(buff.getvalue())
.findall(".//{http://www.w3.org/2000/svg}tspan[@style]"))
# Getting the last element of the style attrib is a close enough
# approximation for parsing the font property.
char_fonts = [shlex.split(tspan.attrib["style"])[-1] for tspan in tspans]
assert char_fonts == fontlist
mpl.font_manager.fontManager.ttflist = mpl.font_manager.fontManager.ttflist[:-1]
mpl.font_manager.fontManager.ttflist.pop()


def test_math_to_image(tmpdir):
Expand Down