mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-03 00:39:44 -06:00
Merge pull request #1184 from saturngod/fix/boolean-stroke-outlines
Fix stroke-only boolean operand outlines
This commit is contained in:
@@ -209,6 +209,18 @@ pub fn stroke_area(n: &Node, path: &BezPath, closed: bool) -> Arc<BezPath> {
|
||||
return Arc::new(o);
|
||||
}
|
||||
}
|
||||
cached_stroke_outline(n, path, closed)
|
||||
}
|
||||
|
||||
/// A non-zero stroke outline, shared by painting and boolean operands.
|
||||
fn cached_stroke_outline(n: &Node, path: &BezPath, closed: bool) -> Arc<BezPath> {
|
||||
let props = &n.stroke;
|
||||
if let Some(sides) = props.sides
|
||||
&& matches!(n.kind, NodeKind::Rectangle | NodeKind::Frame | NodeKind::Component { .. } | NodeKind::Instance { .. })
|
||||
&& n.corners.uniform() == Some(0.0)
|
||||
{
|
||||
return Arc::new(figcraft_pathops::rect_side_strokes(n.width, n.height, sides, props.align));
|
||||
}
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
hash_path(path, &mut h);
|
||||
(props.weight.to_bits(), props.align as u8, props.cap_start as u8, props.cap_end as u8, props.join as u8, props.miter_limit.to_bits(), closed)
|
||||
@@ -281,7 +293,7 @@ fn node_outline_at(doc: &Document, n: &Node, depth: usize) -> Option<(BezPath, F
|
||||
.filter_map(|c| doc.node(*c))
|
||||
.filter(|c| c.visible)
|
||||
.filter_map(|c| {
|
||||
let (mut p, _) = subtree_outline(doc, c, depth + 1)?;
|
||||
let (mut p, _) = subtree_outline_at(doc, c, depth + 1, true)?;
|
||||
p.apply_affine(c.transform);
|
||||
Some((p, c.bool_op.unwrap_or(*op)))
|
||||
})
|
||||
@@ -305,6 +317,11 @@ fn node_outline_at(doc: &Document, n: &Node, depth: usize) -> Option<(BezPath, F
|
||||
|
||||
/// The union outline of a node and its descendants (for booleans of groups/frames).
|
||||
pub fn subtree_outline(doc: &Document, n: &Node, depth: usize) -> Option<(BezPath, FillRule)> {
|
||||
subtree_outline_at(doc, n, depth, false)
|
||||
}
|
||||
|
||||
/// Boolean operands with only a stroke contribute the painted band, not their interior.
|
||||
fn subtree_outline_at(doc: &Document, n: &Node, depth: usize, stroke_only: bool) -> Option<(BezPath, FillRule)> {
|
||||
if depth > 64 {
|
||||
return None;
|
||||
}
|
||||
@@ -313,21 +330,32 @@ pub fn subtree_outline(doc: &Document, n: &Node, depth: usize) -> Option<(BezPat
|
||||
let mut out = BezPath::new();
|
||||
if n.kind.is_frame_like()
|
||||
&& let Some(p) = figcraft_doc::geometry::shape_path(n)
|
||||
&& n.fills.iter().any(|f| f.visible)
|
||||
{
|
||||
out.extend(p.elements().iter().copied());
|
||||
if n.fills.iter().any(|f| f.visible) {
|
||||
out.extend(p.elements().iter().copied());
|
||||
} else if stroke_only && n.has_visible_stroke() {
|
||||
out.extend(cached_stroke_outline(n, &p, true).elements().iter().copied());
|
||||
}
|
||||
}
|
||||
for c in n.children.iter().filter_map(|c| doc.node(*c)) {
|
||||
if !c.visible {
|
||||
continue;
|
||||
}
|
||||
if let Some((mut p, _)) = subtree_outline(doc, c, depth + 1) {
|
||||
if let Some((mut p, _)) = subtree_outline_at(doc, c, depth + 1, stroke_only) {
|
||||
p.apply_affine(c.transform);
|
||||
out.extend(p.elements().iter().copied());
|
||||
}
|
||||
}
|
||||
Some((out, FillRule::Winding))
|
||||
}
|
||||
_ if stroke_only && !n.fills.iter().any(|p| p.visible) && n.has_visible_stroke() => {
|
||||
let (path, _) = node_outline_at(doc, n, depth)?;
|
||||
let path = if matches!(n.kind, NodeKind::Text(_)) { path } else { figcraft_doc::geometry::stroke_geometry(n).unwrap_or(path) };
|
||||
let closed = figcraft_doc::geometry::is_closed(n) || matches!(n.kind, NodeKind::BooleanOp { .. });
|
||||
// Boolean folding uses non-zero winding; the canvas stroke cache also contains
|
||||
// even-odd rectangle rings, so use the winding-correct stroke outline here.
|
||||
Some(((*cached_stroke_outline(n, &path, closed)).clone(), FillRule::Winding))
|
||||
}
|
||||
_ => node_outline_at(doc, n, depth),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user