1 import math
, re
, os
, platform
, warnings
4 version
= "SVGFig 2.0.0alpha2"
5 version_info
= (2, 0, 0, "alpha2")
7 ############################### default filenames
9 class VersionWarning(UserWarning): pass
10 warnings
.filterwarnings("default", category
=VersionWarning
)
12 if re
.search("windows", platform
.system(), re
.I
):
15 directory
= _winreg
.QueryValueEx(_winreg
.OpenKey(_winreg
.HKEY_CURRENT_USER
,
16 r
"Software\Microsoft\Windows\Current Version\Explorer\Shell Folders"), "Desktop")[0]
18 directory
= os
.path
.expanduser("~") + os
.sep
+ "Desktop"
20 def expand_fileName(fileName
):
21 if re
.search("windows", platform
.system(), re
.I
) and not os
.path
.isabs(fileName
):
22 fileName
= defaults
.directory
+ os
.sep
+ fileName
25 ############################### Defaults for each SVG element type
28 <?xml version="1.0" standalone="no"?>
29 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
39 ##### animateTransform
41 signature_circle
= ["cx", "cy", "r", "stroke", "fill"]
42 require_circle
= ["cx", "cy", "r"]
43 defaults_circle
= {"stroke": "black", "fill": "none"}
44 def tonumber_circle(svg
):
45 svg
.cx
, svg
.cy
, svg
.r
= tonumber(svg
.cx
), tonumber(svg
.cy
), tonumber(svg
.r
)
47 def transform_circle(trans
, svg
):
48 if isnumber(svg
.cx
) and isnumber(svg
.cy
):
49 x1
, y1
= trans(svg
.cx
, svg
.cy
)
51 x2
, y2
= trans(svg
.cx
+ svg
.r
, svg
.cy
)
52 svg
.r
= math
.sqrt((x1
- x2
)**2 + (y1
- y2
)**2)
53 svg
.cx
, svg
.cy
= x1
, y1
56 if isnumber(svg
.cx
) and isnumber(svg
.cy
):
58 return BBox(svg
.cx
- svg
.r
, svg
.cx
+ svg
.r
, svg
.cy
- svg
.r
, svg
.cy
+ svg
.r
)
60 return BBox(svg
.cx
, svg
.cx
, svg
.cy
, svg
.cy
)
62 return BBox(None, None, None, None)
75 ##### feComponentTransfer
77 ##### feConvolveMatrix
78 ##### feDiffuseLighting
79 ##### feDisplacementMap
93 ##### feSpecularLighting
100 ##### font-face-format
113 signature_line
= ["x1", "y1", "x2", "y2", "stroke"]
114 require_line
= ["x1", "y1", "x2", "y2"]
115 defaults_line
= {"stroke": "black"}
117 def tonumber_line(svg
):
118 svg
.x1
, svg
.y1
, svg
.x2
, svg
.y2
= tonumber(svg
.x1
), tonumber(svg
.y1
), tonumber(svg
.x2
), tonumber(svg
.y2
)
120 def transform_line(trans
, svg
):
121 if isnumber(svg
.x1
) and isnumber(svg
.y1
):
122 svg
.x1
, svg
.y1
= trans(svg
.x1
, svg
.y1
)
124 if isnumber(svg
.x2
) and isnumber(svg
.y2
):
125 svg
.x2
, svg
.y2
= trans(svg
.x2
, svg
.y2
)
128 isnumber1
= (isnumber(svg
.x1
) and isnumber(svg
.y1
))
129 isnumber2
= (isnumber(svg
.x2
) and isnumber(svg
.y2
))
131 if isnumber1
and isnumber2
: return BBox(svg
.x1
, svg
.x2
, svg
.y1
, svg
.y2
)
132 elif isnumber1
and not isnumber2
: return BBox(svg
.x1
, svg
.x1
, svg
.y1
, svg
.y1
)
133 elif not isnumber1
and isnumber2
: return BBox(svg
.x2
, svg
.x2
, svg
.y2
, svg
.y2
)
134 else: return BBox(None, None, None, None)
138 signature_marker
= None
145 signature_path
= ["d", "stroke", "fill"]
147 defaults_path
= {"d": [], "stroke": "black", "fill": "none"}
149 def tonumber_path(svg
):
150 svg
.d
= pathdata
.parse(svg
.d
)
152 def transform_path(trans
, svg
):
153 svg
.d
= pathdata
.transform(trans
, svg
.d
)
156 return pathdata
.bbox(pathdata
.parse(svg
.d
))
163 signature_rect
= ["x", "y", "width", "height", "stroke", "fill"]
164 require_rect
= ["x", "y", "width", "height"]
165 defaults_rect
= {"stroke": "black", "fill": "none"}
167 def transform_rect(trans
, svg
):
168 if isnumber(svg
.x
) and isnumber(svg
.y
):
169 if isnumber(svg
.width
) and isnumber(svg
.height
):
170 x1
, y1
= trans(svg
.x
, svg
.y
)
171 x2
, y2
= trans(svg
.x
+ svg
.width
, svg
.y
+ svg
.height
)
172 svg
.x
, svg
.y
= x1
, y1
173 svg
.width
, svg
.height
= x2
- x1
, y2
- y1
175 svg
.x
, svg
.y
= trans(svg
.x
, svg
.y
)
178 if isnumber(svg
.x
) and isnumber(svg
.y
):
179 if isnumber(svg
.width
) and isnumber(svg
.height
):
180 return BBox(svg
.x
, svg
.x
+ svg
.width
, svg
.y
, svg
.y
+ svg
.height
)
182 return BBox(svg
.x
, svg
.x
, svg
.y
, svg
.y
)
184 return BBox(None, None, None, None)
191 signature_svg
= ["width", "height", "viewBox"]
193 defaults_svg
= {"width": 400, "height": 400, "viewBox": (0, 0, 100, 100),
194 "style": {"stroke-width": "0.5pt", "font-size": "4px", "text-anchor": "middle"},
195 "font-family": ["Helvetica", "Arial", "FreeSans", "Sans", "sans", "sans-serif"],
196 "xmlns": "http://www.w3.org/2000/svg", "xmlns:xlink": "http://www.w3.org/1999/xlink", "version":"1.1",
199 def tonumber_svg(svg
):
200 svg
.width
= tonumber(svg
.width
)
201 svg
.height
= tonumber(svg
.height
)
202 svg
.viewBox
= tonumberlist(svg
.viewBox
)
203 svg
["style"] = tostringmap(svg
["style"])
204 svg
["font-family"] = tostringlist(svg
["font-family"])
208 signature_symbol
= None
211 signature_text
= ["x", "y", "stroke", "fill"]
212 require_text
= ["x", "y"]
213 defaults_text
= {"stroke": "none", "fill": "black"}
215 def tonumber_text(svg
):
216 svg
.x
= tonumber(svg
.x
)
217 svg
.y
= tonumber(svg
.y
)
219 def transform_text(trans
, svg
):
220 if isnumber(svg
.x
) and isnumber(svg
.y
):
221 svg
.x
, svg
.y
= trans(svg
.x
, svg
.y
)
224 if isnumber(svg
.x
) and isnumber(svg
.y
):
225 return BBox(svg
.x
, svg
.x
, svg
.y
, svg
.y
) # how to calculate text size???
227 return BBox(None, None, None, None)
233 signature_tspan
= None
236 signature_use
= ["x", "y", "xlink:href"]
237 require_use
= ["x", "y", "xlink:href"]
239 def tonumber_use(svg
):
240 svg
.x
= tonumber(svg
.x
)
241 svg
.y
= tonumber(svg
.y
)
243 def transform_use(trans
, svg
):
244 if isnumber(svg
.x
) and isnumber(svg
.y
):
245 svg
.x
, svg
.y
= trans(svg
.x
, svg
.y
)
248 if isnumber(svg
.x
) and isnumber(svg
.y
):
249 return BBox(svg
.x
, svg
.x
, svg
.y
, svg
.y
)
251 return BBox(None, None, None, None)
256 ############################### utility functions for default actions
265 if isinstance(x
, basestring
):
267 return tuple(map(float, re
.split("[, \t]+", x
)))
273 if isinstance(x
, basestring
):
274 return re
.split("[, \t]+", x
)
278 if isinstance(x
, basestring
):
280 return dict(map(lambda word
: word
.split(":"), re
.split("[; \t]+", x
)))
286 def isnumber(x
): return isinstance(x
, (int, long, float))
288 ############################### BBox class
291 def __init__(self
, xmin
, xmax
, ymin
, ymax
):
292 self
.xmin
, self
.xmax
, self
.ymin
, self
.ymax
= xmin
, xmax
, ymin
, ymax
295 return "<BBox xmin=%g xmax=%g ymin=%g ymax=%g>" % (self
.xmin
, self
.xmax
, self
.ymin
, self
.ymax
)
297 def insert(self
, x
, y
):
298 if self
.xmin
== None or x
< self
.xmin
: self
.xmin
= x
299 if self
.ymin
== None or y
< self
.ymin
: self
.ymin
= y
300 if self
.xmax
== None or x
> self
.xmax
: self
.xmax
= x
301 if self
.ymax
== None or y
> self
.ymax
: self
.ymax
= y
303 def __add__(self
, other
):
304 output
= BBox(self
.xmin
, self
.xmax
, self
.ymin
, self
.ymax
)
308 def __iadd__(self
, other
):
309 if self
.xmin
is None: self
.xmin
= other
.xmin
310 elif other
.xmin
is None: pass
311 else: self
.xmin
= min(self
.xmin
, other
.xmin
)
313 if self
.xmax
is None: self
.xmax
= other
.xmax
314 elif other
.xmax
is None: pass
315 else: self
.xmax
= max(self
.xmax
, other
.xmax
)
317 if self
.ymin
is None: self
.ymin
= other
.ymin
318 elif other
.ymin
is None: pass
319 else: self
.ymin
= min(self
.ymin
, other
.ymin
)
321 if self
.ymax
is None: self
.ymax
= other
.ymax
322 elif other
.ymax
is None: pass
323 else: self
.ymax
= max(self
.ymax
, other
.ymax
)
327 def __eq__(self
, other
):
328 return self
.xmin
== other
.xmin
and self
.xmax
== other
.xmax
and self
.ymin
== other
.ymin
and self
.ymax
== other
.ymax
330 def __ne__(self
, other
): return not (self
== other
)