#!/usr/bin/env python # # A Diagram abstraction based on svgwrite # # Copyright (C) 2018 Antonio Ospite # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import warnings from math import degrees import svgwrite from svgwrite.data.types import SVGAttribute from .diagram import Diagram class InkscapeDrawing(svgwrite.Drawing): """An svgwrite.Drawing subclass which supports Inkscape layers""" def __init__(self, *args, **kwargs): super(InkscapeDrawing, self).__init__(*args, **kwargs) inkscape_attributes = { 'xmlns:inkscape': SVGAttribute('xmlns:inkscape', anim=False, types=[], const=frozenset(['http://www.inkscape.org/namespaces/inkscape'])), 'inkscape:groupmode': SVGAttribute('inkscape:groupmode', anim=False, types=[], const=frozenset(['layer'])), 'inkscape:label': SVGAttribute('inkscape:label', anim=False, types=frozenset(['string']), const=[]) } self.validator.attributes.update(inkscape_attributes) elements = self.validator.elements svg_attributes = set(elements['svg'].valid_attributes) svg_attributes.add('xmlns:inkscape') elements['svg'].valid_attributes = frozenset(svg_attributes) g_attributes = set(elements['g'].valid_attributes) g_attributes.add('inkscape:groupmode') g_attributes.add('inkscape:label') elements['g'].valid_attributes = frozenset(g_attributes) self['xmlns:inkscape'] = 'http://www.inkscape.org/namespaces/inkscape' def layer(self, **kwargs): """Create an inkscape layer. An optional 'label' keyword argument can be passed to set a user friendly name for the layer.""" label = kwargs.pop('label', None) new_layer = self.g(**kwargs) new_layer['inkscape:groupmode'] = 'layer' if label: new_layer['inkscape:label'] = label return new_layer class SvgwriteDiagram(Diagram): def __init__(self, width, height, **kwargs): super(SvgwriteDiagram, self).__init__(width, height, **kwargs) self.svg = InkscapeDrawing(None, profile='full', size=(str(width) + "px", str(height) + "px")) self.active_group = self.svg def save_svg(self, filename): self.svg.saveas(filename) def add(self, element): self.active_group.add(element) def color_to_rgba(self, color): color = super(SvgwriteDiagram, self).color_to_rgba(color) return color[0] * 255, color[1] * 255, color[2] * 255, color[3] def _fill(self, element, fill_color): if fill_color: r, g, b, a = self.color_to_rgba(fill_color) fill_color = svgwrite.utils.rgb(r, g, b, mode='RGB') element['fill'] = fill_color element['fill-opacity'] = a else: element['fill'] = 'none' def _stroke(self, element, stroke_color): if stroke_color: r, g, b, a = self.color_to_rgba(stroke_color) stroke_color = svgwrite.utils.rgb(r, g, b, mode='RGB') element['stroke'] = stroke_color element['stroke-opacity'] = a element['stroke-linejoin'] = 'round' else: element['stroke'] = 'none' def draw_polygon_by_verts(self, verts, stroke_color=(0, 0, 0), fill_color=None): polygon = self.svg.polygon(verts, stroke_width=self.stroke_width) self._fill(polygon, fill_color) self._stroke(polygon, stroke_color) self.add(polygon) def draw_star_by_verts(self, cx, cy, verts, stroke_color=(0, 0, 0)): for v in verts: line = self.svg.line((cx, cy), v, stroke_width=self.stroke_width) self._stroke(line, stroke_color) self.add(line) def draw_centered_text(self, cx, cy, text, theta=0.0, color=(0, 0, 0), align_baseline=False, bb_stroke_color=None, bb_fill_color=None): # Using font_size to calculate dy is not optimal as the font _height_ may # be different from the font_size, but it's better than nothing. text_element = self.svg.text(text, x=[cx], y=[cy], dy=[self.font_size / 2.]) self._fill(text_element, color) text_element['font-size'] = self.font_size text_element['text-anchor'] = 'middle' text_element['transform'] = 'rotate(%f, %f, %f)' % (degrees(theta), cx, cy) self.add(text_element) if align_baseline: warnings.warn("The align_baseline option has not been implemented yet.") if bb_stroke_color or bb_fill_color: warnings.warn("Drawing the bounding box has not been implemented yet.")