8000 Merge pull request #28504 from LorenzoPeri17/main · matplotlib/matplotlib@bcfecca · GitHub
[go: up one dir, main page]

Skip to content

Commit bcfecca

Browse files
authored
Merge pull request #28504 from LorenzoPeri17/main
Changes in SVG backend to improve compatibility with Affinity designer
2 parents 24c3f1a + 45cd371 commit bcfecca

30 files changed

+14072
-13299
lines changed

lib/matplotlib/backends/backend_svg.py

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,12 +1066,13 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
10661066
self._update_glyph_map_defs(glyph_map_new)
10671067

10681068
for glyph_id, xposition, yposition, scale in glyph_info:
1069-
attrib = {'xlink:href': f'#{glyph_id}'}
1070-
if xposition != 0.0:
1071-
attrib['x'] = _short_float_fmt(xposition)
1072-
if yposition != 0.0:
1073-
attrib['y'] = _short_float_fmt(yposition)
1074-
writer.element('use', attrib=attrib)
1069+
writer.element(
1070+
'use',
1071+
transform=_generate_transform([
1072+
('translate', (xposition, yposition)),
1073+
('scale', (scale,)),
1074+
]),
1075+
attrib={'xlink:href': f'#{glyph_id}'})
10751076

10761077
else:
10771078
if ismath == "TeX":
@@ -1109,25 +1110,26 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
11091110
writer = self.writer
11101111

11111112
color = rgb2hex(gc.get_rgb())
1112-
style = {}
1113+
font_style = {}
1114+
color_style = {}
11131115
if color != '#000000':
1114-
style['fill'] = color
1116+
color_s F438 tyle['fill'] = color
11151117

11161118
alpha = gc.get_alpha() if gc.get_forced_alpha() else gc.get_rgb()[3]
11171119
if alpha != 1:
1118-
style['opacity'] = _short_float_fmt(alpha)
1120+
color_style['opacity'] = _short_float_fmt(alpha)
11191121

11201122
if not ismath:
11211123
attrib = {}
11221124

1123-
font_parts = []
1125+
# Separate font style in their separate attributes
11241126
if prop.get_style() != 'normal':
1125-
font_parts.append(prop.get_style())
1127+
font_style['font-style'] = prop.get_style()
11261128
if prop.get_variant() != 'normal':
1127-
font_parts.append(prop.get_variant())
1129+
font_style['font-variant'] = prop.get_variant()
11281130
weight = fm.weight_dict[prop.get_weight()]
11291131
if weight != 400:
1130-
font_parts.append(f'{weight}')
1132+
font_style['font-weight'] = f'{weight}'
11311133

11321134
def _normalize_sans(name):
11331135
return 'sans-serif' if name in ['sans', 'sans serif'] else name
@@ -1150,15 +1152,15 @@ def _get_all_quoted_names(prop):
11501152
for entry in prop.get_family()
11511153
for name in _expand_family_entry(entry)]
11521154

1153-
font_parts.extend([
1154-
f'{_short_float_fmt(prop.get_size())}px',
1155-
# ensure expansion, quoting, and dedupe of font names
1156-
", ".join(dict.fromkeys(_get_all_quoted_names(prop)))
1157-
])
1158-
style['font'] = ' '.join(font_parts)
1155+
font_style['font-size'] = f'{_short_float_fmt(prop.get_size())}px'
1156+
# ensure expansion, quoting, and dedupe of font names
1157+
font_style['font-family'] = ", ".join(
1158+
dict.fromkeys(_get_all_quoted_names(prop))
1159+
)
1160+
11591161
if prop.get_stretch() != 'normal':
1160-
style['font-stretch'] = prop.get_stretch()
1161-
attrib['style'] = _generate_css(style)
1162+
font_style['font-stretch'] = prop.get_stretch()
1163+
attrib['style'] = _generate_css({**font_style, **color_style})
11621164

11631165
if mtext and (angle == 0 or mtext.get_rotation_mode() == "anchor"):
11641166
# If text anchoring can be supported, get the original
@@ -1180,11 +1182,11 @@ def _get_all_quoted_names(prop):
11801182

11811183
ha_mpl_to_svg = {'left': 'start', 'right': 'end',
11821184
'center': 'middle'}
1183-
style['text-anchor'] = ha_mpl_to_svg[mtext.get_ha()]
1185+
font_style['text-anchor'] = ha_mpl_to_svg[mtext.get_ha()]
11841186

