Skip to content

Commit

Permalink
Merge pull request matplotlib#5651 from mdboom/shorter-svg-files
Browse files Browse the repository at this point in the history
Shorter svg files
  • Loading branch information
efiring authored and mdboom committed Dec 11, 2015
1 parent 5f1127c commit 92e57c8
Show file tree
Hide file tree
Showing 3 changed files with 13,076 additions and 19,306 deletions.
97 changes: 58 additions & 39 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ def escape_attrib(s):
s = s.replace(">", ">")
return s

def short_float_fmt(x):
"""
Create a short string representation of a float, which is %f
formatting with trailing zeros and the decimal point removed.
"""
return '{0:f}'.format(x).rstrip('0').rstrip('.')

##
# XML writer class.
#
Expand Down Expand Up @@ -231,7 +238,8 @@ def generate_transform(transform_list=[]):
if type == 'matrix' and isinstance(value, Affine2DBase):
value = value.to_values()

output.write('%s(%s)' % (type, ' '.join(str(x) for x in value)))
output.write('%s(%s)' % (
type, ' '.join(short_float_fmt(x) for x in value)))
return output.getvalue()
return ''

Expand Down Expand Up @@ -403,32 +411,32 @@ def _get_style_dict(self, gc, rgbFace):
if gc.get_hatch() is not None:
attrib['fill'] = "url(#%s)" % self._get_hatch(gc, rgbFace)
if rgbFace is not None and len(rgbFace) == 4 and rgbFace[3] != 1.0 and not forced_alpha:
attrib['fill-opacity'] = str(rgbFace[3])
attrib['fill-opacity'] = short_float_fmt(rgbFace[3])
else:
if rgbFace is None:
attrib['fill'] = 'none'
else:
if tuple(rgbFace[:3]) != (0, 0, 0):
attrib['fill'] = rgb2hex(rgbFace)
if len(rgbFace) == 4 and rgbFace[3] != 1.0 and not forced_alpha:
attrib['fill-opacity'] = str(rgbFace[3])
attrib['fill-opacity'] = short_float_fmt(rgbFace[3])

if forced_alpha and gc.get_alpha() != 1.0:
attrib['opacity'] = str(gc.get_alpha())
attrib['opacity'] = short_float_fmt(gc.get_alpha())

offset, seq = gc.get_dashes()
if seq is not None:
attrib['stroke-dasharray'] = ','.join(['%f' % val for val in seq])
attrib['stroke-dashoffset'] = six.text_type(float(offset))
attrib['stroke-dasharray'] = ','.join([short_float_fmt(val) for val in seq])
attrib['stroke-dashoffset'] = short_float_fmt(float(offset))

linewidth = gc.get_linewidth()
if linewidth:
rgb = gc.get_rgb()
attrib['stroke'] = rgb2hex(rgb)
if not forced_alpha and rgb[3] != 1.0:
attrib['stroke-opacity'] = str(rgb[3])
attrib['stroke-opacity'] = short_float_fmt(rgb[3])
if linewidth != 1.0:
attrib['stroke-width'] = str(linewidth)
attrib['stroke-width'] = short_float_fmt(linewidth)
if gc.get_joinstyle() != 'round':
attrib['stroke-linejoin'] = gc.get_joinstyle()
if gc.get_capstyle() != 'butt':
Expand Down Expand Up @@ -476,8 +484,12 @@ def _write_clips(self):
writer.element('path', d=path_data)
else:
x, y, w, h = clip
writer.element('rect', x=six.text_type(x), y=six.text_type(y),
width=six.text_type(w), height=six.text_type(h))
writer.element(
'rect',
x=short_float_fmt(x),
y=short_float_fmt(y),
width=short_float_fmt(w),
height=short_float_fmt(h))
writer.end('clipPath')
writer.end('defs')

Expand All @@ -498,7 +510,8 @@ def _write_svgfonts(self):
'font-family': font.family_name,
'font-style': font.style_name.lower(),
'units-per-em': '72',
'bbox': ' '.join(six.text_type(x / 64.0) for x in font.bbox)})
'bbox': ' '.join(
short_float_fmt(x / 64.0) for x in font.bbox)})
for char in chars:
glyph = font.load_char(char, flags=LOAD_NO_HINTING)
verts, codes = font.get_path()
Expand All @@ -511,7 +524,8 @@ def _write_svgfonts(self):
attrib={
# 'glyph-name': name,
'unicode': unichr(char),
'horiz-adv-x': six.text_type(glyph.linearHoriAdvance / 65536.0)})
'horiz-adv-x':
short_float_fmt(glyph.linearHoriAdvance / 65536.0)})
writer.end('font')
writer.end('defs')

