Skip to content
Snippets Groups Projects

Add support for Artboards and Guides

Merged Manpreet Singh requested to merge artboards-and-guides into main
Files
16
+ 94
19
@@ -13,11 +13,18 @@ import inkex
import lxml.etree
from inkex.base import SvgOutputMixin
from inkaf.svg.fill import AFColor, AFGradient, AFGradientStop, AFGradientType
from inkaf.svg.fill import (
AFColor,
AFGradient,
AFGradientStop,
AFGradientType,
parse_fdsc,
)
from inkaf.svg.util import (
MAX_CLIP_PATH_RECURSION,
AFBoundingBox,
ClipPathRecursionError,
get_transformed_bbox,
interpolate_float,
)
@@ -65,17 +72,20 @@ class AFConverter:
self.afdoc = afdoc
self.doc: lxml.etree._ElementTree
self.document: inkex.SvgDocumentElement
self._page_label = ""
self._has_artboards = False
def convert(self) -> None:
self.doc = inkex.SvgDocumentElement = SvgOutputMixin.get_template(
width=self.afdoc["DocR"]["DfSz"][0],
height=self.afdoc["DocR"]["DfSz"][1],
width=self.afdoc["DocR"].get("DfSz", (100, 100))[0],
height=self.afdoc["DocR"].get("DfSz", (100, 100))[1],
unit="px",
)
self.document = self.doc.getroot()
root_chlds = self.afdoc["DocR"]["Chld"]
assert len(root_chlds) == 1
self._parse_doc(root_chlds[0])
root_chlds = self.afdoc["DocR"].get("Chld", [])
assert len(root_chlds) <= 1
if root_chlds:
self._parse_doc(root_chlds[0])
if "DCMD" in self.afdoc["DocR"] and "FlNm" in self.afdoc["DocR"]["DCMD"]:
title = self.afdoc["DocR"]["DCMD"]["FlNm"]
@@ -93,7 +103,67 @@ class AFConverter:
"viewBox",
f"{child['SprB'][0]} {child['SprB'][1]} {child['SprB'][2]} {child['SprB'][3]}",
)
self._parse_children(child["Chld"])
self._parse_children(child.get("Chld", []))
self._add_guides(child)
if self._has_artboards and self._page_label:
self.document.namedview.get_pages()[0].label = self._page_label
def _add_guides(
self, child: AFObject, page_offset: inkex.Vector2d = inkex.Vector2d()
) -> None:
v_guides: list[float] = child.get("GdsV", [])
h_guides: list[float] = child.get("GdsH", [])
for pos in v_guides:
self.document.namedview.add_guide(pos + page_offset.x, orient=False)
for pos in h_guides:
self.document.namedview.add_guide(pos + page_offset.y, orient=True)
# guide color and opacity
guide_color_af = self.afdoc["DocR"].get("GFil")
if guide_color_af is None:
return
guide_color = parse_fdsc(guide_color_af)
self.document.namedview.set("guidecolor", self._convert_fill(guide_color))
self.document.namedview.set(
"guideopacity",
(guide_color.alpha if isinstance(guide_color, AFColor) else 1.0),
)
def _add_artboard(self, child: AFObject) -> None:
assert child.get_type() in ["ShpN", "PCrv"], "Invalid artboard type"
bbox = AFBoundingBox(*child["ShpB" if child.get_type() == "ShpN" else "CvsB"])
if "Xfrm" in child:
bbox = get_transformed_bbox(bbox, parse_transform(child["Xfrm"]))
vx, vy, _, _ = self.document.get_viewbox() or [0, 0, 0, 0]
self._add_guides(child, inkex.Vector2d(bbox.x - vx, bbox.y - vy))
if not self._has_artboards:
self.document.set(
"viewBox", f"{bbox.x} {bbox.y} {bbox.width} {bbox.height}"
)
self.document.set("width", bbox.width)
self.document.set("height", bbox.height)
self._page_label = child.get("Desc", "Artboard1")
self._has_artboards = True
return
self.document.namedview.new_page(
f"{bbox.x - vx}",
f"{bbox.y - vy}",
f"{bbox.width}",
f"{bbox.height}",
label=child.get(
"Desc", f"Artboard{len(self.document.namedview._get_pages())}"
),
)
def _parse_children(
self,
@@ -117,10 +187,10 @@ class AFConverter:
path_effect_str = element.get("inkscape:path-effect", "")
if path_effect_str:
element.set(
"inkscape:path-effect", f"{path_effect_str},#{path_effect.get_id()}"
"inkscape:path-effect", f"{path_effect_str},{path_effect.get_id(1)}"
)
else:
element.set("inkscape:path-effect", f"#{path_effect.get_id()}")
element.set("inkscape:path-effect", f"{path_effect.get_id(1)}")
def _add_clip(
self, child: inkex.ShapeElement, parent: inkex.ShapeElement, href: bool = True
@@ -190,6 +260,9 @@ class AFConverter:
self, child: AFObject, parent: inkex.Group, parent_af: Optional[AFObject] = None
) -> None:
try:
if child.get("ABEn", False):
self._add_artboard(child)
svg = self._parse_child(child)
except Exception:
traceback.print_exc()
@@ -213,6 +286,7 @@ class AFConverter:
)
try:
self._add_common_properties(child, svg, parent_object)
self._add_layer_style(layer, child)
self._add_style(svg, child)
self._add_fx(layer, child)
self._set_blend_mode(layer, child)
@@ -225,19 +299,19 @@ class AFConverter:
# Vector Masks
masks = child.get("AdCh", [])
for mask in masks:
mask_svg = self._parse_child(mask)
self._add_common_properties(mask, mask_svg)
try:
mask_svg = self._parse_child(mask)
self._add_common_properties(mask, mask_svg)
self._add_clip(svg, mask_svg, href=False)
except ClipPathRecursionError:
print(
f"Warning: Max Vector Mask limit '{MAX_CLIP_PATH_RECURSION}' exceeded"
)
break
except Exception:
traceback.print_exc()
sub_children = child.get("Chld")
if sub_children is not None:
self._parse_children(sub_children, layer, child)
self._parse_children(child.get("Chld", []), layer, child)
def _convert_shape(self, child: AFShape) -> inkex.ShapeElement:
if isinstance(child, AFPointsShapeBase):
@@ -475,13 +549,14 @@ class AFConverter:
else:
raise NotImplementedError(f"Unknown alignment: {para_att.align}")
def _add_style(self, svg: inkex.ShapeElement, child: AFObject) -> None:
def _add_layer_style(self, svg: inkex.ShapeElement, child: AFObject) -> None:
# Visibility and Opacity
svg.style["visibility"] = (
"visible" if "Visi" not in child or child["Visi"] else "hidden"
)
svg.style["opacity"] = child["Opac"] if "Opac" in child else 1
# Opac -> layer opacity, FOpc -> filter opacity
if not child.get("Visi", True):
svg.style["display"] = "none"
svg.style["opacity"] = child.get("Opac", 1.0) * child.get("FOpc", 1.0)
def _add_style(self, svg: inkex.ShapeElement, child: AFObject) -> None:
fill = parse_fill(child)
self._apply_color(svg, True, fill)
Loading