Logo Search packages:      
Sourcecode: inkscape version File versions  Download package

simplepath.py

#!/usr/bin/env python
"""
simplepath.py
functions for digesting paths into a simple list structure

Copyright (C) 2005 Aaron Spike, aaron@ekips.org

This program 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 2 of the License, or
(at your option) any later version.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""
import re, math

def lexPath(d):
      """
      returns and iterator that breaks path data 
      identifies command and parameter tokens
      """
      offset = 0
      length = len(d)
      delim = re.compile(r'[ \t\r\n,]+')
      command = re.compile(r'[MLHVCSQTAZmlhvcsqtaz]')
      parameter = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
      while 1:
            m = delim.match(d, offset)
            if m:
                  offset = m.end()
            if offset >= length:
                  break
            m = command.match(d, offset)
            if m:
                  yield [d[offset:m.end()], True]
                  offset = m.end()
                  continue
            m = parameter.match(d, offset)
            if m:
                  yield [d[offset:m.end()], False]
                  offset = m.end()
                  continue
            #TODO: create new exception
            raise Exception, 'Invalid path data!'
'''
pathdefs = {commandfamily:
      [
      implicitnext,
      #params,
      [casts,cast,cast],
      [coord type,x,y,0]
      ]}
'''
pathdefs = {
      'M':['L', 2, [float, float], ['x','y']], 
      'L':['L', 2, [float, float], ['x','y']], 
      'H':['H', 1, [float], ['x']], 
      'V':['V', 1, [float], ['y']], 
      'C':['C', 6, [float, float, float, float, float, float], ['x','y','x','y','x','y']], 
      'S':['S', 4, [float, float, float, float], ['x','y','x','y']], 
      'Q':['Q', 4, [float, float, float, float], ['x','y','x','y']], 
      'T':['T', 2, [float, float], ['x','y']], 
      'A':['A', 7, [float, float, float, int, int, float, float], [0,0,0,0,0,'x','y']], 
      'Z':['L', 0, [], []]
      }
def parsePath(d):
      """
      Parse SVG path and return an array of segments.
      Removes all shorthand notation.
      Converts coordinates to absolute.
      """
      retval = []
      lexer = lexPath(d)

      pen = (0.0,0.0)
      subPathStart = pen
      lastControl = pen
      lastCommand = ''
      
      while 1:
            try:
                  token, isCommand = lexer.next()
            except StopIteration:
                  break
            params = []
            needParam = True
            if isCommand:
                  if not lastCommand and token.upper() != 'M':
                        raise Exception, 'Invalid path, must begin with moveto.'    
                  else:                   
                        command = token
            else:
                  #command was omited
                  #use last command's implicit next command
                  needParam = False
                  if lastCommand:
                        if token.isupper():
                              command = pathdefs[lastCommand.upper()][0]
                        else:
                              command = pathdefs[lastCommand.upper()][0].lower()
                  else:
                        raise Exception, 'Invalid path, no initial command.'  
            numParams = pathdefs[command.upper()][1]
            while numParams > 0:
                  if needParam:
                        try: 
                              token, isCommand = lexer.next()
                              if isCommand:
                                    raise Exception, 'Invalid number of parameters'
                        except StopIteration:
                                    raise Exception, 'Unexpected end of path'
                  cast = pathdefs[command.upper()][2][-numParams]
                  param = cast(token)
                  if command.islower():
                        if pathdefs[command.upper()][3][-numParams]=='x':
                              param += pen[0]
                        elif pathdefs[command.upper()][3][-numParams]=='y':
                              param += pen[1]
                  params.append(param)
                  needParam = True
                  numParams -= 1
            #segment is now absolute so
            outputCommand = command.upper()
      
            #Flesh out shortcut notation  
            if outputCommand in ('H','V'):
                  if outputCommand == 'H':
                        params.append(pen[1])
                  if outputCommand == 'V':
                        params.insert(0,pen[0])
                  outputCommand = 'L'
            if outputCommand in ('S','T'):
                  params.insert(0,pen[1]+(pen[1]-lastControl[1]))
                  params.insert(0,pen[0]+(pen[0]-lastControl[0]))
                  if outputCommand == 'S':
                        outputCommand = 'C'
                  if outputCommand == 'T':
                        outputCommand = 'Q'

            #current values become "last" values
            if outputCommand == 'M':
                  subPathStart = tuple(params[0:2])
            if outputCommand == 'Z':
                  pen = subPathStart
            else:
                  pen = tuple(params[-2:])

            if outputCommand in ('Q','C'):
                  lastControl = tuple(params[-4:-2])
            else:
                  lastControl = pen
            lastCommand = command

            retval.append([outputCommand,params])
      return retval

def formatPath(a):
      """Format SVG path data from an array"""
      return "".join([cmd + " ".join([str(p) for p in params]) for cmd, params in a])

def translatePath(p, x, y):
      for cmd,params in p:
            defs = pathdefs[cmd]
            for i in range(defs[1]):
                  if defs[3][i] == 'x':
                        params[i] += x
                  elif defs[3][i] == 'y':
                        params[i] += y

def scalePath(p, x, y):
      for cmd,params in p:
            defs = pathdefs[cmd]
            for i in range(defs[1]):
                  if defs[3][i] == 'x':
                        params[i] *= x
                  elif defs[3][i] == 'y':
                        params[i] *= y

def rotatePath(p, a, cx = 0, cy = 0):
      if a == 0:
            return p
      for cmd,params in p:
            defs = pathdefs[cmd]
            for i in range(defs[1]):
                  if defs[3][i] == 'x':
                        x = params[i] - cx
                        y = params[i + 1] - cy
                        r = math.sqrt((x**2) + (y**2))
                        if r != 0:
                              theta = math.atan2(y, x) + a
                              params[i] = (r * math.cos(theta)) + cx
                              params[i + 1] = (r * math.sin(theta)) + cy


Generated by  Doxygen 1.6.0   Back to index