3 # A Diagram abstraction based on svgwrite
5 # Copyright (C) 2018 Antonio Ospite <ao2@ao2.it>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 from math import degrees
23 from svgwrite.data.types import SVGAttribute
24 from .diagram import Diagram
27 class InkscapeDrawing(svgwrite.Drawing):
28 """An svgwrite.Drawing subclass which supports Inkscape layers"""
29 def __init__(self, *args, **kwargs):
30 super(InkscapeDrawing, self).__init__(*args, **kwargs)
32 inkscape_attributes = {
33 'xmlns:inkscape': SVGAttribute('xmlns:inkscape',
36 const=frozenset(['http://www.inkscape.org/namespaces/inkscape'])),
37 'inkscape:groupmode': SVGAttribute('inkscape:groupmode',
40 const=frozenset(['layer'])),
41 'inkscape:label': SVGAttribute('inkscape:label',
43 types=frozenset(['string']),
47 self.validator.attributes.update(inkscape_attributes)
49 elements = self.validator.elements
51 svg_attributes = set(elements['svg'].valid_attributes)
52 svg_attributes.add('xmlns:inkscape')
53 elements['svg'].valid_attributes = frozenset(svg_attributes)
55 g_attributes = set(elements['g'].valid_attributes)
56 g_attributes.add('inkscape:groupmode')
57 g_attributes.add('inkscape:label')
58 elements['g'].valid_attributes = frozenset(g_attributes)
60 self['xmlns:inkscape'] = 'http://www.inkscape.org/namespaces/inkscape'
62 def layer(self, **kwargs):
63 """Create an inkscape layer.
65 An optional 'label' keyword argument can be passed to set a user
66 friendly name for the layer."""
67 label = kwargs.pop('label', None)
69 new_layer = self.g(**kwargs)
70 new_layer['inkscape:groupmode'] = 'layer'
73 new_layer['inkscape:label'] = label
78 class SvgwriteDiagram(Diagram):
79 def __init__(self, width, height, **kwargs):
80 super(SvgwriteDiagram, self).__init__(width, height, **kwargs)
82 self.svg = InkscapeDrawing(None, profile='full', size=(str(width) + "px", str(height) + "px"))
83 self.active_group = self.svg
86 # Reset the SVG object
87 self.svg.elements = []
88 self.svg.add(self.svg.defs)
90 rect = self.svg.rect((0, 0), ('100%', '100%'))
91 self._fill(rect, self.background)
94 def save_svg(self, filename):
95 self.svg.saveas(filename)
97 def add(self, element):
98 self.active_group.add(element)
100 def color_to_rgba(self, color):
101 color = super(SvgwriteDiagram, self).color_to_rgba(color)
103 return color[0] * 255, color[1] * 255, color[2] * 255, color[3]
105 def _fill(self, element, fill_color):
107 r, g, b, a = self.color_to_rgba(fill_color)
108 fill_color = svgwrite.utils.rgb(r, g, b, mode='RGB')
109 element['fill'] = fill_color
110 element['fill-opacity'] = a
112 element['fill'] = 'none'
114 def _stroke(self, element, stroke_color):
116 r, g, b, a = self.color_to_rgba(stroke_color)
117 stroke_color = svgwrite.utils.rgb(r, g, b, mode='RGB')
118 element['stroke'] = stroke_color
119 element['stroke-opacity'] = a
120 element['stroke-linejoin'] = 'round'
121 element['stroke-width'] = self.stroke_width
123 element['stroke'] = 'none'
125 def draw_polygon_by_verts(self, verts,
126 stroke_color=(0, 0, 0),
128 polygon = self.svg.polygon(verts)
130 self._fill(polygon, fill_color)
131 self._stroke(polygon, stroke_color)
135 def draw_star_by_verts(self, cx, cy, verts, stroke_color=(0, 0, 0)):
137 line = self.svg.line((cx, cy), v)
138 self._stroke(line, stroke_color)
141 def draw_circle(self, cx, cy, radius=10.0,
143 fill_color=(0, 0, 0, 0.5)):
144 circle = self.svg.circle((cx, cy), radius)
146 self._fill(circle, fill_color)
147 self._stroke(circle, stroke_color)
151 def draw_line(self, x1, y1, x2, y2, stroke_color=(0, 0, 0, 1)):
152 line = self.svg.line((x1, y1), (x1, y2))
153 self._stroke(line, stroke_color)
157 def draw_rect(self, x, y, width, height, theta=0,
159 fill_color=(1, 1, 1, 0.8)):
160 rect = self.svg.rect((x, y), (width, height))
162 rect['transform'] = 'rotate(%f, %f, %f)' % (degrees(theta), x, y)
164 self._fill(rect, fill_color)
165 self._stroke(rect, stroke_color)
169 def draw_centered_text(self, cx, cy, text, theta=0.0,
171 align_baseline=False,
172 bb_stroke_color=None,
175 # Using font_size to calculate dy is not optimal as the font _height_ may
176 # be different from the font_size, but it's better than nothing.
177 text_element = self.svg.text(text, x=[cx], y=[cy], dy=[self.font_size / 2.])
178 self._fill(text_element, color)
179 text_element['font-size'] = self.font_size
180 text_element['text-anchor'] = 'middle'
181 text_element['transform'] = 'rotate(%f, %f, %f)' % (degrees(theta), cx, cy)
182 self.add(text_element)
185 warnings.warn("The align_baseline option has not been implemented yet.")
187 if bb_stroke_color or bb_fill_color:
188 warnings.warn("Drawing the bounding box has not been implemented yet.")