···
import xml.sax.saxutils as xml
-
from collections import defaultdict
-
from . import defs, url_encode
-
def write_xml_node_args(args, output_file, id_map=None):
-
for k, v in args.items():
-
if isinstance(v, DrawingElement):
-
if id_map and id(v) in id_map:
-
mapped_id = id_map[id(v)]
-
v = '#{}'.format(mapped_id)
-
v = 'url(#{})'.format(mapped_id)
-
output_file.write(' {}="{}"'.format(k,v))
-
'''Base class for drawing elements.
-
Subclasses must implement write_svg_element.
-
def write_svg_element(self, id_map, is_duplicate, output_file, dry_run,
-
raise NotImplementedError('Abstract base class')
-
def get_svg_defs(self):
-
def get_linked_elems(self):
-
def write_svg_defs(self, id_map, is_duplicate, output_file, dry_run):
-
for defn in self.get_svg_defs():
-
defn.write_svg_defs(id_map, is_duplicate, output_file, dry_run)
-
defn.write_svg_element(
-
id_map, is_duplicate, output_file, dry_run, force_dup=True)
-
output_file.write('\n')
-
def __eq__(self, other):
-
class DrawingBasicElement(DrawingElement):
-
'''Base class for SVG drawing elements that are a single node with no child
-
def __init__(self, **args):
-
for k, v in args.items():
-
k = k.replace('__', ':')
-
k = k.replace('_', '-')
-
self.ordered_children = defaultdict(list)
-
def check_children_allowed(self):
-
if not self.has_content:
-
'{} does not support children'.format(type(self)))
-
def all_children(self):
-
'''Return self.children and self.ordered_children as a single list.'''
-
output = list(self.children)
-
for z in sorted(self.ordered_children):
-
output.extend(self.ordered_children[z])
-
return self.args.get('id', None)
-
def write_svg_element(self, id_map, is_duplicate, output_file, dry_run,
-
children = self.all_children()
-
if is_duplicate(self) and self.id is None:
-
for elem in self.get_linked_elems():
-
self.write_content(id_map, is_duplicate, output_file, dry_run)
-
self.write_children_content(
-
id_map, is_duplicate, output_file, dry_run)
-
if is_duplicate(self) and not force_dup:
-
if id_map and id(self) in id_map:
-
mapped_id = id_map[id(self)]
-
output_file.write('<use xlink:href="#{}" />'.format(mapped_id))
-
output_file.write(self.TAG_NAME)
-
override_args = self.args
-
override_args = dict(override_args)
-
override_args['id'] = id_map[id(self)]
-
write_xml_node_args(override_args, output_file, id_map)
-
if not self.has_content and not children:
-
output_file.write(' />')
-
self.write_content(id_map, is_duplicate, output_file, dry_run)
-
self.write_children_content(
-
id_map, is_duplicate, output_file, dry_run)
-
output_file.write('</')
-
output_file.write(self.TAG_NAME)
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
-
'''Override in a subclass to add data between the start and end tags.
-
This will not be called if has_content is False.
-
raise RuntimeError('This element has no content')
-
def write_children_content(self, id_map, is_duplicate, output_file,
-
'''Override in a subclass to add data between the start and end tags.
-
This will not be called if has_content is False.
-
children = self.all_children()
-
child.write_svg_element(
-
id_map, is_duplicate, output_file, dry_run)
-
output_file.write('\n')
-
child.write_svg_element(id_map, is_duplicate, output_file, dry_run)
-
output_file.write('\n')
-
def get_svg_defs(self):
-
return [v for v in self.args.values()
-
if isinstance(v, DrawingElement)]
-
def write_svg_defs(self, id_map, is_duplicate, output_file, dry_run):
-
super().write_svg_defs(id_map, is_duplicate, output_file, dry_run)
-
for child in self.all_children():
-
child.write_svg_defs(id_map, is_duplicate, output_file, dry_run)
-
def __eq__(self, other):
-
if isinstance(other, type(self)):
-
return (self.TAG_NAME == other.TAG_NAME and
-
self.args == other.args and
-
self.children == other.children and
-
self.ordered_children == other.ordered_children)
-
def append_anim(self, animate_element):
-
self.children.append(animate_element)
-
def extend_anim(self, animate_iterable):
-
self.children.extend(animate_iterable)
-
def append_title(self, text, **kwargs):
-
self.children.append(Title(text, **kwargs))
-
class DrawingParentElement(DrawingBasicElement):
-
'''Base class for SVG elements that can have child nodes.'''
-
def __init__(self, children=(), ordered_children=None, **args):
-
super().__init__(**args)
-
self.children = list(children)
-
self.ordered_children.update(
-
(z, list(v)) for z, v in ordered_children.items())
-
if self.children or self.ordered_children:
-
self.check_children_allowed()
-
def draw(self, obj, *, z=None, **kwargs):
-
if not hasattr(obj, 'write_svg_element'):
-
elements = obj.to_drawables(**kwargs)
-
assert len(kwargs) == 0
-
if hasattr(elements, 'write_svg_element'):
-
self.append(elements, z=z)
-
self.extend(elements, z=z)
-
def append(self, element, *, z=None):
-
self.check_children_allowed()
-
self.ordered_children[z].append(element)
-
self.children.append(element)
-
def extend(self, iterable, *, z=None):
-
self.check_children_allowed()
-
self.ordered_children[z].extend(iterable)
-
self.children.extend(iterable)
-
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,
if isinstance(other, type(self)):
···
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
output_file.write(self.content)
···
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
output_file.write(self.escaped_text)
-
def write_children_content(self, id_map, is_duplicate, output_file,
children = self.all_children()
-
child.write_svg_element(id_map, is_duplicate, output_file, 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)
-
def write_content(self, id_map, is_duplicate, output_file, dry_run):
output_file.write(self.escaped_text)
···
return self.append('a', rx, ry, rot, int(bool(large_arc)),
int(bool(sweep)), ex, ey)
-
def arc(self, cx, cy, r, start_deg, end_deg, cw=False, include_m=True,
'''Draw a circular arc, controlled by center, radius, and start/end
-
large_arc = (end_deg - start_deg) % 360 > 180
start_rad, end_rad = start_deg*math.pi/180, end_deg*math.pi/180
-
sx, sy = r*math.cos(start_rad), -r*math.sin(start_rad)
-
ex, ey = r*math.cos(end_rad), -r*math.sin(end_rad)
-
return self.A(r, r, 0, large_arc ^ cw, cw, cx+ex, cy+ey)
'''A sequence of connected lines (or a polygon).
···
import xml.sax.saxutils as xml
+
from . import url_encode
+
from .types import DrawingElement, DrawingBasicElement, DrawingParentElement
class NoElement(DrawingElement):
''' A drawing element that has no effect '''
def write_svg_element(self, id_map, is_duplicate, output_file, dry_run,
+
context, force_dup=False):
if isinstance(other, type(self)):
···
+
def write_content(self, id_map, is_duplicate, output_file, context,
output_file.write(self.content)
···
+
def write_content(self, id_map, is_duplicate, output_file, context,
output_file.write(self.escaped_text)
+
def write_children_content(self, id_map, is_duplicate, output_file, context,
children = self.all_children()
+
child.write_svg_element(
+
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)
+
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)
+
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
+
Angles rotate from the x-axis towards the positive y-axis.
+
large_arc = ((end_deg - start_deg) % 360 <= 180) ^ cw
start_rad, end_rad = start_deg*math.pi/180, end_deg*math.pi/180
+
sx, sy = r*math.cos(start_rad), r*math.sin(start_rad)
+
ex, ey = r*math.cos(end_rad), r*math.sin(end_rad)
+
return self.A(r, r, 0, large_arc, cw, cx+ex, cy+ey)
'''A sequence of connected lines (or a polygon).