Skip to content

Instantly share code, notes, and snippets.

@rindPHI
Created November 29, 2020 11:28
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 rindPHI/a4c362166d879e2acbe21897d59c01c0 to your computer and use it in GitHub Desktop.
Save rindPHI/a4c362166d879e2acbe21897d59c01c0 to your computer and use it in GitHub Desktop.
Python script to convert an Inkscape SVG file with LaTeX Beamer-like layer names (e.g., "My layer [3,5-7,10-]") to a series of PDF files for use in, e.g., Beamer presentations, or as a step to create an animated gif.
#!/usr/bin/python3
import xml.etree.ElementTree as ET
import re
import sys
import os
import threading
from pathlib import Path
from subprocess import call
###########################################################################
# This script extracts "layers" for usage in LaTeX presentations #
# from inkscape SVG files. For this, append a LaTeX overlay specification #
# to the label of the layer, e.g., "1,2-5,3,17-", which you surround by #
# either angle or square brackets. Then, call this script with the name #
# of the SVG file as argument (works also with a path). It will export #
# multiple PDF files "...-step-NNN.pdf" in the same directory as the SVG #
# file. Requires the inkscape binary in the path. #
###########################################################################
ns = {'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape'}
class OverlayRange:
def __init__(self, start, end):
self.start = int(start)
self.end = int(end)
def __str__(self):
if (self.end == -1):
return "range(" + str(self.start) + "-" + ")"
else:
return "range(" + str(self.start) + "-" + str(self.end) + ")"
def max(self):
if (self.end > self.start):
return self.end
else:
return self.start
def setMax(self, max):
if (self.end == -1):
self.end = max
def visibleAt(self, step):
return (self.start <= step and step <= self.end)
@classmethod
def fromStartOnly(cls, start):
return cls(start, -1)
def parseOverlayRange(string):
arr = string.split('-')
if (len(arr) == 1):
return OverlayRange(arr[0], arr[0])
elif (len(arr) == 2 and arr[1] == ''):
return OverlayRange.fromStartOnly(arr[0])
elif (len(arr) == 2):
return OverlayRange(arr[0], arr[1])
class Layer:
def __init__(self, label, element):
self.label = label
self.element = element
self.ranges = []
def addRange(self, therange):
self.ranges.append(therange)
def maxRange(self):
return max(map(lambda r: r.max(), self.ranges))
def visibleAt(self, step):
for therange in self.ranges:
if (therange.visibleAt(step)):
return True
return False
def toNS(elem, namespace):
return '{' + ns.get(namespace) + '}' + elem
if (len(sys.argv) < 2):
print('Expecting input SVG file as argument')
inputfile = sys.argv[1]
outprefix = inputfile.split('.svg')[0]
tree = ET.parse(inputfile)
root = tree.getroot()
layers = []
for elem in tree.iter():
if (elem.tag != toNS('g', 'svg')):
continue
label = elem.get(toNS('label', 'inkscape'))
if (label == None):
continue
groupmode = elem.get(toNS('groupmode', 'inkscape'))
if (groupmode == None or groupmode != 'layer'):
continue
regex = r"^([^(<\[)]*)\W+(?:<|\[)([0-9]+(?:-(?:[0-9]+)?)?(?:,[0-9]+(?:-(?:[0-9]+)?)?)*)(?:>|\])$"
m = re.search(regex, label)
if (m == None):
continue
layer = Layer(m.group(1), elem)
for theRange in m.group(2).split(','):
layer.addRange(parseOverlayRange(theRange))
layers.append(layer)
maxLayer = max(map(lambda l: l.maxRange(), layers))
print("Maximum overlay number: " + str(maxLayer))
for layer in layers:
for therange in layer.ranges:
therange.setMax(maxLayer)
threads = list()
for i in range(1,maxLayer+1):
print("Animation step " + str(i))
for layer in layers:
if (layer.visibleAt(i)):
print (" Layer " + layer.label + " visible")
layer.element.set('style', 'display:inline')
elif (not layer.visibleAt(i)):
print (" Layer " + layer.label + " hidden")
layer.element.set('style', 'display:none')
layerOutPrefix = outprefix + '-step-' + str('{:03d}'.format(i))
svgoutfile = layerOutPrefix + '.svg'
pdfoutfile = layerOutPrefix + '.pdf'
if Path(svgoutfile).exists():
print("Step SVG file " + svgoutfile + " already exists, won't override but cancel")
quit()
print(" Exporting layer " + str(i) + " to file " + pdfoutfile)
tree.write(svgoutfile)
t = threading.Thread(target=(lambda: call(['inkscape', '-z', '-C', '--export-pdf=' + pdfoutfile, svgoutfile])))
threads.append(t)
t.start()
print("Waiting for inkscape threads to terminate...")
for step, thread in enumerate(threads):
thread.join()
svgoutfile = outprefix + '-step-' + str('{:03d}'.format(step+1)) + '.svg'
os.remove(svgoutfile)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment