map-machine/roentgen/extract_icon.py
2020-09-24 23:55:04 +03:00

124 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Extract icons from SVG file.
Author: Sergey Vartanov (me@enzet.ru).
"""
import re
import xml.dom.minidom
from typing import Dict
from xml.dom.minidom import Element, Node
import numpy as np
from svgwrite import Drawing
from roentgen import ui
DEFAULT_SHAPE_ID: str = "default"
DEFAULT_SMALL_SHAPE_ID: str = "default_small"
STANDARD_INKSCAPE_ID: str = "(path|rect)\\d*"
GRID_STEP: int = 16
class Icon:
"""
SVG icon path description.
"""
def __init__(self, path: str, offset: np.array, id_: str):
"""
:param path: SVG icon path
:param offset: vector that should be used to shift the path
:param id_: shape identifier
"""
assert path
self.path: str = path
self.offset: np.array = offset
self.id_: str = id_
def is_default(self) -> bool:
"""
Return true if icon is has a default shape that doesn't represent
anything.
"""
return self.id_ in [DEFAULT_SHAPE_ID, DEFAULT_SMALL_SHAPE_ID]
def get_path(self, svg: Drawing, point: np.array):
"""
Draw icon into SVG file.
:param svg: SVG file to draw to
:param point: icon position
"""
shift: np.array = self.offset + point
return svg.path(
d=self.path, transform=f"translate({shift[0]},{shift[1]})")
class IconExtractor:
"""
Extract icons from SVG file.
Icon is a single path with "id" attribute that aligned to 16×16 grid.
"""
def __init__(self, svg_file_name: str):
"""
:param svg_file_name: input SVG file name with icons. File may contain
any other irrelevant graphics.
"""
self.icons: Dict[str, Icon] = {}
with open(svg_file_name) as input_file:
content = xml.dom.minidom.parse(input_file)
for element in content.childNodes:
if element.nodeName == "svg":
for node in element.childNodes: # type: Node
if isinstance(node, Element):
self.parse(node)
def parse(self, node: Element) -> None:
"""
Extract icon paths into a map.
:param node: XML node that contains icon
"""
if node.nodeName == "g":
for sub_node in node.childNodes:
if isinstance(sub_node, Element):
self.parse(sub_node)
return
if ("id" in node.attributes.keys() and
"d" in node.attributes.keys() and
node.attributes["id"].value):
path: str = node.attributes["d"].value
matcher = re.match("[Mm] ([0-9.e-]*)[, ]([0-9.e-]*)", path)
if not matcher:
ui.error(f"invalid path: {path}")
return
def get_offset(value: float):
""" Get negated icon offset from the origin. """
return -int(value / GRID_STEP) * GRID_STEP - GRID_STEP / 2
point: np.array = np.array((
get_offset(float(matcher.group(1))),
get_offset(float(matcher.group(2)))))
id_: str = node.attributes["id"].value
matcher = re.match(STANDARD_INKSCAPE_ID, id_)
if not matcher:
self.icons[id_] = Icon(node.attributes["d"].value, point, id_)
def get_path(self, id_: str) -> (Icon, bool):
"""
Get SVG path of the icon.
:param id_: string icon identifier
"""
if id_ in self.icons:
return self.icons[id_], True
ui.error(f"no such icon ID {id_}")
return self.icons[DEFAULT_SHAPE_ID], False