Expand Down Expand Up @@ -607,8 +621,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None)
trans_and_flip, clip=clip, simplify=False):
if len(vertices):
x, y = vertices[-2:]
attrib['x'] = six.text_type(x)
attrib['y'] = six.text_type(y)
attrib['x'] = short_float_fmt(x)
attrib['y'] = short_float_fmt(y)
attrib['style'] = self._get_style(gc, rgbFace)
writer.element('use', attrib=attrib)
writer.end('g')
Expand Down Expand Up @@ -659,8 +673,8 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
writer.start('g', attrib={'clip-path': 'url(#%s)' % clipid})
attrib = {
'xlink:href': '#%s' % path_id,
'x': six.text_type(xo),
'y': six.text_type(self.height - yo),
'x': short_float_fmt(xo),
'y': short_float_fmt(self.height - yo),
'style': self._get_style(gc0, rgbFace)
}
writer.element('use', attrib=attrib)
Expand Down Expand Up @@ -729,13 +743,13 @@ def draw_gouraud_triangle(self, gc, points, colors, trans):
writer.start(
'linearGradient',
id="GR%x_%d" % (self._n_gradients, i),
x1=six.text_type(x1), y1=six.text_type(y1),
x2=six.text_type(xb), y2=six.text_type(yb))
x1=short_float_fmt(x1), y1=short_float_fmt(y1),
x2=short_float_fmt(xb), y2=short_float_fmt(yb))
writer.element(
'stop',
offset='0',
style=generate_css({'stop-color': rgb2hex(c),
'stop-opacity': six.text_type(c[-1])}))
'stop-opacity': short_float_fmt(c[-1])}))
writer.element(
'stop',
offset='1',
Expand All @@ -746,7 +760,7 @@ def draw_gouraud_triangle(self, gc, points, colors, trans):
writer.element(
'polygon',
id='GT%x' % self._n_gradients,
points=" ".join([six.text_type(x)
points=" ".join([short_float_fmt(x)
for x in (x1, y1, x2, y2, x3, y3)]))
writer.end('defs')

Expand All @@ -756,7 +770,7 @@ def draw_gouraud_triangle(self, gc, points, colors, trans):
'use',
attrib={'xlink:href': href,
'fill': rgb2hex(avg_color),
'fill-opacity': str(avg_color[-1])})
'fill-opacity': short_float_fmt(avg_color[-1])})
for i in range(3):
writer.element(
'use',
Expand Down Expand Up @@ -842,16 +856,16 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None):

alpha = gc.get_alpha()
if alpha != 1.0:
attrib['opacity'] = str(alpha)
attrib['opacity'] = short_float_fmt(alpha)

attrib['id'] = oid

if transform is None:
self.writer.element(
'image',
x=six.text_type(x/trans[0]),
y=six.text_type((self.height-y)/trans[3]-h),
width=six.text_type(w), height=six.text_type(h),
x=short_float_fmt(x/trans[0]),
y=short_float_fmt((self.height-y)/trans[3]-h),
width=short_float_fmt(w), height=short_float_fmt(h),
attrib=attrib)
else:
flipped = self._make_flip_transform(transform)
Expand All @@ -864,8 +878,8 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None):
[('matrix', flipped)])
self.writer.element(
'image',
x=six.text_type(x), y=six.text_type(y),
width=six.text_type(dx), height=six.text_type(abs(dy)),
x=short_float_fmt(x), y=short_float_fmt(y),
width=short_float_fmt(dx), height=short_float_fmt(abs(dy)),
attrib=attrib)

if url is not None:
Expand Down Expand Up @@ -906,7 +920,7 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
if color != '#000000':
style['fill'] = color
if gc.get_alpha() != 1.0:
style['opacity'] = six.text_type(gc.get_alpha())
style['opacity'] = short_float_fmt(gc.get_alpha())

if not ismath:
font = text2path._get_font(prop)
Expand Down Expand Up @@ -936,9 +950,9 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
for glyph_id, xposition, yposition, scale in glyph_info:
attrib={'xlink:href': '#%s' % glyph_id}
if xposition != 0.0:
attrib['x'] = six.text_type(xposition)
attrib['x'] = short_float_fmt(xposition)
if yposition != 0.0:
attrib['y'] = six.text_type(yposition)
attrib['y'] = short_float_fmt(yposition)
writer.element(
'use',
attrib=attrib)
Expand Down Expand Up @@ -1007,7 +1021,7 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
if color != '#000000':
style['fill'] = color
if gc.get_alpha() != 1.0:
style['opacity'] = six.text_type(gc.get_alpha())
style['opacity'] = short_float_fmt(gc.get_alpha())

if not ismath:
font = self._get_font(prop)
Expand All @@ -1020,7 +1034,7 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):

attrib = {}
# Must add "px" to workaround a Firefox bug
style['font-size'] = six.text_type(fontsize) + 'px'
style['font-size'] = short_float_fmt(fontsize) + 'px'
style['font-family'] = six.text_type(fontfamily)
style['font-style'] = prop.get_style().lower()
style['font-weight'] = six.text_type(prop.get_weight()).lower()
Expand Down Expand Up @@ -1048,10 +1062,13 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
'center': 'middle'}
style['text-anchor'] = ha_mpl_to_svg[mtext.get_ha()]

attrib['x'] = str(ax)
attrib['y'] = str(ay)
attrib['x'] = short_float_fmt(ax)
attrib['y'] = short_float_fmt(ay)
attrib['style'] = generate_css(style)
attrib['transform'] = "rotate(%f, %f, %f)" % (-angle, ax, ay)
attrib['transform'] = "rotate(%s, %s, %s)" % (
short_float_fmt(-angle),
short_float_fmt(ax),
short_float_fmt(ay))
writer.element('text', s, attrib=attrib)
else:
attrib['transform'] = generate_transform([
Expand Down Expand Up @@ -1090,7 +1107,7 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
spans = {}
for font, fontsize, thetext, new_x, new_y, metrics in svg_glyphs:
style = generate_css({
'font-size': six.text_type(fontsize) + 'px',
'font-size': short_float_fmt(fontsize) + 'px',
'font-family': font.family_name,
'font-style': font.style_name.lower(),
'font-weight': font.style_name.lower()})
Expand Down Expand Up @@ -1120,7 +1137,7 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):

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

Expand All @@ -1135,8 +1152,10 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
for x, y, width, height in svg_rects:
writer.element(
'rect',
x=six.text_type(x), y=six.text_type(-y + height),
width=six.text_type(width), height=six.text_type(height)
x=short_float_fmt(x),
y=short_float_fmt(-y + height),
width=short_float_fmt(width),
height=short_float_fmt(height)
)

writer.end('g')
Expand Down
Loading

0 comments on commit 92e57c8

Please sign in to comment.