|
13 | 13 | from PIL import Image
|
14 | 14 |
|
15 | 15 | import matplotlib as mpl
|
16 |
| -from matplotlib import _api, cbook |
| 16 | +from matplotlib import _api, cbook, font_manager as fm |
17 | 17 | from matplotlib.backend_bases import (
|
18 | 18 | _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
|
19 | 19 | RendererBase)
|
20 | 20 | from matplotlib.backends.backend_mixed import MixedModeRenderer
|
21 | 21 | from matplotlib.colors import rgb2hex
|
22 | 22 | from matplotlib.dates import UTC
|
23 |
| -from matplotlib.font_manager import findfont, get_font |
24 |
| -from matplotlib.ft2font import LOAD_NO_HINTING |
25 | 23 | from matplotlib.mathtext import MathTextParser
|
26 | 24 | from matplotlib.path import Path
|
27 | 25 | from matplotlib import _path
|
@@ -93,6 +91,12 @@ def escape_attrib(s):
|
93 | 91 | return s
|
94 | 92 |
|
95 | 93 |
|
| 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 | + |
96 | 100 | def short_float_fmt(x):
|
97 | 101 | """
|
98 | 102 | Create a short string representation of a float, which is %f
|
@@ -158,8 +162,8 @@ def start(self, tag, attrib={}, **extra):
|
158 | 162 | for k, v in {**attrib, **extra}.items():
|
159 | 163 | if v:
|
160 | 164 | 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)) |
163 | 167 | self.__open = 1
|
164 | 168 | return len(self.__tags) - 1
|
165 | 169 |
|
@@ -261,15 +265,7 @@ def generate_transform(transform_list=[]):
|
261 | 265 |
|
262 | 266 |
|
263 | 267 | 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()) |
273 | 269 |
|
274 | 270 |
|
275 | 271 | _capstyle_d = {'projecting': 'square', 'butt': 'butt', 'round': 'round'}
|
@@ -462,8 +458,8 @@ def _make_flip_transform(self, transform):
|
462 | 458 | .translate(0.0, self.height))
|
463 | 459 |
|
464 | 460 | 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) |
467 | 463 | font.clear()
|
468 | 464 | size = prop.get_size_in_points()
|
469 | 465 | 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):
|
1106 | 1102 | style['opacity'] = short_float_fmt(alpha)
|
1107 | 1103 |
|
1108 | 1104 | if not ismath:
|
1109 |
| - font = self._get_font(prop) |
1110 |
| - font.set_text(s, 0.0, flags=LOAD_NO_HINTING) |
1111 |
| - |
1112 | 1105 | 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() |
1119 | 1122 | attrib['style'] = generate_css(style)
|
1120 | 1123 |
|
1121 | 1124 | 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):
|
1175 | 1178 | # Sort the characters by font, and output one tspan for each.
|
1176 | 1179 | spans = {}
|
1177 | 1180 | 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) |
1183 | 1197 | if thetext == 32:
|
1184 | 1198 | thetext = 0xa0 # non-breaking space
|
1185 | 1199 | spans.setdefault(style, []).append((new_x, -new_y, thetext))
|
|
0 commit comments