Source code for knittingpattern.convert.InstructionToSVG

"""This module maps instructions to SVG.

Use :func:`default_instructions_to_svg` to load the svg files provided by
this package.
"""
import os
import xmltodict
from knittingpattern.Loader import PathLoader

#:  The string to replace with the pattern name in the SVG file.
REPLACE_IN_DEFAULT_SVG = "{instruction.type}"


[docs]class InstructionToSVG(object): """This class maps instructions to SVGs.""" @property def _loader_class(self): """:return: the loader to load svgs from different locations :rtype: knittingpattern.Loader.PathLoader .""" return PathLoader
[docs] def __init__(self): """create a InstructionToSVG object without arguments.""" self._instruction_type_to_file_content = {}
@property def load(self): """:return: a loader object that allows loading SVG files from various sources such as files and folders. :rtype: knittingpattern.Loader.PathLoader Examples: - ``instruction_to_svg.load.path(path)`` loads an SVG from a file named path - ``instruction_to_svg.load.folder(path)`` loads all SVG files for instructions in the folder recursively. If multiple files have the same name, the last occurrence is used. """ return self._loader_class(self._process_loaded_object) def _process_loaded_object(self, path): """process the :paramref:`path`. :param str path: the path to load an svg from """ file_name = os.path.basename(path) name = os.path.splitext(file_name)[0] with open(path) as file: string = file.read() self._instruction_type_to_file_content[name] = string
[docs] def instruction_to_svg_dict(self, instruction): """ :return: an xml-dictionary with the same content as :meth:`instruction_to_svg`. """ instruction_type = instruction.type if instruction_type in self._instruction_type_to_file_content: svg = self._instruction_type_to_file_content[instruction_type] return self._set_fills_in_color_layer(svg, instruction.hex_color) return self.default_instruction_to_svg_dict(instruction)
[docs] def instruction_to_svg(self, instruction): """:return: an SVG representing the instruction. The SVG file is determined by the type attribute of the instruction. An instruction of type ``"knit"`` is looked for in a file named ``"knit.svg"``. Every element inside a group labeled ``"color"`` of mode ``"layer"`` that has a ``"fill"`` style gets this fill replaced by the color of the instruction. Example of a recangle that gets filled like the instruction: .. code:: xml <g inkscape:label="color" inkscape:groupmode="layer"> <rect style="fill:#ff0000;fill-opacity:1;fill-rule:nonzero" id="rectangle1" width="10" height="10" x="0" y="0" /> </g> If nothing was loaded to display this instruction, a default image is be generated by :meth:`default_instruction_to_svg`. """ return xmltodict.unparse(self.instruction_to_svg_dict(instruction))
def _set_fills_in_color_layer(self, svg_string, color): """replaces fill colors in ``<g inkscape:label="color" inkscape:groupmode="layer">`` with :paramref:`color` :param color: a color fill the objects in the layer with """ structure = xmltodict.parse(svg_string) if color is None: return structure layers = structure["svg"]["g"] if not isinstance(layers, list): layers = [layers] for layer in layers: if not isinstance(layer, dict): continue if layer.get("@inkscape:label") == "color" and \ layer.get("@inkscape:groupmode") == "layer": for key, elements in layer.items(): if key.startswith("@") or key.startswith("#"): continue if not isinstance(elements, list): elements = [elements] for element in elements: style = element.get("@style", None) if style: style = style.split(";") processed_style = [] for style_element in style: if style_element.startswith("fill:"): style_element = "fill:" + color processed_style.append(style_element) style = ";".join(processed_style) element["@style"] = style return structure
[docs] def has_svg_for_instruction(self, instruction): """:return: whether there is an image for the instruction :rtype: bool This can be used before :meth:`instruction_to_svg` as it determines whether - the default value is used (:obj:`False`) - or there is a dedicated svg representation (:obj:`True`). """ instruction_type = instruction.type return instruction_type in self._instruction_type_to_file_content
[docs] def default_instruction_to_svg(self, instruction): """As :meth:`instruction_to_svg` but it only takes the ``default.svg`` file into account. In case no file is found for an instruction in :meth:`instruction_to_svg`, this method is used to determine the default svg for it. The content is created by replacing the text ``{instruction.type}`` in the whole svg file named ``default.svg``. If no file ``default.svg`` was loaded, an empty string is returned. """ svg_dict = self.default_instruction_to_svg_dict(instruction) return xmltodict.unparse(svg_dict)
[docs] def default_instruction_to_svg_dict(self, instruction): """Returns an xml-dictionary with the same content as :meth:`default_instruction_to_svg` If no file ``default.svg`` was loaded, an empty svg-dict is returned. """ instruction_type = instruction.type default_type = "default" rep_str = "{instruction.type}" if default_type not in self._instruction_type_to_file_content: return {"svg": ""} default_svg = self._instruction_type_to_file_content[default_type] default_svg = default_svg.replace(rep_str, instruction_type) colored_svg = self._set_fills_in_color_layer(default_svg, instruction.hex_color) return colored_svg
#: The name of the folder containing the svg files for the default #: instructions. DEFAULT_SVG_FOLDER = "instruction-svgs"
[docs]def default_instructions_to_svg(): """load the default set of svg files for instructions :return: the default svg files for the instructions in this package :rtype: knittingpattern.InstructionToSVG.InstructionToSVG """ instruction_to_svg = InstructionToSVG() instruction_to_svg.load.relative_folder(__name__, DEFAULT_SVG_FOLDER) return instruction_to_svg
__all__ = ["InstructionToSVG", "default_instructions_to_svg", "DEFAULT_SVG_FOLDER"]