Difference between revisions of "InkSlide"
Terry brown (talk | contribs) |
Terry brown (talk | contribs) |
||
Line 34: | Line 34: | ||
This creates a slide00xx.svg for each slide in your presentation. | This creates a slide00xx.svg for each slide in your presentation. | ||
<pre>python inkslide.py --writeInstance <instanceId> myTemplate.svg mySlides.txt</pre> | |||
Creates / updates the Inkscape file you can edit to add special content to this particular slide. '''NOT IMPLEMENTED YET''' - the code exists, but not the command line hook. | |||
=== How it works === | === How it works === | ||
Line 42: | Line 46: | ||
Finally, InkSlide checks to see if there's an %id tag for the slide, and if so if there's a corresponding Inkscape SVG file with content for this particular slide. This content has to occur in layers whose name starts with ''Instance'', and these layers have to be present in the template file, but you can have more than one, so they can be above and below the regular content. If such content is found, it's copied into the output slide. | Finally, InkSlide checks to see if there's an %id tag for the slide, and if so if there's a corresponding Inkscape SVG file with content for this particular slide. This content has to occur in layers whose name starts with ''Instance'', and these layers have to be present in the template file, but you can have more than one, so they can be above and below the regular content. If such content is found, it's copied into the output slide. | ||
=== The Template File === | |||
=== Text Input === | |||
<pre> | |||
%title Inkslide: an example | |||
This text will appear as a top level paragraph. | |||
Indented by a single tab, this text will appear as a bulleted item. | |||
Two tabs -> second level bulleted list. | |||
A gap will be left for the preceeding line. | |||
%pause | |||
Two slides will be produced for this input, one with everything up to the %pause above, and one with the remaining content. Incremental list display can be done this way. | |||
The %id tag below indicates that a file with a name like 'extra/is_intro.svg' should be inspected for extra content for this particular slide. | |||
%id is_intro | |||
%title Inkslide: the next slide... | |||
</pre> | |||
=== The GUI === | |||
A GUI for InkSlide is available as a plug-in for the [http://webpages.charter.net/edreamleo/front.html Leo] outline editor. This makes slide re-arranging easy, and adds some macro substitution capability. It also adds buttons for editing slide specific content in Inkscape. | |||
FIXME: post plug-in on Leo wiki and link. | |||
=== To do / status === | === To do / status === | ||
Line 54: | Line 83: | ||
*more helper tool to make PNG and PDF versions of slides | *more helper tool to make PNG and PDF versions of slides | ||
*configuring / using a directory for edited slides | *configuring / using a directory for edited slides | ||
*command line flags for generating specific slides | |||
=== Bugs === | === Bugs === | ||
Line 59: | Line 89: | ||
*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. | *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 === | === Author / Credits === | ||
InkSlide was written by Terry Brown, terry-n-brown@yahoo.com - but use underscore, not '-'. | |||
InkSlide was inspired by [http://member.wide.ad.jp/wg/mgp/ MagicPoint] | |||
=== Code === | === Code === |
Revision as of 16:29, 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.
python inkslide.py --writeInstance <instanceId> myTemplate.svg mySlides.txt
Creates / updates the Inkscape file you can edit to add special content to this particular slide. NOT IMPLEMENTED YET - the code exists, but not the command line hook.
How it works
First InkSlide parses the text input, something like the example at the top of this page. It looks for flowed text boxes in the template file with special @id attributes like fr_title for the title, and fr_tab2 for the second level of bulleted lists. Copies of these elements are made and placed in a layer called 'Texts'. At this point, the text is all piled up at the top of the page.
InkSlide saves the file, and uses Inkscape's command line --query-height and friends to find out how high the wrapped text pieces are. At it then repositions them properly, adds clones of the bullet markers, etc. Bullet markers have special @id attributes like bu_1 for the first level of bulleted list.
Finally, InkSlide checks to see if there's an %id tag for the slide, and if so if there's a corresponding Inkscape SVG file with content for this particular slide. This content has to occur in layers whose name starts with Instance, and these layers have to be present in the template file, but you can have more than one, so they can be above and below the regular content. If such content is found, it's copied into the output slide.
The Template File
Text Input
%title Inkslide: an example This text will appear as a top level paragraph. Indented by a single tab, this text will appear as a bulleted item. Two tabs -> second level bulleted list. A gap will be left for the preceeding line. %pause Two slides will be produced for this input, one with everything up to the %pause above, and one with the remaining content. Incremental list display can be done this way. The %id tag below indicates that a file with a name like 'extra/is_intro.svg' should be inspected for extra content for this particular slide. %id is_intro %title Inkslide: the next slide...
The GUI
A GUI for InkSlide is available as a plug-in for the Leo outline editor. This makes slide re-arranging easy, and adds some macro substitution capability. It also adds buttons for editing slide specific content in Inkscape.
FIXME: post plug-in on Leo wiki and link.
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
- command line flags for generating specific 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 / Credits
InkSlide was written by Terry Brown, terry-n-brown@yahoo.com - but use underscore, not '-'.
InkSlide was inspired by MagicPoint
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())