|
1 #!/usr/bin/env python |
|
2 """ |
|
3 simplepath.py |
|
4 functions for digesting paths into a simple list structure |
|
5 |
|
6 Copyright (C) 2005 Aaron Spike, aaron@ekips.org |
|
7 |
|
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. |
|
12 |
|
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. |
|
17 |
|
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 |
|
21 |
|
22 """ |
|
23 import re, math |
|
24 |
|
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) |
|
82 |
|
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 lastCommand.isupper(): |
|
106 command = pathdefs[lastCommand][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' |
|
147 |
|
148 #current values become "last" values |
|
149 if outputCommand == 'M': |
|
150 subPathStart = tuple(params[0:2]) |
|
151 pen = subPathStart |
|
152 if outputCommand == 'Z': |
|
153 pen = subPathStart |
|
154 else: |
|
155 pen = tuple(params[-2:]) |
|
156 |
|
157 if outputCommand in ('Q','C'): |
|
158 lastControl = tuple(params[-4:-2]) |
|
159 else: |
|
160 lastControl = pen |
|
161 lastCommand = command |
|
162 |
|
163 retval.append([outputCommand,params]) |
|
164 return retval |
|
165 |
|
166 def formatPath(a): |
|
167 """Format SVG path data from an array""" |
|
168 return "".join([cmd + " ".join([str(p) for p in params]) for cmd, params in a]) |
|
169 |
|
170 def translatePath(p, x, y): |
|
171 for cmd,params in p: |
|
172 defs = pathdefs[cmd] |
|
173 for i in range(defs[1]): |
|
174 if defs[3][i] == 'x': |
|
175 params[i] += x |
|
176 elif defs[3][i] == 'y': |
|
177 params[i] += y |
|
178 |
|
179 def scalePath(p, x, y): |
|
180 for cmd,params in p: |
|
181 defs = pathdefs[cmd] |
|
182 for i in range(defs[1]): |
|
183 if defs[3][i] == 'x': |
|
184 params[i] *= x |
|
185 elif defs[3][i] == 'y': |
|
186 params[i] *= y |
|
187 |
|
188 def rotatePath(p, a, cx = 0, cy = 0): |
|
189 if a == 0: |
|
190 return p |
|
191 for cmd,params in p: |
|
192 defs = pathdefs[cmd] |
|
193 for i in range(defs[1]): |
|
194 if defs[3][i] == 'x': |
|
195 x = params[i] - cx |
|
196 y = params[i + 1] - cy |
|
197 r = math.sqrt((x**2) + (y**2)) |
|
198 if r != 0: |
|
199 theta = math.atan2(y, x) + a |
|
200 params[i] = (r * math.cos(theta)) + cx |
|
201 params[i + 1] = (r * math.sin(theta)) + cy |
|
202 |