"paint-first" not working
Closed this issue · 3 comments
I don't know if it is a bug or a feature request:
I have tried "paint-order" and "paint_order" for arguments I want to use for... paint... order... and well... it doesn't appear to work during the rasterization process. See the below animations. I increase the stroke width from each image (by what's supposed to be 1/10 of a px).
versus what should be the corresponding SVGs below
So far I really like this library. Is there any potential support for this (if so, any idea of the time frame), or is it a bug? Any other potential ideas for how I could accomplish the same effect as a stroke width, paint-fill-first paths without "paint-first" if it's not going to be supported?
EDIT - I'll add some more code to help make it reproducible:
import xml.etree.ElementTree as et
from pathlib import Path
import re
from svgpathtools import parse_path
class SVG:
def __init__(self, filename, used_tags=None):
self.svg = Path(filename)
self._used_tags = used_tags
if not self.svg.exists():
raise FileNotFound("SVG file not found.")
self.elements_root = self.populate()
self.paths = self._collect_map_nodes_with_paths()
@staticmethod
def get_tag_namespace(element):
m = re.match(r'\{.*\}', element.tag)
return m.group(0) if m else ''
@staticmethod
def get_tag_name(element):
return SVG._remove_namespace(element)
@staticmethod
def _remove_namespace(element):
return element.tag.removeprefix(SVG.get_tag_namespace(element))
def _element_generator(self):
it = et.iterparse(self.svg, events=('start', 'end'))
for evt, el in it:
# if evt == 'start':
check_list = []
if self._used_tags is not None:
check_list = [1 for e in self._used_tags if e in SVG.get_tag_name(el)]
if self._used_tags is None or len(check_list) > 0:
yield el
def populate(self):
_map = None
stack = []
for element in self._element_generator():
styles = element.attrib.get("style", None)
if styles:
styles = styles.strip().split(";")
styles = styles[:-1] if len(styles) % 2 == 1 else styles
for style in styles:
k, v = style.strip().split(":")
element.attrib[k.strip()] = v.strip()
del element.attrib["style"]
if _map is None:
_map = Node(self, element)
stack.append(_map)
continue
if len(stack) > 0:
# check if the elements are closing
# <path> </path> for instance
if element == stack[-1].element_tree_node:
child = stack.pop()
if child is not _map:
stack[-1].children.append(child)
# else just add the element to the map
else:
node = Node(self, element, parent=stack[-1])
stack.append(node)
return _map
def _collect_map_nodes_with_paths(self, node=None):
node = node if node is not None else self.elements_root
paths = []
stack = [node]
while len(stack) > 0:
node = stack.pop()
stack += node.children
if node.path:
paths.append(node)
return paths
class Node:
def __init__(self, _map, element_tree_node, parent=None, children=None):
self.root = _map
self.element_tree_node = element_tree_node
self.parent = parent
self.children = list() if children is None else children
self.lower_case_attributes()
d = element_tree_node.attrib.get("d", None)
if d:
self.path = parse_path(d)
self.bbox = BoundingBox(self.path)
def lower_case_attributes(self):
_attributes = self.element_tree_node.attrib
ignore_attributes = [
"d"
]
self.element_tree_node.attrib = {k.lower(): v.lower() if k.lower() not in ignore_attributes else v for k, v in _attributes.items()}
def rasterization_styles(self) -> dict:
ignore_attributes = [
"d"
]
return {k: v for k, v in self.element_tree_node.attrib.items() if k not in ignore_attributes}
class BoundingBox:
def __init__(self, path):
self.path = path
xmin, xmax, ymin, ymax = path.bbox()
self.xmax = xmax
self.ymax = ymax
self.xmin = xmin
self.ymin = ymin
self.width = self.xmax - self.xmin
self.height = self.ymax - self.ymin
self.center_point = (self.xmin + (self.width / 2), self.ymin + (self.height / 2))
if __name__ == "__main__":
import drawsvg as draw
import imageio.v2 as imageio
tags = ["path"]
svg = SVG("pc5dsvg_0_0.svg", used_tags=tags)
paths = svg.paths
for i, path in enumerate(paths):
attributes = path.rasterization_styles()
attributes['fill'] = f"red"
attributes['stroke'] = f"green"
attributes['paint-order'] = f"stroke"
attributes['paint_order'] = f"stroke"
images = []
print(attributes)
for j in range(40):
attributes['stroke-width'] = f"{j / 10}"
d = draw.Drawing(f"{path.bbox.width + (j / 10)}", f"{path.bbox.height + (j / 10)}")
d.set_pixel_scale(1)
d.view_box = (path.bbox.xmin - (j / 10 / 2), path.bbox.ymin - (j / 10 / 2), path.bbox.width + (j / 10), path.bbox.height + (j / 10))
p = draw.Path(**attributes)
p.args["d"] = path.element_tree_node.attrib.get("d")
d.append(p)
d.save_svg(f"{svg.svg.stem}_{i}_{j}.svg")
d.save_png(f"{svg.svg.stem}_{i}_{j}.png")
images.append(imageio.imread(f"{svg.svg.stem}_{i}_{j}.png"))
imageio.mimsave(f"{svg.svg.stem}_{i}.gif", images)
Everything below "name" is the code used to generate the gifs and related svgs
Thank you!
Hi! Thanks for reporting this. It looks like a limitation of the rendering backend. paint-order
is an SVG 2 feature but the current rendering backend is CairoSVG which only supports SVG 1.1.
I don't have the time right now to add any better backend to drawsvg but try out resvg or Inkscape (its command-line tool) and please share code snippets (or even a PR) if either works for you.
To keep things organized, I'm going to close this issue as a duplicate of #102.
By the way, drawsvg supports key-frame animations and exporting them to GIF if you want to simplify your code.
Hi! Thanks for reporting this. It looks like a limitation of the rendering backend.
paint-order
is an SVG 2 feature but the current rendering backend is CairoSVG which only supports SVG 1.1.I don't have the time right now to add any better backend to drawsvg but try out resvg or Inkscape (its command-line tool) and please share code snippets (or even a PR) if either works for you.
To keep things organized, I'm going to close this issue as a duplicate of #102.
By the way, drawsvg supports key-frame animations and exporting them to GIF if you want to simplify your code.
RIP! Thank you. So sad that this is a limitation; hopefully one day SVG 2 will be supported by CairoSVG. I was using Cairo, which is part of the reason why I changed to this, hoping for a "better" rasterizer (unknown to me at the time that Cairo was the driver and that the limitation was with SVG 1.1 and not that it was just garbage rasterizer). I'll try to keep you updated if I find a solution myself, but I am now looking for some SVG 2.0 rasterizer, soooo yah. Anyways, thanks again, and kudos to your incredible module!
@cduck I was able to get the proper output from resvg. I have other things grabbing my attention at this time, so I won't be implementing it into my project, yet, but when I do, I will try to throw in a patch for drawsvg. It wasn't anything super difficult; I just installed rust and installed resvg by downloading it from github, unzipping it, running cargo install on it, which then put the resvg cli on my path. Then I ran it...