Created
February 9, 2021 23:00
-
-
Save jehiah/fd6bdda583bb3f60f67c6f2eb4b89112 to your computer and use it in GitHub Desktop.
Resize SVG images
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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