Code

moving trunk for module inkscape
[inkscape.git] / share / extensions / simplepath.py
1 #!/usr/bin/env python
2 """
3 simplepath.py
4 functions for digesting paths into a simple list structure
6 Copyright (C) 2005 Aaron Spike, aaron@ekips.org
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 """
23 import re, math
25 def lexPath(d):
26         """
27         returns and iterator that breaks path data 
28         identifies command and parameter tokens
29         """
30         offset = 0
31         length = len(d)
32         delim = re.compile(r'[ \t\r\n,]+')
33         command = re.compile(r'[MLHVCSQTAZmlhvcsqtaz]')
34         parameter = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
35         while 1:
36                 m = delim.match(d, offset)
37                 if m:
38                         offset = m.end()
39                 if offset >= length:
40                         break
41                 m = command.match(d, offset)
42                 if m:
43                         yield [d[offset:m.end()], True]
44                         offset = m.end()
45                         continue
46                 m = parameter.match(d, offset)
47                 if m:
48                         yield [d[offset:m.end()], False]
49                         offset = m.end()
50                         continue
51                 #TODO: create new exception
52                 raise Exception, 'Invalid path data!'
53 '''
54 pathdefs = {commandfamily:
55         [
56         implicitnext,
57         #params,
58         [casts,cast,cast],
59         [coord type,x,y,0]
60         ]}
61 '''
62 pathdefs = {
63         'M':['L', 2, [float, float], ['x','y']], 
64         'L':['L', 2, [float, float], ['x','y']], 
65         'H':['H', 1, [float], ['x']], 
66         'V':['V', 1, [float], ['y']], 
67         'C':['C', 6, [float, float, float, float, float, float], ['x','y','x','y','x','y']], 
68         'S':['S', 4, [float, float, float, float], ['x','y','x','y']], 
69         'Q':['Q', 4, [float, float, float, float], ['x','y','x','y']], 
70         'T':['T', 2, [float, float], ['x','y']], 
71         'A':['A', 7, [float, float, float, int, int, float, float], [0,0,0,0,0,'x','y']], 
72         'Z':['L', 0, [], []]
73         }
74 def parsePath(d):
75         """
76         Parse SVG path and return an array of segments.
77         Removes all shorthand notation.
78         Converts coordinates to absolute.
79         """
80         retval = []
81         lexer = lexPath(d)
83         pen = (0.0,0.0)
84         subPathStart = pen
85         lastControl = pen
86         lastCommand = ''
87         
88         while 1:
89                 try:
90                         token, isCommand = lexer.next()
91                 except StopIteration:
92                         break
93                 params = []
94                 needParam = True
95                 if isCommand:
96                         if not lastCommand and token.upper() != 'M':
97                                 raise Exception, 'Invalid path, must begin with moveto.'        
98                         else:                           
99                                 command = token
100                 else:
101                         #command was omited
102                         #use last command's implicit next command
103                         needParam = False
104                         if lastCommand:
105                                 if token.isupper():
106                                         command = pathdefs[lastCommand.upper()][0]
107                                 else:
108                                         command = pathdefs[lastCommand.upper()][0].lower()
109                         else:
110                                 raise Exception, 'Invalid path, no initial command.'    
111                 numParams = pathdefs[command.upper()][1]
112                 while numParams > 0:
113                         if needParam:
114                                 try: 
115                                         token, isCommand = lexer.next()
116                                         if isCommand:
117                                                 raise Exception, 'Invalid number of parameters'
118                                 except StopIteration:
119                                         raise Exception, 'Unexpected end of path'
120                         cast = pathdefs[command.upper()][2][-numParams]
121                         param = cast(token)
122                         if command.islower():
123                                 if pathdefs[command.upper()][3][-numParams]=='x':
124                                         param += pen[0]
125                                 elif pathdefs[command.upper()][3][-numParams]=='y':
126                                         param += pen[1]
127                         params.append(param)
128                         needParam = True
129                         numParams -= 1
130                 #segment is now absolute so
131                 outputCommand = command.upper()
132         
133                 #Flesh out shortcut notation    
134                 if outputCommand in ('H','V'):
135                         if outputCommand == 'H':
136                                 params.append(pen[1])
137                         if outputCommand == 'V':
138                                 params.insert(0,pen[0])
139                         outputCommand = 'L'
140                 if outputCommand in ('S','T'):
141                         params.insert(0,pen[1]+(pen[1]-lastControl[1]))
142                         params.insert(0,pen[0]+(pen[0]-lastControl[0]))
143                         if outputCommand == 'S':
144                                 outputCommand = 'C'
145                         if outputCommand == 'T':
146                                 outputCommand = 'Q'
148                 #current values become "last" values
149                 if outputCommand == 'M':
150                         subPathStart = tuple(params[0:2])
151                 if outputCommand == 'Z':
152                         pen = subPathStart
153                 else:
154                         pen = tuple(params[-2:])
156                 if outputCommand in ('Q','C'):
157                         lastControl = tuple(params[-4:-2])
158                 else:
159                         lastControl = pen
160                 lastCommand = command
162                 retval.append([outputCommand,params])
163         return retval
165 def formatPath(a):
166         """Format SVG path data from an array"""
167         return "".join([cmd + " ".join([str(p) for p in params]) for cmd, params in a])
169 def translatePath(p, x, y):
170         for cmd,params in p:
171                 defs = pathdefs[cmd]
172                 for i in range(defs[1]):
173                         if defs[3][i] == 'x':
174                                 params[i] += x
175                         elif defs[3][i] == 'y':
176                                 params[i] += y
178 def scalePath(p, x, y):
179         for cmd,params in p:
180                 defs = pathdefs[cmd]
181                 for i in range(defs[1]):
182                         if defs[3][i] == 'x':
183                                 params[i] *= x
184                         elif defs[3][i] == 'y':
185                                 params[i] *= y
187 def rotatePath(p, a, cx = 0, cy = 0):
188         if a == 0:
189                 return p
190         for cmd,params in p:
191                 defs = pathdefs[cmd]
192                 for i in range(defs[1]):
193                         if defs[3][i] == 'x':
194                                 x = params[i] - cx
195                                 y = params[i + 1] - cy
196                                 r = math.sqrt((x**2) + (y**2))
197                                 if r != 0:
198                                         theta = math.atan2(y, x) + a
199                                         params[i] = (r * math.cos(theta)) + cx
200                                         params[i + 1] = (r * math.sin(theta)) + cy