11851187
attrib['x'] = _short_float_fmt(ax)
11861188
attrib['y'] = _short_float_fmt(ay)
1187-
attrib['style'] = _generate_css(style)
1189+
attrib['style'] = _generate_css({**font_style, **color_style})
11881190
attrib['transform'] = _generate_transform([
11891191
("rotate", (-angle, ax, ay))])
11901192

@@ -1204,7 +1206,7 @@ def _get_all_quoted_names(prop):
12041206
# Apply attributes to 'g', not 'text', because we likely have some
12051207
# rectangles as well with the same style and transformation.
12061208
writer.start('g',
1207-
style=_generate_css(style),
1209+
style=_generate_css({**font_style, **color_style}),
12081210
transform=_generate_transform([
12091211
('translate', (x, y)),
12101212
('rotate', (-angle,))]),
@@ -1216,43 +1218,32 @@ def _get_all_quoted_names(prop):
12161218
spans = {}
12171219
for font, fontsize, thetext, new_x, new_y in glyphs:
12181220
entry = fm.ttfFontProperty(font)
1219-
font_parts = []
1221+
font_style = {}
1222+
# Separate font style in its separate attributes
12201223
if entry.style != 'normal':
1221-
font_parts.append(entry.style)
1224+
font_style['font-style'] = entry.style
12221225
if entry.variant != 'normal':
1223-
font_parts.append(entry.variant)
1226+
font_style['font-variant'] = entry.variant
12241227
if entry.weight != 400:
1225-
font_parts.append(f'{entry.weight}')
1226-
font_parts.extend([
1227-
f'{_short_float_fmt(fontsize)}px',
1228-
f'{entry.name!r}', # ensure quoting
1229-
])
1230-
style = {'font': ' '.join(font_parts)}
1228+
font_style['font-weight'] = f'{entry.weight}'
1229+
font_style['font-size'] = f'{_short_float_fmt(fontsize)}px'
1230+
font_style['font-family'] = f'{entry.name!r}' # ensure quoting
12311231
if entry.stretch != 'normal':
1232-
style['font-stretch'] = entry.stretch
1233-
style = _generate_css(style)
1232+
font_style['font-stretch'] = entry.stretch
1233+
style = _generate_css({**font_style, **color_style})
12341234
if thetext == 32:
12351235
thetext = 0xa0 # non-breaking space
12361236
spans.setdefault(style, []).append((new_x, -new_y, thetext))
12371237

12381238
for style, chars in spans.items():
1239-
chars.sort()
1240-
1241-
if len({y for x, y, t in chars}) == 1: # Are all y's the same?
1242-
ys = str(chars[0][1])
1243-
else:
1244-
ys = ' '.join(str(c[1]) for c in chars)
1245-
1246-
attrib = {
1247-
'style': style,
1248-
'x': ' '.join(_short_float_fmt(c[0]) for c in chars),
1249-
'y': ys
1250-
}
1251-
1252-
writer.element(
1253-
'tspan',
1254-
''.join(chr(c[2]) for c in chars),
1255-
attrib=attrib)
1239+
chars.sort() # Sort by increasing x position
1240+
for x, y, t in chars: # Output one tspan for each character
1241+
writer.element(
1242+
'tspan',
1243+
chr(t),
1244+
x=_short_float_fmt(x),
1245+
y=_short_float_fmt(y),
1246+
style=style)
12561247

12571248
writer.end('text')
12581249

lib/matplotlib/testing/compare.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import sys
1414
from tempfile import TemporaryDirectory, TemporaryFile
1515
import weakref
16+
import re
1617

1718
import numpy as np
1819
from PIL import Image
@@ -305,7 +306,15 @@ def convert(filename, cache):
305306
# re-generate any SVG test files using this mode, or else such tests will
306307
# fail to use the converter for the expected images (but will for the
307308
# results), and the tests will fail strangely.
308-
if 'style="font:' in contents:
309+
if re.search(
310+
# searches for attributes :
311+
# style=[font|font-size|font-weight|
312+
# font-family|font-variant|font-style]
313+
# taking care of the possibility of multiple style attributes
314+
# before the font styling (i.e. opacity)
315+
r'style="[^"]*font(|-size|-weight|-family|-variant|-style):',
316+
contents # raw contents of the svg file
317+
):
309318
# for svg.fonttype = none, we explicitly patch the font search
310319
# path so that fonts shipped by Matplotlib are found.
311320
convert = _svg_with_matplotlib_fonts_converter

0 commit comments

Comments
 (0)
0