//! The paths of long paths and compound paths kept by object id, with the boxes of their segments //! and of their miter tips by runs of segments. When an object's anchors move, the renderer changes //! only the path elements, boxes and tips of the segments that start or end at a moved anchor. //! When anchors are added or deleted, or a subpath is closed or opened, it replaces only the path //! elements of the segments between the first and the last anchor that changed, and measures again //! only the runs that hold them. When a compound path gains or loses members, or a member gains or //! loses subpaths, it makes the path of those members again. In each case it does not make the //! object's path again or measure it whole. use std::sync::Arc; use vectorcraft_doc::{LineJoin, Node, NodeKind, StrokeLayer}; use vectorcraft_geom::bez::has_length; use vectorcraft_geom::path::segment_bounds; use vectorcraft_geom::{Anchor, BezPath, PathData, PathEl, PathSeg, Point, Rect, SubPath}; /// Paths and compound paths with at least this many anchors are kept ([`KeptPath`]). pub(crate) const KEPT_ANCHORS: usize = 1024; /// Segments measured together into one box in a subpath measured whole. A run that anchors were /// added to or deleted from holds more or fewer, and one that grows past twice this is split. pub(crate) const RUN: usize = 128; /// The most subpaths one edit may add anchors to, delete anchors from, close or open: past this, /// the kept path is made again whole. const SPLICES: usize = 16; /// The boxes and the miter tips' boxes of a subpath's runs ([`KeptPath::runs`]). #[cfg(test)] pub(crate) type RunBoxes = (Vec>, Vec>); /// A subpath's MoveTo, segments and anchors, whether it is closed, and the segments of each of its /// runs ([`KeptPath::layout`]). #[cfg(test)] pub(crate) type Layout = ((usize, usize, usize, bool), Vec); /// A subpath of a kept path. struct Sub { /// Where its MoveTo is in the path's elements. start: usize, /// Its segments (as [`SubPath::segment_count`] counts them). segs: usize, anchors: usize, closed: bool, /// The segment after the last of each run of segments. ends: Vec, /// The box of each run of segments ([`segment_bounds`]). boxes: Vec>, /// The box of the miter tips of `tips_of` at the joints whose first segment is in each run. tips: Vec>, } /// The path of a long path or compound path ([`Node::stroke_path`]), with its geometric bounds by /// runs of segments and the tips of a miter-joined stroke along it by runs of joints. pub(crate) struct KeptPath { /// The object the path was made from (kept alive). pub(crate) node: Arc, pub(crate) path: Arc, /// The subpaths of each path of the object: the path itself, or each member of a compound path. parts: Vec>, /// The stroke the subpaths' tips were measured for. tips_of: Option, /// The last frame that used it. pub(crate) stamp: u64, } /// The paths of `n`: its own, or those of the members of a compound path whose members are all /// paths and that is not a Shaper group or an Image Trace object. `None` for anything else. fn paths(n: &Node) -> Option> { match &n.kind { NodeKind::Path { path, .. } => Some(vec![path]), NodeKind::Compound { children, .. } if n.extras().shaper.is_none() && n.extras().trace.is_none() => { children.iter().map(|c| c.path_data()).collect() } _ => None, } } /// Whether `n` has a path that is kept ([`KeptPath`]): a path or compound path with at least /// [`KEPT_ANCHORS`] anchors, none of whose closed subpaths starts at a point that is not a number /// (its path closes with an extra segment there). pub(crate) fn kept(n: &Node) -> bool { let Some(paths) = paths(n) else { return false }; let subs = || paths.iter().flat_map(|p| &p.subpaths); subs().map(|s| s.anchors.len()).sum::() >= KEPT_ANCHORS && subs().filter(|s| s.closed).all(|s| s.anchors.first().is_none_or(|a| !a.p.x.is_nan() && !a.p.y.is_nan())) } /// Whether anchors `a` and `b` have the same points, bit for bit. fn same(a: &Anchor, b: &Anchor) -> bool { same_drawn(a, b, true, true) } /// Whether anchors `a` and `b` have the same point, bit for bit, and the same incoming handle when /// `h_in` and outgoing handle when `h_out`. fn same_drawn(a: &Anchor, b: &Anchor, h_in: bool, h_out: bool) -> bool { let bits = |p: Point| [p.x, p.y].map(f64::to_bits); bits(a.p) == bits(b.p) && (!h_in || bits(a.h_in) == bits(b.h_in)) && (!h_out || bits(a.h_out) == bits(b.h_out)) } /// The path element of segment `i` of `sp`, as [`SubPath::to_bezpath_into`] writes it. fn element(sp: &SubPath, i: usize) -> PathEl { let c = sp.segment(i); if sp.segment_is_line(i) { PathEl::LineTo(c.p3) } else { PathEl::CurveTo(c.p1, c.p2, c.p3) } } /// Element `o` of the elements [`SubPath::to_bezpath_into`] writes for `sp`, a subpath with /// anchors: its MoveTo, then its segments, then its ClosePath. fn element_at(sp: &SubPath, o: usize) -> PathEl { match o { 0 => PathEl::MoveTo(sp.anchors.first().map_or(Point::ORIGIN, |a| a.p)), o if o <= sp.segment_count() => element(sp, o - 1), _ => PathEl::ClosePath, } } /// The point a path element ends at (`None` for a ClosePath). fn end(el: &PathEl) -> Option { match *el { PathEl::MoveTo(p) | PathEl::LineTo(p) | PathEl::QuadTo(_, p) | PathEl::CurveTo(_, _, p) => Some(p), PathEl::ClosePath => None, } } /// The ends of the runs of a subpath of `segs` segments measured whole: [`RUN`] segments each. fn whole_runs(segs: usize) -> Vec { (1..=segs.div_ceil(RUN)).map(|r| (r * RUN).min(segs)).collect() } /// The paths `ps` written over the `len` elements at `at` in `els`, and their subpaths measured /// whole with the tips of `st`. `at` moves past them. fn made_again(ps: &[&PathData], len: usize, els: &mut Vec, at: &mut usize, st: Option<&StrokeLayer>) -> Vec> { let mut bp = BezPath::new(); ps.iter().flat_map(|p| &p.subpaths).for_each(|sp| sp.to_bezpath_into(&mut bp)); let to = (*at + len).min(els.len()); els.splice((*at).min(to)..to, bp.into_elements()); let mut measure = |sp: &SubPath| { let sub = Sub::measured(sp, *at, els, st); *at += sub.len(); sub }; ps.iter().map(|p| p.subpaths.iter().map(&mut measure).collect()).collect() } impl Sub { /// Subpath `sp`, its MoveTo at `start` in `els`, measured whole, with the tips of `st`. fn measured(sp: &SubPath, start: usize, els: &[PathEl], st: Option<&StrokeLayer>) -> Self { let segs = sp.segment_count(); let mut sub = Sub { start, segs, anchors: sp.anchors.len(), closed: sp.closed, ends: whole_runs(segs), boxes: vec![], tips: vec![] }; sub.boxes = (0..sub.runs()).map(|r| sub.run_box(els, r)).collect(); if let Some(st) = st { sub.tips = (0..sub.runs()).map(|r| sub.run_tips(els, r, st)).collect(); } sub } /// Its elements in the path: none without anchors, otherwise its MoveTo, its segments and /// its ClosePath when closed. fn len(&self) -> usize { if self.anchors == 0 { 0 } else { 1 + self.segs + usize::from(self.closed) } } /// Segment `i`, as kurbo's `segments` makes it from the path's elements. fn seg(&self, els: &[PathEl], i: usize) -> Option { let p0 = end(els.get(self.start + i)?)?; Some(match *els.get(self.start + 1 + i)? { PathEl::LineTo(p) => PathSeg::Line(vectorcraft_geom::Line::new(p0, p)), PathEl::QuadTo(a, p) => PathSeg::Quad(vectorcraft_geom::kurbo::QuadBez::new(p0, a, p)), PathEl::CurveTo(a, b, p) => PathSeg::Cubic(vectorcraft_geom::CubicBez::new(p0, a, b, p)), PathEl::MoveTo(_) | PathEl::ClosePath => return None, }) } fn runs(&self) -> usize { self.ends.len() } /// The segments of run `r`. fn range(&self, r: usize) -> std::ops::Range { let from = r.checked_sub(1).and_then(|p| self.ends.get(p)).copied().unwrap_or(0); from..self.ends.get(r).copied().unwrap_or(from) } /// The run that holds segment `i` (the last run for a segment past the end). fn run_of(&self, i: usize) -> usize { self.ends.partition_point(|&e| e <= i).min(self.runs().saturating_sub(1)) } /// The box of the segments of run `r`. fn run_box(&self, els: &[PathEl], r: usize) -> Option { self.range(r).filter_map(|i| self.seg(els, i)).map(segment_bounds).reduce(|a, b| a.union(b)) } /// The segment with length that follows segment `i` (round to the start when closed), or /// `None` when there is none other than `i`. fn next_drawn(&self, els: &[PathEl], i: usize) -> Option { let after = i + 1..self.segs; let round = if self.closed { 0..i } else { 0..0 }; after.chain(round).filter_map(|j| self.seg(els, j)).find(has_length) } /// The segment with length before segment `i` (round from the end when closed), when it is /// within [`RUN`] segments of `i`: `Ok(None)` when there is none, `Err(())` when the search /// gave up. fn prev_drawn(&self, els: &[PathEl], i: usize) -> Result, ()> { let before = (0..i).rev(); let round = if self.closed { (i + 1..self.segs).rev() } else { (0..0).rev() }; for (steps, j) in before.chain(round).enumerate() { if steps >= RUN { return Err(()); } if self.seg(els, j).is_some_and(|s| has_length(&s)) { return Ok(Some(j)); } } Ok(None) } /// The box of the miter tips of `st` at the joints whose first segment is in run `r` /// ([`StrokeLayer::miter_tip`]). fn run_tips(&self, els: &[PathEl], r: usize, st: &StrokeLayer) -> Option { let mut out: Option = None; for i in self.range(r) { let Some(a) = self.seg(els, i).filter(has_length) else { continue }; let Some(b) = self.next_drawn(els, i) else { continue }; if let Some(tip) = st.miter_tip(self.closed, &a, &b) { out = Some(out.map_or(Rect::from_points(tip, tip), |o| o.union_pt(tip))); } } out } /// Bring the subpath, its MoveTo now at `at` in `els`, up to date from `was` to `now`, with the /// tips of `st`. fn update(&mut self, was: &SubPath, now: &SubPath, at: usize, els: &mut Vec, st: Option<&StrokeLayer>) { self.start = at; if was.anchors.len() == now.anchors.len() && was.closed == now.closed { self.moved(was, now, els, st); } else if was.anchors.len() < 2 || now.anchors.len() < 2 { self.remade(now, at, self.len(), els, st); } else { let mut span = self.len(); if self.spliced(was, now, els, st, &mut span).is_none() { self.remade(now, at, span, els, st); } } } /// Write the elements of `now` over the `span` elements at `at` in `els` that the subpath holds, /// and measure it whole with the tips of `st`. fn remade(&mut self, now: &SubPath, at: usize, span: usize, els: &mut Vec, st: Option<&StrokeLayer>) { let to = at.saturating_add(span).min(els.len()); let len = if now.anchors.is_empty() { 0 } else { 1 + now.segment_count() + usize::from(now.closed) }; els.splice(at.min(to)..to, (0..len).map(|o| element_at(now, o))); *self = Sub::measured(now, at, els, st); } /// [`Self::update`] for a subpath with the same anchor count and closed state: the elements of /// the segments that start or end at an anchor that moved are made again, with the boxes of /// their runs and the tips of those runs and of the runs of the segments with length before /// them. fn moved(&mut self, was: &SubPath, now: &SubPath, els: &mut [PathEl], st: Option<&StrokeLayer>) { let (mut first, mut segs) = (false, vec![]); for (k, (a, b)) in was.anchors.iter().zip(&now.anchors).enumerate() { if same(a, b) { continue; } if k >= 1 && k - 1 < self.segs { segs.push(k - 1); } if k == 0 { first = true; if self.closed && self.segs > 0 { segs.push(self.segs - 1); } } if k < self.segs { segs.push(k); } } if !first && segs.is_empty() { return; } segs.sort_unstable(); segs.dedup(); if first && let (Some(a), Some(el)) = (now.anchors.first(), els.get_mut(self.start)) { *el = PathEl::MoveTo(a.p); } for &i in &segs { if let Some(el) = els.get_mut(self.start + 1 + i) { *el = element(now, i); } } let els: &[PathEl] = els; // Many moved: the subpath is measured again whole. let all = segs.len() * 4 > self.segs; let mut boxes: Vec = if all { (0..self.runs()).collect() } else { segs.iter().map(|&i| self.run_of(i)).collect() }; boxes.dedup(); for r in boxes { let b = self.run_box(els, r); if let Some(slot) = self.boxes.get_mut(r) { *slot = b; } } let Some(st) = st else { return }; // A joint's tip changes with its segments and with which segment with length comes next: // the runs of the moved segments and of the segments with length before them. let mut runs = vec![]; let mut whole = all; for &i in &segs { runs.push(self.run_of(i)); match self.prev_drawn(els, i) { Ok(Some(j)) => runs.push(self.run_of(j)), Ok(None) => {} Err(()) => whole = true, } } self.measure_tips(els, runs, whole, st); } /// [`Self::update`] for a subpath of at least two anchors before and after that gained or lost /// anchors or was closed or opened. Its elements from the first anchor that changed to the /// last are written again, and the closing segment and the ClosePath when the first or last /// anchor changed or it was closed or opened; the elements before and after stay. The runs /// that held the segments written are measured again, with the tips of the runs of the /// segments with length before them. `span` is the count of elements the subpath holds in /// `els`, kept up to date as they are written. `None` when the segments and runs kept do not /// fit `was`: the caller then makes the subpath again from those `span` elements. fn spliced(&mut self, was: &SubPath, now: &SubPath, els: &mut Vec, st: Option<&StrokeLayer>, span: &mut usize) -> Option<()> { let (n0, n1) = (was.anchors.len(), now.anchors.len()); let least = n0.min(n1); // On a subpath open before and after, the handle before the first anchor and the one after // the last draw nothing. let open = !was.closed && !now.closed; let a = was.anchors.iter().zip(&now.anchors).enumerate().take_while(|(i, (x, y))| same_drawn(x, y, !open || *i > 0, true)).count().min(least); let b = was .anchors .iter() .rev() .zip(now.anchors.iter().rev()) .take(least - a) .enumerate() .take_while(|(i, (x, y))| same_drawn(x, y, true, !open || *i > 0)) .count(); let (s0, s1) = (self.segs, now.segment_count()); let (c0, c1) = (self.closed, now.closed); let (len0, len1) = (1 + s0 + usize::from(c0), 1 + s1 + usize::from(c1)); // Elements `a..mid` change: the MoveTo when the first anchor changed and the segments up // to the first of those between the last `b` anchors. let (mid0, mid1) = ((1 + n0.checked_sub(b)?).min(1 + s0), (1 + n1.checked_sub(b)?).min(1 + s1)); // Elements `tail..len` follow the segments between the last `b` anchors: the closing // segment and the ClosePath. They stay when the anchors either side of the closing segment // stayed and the subpath was neither closed nor opened. let (tail0, tail1) = if b >= 1 { (n0, n1) } else { (mid0, mid1) }; let keep_tail = c0 == c1 && (!c1 || b == 0 || a >= 1); // The `old` segments from segment `lo` become `new` segments, and when the tail is written, // the segments from the one before it do too. let lo = a.saturating_sub(1); let (old, new) = (mid0.checked_sub(1)?.checked_sub(lo)?, mid1.checked_sub(1)?.checked_sub(lo)?); let tail = match keep_tail { true => None, false => Some((tail0.checked_sub(1)?, (1 + s0).saturating_sub(tail0), (1 + s1).saturating_sub(tail1), tail1.checked_sub(1)?)), }; let at = self.start; let mut stale = vec![false; self.runs()]; let tips = st.is_some(); // Writes elements `e0..e1` of `now` over elements `o0..o1` of the subpath and updates `span`. let mut write = |els: &mut Vec, (o0, o1): (usize, usize), (e0, e1): (usize, usize)| { let to = at.saturating_add(o1).min(els.len()); let from = at.saturating_add(o0).min(to); els.splice(from..to, (e0..e1).map(|o| element_at(now, o))); *span = from.saturating_sub(at) + e1.saturating_sub(e0) + at.saturating_add(*span).saturating_sub(to); }; if let Some((lo, old, new, _)) = tail { self.resize(lo, old, new, &mut stale, tips)?; write(els, (tail0, len0), (tail1, len1)); } self.resize(lo, old, new, &mut stale, tips)?; write(els, (a, mid0), (a, mid1)); self.anchors = n1; self.closed = c1; let els: &[PathEl] = els; for (r, _) in stale.iter().enumerate().filter(|(_, s)| **s) { let b = self.run_box(els, r); if let Some(slot) = self.boxes.get_mut(r) { *slot = b; } } let Some(st) = st else { return Some(()) }; let mut runs: Vec = stale.iter().enumerate().filter(|(_, s)| **s).map(|(r, _)| r).collect(); // Closed or opened: the tips of every joint change when the stroke reaches farther from one // than from the other. let mut whole = c0 != c1 && st.side_reach(c0).to_bits() != st.side_reach(c1).to_bits(); for i in [lo].into_iter().chain(tail.map(|(.., last)| last)) { match self.prev_drawn(els, i) { Ok(Some(j)) => runs.push(self.run_of(j)), Ok(None) => {} Err(()) => whole = true, } } self.measure_tips(els, runs, whole, st); Some(()) } /// Replace the `old` segments from segment `lo` with `new` segments in the runs: the runs that /// held them become one run, split into runs of [`RUN`] segments when longer than twice that, /// whose boxes and (with `tips`) tips are marked in `stale` to be measured again. `None`, and /// nothing changed, when the runs kept do not hold those `old` segments. fn resize(&mut self, lo: usize, old: usize, new: usize, stale: &mut Vec, tips: bool) -> Option<()> { if old == 0 && new == 0 { return Some(()); } let (r0, r1, first, last) = match self.runs() { 0 => (0, 0, 0, new), _ => { let r0 = self.run_of(lo); let r1 = if old == 0 { r0 } else { self.run_of(lo + old - 1) }; let last = self.ends.get(r1).copied().unwrap_or(lo + old).checked_add(new)?.checked_sub(old)?; (r0, r1 + 1, self.range(r0).start, last) } }; let len = last.checked_sub(first)?; let segs = self.segs.checked_add(new)?.checked_sub(old)?; let after: Vec = self.ends.get(r1..)?.iter().map(|&e| e.checked_add(new)?.checked_sub(old)).collect::>()?; if r1 > self.boxes.len() || r1 > stale.len() { return None; } let count = if len > 2 * RUN { len.div_ceil(RUN) } else { usize::from(len > 0) }; self.ends.truncate(r1); self.ends.extend(after); self.ends.splice(r0..r1, (1..=count).map(|j| if j == count { last } else { first + j * RUN })); self.boxes.splice(r0..r1, std::iter::repeat_n(None, count)); if tips { self.tips.splice(r0.min(self.tips.len())..r1.min(self.tips.len()), std::iter::repeat_n(None, count)); } stale.splice(r0..r1, std::iter::repeat_n(true, count)); self.segs = segs; Some(()) } /// Measure again the tips of `st` in `runs`, or in every run when `whole`. fn measure_tips(&mut self, els: &[PathEl], mut runs: Vec, whole: bool, st: &StrokeLayer) { if whole { runs = (0..self.runs()).collect(); } runs.sort_unstable(); runs.dedup(); for r in runs { let t = self.run_tips(els, r, st); if let Some(slot) = self.tips.get_mut(r) { *slot = t; } } } } /// What [`KeptPath::tips`] measures `st`'s tips by: its reach from an open and a closed path and /// its miter limit. fn tips_key(st: &StrokeLayer) -> [u64; 3] { [st.side_reach(false).to_bits(), st.side_reach(true).to_bits(), st.miter_limit.to_bits()] } /// Whether the subpaths of `was` and `now` differ: in number, in anchors or in being closed. fn differs(was: &PathData, now: &PathData) -> bool { !std::ptr::eq(was, now) && (was.subpaths.len() != now.subpaths.len() || was.subpaths.iter().zip(&now.subpaths).any(|(w, p)| { w.closed != p.closed || w.anchors.len() != p.anchors.len() || w.anchors.iter().zip(&p.anchors).any(|(a, b)| !same(a, b)) })) } /// The subpaths of `was` and `now`, paths with as many subpaths, whose anchors were added or /// deleted or that were closed or opened. fn splices(was: &PathData, now: &PathData) -> usize { if std::ptr::eq(was, now) || was.subpaths.len() != now.subpaths.len() { return 0; } was.subpaths.iter().zip(&now.subpaths).filter(|(w, p)| w.anchors.len() != p.anchors.len() || w.closed != p.closed).count() } impl KeptPath { /// The kept path of `n`, or `None` when it has none ([`kept`]). pub(crate) fn new(n: &Arc, stamp: u64) -> Option { if !kept(n) { return None; } let path = n.stroke_path()?; let els = path.elements(); let mut start = 0; let mut parts = vec![]; for pd in paths(n)? { let mut subs = vec![]; for sp in &pd.subpaths { let sub = Sub::measured(sp, start, els, None); start += sub.len(); subs.push(sub); } parts.push(subs); } (start == els.len()).then(|| Self { node: n.clone(), path: Arc::new(path), parts, tips_of: None, stamp }) } /// Bring the path up to date with `n`, a later version of the object it was made from. The /// subpaths whose anchors moved change as [`Sub::moved`] says, and those whose anchors were /// added or deleted or that were closed or opened as [`Sub::spliced`] says. A member of a /// compound path that gained or lost subpaths is made again, and so are the members added /// between the first and the last member that the edit left as they were, when the edit added /// or removed members. False, and nothing changed, when `n` has no kept path, or when the edit /// added or deleted anchors in or closed or opened more than [`SPLICES`] subpaths, or when the /// members kept do not pair with those of `n`. A subpath whose segments or runs kept do not fit /// its anchors before the edit is made again whole ([`Sub::spliced`]). False as well /// when the path written does not end where the subpaths measured end. The renderer makes the /// path again whole after a false. pub(crate) fn update(&mut self, n: &Arc) -> bool { let old = self.node.clone(); let (Some(was), Some(now)) = (paths(&old), paths(n)) else { return false }; if !kept(n) || self.parts.len() != was.len() || self.parts.iter().zip(&was).any(|(s, p)| s.len() != p.subpaths.len()) { return false; } // The members paired with one of `was`: all when there are as many, otherwise those // before the first and after the last that is not the same. The others are made again. let (head, tail) = if was.len() == now.len() { (was.len(), 0) } else { let head = was.iter().zip(&now).take_while(|(w, p)| std::ptr::eq(**w, **p)).count(); let rest = (was.len() - head).min(now.len() - head); (head, was.iter().rev().zip(now.iter().rev()).take(rest).take_while(|(w, p)| std::ptr::eq(**w, **p)).count()) }; // Where the members that are not paired end in `was` and in `now`. let (Some(was_end), Some(now_end)) = (was.len().checked_sub(tail), now.len().checked_sub(tail)) else { return false }; if head > was_end || head > now_end { return false; } let paired = |i: usize| match i < head { true => Some(i), false => now.len().checked_sub(i).filter(|&back| back <= tail).and_then(|back| was.len().checked_sub(back)), }; let spliced: usize = (0..now.len()).filter_map(|i| paired(i).map(|w| splices(was[w], now[i]))).sum(); if spliced > SPLICES { return false; } self.node = n.clone(); // Nothing to change: the path is left as it is, shared or not. if was.len() == now.len() && was.iter().zip(&now).all(|(w, p)| !differs(w, p)) { return true; } let st = self.tips_of.clone(); let st = st.as_ref(); let path = Arc::make_mut(&mut self.path); let mut els = std::mem::take(path).into_elements(); let mut old_parts: Vec>> = std::mem::take(&mut self.parts).into_iter().map(Some).collect(); let mut at = 0; let mut parts = Vec::with_capacity(now.len()); for (i, p) in now.iter().enumerate() { if i == head { // The members of `was` that are not paired give way to those of `now`. let gone: usize = old_parts.get_mut(head..was_end).into_iter().flatten().flat_map(|s| s.take().into_iter().flatten()).map(|s| s.len()).sum(); parts.extend(made_again(now.get(head..now_end).unwrap_or_default(), gone, &mut els, &mut at, st)); } let Some(w) = paired(i) else { continue }; let Some(mut subs) = old_parts.get_mut(w).and_then(Option::take) else { continue }; if was[w].subpaths.len() != p.subpaths.len() { let len = subs.iter().map(Sub::len).sum(); parts.extend(made_again(&[*p], len, &mut els, &mut at, st)); continue; } let moved = !std::ptr::eq(was[w], *p); for (sub, (ws, ps)) in subs.iter_mut().zip(was[w].subpaths.iter().zip(&p.subpaths)) { if moved { sub.update(ws, ps, at, &mut els, st); } sub.start = at; at += sub.len(); } parts.push(subs); } if head == now.len() && head < was.len() { // Members were removed from the end. let gone: usize = old_parts[head..].iter_mut().flat_map(|s| s.take().into_iter().flatten()).map(|s| s.len()).sum(); parts.extend(made_again(&[], gone, &mut els, &mut at, st)); } let made = at == els.len(); *path = BezPath::from_vec(els); self.parts = parts; made } /// The object's geometric bounds ([`Node::geometric_bounds`]): each path's as /// [`PathData::bounds`] measures it, joined. pub(crate) fn geometric(&self) -> Option { let els = self.path.elements(); self.parts.iter().fold(None, |acc, subs| { let anchors: usize = subs.iter().map(|s| s.anchors).sum(); let b = match anchors { 0 => None, // One anchor: the point itself. 1 => subs.iter().find(|s| s.anchors == 1).and_then(|s| els.get(s.start)).and_then(end).map(|p| Rect::from_points(p, p)), _ => Some(subs.iter().flat_map(|s| s.boxes.iter().flatten()).copied().reduce(|a, b| a.union(b)).unwrap_or_default()), }; vectorcraft_geom::union_opt(acc, b) }) } /// The box of the miter tips of `st` at every joint of the path ([`StrokeLayer::miter_tip`]), /// for [`StrokeLayer::bounds_with_tips`]: measured whole when the tips kept are another /// stroke's, then kept up to date with the path. pub(crate) fn tips(&mut self, st: &StrokeLayer) -> Option { if st.join != LineJoin::Miter { return None; } if self.tips_of.as_ref().is_none_or(|t| t.join != LineJoin::Miter || tips_key(t) != tips_key(st)) { let els = self.path.elements(); for sub in self.parts.iter_mut().flatten() { sub.tips = (0..sub.runs()).map(|r| sub.run_tips(els, r, st)).collect(); } self.tips_of = Some(st.clone()); } self.parts.iter().flatten().flat_map(|s| s.tips.iter().flatten()).copied().reduce(|a, b| a.union(b)) } /// Each subpath's MoveTo, segments and anchors, whether it is closed, and the segments of each /// of its runs. #[cfg(test)] pub(crate) fn layout(&self) -> Vec { let runs = |s: &Sub| (0..s.runs()).map(|r| s.range(r).len()).collect(); self.parts.iter().flatten().map(|s| ((s.start, s.segs, s.anchors, s.closed), runs(s))).collect() } /// The boxes and tips kept per run of each subpath, the same measured again from the path /// over the same runs, and the stroke the tips were measured for. #[cfg(test)] pub(crate) fn runs(&self) -> (Vec, Vec, Option) { let els = self.path.elements(); let kept = self.parts.iter().flatten().map(|s| (s.boxes.clone(), s.tips.clone())).collect(); let again = self.parts.iter().flatten().map(|s| { let boxes = (0..s.runs()).map(|r| s.run_box(els, r)).collect(); let tips = self.tips_of.as_ref().map_or(vec![], |st| (0..s.runs()).map(|r| s.run_tips(els, r, st)).collect()); (boxes, tips) }); (kept, again.collect(), self.tips_of.clone()) } }