Skip to content

Commit

Permalink
Merge pull request matplotlib#28504 from LorenzoPeri17/main
Browse files Browse the repository at this point in the history
Changes in SVG backend to improve compatibility with Affinity designer
  • Loading branch information
tacaswell authored Jul 22, 2024
2 parents 24c3f1a + 45cd371 commit bcfecca
Show file tree
Hide file tree
Showing 30 changed files with 14,072 additions and 13,299 deletions.
95 changes: 43 additions & 52 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1066,12 +1066,13 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
self._update_glyph_map_defs(glyph_map_new)

for glyph_id, xposition, yposition, scale in glyph_info:
attrib = {'xlink:href': f'#{glyph_id}'}
if xposition != 0.0:
attrib['x'] = _short_float_fmt(xposition)
if yposition != 0.0:
attrib['y'] = _short_float_fmt(yposition)
writer.element('use', attrib=attrib)
writer.element(
'use',
transform=_generate_transform([
('translate', (xposition, yposition)),
('scale', (scale,)),
]),
attrib={'xlink:href': f'#{glyph_id}'})

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

color = rgb2hex(gc.get_rgb())
style = {}
font_style = {}
color_style = {}
if color != '#000000':
style['fill'] = color
color_style['fill'] = color

alpha = gc.get_alpha() if gc.get_forced_alpha() else gc.get_rgb()[3]
if alpha != 1:
style['opacity'] = _short_float_fmt(alpha)
color_style['opacity'] = _short_float_fmt(alpha)

if not ismath:
attrib = {}

font_parts = []
# Separate font style in their separate attributes
if prop.get_style() != 'normal':
font_parts.append(prop.get_style())
font_style['font-style'] = prop.get_style()
if prop.get_variant() != 'normal':
font_parts.append(prop.get_variant())
font_style['font-variant'] = prop.get_variant()
weight = fm.weight_dict[prop.get_weight()]
if weight != 400:
font_parts.append(f'{weight}')
font_style['font-weight'] = f'{weight}'

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

font_parts.extend([
f'{_short_float_fmt(prop.get_size())}px',
# ensure expansion, quoting, and dedupe of font names
", ".join(dict.fromkeys(_get_all_quoted_names(prop)))
])
style['font'] = ' '.join(font_parts)
font_style['font-size'] = f'{_short_float_fmt(prop.get_size())}px'
# ensure expansion, quoting, and dedupe of font names
font_style['font-family'] = ", ".join(
dict.fromkeys(_get_all_quoted_names(prop))
)

if prop.get_stretch() != 'normal':
style['font-stretch'] = prop.get_stretch()
attrib['style'] = _generate_css(style)
font_style['font-stretch'] = prop.get_stretch()
attrib['style'] = _generate_css({**font_style, **color_style})

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

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

attrib['x'] = _short_float_fmt(ax)
attrib['y'] = _short_float_fmt(ay)
attrib['style'] = _generate_css(style)
attrib['style'] = _generate_css({**font_style, **color_style})
attrib['transform'] = _generate_transform([
("rotate", (-angle, ax, ay))])

Expand All @@ -1204,7 +1206,7 @@ def _get_all_quoted_names(prop):
# Apply attributes to 'g', not 'text', because we likely have some
# rectangles as well with the same style and transformation.
writer.start('g',
style=_generate_css(style),
style=_generate_css({**font_style, **color_style}),
transform=_generate_transform([
('translate', (x, y)),
('rotate', (-angle,))]),
Expand All @@ -1216,43 +1218,32 @@ def _get_all_quoted_names(prop):
spans = {}
for font, fontsize, thetext, new_x, new_y in glyphs:
entry = fm.ttfFontProperty(font)
font_parts = []
font_style = {}
# Separate font style in its separate attributes
if entry.style != 'normal':
font_parts.append(entry.style)
font_style['font-style'] = entry.style
if entry.variant != 'normal':
font_parts.append(entry.variant)
font_style['font-variant'] = 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)}
font_style['font-weight'] = f'{entry.weight}'
font_style['font-size'] = f'{_short_float_fmt(fontsize)}px'
font_style['font-family'] = f'{entry.name!r}' # ensure quoting
if entry.stretch != 'normal':
style['font-stretch'] = entry.stretch
style = _generate_css(style)
font_style['font-stretch'] = entry.stretch
style = _generate_css({**font_style, **color_style})
if thetext == 32:
thetext = 0xa0 # non-breaking space
spans.setdefault(style, []).append((new_x, -new_y, thetext))

for style, chars in spans.items():
chars.sort()

if len({y for x, y, t in chars}) == 1: # Are all y's the same?
ys = str(chars[0][1])
else:
ys = ' '.join(str(c[1]) for c in chars)

attrib = {
'style': style,
'x': ' '.join(_short_float_fmt(c[0]) for c in chars),
'y': ys
}

writer.element(
'tspan',
''.join(chr(c[2]) for c in chars),
attrib=attrib)
chars.sort() # Sort by increasing x position
for x, y, t in chars: # Output one tspan for each character
writer.element(
'tspan',
chr(t),
x=_short_float_fmt(x),
y=_short_float_fmt(y),
style=style)

writer.end('text')

Expand Down
11 changes: 10 additions & 1 deletion lib/matplotlib/testing/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import sys
from tempfile import TemporaryDirectory, TemporaryFile
import weakref
import re

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

0 comments on commit bcfecca

Please sign in to comment.