Skip to content

Instantly share code, notes, and snippets.

@paulgb
Created August 18, 2019 12:56
Show Gist options
  • Save paulgb/7613625ba13638f08a4c42981ddb753a to your computer and use it in GitHub Desktop.
Save paulgb/7613625ba13638f08a4c42981ddb753a to your computer and use it in GitHub Desktop.
Recursive SVG generator.
#!/usr/bin/env python3
from argparse import ArgumentParser
from datetime import datetime
from os import stat
from time import sleep
from xml.dom.minidom import Element, parse
def get_viewbox_dimensions(view_box_string):
_, _, width, height = view_box_string.split()
return float(width), float(height)
def process_doc(doc, iters=2, keep_rects=True):
svg_elem = doc.getElementsByTagName('svg')[0]
svg_width, svg_height = get_viewbox_dimensions(
svg_elem.getAttribute('viewBox'))
g_clone = doc.createElement('g')
g_clone.childNodes = [c.cloneNode(True)
for c in svg_elem.childNodes
if isinstance(c, Element)
and c.tagName != 'defs']
for _ in range(iters):
for rect in svg_elem.getElementsByTagName('rect'):
if rect.getAttribute('class') == 'recur':
x = rect.getAttribute('x')
y = rect.getAttribute('y')
width = float(rect.getAttribute('width'))
height = float(rect.getAttribute('height'))
x_scale = width / svg_width
y_scale = height / svg_height
new_g = g_clone.cloneNode(True)
new_g.setAttribute('transform',
rect.getAttribute('transform') +
f'translate({x} {y}) '
f'scale({x_scale} {y_scale}) '
)
rect.parentNode.replaceChild(new_g, rect)
if not keep_rects:
for rect in svg_elem.getElementsByTagName('rect'):
if rect.getAttribute('class') == 'recur':
rect.parentNode.removeChild(rect)
return svg_elem
def process_file(input_filename, output_filename, iters, keep_rects):
with open(input_filename) as fh:
doc = parse(fh)
out_doc = process_doc(doc, iters, keep_rects)
with open(output_filename, 'w') as fh:
out_doc.writexml(fh)
def watch_loop(input_filename, output_filename, iters, keep_rects):
last_updated = 0
while True:
last_modified = stat(input_filename).st_mtime
if last_modified == last_updated:
sleep(0.1)
continue
print(f'Change detected {datetime.now()}...', end='', flush=True)
process_file(input_filename, output_filename, iters, keep_rects)
print('Saved.')
last_updated = last_modified
def main():
parser = ArgumentParser(description='''Recursive SVG generator.
Every <rect> element with the class name "recur" will be replaced with
a copy of the whole drawing. The drawing’s viewport will be scaled to
the size of the rect. This process is repeated iters times.''')
parser.add_argument('input_svg', help='input SVG file')
parser.add_argument('output_svg', help='desired output file')
parser.add_argument('--iters', '-i', default=4, type=int,
help='number of recursive iterations')
parser.add_argument('--keep-rects', '-k', action='store_true',
help='don’t delete recur rectangles (for debugging)')
parser.add_argument('--watch', '-w', action='store_true',
help='watch input file for modifications')
args = parser.parse_args()
if args.watch:
watch_loop(args.input_svg, args.output_svg,
args.iters, args.keep_rects)
else:
process_file(args.input_svg, args.output_svg,
args.iters, args.keep_rects)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment