Source code for knittingpattern.convert.SVGBuilder

"""build SVG files


"""
import xmltodict

#: an empty svg file as a basis
SVG_FILE = """
<svg
   xmlns:ns="http://PURL.org/dc/elements/1.1/"
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>knittingpattern</title>
    <defs></defs>
</svg>
"""


[docs]class SVGBuilder(object): """This class builds an SVG to a file. The class itself does not know what the objects look like. It offers a more convinient interface to build SVG files. """
[docs] def __init__(self): """Initialize this object without arguments.""" self._structure = xmltodict.parse(SVG_FILE) self._layer_id_to_layer = {} self._svg = self._structure["svg"] self._min_x = None self._min_y = None self._max_x = None self._max_y = None
@property def bounding_box(self): """the bounding box of this SVG ``(min_x, min_y, max_x, max_y)``. .. code:: python svg_builder10x10.bounding_box = (0, 0, 10, 10) assert svg_builder10x10.bounding_box == (0, 0, 10, 10) ``viewBox``, ``width`` and ``height`` are computed from this. If the bounding box was never set, the result is a tuple of four :obj:`None`. """ return (self._min_x, self._min_y, self._max_x, self._max_y) @bounding_box.setter def bounding_box(self, bbox): min_x, min_y, max_x, max_y = bbox self._min_x = min_x self._min_y = min_y self._max_x = max_x self._max_y = max_y self._svg["@height"] = str(max_y - min_y) self._svg["@width"] = str(max_x - min_x) self._svg["@viewBox"] = "{} {} {} {}".format(min_x, min_y, max_x, max_y)
[docs] def place(self, x, y, svg, layer_id): """Place the :paramref:`svg` content at ``(x, y)`` position in the SVG, in a layer with the id :paramref:`layer_id`. :param float x: the x position of the svg :param float y: the y position of the svg :param str svg: the SVG to place at ``(x, y)`` :param str layer_id: the id of the layer that this :paramref:`svg` should be placed inside """ content = xmltodict.parse(svg) self.place_svg_dict(x, y, content, layer_id)
[docs] def place_svg_dict(self, x, y, svg_dict, layer_id, group=None): """Same as :meth:`place` but with a dictionary as :paramref:`svg_dict`. :param dict svg_dict: a dictionary returned by `xmltodict.parse() <https://github.com/martinblech/xmltodict>`__ :param dict group: a dictionary of values to add to the group the :paramref:`svg_dict` will be added to or :obj:`None` if nothing should be added """ if group is None: group = {} group_ = { "@transform": "translate({},{})".format(x, y), "g": list(svg_dict.values()) } group_.update(group) layer = self._get_layer(layer_id) layer["g"].append(group_)
[docs] def place_svg_use_coords(self, x, y, symbol_id, layer_id, group=None): """Similar to :meth:`place` but with an id as :paramref:`symbol_id`. :param str symbol_id: an id which identifies an svg object defined in the defs :param dict group: a dictionary of values to add to the group the use statement will be added to or :obj:`None` if nothing should be added """ if group is None: group = {} use = {"@x": x, "@y": y, "@xlink:href": "#{}".format(symbol_id)} group_ = {"use": use} group_.update(group) layer = self._get_layer(layer_id) layer["g"].append(group_)
[docs] def place_svg_use(self, symbol_id, layer_id, group=None): """Same as :meth:`place_svg_use_coords`. With implicit `x` and `y` which are set to `0` in this method and then :meth:`place_svg_use_coords` is called. """ self.place_svg_use_coords(0, 0, symbol_id, layer_id, group)
def _get_layer(self, layer_id): """ :return: the layer with the :paramref:`layer_id`. If the layer does not exist, it is created. :param str layer_id: the id of the layer """ if layer_id not in self._layer_id_to_layer: self._svg.setdefault("g", []) layer = { "g": [], "@inkscape:label": layer_id, "@id": layer_id, "@inkscape:groupmode": "layer", "@class": "row" } self._layer_id_to_layer[layer_id] = layer self._svg["g"].append(layer) return self._layer_id_to_layer[layer_id]
[docs] def insert_defs(self, defs): """Adds the defs to the SVG structure. :param defs: a list of SVG dictionaries, which contain the defs, which should be added to the SVG structure. """ if self._svg["defs"] is None: self._svg["defs"] = {} for def_ in defs: for key, value in def_.items(): if key.startswith("@"): continue if key not in self._svg["defs"]: self._svg["defs"][key] = [] if not isinstance(value, list): value = [value] self._svg["defs"][key].extend(value)
[docs] def get_svg_dict(self): """Return the SVG structure generated.""" return self._structure
[docs] def write_to_file(self, file): """Writes the current SVG to the :paramref:`file`. :param file: a file-like object """ xmltodict.unparse(self._structure, file, pretty=True)
__all__ = ["SVGBuilder", "SVG_FILE"]