Difference between revisions of "InkSlide"

From Inkscape Wiki
Jump to navigation Jump to search
Line 36: Line 36:


=== How it works ===
=== 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.


=== To do / status ===
=== To do / status ===

Revision as of 16:03, 17 October 2007

InkSlide - quick and easy presentations using Inkscape

InkSlide produces slides like this:

Slide0004.jpg

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

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.

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())