|
21 | 21 | from matplotlib.backends.backend_mixed import MixedModeRenderer
|
22 | 22 | from matplotlib.colors import rgb2hex
|
23 | 23 | from matplotlib.dates import UTC
|
24 |
| -from matplotlib.font_manager import findfont, get_font |
25 |
| -from matplotlib.ft2font import LOAD_NO_HINTING |
| 24 | +from matplotlib.font_manager import findfont, get_font, ttfFontProperty |
26 | 25 | from matplotlib.mathtext import MathTextParser
|
27 | 26 | from matplotlib.path import Path
|
28 | 27 | from matplotlib import _path
|
@@ -94,6 +93,12 @@ def escape_attrib(s):
|
94 | 93 | return s
|
95 | 94 |
|
96 | 95 |
|
| 96 | +def _quote_escape_attrib(s): |
| 97 | + return ('"' + escape_cdata(s) + '"' if '"' not in s else |
| 98 | + "'" + escape_cdata(s) + "'" if "'" not in s else |
| 99 | + '"' + escape_attrib(s) + '"') |
| 100 | + |
| 101 | + |
97 | 102 | def short_float_fmt(x):
|
98 | 103 | """
|
99 | 104 | Create a short string representation of a float, which is %f
|
@@ -159,8 +164,8 @@ def start(self, tag, attrib={}, **extra):
|
159 | 164 | for k, v in sorted({**attrib, **extra}.items()):
|
160 | 165 | if v:
|
161 | 166 | k = escape_cdata(k)
|
162 |
| - v = escape_attrib(v) |
163 |
| - self.__write(' %s="%s"' % (k, v)) |
| 167 | + v = _quote_escape_attrib(v) |
| 168 | + self.__write(' %s=%s' % (k, v)) |
164 | 169 | self.__open = 1
|
165 | 170 | return len(self.__tags) - 1
|
166 | 171 |
|
@@ -262,15 +267,7 @@ def generate_transform(transform_list=[]):
|
262 | 267 |
|
263 | 268 |
|
264 | 269 | def generate_css(attrib={}):
|
265 |
| - if attrib: |
266 |
| - output = StringIO() |
267 |
| - attrib = sorted(attrib.items()) |
268 |
| - for k, v in attrib: |
269 |
| - k = escape_attrib(k) |
270 |
| - v = escape_attrib(v) |
271 |
| - output.write("%s:%s;" % (k, v)) |
272 |
| - return output.getvalue() |
273 |
| - return '' |
| 270 | + return '; '.join(f'{k}: {v}' for k, v in sorted(attrib.items())) |
274 | 271 |
|
275 | 272 |
|
276 | 273 | _capstyle_d = {'projecting': 'square', 'butt': 'butt', 'round': 'round'}
|
@@ -1128,16 +1125,22 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
|
1128 | 1125 | style['opacity'] = short_float_fmt(alpha)
|
1129 | 1126 |
|
1130 | 1127 | if not ismath:
|
1131 |
| - font = self._get_font(prop) |
1132 |
| - font.set_text(s, 0.0, flags=LOAD_NO_HINTING) |
1133 |
| - |
1134 | 1128 | attrib = {}
|
1135 |
| - style['font-family'] = str(font.family_name) |
1136 |
| - style['font-weight'] = str(prop.get_weight()).lower() |
1137 |
| - style['font-stretch'] = str(prop.get_stretch()).lower() |
1138 |
| - style['font-style'] = prop.get_style().lower() |
1139 |
| - # Must add "px" to workaround a Firefox bug |
1140 |
| - style['font-size'] = short_float_fmt(prop.get_size()) + 'px' |
| 1129 | + |
| 1130 | + font_parts = [] |
| 1131 | + if prop.get_style() != 'normal': |
| 1132 | + font_parts.append(prop.get_style()) |
| 1133 | + if prop.get_variant() != 'normal': |
| 1134 | + font_parts.append(prop.get_variant()) |
| 1135 | + if prop.get_weight() not in ['normal', 'regular', 400]: |
| 1136 | + font_parts.append(prop.get_weight()) |
| 1137 | + font_parts.extend([ |
| 1138 | + f'{short_float_fmt(prop.get_size())}px', |
| 1139 | + f'{prop.get_family()[0]!r}', # ensure quoting |
| 1140 | + ]) |
| 1141 | + style['font'] = ' '.join(font_parts) |
| 1142 | + if prop.get_stretch() != 'normal': |
| 1143 | + style['font-stretch'] = prop.get_stretch() |
1141 | 1144 | attrib['style'] = generate_css(style)
|
1142 | 1145 |
|
1143 | 1146 | if mtext and (angle == 0 or mtext.get_rotation_mode() == "anchor"):
|
@@ -1197,11 +1200,22 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
|
1197 | 1200 | # Sort the characters by font, and output one tspan for each.
|
1198 | 1201 | spans = OrderedDict()
|
1199 | 1202 | 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()}) |
| 1203 | + entry = ttfFontProperty(font) |
| 1204 | + font_parts = [] |
| 1205 | + if entry.style != 'normal': |
| 1206 | + font_parts.append(entry.style) |
| 1207 | + if entry.variant != 'normal': |
| 1208 | + font_parts.append(entry.variant) |
| 1209 | + if entry.weight != 400: |
| 1210 | + font_parts.append(f'{entry.weight}') |
| 1211 | + font_parts.extend([ |
| 1212 | + f'{short_float_fmt(fontsize)}px', |
| 1213 | + f'{entry.name!r}', # ensure quoting |
| 1214 | + ]) |
| 1215 | + style = {'font': ' '.join(font_parts)} |
| 1216 | + if entry.stretch != 'normal': |
| 1217 | + style['font-stretch'] = entry.stretch |
| 1218 | + style = generate_css(style) |
1205 | 1219 | if thetext == 32:
|
1206 | 1220 | thetext = 0xa0 # non-breaking space
|
1207 | 1221 | spans.setdefault(style, []).append((new_x, -new_y, thetext))
|
|
0 commit comments