1 import math
, cmath
, random
, re
, os
, sys
, copy
, itertools
, codecs
, tempfile
, new
, types
, copy_reg
, warnings
4 saved
= [] # keep track of all fileNames saved for the user's convenience
6 ############################### convenient functions for dealing with SVG
8 def randomid(prefix
="", characters
=10):
9 return prefix
+ "".join(random
.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", characters
))
11 # This rgb function could be a lot better... something to think about...
12 def rgb(r
, g
, b
, maximum
=1.):
13 return "#%02x%02x%02x" % (max(0, min(r
*255./maximum
, 255)), max(0, min(g
*255./maximum
, 255)), max(0, min(b
*255./maximum
, 255)))
15 ############################### class SVG
17 def shortcut(tag
): return eval("lambda *args, **kwds: SVG(\"%s\", *args, **kwds)" % tag
)
20 def _preprocess_attribname(self
, name
):
21 name_colon
= re
.sub("__", ":", name
)
22 if name_colon
!= name
: name
= name_colon
24 name_dash
= re
.sub("_", "-", name
)
25 if name_dash
!= name
: name
= name_dash
29 def __init__(self
, tag
, *signature_attrib
, **more_attrib
):
30 self
.__dict
__["tag"] = tag
31 self
.__dict
__["attrib"] = dict(getattr(defaults
, "defaults_%s" % tag
, {}))
32 self
.__dict
__["children"] = []
33 self
.__dict
__["_svg"] = self
35 signature
= getattr(defaults
, "signature_%s" % tag
, None)
37 # if there is no signature, inline arguments are interpreted as children
39 self
.children
.extend(signature_attrib
)
42 if len(signature_attrib
) > len(signature
):
43 raise TypeError, "Tag '%s' expects no more than %d signature attributes (saw %d)" % (tag
, len(signature
), len(signature_attrib
))
45 for name
, value
in zip(signature
, signature_attrib
):
46 self
.attrib
[name
] = value
48 # preprocess more_attrib names
49 for name
, value
in more_attrib
.items():
50 processed_name
= self
._preprocess
_attribname
(name
)
51 if processed_name
!= name
:
53 more_attrib
[processed_name
] = value
55 self
.attrib
.update(more_attrib
)
57 require
= getattr(defaults
, "require_%s" % tag
, None)
58 if require
is not None:
60 if name
not in self
.attrib
:
61 raise TypeError, "Tag '%s' requires a '%s' attribute" % (tag
, name
)
63 ### construct trees inline
64 def __call__(self
, *children
):
65 self
.children
.extend(children
)
68 ### recursively tonumber, transform, bbox, and svg
70 if self
.tag
is not None:
71 tonumber_tag
= getattr(defaults
, "tonumber_%s" % self
.tag
, None)
72 if tonumber_tag
is not None: tonumber_tag(self
)
74 for child
in self
.children
:
75 if isinstance(child
, SVG
): child
.tonumber()
77 def transform(self
, t
):
78 t
= cannonical_transformation(t
)
80 if self
.tag
is not None:
81 tonumber_tag
= getattr(defaults
, "tonumber_%s" % self
.tag
, None)
82 if tonumber_tag
is not None: tonumber_tag(self
)
84 transform_tag
= getattr(defaults
, "transform_%s" % self
.tag
, None)
85 if transform_tag
is not None: transform_tag(t
, self
)
87 for child
in self
.children
:
88 if isinstance(child
, SVG
): child
.transform(t
)
91 if self
.tag
is not None:
92 tonumber_tag
= getattr(defaults
, "tonumber_%s" % self
.tag
, None)
93 if tonumber_tag
is not None: tonumber_tag(self
)
95 bbox_tag
= getattr(defaults
, "bbox_%s" % self
.tag
, None)
96 if bbox_tag
is not None:
97 output
= bbox_tag(self
)
99 output
= defaults
.BBox(None, None, None, None)
101 for child
in self
.children
:
102 if isinstance(child
, SVG
): output
+= child
.bbox()
105 def svg(self
): self
._svg
= self
107 ### signature attributes are accessible as member data
108 def __getattr__(self
, name
):
109 if self
.__dict
__["tag"] is None: return self
.__dict
__[name
]
111 signature
= getattr(defaults
, "signature_%s" % self
.__dict
__["tag"], None)
112 if signature
is not None and name
in signature
:
113 return self
.attrib
[name
]
115 raise AttributeError, "Tag '%s' has no signature attrib '%s'" % (self
.tag
, name
)
117 def __setattr__(self
, name
, value
):
118 if self
.__dict
__["tag"] is None or name
== "repr" or name
in self
.__dict
__:
119 self
.__dict
__[name
] = value
122 signature
= getattr(defaults
, "signature_%s" % self
.__dict
__["tag"], None)
123 if signature
is not None and name
in signature
:
124 self
.attrib
[name
] = value
126 raise AttributeError, "Tag '%s' has no signature attrib '%s'" % (self
.tag
, name
)
128 def __nonzero__(self
): return True
130 ### support access to deep children with tree indexes
131 def _treeindex_descend(self
, obj
, treeindex
):
132 if isinstance(treeindex
, (list, tuple)):
133 for i
in treeindex
[:-1]:
135 treeindex
= treeindex
[-1]
136 return treeindex
, obj
138 def __getitem__(self
, treeindex
):
139 treeindex
, obj
= self
._treeindex
_descend
(self
, treeindex
)
141 if isinstance(treeindex
, (int, long, slice)): return obj
.children
[treeindex
]
142 elif isinstance(treeindex
, basestring
): return obj
.attrib
[treeindex
]
144 raise IndexError, "treeindex must be [#, #, ... #] or [#, #, ... \"str\"]"
146 def __setitem__(self
, treeindex
, value
):
147 treeindex
, obj
= self
._treeindex
_descend
(self
, treeindex
)
149 if isinstance(treeindex
, (int, long, slice)): obj
.children
[treeindex
] = value
150 elif isinstance(treeindex
, basestring
): obj
.attrib
[treeindex
] = value
152 raise IndexError, "treeindex must be [#, #, ... #] or [#, #, ... \"str\"]"
154 def __delitem__(self
, treeindex
):
155 treeindex
, obj
= self
._treeindex
_descend
(self
, treeindex
)
157 if isinstance(treeindex
, (int, long, slice)): del obj
.children
[treeindex
]
158 elif isinstance(treeindex
, basestring
): del obj
.attrib
[treeindex
]
160 raise IndexError, "treeindex must be [#, #, ... #] or [#, #, ... \"str\"]"
162 ################ nested class for walking the tree
163 class _SVGDepthIterator
:
164 def __init__(self
, svg
, treeindex
, depth_limit
, attrib
, attrib_first
):
166 self
.treeindex
= treeindex
168 self
.depth_limit
= depth_limit
170 self
.attrib_first
= attrib_first
172 def __iter__(self
): return self
174 def make_children_iterators(self
):
175 if getattr(self
.current
, "children", None) is not None:
176 for i
, s
in enumerate(self
.current
.children
):
177 self
.iterators
.append(self
.__class
__(s
, self
.treeindex
+ (i
,), self
.depth_limit
, self
.attrib
, self
.attrib_first
))
179 def make_attrib_iterators(self
):
180 if getattr(self
.current
, "attrib", None) is not None:
181 items
= self
.current
.attrib
.items()
184 self
.iterators
.append(self
.__class
__(s
, self
.treeindex
+ (k
,), self
.depth_limit
, self
.attrib
, self
.attrib_first
))
189 if self
.treeindex
!= ():
190 return self
.treeindex
, self
.current
192 if self
.depth_limit
is not None and len(self
.treeindex
) >= self
.depth_limit
: raise StopIteration
194 if "iterators" not in self
.__dict
__:
197 if self
.attrib
and self
.attrib_first
: self
.make_attrib_iterators()
198 self
.make_children_iterators()
199 if self
.attrib
and not self
.attrib_first
: self
.make_attrib_iterators()
201 self
.iterators
= itertools
.chain(*self
.iterators
)
203 return self
.iterators
.next()
204 ################ end nested class
206 ### walk the tree or show it (uses the nested class)
207 def walk(self
, depth_limit
=None, attrib
=False, attrib_first
=False):
208 return self
._SVGDepthIterator
(self
, (), depth_limit
, attrib
, attrib_first
)
210 def tree(self
, depth_limit
=None, attrib
=False, attrib_first
=False, index_width
=20, showtop
=True, asstring
=False):
212 output
= [("%s %s" % (("%%-%ds" % index_width
) % repr(None), repr(self
)))]
216 for treeindex
, element
in self
.walk(depth_limit
, attrib
, attrib_first
):
217 if isinstance(element
, basestring
):
218 if len(element
) > 13:
219 repr_element
= "'%s...'" % element
[0:10]
221 repr_element
= "'%s'" % element
223 repr_element
= repr(element
)
225 output
.append(("%s %s%s" % (("%%-%ds" % index_width
) % repr(list(treeindex
)), ". . " * len(treeindex
), repr_element
)))
227 if asstring
: return "\n".join(output
)
228 else: print "\n".join(output
)
230 ### how to present SVG objects on the commandline (used in tree)
232 if "repr" in self
.__dict
__: return self
.repr
234 output
= ["%s" % self
.tag
]
236 remaining
= copy
.copy(self
.attrib
) # shallow copy
238 value
= remaining
.pop("id", None)
239 if value
is not None:
240 output
.append("id='%s'" % value
)
242 # special handling of a text child: print it out and truncate if it's too long
243 if self
.tag
in ("text", "tspan") and len(self
.children
) == 1 and isinstance(self
.children
[0], basestring
):
244 value
= re
.sub("\n", "\\\\n", self
.children
[0])
246 repr_value
= "'%s...'" % value
[0:10]
248 repr_value
= "'%s'" % value
249 output
.append(repr_value
)
251 signature
= getattr(defaults
, "signature_%s" % self
.tag
, None)
252 if signature
is not None:
253 for name
in signature
:
255 value
= remaining
.pop(name
)
257 # special handling of path data: truncate it if it's too long
259 if isinstance(value
, basestring
):
260 value
= re
.sub("\n", "\\\\n", value
)
262 repr_value
= "'%s...'" % value
[0:10]
264 repr_value
= "'%s'" % value
266 elif isinstance(value
, (list, tuple)):
268 repr_value
= repr(value
[0:3])
269 repr_value
= "%s, ...%s" % (repr_value
[0:-1], repr_value
[-1])
271 repr_value
= repr(value
)
274 repr_value
= repr(value
)
276 # special handling of floats: use __str__ instead of __repr__
277 elif isinstance(value
, float):
278 repr_value
= "%s" % str(value
)
282 repr_value
= repr(value
)
284 output
.append("%s=%s" % (name
, repr_value
))
286 # if the signature item is not present, don't print it
290 lenchildren
= len(self
.children
)
292 # special handling of a text child: already printed
293 if self
.tag
in ("text", "tspan") and isinstance(self
.children
[0], basestring
):
296 output
.append("(1 child)")
298 elif lenchildren
> 1:
299 output
.append("(%d children)" % lenchildren
)
301 lenremaining
= len(remaining
)
302 if lenremaining
== 1:
303 output
.append("(1 other attribute)")
304 elif lenremaining
> 1:
305 output
.append("(%d other attributes)" % lenremaining
)
307 return "<%s>" % " ".join(output
)
309 ### convert to XML, view, and save
310 def xml(self
, indent
=u
" ", newl
=u
"\n"):
312 if self
.tag
== "svg" or "_tag" in self
.__dict
__ and self
._tag
== "svg":
315 svg
= SVG("svg")(self
)
317 output
= [defaults
.xml_header
] + svg_to_xml(svg
, indent
) + [u
""]
318 return unicode(newl
.join(output
))
320 def view(self
): # no writing-to-disk needed!
322 _viewer
.str(self
.xml())
324 def save(self
, fileName
, encoding
="utf-8", compresslevel
=None):
325 fileName
= defaults
.expand_fileName(fileName
)
327 if compresslevel
is not None or re
.search("\.svgz$", fileName
, re
.I
) or re
.search("\.gz$", fileName
, re
.I
):
329 if compresslevel
is None:
330 f
= gzip
.GzipFile(fileName
, "w")
332 f
= gzip
.GzipFile(fileName
, "w", compresslevel
)
334 f
= codecs
.EncodedFile(f
, "utf-16", encoding
)
339 f
= codecs
.open(fileName
, "w", encoding
=encoding
)
343 saved
.append(fileName
)
345 def _write_tempfile(self
, fileName
=None, encoding
="utf-8"):
347 fd
, fileName
= tempfile
.mkstemp(".svg", "svgfig-")
350 fileName
= defaults
.expand_fileName(fileName
)
352 self
.save(fileName
, encoding
)
355 def inkview(self
, fileName
=None, encoding
="utf-8"):
356 fileName
= self
._write
_tempfile
(fileName
, encoding
)
357 os
.spawnvp(os
.P_NOWAIT
, "inkview", ("inkview", fileName
))
359 def inkscape(self
, fileName
=None, encoding
="utf-8"):
360 fileName
= self
._write
_tempfile
(fileName
, encoding
)
361 os
.spawnvp(os
.P_NOWAIT
, "inkscape", ("inkscape", fileName
))
363 def firefox(self
, fileName
=None, encoding
="utf-8"):
364 fileName
= self
._write
_tempfile
(fileName
, encoding
)
365 os
.spawnvp(os
.P_NOWAIT
, "firefox", ("firefox", fileName
))
367 ### pickleability and value-based equality
368 def __getstate__(self
):
369 return (sys
.version_info
, defaults
.version_info
, self
.__dict
__)
371 def __setstate__(self
, state
):
372 python_version
= state
[0]
373 svgfig_version
= state
[1]
374 if svgfig_version
!= defaults
.version_info
:
375 warnings
.warn("Object created in SVGFig %s, but this is SVGFig %s" % (".".join(map(str, svgfig_version
)), ".".join(map(str, defaults
.version_info
))), defaults
.VersionWarning
, 5)
376 self
.__dict
__ = state
[2]
378 def __eq__(self
, other
):
379 if id(self
) == id(other
): return True
380 if self
.__class
__ != other
.__class
__: return False
381 selfdict
= copy
.copy(self
.__dict
__)
382 otherdict
= copy
.copy(other
.__dict
__)
384 del otherdict
["_svg"]
385 return selfdict
== otherdict
387 def __ne__(self
, other
): return not (self
== other
)
389 def __deepcopy__(self
, memo
={}):
390 output
= new
.instance(self
.__class
__)
391 output
.__dict
__ = copy
.deepcopy(self
.__dict
__, memo
)
392 if "repr" in output
.__dict
__: del output
.__dict
__["repr"]
393 memo
[id(self
)] = output
397 def append(self
, other
): self
.children
.append(other
)
398 def prepend(self
, other
): self
.children
[0:0] = [other
]
399 def insert(self
, i
, other
): self
.children
.insert(i
, other
)
400 def remove(self
, other
): self
.children
.remove(other
)
401 def __len__(self
): return len(self
.children
)
403 def extend(self
, other
):
404 if isinstance(other
, SVG
):
405 self
.children
.extend(other
.children
)
406 elif isinstance(other
, basestring
):
407 self
.children
.append(other
)
409 self
.children
.extend(other
)
411 def __add__(self
, other
):
412 output
= copy
.deepcopy(self
)
416 def __iadd__(self
, other
):
417 self
.children
.append(other
)
420 def __mul__(self
, other
):
421 output
= copy
.deepcopy(self
)
425 def __rmul__(self
, other
):
428 def __imul__(self
, other
):
429 self
.children
*= other
432 def count(self
, *args
, **kwds
): return self
.children
.count(*args
, **kwds
)
433 def index(self
, *args
, **kwds
): return self
.children
.index(*args
, **kwds
)
434 def pop(self
, *args
, **kwds
): return self
.children
.pop(*args
, **kwds
)
435 def reverse(self
, *args
, **kwds
): return self
.children
.reverse(*args
, **kwds
)
438 def clear(self
, *args
, **kwds
):
440 self
.attrib
.clear(*args
, **kwds
)
442 def update(self
, other
):
443 if isinstance(other
, SVG
):
444 self
.attrib
.update(other
.attrib
)
446 self
.attrib
.update(other
)
448 def __contains__(self
, other
):
449 return other
in self
.attrib
or other
in self
.children
451 def fromkeys(self
, *args
, **kwds
): return self
.attrib
.fromkeys(*args
, **kwds
)
452 def has_key(self
, *args
, **kwds
): return self
.attrib
.has_key(*args
, **kwds
)
453 def items(self
, *args
, **kwds
): return self
.attrib
.items(*args
, **kwds
)
454 def keys(self
, *args
, **kwds
): return self
.attrib
.keys(*args
, **kwds
)
455 def values(self
, *args
, **kwds
): return self
.attrib
.values(*args
, **kwds
)
456 def get(self
, *args
, **kwds
): return self
.attrib
.get(*args
, **kwds
)
457 def setdefault(self
, *args
, **kwds
): return self
.attrib
.setdefault(*args
, **kwds
)
458 def iteritems(self
, *args
, **kwds
): return self
.attrib
.iteritems(*args
, **kwds
)
459 def iterkeys(self
, *args
, **kwds
): return self
.attrib
.iterkeys(*args
, **kwds
)
460 def itervalues(self
, *args
, **kwds
): return self
.attrib
.itervalues(*args
, **kwds
)
461 def pop(self
, *args
, **kwds
): return self
.attrib
.pop(*args
, **kwds
)
462 def popitem(self
, *args
, **kwds
): return self
.attrib
.popitem(*args
, **kwds
)
463 def copy(self
): return copy
.copy(self
)
464 def deepcopy(self
): return copy
.deepcopy(self
)
466 ############################### rules for converting into XML
468 # how to convert SVG objects into XML (as a list of lines to be joined later)
469 def svg_to_xml(svg
, indent
, depth
=0):
470 # if the tag is None, it's a dynamic object that needs to be turned into _svg
471 if isinstance(svg
, SVG
) and svg
.tag
is None:
473 svg
= svg
._svg
# follow that? good.
475 if isinstance(svg
, basestring
):
478 elif isinstance(svg
, SVG
):
479 line
= [indent
* depth
, u
"<", svg
.tag
, u
" "]
480 remaining
= copy
.copy(svg
.attrib
) # shallow copy that we can pop
483 line
.append(u
"id=\"%s\" " % remaining
.pop("id"))
484 except KeyError: pass
486 # signature attributes first, for readability
487 signature
= getattr(defaults
, "signature_%s" % svg
.tag
, None)
488 if signature
is not None:
489 for name
in signature
:
491 line
.append(u
"%s=\"%s\" " % (name
, attrib_to_xml(svg
.tag
, name
, remaining
.pop(name
))))
492 except KeyError: pass
494 remainingkeys
= remaining
.keys()
495 remainingkeys
.sort() # for reproducible XML (maybe also helps readability)
496 for name
in remainingkeys
:
497 line
.append(u
"%s=\"%s\" " % (name
, attrib_to_xml(svg
.tag
, name
, remaining
[name
])))
499 if len(svg
.children
) == 0:
501 return [u
"".join(line
)]
506 # no indenting for text
507 if svg
.tag
in ("text", "tspan"):
508 for i
in svg
.children
:
509 line
.extend(svg_to_xml(i
, indent
, 0))
510 line
.append(u
"</%s>" % (svg
.tag
))
511 return [u
"".join(line
)]
514 lines
= [u
"".join(line
)]
515 for i
in svg
.children
:
516 lines
.extend(svg_to_xml(i
, indent
, depth
+1))
517 lines
.append(u
"%s</%s>" % (indent
* depth
, svg
.tag
))
521 if type(svg
) == types
.InstanceType
:
522 raise TypeError, "SVG contains an unrecognized object: instance of class %s" % svg
.__class
__.__name
__
524 raise TypeError, "SVG contains an unrecognized object: %s" % type(svg
)
526 # how to convert different attribute types into XML
527 def attrib_to_xml(tag
, name
, value
):
528 if isinstance(value
, basestring
):
531 elif isinstance(value
, (int, long, float)):
532 return repr(value
) # more precise
534 elif isinstance(value
, (list, tuple)) and tag
== "path" and name
== "d":
536 if isinstance(x
, (int, long, float)): return repr(x
) # more precise
542 if not isinstance(datum
, (list, tuple)):
543 raise TypeError, "Pathdata elements must be lists/tuples"
546 args
= map(numbertostr
, datum
[1:])
548 if lastcommand
== command
:
550 line
.append(u
" ".join(args
))
551 lastcommand
= command
554 line
.append(u
" ".join(args
))
556 lastcommand
= command
558 return u
"".join(line
)
560 elif isinstance(value
, (list, tuple)):
563 line
.append(attrib_to_xml(tag
, name
, v
))
564 return u
", ".join(line
)
566 elif isinstance(value
, dict):
568 for n
, v
in value
.items():
569 line
.append(u
"%s:%s" % (n
, attrib_to_xml(tag
, name
, v
)))
570 return u
"; ".join(line
)
573 return unicode(value
)
575 ############################### XML preprocessor instructions and comments
577 class Instruction(SVG
):
578 def __init__(self
, tag
, text
):
579 self
.__dict
__["tag"] = tag
580 self
.__dict
__["attrib"] = {}
581 self
.__dict
__["children"] = []
582 self
.__dict
__["_svg"] = self
583 self
.__dict
__["text"] = text
585 def xml(self
): return "<?%s %s?>" % (self
.tag
, self
.text
)
588 value
= re
.sub("\n", "\\\\n", self
.text
)
590 return "<?%s %s...?>" % (self
.tag
, value
[0:20])
592 return "<?%s %s?>" % (self
.tag
, value
)
595 def __init__(self
, text
):
596 if text
.find("--") != -1:
597 raise ValueError, "SVG comments must not include '--'"
598 self
.__dict
__["tag"] = "comment"
599 self
.__dict
__["attrib"] = {}
600 self
.__dict
__["children"] = []
601 self
.__dict
__["_svg"] = self
602 self
.__dict
__["text"] = text
604 def __eq__(self
, other
): return SVG
.__eq
__(self
, other
) and self
.text
== other
.text
606 def xml(self
): return "<!-- %s -->" % self
.text
609 value
= re
.sub("\n", "\\\\n", self
.text
)
611 return "<!-- %s... -->" % value
[0:20]
613 return "<!-- %s -->" % value
616 def __init__(self
, text
):
617 if text
.find("]]>") != -1:
618 raise ValueError, "CDATA must not include ']]>'"
619 self
.__dict
__["tag"] = "CDATA"
620 self
.__dict
__["attrib"] = {}
621 self
.__dict
__["children"] = []
622 self
.__dict
__["_svg"] = self
623 self
.__dict
__["text"] = text
625 def __eq__(self
, other
): return SVG
.__eq
__(self
, other
) and self
.text
== other
.text
627 def xml(self
): return "<![CDATA[%s]]>" % self
.text
630 value
= re
.sub("\n", "\\\\n", self
.text
)
632 return "<![CDATA[%s...]]>" % value
[0:20]
634 return "<![CDATA[%s]]>" % value
636 ############################### reading SVG from a file
639 if re
.search("\.svgz$", fileName
, re
.I
) or re
.search("\.gz$", fileName
, re
.I
):
641 f
= gzip
.GzipFile(fileName
)
644 return load_stream(f
)
646 def template(fileName
, svg
, replaceme
="REPLACEME"):
647 output
= load(fileName
)
648 for treeindex
, i
in output
.walk():
649 if isinstance(i
, SVG
) and i
.tag
== replaceme
:
650 output
[treeindex
] = svg
653 def load_stream(stream
):
654 from xml
.sax
import handler
, make_parser
655 from xml
.sax
.handler
import feature_namespaces
, feature_external_ges
, feature_external_pes
657 # With a little thought, this could be streamlined. In its current
658 # state, it works (with processing instructions, comments, and CDATA).
659 class ContentHandler(handler
.ContentHandler
):
663 self
.all_whitespace
= re
.compile("^\s*$")
666 def startElement(self
, tag
, attrib
):
668 s
.attrib
= dict(attrib
.items())
669 if len(self
.stack
) > 0:
670 last
= self
.stack
[-1]
671 last
.children
.append(s
)
674 def characters(self
, ch
):
676 last
= self
.stack
[-1]
680 if not isinstance(ch
, basestring
) or self
.all_whitespace
.match(ch
) is None:
681 if len(self
.stack
) > 0:
682 last
= self
.stack
[-1]
683 if len(last
.children
) > 0 and isinstance(last
.children
[-1], basestring
):
684 last
.children
[-1] = last
.children
[-1] + "\n" + ch
686 last
.children
.append(ch
)
688 def endElement(self
, tag
):
689 if len(self
.stack
) > 0:
690 last
= self
.stack
[-1]
691 self
.output
= self
.stack
.pop()
693 # If a processing instruction is outside the main <svg> tag, it will be lost.
694 def processingInstruction(self
, target
, data
):
695 s
= Instruction(target
, data
)
696 if len(self
.stack
) > 0:
697 last
= self
.stack
[-1]
698 last
.children
.append(s
)
701 def comment(self
, comment
):
702 s
= Comment(re
.sub("(^ | $)", "", comment
))
703 if len(self
.stack
) > 0:
704 last
= self
.stack
[-1]
705 last
.children
.append(s
)
708 def startCDATA(self
):
710 if len(self
.stack
) > 0:
711 last
= self
.stack
[-1]
712 last
.children
.append(s
)
717 if len(self
.stack
) > 0:
718 last
= self
.stack
[-1]
719 self
.output
= self
.stack
.pop()
722 def startDTD(self
, name
, public_id
, system_id
): pass
723 def endDTD(self
): pass
724 def startEntity(self
, name
): pass
725 def endEntity(self
, name
): pass
727 ch
= ContentHandler()
728 parser
= make_parser()
729 parser
.setContentHandler(ch
)
730 parser
.setProperty(handler
.property_lexical_handler
, ch
)
731 parser
.setFeature(feature_namespaces
, 0)
732 parser
.setFeature(feature_external_ges
, 0)
736 ############################### standard representation for transformations and parametric functions
738 def cannonical_transformation(expr
):
740 output
= lambda x
, y
: (x
, y
)
741 output
.func_name
= "identity"
747 if expr
.func_code
.co_argcount
== 2:
751 elif expr
.func_code
.co_argcount
== 1:
752 split
= lambda z
: (z
.real
, z
.imag
)
753 output
= lambda x
, y
: split(expr(complex(x
, y
)))
754 output
.func_name
= expr
.func_name
758 raise TypeError, "Must be a 2 -> 2 real function or a complex -> complex function"
761 compiled
= compile(expr
, expr
, "eval")
764 if "x" in compiled
.co_names
and "y" in compiled
.co_names
:
766 evalexpr
= re
.sub("x", "float(x)", evalexpr
)
767 evalexpr
= re
.sub("y", "float(y)", evalexpr
)
768 output
= eval("lambda x,y: (%s)" % evalexpr
, math
.__dict
__)
769 output
.func_name
= "x, y -> %s" % expr
773 elif "z" in compiled
.co_names
:
774 evalexpr
= re
.sub("z", "complex(x,y)", expr
)
775 output
= eval("lambda x,y: ((%s).real, (%s).imag)" % (evalexpr
, evalexpr
), cmath
.__dict
__)
776 output
.func_name
= "z -> %s" % expr
780 raise TypeError, "Transformation string '%s' must contain real 'x' and 'y' or complex 'z'" % expr
782 def cannonical_parametric(expr
):
786 if expr
.func_code
.co_argcount
== 1:
790 raise TypeError, "Must be a 1 -> 2 real function"
793 compiled
= compile(expr
, expr
, "eval")
796 if "t" in compiled
.co_names
:
797 output
= eval("lambda t: (%s)" % re
.sub("t", "float(t)", expr
), math
.__dict
__)
798 output
.func_name
= "t -> %s" % expr
802 elif "x" in compiled
.co_names
:
803 output
= eval("lambda t: (t, %s)" % re
.sub("x", "float(t)", expr
), math
.__dict
__)
804 output
.func_name
= "x -> %s" % expr
807 # real (a complex number restricted to the real axis) -> complex
808 elif "z" in compiled
.co_names
:
809 evalexpr
= re
.sub("z", "complex(t,0)", expr
)
810 output
= eval("lambda t: ((%s).real, (%s).imag)" % (evalexpr
, evalexpr
), cmath
.__dict
__)
811 output
.func_name
= "z -> %s" % expr
815 raise TypeError, "Parametric string '%s' must contain real 't', 'x', or 'z'" % expr
817 ############################### make code objects pickleable (so that curves and transformations are pickleable)
819 def _code_constructor(code_args
, python_version
, svgfig_version
):
820 if python_version
!= sys
.version_info
or svgfig_version
!= defaults
.version_info
:
821 warnings
.warn("Function created in Python %s/SVGFig %s, but this is Python %s/SVGFig %s"
822 % (".".join(map(str, python_version
)), ".".join(map(str, svgfig_version
)), ".".join(map(str, sys
.version_info
)), ".".join(map(str, defaults
.version_info
))),
823 defaults
.VersionWarning
, 5)
824 return new
.code(*code_args
)
826 def _code_serializer(code
):
827 if code
.co_freevars
or code
.co_cellvars
:
828 raise ValueError, "Sorry, can't pickle code that depends on local variables %s %s" % (str(code
.co_freevars
), str(code
.co_cellvars
))
829 return _code_constructor
, ((code
.co_argcount
, code
.co_nlocals
, code
.co_stacksize
,
830 code
.co_flags
, code
.co_code
, code
.co_consts
, code
.co_names
,
831 code
.co_varnames
, code
.co_filename
, code
.co_name
,
832 code
.co_firstlineno
, code
.co_lnotab
),
833 sys
.version_info
, defaults
.version_info
)
836 copy_reg
.pickle(types
.CodeType
, _code_serializer
)