inital default
This commit is contained in:
203
simplepath.py
Executable file
203
simplepath.py
Executable file
@ -0,0 +1,203 @@
|
||||
#!/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
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
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 = next(lexer)
|
||||
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 lastCommand.isupper():
|
||||
command = pathdefs[lastCommand][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 = next(lexer)
|
||||
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])
|
||||
pen = subPathStart
|
||||
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
|
||||
|
Reference in New Issue
Block a user