Skip to content

Instantly share code, notes, and snippets.

@jehiah
Created February 9, 2021 23:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jehiah/fd6bdda583bb3f60f67c6f2eb4b89112 to your computer and use it in GitHub Desktop.
Save jehiah/fd6bdda583bb3f60f67c6f2eb4b89112 to your computer and use it in GitHub Desktop.
Resize SVG images
from decimal import Decimal
from xml.dom import minidom
import sys
import os
import re
import simplejson as json
import string
import tornado.options
import logging
"""
convert a path from one with absolute coordinates to one with relative coordinates
svg spec http://www.w3.org/TR/2003/REC-SVG11-20030114/paths.html
"""
def scale(svg, scale_factor, float_format="%0.2f"):
scale_factor = Decimal(scale_factor)
path = svg.split(' ')
out = []
o = tornado.options.options
for entry in path:
if entry in string.lowercase + string.uppercase:
out.append(entry)
else:
out.append(float_format % (Decimal(entry) * scale_factor))
if o.offset_x is not None:
assert out[0] in ['M', 'm']
out[1] = float_format % (Decimal(out[1]) + Decimal(str(o.offset_x)))
if o.offset_y is not None:
assert out[0] in ['M', 'm']
out[2] = float_format % (Decimal(out[2]) + Decimal(str(o.offset_y)))
return ' '.join(out)
def abs_svg_to_relative_svg(source_path, float_format="%0.2f"):
path = source_path.replace(',', ' ')
for character in string.lowercase + string.uppercase:
path = path.replace(character, ' %s ' % character)
temp_path = re.sub('\s+', ' ', path).split('-')
path = ' -'.join(temp_path).split(' ')
path = [x for x in path if x]
logging.debug(path)
out_path = []
max_x = Decimal('0')
min_x = Decimal('1000')
min_y = Decimal('0')
max_y = Decimal('1000')
X,Y=Decimal("0"),Decimal("0")
while True:
try:
e = path.pop(0)
except:
break
if e in ['z','Z']:
out_path.append(e)
continue
if e in ['l','m']:
out_path.append(e)
x,y = (path.pop(0), path.pop(0))
out_path.append(x)
out_path.append(y)
X += Decimal(x)
Y += Decimal(y)
max_x = max(max_x, X)
min_x = min(min_x, X)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
continue
if e in ['h']:
x = path.pop(0)
X += Decimal(x)
max_x = max(max_x, X)
min_x = min(min_x, X)
out_path.append(e)
out_path.append(x)
continue
if e in ['H']:
x = Decimal(path.pop(0))
xd = x - X
X = x
max_x = max(max_x, X)
min_x = min(min_x, X)
out_path.append(e.lower())
out_path.append(float_format % xd)
continue
if e in ['v']:
y = path.pop(0)
Y += Decimal(y)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
out_path.append(e)
out_path.append(y)
continue
if e in ['V']:
y = Decimal(path.pop(0))
yd = y - Y
Y = y
max_y = max(max_y, Y)
min_y = min(min_y, Y)
out_path.append(e.lower())
out_path.append(float_format % yd)
continue
if e in ['c']:
out_path.append(e)
while path and path[0] not in string.lowercase + string.uppercase:
srcd = [path.pop(0) for x in range(6)]
x1, y1, x2, y2, x, y = srcd
X += Decimal(x)
Y += Decimal(y)
max_x = max(max_x, X)
min_x = min(min_x, X)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
out_path += srcd
continue
if e in ['s']:
out_path.append(e.lower())
while path and path[0] not in string.lowercase + string.uppercase:
srcd = [path.pop(0) for x in range(4)]
x1, y1, x, y = srcd
X += Decimal(x)
Y += Decimal(y)
max_x = max(max_x, X)
min_x = min(min_x, X)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
out_path += srcd
continue
if e in ['S']:
# curve
out_path.append(e.lower())
while path and path[0] not in string.lowercase + string.uppercase:
srcd = [Decimal(path.pop(0)) for x in range(4)]
x1, y1, x, y = srcd
# if X == None and Y == None:
# X = Decimal('0')
# Y = Decimal('0')
x1d = x1 - X
y1d = y1 - Y
xd = x - X
yd = y - Y
X = x
Y = y
max_x = max(max_x, X)
min_x = min(min_x, X)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
logging.debug(srcd)
out_path += [float_format % z for z in [x1d, y1d, xd, yd]]
continue
if e in ['C']:
# curve
out_path.append(e.lower())
while path and path[0] not in string.lowercase + string.uppercase:
logging.debug(e, X, Y)
srcd = [Decimal(path.pop(0)) for x in range(6)]
x1, y1, x2, y2, x, y = srcd
# if X == None and Y == None:
# X = Decimal('0')
# Y = Decimal('0')
x1d = x1 - X
y1d = y1 - Y
x2d = x2 - X
y2d = y2 - Y
xd = x - X
yd = y - Y
X = x
Y = y
max_x = max(max_x, X)
min_x = min(min_x, X)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
logging.debug(srcd)
logging.debug('\t', [float_format % z for z in [x1d, y1d, x2d, y2d, xd, yd]])
out_path += [float_format % z for z in [x1d, y1d, x2d, y2d, xd, yd]]
continue
if e in ['L','M']:
logging.debug(e, X, Y)
x,y = (path.pop(0), path.pop(0))
if X == None and Y == None:
out_path.append(e)
out_path.append(x)
out_path.append(y)
X = Decimal(x)
Y = Decimal(y)
max_x = max(max_x, X)
min_x = min(min_x, X)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
continue
x = Decimal(x)
y = Decimal(y)
xd = x - X
yd = y - Y
X = x
Y = y
max_x = max(max_x, X)
min_x = min(min_x, X)
max_y = max(max_y, Y)
min_y = min(min_y, Y)
logging.debug('\t',x,y)
logging.debug('\t',xd,yd)
out_path.append(e.lower())
out_path.append(float_format % xd)
out_path.append(float_format % yd)
continue
raise Exception("error, %r not handled" % e)
extra = dict(
max_x = float_format % max_x,
min_x = float_format % min_x,
max_y = float_format % max_y,
min_y = float_format % min_y,
)
return (' '.join(out_path), extra)
if __name__ == "__main__":
tornado.options.define('input_file', type=str)
tornado.options.define('scale', type=float, default=None)
tornado.options.define("offset_x", type=float, default=None)
tornado.options.define("offset_y", type=float, default=None)
tornado.options.define("precision", type=str, default="%0.2f")
tornado.options.parse_command_line()
o = tornado.options.options
if o.input_file.endswith('.svg'):
tree = minidom.parseString(open(o.input_file, 'rb').read())
logging.debug(tree)
paths = []
min_x_l = []
max_x_l = []
for path in tree.getElementsByTagName('path'):
path_str = path.attributes['d'].value
logging.debug("%s %s", path, path_str)
svg, extra = abs_svg_to_relative_svg(path_str, float_format=o.precision)
logging.info('%r', extra)
min_x_l.append(extra['min_x'])
max_x_l.append(extra['max_x'])
if o.scale is not None:
svg = scale(svg, o.scale, float_format=o.precision)
paths.append(svg)
print json.dumps(paths)
print "(before scale) min_x", min(min_x_l), "max_x", max(max_x_l)
sys.exit(0)
if o.input_file == "-":
svg, extra= abs_svg_to_relative_svg(sys.stdin.read())
if o.scale is not None:
svg = scale(svg, o.scale)
print extra
print svg
sys.exit(0)
if os.path.exists(sys.argv[-1]):
data = open(sys.argv[-1], 'r').read()
data = json.loads(data)
translated = []
for key, svg in data.items():
svg, width, kern = abs_svg_to_relative_svg(svg)
svg = scale(svg, '.13')
width = '%0.2f' % (Decimal(width) * Decimal('.13'))
kern = '%0.2f' % (Decimal(kern) * Decimal('.13'))
translated.append((key, {"path":svg, "width": width, "kern":kern}))
out = open(sys.argv[-2], 'w')
out.write(json.dumps(dict(translated)))
out.close
else:
print abs_svg_to_relative_svg(sys.argv[-1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment