···
import xml.sax.saxutils as xml
6
-
from collections import defaultdict
8
-
from . import defs, url_encode
6
+
from . import url_encode
7
+
from .types import DrawingElement, DrawingBasicElement, DrawingParentElement
11
-
def write_xml_node_args(args, output_file, id_map=None):
12
-
for k, v in args.items():
13
-
if v is None: continue
14
-
if isinstance(v, DrawingElement):
16
-
if id_map and id(v) in id_map:
17
-
mapped_id = id_map[id(v)]
18
-
if mapped_id is None:
20
-
if k == 'xlink:href':
21
-
v = '#{}'.format(mapped_id)
23
-
v = 'url(#{})'.format(mapped_id)
24
-
output_file.write(' {}="{}"'.format(k,v))
27
-
class DrawingElement:
28
-
'''Base class for drawing elements.
30
-
Subclasses must implement write_svg_element.
32
-
def write_svg_element(self, id_map, is_duplicate, output_file, dry_run,
34
-
raise NotImplementedError('Abstract base class')
35
-
def get_svg_defs(self):
37
-
def get_linked_elems(self):
39
-
def write_svg_defs(self, id_map, is_duplicate, output_file, dry_run):
40
-
for defn in self.get_svg_defs():
41
-
if is_duplicate(defn):
43
-
defn.write_svg_defs(id_map, is_duplicate, output_file, dry_run)
46
-
defn.write_svg_element(
47
-
id_map, is_duplicate, output_file, dry_run, force_dup=True)
49
-
output_file.write('\n')
50
-
def __eq__(self, other):
51
-
return self is other
53
-
class DrawingBasicElement(DrawingElement):
54
-
'''Base class for SVG drawing elements that are a single node with no child
59
-
def __init__(self, **args):
61
-
for k, v in args.items():
62
-
k = k.replace('__', ':')
63
-
k = k.replace('_', '-')
68
-
self.ordered_children = defaultdict(list)
69
-
def check_children_allowed(self):
70
-
if not self.has_content:
72
-
'{} does not support children'.format(type(self)))
73
-
def all_children(self):
74
-
'''Return self.children and self.ordered_children as a single list.'''
75
-
output = list(self.children)
76
-
for z in sorted(self.ordered_children):
77
-
output.extend(self.ordered_children[z])
81
-
return self.args.get('id', None)
82
-
def write_svg_element(self, id_map, is_duplicate, output_file, dry_run,
84
-
children = self.all_children()
86
-
if is_duplicate(self) and self.id is None:
88
-
for elem in self.get_linked_elems():
91
-
if self.has_content:
92
-
self.write_content(id_map, is_duplicate, output_file, dry_run)
94
-
self.write_children_content(
95
-
id_map, is_duplicate, output_file, dry_run)
97
-
if is_duplicate(self) and not force_dup:
99
-
if id_map and id(self) in id_map:
100
-
mapped_id = id_map[id(self)]
101
-
output_file.write('<use xlink:href="#{}" />'.format(mapped_id))
103
-
output_file.write('<')
104
-
output_file.write(self.TAG_NAME)
105
-
override_args = self.args
106
-
if id(self) in id_map:
107
-
override_args = dict(override_args)
108
-
override_args['id'] = id_map[id(self)]
109
-
write_xml_node_args(override_args, output_file, id_map)
110
-
if not self.has_content and not children:
111
-
output_file.write(' />')
113
-
output_file.write('>')
114
-
if self.has_content:
115
-
self.write_content(id_map, is_duplicate, output_file, dry_run)
117
-
self.write_children_content(
118
-
id_map, is_duplicate, output_file, dry_run)
119
-
output_file.write('</')
120
-
output_file.write(self.TAG_NAME)
121
-
output_file.write('>')
122
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
123
-
'''Override in a subclass to add data between the start and end tags.
125
-
This will not be called if has_content is False.
127
-
raise RuntimeError('This element has no content')
128
-
def write_children_content(self, id_map, is_duplicate, output_file,
130
-
'''Override in a subclass to add data between the start and end tags.
132
-
This will not be called if has_content is False.
134
-
children = self.all_children()
136
-
for child in children:
137
-
child.write_svg_element(
138
-
id_map, is_duplicate, output_file, dry_run)
140
-
output_file.write('\n')
141
-
for child in children:
142
-
child.write_svg_element(id_map, is_duplicate, output_file, dry_run)
143
-
output_file.write('\n')
144
-
def get_svg_defs(self):
145
-
return [v for v in self.args.values()
146
-
if isinstance(v, DrawingElement)]
147
-
def write_svg_defs(self, id_map, is_duplicate, output_file, dry_run):
148
-
super().write_svg_defs(id_map, is_duplicate, output_file, dry_run)
149
-
for child in self.all_children():
150
-
child.write_svg_defs(id_map, is_duplicate, output_file, dry_run)
151
-
def __eq__(self, other):
152
-
if isinstance(other, type(self)):
153
-
return (self.TAG_NAME == other.TAG_NAME and
154
-
self.args == other.args and
155
-
self.children == other.children and
156
-
self.ordered_children == other.ordered_children)
158
-
def append_anim(self, animate_element):
159
-
self.children.append(animate_element)
160
-
def extend_anim(self, animate_iterable):
161
-
self.children.extend(animate_iterable)
162
-
def append_title(self, text, **kwargs):
163
-
self.children.append(Title(text, **kwargs))
165
-
class DrawingParentElement(DrawingBasicElement):
166
-
'''Base class for SVG elements that can have child nodes.'''
168
-
def __init__(self, children=(), ordered_children=None, **args):
169
-
super().__init__(**args)
170
-
self.children = list(children)
171
-
if ordered_children:
172
-
self.ordered_children.update(
173
-
(z, list(v)) for z, v in ordered_children.items())
174
-
if self.children or self.ordered_children:
175
-
self.check_children_allowed()
176
-
def draw(self, obj, *, z=None, **kwargs):
179
-
if not hasattr(obj, 'write_svg_element'):
180
-
elements = obj.to_drawables(**kwargs)
182
-
assert len(kwargs) == 0
184
-
if hasattr(elements, 'write_svg_element'):
185
-
self.append(elements, z=z)
187
-
self.extend(elements, z=z)
188
-
def append(self, element, *, z=None):
189
-
self.check_children_allowed()
191
-
self.ordered_children[z].append(element)
193
-
self.children.append(element)
194
-
def extend(self, iterable, *, z=None):
195
-
self.check_children_allowed()
197
-
self.ordered_children[z].extend(iterable)
199
-
self.children.extend(iterable)
200
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
class NoElement(DrawingElement):
''' A drawing element that has no effect '''
def write_svg_element(self, id_map, is_duplicate, output_file, dry_run,
15
+
context, force_dup=False):
if isinstance(other, type(self)):
···
234
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
41
+
def write_content(self, id_map, is_duplicate, output_file, context,
output_file.write(self.content)
···
526
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
334
+
def write_content(self, id_map, is_duplicate, output_file, context,
output_file.write(self.escaped_text)
530
-
def write_children_content(self, id_map, is_duplicate, output_file,
339
+
def write_children_content(self, id_map, is_duplicate, output_file, context,
children = self.all_children()
534
-
child.write_svg_element(id_map, is_duplicate, output_file, dry_run)
343
+
child.write_svg_element(
344
+
id_map, is_duplicate, output_file, context, dry_run)
def append_line(self, line, **kwargs):
if self._text_path is not None:
raise ValueError('appendLine is not supported for text on a path')
···
def __init__(self, text, **kwargs):
super().__init__(**kwargs)
self.escaped_text = xml.escape(text)
553
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
363
+
def write_content(self, id_map, is_duplicate, output_file, context,
output_file.write(self.escaped_text)
···
return self.append('a', rx, ry, rot, int(bool(large_arc)),
int(bool(sweep)), ex, ey)
726
-
def arc(self, cx, cy, r, start_deg, end_deg, cw=False, include_m=True,
537
+
def arc(self, cx, cy, r, start_deg, end_deg, cw=True, include_m=True,
'''Draw a circular arc, controlled by center, radius, and start/end
542
+
Angles rotate from the x-axis towards the positive y-axis.
731
-
large_arc = (end_deg - start_deg) % 360 > 180
544
+
large_arc = ((end_deg - start_deg) % 360 <= 180) ^ cw
start_rad, end_rad = start_deg*math.pi/180, end_deg*math.pi/180
733
-
sx, sy = r*math.cos(start_rad), -r*math.sin(start_rad)
734
-
ex, ey = r*math.cos(end_rad), -r*math.sin(end_rad)
546
+
sx, sy = r*math.cos(start_rad), r*math.sin(start_rad)
547
+
ex, ey = r*math.cos(end_rad), r*math.sin(end_rad)
739
-
return self.A(r, r, 0, large_arc ^ cw, cw, cx+ex, cy+ey)
552
+
return self.A(r, r, 0, large_arc, cw, cx+ex, cy+ey)
'''A sequence of connected lines (or a polygon).