from pathlib import Path
from typing import List
from lxml import etree
from lxml.etree import _Element, fromstring
from shapeflow.core import RootException, get_logger
from shapeflow.design.render import save_svg
log = get_logger(__name__)
[docs]class DesignFileError(RootException):
pass
[docs]class Layer:
"""Represents a layer within an SVG file
"""
_root: _Element
_label: str
def __init__(self, root: _Element, label: str):
self._root = root
self._label = label
@property
def label(self):
return self._label
[docs] def hide(self) -> None:
"""Hide this layer
"""
self._root.attrib['style'] = 'display:none'
[docs] def show(self) -> None:
"""Show this layer
"""
self._root.attrib['style'] = 'display:inline'
[docs]class Peeler:
"""Renders an SVG file into multiple PNG files layer-per-layer.
"""
file: Path
"""Path to the SVG file
"""
G = "{http://www.w3.org/2000/svg}g"
LABEL = "{http://www.inkscape.org/namespaces/inkscape}label"
NAMEDVIEW = "{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview"
BG_COLOR = "pagecolor"
BG_OPACITY = "{http://www.inkscape.org/namespaces/inkscape}pageopacity"
_header: bytes
_root: _Element
_layers: List[Layer]
def __init__(self, file: Path):
self.file = file
try:
with open(file, 'r') as f:
svg = bytes(f.read(), encoding='UTF-8')
self._header = bytes(
svg.decode(encoding='UTF-8').split('<svg')[0],
encoding='UTF-8'
)
self._root = fromstring(svg)
self._set_background_to_white()
except Exception as e:
raise DesignFileError(f"Invalid SVG file: {file} ({e})")
self._get_layers()
[docs] def peel(self, dpi: int, to_dir: Path) -> None:
""""Peel" the layers
"""
log.info(f"Peeling {self.file} @ {dpi} DPI...")
if to_dir.is_dir():
for file in to_dir.iterdir():
file.unlink()
else:
to_dir.mkdir()
for layer in self._layers:
for hidden in self._layers:
hidden.hide()
layer.show()
self._save(to_dir / f"{layer.label}.png", dpi)
log.info(f"Done.")
def _set_background_to_white(self):
namedview = self._root.find(self.NAMEDVIEW)
namedview.set(self.BG_COLOR, '#ffffff')
namedview.set(self.BG_OPACITY, '1.0')
def _get_layers(self):
self._layers = []
for child in self._root.getchildren():
if child.tag == self.G and self.LABEL in child.attrib:
if child.attrib[self.LABEL][0] != "_":
self._layers.append(Layer(child, child.attrib[self.LABEL]))
def _as_svg(self) -> bytes:
return self._header + etree.tostring(self._root)
def _save(self, to: Path, dpi: int):
save_svg(self._as_svg(), dpi, to)