From 7fe0770b9d74a072bdccae449edc347819617231 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 17 Oct 2026 13:45:16 +0700 Subject: [PATCH] #617: point joins equal to the layer's no longer count as point styles (SVG keeps a plain stroke) --- crates/doc/src/vector.rs | 6 +++--- crates/pathops/src/points.rs | 3 +-- crates/render/src/draw.rs | 2 +- crates/svg/src/lib.rs | 2 +- crates/svg/src/tests.rs | 25 +++++++++++++++++++++++++ 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/crates/doc/src/vector.rs b/crates/doc/src/vector.rs index fd0a0f7e..1165bd60 100644 --- a/crates/doc/src/vector.rs +++ b/crates/doc/src/vector.rs @@ -468,9 +468,9 @@ impl VectorNetwork { start_dir(&self.outward(seg, at_a)?) } - /// Whether any point has a stroke cap or join of its own. - pub fn has_point_styles(&self) -> bool { - self.vertices.iter().any(|v| v.cap.is_some() || v.join.is_some()) + /// Whether any point has a stroke cap of its own or a join other than the layer's `layer_join`. + pub fn has_point_styles(&self, layer_join: StrokeJoin) -> bool { + self.vertices.iter().any(|v| v.cap.is_some() || v.join.is_some_and(|j| j != layer_join)) } /// The fill path: every region's loops. diff --git a/crates/pathops/src/points.rs b/crates/pathops/src/points.rs index 9c161b7c..05f58216 100644 --- a/crates/pathops/src/points.rs +++ b/crates/pathops/src/points.rs @@ -20,8 +20,7 @@ impl Styled { /// shortcut: dashed, variable-width and brush strokes keep the layer's caps and joins at every point; stroke them run by run too if asked for. /// Joins equal to the layer do not require styled runs; stored overrides remain unchanged. pub(crate) fn new(net: &VectorNetwork, props: &StrokeProps) -> Option { - let styled = net.vertices.iter().any(|v| v.cap.is_some() || v.join.is_some_and(|join| join != props.join)); - if !styled || !props.dashes.is_empty() || props.profile().is_some() || props.brush.is_some() { + if !net.has_point_styles(props.join) || !props.dashes.is_empty() || props.profile().is_some() || props.brush.is_some() { return None; } let mut net = net.rounded().unwrap_or_else(|| net.clone()); diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index 7bfafd33..021af92d 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -155,7 +155,7 @@ pub fn stroke_area(n: &Node, path: &BezPath, closed: bool) -> Arc { props.dashes.len().hash(&mut h); // Points' own caps and joins (vector networks). let net = match &n.kind { - NodeKind::Vector { network } if network.has_point_styles() => Some(network), + NodeKind::Vector { network } if network.has_point_styles(props.join) => Some(network), _ => None, }; for v in net.map(|n| n.vertices.as_slice()).unwrap_or_default() { diff --git a/crates/svg/src/lib.rs b/crates/svg/src/lib.rs index a6813017..1433b942 100644 --- a/crates/svg/src/lib.rs +++ b/crates/svg/src/lib.rs @@ -638,7 +638,7 @@ impl W<'_> { || k.brush.is_some() || k.cap_start.is_arrow() || k.cap_end.is_arrow() - || matches!(&n.kind, NodeKind::Vector { network } if network.has_point_styles()) + || matches!(&n.kind, NodeKind::Vector { network } if network.has_point_styles(k.join)) || !(w.is_finite() && w > 0.0) { return None; diff --git a/crates/svg/src/tests.rs b/crates/svg/src/tests.rs index b15cd999..5a333d07 100644 --- a/crates/svg/src/tests.rs +++ b/crates/svg/src/tests.rs @@ -115,3 +115,28 @@ fn text_run_fill_survives_live_text_export() { assert!(svg.contains("#000000"), "{svg}"); assert!(svg.contains("fill=\"#00FF00\"") && svg.contains(">b") && svg.contains(">a"), "{svg}"); } + +#[test] +fn point_joins_equal_to_the_layer_join_keep_a_plain_stroke() { + use figcraft_doc::{StrokeJoin, VectorNetwork}; + use figcraft_geom::kurbo::Point; + let svg_with = |join: StrokeJoin| { + let mut network = VectorNetwork::from_points(&[Point::new(0.0, 0.0), Point::new(50.0, 50.0), Point::new(100.0, 0.0)], false); + for v in &mut network.vertices { + v.join = Some(StrokeJoin::Miter); + } + if let Some(v) = network.vertices.get_mut(1) { + v.join = Some(join); + } + let mut d = Document::new("t"); + let p = d.first_page().unwrap(); + let v = d.create(p, NodeKind::Vector { network }, "V", 0.0, 0.0, 100.0, 50.0).unwrap(); + let n = d.get_mut(v).unwrap(); + n.strokes = vec![Paint::solid(Color::from_hex("000000").unwrap())]; + n.stroke.weight = 2.0; + n.stroke.join = StrokeJoin::Miter; + export_svg(&d, &[v], &SvgOptions::default()).unwrap() + }; + assert!(svg_with(StrokeJoin::Miter).contains("stroke-width=\"2\""), "equal joins stay a stroke"); + assert!(!svg_with(StrokeJoin::Round).contains("stroke-width="), "a differing join is drawn as an outline"); +}