Files
vectorcraft/crates/doc/src/document_guides.rs
T
dotCooCoo 605a74f19f Artboards: Convert to Artboards gives each new artboard its id without reading every artboard
Convert to Artboards found the highest artboard id twice for each object it
converted: once for the new artboard's name and once in
Document::next_artboard_id. Converting N objects read about N^2 artboards.

The command now keeps the highest id as it adds artboards, and
Document::next_artboard_id_above gives the next id from it. When an artboard
has id u32::MAX, it falls back to next_artboard_id, which takes the smallest
free id. The ids and names it gives are unchanged.
2026-11-11 20:56:36 -08:00

694 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::document_ext::free_id;
use super::guides::Guide;
use super::limits::MAX_NEXT_ID;
use super::{Affine, Arc, Document, Node, NodeId, NodeKind, PatternDef, Point, Rect, Selection, SymbolBoxes, Vec2, artboard_holds};
use std::collections::{BTreeMap, HashSet};
impl Document {
/// Union of the geometric bounds of `ids` (their visual bounds when `visual`), with symbol
/// instances measured by their art ([`Node::geometric_bounds_with`]).
pub fn bounds_of(&self, ids: &[NodeId], visual: bool) -> Option<Rect> {
self.bounds_of_with(ids, visual, &self.symbol_boxes())
}
/// [`bounds_of`](Document::bounds_of) measuring symbol instances from `symbols`, a cache shared
/// across calls: measuring many instances one by one otherwise measures their art again for
/// each.
pub fn bounds_of_with(&self, ids: &[NodeId], visual: bool, symbols: &crate::SymbolBoxes) -> Option<Rect> {
ids.iter().filter_map(|id| self.node(*id)).fold(None, |acc, n| {
vectorcraft_geom::union_opt(acc, if visual { n.visual_bounds_with(symbols) } else { n.geometric_bounds_with(symbols) })
})
}
/// Visual bounds of all art, with symbol instances measured by their art.
pub fn art_bounds(&self) -> Option<Rect> {
let symbols = self.symbol_boxes();
self.layers.iter().fold(None, |acc, l| vectorcraft_geom::union_opt(acc, l.visual_bounds_with(&symbols)))
}
/// Artboard index containing point `p` (topmost = last), its edges included
/// ([`Document::artboard_holding`]).
pub fn artboard_at(&self, p: Point) -> Option<usize> {
self.artboard_holding(p, true)
}
/// Index of the artboard that holds point `p`: the first (the last when `topmost`) that holds it
/// inside or on its left or top edge, else the first (the last) that holds it by
/// [`artboard_holds`]: on its right or bottom edge, or within a millionth of a point outside an
/// edge. On an edge two artboards share, the artboard to the right of it or below it holds the
/// point, except at the end of the edge that lies on that artboard's bottom or right edge, such
/// as the point where both artboards' bottom edges meet; there the first (the last) holds it.
pub fn artboard_holding(&self, p: Point, topmost: bool) -> Option<usize> {
let pick = |on: &dyn Fn(Rect) -> bool| {
let on = |i: &usize| self.artboards.get(*i).is_some_and(|a| on(a.rect));
let mut all = 0..self.artboards.len();
if topmost { all.rfind(on) } else { all.find(on) }
};
pick(&|r| r.contains(p)).or_else(|| pick(&|r| artboard_holds(r, p)))
}
/// The art that moves with an artboard at `rect`: top-level objects (children of layers and
/// sublayers) lying entirely inside it, with symbol instances measured by their art. Locked and
/// hidden objects and layers stay put unless `locked_and_hidden` (Selection & Anchor Display ›
/// Move Locked and Hidden Artwork with Artboard).
pub fn art_on_artboard(&self, rect: Rect, locked_and_hidden: bool) -> Vec<NodeId> {
// Art drawn flush with the artboard can end up a rounding error outside it (a background
// 3e-14 pt past the top edge, #1295): a millionth of a point is far below anything visible.
let rect = rect.inflate(1e-6, 1e-6);
fn collect(n: &Node, s: &SymbolBoxes, rect: Rect, all: bool, out: &mut Vec<NodeId>) {
for c in n.children().into_iter().flatten().filter(|c| c.rides_with_artboard(all)) {
if c.is_layer() {
collect(c, s, rect, all, out);
} else if let Some(b) = c.geometric_bounds_with(s)
// Inside it, edges included: art flush with the right or bottom edge is on it too
// (`Rect::contains` leaves those out, #890).
&& b.x0 >= rect.x0
&& b.y0 >= rect.y0
&& b.x1 <= rect.x1
&& b.y1 <= rect.y1
{
out.push(c.id);
}
}
}
let symbols = self.symbol_boxes();
let mut art = vec![];
for l in self.layers.iter().filter(|l| l.rides_with_artboard(locked_and_hidden)) {
collect(l, &symbols, rect, locked_and_hidden, &mut art);
}
art
}
/// Does any artwork lie on `rect` (an artboard)? Artwork is any object under the layers,
/// hidden and locked ones and those on template layers included, whose visual bounds (symbol
/// instances measured by their art) meet `rect`; touching its edge counts. A group (not a clip
/// group) counts by its members, so one whose members sit on two other artboards leaves the one
/// between them empty. A clip group or clipping layer counts by its members inside its clip (and
/// by its clipping path where that paints). Guides (paths made into guides too) aren't artwork
/// (Delete Empty Artboards).
pub fn artboard_has_art(&self, rect: Rect) -> bool {
self.art_meets(&self.symbol_boxes(), rect, true)
}
/// Is there a rect in `rects` (artboards) with no artwork on it, by [`Document::artboard_has_art`]?
/// The symbols are measured once for all of them. A first pass rules art out by a quick box
/// (paths and compound paths: their control points grown by the farthest their strokes reach),
/// so a rect away from every path is found without measuring along any; only when every rect
/// may hold art is each one measured.
pub fn any_artboard_without_art(&self, rects: impl IntoIterator<Item = Rect>) -> bool {
let rects: Vec<Rect> = rects.into_iter().collect();
let symbols = self.symbol_boxes();
rects.iter().any(|r| !self.art_meets(&symbols, *r, false)) || rects.iter().any(|r| !self.art_meets(&symbols, *r, true))
}
/// [`Document::artboard_has_art`] of `rect` when `exact`. Otherwise whether art may lie on it:
/// paths and compound paths count when their quick box meets it, so a false answer is exact and
/// a true one may not be.
fn art_meets(&self, symbols: &SymbolBoxes, rect: Rect, exact: bool) -> bool {
// As in `art_on_artboard`: a millionth of a point covers rounding at the edges.
let rect = rect.inflate(1e-6, 1e-6);
fn meets(b: Option<Rect>, r: Rect) -> bool {
b.is_some_and(|b| b.x0 <= r.x1 && r.x0 <= b.x1 && b.y0 <= r.y1 && r.y0 <= b.y1)
}
/// A box that holds the visual bounds of a path, or of a compound path of paths, found
/// without measuring along its shape: its control points' box grown by the farthest its
/// strokes reach ([`crate::Appearance::outset`], as in [`Node::reach_bounds`]), with a
/// margin for rounding. None for other objects, and when the reach is not finite.
fn quick_box(c: &Node) -> Option<Rect> {
let b = match &c.kind {
NodeKind::Path { path, .. } => path.control_bounds()?,
NodeKind::Compound { children, .. } => children.iter().try_fold(None, |acc, m| {
let NodeKind::Path { path, .. } = &m.kind else { return None };
Some(vectorcraft_geom::union_opt(acc, path.control_bounds()))
})??,
_ => return None,
};
let o = c.appearance.outset();
let o = o * (1.0 + 1e-9) + 1e-6;
o.is_finite().then(|| b.inflate(o, o))
}
/// Does `c`'s visual box meet `rect`? Without `exact`, a quick box that meets it answers yes.
fn art_box_meets(c: &Node, s: &SymbolBoxes, rect: Rect, exact: bool) -> bool {
match quick_box(c).map(|b| meets(Some(b), rect)) {
Some(false) => false,
Some(true) if !exact => true,
_ => meets(c.visual_bounds_with(s), rect),
}
}
fn has(c: &Node, s: &SymbolBoxes, rect: Rect, exact: bool) -> bool {
match &c.kind {
NodeKind::Group { children, clip: true } | NodeKind::Layer { children, clip: true, .. } => {
let Some((clip, rest)) = children.split_first() else { return false };
let paint = clip.clip_paint();
if (paint.fill.is_some() || paint.stroke.is_some()) && art_box_meets(clip, s, rect, exact) {
return true;
}
if !exact {
// The part of `rect` inside the clip's box lies inside `rect`.
return rest.iter().any(|m| has(m, s, rect, false));
}
// The members show only inside the clip's box.
let Some(b) = c.visual_bounds_with(s).filter(|b| meets(Some(*b), rect)) else { return false };
let inside = Rect::new(b.x0.max(rect.x0), b.y0.max(rect.y0), b.x1.min(rect.x1), b.y1.min(rect.y1));
rest.iter().any(|m| has(m, s, inside, true))
}
NodeKind::Layer { children, .. } | NodeKind::Group { children, .. } => children.iter().any(|m| has(m, s, rect, exact)),
NodeKind::Path { guide: true, .. } | NodeKind::RulerGuide { .. } => false,
_ => art_box_meets(c, s, rect, exact),
}
}
self.layers.iter().any(|l| has(l, symbols, rect, exact))
}
/// The art that moves, scales or is cut with artboard `index`: [`Document::art_on_artboard`]
/// without the objects on another locked artboard, which stay unless `locked_and_hidden`, as
/// locked art does.
pub fn art_moving_with(&self, index: usize, locked_and_hidden: bool) -> Vec<NodeId> {
let Some(rect) = self.artboards.get(index).map(|a| a.rect) else { return vec![] };
let mut art = self.art_on_artboard(rect, locked_and_hidden);
let locks = self.locked_artboards_except(index);
if !locked_and_hidden && !locks.is_empty() {
art.retain(|id| !self.node(*id).is_some_and(|n| locks.holds(n)));
}
art
}
/// Put the ruler guide nodes a file holds where ruler guides go: directly in a layer or
/// sublayer, above its art. A ruler guide below art in a layer moves up to the layer's other
/// guides, keeping their order. A ruler guide anywhere else (at the top level, in a group or
/// another object, in opacity-mask art, in a symbol, a graph design or a pattern) is removed.
/// Returns how many were removed.
pub fn tidy_ruler_guides(&mut self) -> usize {
let mut gone = self.layers.len();
self.layers.retain(|l| !l.is_ruler_guide());
gone -= self.layers.len();
for l in &mut self.layers {
if layer_untidy(l) {
gone += tidy_layer(Arc::make_mut(l));
}
}
let others = self
.symbols
.iter_mut()
.map(|s| &mut s.art)
.chain(self.graph_designs.iter_mut().map(|g| &mut g.art))
.chain(self.patterns.iter_mut().flat_map(|p| p.art.iter_mut()))
.chain(self.pattern_edit.iter_mut().filter_map(|pe| pe.original.as_mut()).flat_map(|p| p.art.iter_mut()));
for root in others {
if root.is_ruler_guide() {
*root = Arc::new(Node::group(root.id, vec![]));
gone += 1;
} else if any_guide(root) {
gone += drop_guides_below(Arc::make_mut(root));
}
}
gone
}
/// Make the saved ruler guides (read from a file: [`Document::guides_to_saved`]'s list) into
/// [`NodeKind::RulerGuide`] nodes, each at the top of its layer, in list order. A guide on a
/// layer or sublayer the document has goes on it; one on no layer, or on a layer that is gone,
/// goes on the top visible, unlocked layer ([`Self::default_layer`]), or on a new "Layer 1"
/// when there is none. A guide keeps its saved id when no other node, slice or guide has it,
/// and its name, lock and visibility. The saved list is emptied, so running this again does
/// nothing. Returns how many guides became nodes.
pub fn guides_into_nodes(&mut self) -> usize {
let saved = std::mem::take(&mut self.saved_guides);
if saved.is_empty() {
return 0;
}
let mut used = HashSet::new();
self.walk(|n| {
used.insert(n.id);
});
used.extend(self.slices.iter().map(|s| s.id));
used.extend(self.assets.iter().map(|a| NodeId(a.id)));
let kept: Vec<Option<NodeId>> = saved.iter().map(|g| g.id.filter(|id| id.0 > 0 && id.0 < MAX_NEXT_ID && used.insert(*id))).collect();
if let Some(max) = kept.iter().flatten().map(|id| id.0).max() {
self.raise_next_id(max.saturating_add(1));
}
let mut home = None;
// The guides of each layer, in list order.
let mut by_layer: BTreeMap<NodeId, Vec<Arc<Node>>> = BTreeMap::new();
let mut order = vec![];
for (g, id) in saved.iter().zip(kept) {
let layer = match g.layer.filter(|l| self.node(*l).is_some_and(Node::is_layer)) {
Some(l) => l,
None => match home {
Some(h) => h,
None => {
let h = self.default_layer().unwrap_or_else(|| self.add_layer(None));
home = Some(h);
h
}
},
};
let id = id.unwrap_or_else(|| self.alloc_id());
let mut n = Node::new(id, NodeKind::RulerGuide { vertical: g.vertical, pos: g.pos, artboard: g.artboard });
n.name = g.name.clone();
n.locked = g.locked;
n.visible = !g.hidden;
if !by_layer.contains_key(&layer) {
order.push(layer);
}
by_layer.entry(layer).or_default().push(Arc::new(n));
}
for layer in order {
let guides = by_layer.remove(&layer).unwrap_or_default();
if let Ok(ch) = self.children_mut(Some(layer)) {
ch.extend(guides);
}
}
saved.len()
}
/// Take the ruler guide nodes out of the layers and put them in the saved list, by ascending
/// id, each naming its layer, after any entries not yet made into nodes: the form a file keeps
/// them in, readable by versions from before ruler guides were nodes. For saving a copy of
/// the document; [`Self::guides_into_nodes`] reverses it.
pub fn guides_to_saved(&mut self) {
let guides = self.ruler_guides();
if guides.is_empty() {
return;
}
for l in &mut self.layers {
if l.holds_ruler_guides() {
Arc::make_mut(l).strip_ruler_guides();
}
}
self.saved_guides.extend(guides);
}
/// Every ruler guide, by ascending id, with the layer or sublayer it is on, its id, name and
/// its own lock and visibility. A guide's place in this list is its index in the guide
/// commands.
pub fn ruler_guides(&self) -> Vec<Guide> {
if let Some(listed) = self.guide_cache.get(&self.layers) {
return listed.as_ref().clone();
}
fn visit(layer: &Node, out: &mut Vec<Guide>) {
for c in layer.children().into_iter().flatten() {
if let Some(g) = guide_of(c, layer.id) {
out.push(g);
} else if c.is_layer() {
visit(c, out);
}
}
}
let mut out = vec![];
for l in &self.layers {
visit(l, &mut out);
}
out.sort_by_key(|g| g.id);
self.guide_cache.set(&self.layers, &out);
out
}
/// Ruler guide `id`, as [`Self::ruler_guides`] lists it. None when `id` is not a ruler guide
/// on a layer.
pub fn ruler_guide(&self, id: NodeId) -> Option<Guide> {
let layer = self.parent_of(id)?;
guide_of(self.node(id)?, layer)
}
/// Change ruler guide `id` with `f` (its line, its artboard; the layer, id, name, lock and
/// visibility fields are not read back). Returns false when `id` is not a ruler guide.
pub fn edit_ruler_guide(&mut self, id: NodeId, f: impl FnOnce(&mut Guide)) -> bool {
let Some(mut g) = self.ruler_guide(id) else { return false };
f(&mut g);
let Some(n) = self.node_mut(id) else { return false };
n.kind = NodeKind::RulerGuide { vertical: g.vertical, pos: g.pos, artboard: g.artboard };
true
}
/// Add ruler guide `g` as a node with a new id at the top of its layer: `g.layer` when the
/// document has that layer or sublayer, else the top visible, unlocked layer
/// ([`Self::default_layer`]), or a new "Layer 1" when there is none. Its name, lock and
/// visibility come from `g`. Returns the new id.
pub fn add_ruler_guide(&mut self, g: &Guide) -> NodeId {
let layer = match g.layer.filter(|l| self.node(*l).is_some_and(Node::is_layer)) {
Some(l) => l,
None => self.default_layer().unwrap_or_else(|| self.add_layer(None)),
};
let id = self.alloc_id();
let mut n = Node::new(id, NodeKind::RulerGuide { vertical: g.vertical, pos: g.pos, artboard: g.artboard });
n.name = g.name.clone();
n.locked = g.locked;
n.visible = !g.hidden;
if let Ok(ch) = self.children_mut(Some(layer)) {
ch.push(Arc::new(n));
}
id
}
/// Remove node `id`, the temporary layer of opacity-mask, symbol or pattern editing, keeping the
/// ruler guides on it and on its sublayers: they go to the top of the top visible, unlocked
/// layer left ([`Self::default_layer`]), or of a new "Layer 1" when no layer is left, where a
/// saved guide whose layer is gone opens. Returns the removed node, without the guides.
pub fn remove_edit_layer(&mut self, id: NodeId) -> Result<Arc<Node>, crate::DocError> {
let mut layer = self.remove(id)?;
if !layer.holds_ruler_guides() {
return Ok(layer);
}
let mut guides = vec![];
layer.walk(&mut |c| {
if c.is_ruler_guide() {
guides.push(Arc::new(c.clone()));
}
});
Arc::make_mut(&mut layer).strip_ruler_guides();
let home = self.default_layer().unwrap_or_else(|| self.add_layer(None));
self.children_mut(Some(home))?.extend(guides);
Ok(layer)
}
/// Where ruler guide `g` runs along its line (the y range of a vertical guide): across its
/// artboard for an artboard guide, None (the whole canvas) for a canvas guide or one whose
/// artboard is gone.
pub fn guide_span(&self, g: &Guide) -> Option<(f64, f64)> {
let r = self.artboards.iter().find(|a| Some(a.id) == g.artboard)?.rect;
Some(if g.vertical { (r.y0, r.y1) } else { (r.x0, r.x1) })
}
/// The ruler guide node `g` names, if this document has it.
fn guide_node(&self, g: &Guide) -> Option<NodeId> {
g.id.filter(|id| self.node(*id).is_some_and(Node::is_ruler_guide))
}
/// The layer (or sublayer) ruler guide `g` is on, if it is on one this document has.
fn guide_layer(&self, g: &Guide) -> Option<NodeId> {
g.layer.filter(|l| self.node(*l).is_some_and(Node::is_layer))
}
/// Is ruler guide `g` shown: its node, and the layers it is on, visible? A guide that is not a
/// node of this document shows unless hidden itself or on a hidden layer.
pub fn guide_shown(&self, g: &Guide) -> bool {
match self.guide_node(g) {
Some(id) => self.is_visible(id),
None => !g.hidden && self.guide_layer(g).is_none_or(|l| self.is_visible(l)),
}
}
/// Can ruler guide `g` be picked, moved and deleted: shown, and neither it nor its layers
/// locked?
pub fn guide_editable(&self, g: &Guide) -> bool {
match self.guide_node(g) {
Some(id) => self.is_editable(id),
None => !g.hidden && !g.locked && self.guide_layer(g).is_none_or(|l| self.is_editable(l)),
}
}
/// The ruler guides directly on layer `layer` (not its sublayers'), by ascending id.
pub fn guides_on(&self, layer: NodeId) -> Vec<Guide> {
let mut out: Vec<Guide> = self.children(Some(layer)).into_iter().flatten().filter_map(|c| guide_of(c, layer)).collect();
out.sort_by_key(|g| g.id);
out
}
/// Does ruler guide `g` run past `p` (up to `tol` beyond its ends)?
pub fn guide_passes(&self, g: &Guide, p: Point, tol: f64) -> bool {
let along = if g.vertical { p.y } else { p.x };
self.guide_span(g).is_none_or(|(a, b)| along >= a - tol && along <= b + tol)
}
/// Keep the ruler guides `keep` accepts, deleting the others; `sel` forgets the deleted ones
/// (the selected art stays selected). Returns how many went.
pub fn retain_guides(&mut self, sel: &mut Selection, keep: impl Fn(&Guide) -> bool) -> usize {
let gone: HashSet<NodeId> = self.ruler_guides().into_iter().filter(|g| !keep(g)).filter_map(|g| g.id).collect();
if gone.is_empty() {
return 0;
}
fn drop(layer: &mut Arc<Node>, gone: &HashSet<NodeId>) {
fn holds(n: &Node, gone: &HashSet<NodeId>) -> bool {
n.is_layer() && n.children().is_some_and(|ch| ch.iter().any(|c| gone.contains(&c.id) || holds(c, gone)))
}
if !holds(layer, gone) {
return;
}
let Some(ch) = Arc::make_mut(layer).children_mut() else { return };
ch.retain(|c| !(c.is_ruler_guide() && gone.contains(&c.id)));
for c in ch.iter_mut().filter(|c| c.is_layer()) {
drop(c, gone);
}
}
for l in &mut self.layers {
drop(l, &gone);
}
sel.guides.retain(|id| !gone.contains(id));
if sel.key_guide.is_some_and(|k| gone.contains(&k)) {
sel.key_guide = None;
}
gone.len()
}
/// Change the line of every ruler guide on artboard `id` with `f` (whether it is vertical, its
/// position).
fn edit_artboard_guides(&mut self, id: u32, f: &mut impl FnMut(bool, &mut f64)) {
fn visit(layer: &mut Arc<Node>, id: u32, f: &mut impl FnMut(bool, &mut f64)) {
fn holds(n: &Node, id: u32) -> bool {
n.is_layer()
&& n.children().is_some_and(|ch| {
ch.iter().any(|c| matches!(c.kind, NodeKind::RulerGuide { artboard: Some(a), .. } if a == id) || holds(c, id))
})
}
if !holds(layer, id) {
return;
}
let Some(ch) = Arc::make_mut(layer).children_mut() else { return };
for c in ch.iter_mut() {
if matches!(c.kind, NodeKind::RulerGuide { artboard: Some(a), .. } if a == id) {
if let NodeKind::RulerGuide { vertical, pos, .. } = &mut Arc::make_mut(c).kind {
f(*vertical, pos);
}
} else if c.is_layer() {
visit(c, id, f);
}
}
}
for l in &mut self.layers {
visit(l, id, f);
}
}
/// Move the guides of artboard `id` by `d` (along with their artboard).
pub fn move_artboard_guides(&mut self, id: u32, d: Vec2) {
self.edit_artboard_guides(id, &mut |vertical, pos| *pos += if vertical { d.x } else { d.y });
}
/// Map the guides of artboard `id` through `xf`, an axis-aligned scale and move (Scale Artwork
/// with Artboard).
pub fn map_artboard_guides(&mut self, id: u32, xf: Affine) {
self.edit_artboard_guides(id, &mut |vertical, pos| {
*pos = if vertical { (xf * Point::new(*pos, 0.0)).x } else { (xf * Point::new(0.0, *pos)).y };
});
}
/// Copy the guides of artboard `from` onto artboard `to`, `d` away (with a copy of their
/// artboard), each on its guide's layer with a new id.
pub fn copy_artboard_guides(&mut self, from: u32, to: u32, d: Vec2) {
let copies: Vec<Guide> =
self.ruler_guides().into_iter().filter(|g| g.artboard == Some(from)).map(|g| Guide { artboard: Some(to), ..g.moved(d) }).collect();
for g in &copies {
self.add_ruler_guide(g);
}
}
/// An id for a new artboard: one no artboard has, above the ids given out before and the ids of
/// the artboards deleted from the document ([`Document::retired_artboard_id`]). The id counts
/// as given out, so an artboard made after an undo does not take the id of one the undo took
/// away.
pub fn next_artboard_id(&self) -> u32 {
let id = free_id(self.artboards.iter().map(|a| a.id).chain([self.retired_artboard_id.get()]));
self.retired_artboard_id.raise(id);
id
}
/// [`Document::next_artboard_id`] when `top` is the highest id the artboards have (`None`: there
/// are none). It reads no artboard unless that id is `u32::MAX`, so a caller that adds many
/// artboards and keeps `top` up to date gets each id in constant time.
pub fn next_artboard_id_above(&self, top: Option<u32>) -> u32 {
match top.unwrap_or(0).max(self.retired_artboard_id.get()).checked_add(1) {
Some(id) => {
self.retired_artboard_id.raise(id);
id
}
None => self.next_artboard_id(),
}
}
/// Record that the artboard with `id` was deleted: no later artboard takes its id.
pub fn retire_artboard_id(&self, id: u32) {
self.retired_artboard_id.raise(id);
}
pub fn pattern(&self, name: &str) -> Option<&PatternDef> {
self.patterns.iter().find(|p| p.name == name)
}
pub fn pattern_mut(&mut self, name: &str) -> Option<&mut PatternDef> {
self.patterns.iter_mut().find(|p| p.name == name)
}
/// Deep-clone `node` with fresh ids for it and all descendants, its images' own pixels taken
/// in ([`Self::adopt_pixels`]).
pub fn reid(&mut self, node: &Node) -> Node {
let mut n = node.clone();
n.id = self.alloc_id();
if let NodeKind::Image(im) = &mut n.kind {
self.adopt_image_pixels(im);
}
// A copied annotation must never bind its source ids to unrelated art in a different
// document. Keep the measured geometry; new measurements can be linked after pasting.
if let Some(e) = n.extras_if_any()
&& let Some(dimension) = &mut e.dimension
{
dimension.source = None;
}
// A copy of a threaded frame is in no thread, so its first paragraph starts in it and its
// layout caches are dropped. A caller that keeps the copy as an object fills them again.
if let NodeKind::Text(t) = &mut n.kind
&& t.first_para_continues
{
t.first_para_continues = false;
t.cached_bounds = None;
t.cached_baselines.clear();
t.cached_ink = None;
}
let on_path = n.extras().objects_on_path.is_some();
let mut renamed = std::collections::HashMap::new();
if let Some(ch) = n.children_mut() {
let old: Vec<Arc<Node>> = std::mem::take(ch);
*ch = old.iter().map(|c| Arc::new(self.reid(c))).collect();
if on_path {
renamed.extend(old.iter().zip(ch.iter()).map(|(o, c)| (o.id, c.id)));
}
}
// An Objects on Path group names its path and its objects by id: the copy names the copies.
if let Some(e) = n.extras_if_any()
&& let Some(on_path) = &mut e.objects_on_path
{
on_path.rename(&renamed);
}
n
}
}
/// Does `n`, or anything below it (opacity-mask art included), hold a ruler guide?
fn any_guide(n: &Node) -> bool {
let mut any = false;
crate::swatches::walk_with_masks(n, &mut |m| any |= m.is_ruler_guide());
any
}
/// Remove every ruler guide below `n`, in its members and its opacity-mask art (`n` itself
/// stays). Mask art that is a ruler guide becomes an empty group. Returns how many went.
fn drop_guides_below(n: &mut Node) -> usize {
let mut gone = drop_mask_guides(n);
if let Some(ch) = n.children_mut() {
let before = ch.len();
ch.retain(|c| !c.is_ruler_guide());
gone += before - ch.len();
for c in ch.iter_mut().filter(|c| any_guide(c)) {
gone += drop_guides_below(Arc::make_mut(c));
}
}
gone
}
/// Remove every ruler guide in the opacity-mask art of `n`. Mask art that is a ruler guide
/// becomes an empty group. Returns how many went.
fn drop_mask_guides(n: &mut Node) -> usize {
let Some(mask) = n.extras_if_any().and_then(|e| e.mask.as_deref_mut()) else { return 0 };
if mask.art.is_ruler_guide() {
mask.art = Arc::new(Node::group(mask.art.id, vec![]));
1
} else if any_guide(&mask.art) {
drop_guides_below(Arc::make_mut(&mut mask.art))
} else {
0
}
}
/// Does layer `l` hold a ruler guide out of place: below art, in an object or in mask art, or so
/// in one of its sublayers?
fn layer_untidy(l: &Node) -> bool {
let Some(ch) = l.children() else { return false };
let start = Node::guide_run_start(ch);
l.extras().mask.as_deref().is_some_and(|m| any_guide(&m.art))
|| ch.iter().take(start).any(|c| c.is_ruler_guide() || if c.is_layer() { layer_untidy(c) } else { any_guide(c) })
}
/// [`Document::tidy_ruler_guides`] for layer `l`. Returns how many guides were removed.
fn tidy_layer(l: &mut Node) -> usize {
let mut gone = drop_mask_guides(l);
let Some(ch) = l.children_mut() else { return gone };
for c in ch.iter_mut() {
if c.is_layer() {
if layer_untidy(c) {
gone += tidy_layer(Arc::make_mut(c));
}
} else if !c.is_ruler_guide() && any_guide(c) {
gone += drop_guides_below(Arc::make_mut(c));
}
}
if ch.iter().take(Node::guide_run_start(ch)).any(|c| c.is_ruler_guide()) {
let (guides, art): (Vec<_>, Vec<_>) = std::mem::take(ch).into_iter().partition(|c| c.is_ruler_guide());
*ch = art;
ch.extend(guides);
}
gone
}
/// Node `n`, on layer `layer`, as a [`Guide`] value, if it is a ruler guide.
fn guide_of(n: &Node, layer: NodeId) -> Option<Guide> {
let NodeKind::RulerGuide { vertical, pos, artboard } = n.kind else { return None };
Some(Guide { vertical, pos, artboard, layer: Some(layer), id: Some(n.id), name: n.name.clone(), locked: n.locked, hidden: !n.visible })
}
/// The list [`Document::ruler_guides`] last made, with weak pointers to the top-level layers it was
/// made from. While a weak pointer to a layer's allocation is held, nothing else can be at that
/// address, and the layer can't be changed in place (`Arc::get_mut` refuses it and `Arc::make_mut`
/// moves it), so any edit inside a layer, and any layer added, removed or replaced, gives a list of
/// layers the cache does not match. Clones of a document share the cached list.
#[derive(Default)]
pub(crate) struct GuideCache(std::sync::RwLock<Option<Listed>>);
/// The top-level layers a guide list was made from, and the list.
type Listed = (Vec<std::sync::Weak<Node>>, Arc<Vec<Guide>>);
impl GuideCache {
/// The cached list, when it was made from `layers`.
fn get(&self, layers: &[Arc<Node>]) -> Option<Arc<Vec<Guide>>> {
let g = self.0.read().ok()?;
let (from, listed) = g.as_ref()?;
let same = from.len() == layers.len() && from.iter().zip(layers).all(|(w, l)| std::ptr::eq(w.as_ptr(), Arc::as_ptr(l)));
same.then(|| listed.clone())
}
/// Keep `listed`, made from `layers`.
fn set(&self, layers: &[Arc<Node>], listed: &[Guide]) {
if let Ok(mut g) = self.0.write() {
*g = Some((layers.iter().map(Arc::downgrade).collect(), Arc::new(listed.to_vec())));
}
}
}
impl Clone for GuideCache {
fn clone(&self) -> Self {
GuideCache(std::sync::RwLock::new(self.0.read().ok().and_then(|g| g.clone())))
}
}
/// A cache, not document content: two documents are equal whatever their caches hold.
impl PartialEq for GuideCache {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl std::fmt::Debug for GuideCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("GuideCache")
}
}