moved to tools
authorMarcel <radermacher@kit.edu>
Tue, 10 May 2016 07:41:42 +0000 (09:41 +0200)
committerMarcel <radermacher@kit.edu>
Tue, 10 May 2016 07:41:42 +0000 (09:41 +0200)
pdftoipe.sh [deleted file]
svgtoipe.py [deleted file]
tools/pdftoipe.sh [new file with mode: 0755]
tools/svgtoipe.py [new file with mode: 0755]

diff --git a/pdftoipe.sh b/pdftoipe.sh
deleted file mode 100755 (executable)
index 16f641a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-# !/bin/bash
-echo $1
-inkscape --verb=ObjectToPath --select=EditSelectAll --export-plain-svg=/tmp/$1.svg $1
-svgtoipe.py /tmp/$1.svg $2
-rm /tmp/$1.svg
diff --git a/svgtoipe.py b/svgtoipe.py
deleted file mode 100755 (executable)
index 45f3e98..0000000
+++ /dev/null
@@ -1,865 +0,0 @@
-#!/usr/bin/env python
-# --------------------------------------------------------------------
-# convert SVG to Ipe format
-# --------------------------------------------------------------------
-# 
-# Copyright (C) 2009-2014  Otfried Cheong
-#
-# svgtoipe is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-# 
-# svgtoipe is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with svgtoipe; if not, you can find it at
-# "http://www.gnu.org/copyleft/gpl.html", or write to the Free
-# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-#
-# --------------------------------------------------------------------
-
-svgtoipe_version = "20091018"
-
-import sys
-import xml.dom.minidom as xml
-from xml.dom.minidom import Node
-import re
-import math
-
-import base64
-import cStringIO
-
-try:
-  from PIL import Image
-  have_pil = True
-except:
-  have_pil = False
-
-# --------------------------------------------------------------------
-
-color_keywords = {
-  "black" : "rgb(0, 0, 0)",
-  "green" :"rgb(0, 128, 0)",
-  "silver" :"rgb(192, 192, 192)",
-  "lime" :"rgb(0, 255, 0)",
-  "gray" :"rgb(128, 128, 128)",
-  "olive" :"rgb(128, 128, 0)",
-  "white" :"rgb(255, 255, 255)",
-  "yellow" :"rgb(255, 255, 0)",
-  "maroon" :"rgb(128, 0, 0)",
-  "navy" :"rgb(0, 0, 128)",
-  "red" :"rgb(255, 0, 0)",
-  "blue" :"rgb(0, 0, 255)",
-  "purple" :"rgb(128, 0, 128)",
-  "teal" :"rgb(0, 128, 128)",
-  "fuchsia" :"rgb(255, 0, 255)",
-  "aqua" :"rgb(0, 255, 255)",
-}
-
-attribute_names = [ "stroke", 
-                    "fill",
-                    "stroke-opacity",
-                    "fill-opacity",
-                    "stroke-width",
-                    "fill-rule",
-                    "stroke-linecap",
-                    "stroke-linejoin",
-                    "stroke-dasharray",
-                    "stroke-dashoffset",
-                    "stroke-miterlimit",
-                    "opacity", 
-                    "font-size" ]
-  
-def printAttributes(n):
-  a = n.attributes
-  for i in range(a.length):
-    name = a.item(i).name
-    if name[:9] != "sodipodi:" and name[:9] != "inkscape:":
-      print "   ", name, n.getAttribute(name)
-
-def parse_float(txt):
-  if not txt:
-    return None
-  if txt.endswith('px') or txt.endswith('pt'):
-    return float(txt[:-2])
-  elif txt.endswith('pc'):
-    return 12 * float(txt[:-2])
-  elif txt.endswith('mm'):
-    return 72.0 * float(txt[:-2]) / 25.4
-  elif txt.endswith('cm'):
-    return 72.0 * float(txt[:-2]) / 2.54
-  elif txt.endswith('in'):
-    return 72.0 * float(txt[:-2])
-  else:
-    return float(txt)
-
-def parse_opacity(txt):
-  if not txt: 
-    return None
-  m = int(10 * (float(txt) + 0.05))
-  if m == 0: m = 1
-  return 10 * m
-
-def parse_list(string):
-  return re.findall("([A-Za-z]|-?[0-9]+\.?[0-9]*(?:e-?[0-9]*)?)", string)
-
-def parse_style(string):
-  sdict = {}
-  for item in string.split(';'):
-    if ':' in item:
-      key, value = item.split(':')
-      sdict[key.strip()] = value.strip()
-  return sdict
-
-def parse_color_component(txt):
-  if txt.endswith("%"):
-    return float(txt[:-1]) / 100.0
-  else:
-    return int(txt) / 255.0
-  
-def parse_color(c):
-  if not c or c == 'none':
-    return None
-  if c in color_keywords:
-    c = color_keywords[c]
-  m =  re.match(r"rgb\(([0-9\.]+%?),\s*([0-9\.]+%?),\s*([0-9\.]+%?)\s*\)", c)
-  if m:
-    r = parse_color_component(m.group(1))
-    g = parse_color_component(m.group(2))
-    b = parse_color_component(m.group(3))
-    return (r, g, b)
-  m = re.match(r"#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$", c)
-  if m:
-    r = int(m.group(1), 16) / 15.0
-    g = int(m.group(2), 16) / 15.0
-    b = int(m.group(3), 16) / 15.0
-    return (r, g, b)
-  m = re.match(r"#([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])"
-               + r"([0-9a-fA-F][0-9a-fA-F])$", c)
-  if m:
-    r = int(m.group(1), 16) / 255.0
-    g = int(m.group(2), 16) / 255.0
-    b = int(m.group(3), 16) / 255.0
-    return (r, g, b)
-  sys.stderr.write("Unknown color: %s\n" % c)
-  return None
-
-def pnext(d, n):
-  l = []
-  while n > 0:
-    l.append(float(d.pop(0)))
-    n -= 1
-  return tuple(l)
-
-def parse_path(out, d):
-  d = re.findall("([A-Za-z]|-?[0-9]+\.?[0-9]*(?:e-?[0-9]*)?)", d)
-  x, y = 0.0, 0.0
-  xs, ys = 0.0, 0.0
-  while d:
-    if not d[0][0] in "01234567890.-":
-      opcode = d.pop(0)
-    if opcode == 'M':
-      x, y = pnext(d, 2)
-      out.write("%g %g m\n" % (x, y))
-      opcode = 'L'
-    elif opcode == 'm':
-      x1, y1 = pnext(d, 2)
-      x += x1
-      y += y1
-      out.write("%g %g m\n" % (x, y))
-      opcode = 'l'
-    elif opcode == 'L':
-      x, y = pnext(d, 2)
-      out.write("%g %g l\n" % (x, y))
-    elif opcode == 'l':
-      x1, y1 = pnext(d, 2)
-      x += x1
-      y += y1
-      out.write("%g %g l\n" % (x, y))
-    elif opcode == 'H':
-      x = pnext(d, 1)[0]
-      out.write("%g %g l\n" % (x, y))
-    elif opcode == 'h':
-      x += pnext(d, 1)[0]
-      out.write("%g %g l\n" % (x, y))
-    elif opcode == 'V':
-      y = pnext(d, 1)[0]
-      out.write("%g %g l\n" % (x, y))
-    elif opcode == 'v':
-      y += pnext(d, 1)[0]
-      out.write("%g %g l\n" % (x, y))
-    elif opcode == 'C':
-      x1, y1, xs, ys, x, y = pnext(d, 6)
-      out.write("%g %g %g %g %g %g c\n" % (x1, y1, xs, ys, x, y))
-    elif opcode == 'c':
-      x1, y1, xs, ys, xf, yf = pnext(d, 6)
-      x1 += x; y1 += y
-      xs += x; ys += y
-      x += xf; y += yf
-      out.write("%g %g %g %g %g %g c\n" % (x1, y1, xs, ys, x, y))
-    elif opcode == 'S' or opcode == 's':
-      x2, y2, xf, yf = pnext(d, 4)
-      if opcode == 's':
-        x2 += x; y2 += y
-        xf += x; yf += y
-      x1 = x + (x - xs); y1 = y + (y - ys)
-      out.write("%g %g %g %g %g %g c\n" % (x1, y1, x2, y2, xf, yf))
-      xs, ys = x2, y2
-      x, y = xf, yf
-    elif opcode == 'Q':
-      xs, ys, x, y = pnext(d, 4)
-      out.write("%g %g %g %g q\n" % (xs, ys, x, y))
-    elif opcode == 'q':
-      xs, ys, xf, yf = pnext(d, 4)
-      xs += x; ys += y
-      x += xf; y += yf
-      out.write("%g %g %g %g q\n" % (xs, ys, x, y))
-    elif opcode == 'T' or opcode == 't':
-      xf, yf = pnext(d, 2)
-      if opcode == 't':
-        xf += x; yf += y
-      x1 = x + (x - xs); y1 = y + (y - ys)
-      out.write("%g %g %g %g q\n" % (x1, y1, xf, yf))
-      xs, ys = x1, y1
-      x, y = xf, yf
-    elif opcode == 'A' or opcode == 'a':
-      rx, ry, phi, large_arc, sweep, x2, y2 = pnext(d, 7)
-      if opcode == 'a':
-        x2 += x; y2 += y
-      draw_arc(out, x, y, rx, ry, phi, large_arc, sweep, x2, y2)
-      x, y = x2, y2
-    elif opcode in 'zZ':
-      out.write("h\n")
-    else:
-      sys.stderr.write("Unrecognised opcode: %s\n" % opcode)
-
-def parse_transformation(txt):
-  d = re.findall("[a-zA-Z]+\([^)]*\)", txt)
-  m = Matrix()
-  while d:
-    m1 = Matrix(d.pop(0))
-    m = m * m1
-  return m
-
-def get_gradientTransform(n):
-  if n.hasAttribute("gradientTransform"):
-    return parse_transformation(n.getAttribute("gradientTransform"))
-  return Matrix()
-
-def parse_transform(n):
-  if n.hasAttribute("transform"):
-    return parse_transformation(n.getAttribute("transform"))
-  return None
-
-# Convert from endpoint to center parameterization
-# www.w3.org/TR/2003/REC-SVG11-20030114/implnote.html#ArcImplementationNotes
-def draw_arc(out, x1, y1, rx, ry, phi, large_arc, sweep, x2, y2):
-  phi = math.pi * phi / 180.0
-  cp = math.cos(phi); sp = math.sin(phi)
-  dx = .5 * (x1 - x2); dy = .5 * (y1 - y2)
-  x1p = cp * dx + sp * dy; y1p = -sp * dx + cp * dy
-  r2 = (((rx * ry)**2 - (rx * y1p)**2 - (ry * x1p)**2)/
-        ((rx * y1p)**2 + (ry * x1p)**2))
-  if r2 < 0: r2 = 0
-  r = math.sqrt(r2)
-  if large_arc == sweep:
-    r = -r
-  cxp = r * rx * y1p / ry; cyp = -r * ry * x1p / rx
-  cx = cp * cxp - sp * cyp + .5 * (x1 + x2)
-  cy = sp * cxp + cp * cyp + .5 * (y1 + y2)
-  m = Matrix([rx, 0, 0, ry, 0, 0])
-  m = Matrix([cp, sp, -sp, cp, cx, cy]) * m
-  if sweep == 0:
-    m = m * Matrix([1, 0, 0, -1, 0, 0])
-  out.write("%s %g %g a\n" % (str(m), x2, y2))
-
-# --------------------------------------------------------------------
-
-class Matrix(object):
-
-  # Default is identity matrix
-  def __init__(self, string = None):
-    self.values = [1, 0, 0, 1, 0, 0] 
-    if not string or string == "":
-      return
-    if isinstance(string, list):
-      self.values = string
-      return
-    mat = re.match(r"([a-zA-Z]+)\(([^)]*)\)$", string)
-    if not mat:
-      sys.stderr.write("Unknown transform: %s\n" % string)
-    op = mat.group(1)
-    d = [float(x) for x in parse_list(mat.group(2))]
-    if op == "matrix":
-      self.values = d
-    elif op == "translate":
-      if len(d) == 1: d.append(0.0)
-      self.values = [1, 0, 0, 1, d[0], d[1]]
-    elif op == "scale":
-      if len(d) == 1: d.append(d[0])
-      sx, sy = d
-      self.values = [sx, 0, 0, sy, 0, 0]
-    elif op == "rotate":
-      phi = math.pi * d[0] / 180.0
-      self.values = [math.cos(phi), math.sin(phi), 
-                     -math.sin(phi), math.cos(phi), 0, 0]           
-    elif op == "skewX":
-      tphi = math.tan(math.pi * d[0] / 180.0)
-      self.values = [1, 0, tphi, 1, 0, 0]
-    elif op == "skewY":
-      tphi = math.tan(math.pi * d[0] / 180.0)
-      self.values = [1, tphi, 0, 1, 0, 0]
-    else:
-      sys.stderr.write("Unknown transform: %s\n" % string)
-      
-  def __call__(self, other):
-    return (self.values[0]*other[0] + self.values[2]*other[1] + self.values[4],
-            self.values[1]*other[0] + self.values[3]*other[1] + self.values[5])
-  
-  def inverse(self):
-    d = float(self.values[0]*self.values[3] - self.values[1]*self.values[2])
-    return Matrix([self.values[3]/d, -self.values[1]/d, 
-                   -self.values[2]/d, self.values[0]/d,
-                   (self.values[2]*self.values[5] - 
-                    self.values[3]*self.values[4])/d,
-                   (self.values[1]*self.values[4] - 
-                    self.values[0]*self.values[5])/d])
-
-  def __mul__(self, other):
-    a, b, c, d, e, f = self.values
-    u, v, w, x, y, z = other.values
-    return Matrix([a*u + c*v, b*u + d*v, a*w + c*x, 
-                   b*w + d*x, a*y + c*z + e, b*y + d*z + f])
-  
-  def __str__(self):
-    a, b, c, d, e, f = self.values
-    return "%g %g %g %g %g %g" % (a, b, c, d, e, f)
-    
-# --------------------------------------------------------------------
-                               
-class Svg():
-
-  def __init__(self, fname):
-    self.dom = xml.parse(fname)
-    attr = { }
-    for a in attribute_names:
-      attr[a] = None
-    self.attributes = [ attr ]
-    self.defs = { }
-    for n in self.dom.childNodes:
-      if n.nodeType == Node.ELEMENT_NODE and n.tagName == "svg":
-        if n.hasAttribute("viewBox"):
-          x, y, w, h = [float(x) for x in parse_list(n.getAttribute("viewBox"))]
-          self.width = w
-          self.height = h
-          self.origin = (x, y)
-        else:
-          self.width = parse_float(n.getAttribute("width"))
-          self.height = parse_float(n.getAttribute("height"))
-          self.origin = (0, 0)
-        self.root = n
-        return
-
-# --------------------------------------------------------------------
-
-  def parse_svg(self, outname):
-    self.out = open(outname, "w")
-    self.out.write('<?xml version="1.0"?>\n')
-    self.out.write('<!DOCTYPE ipe SYSTEM "ipe.dtd">\n')
-    self.out.write('<ipe version="70005" creator="svgtoipe %s">\n' %
-                   svgtoipe_version)
-    self.out.write('<ipestyle>\n')
-    self.out.write(('<layout paper="%d %d" frame="%d %d" ' + 
-                    'origin="0 0" crop="no"/>\n') % 
-                   (self.width, self.height, self.width, self.height))
-    for t in range(10, 100, 10):
-      self.out.write('<opacity name="%d%%" value="0.%d"/>\n' % (t, t))
-    # set SVG defaults
-    self.out.write('<pathstyle cap="0" join="0" fillrule="wind"/>\n')
-    self.out.write('</ipestyle>\n')
-    # collect definitions
-    for n in self.root.childNodes:
-      if n.nodeType != Node.ELEMENT_NODE:
-        continue
-      if hasattr(self, "def_" + n.tagName):
-        getattr(self, "def_" + n.tagName)(n)
-    # write definitions into stylesheet
-    if len(self.defs) > 0:
-      self.out.write('<ipestyle>\n')
-      for k in self.defs:
-        if self.defs[k][0] == "linearGradient":
-          self.write_linear_gradient(k)
-        elif self.defs[k][0] == "radialGradient":
-          self.write_radial_gradient(k)
-      self.out.write('</ipestyle>\n')
-    # start real data
-    self.out.write('<page>\n')
-    m = Matrix([1, 0, 0, 1, 0, self.height / 2.0])
-    m = m * Matrix([1, 0, 0, -1, 0, 0])
-    m = m * Matrix([1, 0, 0, 1, 
-                    -self.origin[0], -(self.origin[1] + self.height / 2.0)])
-    self.out.write('<group matrix="%s">\n' % str(m))
-    for n in self.root.childNodes:
-      if n.nodeType != Node.ELEMENT_NODE:
-        continue
-      if hasattr(self, "node_" + n.tagName):
-        getattr(self, "node_" + n.tagName)(n)
-      else:
-        sys.stderr.write("Unhandled node: %s\n" % n.tagName)
-    self.out.write('</group>\n')
-    self.out.write('</page>\n')
-    self.out.write('</ipe>\n')
-    self.out.close()
-
-# --------------------------------------------------------------------
-
-  def write_linear_gradient(self, k):
-    typ, x1, x2, y1, y2, stops, matrix = self.defs[k]
-    self.out.write('<gradient name="g%s" type="axial" extend="yes"\n' % k)
-    self.out.write(' matrix="%s"' % str(matrix))
-    self.out.write(' coords="%g %g %g %g">\n' % (x1, y1, x2, y2))
-    for s in stops:
-      offset, color = s
-      self.out.write(' <stop offset="%g" color="%g %g %g"/>\n' % 
-                     (offset, color[0], color[1], color[2]))
-    self.out.write('</gradient>\n')
-    
-  def write_radial_gradient(self, k):
-    typ, cx, cy, r, fx, fy, stops, matrix = self.defs[k]
-    self.out.write('<gradient name="g%s" type="radial" extend="yes"\n' % k)
-    self.out.write(' matrix="%s"' % str(matrix))
-    self.out.write(' coords="%g %g %g %g %g %g">\n' % (fx, fy, 0, cx, cy, r))
-    for s in stops:
-      offset, color = s
-      self.out.write(' <stop offset="%g" color="%g %g %g"/>\n' % 
-                     (offset, color[0], color[1], color[2]))
-    self.out.write('</gradient>\n')
-
-  def get_stops(self, n):
-    stops = []
-    for m in n.childNodes:
-      if m.nodeType != Node.ELEMENT_NODE:
-        continue
-      if m.tagName != "stop":
-        continue # should not happen
-      offs = m.getAttribute("offset")
-      if offs.endswith("%"):
-        offs = float(offs[:-1]) / 100.0
-      else:
-        offs = float(offs)
-      color = parse_color(m.getAttribute("stop-color"))
-      if m.hasAttribute("style"):
-        sdict = parse_style(m.getAttribute("style"))
-        if "stop-color" in sdict:
-          color = parse_color(sdict["stop-color"])
-      stops.append((offs, color))
-    if len(stops) == 0:
-      if n.hasAttribute("xlink:href"):
-        ref = n.getAttribute("xlink:href")
-        if ref.startswith("#") and ref[1:] in self.defs:
-          stops = self.defs[ref[1:]][5]
-    return stops
-
-  def def_linearGradient(self, n):
-    #printAttributes(n)
-    kid = n.getAttribute("id")
-    x1 = 0; y1 = 0
-    x2 = self.width; y2 = self.height
-    if n.hasAttribute("x1"):
-      s = n.getAttribute("x1")
-      if s.endswith("%"):
-        x1 = self.width * float(s[:-1]) / 100.0
-      else:
-        x1 = parse_float(s)
-    if n.hasAttribute("x2"):
-      s = n.getAttribute("x2")
-      if s.endswith("%"):
-        x2 = self.width * float(s[:-1]) / 100.0
-      else:
-        x2 = parse_float(s)
-    if n.hasAttribute("y1"):
-      s = n.getAttribute("y1")
-      if s.endswith("%"):
-        y1 = self.width * float(s[:-1]) / 100.0
-      else:
-        y1 = parse_float(s)
-    if n.hasAttribute("y2"):
-      s = n.getAttribute("y2")
-      if s.endswith("%"):
-        y2 = self.width * float(s[:-1]) / 100.0
-      else:
-        y2 = parse_float(s)
-    matrix = get_gradientTransform(n)
-    stops = self.get_stops(n)
-    self.defs[kid] = ("linearGradient", x1, x2, y1, y2, stops, matrix)
-    
-  def def_radialGradient(self, n):
-    #printAttributes(n)
-    kid = n.getAttribute("id")
-    cx = "50%"; cy = "50%"; r = "50%"
-    if n.hasAttribute("cx"):
-      cx = n.getAttribute("cx")
-    if cx.endswith("%"):
-      cx = self.width * float(cx[:-1]) / 100.0
-    else:
-      cx = parse_float(cx)
-    if n.hasAttribute("cy"):
-      cy = n.getAttribute("cy")
-    if cy.endswith("%"):
-      cy = self.width * float(cy[:-1]) / 100.0
-    else:
-      cy = parse_float(cy)
-    if n.hasAttribute("r"):
-      r = n.getAttribute("r")
-    if r.endswith("%"):
-      r = self.width * float(r[:-1]) / 100.0
-    else:
-      r = parse_float(r)
-    if n.hasAttribute("fx"):
-      s = n.getAttribute("fx")
-      if s.endswith("%"):
-        fx = self.width * float(s[:-1]) / 100.0
-      else:
-        fx = parse_float(s)
-    else:
-      fx = cx
-    if n.hasAttribute("fy"):
-      s = n.getAttribute("fy")
-      if s.endswith("%"):
-        fy = self.width * float(s[:-1]) / 100.0
-      else:
-        fy = parse_float(s)
-    else:
-      fy = cy
-    matrix = get_gradientTransform(n)
-    stops = self.get_stops(n)
-    self.defs[kid] = ("radialGradient", cx, cy, r, fx, fy, stops, matrix)
-
-  def def_clipPath(self, node):
-    kid = node.getAttribute("id")
-    # only a single path is implemented
-    for n in node.childNodes:
-      if n.nodeType != Node.ELEMENT_NODE or n.tagName != "path":
-        continue
-      m = parse_transform(n)
-      d = n.getAttribute("d")
-      output = cStringIO.StringIO()
-      parse_path(output, d)
-      path = output.getvalue()
-      output.close()
-      self.defs[kid] = ("clipPath", m, path)
-      return
-
-  def def_g(self, group):
-    for n in group.childNodes:
-      if n.nodeType != Node.ELEMENT_NODE: 
-        continue
-      if hasattr(self, "def_" + n.tagName):
-        getattr(self, "def_" + n.tagName)(n)
-
-  def def_defs(self, node):
-    self.def_g(node)
-
-# --------------------------------------------------------------------
-
-  def parse_attributes(self, n):
-    pattr = self.attributes[-1]
-    attr = { }
-    for a in attribute_names:
-      if n.hasAttribute(a):
-        attr[a] = n.getAttribute(a)
-      else:
-        attr[a] = pattr[a]
-    if n.hasAttribute("style"):
-      sdict = parse_style(n.getAttribute("style"))
-      for a in attribute_names:
-        if a in sdict:
-          attr[a] = sdict[a]
-    return attr
-
-  def write_pathattributes(self, a):
-    stroke = parse_color(a["stroke"])
-    if stroke:
-      self.out.write(' stroke="%g %g %g"' % stroke)
-    fill = a["fill"]
-    if fill and fill.startswith("url("):
-      mat = re.match("url\(#([^)]+)\).*", fill)
-      if mat:
-        grad = mat.group(1)
-        if grad in self.defs and (self.defs[grad][0] == "linearGradient" or
-                                  self.defs[grad][0] == "radialGradient"):
-          self.out.write(' fill="1" gradient="g%s"' % grad)
-    else:
-      fill = parse_color(a["fill"])
-      if fill:
-        self.out.write(' fill="%g %g %g"' % fill)
-    opacity = parse_opacity(a["opacity"])
-    fill_opacity = parse_opacity(a["fill-opacity"])
-    stroke_opacity = parse_opacity(a["stroke-opacity"])
-    if fill and fill_opacity:
-      opacity = fill_opacity
-    if not fill and stroke and stroke_opacity:
-      opacity = stroke_opacity
-    if opacity and opacity != 100:
-      self.out.write(' opacity="%d%%"' % opacity)
-    stroke_width = parse_float(a["stroke-width"])
-    if a["stroke-width"]:
-      self.out.write(' pen="%g"' % stroke_width)
-    if a["fill-rule"] == "nonzero":
-      self.out.write(' fillrule="wind"')
-    k = {"butt" : 0, "round" : 1, "square" : 2 }
-    if a["stroke-linecap"] in k:
-      self.out.write(' cap="%d"' % k[a["stroke-linecap"]])
-    k = {"miter" : 0, "round" : 1, "bevel" : 2 }
-    if a["stroke-linejoin"] in k:
-      self.out.write(' join="%d"' % k[a["stroke-linejoin"]])
-    dasharray = a["stroke-dasharray"]
-    dashoffset = a["stroke-dashoffset"]
-    if dasharray and dashoffset and dasharray != "none":
-      d = parse_list(dasharray)
-      off = parse_float(dashoffset)
-      self.out.write(' dash="[%s] %g"' % (" ".join(d), off))
-
-# --------------------------------------------------------------------
-
-  def node_g(self, group):
-    # printAttributes(group)
-    attr = self.parse_attributes(group)
-    self.attributes.append(attr)
-    self.out.write('<group')
-    m = parse_transform(group)
-    if m:   
-      self.out.write(' matrix="%s"' % m)
-    self.out.write('>\n')
-    for n in group.childNodes:
-      if n.nodeType != Node.ELEMENT_NODE: 
-        continue
-      if hasattr(self, "node_" + n.tagName):
-        getattr(self, "node_" + n.tagName)(n)
-      else:
-        sys.stderr.write("Unhandled node: %s\n" % n.tagName)
-    self.out.write('</group>\n')
-    self.attributes.pop()
-
-  def collect_text(self, root):
-    for n in root.childNodes:
-      if n.nodeType == Node.TEXT_NODE:
-        self.text += n.data
-      if n.nodeType != Node.ELEMENT_NODE: 
-        continue
-      if n.tagName == "tspan":  # recurse
-        self.collect_text(n)
-        
-  def node_text(self, t):
-    if not t.hasAttribute("x") or not t.hasAttribute("y"):
-      sys.stderr.write("Text without coordinates ignored\n")
-      return
-    x = float(t.getAttribute("x"))
-    y = float(t.getAttribute("y"))
-    attr = self.parse_attributes(t)
-    self.out.write('<text pos="%g %g"' % (x,y))
-    self.out.write(' transformations="affine" valign="baseline"')
-    m = parse_transform(t)
-    if not m: m = Matrix()
-    m = m * Matrix([1, 0, 0, -1, x, y]) * Matrix([1, 0, 0, 1, -x, -y])
-    self.out.write(' matrix="%s"' % m)
-    if attr["font-size"]:
-      self.out.write(' size="%g"' % parse_float(attr["font-size"]))
-    color = parse_color(attr["fill"])
-    if color:
-      self.out.write(' stroke="%g %g %g"' % color)
-    self.text = ""
-    self.collect_text(t)
-    self.out.write('>%s</text>\n' % self.text.encode("UTF-8"))
-    
-  def node_image(self, node):
-    if not have_pil:
-      sys.stderr.write("No Python image library, <image> ignored\n")
-      return
-    href = node.getAttribute("xlink:href")
-    if not href.startswith("data:image/png;base64,"):
-      sys.stderr.write("Image ignored, href = %s...\n" % href[:40])
-      return
-    x = float(node.getAttribute("x"))
-    y = float(node.getAttribute("y"))
-    w = float(node.getAttribute("width"))
-    h = float(node.getAttribute("height"))
-    clipped = False
-    if node.hasAttribute("clip-path"):
-      mat = re.match("url\(#([^)]+)\).*", node.getAttribute("clip-path"))
-      if mat:
-        cp = mat.group(1)
-        if cp in self.defs and self.defs[cp][0] == "clipPath":
-          cp, m, path = self.defs[cp]
-          clipped = True
-          self.out.write('<group matrix="%s" clip="%s">\n' % (str(m), path))
-          self.out.write('<group matrix="%s">\n' % str(m.inverse()))
-    self.out.write('<image rect="%g %g %g %g"' % (x, y, x + w, y + h))
-    data = base64.b64decode(href[22:])
-    fin = cStringIO.StringIO(data)
-    image = Image.open(fin)
-    m = parse_transform(node)
-    if not m:   
-      m = Matrix()
-    m = m * Matrix([1, 0, 0, -1, x, y+h]) * Matrix([1, 0, 0, 1, -x, -y])
-    self.out.write(' matrix="%s"' % m)
-    self.out.write(' width="%d" height="%d" ColorSpace="DeviceRGB"' %
-                   image.size)
-    self.out.write(' BitsPerComponent="8" encoding="base64"> \n')
-    if True:
-      data = cStringIO.StringIO()
-      for pixel in image.getdata():
-        data.write("%c%c%c" % pixel[:3])
-      self.out.write(base64.b64encode(data.getvalue()))
-      data.close()
-    else:
-      count = 0
-      for pixel in image.getdata():
-        self.out.write("%02x%02x%02x" % pixel[:3])
-        count += 1
-        if count == 10:
-          self.out.write("\n")
-          count = 0
-    fin.close()
-    self.out.write('</image>\n')
-    if clipped:
-      self.out.write('</group>\n</group>\n')
-
-  # handled in def pass
-  def node_linearGradient(self, n):
-    pass 
-
-  def node_radialGradient(self, n):
-    pass 
-
-  def node_rect(self, n):
-    attr = self.parse_attributes(n)
-    self.out.write('<path')
-    m = parse_transform(n)
-    if m:   
-      self.out.write(' matrix="%s"' % m)
-    self.write_pathattributes(attr)
-    self.out.write('>\n')
-    x = float(n.getAttribute("x"))
-    y = float(n.getAttribute("y"))
-    w = float(n.getAttribute("width"))
-    h = float(n.getAttribute("height"))
-    self.out.write("%g %g m %g %g l %g %g l %g %g l h\n" %
-                   (x, y, x + w, y, x + w, y + h, x, y + h))
-    self.out.write('</path>\n')
-
-  def node_circle(self, n):
-    self.out.write('<path')
-    m = parse_transform(n)
-    if m:   
-      self.out.write(' matrix="%s"' % m)
-    attr = self.parse_attributes(n)
-    self.write_pathattributes(attr)
-    self.out.write('>\n')
-    cx = float(n.getAttribute("cx"))
-    cy = float(n.getAttribute("cy"))
-    r = float(n.getAttribute("r"))
-    self.out.write("%g 0 0 %g %g %g e\n" % (r, r, cx, cy))
-    self.out.write('</path>\n')
-
-  def node_ellipse(self, n):
-    self.out.write('<path')
-    m = parse_transform(n)
-    if m:   
-      self.out.write(' matrix="%s"' % m)
-    attr = self.parse_attributes(n)
-    self.write_pathattributes(attr)
-    self.out.write('>\n')
-    cx = 0
-    cy = 0
-    if n.hasAttribute("cx"):
-      cx = float(n.getAttribute("cx"))
-    if n.hasAttribute("cy"):
-      cy = float(n.getAttribute("cy"))
-    rx = float(n.getAttribute("rx"))
-    ry = float(n.getAttribute("ry"))
-    self.out.write("%g 0 0 %g %g %g e\n" % (rx, ry, cx, cy))
-    self.out.write('</path>\n')
-
-  def node_line(self, n):
-    self.out.write('<path')
-    m = parse_transform(n)
-    if m:   
-      self.out.write(' matrix="%s"' % m)
-    attr = self.parse_attributes(n)
-    self.write_pathattributes(attr)
-    self.out.write('>\n')
-    x1 = 0; y1 = 0; x2 = 0; y2 = 0
-    if n.hasAttribute("x1"):
-      x1 = float(n.getAttribute("x1"))
-    if n.hasAttribute("y1"):
-      y1 = float(n.getAttribute("y1"))
-    if n.hasAttribute("x2"):
-      x2 = float(n.getAttribute("x2"))
-    if n.hasAttribute("y2"):
-      y2 = float(n.getAttribute("y2"))
-    self.out.write("%g %g m %g %g l\n" % (x1, y1, x2, y2))
-    self.out.write('</path>\n')
-
-  def node_polyline(self, n):
-    self.polygon(n, closed=False)
-    
-  def node_polygon(self, n):
-    self.polygon(n, closed=True)
-
-  def polygon(self, n, closed):
-    self.out.write('<path')
-    m = parse_transform(n)
-    if m:   
-      self.out.write(' matrix="%s"' % m)
-    attr = self.parse_attributes(n)
-    self.write_pathattributes(attr)
-    self.out.write('>\n')
-    d = parse_list(n.getAttribute("points"))
-    op = "m"
-    while d:
-      x = float(d.pop(0))
-      y = float(d.pop(0))
-      self.out.write("%g %g %s\n" % (x, y, op))
-      op = "l"
-    if closed:
-      self.out.write("h\n")
-    self.out.write('</path>\n')
-
-  def node_path(self, n):
-    self.out.write('<path')
-    m = parse_transform(n)
-    if m:   
-      self.out.write(' matrix="%s"' % m)
-    attr = self.parse_attributes(n)
-    self.write_pathattributes(attr)
-    self.out.write('>\n')
-    d = n.getAttribute("d")
-    parse_path(self.out, d)
-    self.out.write('</path>\n')
-
-# --------------------------------------------------------------------
-
-def main():
-  if len(sys.argv) != 2 and len(sys.argv) != 3:
-    sys.stderr.write("Usage: svgtoipe <figure.svg> [ <figure.ipe> ]\n")
-    return
-  fname = sys.argv[1]
-  if len(sys.argv) > 2:
-    outname = sys.argv[2]
-  else:
-    if fname[-4:].lower() == ".svg":
-      outname = fname[:-4] + ".ipe"
-    else:
-      outname = fname + ".ipe"
-  svg = Svg(fname)
-  svg.parse_svg(outname)
-
-if __name__ == '__main__':
-  main()
-
-# --------------------------------------------------------------------
diff --git a/tools/pdftoipe.sh b/tools/pdftoipe.sh
new file mode 100755 (executable)
index 0000000..5dc732b
--- /dev/null
@@ -0,0 +1,5 @@
+# !/bin/bash
+echo $1
+inkscape --verb=ObjectToPath --select=EditSelectAll --export-plain-svg=/tmp/$1.svg $1
+./svgtoipe.py /tmp/$1.svg $2
+rm /tmp/$1.svg
diff --git a/tools/svgtoipe.py b/tools/svgtoipe.py
new file mode 100755 (executable)
index 0000000..45f3e98
--- /dev/null
@@ -0,0 +1,865 @@
+#!/usr/bin/env python
+# --------------------------------------------------------------------
+# convert SVG to Ipe format
+# --------------------------------------------------------------------
+# 
+# Copyright (C) 2009-2014  Otfried Cheong
+#
+# svgtoipe is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+# 
+# svgtoipe is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with svgtoipe; if not, you can find it at
+# "http://www.gnu.org/copyleft/gpl.html", or write to the Free
+# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# --------------------------------------------------------------------
+
+svgtoipe_version = "20091018"
+
+import sys
+import xml.dom.minidom as xml
+from xml.dom.minidom import Node
+import re
+import math
+
+import base64
+import cStringIO
+
+try:
+  from PIL import Image
+  have_pil = True
+except:
+  have_pil = False
+
+# --------------------------------------------------------------------
+
+color_keywords = {
+  "black" : "rgb(0, 0, 0)",
+  "green" :"rgb(0, 128, 0)",
+  "silver" :"rgb(192, 192, 192)",
+  "lime" :"rgb(0, 255, 0)",
+  "gray" :"rgb(128, 128, 128)",
+  "olive" :"rgb(128, 128, 0)",
+  "white" :"rgb(255, 255, 255)",
+  "yellow" :"rgb(255, 255, 0)",
+  "maroon" :"rgb(128, 0, 0)",
+  "navy" :"rgb(0, 0, 128)",
+  "red" :"rgb(255, 0, 0)",
+  "blue" :"rgb(0, 0, 255)",
+  "purple" :"rgb(128, 0, 128)",
+  "teal" :"rgb(0, 128, 128)",
+  "fuchsia" :"rgb(255, 0, 255)",
+  "aqua" :"rgb(0, 255, 255)",
+}
+
+attribute_names = [ "stroke", 
+                    "fill",
+                    "stroke-opacity",
+                    "fill-opacity",
+                    "stroke-width",
+                    "fill-rule",
+                    "stroke-linecap",
+                    "stroke-linejoin",
+                    "stroke-dasharray",
+                    "stroke-dashoffset",
+                    "stroke-miterlimit",
+                    "opacity", 
+                    "font-size" ]
+  
+def printAttributes(n):
+  a = n.attributes
+  for i in range(a.length):
+    name = a.item(i).name
+    if name[:9] != "sodipodi:" and name[:9] != "inkscape:":
+      print "   ", name, n.getAttribute(name)
+
+def parse_float(txt):
+  if not txt:
+    return None
+  if txt.endswith('px') or txt.endswith('pt'):
+    return float(txt[:-2])
+  elif txt.endswith('pc'):
+    return 12 * float(txt[:-2])
+  elif txt.endswith('mm'):
+    return 72.0 * float(txt[:-2]) / 25.4
+  elif txt.endswith('cm'):
+    return 72.0 * float(txt[:-2]) / 2.54
+  elif txt.endswith('in'):
+    return 72.0 * float(txt[:-2])
+  else:
+    return float(txt)
+
+def parse_opacity(txt):
+  if not txt: 
+    return None
+  m = int(10 * (float(txt) + 0.05))
+  if m == 0: m = 1
+  return 10 * m
+
+def parse_list(string):
+  return re.findall("([A-Za-z]|-?[0-9]+\.?[0-9]*(?:e-?[0-9]*)?)", string)
+
+def parse_style(string):
+  sdict = {}
+  for item in string.split(';'):
+    if ':' in item:
+      key, value = item.split(':')
+      sdict[key.strip()] = value.strip()
+  return sdict
+
+def parse_color_component(txt):
+  if txt.endswith("%"):
+    return float(txt[:-1]) / 100.0
+  else:
+    return int(txt) / 255.0
+  
+def parse_color(c):
+  if not c or c == 'none':
+    return None
+  if c in color_keywords:
+    c = color_keywords[c]
+  m =  re.match(r"rgb\(([0-9\.]+%?),\s*([0-9\.]+%?),\s*([0-9\.]+%?)\s*\)", c)
+  if m:
+    r = parse_color_component(m.group(1))
+    g = parse_color_component(m.group(2))
+    b = parse_color_component(m.group(3))
+    return (r, g, b)
+  m = re.match(r"#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$", c)
+  if m:
+    r = int(m.group(1), 16) / 15.0
+    g = int(m.group(2), 16) / 15.0
+    b = int(m.group(3), 16) / 15.0
+    return (r, g, b)
+  m = re.match(r"#([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])"
+               + r"([0-9a-fA-F][0-9a-fA-F])$", c)
+  if m:
+    r = int(m.group(1), 16) / 255.0
+    g = int(m.group(2), 16) / 255.0
+    b = int(m.group(3), 16) / 255.0
+    return (r, g, b)
+  sys.stderr.write("Unknown color: %s\n" % c)
+  return None
+
+def pnext(d, n):
+  l = []
+  while n > 0:
+    l.append(float(d.pop(0)))
+    n -= 1
+  return tuple(l)
+
+def parse_path(out, d):
+  d = re.findall("([A-Za-z]|-?[0-9]+\.?[0-9]*(?:e-?[0-9]*)?)", d)
+  x, y = 0.0, 0.0
+  xs, ys = 0.0, 0.0
+  while d:
+    if not d[0][0] in "01234567890.-":
+      opcode = d.pop(0)
+    if opcode == 'M':
+      x, y = pnext(d, 2)
+      out.write("%g %g m\n" % (x, y))
+      opcode = 'L'
+    elif opcode == 'm':
+      x1, y1 = pnext(d, 2)
+      x += x1
+      y += y1
+      out.write("%g %g m\n" % (x, y))
+      opcode = 'l'
+    elif opcode == 'L':
+      x, y = pnext(d, 2)
+      out.write("%g %g l\n" % (x, y))
+    elif opcode == 'l':
+      x1, y1 = pnext(d, 2)
+      x += x1
+      y += y1
+      out.write("%g %g l\n" % (x, y))
+    elif opcode == 'H':
+      x = pnext(d, 1)[0]
+      out.write("%g %g l\n" % (x, y))
+    elif opcode == 'h':
+      x += pnext(d, 1)[0]
+      out.write("%g %g l\n" % (x, y))
+    elif opcode == 'V':
+      y = pnext(d, 1)[0]
+      out.write("%g %g l\n" % (x, y))
+    elif opcode == 'v':
+      y += pnext(d, 1)[0]
+      out.write("%g %g l\n" % (x, y))
+    elif opcode == 'C':
+      x1, y1, xs, ys, x, y = pnext(d, 6)
+      out.write("%g %g %g %g %g %g c\n" % (x1, y1, xs, ys, x, y))
+    elif opcode == 'c':
+      x1, y1, xs, ys, xf, yf = pnext(d, 6)
+      x1 += x; y1 += y
+      xs += x; ys += y
+      x += xf; y += yf
+      out.write("%g %g %g %g %g %g c\n" % (x1, y1, xs, ys, x, y))
+    elif opcode == 'S' or opcode == 's':
+      x2, y2, xf, yf = pnext(d, 4)
+      if opcode == 's':
+        x2 += x; y2 += y
+        xf += x; yf += y
+      x1 = x + (x - xs); y1 = y + (y - ys)
+      out.write("%g %g %g %g %g %g c\n" % (x1, y1, x2, y2, xf, yf))
+      xs, ys = x2, y2
+      x, y = xf, yf
+    elif opcode == 'Q':
+      xs, ys, x, y = pnext(d, 4)
+      out.write("%g %g %g %g q\n" % (xs, ys, x, y))
+    elif opcode == 'q':
+      xs, ys, xf, yf = pnext(d, 4)
+      xs += x; ys += y
+      x += xf; y += yf
+      out.write("%g %g %g %g q\n" % (xs, ys, x, y))
+    elif opcode == 'T' or opcode == 't':
+      xf, yf = pnext(d, 2)
+      if opcode == 't':
+        xf += x; yf += y
+      x1 = x + (x - xs); y1 = y + (y - ys)
+      out.write("%g %g %g %g q\n" % (x1, y1, xf, yf))
+      xs, ys = x1, y1
+      x, y = xf, yf
+    elif opcode == 'A' or opcode == 'a':
+      rx, ry, phi, large_arc, sweep, x2, y2 = pnext(d, 7)
+      if opcode == 'a':
+        x2 += x; y2 += y
+      draw_arc(out, x, y, rx, ry, phi, large_arc, sweep, x2, y2)
+      x, y = x2, y2
+    elif opcode in 'zZ':
+      out.write("h\n")
+    else:
+      sys.stderr.write("Unrecognised opcode: %s\n" % opcode)
+
+def parse_transformation(txt):
+  d = re.findall("[a-zA-Z]+\([^)]*\)", txt)
+  m = Matrix()
+  while d:
+    m1 = Matrix(d.pop(0))
+    m = m * m1
+  return m
+
+def get_gradientTransform(n):
+  if n.hasAttribute("gradientTransform"):
+    return parse_transformation(n.getAttribute("gradientTransform"))
+  return Matrix()
+
+def parse_transform(n):
+  if n.hasAttribute("transform"):
+    return parse_transformation(n.getAttribute("transform"))
+  return None
+
+# Convert from endpoint to center parameterization
+# www.w3.org/TR/2003/REC-SVG11-20030114/implnote.html#ArcImplementationNotes
+def draw_arc(out, x1, y1, rx, ry, phi, large_arc, sweep, x2, y2):
+  phi = math.pi * phi / 180.0
+  cp = math.cos(phi); sp = math.sin(phi)
+  dx = .5 * (x1 - x2); dy = .5 * (y1 - y2)
+  x1p = cp * dx + sp * dy; y1p = -sp * dx + cp * dy
+  r2 = (((rx * ry)**2 - (rx * y1p)**2 - (ry * x1p)**2)/
+        ((rx * y1p)**2 + (ry * x1p)**2))
+  if r2 < 0: r2 = 0
+  r = math.sqrt(r2)
+  if large_arc == sweep:
+    r = -r
+  cxp = r * rx * y1p / ry; cyp = -r * ry * x1p / rx
+  cx = cp * cxp - sp * cyp + .5 * (x1 + x2)
+  cy = sp * cxp + cp * cyp + .5 * (y1 + y2)
+  m = Matrix([rx, 0, 0, ry, 0, 0])
+  m = Matrix([cp, sp, -sp, cp, cx, cy]) * m
+  if sweep == 0:
+    m = m * Matrix([1, 0, 0, -1, 0, 0])
+  out.write("%s %g %g a\n" % (str(m), x2, y2))
+
+# --------------------------------------------------------------------
+
+class Matrix(object):
+
+  # Default is identity matrix
+  def __init__(self, string = None):
+    self.values = [1, 0, 0, 1, 0, 0] 
+    if not string or string == "":
+      return
+    if isinstance(string, list):
+      self.values = string
+      return
+    mat = re.match(r"([a-zA-Z]+)\(([^)]*)\)$", string)
+    if not mat:
+      sys.stderr.write("Unknown transform: %s\n" % string)
+    op = mat.group(1)
+    d = [float(x) for x in parse_list(mat.group(2))]
+    if op == "matrix":
+      self.values = d
+    elif op == "translate":
+      if len(d) == 1: d.append(0.0)
+      self.values = [1, 0, 0, 1, d[0], d[1]]
+    elif op == "scale":
+      if len(d) == 1: d.append(d[0])
+      sx, sy = d
+      self.values = [sx, 0, 0, sy, 0, 0]
+    elif op == "rotate":
+      phi = math.pi * d[0] / 180.0
+      self.values = [math.cos(phi), math.sin(phi), 
+                     -math.sin(phi), math.cos(phi), 0, 0]           
+    elif op == "skewX":
+      tphi = math.tan(math.pi * d[0] / 180.0)
+      self.values = [1, 0, tphi, 1, 0, 0]
+    elif op == "skewY":
+      tphi = math.tan(math.pi * d[0] / 180.0)
+      self.values = [1, tphi, 0, 1, 0, 0]
+    else:
+      sys.stderr.write("Unknown transform: %s\n" % string)
+      
+  def __call__(self, other):
+    return (self.values[0]*other[0] + self.values[2]*other[1] + self.values[4],
+            self.values[1]*other[0] + self.values[3]*other[1] + self.values[5])
+  
+  def inverse(self):
+    d = float(self.values[0]*self.values[3] - self.values[1]*self.values[2])
+    return Matrix([self.values[3]/d, -self.values[1]/d, 
+                   -self.values[2]/d, self.values[0]/d,
+                   (self.values[2]*self.values[5] - 
+                    self.values[3]*self.values[4])/d,
+                   (self.values[1]*self.values[4] - 
+                    self.values[0]*self.values[5])/d])
+
+  def __mul__(self, other):
+    a, b, c, d, e, f = self.values
+    u, v, w, x, y, z = other.values
+    return Matrix([a*u + c*v, b*u + d*v, a*w + c*x, 
+                   b*w + d*x, a*y + c*z + e, b*y + d*z + f])
+  
+  def __str__(self):
+    a, b, c, d, e, f = self.values
+    return "%g %g %g %g %g %g" % (a, b, c, d, e, f)
+    
+# --------------------------------------------------------------------
+                               
+class Svg():
+
+  def __init__(self, fname):
+    self.dom = xml.parse(fname)
+    attr = { }
+    for a in attribute_names:
+      attr[a] = None
+    self.attributes = [ attr ]
+    self.defs = { }
+    for n in self.dom.childNodes:
+      if n.nodeType == Node.ELEMENT_NODE and n.tagName == "svg":
+        if n.hasAttribute("viewBox"):
+          x, y, w, h = [float(x) for x in parse_list(n.getAttribute("viewBox"))]
+          self.width = w
+          self.height = h
+          self.origin = (x, y)
+        else:
+          self.width = parse_float(n.getAttribute("width"))
+          self.height = parse_float(n.getAttribute("height"))
+          self.origin = (0, 0)
+        self.root = n
+        return
+
+# --------------------------------------------------------------------
+
+  def parse_svg(self, outname):
+    self.out = open(outname, "w")
+    self.out.write('<?xml version="1.0"?>\n')
+    self.out.write('<!DOCTYPE ipe SYSTEM "ipe.dtd">\n')
+    self.out.write('<ipe version="70005" creator="svgtoipe %s">\n' %
+                   svgtoipe_version)
+    self.out.write('<ipestyle>\n')
+    self.out.write(('<layout paper="%d %d" frame="%d %d" ' + 
+                    'origin="0 0" crop="no"/>\n') % 
+                   (self.width, self.height, self.width, self.height))
+    for t in range(10, 100, 10):
+      self.out.write('<opacity name="%d%%" value="0.%d"/>\n' % (t, t))
+    # set SVG defaults
+    self.out.write('<pathstyle cap="0" join="0" fillrule="wind"/>\n')
+    self.out.write('</ipestyle>\n')
+    # collect definitions
+    for n in self.root.childNodes:
+      if n.nodeType != Node.ELEMENT_NODE:
+        continue
+      if hasattr(self, "def_" + n.tagName):
+        getattr(self, "def_" + n.tagName)(n)
+    # write definitions into stylesheet
+    if len(self.defs) > 0:
+      self.out.write('<ipestyle>\n')
+      for k in self.defs:
+        if self.defs[k][0] == "linearGradient":
+          self.write_linear_gradient(k)
+        elif self.defs[k][0] == "radialGradient":
+          self.write_radial_gradient(k)
+      self.out.write('</ipestyle>\n')
+    # start real data
+    self.out.write('<page>\n')
+    m = Matrix([1, 0, 0, 1, 0, self.height / 2.0])
+    m = m * Matrix([1, 0, 0, -1, 0, 0])
+    m = m * Matrix([1, 0, 0, 1, 
+                    -self.origin[0], -(self.origin[1] + self.height / 2.0)])
+    self.out.write('<group matrix="%s">\n' % str(m))
+    for n in self.root.childNodes:
+      if n.nodeType != Node.ELEMENT_NODE:
+        continue
+      if hasattr(self, "node_" + n.tagName):
+        getattr(self, "node_" + n.tagName)(n)
+      else:
+        sys.stderr.write("Unhandled node: %s\n" % n.tagName)
+    self.out.write('</group>\n')
+    self.out.write('</page>\n')
+    self.out.write('</ipe>\n')
+    self.out.close()
+
+# --------------------------------------------------------------------
+
+  def write_linear_gradient(self, k):
+    typ, x1, x2, y1, y2, stops, matrix = self.defs[k]
+    self.out.write('<gradient name="g%s" type="axial" extend="yes"\n' % k)
+    self.out.write(' matrix="%s"' % str(matrix))
+    self.out.write(' coords="%g %g %g %g">\n' % (x1, y1, x2, y2))
+    for s in stops:
+      offset, color = s
+      self.out.write(' <stop offset="%g" color="%g %g %g"/>\n' % 
+                     (offset, color[0], color[1], color[2]))
+    self.out.write('</gradient>\n')
+    
+  def write_radial_gradient(self, k):
+    typ, cx, cy, r, fx, fy, stops, matrix = self.defs[k]
+    self.out.write('<gradient name="g%s" type="radial" extend="yes"\n' % k)
+    self.out.write(' matrix="%s"' % str(matrix))
+    self.out.write(' coords="%g %g %g %g %g %g">\n' % (fx, fy, 0, cx, cy, r))
+    for s in stops:
+      offset, color = s
+      self.out.write(' <stop offset="%g" color="%g %g %g"/>\n' % 
+                     (offset, color[0], color[1], color[2]))
+    self.out.write('</gradient>\n')
+
+  def get_stops(self, n):
+    stops = []
+    for m in n.childNodes:
+      if m.nodeType != Node.ELEMENT_NODE:
+        continue
+      if m.tagName != "stop":
+        continue # should not happen
+      offs = m.getAttribute("offset")
+      if offs.endswith("%"):
+        offs = float(offs[:-1]) / 100.0
+      else:
+        offs = float(offs)
+      color = parse_color(m.getAttribute("stop-color"))
+      if m.hasAttribute("style"):
+        sdict = parse_style(m.getAttribute("style"))
+        if "stop-color" in sdict:
+          color = parse_color(sdict["stop-color"])
+      stops.append((offs, color))
+    if len(stops) == 0:
+      if n.hasAttribute("xlink:href"):
+        ref = n.getAttribute("xlink:href")
+        if ref.startswith("#") and ref[1:] in self.defs:
+          stops = self.defs[ref[1:]][5]
+    return stops
+
+  def def_linearGradient(self, n):
+    #printAttributes(n)
+    kid = n.getAttribute("id")
+    x1 = 0; y1 = 0
+    x2 = self.width; y2 = self.height
+    if n.hasAttribute("x1"):
+      s = n.getAttribute("x1")
+      if s.endswith("%"):
+        x1 = self.width * float(s[:-1]) / 100.0
+      else:
+        x1 = parse_float(s)
+    if n.hasAttribute("x2"):
+      s = n.getAttribute("x2")
+      if s.endswith("%"):
+        x2 = self.width * float(s[:-1]) / 100.0
+      else:
+        x2 = parse_float(s)
+    if n.hasAttribute("y1"):
+      s = n.getAttribute("y1")
+      if s.endswith("%"):
+        y1 = self.width * float(s[:-1]) / 100.0
+      else:
+        y1 = parse_float(s)
+    if n.hasAttribute("y2"):
+      s = n.getAttribute("y2")
+      if s.endswith("%"):
+        y2 = self.width * float(s[:-1]) / 100.0
+      else:
+        y2 = parse_float(s)
+    matrix = get_gradientTransform(n)
+    stops = self.get_stops(n)
+    self.defs[kid] = ("linearGradient", x1, x2, y1, y2, stops, matrix)
+    
+  def def_radialGradient(self, n):
+    #printAttributes(n)
+    kid = n.getAttribute("id")
+    cx = "50%"; cy = "50%"; r = "50%"
+    if n.hasAttribute("cx"):
+      cx = n.getAttribute("cx")
+    if cx.endswith("%"):
+      cx = self.width * float(cx[:-1]) / 100.0
+    else:
+      cx = parse_float(cx)
+    if n.hasAttribute("cy"):
+      cy = n.getAttribute("cy")
+    if cy.endswith("%"):
+      cy = self.width * float(cy[:-1]) / 100.0
+    else:
+      cy = parse_float(cy)
+    if n.hasAttribute("r"):
+      r = n.getAttribute("r")
+    if r.endswith("%"):
+      r = self.width * float(r[:-1]) / 100.0
+    else:
+      r = parse_float(r)
+    if n.hasAttribute("fx"):
+      s = n.getAttribute("fx")
+      if s.endswith("%"):
+        fx = self.width * float(s[:-1]) / 100.0
+      else:
+        fx = parse_float(s)
+    else:
+      fx = cx
+    if n.hasAttribute("fy"):
+      s = n.getAttribute("fy")
+      if s.endswith("%"):
+        fy = self.width * float(s[:-1]) / 100.0
+      else:
+        fy = parse_float(s)
+    else:
+      fy = cy
+    matrix = get_gradientTransform(n)
+    stops = self.get_stops(n)
+    self.defs[kid] = ("radialGradient", cx, cy, r, fx, fy, stops, matrix)
+
+  def def_clipPath(self, node):
+    kid = node.getAttribute("id")
+    # only a single path is implemented
+    for n in node.childNodes:
+      if n.nodeType != Node.ELEMENT_NODE or n.tagName != "path":
+        continue
+      m = parse_transform(n)
+      d = n.getAttribute("d")
+      output = cStringIO.StringIO()
+      parse_path(output, d)
+      path = output.getvalue()
+      output.close()
+      self.defs[kid] = ("clipPath", m, path)
+      return
+
+  def def_g(self, group):
+    for n in group.childNodes:
+      if n.nodeType != Node.ELEMENT_NODE: 
+        continue
+      if hasattr(self, "def_" + n.tagName):
+        getattr(self, "def_" + n.tagName)(n)
+
+  def def_defs(self, node):
+    self.def_g(node)
+
+# --------------------------------------------------------------------
+
+  def parse_attributes(self, n):
+    pattr = self.attributes[-1]
+    attr = { }
+    for a in attribute_names:
+      if n.hasAttribute(a):
+        attr[a] = n.getAttribute(a)
+      else:
+        attr[a] = pattr[a]
+    if n.hasAttribute("style"):
+      sdict = parse_style(n.getAttribute("style"))
+      for a in attribute_names:
+        if a in sdict:
+          attr[a] = sdict[a]
+    return attr
+
+  def write_pathattributes(self, a):
+    stroke = parse_color(a["stroke"])
+    if stroke:
+      self.out.write(' stroke="%g %g %g"' % stroke)
+    fill = a["fill"]
+    if fill and fill.startswith("url("):
+      mat = re.match("url\(#([^)]+)\).*", fill)
+      if mat:
+        grad = mat.group(1)
+        if grad in self.defs and (self.defs[grad][0] == "linearGradient" or
+                                  self.defs[grad][0] == "radialGradient"):
+          self.out.write(' fill="1" gradient="g%s"' % grad)
+    else:
+      fill = parse_color(a["fill"])
+      if fill:
+        self.out.write(' fill="%g %g %g"' % fill)
+    opacity = parse_opacity(a["opacity"])
+    fill_opacity = parse_opacity(a["fill-opacity"])
+    stroke_opacity = parse_opacity(a["stroke-opacity"])
+    if fill and fill_opacity:
+      opacity = fill_opacity
+    if not fill and stroke and stroke_opacity:
+      opacity = stroke_opacity
+    if opacity and opacity != 100:
+      self.out.write(' opacity="%d%%"' % opacity)
+    stroke_width = parse_float(a["stroke-width"])
+    if a["stroke-width"]:
+      self.out.write(' pen="%g"' % stroke_width)
+    if a["fill-rule"] == "nonzero":
+      self.out.write(' fillrule="wind"')
+    k = {"butt" : 0, "round" : 1, "square" : 2 }
+    if a["stroke-linecap"] in k:
+      self.out.write(' cap="%d"' % k[a["stroke-linecap"]])
+    k = {"miter" : 0, "round" : 1, "bevel" : 2 }
+    if a["stroke-linejoin"] in k:
+      self.out.write(' join="%d"' % k[a["stroke-linejoin"]])
+    dasharray = a["stroke-dasharray"]
+    dashoffset = a["stroke-dashoffset"]
+    if dasharray and dashoffset and dasharray != "none":
+      d = parse_list(dasharray)
+      off = parse_float(dashoffset)
+      self.out.write(' dash="[%s] %g"' % (" ".join(d), off))
+
+# --------------------------------------------------------------------
+
+  def node_g(self, group):
+    # printAttributes(group)
+    attr = self.parse_attributes(group)
+    self.attributes.append(attr)
+    self.out.write('<group')
+    m = parse_transform(group)
+    if m:   
+      self.out.write(' matrix="%s"' % m)
+    self.out.write('>\n')
+    for n in group.childNodes:
+      if n.nodeType != Node.ELEMENT_NODE: 
+        continue
+      if hasattr(self, "node_" + n.tagName):
+        getattr(self, "node_" + n.tagName)(n)
+      else:
+        sys.stderr.write("Unhandled node: %s\n" % n.tagName)
+    self.out.write('</group>\n')
+    self.attributes.pop()
+
+  def collect_text(self, root):
+    for n in root.childNodes:
+      if n.nodeType == Node.TEXT_NODE:
+        self.text += n.data
+      if n.nodeType != Node.ELEMENT_NODE: 
+        continue
+      if n.tagName == "tspan":  # recurse
+        self.collect_text(n)
+        
+  def node_text(self, t):
+    if not t.hasAttribute("x") or not t.hasAttribute("y"):
+      sys.stderr.write("Text without coordinates ignored\n")
+      return
+    x = float(t.getAttribute("x"))
+    y = float(t.getAttribute("y"))
+    attr = self.parse_attributes(t)
+    self.out.write('<text pos="%g %g"' % (x,y))
+    self.out.write(' transformations="affine" valign="baseline"')
+    m = parse_transform(t)
+    if not m: m = Matrix()
+    m = m * Matrix([1, 0, 0, -1, x, y]) * Matrix([1, 0, 0, 1, -x, -y])
+    self.out.write(' matrix="%s"' % m)
+    if attr["font-size"]:
+      self.out.write(' size="%g"' % parse_float(attr["font-size"]))
+    color = parse_color(attr["fill"])
+    if color:
+      self.out.write(' stroke="%g %g %g"' % color)
+    self.text = ""
+    self.collect_text(t)
+    self.out.write('>%s</text>\n' % self.text.encode("UTF-8"))
+    
+  def node_image(self, node):
+    if not have_pil:
+      sys.stderr.write("No Python image library, <image> ignored\n")
+      return
+    href = node.getAttribute("xlink:href")
+    if not href.startswith("data:image/png;base64,"):
+      sys.stderr.write("Image ignored, href = %s...\n" % href[:40])
+      return
+    x = float(node.getAttribute("x"))
+    y = float(node.getAttribute("y"))
+    w = float(node.getAttribute("width"))
+    h = float(node.getAttribute("height"))
+    clipped = False
+    if node.hasAttribute("clip-path"):
+      mat = re.match("url\(#([^)]+)\).*", node.getAttribute("clip-path"))
+      if mat:
+        cp = mat.group(1)
+        if cp in self.defs and self.defs[cp][0] == "clipPath":
+          cp, m, path = self.defs[cp]
+          clipped = True
+          self.out.write('<group matrix="%s" clip="%s">\n' % (str(m), path))
+          self.out.write('<group matrix="%s">\n' % str(m.inverse()))
+    self.out.write('<image rect="%g %g %g %g"' % (x, y, x + w, y + h))
+    data = base64.b64decode(href[22:])
+    fin = cStringIO.StringIO(data)
+    image = Image.open(fin)
+    m = parse_transform(node)
+    if not m:   
+      m = Matrix()
+    m = m * Matrix([1, 0, 0, -1, x, y+h]) * Matrix([1, 0, 0, 1, -x, -y])
+    self.out.write(' matrix="%s"' % m)
+    self.out.write(' width="%d" height="%d" ColorSpace="DeviceRGB"' %
+                   image.size)
+    self.out.write(' BitsPerComponent="8" encoding="base64"> \n')
+    if True:
+      data = cStringIO.StringIO()
+      for pixel in image.getdata():
+        data.write("%c%c%c" % pixel[:3])
+      self.out.write(base64.b64encode(data.getvalue()))
+      data.close()
+    else:
+      count = 0
+      for pixel in image.getdata():
+        self.out.write("%02x%02x%02x" % pixel[:3])
+        count += 1
+        if count == 10:
+          self.out.write("\n")
+          count = 0
+    fin.close()
+    self.out.write('</image>\n')
+    if clipped:
+      self.out.write('</group>\n</group>\n')
+
+  # handled in def pass
+  def node_linearGradient(self, n):
+    pass 
+
+  def node_radialGradient(self, n):
+    pass 
+
+  def node_rect(self, n):
+    attr = self.parse_attributes(n)
+    self.out.write('<path')
+    m = parse_transform(n)
+    if m:   
+      self.out.write(' matrix="%s"' % m)
+    self.write_pathattributes(attr)
+    self.out.write('>\n')
+    x = float(n.getAttribute("x"))
+    y = float(n.getAttribute("y"))
+    w = float(n.getAttribute("width"))
+    h = float(n.getAttribute("height"))
+    self.out.write("%g %g m %g %g l %g %g l %g %g l h\n" %
+                   (x, y, x + w, y, x + w, y + h, x, y + h))
+    self.out.write('</path>\n')
+
+  def node_circle(self, n):
+    self.out.write('<path')
+    m = parse_transform(n)
+    if m:   
+      self.out.write(' matrix="%s"' % m)
+    attr = self.parse_attributes(n)
+    self.write_pathattributes(attr)
+    self.out.write('>\n')
+    cx = float(n.getAttribute("cx"))
+    cy = float(n.getAttribute("cy"))
+    r = float(n.getAttribute("r"))
+    self.out.write("%g 0 0 %g %g %g e\n" % (r, r, cx, cy))
+    self.out.write('</path>\n')
+
+  def node_ellipse(self, n):
+    self.out.write('<path')
+    m = parse_transform(n)
+    if m:   
+      self.out.write(' matrix="%s"' % m)
+    attr = self.parse_attributes(n)
+    self.write_pathattributes(attr)
+    self.out.write('>\n')
+    cx = 0
+    cy = 0
+    if n.hasAttribute("cx"):
+      cx = float(n.getAttribute("cx"))
+    if n.hasAttribute("cy"):
+      cy = float(n.getAttribute("cy"))
+    rx = float(n.getAttribute("rx"))
+    ry = float(n.getAttribute("ry"))
+    self.out.write("%g 0 0 %g %g %g e\n" % (rx, ry, cx, cy))
+    self.out.write('</path>\n')
+
+  def node_line(self, n):
+    self.out.write('<path')
+    m = parse_transform(n)
+    if m:   
+      self.out.write(' matrix="%s"' % m)
+    attr = self.parse_attributes(n)
+    self.write_pathattributes(attr)
+    self.out.write('>\n')
+    x1 = 0; y1 = 0; x2 = 0; y2 = 0
+    if n.hasAttribute("x1"):
+      x1 = float(n.getAttribute("x1"))
+    if n.hasAttribute("y1"):
+      y1 = float(n.getAttribute("y1"))
+    if n.hasAttribute("x2"):
+      x2 = float(n.getAttribute("x2"))
+    if n.hasAttribute("y2"):
+      y2 = float(n.getAttribute("y2"))
+    self.out.write("%g %g m %g %g l\n" % (x1, y1, x2, y2))
+    self.out.write('</path>\n')
+
+  def node_polyline(self, n):
+    self.polygon(n, closed=False)
+    
+  def node_polygon(self, n):
+    self.polygon(n, closed=True)
+
+  def polygon(self, n, closed):
+    self.out.write('<path')
+    m = parse_transform(n)
+    if m:   
+      self.out.write(' matrix="%s"' % m)
+    attr = self.parse_attributes(n)
+    self.write_pathattributes(attr)
+    self.out.write('>\n')
+    d = parse_list(n.getAttribute("points"))
+    op = "m"
+    while d:
+      x = float(d.pop(0))
+      y = float(d.pop(0))
+      self.out.write("%g %g %s\n" % (x, y, op))
+      op = "l"
+    if closed:
+      self.out.write("h\n")
+    self.out.write('</path>\n')
+
+  def node_path(self, n):
+    self.out.write('<path')
+    m = parse_transform(n)
+    if m:   
+      self.out.write(' matrix="%s"' % m)
+    attr = self.parse_attributes(n)
+    self.write_pathattributes(attr)
+    self.out.write('>\n')
+    d = n.getAttribute("d")
+    parse_path(self.out, d)
+    self.out.write('</path>\n')
+
+# --------------------------------------------------------------------
+
+def main():
+  if len(sys.argv) != 2 and len(sys.argv) != 3:
+    sys.stderr.write("Usage: svgtoipe <figure.svg> [ <figure.ipe> ]\n")
+    return
+  fname = sys.argv[1]
+  if len(sys.argv) > 2:
+    outname = sys.argv[2]
+  else:
+    if fname[-4:].lower() == ".svg":
+      outname = fname[:-4] + ".ipe"
+    else:
+      outname = fname + ".ipe"
+  svg = Svg(fname)
+  svg.parse_svg(outname)
+
+if __name__ == '__main__':
+  main()
+
+# --------------------------------------------------------------------