Difference between revisions of "InkSlide"
Terry brown (talk | contribs) |
Terry brown (talk | contribs) |
||
| Line 27: | Line 27: | ||
=== Download === | === Download === | ||
[[ | Copy the [[InkSlide#Code|text below]] into a file called 'inkslide.py'. | ||
=== Use === | === Use === | ||
<pre>python inkslide.py myTemplate.svg mySlides.txt</pre> | <pre>python inkslide.py myTemplate.svg mySlides.txt</pre> | ||
This creates a slide00xx.svg for each slide in your presentation. | |||
=== How it works === | |||
=== To do / status === | |||
InkSlide is very usable, but is missing some features: | |||
*Inline text formatting (bold, color, etc.) for part of a piece of text | |||
*Justification switching | |||
*Verbatim code display | |||
*Simple image inclusion | |||
* currently you can do this using the Inkscape editing features | |||
*more helper tool to make PNG and PDF versions of slides | |||
*configuring / using a directory for edited slides | |||
=== Bugs === | |||
*There may be an issue with the way slide specific content is merged into the template producing conflicting @id attributes, but I haven't seen this happen yet. | |||
=== Author === | |||
Terry Brown, terry-n-brown@yahoo.com - but use underscore, not '-'. | |||
=== Code === | |||
<pre> | |||
# inkslide.py $Id$ | |||
# Author: Terry Brown | |||
# Created: Thu Oct 11 2007 | |||
import lxml.etree as ET | |||
import copy | |||
import subprocess | |||
import os.path | |||
NS = { 'i': 'http://www.inkscape.org/namespaces/inkscape', | |||
's': 'http://www.w3.org/2000/svg', | |||
'xlink' : 'http://www.w3.org/1999/xlink'} | |||
# number of defined tablevels, FIXME, could derive from template? | |||
tabLevels = 4 | |||
def clearId(x, what='id', NS={}): | |||
"""recursively clear @id on element x and descendants""" | |||
if what in x.keys(): | |||
del x.attrib[what] | |||
for i in x.xpath('.//*[@%s]'%what, NS): | |||
del i.attrib[what] | |||
return x | |||
def getDim(fn, Id, what): | |||
"""return dimension of element in fn with @id Id, what is | |||
x, y, width, height | |||
""" | |||
cmd = ('inkscape', '--without-gui', '--query-id', Id, '--query-'+what, fn) | |||
proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, | |||
stderr = subprocess.PIPE) | |||
# make new pipe for stderr to supress chatter from inkscape | |||
proc.wait() | |||
return proc.stdout.read() | |||
def textReader(text): | |||
"""iterate parts in input text""" | |||
if not text: return | |||
for line in text.split('\n'): | |||
if line.startswith('%slide '): | |||
yield {'type':'TITLE', 's':line[7:].strip()} | |||
continue | |||
if line.startswith('%pause'): | |||
yield {'type':'PAUSE'} | |||
continue | |||
if line.startswith('%id '): | |||
yield {'type':'ID', 's':line[4:].strip()} | |||
continue | |||
if not line.strip(): | |||
yield {'type':'BLANK'} | |||
continue | |||
tabs = len(line) - len(line.lstrip('\t')) | |||
yield {'type':'TAB', 'tabs':tabs, 's':line.strip()} | |||
nid = 0 | |||
def nextId(): | |||
"""return an unsed Id""" | |||
# FIXME, should inspect templat doc. | |||
global nid | |||
nid += 1 | |||
return 'is'+str(nid) | |||
def instanceFromTemplate(template, instance, text, pauseOk = True): | |||
slide = 0 | |||
hlist = [] | |||
doc0 = ET.parse(template) | |||
slideId = None | |||
for t in textReader(text): | |||
if t['type'] == 'ID': | |||
slideId = t['s'] | |||
continue | |||
if t['type'] == 'TITLE': | |||
print t['s'] | |||
if hlist: | |||
process(instance, doc0, hlist, slideId, slide) | |||
slide = slide + 1 | |||
hlist = [t] | |||
doc0 = ET.parse(template) | |||
slideId = None | |||
else: | |||
hlist.append(t) | |||
dst = doc0.xpath('//s:g[@id="gr_title"]', NS)[0] | |||
src = doc0.xpath('//s:flowRoot[@id="fr_title"]', NS)[0] | |||
cpy = clearId(copy.deepcopy(src)) | |||
Id = nextId() | |||
cpy.set('id', Id) | |||
hlist[-1]['id']=Id | |||
cpy.xpath('s:flowPara',NS)[0].text = t['s'] | |||
dst.append(cpy) | |||
continue | |||
if t['type'] == 'PAUSE': | |||
if pauseOk: | |||
process(instance, doc0, hlist, slideId, slide) | |||
slide = slide + 1 | |||
continue | |||
hlist.append(t) | |||
if t['type'] == 'BLANK': | |||
continue | |||
if t['type'] == 'TAB': | |||
dst = doc0.xpath('//s:g[@i:label="Texts"]', NS)[0] | |||
src = doc0.xpath('//s:flowRoot[@id="fr_tab%d"]' % t['tabs'], NS)[0] | |||
cpy = clearId(copy.deepcopy(src)) | |||
Id = nextId() | |||
cpy.set('id', Id) | |||
hlist[-1]['id']=Id | |||
cpy.xpath('s:flowPara',NS)[0].text = t['s'] | |||
dst.append(cpy) | |||
#break | |||
continue | |||
process(instance, doc0, hlist, slideId, slide) | |||
def process(instance, doc0, hlist, slideId, slide): | |||
instance = instance.replace('%s', '%04d'%slide) | |||
doc0.write(file(instance, 'w')) | |||
bullDim = {} | |||
tabDim = {} | |||
for n in range(0,tabLevels): | |||
b = {} | |||
x = doc0.xpath('//*[@id="bu_%d"]'%n, NS) | |||
if x: | |||
# clearId(x[0], what='transform') | |||
for i in 'x', 'y', 'width', 'height': | |||
b[i] = float(getDim(instance, 'bu_%d'%n, i)) | |||
bullDim[n] = b | |||
b = {} | |||
x = doc0.xpath('//*[@id="fr_tab%d"]'%n, NS) | |||
if x: | |||
# clearId(x[0], what='transform') | |||
for i in 'x', 'y', 'height': | |||
b[i] = float(getDim(instance, 'fr_tab%d'%n, i)) | |||
tabDim[n] = b | |||
delta = 0. | |||
firstVert = True | |||
for n, i in enumerate(hlist): | |||
if hlist[n]['type'] in ['TITLE']: | |||
continue # already in right place, not a vertical element | |||
hgt = 0 | |||
if hlist[n-1]['type'] == 'BLANK': | |||
hgt = 30 | |||
else: | |||
hgt = float(getDim(instance, hlist[n-1]['id'], 'height')) | |||
hgt += 5 | |||
if firstVert: | |||
hgt = 0 | |||
firstVert = False | |||
delta += hgt | |||
if hlist[n]['type'] == 'BLANK': continue | |||
tabs = hlist[n]['tabs'] | |||
r = doc0.xpath('//s:flowRoot[@id="%s"]' % hlist[n]['id'], NS)[0] | |||
clearId(r, what='transform') | |||
r = r.xpath('.//s:rect', NS)[0] | |||
r.set('x', str(tabDim[tabs]['x'])) | |||
y = tabDim[tabs]['y'] + delta | |||
#print '%s -> %s' % (str(r.get('y')), str(y)) | |||
#print tabDim[tabs]['y'] | |||
r.set('y', str(y)) | |||
if (hlist[n]['type'] == 'TAB' and hlist[n]['tabs'] in bullDim | |||
and 'isDone' not in hlist[n]): | |||
hlist[n]['isDone'] = True | |||
dx = (tabDim[tabs]['x'] | |||
- bullDim[tabs]['x'] | |||
- 1.5*bullDim[tabs]['width'] | |||
) | |||
dy = (y | |||
+ tabDim[tabs]['height'] / 2. | |||
- bullDim[tabs]['y'] | |||
- bullDim[tabs]['height'] / 2. | |||
) | |||
dst = doc0.xpath('//s:g[@i:label="Texts"]', NS)[0] | |||
#print y, tabDim[tabs]['height'], bullDim[tabs]['y'], bullDim[tabs]['height'], dy | |||
clone = ET.Element('svg:use') | |||
clone.set('transform', 'translate(%f,%f)' % (dx,dy)) | |||
clone.set('{%s}href'%NS['xlink'], '#bu_%d'%tabs) | |||
dst.append(clone) | |||
#path = ET.Element('svg:path') | |||
#path.set('d', ('M %f,%f' + ' L %f,%f'*5 + ' z') % ( | |||
# bullDim[tabs]['x'], bullDim[tabs]['y'], | |||
# bullDim[tabs]['x']+20, bullDim[tabs]['y']+bullDim[tabs]['height']/2., | |||
# bullDim[tabs]['x'], bullDim[tabs]['y']+bullDim[tabs]['height'], | |||
# tabDim[tabs]['x'], tabDim[tabs]['y'], | |||
# tabDim[tabs]['x']+20, tabDim[tabs]['y']+tabDim[tabs]['height']/2., | |||
# tabDim[tabs]['x'], tabDim[tabs]['y']+tabDim[tabs]['height'], | |||
# )) | |||
#dst.append(path) | |||
#break | |||
# look for instance specific parts | |||
if slideId: | |||
f = instancePath(slideId) | |||
if os.path.isfile(f): | |||
comp = ET.parse(f) | |||
svg = doc0.xpath('//s:svg', NS)[0] | |||
g = "{%s}g"%NS['s'] | |||
k = "{%s}label"%NS['i'] | |||
for n, i in enumerate(svg.getchildren()): | |||
if i.tag == g and i.get(k, '').startswith('Instance'): | |||
x = comp.xpath('//s:svg//s:g[@i:label="%s"]'%i.get(k), NS) | |||
if x: | |||
x = x[0] | |||
cpy = clearId(copy.deepcopy(x)) | |||
svg[n] = cpy | |||
defs = comp.xpath('//s:svg/s:defs/*', NS) | |||
dst = doc0.xpath('//s:svg/s:defs', NS)[0] | |||
for d in defs: | |||
Id = d.get('id') | |||
current = doc0.xpath('//s:svg/s:defs/*[@id="%s"]'%Id, NS) | |||
if not current: | |||
cpy = copy.deepcopy(d) | |||
dst.append(cpy) | |||
doc0.write(file(instance, 'w')) | |||
return slideId | |||
def instancePath(slideId, component = None): | |||
"""return path to component for slide""" | |||
# return os.path.join(slideId, component) | |||
return slideId+'.svg' | |||
def writeComponents(inkscapeFile, slideId): | |||
"""write components from file for slide""" | |||
if not slideId: return | |||
doc0 = ET.parse(inkscapeFile) | |||
hasComponents = False | |||
for i in doc0.xpath('//s:svg//s:g', NS): | |||
k = "{%s}label"%NS['i'] | |||
# print i.keys(),k | |||
if (k in i.keys() and i.get(k).startswith('Instance')): | |||
if len(i) > 0: | |||
hasComponents = True | |||
break | |||
# ET.ElementTree(i).write(f) | |||
f = instancePath(slideId, 'components') | |||
if hasComponents: | |||
if (os.path.abspath(os.path.realpath(inkscapeFile)) != | |||
os.path.abspath(os.path.realpath(f))): | |||
file(f, 'w').write(file(inkscapeFile).read()) | |||
else: | |||
if os.path.isfile(f): | |||
os.remove(f) | |||
if __name__ == '__main__': | |||
import sys | |||
instanceFromTemplate(sys.argv[1], 'slide%s.svg', file(sys.argv[2]).read()) | |||
</pre> | |||
Revision as of 15:43, 17 October 2007
InkSlide - quick and easy presentations using Inkscape
InkSlide produces slides like this:
from simple text input like this:
%slide InkSlide: Features
Features include wrapped top level text and
mulitple
levels
of wrapped bulleted lists with bullets and font
information taken from the template file.
Slide specific content like this:
which is updated when the template changes.
An Inkscape file is used as a template file to define the background, title position and font, fonts and positions for text at different levels of indentation, groups to be cloned and used as bullets, etc.
Content specific to a particular slide can also be created in Inkscape, this content merged with the template and text input to make the final slide, so changes to the template after a particular slide is edited in Inkscape are included.
Download
Copy the text below into a file called 'inkslide.py'.
Use
python inkslide.py myTemplate.svg mySlides.txt
This creates a slide00xx.svg for each slide in your presentation.
How it works
To do / status
InkSlide is very usable, but is missing some features:
- Inline text formatting (bold, color, etc.) for part of a piece of text
- Justification switching
- Verbatim code display
- Simple image inclusion
* currently you can do this using the Inkscape editing features
- more helper tool to make PNG and PDF versions of slides
- configuring / using a directory for edited slides
Bugs
- There may be an issue with the way slide specific content is merged into the template producing conflicting @id attributes, but I haven't seen this happen yet.
Author
Terry Brown, terry-n-brown@yahoo.com - but use underscore, not '-'.
Code
# inkslide.py $Id$
# Author: Terry Brown
# Created: Thu Oct 11 2007
import lxml.etree as ET
import copy
import subprocess
import os.path
NS = { 'i': 'http://www.inkscape.org/namespaces/inkscape',
's': 'http://www.w3.org/2000/svg',
'xlink' : 'http://www.w3.org/1999/xlink'}
# number of defined tablevels, FIXME, could derive from template?
tabLevels = 4
def clearId(x, what='id', NS={}):
"""recursively clear @id on element x and descendants"""
if what in x.keys():
del x.attrib[what]
for i in x.xpath('.//*[@%s]'%what, NS):
del i.attrib[what]
return x
def getDim(fn, Id, what):
"""return dimension of element in fn with @id Id, what is
x, y, width, height
"""
cmd = ('inkscape', '--without-gui', '--query-id', Id, '--query-'+what, fn)
proc = subprocess.Popen(cmd, stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
# make new pipe for stderr to supress chatter from inkscape
proc.wait()
return proc.stdout.read()
def textReader(text):
"""iterate parts in input text"""
if not text: return
for line in text.split('\n'):
if line.startswith('%slide '):
yield {'type':'TITLE', 's':line[7:].strip()}
continue
if line.startswith('%pause'):
yield {'type':'PAUSE'}
continue
if line.startswith('%id '):
yield {'type':'ID', 's':line[4:].strip()}
continue
if not line.strip():
yield {'type':'BLANK'}
continue
tabs = len(line) - len(line.lstrip('\t'))
yield {'type':'TAB', 'tabs':tabs, 's':line.strip()}
nid = 0
def nextId():
"""return an unsed Id"""
# FIXME, should inspect templat doc.
global nid
nid += 1
return 'is'+str(nid)
def instanceFromTemplate(template, instance, text, pauseOk = True):
slide = 0
hlist = []
doc0 = ET.parse(template)
slideId = None
for t in textReader(text):
if t['type'] == 'ID':
slideId = t['s']
continue
if t['type'] == 'TITLE':
print t['s']
if hlist:
process(instance, doc0, hlist, slideId, slide)
slide = slide + 1
hlist = [t]
doc0 = ET.parse(template)
slideId = None
else:
hlist.append(t)
dst = doc0.xpath('//s:g[@id="gr_title"]', NS)[0]
src = doc0.xpath('//s:flowRoot[@id="fr_title"]', NS)[0]
cpy = clearId(copy.deepcopy(src))
Id = nextId()
cpy.set('id', Id)
hlist[-1]['id']=Id
cpy.xpath('s:flowPara',NS)[0].text = t['s']
dst.append(cpy)
continue
if t['type'] == 'PAUSE':
if pauseOk:
process(instance, doc0, hlist, slideId, slide)
slide = slide + 1
continue
hlist.append(t)
if t['type'] == 'BLANK':
continue
if t['type'] == 'TAB':
dst = doc0.xpath('//s:g[@i:label="Texts"]', NS)[0]
src = doc0.xpath('//s:flowRoot[@id="fr_tab%d"]' % t['tabs'], NS)[0]
cpy = clearId(copy.deepcopy(src))
Id = nextId()
cpy.set('id', Id)
hlist[-1]['id']=Id
cpy.xpath('s:flowPara',NS)[0].text = t['s']
dst.append(cpy)
#break
continue
process(instance, doc0, hlist, slideId, slide)
def process(instance, doc0, hlist, slideId, slide):
instance = instance.replace('%s', '%04d'%slide)
doc0.write(file(instance, 'w'))
bullDim = {}
tabDim = {}
for n in range(0,tabLevels):
b = {}
x = doc0.xpath('//*[@id="bu_%d"]'%n, NS)
if x:
# clearId(x[0], what='transform')
for i in 'x', 'y', 'width', 'height':
b[i] = float(getDim(instance, 'bu_%d'%n, i))
bullDim[n] = b
b = {}
x = doc0.xpath('//*[@id="fr_tab%d"]'%n, NS)
if x:
# clearId(x[0], what='transform')
for i in 'x', 'y', 'height':
b[i] = float(getDim(instance, 'fr_tab%d'%n, i))
tabDim[n] = b
delta = 0.
firstVert = True
for n, i in enumerate(hlist):
if hlist[n]['type'] in ['TITLE']:
continue # already in right place, not a vertical element
hgt = 0
if hlist[n-1]['type'] == 'BLANK':
hgt = 30
else:
hgt = float(getDim(instance, hlist[n-1]['id'], 'height'))
hgt += 5
if firstVert:
hgt = 0
firstVert = False
delta += hgt
if hlist[n]['type'] == 'BLANK': continue
tabs = hlist[n]['tabs']
r = doc0.xpath('//s:flowRoot[@id="%s"]' % hlist[n]['id'], NS)[0]
clearId(r, what='transform')
r = r.xpath('.//s:rect', NS)[0]
r.set('x', str(tabDim[tabs]['x']))
y = tabDim[tabs]['y'] + delta
#print '%s -> %s' % (str(r.get('y')), str(y))
#print tabDim[tabs]['y']
r.set('y', str(y))
if (hlist[n]['type'] == 'TAB' and hlist[n]['tabs'] in bullDim
and 'isDone' not in hlist[n]):
hlist[n]['isDone'] = True
dx = (tabDim[tabs]['x']
- bullDim[tabs]['x']
- 1.5*bullDim[tabs]['width']
)
dy = (y
+ tabDim[tabs]['height'] / 2.
- bullDim[tabs]['y']
- bullDim[tabs]['height'] / 2.
)
dst = doc0.xpath('//s:g[@i:label="Texts"]', NS)[0]
#print y, tabDim[tabs]['height'], bullDim[tabs]['y'], bullDim[tabs]['height'], dy
clone = ET.Element('svg:use')
clone.set('transform', 'translate(%f,%f)' % (dx,dy))
clone.set('{%s}href'%NS['xlink'], '#bu_%d'%tabs)
dst.append(clone)
#path = ET.Element('svg:path')
#path.set('d', ('M %f,%f' + ' L %f,%f'*5 + ' z') % (
# bullDim[tabs]['x'], bullDim[tabs]['y'],
# bullDim[tabs]['x']+20, bullDim[tabs]['y']+bullDim[tabs]['height']/2.,
# bullDim[tabs]['x'], bullDim[tabs]['y']+bullDim[tabs]['height'],
# tabDim[tabs]['x'], tabDim[tabs]['y'],
# tabDim[tabs]['x']+20, tabDim[tabs]['y']+tabDim[tabs]['height']/2.,
# tabDim[tabs]['x'], tabDim[tabs]['y']+tabDim[tabs]['height'],
# ))
#dst.append(path)
#break
# look for instance specific parts
if slideId:
f = instancePath(slideId)
if os.path.isfile(f):
comp = ET.parse(f)
svg = doc0.xpath('//s:svg', NS)[0]
g = "{%s}g"%NS['s']
k = "{%s}label"%NS['i']
for n, i in enumerate(svg.getchildren()):
if i.tag == g and i.get(k, '').startswith('Instance'):
x = comp.xpath('//s:svg//s:g[@i:label="%s"]'%i.get(k), NS)
if x:
x = x[0]
cpy = clearId(copy.deepcopy(x))
svg[n] = cpy
defs = comp.xpath('//s:svg/s:defs/*', NS)
dst = doc0.xpath('//s:svg/s:defs', NS)[0]
for d in defs:
Id = d.get('id')
current = doc0.xpath('//s:svg/s:defs/*[@id="%s"]'%Id, NS)
if not current:
cpy = copy.deepcopy(d)
dst.append(cpy)
doc0.write(file(instance, 'w'))
return slideId
def instancePath(slideId, component = None):
"""return path to component for slide"""
# return os.path.join(slideId, component)
return slideId+'.svg'
def writeComponents(inkscapeFile, slideId):
"""write components from file for slide"""
if not slideId: return
doc0 = ET.parse(inkscapeFile)
hasComponents = False
for i in doc0.xpath('//s:svg//s:g', NS):
k = "{%s}label"%NS['i']
# print i.keys(),k
if (k in i.keys() and i.get(k).startswith('Instance')):
if len(i) > 0:
hasComponents = True
break
# ET.ElementTree(i).write(f)
f = instancePath(slideId, 'components')
if hasComponents:
if (os.path.abspath(os.path.realpath(inkscapeFile)) !=
os.path.abspath(os.path.realpath(f))):
file(f, 'w').write(file(inkscapeFile).read())
else:
if os.path.isfile(f):
os.remove(f)
if __name__ == '__main__':
import sys
instanceFromTemplate(sys.argv[1], 'slide%s.svg', file(sys.argv[2]).read())
