root/trunk/RBRapier/RBRapier/Formats/SVG/SVGSkin/SVGItems/PathBuilder.py

Revision 665, 8.9 kB (checked in by sholloway, 5 years ago)

*** empty log message ***

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ License
4 ##~
5 ##- The RuneBlade Foundation library is intended to ease some
6 ##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.)
7 ##- applications, while providing the flexibility to modularly change the
8 ##- architecture. Enjoy.
9 ##~
10 ##~ Copyright (C) 2002  TechGame Networks, LLC.
11 ##~
12 ##~ This library is free software; you can redistribute it and/or
13 ##~ modify it under the terms of the BSD style License as found in the
14 ##~ LICENSE file included with this distribution.
15 ##~
16 ##~ TechGame Networks, LLC can be reached at:
17 ##~ 3578 E. Hartsel Drive #211
18 ##~ Colorado Springs, Colorado, USA, 80920
19 ##~
20 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21
22 """
23 Path commands (info from http://www.protocol7.com/svg-wiki/ow.asp?ThePath)
24
25 moveto 
26     M   absolute moveto         (x y)+
27     m   relative moveto         (x y)+
28 closepath       
29     Z   absolute closepath      (none)
30     z   relative closepath      (none)
31 lineto 
32     L   absolute lineto         (x y)+
33     l   relative lineto         (x y)+
34 horizontal lineto       
35     H   absolute horizontal lineto      x+
36     h   relative horizontal lineto      x+
37 vertical lineto         
38     V   absolute vertical lineto        y+
39     v   relative vertical lineto        y+
40 curveto (cubic bezier) 
41     C   absolute curveto        (x1 y1 x2 y2 x y)+
42     c   relative curveto        (x1 y1 x2 y2 x y)+
43 smooth curveto 
44     S   absolute smooth curveto         (x2 y2 x y)+
45     s   relative smooth curveto         (x2 y2 x y)+
46 quadratic bezier curveto       
47     Q   absolute quadratic bezier curveto       (x1 y1 x y)+
48     q   relative quadratic bezier curveto       (x1 y1 x y)+
49 smooth quadratic bezier curveto         
50     T   absolute smooth quadratic bezier curveto        (x y)+
51     t   relative smooth quadratic bezier curveto        (x y)+
52 elliptical arc 
53     A   absolute elliptical arc         (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
54 """
55
56 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57 #~ Imports
58 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59
60 import re
61
62 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63 #~ Path Factories
64 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65
66 class AbstractPathFactory(object):
67     __slots__ = ()
68
69     command_table = {
70         'M': ('move', False),
71         'm': ('move', True),
72
73         'Z': ('closepath', False),
74         'z': ('closepath', True),
75
76         'L': ('line', False),
77         'l': ('line', True),
78
79         'H': ('hline', False),
80         'h': ('hline', True),
81
82         'V': ('vline', False),
83         'v': ('vline', True),
84
85         'C': ('cubicbezier', False),
86         'c': ('cubicbezier', True),
87
88         'S': ('smoothcurve', False),
89         's': ('smoothcurve', True),
90
91         'Q': ('quadraticbezier', False),
92         'q': ('quadraticbezier', True),
93
94         'T': ('smoothquadraticbezier', False),
95         't': ('smoothquadraticbezier', True),
96
97         'A': ('ellipticarc', False),
98         'a': ('ellipticarc', True),
99         }
100
101     def BeginPath(self, pathcmd):
102         raise NotImplementedError
103     def AddPathElement(self, name, *args):
104         raise NotImplementedError
105     def EndPath(self, pathcmd):
106         raise NotImplementedError
107
108     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109
110     #def move(self, relative, x, y):
111     #    raise NotImplementedError
112     #def closepath(self, relative):
113     #    raise NotImplementedError
114     #def line(self, relative, x, y):
115     #    raise NotImplementedError
116     #def hline(self, relative, x):
117     #    raise NotImplementedError
118     #def vline(self, relative, y):
119     #    raise NotImplementedError
120     #def cubicbezier(self, relative, x1, y1, x2, y2, x, y):
121     #    raise NotImplementedError
122     #def smoothcurve(self, relative, x2, y2, x, y):
123     #    raise NotImplementedError
124     #def quadraticbezier(self, relative, x1, y1, x, y):
125     #    raise NotImplementedError
126     #def smoothquadraticbezier(self, relative, x, y):
127     #    raise NotImplementedError
128     #def ellipticarc(self, relative, rx, ry, xrotation, largeArcFlag, sweepFlag, x, y):
129     #    raise NotImplementedError
130
131 class SavePathFactory(AbstractPathFactory):
132     __slots__ = ('pathcmd', 'path')
133
134     def BeginPath(self, pathcmd):
135         self.pathcmd = pathcmd
136         self.path = []
137
138     def AddPathElement(self, *args):
139         self.path.append(args)
140        
141     def EndPath(self, pathcmd):
142         pass
143
144     def RestoreTo(self, pathfactory):
145         self.RestoreToEx(
146             onbegin=transformfactory.BeginPath,
147             onadd=transformfactory.AddPathElement,
148             onend=transformfactory.EndPath)
149         return pathfactory
150
151     def RestoreToEx(self, onbegin=lambda xformcmd:None, onadd=lambda name, *args:None, onend=lambda xformcmd:None):
152         onbegin(self.pathcmd)
153         for args in self.path:
154             onadd(*args)
155         onend(self.pathcmd)
156
157 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
158 #~ Path Builder/Interpreter
159 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160
161 class PathCommandBuilder(object):
162     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
163     #~ Constants / Variables / Etc.
164     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
165
166     _delim = '\s*,?\s*'
167     _re_command = re.compile(r'\s*([MmZzLlHhVvCcSsQqTtAa])?\s*')
168     _re_coord = re.compile(r'([-+]?(?:\d+(?:\.\d*)?|\d*\.\d+)(?:[eE][-+]?\d+)?)'+_delim)
169     _coord = (_re_coord, float)
170     _re_rotation = re.compile(r'([-+]?(?:\d+(?:\.\d*)?|\d*\.\d+)(?:[eE][-+]?\d+)?)'+_delim)
171     _rotation = (_re_rotation, float)
172     _re_flag = re.compile(r'(0|1)'+_delim)
173     _flag = (_re_flag, int)
174
175     path_transitions = { 'M': 'L', 'm': 'l', 'Z': 'M', 'z': 'm', }
176     path_args = {
177         'M': (_coord, _coord),
178         'm': (_coord, _coord),
179
180         'Z': (),
181         'z': (),
182
183         'L': (_coord, _coord),
184         'l': (_coord, _coord),
185
186         'H': (_coord, ),
187         'h': (_coord, ),
188
189         'V': (_coord, ),
190         'v': (_coord, ),
191
192         'C': (_coord, _coord, _coord, _coord, _coord, _coord),
193         'c': (_coord, _coord, _coord, _coord, _coord, _coord),
194
195         'S': (_coord, _coord, _coord, _coord),
196         's': (_coord, _coord, _coord, _coord),
197
198         'Q': (_coord, _coord, _coord, _coord),
199         'q': (_coord, _coord, _coord, _coord),
200
201         'T': (_coord, _coord),
202         't': (_coord, _coord),
203
204         'A': (_coord, _coord, _rotation, _flag, _flag, _coord, _coord),
205         'a': (_coord, _coord, _rotation, _flag, _flag, _coord, _coord),
206         }
207
208     def __init__(self, pathfactory=None):
209         self.pathfactory = pathfactory
210
211     def Build(self, *args, **kw):
212         return list(self.Build(*args, **kw))
213
214     def Build(self, pathcmd, pathfactory=None):
215         pathfactory = pathfactory or self.pathfactory
216
217         pathfactory.BeginPath(pathcmd)
218
219         matchidx = 0
220         # MoveTo absolute is the default command
221         commandidx = 'M'
222         while matchidx < len(pathcmd):
223             # Find out what command we're dealing with
224             commandmatch = self._re_command.match(pathcmd, matchidx)
225             if commandmatch is not None:
226                 commandidx = commandmatch.groups()[0] or commandidx
227                 matchidx = commandmatch.end()
228
229             # parse out the arguments for the path element
230             arguments = []
231             for argre, argvalue in self.path_args[commandidx]: # CommandArguments
232                 argmatch = argre.match(pathcmd, matchidx)
233                 if argmatch is None:
234                     raise ValueError, "Error parsing argument of path at index %d (path=%r)" % (matchidx, pathcmd)
235                 else:
236                     arguments.append(argvalue(argmatch.groups()[0]))
237                     matchidx = argmatch.end()
238
239             # go get the path factory method for our command
240             pathfactory.AddPathElement(commandidx, *arguments)
241
242             # see if we have a command transfer
243             commandidx = self.path_transitions.get(commandidx, commandidx)
244
245         pathfactory.EndPath(pathcmd)
246         return pathfactory
247
248 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
249 #~ Testing
250 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
251
252 if __name__=='__main__':
253     class TestPathFactory(AbstractPathFactory):
254         def __iter__(self):
255             return iter(self.results)
256         def BeginPath(self, pathcmd):
257             self.results = ["begin()"]
258         def AddPathElement(self, name, *args):
259             name, relative = self.command_table[name]
260             self.results.append('%s%r' % (name, (relative,) + args))
261         def EndPath(self, pathcmd):
262             self.results.append("end()")
263
264     separator = '\n    '
265     builder = PathCommandBuilder(TestPathFactory())
266
267     print
268     print "Example 1:"
269     for each in builder.Build('M28.495428 -80.762271 L76.495428 -80.762271 '):
270         print '   ', each
271
272     print
273     print "Example 2:"
274     for each in builder.Build('M28.495428 -80.762271 76.495428 -80.762271 '):
275         print '   ', each
276
277     print
278     print "Example 3:"
279     for each in builder.Build('M-32.351307 -1329.4836 A0.8746 0.8746 0 1 0 -30.602107 -1329.4836'):
280         print '   ', each
281
Note: See TracBrowser for help on using the browser.