mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 11:47:21 -06:00
787 lines
33 KiB
Rust
787 lines
33 KiB
Rust
use super::artboard::{Artboard, LockedArtboards};
|
||
use super::graphic_style::default_graphic_styles;
|
||
use super::guides::{GridPrefs, RulerMode};
|
||
use super::limits::{DocError, FORMAT_VERSION, MAX_COORD, MAX_NEXT_ID};
|
||
use super::units::{ColorMode, Unit};
|
||
use super::*;
|
||
|
||
impl Document {
|
||
/// Is everything but the layers the same in `other`, as the next version of the document after
|
||
/// an edit of its art? The id counter and the id index are left out, and art outside the layers
|
||
/// (symbols, patterns, graph designs) compares by identity, which an edit keeps for what it
|
||
/// doesn't change: quick for every frame of an animation.
|
||
pub fn same_outside_layers(&self, other: &Self) -> bool {
|
||
let same_art = |a: &[Arc<Node>], b: &[Arc<Node>]| a.len() == b.len() && a.iter().zip(b).all(|(a, b)| Arc::ptr_eq(a, b));
|
||
let same_pattern = |a: &PatternDef, b: &PatternDef| {
|
||
same_art(&a.art, &b.art) && PatternDef { art: Vec::new(), ..a.clone() } == PatternDef { art: Vec::new(), ..b.clone() }
|
||
};
|
||
let Self {
|
||
version,
|
||
title,
|
||
template,
|
||
units,
|
||
ruler_origin,
|
||
rulers,
|
||
artboard_ruler_origins,
|
||
color_mode,
|
||
artboards,
|
||
layers: _,
|
||
swatches,
|
||
swatch_groups,
|
||
graphic_styles,
|
||
char_styles,
|
||
para_styles,
|
||
text_threads,
|
||
symbols,
|
||
graph_designs,
|
||
guides,
|
||
views,
|
||
saved_selections,
|
||
variables,
|
||
grid,
|
||
raster_effects_ppi,
|
||
images,
|
||
patterns,
|
||
pattern_edit,
|
||
mask_edit,
|
||
next_id: _,
|
||
unknown,
|
||
page_isolate,
|
||
page_knockout,
|
||
spot_use_lab,
|
||
setup,
|
||
paste_remembers_layers,
|
||
metadata,
|
||
raster_effects,
|
||
last_view,
|
||
transparency_grid,
|
||
layers_open,
|
||
color_profiles,
|
||
export_settings,
|
||
extra,
|
||
slices,
|
||
slices_clip_to_artboard,
|
||
print_setup,
|
||
assets,
|
||
svg_scripts,
|
||
puppet,
|
||
new_art,
|
||
dimensions,
|
||
retired_artboard_id: _,
|
||
index: _,
|
||
} = self;
|
||
*version == other.version
|
||
&& *title == other.title
|
||
&& *template == other.template
|
||
&& *units == other.units
|
||
&& *ruler_origin == other.ruler_origin
|
||
&& *rulers == other.rulers
|
||
&& *artboard_ruler_origins == other.artboard_ruler_origins
|
||
&& *color_mode == other.color_mode
|
||
&& *artboards == other.artboards
|
||
&& *swatches == other.swatches
|
||
&& *swatch_groups == other.swatch_groups
|
||
&& *graphic_styles == other.graphic_styles
|
||
&& *char_styles == other.char_styles
|
||
&& *para_styles == other.para_styles
|
||
&& *text_threads == other.text_threads
|
||
&& symbols.len() == other.symbols.len()
|
||
&& symbols.iter().zip(&other.symbols).all(|(a, b)| a.name == b.name && Arc::ptr_eq(&a.art, &b.art))
|
||
&& graph_designs.len() == other.graph_designs.len()
|
||
&& graph_designs.iter().zip(&other.graph_designs).all(|(a, b)| a.name == b.name && Arc::ptr_eq(&a.art, &b.art))
|
||
&& *guides == other.guides
|
||
&& *views == other.views
|
||
&& *saved_selections == other.saved_selections
|
||
&& *variables == other.variables
|
||
&& *grid == other.grid
|
||
&& *raster_effects_ppi == other.raster_effects_ppi
|
||
&& *images == other.images
|
||
&& patterns.len() == other.patterns.len()
|
||
&& patterns.iter().zip(&other.patterns).all(|(a, b)| same_pattern(a, b))
|
||
&& match (pattern_edit, &other.pattern_edit) {
|
||
(Some(a), Some(b)) => {
|
||
a.pattern == b.pattern
|
||
&& a.layer == b.layer
|
||
&& match (&a.original, &b.original) {
|
||
(Some(a), Some(b)) => same_pattern(a, b),
|
||
(a, b) => a.is_none() && b.is_none(),
|
||
}
|
||
}
|
||
(a, b) => a.is_none() && b.is_none(),
|
||
}
|
||
&& *mask_edit == other.mask_edit
|
||
&& *unknown == other.unknown
|
||
&& *page_isolate == other.page_isolate
|
||
&& *page_knockout == other.page_knockout
|
||
&& *spot_use_lab == other.spot_use_lab
|
||
&& *setup == other.setup
|
||
&& *paste_remembers_layers == other.paste_remembers_layers
|
||
&& *metadata == other.metadata
|
||
&& *raster_effects == other.raster_effects
|
||
&& *last_view == other.last_view
|
||
&& *transparency_grid == other.transparency_grid
|
||
&& *layers_open == other.layers_open
|
||
&& *color_profiles == other.color_profiles
|
||
&& *export_settings == other.export_settings
|
||
&& *extra == other.extra
|
||
&& *slices == other.slices
|
||
&& *slices_clip_to_artboard == other.slices_clip_to_artboard
|
||
&& *print_setup == other.print_setup
|
||
&& *assets == other.assets
|
||
&& *svg_scripts == other.svg_scripts
|
||
&& match (puppet, &other.puppet) {
|
||
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
|
||
(a, b) => a.is_none() && b.is_none(),
|
||
}
|
||
&& *new_art == other.new_art
|
||
&& *dimensions == other.dimensions
|
||
}
|
||
|
||
/// Document Raster Effects Settings › Resolution in ppi, 1 to 2400 (72 when the stored value
|
||
/// isn't a number).
|
||
pub fn raster_ppi(&self) -> f64 {
|
||
Some(self.raster_effects_ppi).filter(|v| v.is_finite()).unwrap_or(72.0).clamp(1.0, 2400.0)
|
||
}
|
||
|
||
/// The ruler zero point, or zero on both axes when either stored coordinate is invalid.
|
||
/// Rulers, coordinate fields and guide snapping use the same supported range.
|
||
pub fn ruler_origin_or_zero(&self) -> Point {
|
||
let origin = self.ruler_origin;
|
||
if origin.is_finite() && origin.x.abs() <= MAX_COORD && origin.y.abs() <= MAX_COORD { origin } else { Point::ZERO }
|
||
}
|
||
|
||
/// Where the rulers measure from with artboard `index` active: [`Self::ruler_origin_or_zero`]
|
||
/// with global rulers; with artboard rulers that artboard's top-left corner moved by its own
|
||
/// offset (the first artboard's when `index` is out of range, the global zero point when the
|
||
/// document has no artboard or the result is out of the supported range).
|
||
pub fn ruler_origin_for(&self, index: usize) -> Point {
|
||
if self.rulers == RulerMode::Global {
|
||
return self.ruler_origin_or_zero();
|
||
}
|
||
let Some(ab) = self.artboards.get(index).or_else(|| self.artboards.first()) else { return self.ruler_origin_or_zero() };
|
||
let corner = Point::new(ab.rect.x0, ab.rect.y0);
|
||
let offset = self.artboard_ruler_origins.get(&ab.id).copied().unwrap_or(Vec2::ZERO);
|
||
let origin = corner + offset;
|
||
if origin.is_finite() && origin.x.abs() <= MAX_COORD && origin.y.abs() <= MAX_COORD {
|
||
origin
|
||
} else if corner.is_finite() && corner.x.abs() <= MAX_COORD && corner.y.abs() <= MAX_COORD {
|
||
corner
|
||
} else {
|
||
Point::ZERO
|
||
}
|
||
}
|
||
|
||
/// A new RGB document with one artboard of `size` and one layer ("Layer 1").
|
||
pub fn new(width: f64, height: f64) -> Self {
|
||
Self::new_with_mode(width, height, ColorMode::Rgb)
|
||
}
|
||
|
||
/// [`Document::new`] in colour `mode`, with that mode's default swatches.
|
||
pub fn new_with_mode(width: f64, height: f64, mode: ColorMode) -> Self {
|
||
let (swatches, swatch_groups) = vectorcraft_color::default_swatches(mode.model());
|
||
let mut d = Self {
|
||
version: FORMAT_VERSION,
|
||
title: "Untitled-1".into(),
|
||
template: false,
|
||
units: Unit::Points,
|
||
ruler_origin: Point::ZERO,
|
||
rulers: RulerMode::Artboard,
|
||
artboard_ruler_origins: BTreeMap::new(),
|
||
color_mode: mode,
|
||
artboards: vec![Artboard {
|
||
id: 1,
|
||
name: "Artboard 1".into(),
|
||
rect: Rect::new(0.0, 0.0, width, height),
|
||
locked: false,
|
||
show_center_mark: false,
|
||
show_cross_hairs: false,
|
||
show_video_safe_areas: false,
|
||
video_ruler_pixel_aspect_ratio: 1.0,
|
||
color: None,
|
||
}],
|
||
layers: vec![],
|
||
swatches: swatches.into(),
|
||
swatch_groups: swatch_groups.into(),
|
||
graphic_styles: default_graphic_styles().into(),
|
||
char_styles: Default::default(),
|
||
para_styles: Default::default(),
|
||
text_threads: vec![],
|
||
symbols: vec![],
|
||
graph_designs: vec![],
|
||
guides: vec![],
|
||
views: vec![],
|
||
saved_selections: Default::default(),
|
||
variables: Default::default(),
|
||
grid: GridPrefs::default(),
|
||
raster_effects_ppi: 72.0,
|
||
images: BTreeMap::new(),
|
||
patterns: vec![],
|
||
pattern_edit: None,
|
||
mask_edit: None,
|
||
next_id: 1,
|
||
unknown: Default::default(),
|
||
page_isolate: false,
|
||
page_knockout: false,
|
||
spot_use_lab: true,
|
||
setup: DocSetup::default(),
|
||
paste_remembers_layers: false,
|
||
metadata: DocMetadata::default(),
|
||
raster_effects: RasterEffectsSettings::default(),
|
||
last_view: None,
|
||
transparency_grid: false,
|
||
layers_open: None,
|
||
color_profiles: ColorProfiles::default(),
|
||
export_settings: Default::default(),
|
||
extra: Default::default(),
|
||
slices: vec![],
|
||
slices_clip_to_artboard: true,
|
||
print_setup: None,
|
||
assets: vec![],
|
||
svg_scripts: vec![],
|
||
puppet: None,
|
||
new_art: Default::default(),
|
||
dimensions: false,
|
||
retired_artboard_id: Default::default(),
|
||
index: Default::default(),
|
||
};
|
||
let id = d.alloc_id();
|
||
d.layers.push(Arc::new(Node::layer(id, "Layer 1", LayerColor::Preset(0))));
|
||
d
|
||
}
|
||
|
||
/// Allocate a fresh node id.
|
||
pub fn alloc_id(&mut self) -> NodeId {
|
||
let id = NodeId(self.next_id);
|
||
// Never reached from a counter [`Self::fix_next_id`] has seen (it stays under
|
||
// [`MAX_NEXT_ID`]); saturating so that no counter can overflow.
|
||
self.next_id = self.next_id.saturating_add(1);
|
||
id
|
||
}
|
||
|
||
pub fn peek_next_id(&self) -> u64 {
|
||
self.next_id
|
||
}
|
||
|
||
/// Ensure `next_id` is above every id in the tree (after deserializing foreign data).
|
||
pub fn fix_next_id(&mut self) {
|
||
// Ids from [`MAX_NEXT_ID`] up (a damaged or hand-made file: #1718) are left out, as is a
|
||
// counter up there: new ids start under them and never reach them, where counting on from
|
||
// the largest would run out and hand out ids the document already uses.
|
||
let mut max = 0;
|
||
let mut see = |id: u64| {
|
||
if id < MAX_NEXT_ID {
|
||
max = max.max(id);
|
||
}
|
||
};
|
||
for l in &self.layers {
|
||
l.walk(&mut |n| see(n.id.0));
|
||
}
|
||
self.slices.iter().for_each(|s| see(s.id.0));
|
||
self.assets.iter().for_each(|a| see(a.id));
|
||
let counter = Some(self.next_id).filter(|n| *n <= MAX_NEXT_ID).unwrap_or(0);
|
||
self.next_id = counter.max(max.saturating_add(1));
|
||
}
|
||
|
||
/// Find a node anywhere in the tree.
|
||
pub fn node(&self, id: NodeId) -> Option<&Node> {
|
||
self.index_path(id).and_then(|p| self.node_at(&p))
|
||
}
|
||
|
||
/// [`Document::node`] without building the lookup cache when there is none: for a look at a
|
||
/// document kept in the undo history ([`Document::drop_lookup_cache`]).
|
||
pub fn node_uncached(&self, id: NodeId) -> Option<&Node> {
|
||
match self.index.is_built() {
|
||
true => self.node(id),
|
||
false => self.walk_index_path(id).and_then(|p| self.node_at(&p)),
|
||
}
|
||
}
|
||
|
||
/// Let go of the lookup cache ([`id_index`]), as a document kept in the undo history does: a
|
||
/// table per step would add up. Lookups still work; the first one builds the cache again.
|
||
pub fn drop_lookup_cache(&self) {
|
||
self.index.clear();
|
||
}
|
||
|
||
/// Whether the lookup cache ([`id_index`]) is built (see [`Document::drop_lookup_cache`]).
|
||
pub fn has_lookup_cache(&self) -> bool {
|
||
self.index.is_built()
|
||
}
|
||
|
||
/// Index path from the top-level layer list to `id` (e.g. `[0, 3, 1]`): the first place in
|
||
/// tree order where `id` appears.
|
||
pub fn index_path(&self, id: NodeId) -> Option<Vec<usize>> {
|
||
// The cached path, while the node there still has the id ([`id_index`]).
|
||
let cached = self.index.get();
|
||
match cached.as_ref().and_then(|m| m.get(&id)) {
|
||
Some(Some(p)) if self.node_at(&p).is_some_and(|n| n.id == id) => return Some(p),
|
||
Some(None) => return self.walk_index_path(id),
|
||
_ => {}
|
||
}
|
||
// A stale path (the tree changed), an id the cache doesn't have (deleted or added since),
|
||
// or no cache yet: walk, and rebuild now and then.
|
||
let found = self.walk_index_path(id);
|
||
let stale = cached.as_ref().is_some_and(|m| m.contains_key(&id));
|
||
if cached.is_none() || ((stale || found.is_some()) && self.index.missed()) {
|
||
self.index.rebuild(&self.layers);
|
||
}
|
||
found
|
||
}
|
||
|
||
/// [`Document::index_path`] by walking the tree.
|
||
pub(crate) fn walk_index_path(&self, id: NodeId) -> Option<Vec<usize>> {
|
||
fn find(nodes: &[Arc<Node>], id: NodeId, path: &mut Vec<usize>) -> bool {
|
||
for (i, n) in nodes.iter().enumerate() {
|
||
path.push(i);
|
||
if n.id == id {
|
||
return true;
|
||
}
|
||
if let Some(ch) = n.children()
|
||
&& find(ch, id, path)
|
||
{
|
||
return true;
|
||
}
|
||
path.pop();
|
||
}
|
||
false
|
||
}
|
||
let mut p = vec![];
|
||
find(&self.layers, id, &mut p).then_some(p)
|
||
}
|
||
|
||
/// [`Document::index_path`] of every id in `ids` that is in the tree, in one walk of it: an
|
||
/// edit of many objects looks them all up for the cost of one search instead of one each. Like
|
||
/// `index_path`, an id that appears twice maps to its first place in tree order.
|
||
pub fn index_paths(&self, ids: impl IntoIterator<Item = NodeId>) -> std::collections::HashMap<NodeId, Vec<usize>> {
|
||
use std::collections::{HashMap, HashSet};
|
||
fn walk(nodes: &[Arc<Node>], want: &HashSet<NodeId>, path: &mut Vec<usize>, out: &mut HashMap<NodeId, Vec<usize>>) -> bool {
|
||
for (i, n) in nodes.iter().enumerate() {
|
||
path.push(i);
|
||
if want.contains(&n.id) && !out.contains_key(&n.id) {
|
||
out.insert(n.id, path.clone());
|
||
if out.len() == want.len() {
|
||
return true;
|
||
}
|
||
}
|
||
if let Some(ch) = n.children()
|
||
&& walk(ch, want, path, out)
|
||
{
|
||
return true;
|
||
}
|
||
path.pop();
|
||
}
|
||
false
|
||
}
|
||
let mut want: HashSet<NodeId> = ids.into_iter().collect();
|
||
let mut out = HashMap::with_capacity(want.len());
|
||
// Cached paths that still check out ([`id_index`]); the rest take one walk. For a good
|
||
// share of the document, one walk is quicker than checking each cached path.
|
||
if let Some(cache) = self.index.get().filter(|c| want.len().saturating_mul(8) < c.len()) {
|
||
want.retain(|id| match cache.get(id) {
|
||
Some(Some(p)) if self.node_at(&p).is_some_and(|n| n.id == *id) => {
|
||
out.insert(*id, p);
|
||
false
|
||
}
|
||
_ => true,
|
||
});
|
||
}
|
||
if !want.is_empty() {
|
||
let mut rest = HashMap::with_capacity(want.len());
|
||
walk(&self.layers, &want, &mut Vec::new(), &mut rest);
|
||
out.extend(rest);
|
||
}
|
||
out
|
||
}
|
||
|
||
/// The node at index path `path` (as [`Document::index_path`] gives it).
|
||
pub fn node_at(&self, path: &[usize]) -> Option<&Node> {
|
||
let (first, rest) = path.split_first()?;
|
||
let mut cur: &Node = self.layers.get(*first)?;
|
||
for &i in rest {
|
||
cur = cur.children()?.get(i)?;
|
||
}
|
||
Some(cur)
|
||
}
|
||
|
||
/// Mutable access to the node at index path `path`; clones the Arc path (copy-on-write), as
|
||
/// [`Document::node_mut`] does.
|
||
pub fn node_mut_at(&mut self, path: &[usize]) -> Option<&mut Node> {
|
||
let (first, rest) = path.split_first()?;
|
||
let mut cur: &mut Arc<Node> = self.layers.get_mut(*first)?;
|
||
for &i in rest {
|
||
cur = Arc::make_mut(cur).children_mut()?.get_mut(i)?;
|
||
}
|
||
Some(Arc::make_mut(cur))
|
||
}
|
||
|
||
/// [`Document::ancestry`] of the node at index path `path`.
|
||
pub fn ancestry_at(&self, path: &[usize]) -> Option<Vec<NodeId>> {
|
||
let mut out = Vec::with_capacity(path.len());
|
||
let mut nodes = &self.layers;
|
||
for &i in path {
|
||
let n = nodes.get(i)?;
|
||
out.push(n.id);
|
||
if let Some(ch) = n.children() {
|
||
nodes = ch;
|
||
}
|
||
}
|
||
Some(out)
|
||
}
|
||
|
||
/// Ids from the top-level layer down to `id` inclusive.
|
||
pub fn ancestry(&self, id: NodeId) -> Option<Vec<NodeId>> {
|
||
let path = self.index_path(id)?;
|
||
let mut out = Vec::with_capacity(path.len());
|
||
let mut nodes = &self.layers;
|
||
for &i in &path {
|
||
let n = &nodes[i];
|
||
out.push(n.id);
|
||
if let Some(ch) = n.children() {
|
||
nodes = ch;
|
||
}
|
||
}
|
||
Some(out)
|
||
}
|
||
|
||
pub fn parent_of(&self, id: NodeId) -> Option<NodeId> {
|
||
let a = self.ancestry(id)?;
|
||
(a.len() >= 2).then(|| a[a.len() - 2])
|
||
}
|
||
|
||
/// The top-level layer that contains `id`.
|
||
pub fn layer_of(&self, id: NodeId) -> Option<NodeId> {
|
||
self.ancestry(id).and_then(|a| a.first().copied())
|
||
}
|
||
|
||
/// Mutable access to a node; clones the Arc path (copy-on-write).
|
||
pub fn node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
|
||
let path = self.index_path(id)?;
|
||
let mut cur: &mut Arc<Node> = &mut self.layers[path[0]];
|
||
for &i in &path[1..] {
|
||
cur = &mut Arc::make_mut(cur).children_mut()?[i];
|
||
}
|
||
Some(Arc::make_mut(cur))
|
||
}
|
||
|
||
/// Children list of a container (or the top-level layer list for `None`).
|
||
pub fn children_mut(&mut self, parent: Option<NodeId>) -> Result<&mut Vec<Arc<Node>>, DocError> {
|
||
match parent {
|
||
None => Ok(&mut self.layers),
|
||
Some(p) => self.node_mut(p).ok_or(DocError::NoNode(p))?.children_mut().ok_or(DocError::NotContainer(p)),
|
||
}
|
||
}
|
||
|
||
pub fn children(&self, parent: Option<NodeId>) -> Option<&Vec<Arc<Node>>> {
|
||
match parent {
|
||
None => Some(&self.layers),
|
||
Some(p) => self.node(p)?.children(),
|
||
}
|
||
}
|
||
|
||
/// Insert `node` into `parent` at `index` (clamped; `usize::MAX` = top).
|
||
pub fn insert(&mut self, parent: Option<NodeId>, index: usize, node: Node) -> Result<NodeId, DocError> {
|
||
let id = node.id;
|
||
let ch = self.children_mut(parent)?;
|
||
let i = index.min(ch.len());
|
||
ch.insert(i, Arc::new(node));
|
||
Ok(id)
|
||
}
|
||
|
||
/// Remove a node and return it.
|
||
pub fn remove(&mut self, id: NodeId) -> Result<Arc<Node>, DocError> {
|
||
let parent = self.parent_of(id);
|
||
let path = self.index_path(id).ok_or(DocError::NoNode(id))?;
|
||
let idx = *path.last().ok_or(DocError::NoNode(id))?;
|
||
let ch = self.children_mut(parent)?;
|
||
if idx >= ch.len() {
|
||
return Err(DocError::NoNode(id));
|
||
}
|
||
Ok(ch.remove(idx))
|
||
}
|
||
|
||
/// Move a node to `parent` at `index` (index measured after removal).
|
||
pub fn move_node(&mut self, id: NodeId, parent: Option<NodeId>, index: usize) -> Result<(), DocError> {
|
||
if let Some(p) = parent
|
||
&& self.ancestry(p).is_some_and(|a| a.contains(&id))
|
||
{
|
||
return Err(DocError::Invalid("cannot move an object into itself".into()));
|
||
}
|
||
let n = self.remove(id)?;
|
||
let ch = self.children_mut(parent)?;
|
||
let i = index.min(ch.len());
|
||
ch.insert(i, n);
|
||
Ok(())
|
||
}
|
||
|
||
/// Position of `id` within its parent: (parent, index, sibling count).
|
||
pub fn position(&self, id: NodeId) -> Option<(Option<NodeId>, usize, usize)> {
|
||
let parent = self.parent_of(id);
|
||
let idx = *self.index_path(id)?.last()?;
|
||
let n = self.children(parent)?.len();
|
||
Some((parent, idx, n))
|
||
}
|
||
|
||
/// Remove the nodes `ids` names and return them in the order of `ids`, in one pass over the
|
||
/// children of each parent. Each id is listed once and none lies inside another listed node, as
|
||
/// in a list of top-level objects. For an id that is not in the tree, is listed twice or lies
|
||
/// inside another listed node, it fails with `NoNode` and leaves the document as it was.
|
||
pub fn remove_many(&mut self, ids: &[NodeId]) -> Result<Vec<Arc<Node>>, DocError> {
|
||
let places = self.places(ids)?;
|
||
let removed = self.take(places, ids.len())?;
|
||
self.index.clear();
|
||
Ok(removed)
|
||
}
|
||
|
||
/// Remove the nodes `ids` names, as [`Document::remove_many`] does, and put the node that `make`
|
||
/// builds from them (in the order of `ids`) in the parent of the last of them, at that node's
|
||
/// index less the number of listed nodes below it there. Returns the new node's id.
|
||
pub fn wrap(&mut self, ids: &[NodeId], make: impl FnOnce(Vec<Arc<Node>>) -> Node) -> Result<NodeId, DocError> {
|
||
let places = self.places(ids)?;
|
||
let last = ids.len().checked_sub(1);
|
||
let found = places.iter().find_map(|(up, kids)| kids.iter().find(|(_, k)| Some(*k) == last).map(|(i, _)| (up.clone(), *i)));
|
||
let Some((up, i)) = found else { return Err(DocError::Invalid("nothing to wrap".into())) };
|
||
// The parent and each node it is in move down by the listed nodes before them.
|
||
let below = |parent: &[usize], index: usize| places.get(parent).map_or(0, |kids| kids.partition_point(|(j, _)| *j < index));
|
||
let to: Vec<usize> = up.iter().enumerate().map(|(l, &j)| j.saturating_sub(below(&up[..l], j))).collect();
|
||
let at = i.saturating_sub(below(&up, i));
|
||
let node = make(self.take(places, ids.len())?);
|
||
let id = node.id;
|
||
let children = self.children_at(&to).ok_or_else(|| DocError::Invalid("the container of the listed objects is gone".into()))?;
|
||
let at = at.min(children.len());
|
||
children.insert(at, Arc::new(node));
|
||
self.index.clear();
|
||
Ok(id)
|
||
}
|
||
|
||
/// Put each list of nodes in `items` directly above its anchor (`replace`: in its place), in
|
||
/// list order, in one pass over the children of each parent. Each anchor is listed once and none
|
||
/// lies inside another. For an anchor that is not in the tree, is listed twice or lies inside
|
||
/// another listed anchor, it fails with `NoNode` and leaves the document as it was.
|
||
pub fn insert_beside(&mut self, items: Vec<(NodeId, Vec<Node>)>, replace: bool) -> Result<(), DocError> {
|
||
let anchors: Vec<NodeId> = items.iter().map(|(id, _)| *id).collect();
|
||
let places = self.places(&anchors)?;
|
||
let mut lists: Vec<Vec<Node>> = items.into_iter().map(|(_, nodes)| nodes).collect();
|
||
self.edit_children(places, |k, anchor, children| {
|
||
if !replace {
|
||
children.push(anchor);
|
||
}
|
||
let nodes = lists.get_mut(k).map(std::mem::take).unwrap_or_default();
|
||
children.extend(nodes.into_iter().map(Arc::new));
|
||
})?;
|
||
self.index.clear();
|
||
Ok(())
|
||
}
|
||
|
||
/// Where each of `ids` is ([`Places`]). Fails with `NoNode` for an id that is not in the tree,
|
||
/// is listed twice or lies inside another listed node.
|
||
fn places(&self, ids: &[NodeId]) -> Result<Places, DocError> {
|
||
let paths = self.index_paths(ids.iter().copied());
|
||
let mut places = Places::new();
|
||
for (k, id) in ids.iter().enumerate() {
|
||
let (&i, up) = paths.get(id).and_then(|p| p.split_last()).ok_or(DocError::NoNode(*id))?;
|
||
match places.get_mut(up) {
|
||
Some(kids) => kids.push((i, k)),
|
||
None => {
|
||
places.insert(up.to_vec(), vec![(i, k)]);
|
||
}
|
||
}
|
||
}
|
||
for kids in places.values_mut() {
|
||
kids.sort_unstable();
|
||
}
|
||
// An id listed twice has one path, so its parent lists the same child twice. A listed node
|
||
// lies inside another when its parent's path, or a part of it from the top, is the path of a
|
||
// listed node.
|
||
let twice = places.values().flat_map(|kids| kids.windows(2)).find(|w| w[0].0 == w[1].0).map(|w| w[1].1);
|
||
let listed = |path: &[usize]| {
|
||
path.split_last().is_some_and(|(i, up)| places.get(up).is_some_and(|kids| kids.binary_search_by_key(i, |(j, _)| *j).is_ok()))
|
||
};
|
||
let nested = places.iter().find(|(up, _)| (1..=up.len()).any(|l| listed(&up[..l])));
|
||
let inner = nested.and_then(|(_, kids)| kids.first()).map(|(_, k)| *k);
|
||
if let Some(id) = twice.or(inner).and_then(|k| ids.get(k)) {
|
||
return Err(DocError::NoNode(*id));
|
||
}
|
||
Ok(places)
|
||
}
|
||
|
||
/// Take the nodes `places` lists out of their parents and return them in the order of the list,
|
||
/// which is `len` long.
|
||
fn take(&mut self, places: Places, len: usize) -> Result<Vec<Arc<Node>>, DocError> {
|
||
let mut taken = vec![None; len];
|
||
self.edit_children(places, |k, node, _| {
|
||
if let Some(t) = taken.get_mut(k) {
|
||
*t = Some(node);
|
||
}
|
||
})?;
|
||
Ok(taken.into_iter().flatten().collect())
|
||
}
|
||
|
||
/// Rebuild the children of each parent in `places` once, the parents later in tree order
|
||
/// first. `each` gets each listed child, its place in the list and the parent's new children,
|
||
/// onto which it pushes what takes the child's place.
|
||
fn edit_children(&mut self, places: Places, mut each: impl FnMut(usize, Arc<Node>, &mut Vec<Arc<Node>>)) -> Result<(), DocError> {
|
||
for (up, kids) in places.into_iter().rev() {
|
||
let children = self.children_at(&up).ok_or_else(|| DocError::Invalid("a container of the listed objects is gone".into()))?;
|
||
let old = std::mem::take(children);
|
||
children.reserve(old.len());
|
||
let mut kids = kids.into_iter().peekable();
|
||
for (i, node) in old.into_iter().enumerate() {
|
||
match kids.next_if(|(j, _)| *j == i) {
|
||
Some((_, k)) => each(k, node, children),
|
||
None => children.push(node),
|
||
}
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// The children of the node at index path `path`, or the top-level layers for an empty path.
|
||
fn children_at(&mut self, path: &[usize]) -> Option<&mut Vec<Arc<Node>>> {
|
||
match path {
|
||
[] => Some(&mut self.layers),
|
||
_ => self.node_mut_at(path)?.children_mut(),
|
||
}
|
||
}
|
||
|
||
/// Default target layer: the topmost visible, unlocked layer.
|
||
pub fn default_layer(&self) -> Option<NodeId> {
|
||
self.layers.iter().rev().find(|l| l.visible && !l.locked).or(self.layers.last()).map(|l| l.id)
|
||
}
|
||
|
||
/// Add a new top-level layer above all others.
|
||
pub fn add_layer(&mut self, name: Option<&str>) -> NodeId {
|
||
let id = self.alloc_id();
|
||
let n = self.layers.len();
|
||
let name = name.map(str::to_string).unwrap_or_else(|| self.next_layer_name());
|
||
self.layers.push(Arc::new(Node::layer(id, &name, LayerColor::Preset((n % LAYER_COLORS.len()) as u8))));
|
||
id
|
||
}
|
||
|
||
/// The default name of a new layer or sublayer, "Layer N". Layers and sublayers share the
|
||
/// numbering: it starts after the number of layers there are and skips any layer's name.
|
||
pub fn next_layer_name(&self) -> String {
|
||
let (mut count, mut taken) = (0, std::collections::HashSet::new());
|
||
self.walk(|n| {
|
||
if n.is_layer() {
|
||
count += 1;
|
||
taken.extend(n.name.as_deref());
|
||
}
|
||
});
|
||
(count + 1..).map(|i| format!("Layer {i}")).find(|name| !taken.contains(name.as_str())).unwrap_or_default()
|
||
}
|
||
|
||
/// Visit every node depth first in paint order (bottom to top).
|
||
pub fn walk<'a>(&'a self, mut f: impl FnMut(&'a Node)) {
|
||
for l in &self.layers {
|
||
l.walk(&mut f);
|
||
}
|
||
}
|
||
|
||
pub fn node_count(&self) -> usize {
|
||
self.layers.iter().map(|l| l.count()).sum()
|
||
}
|
||
|
||
/// Is the node (and every ancestor) visible and unlocked, and not on a locked artboard
|
||
/// ([`Document::on_locked_artboard`])?
|
||
pub fn is_editable(&self, id: NodeId) -> bool {
|
||
self.is_editable_with(&self.locked_artboards(), id)
|
||
}
|
||
|
||
/// [`Document::is_editable`] with the locked artboards `locks` of this document, measured
|
||
/// once for many ids.
|
||
pub fn is_editable_with(&self, locks: &LockedArtboards, id: NodeId) -> bool {
|
||
self.ancestry(id).is_some_and(|a| a.iter().all(|i| self.node(*i).is_some_and(|n| n.visible && !n.locked))) && !locks.holds_id(id)
|
||
}
|
||
|
||
/// [`Document::is_editable`] for many ids: the art on the locked artboards is measured once
|
||
/// for all of them, a group once for all its members.
|
||
pub fn editable(&self) -> impl Fn(NodeId) -> bool + '_ {
|
||
let locks = self.locked_artboards();
|
||
move |id| self.is_editable_with(&locks, id)
|
||
}
|
||
|
||
/// The locked artboards, for asking which art is on them ([`LockedArtboards::holds`]).
|
||
pub fn locked_artboards(&self) -> LockedArtboards<'_> {
|
||
self.locked_artboards_except(usize::MAX)
|
||
}
|
||
|
||
/// [`Document::locked_artboards`] leaving out artboard `index`.
|
||
pub fn locked_artboards_except(&self, index: usize) -> LockedArtboards<'_> {
|
||
// Art flush with an edge from outside can overlap it by a rounding error, as in
|
||
// `art_bounds_on` (#1295).
|
||
let rects: Vec<Rect> =
|
||
self.artboards.iter().enumerate().filter(|(i, a)| a.locked && *i != index).map(|(_, a)| a.rect.inflate(-1e-6, -1e-6)).collect();
|
||
// The objects of pattern editing mode's layer, a sublayer's objects included.
|
||
fn objects(n: &Node, out: &mut std::collections::HashSet<NodeId>) {
|
||
for c in n.children().into_iter().flatten() {
|
||
if c.is_layer() {
|
||
objects(c, out);
|
||
} else {
|
||
out.insert(c.id);
|
||
}
|
||
}
|
||
}
|
||
let mut pattern_art = std::collections::HashSet::new();
|
||
if let Some(layer) = self.pattern_edit.as_ref().filter(|_| !rects.is_empty()).and_then(|e| self.layers.iter().find(|l| l.id == e.layer)) {
|
||
objects(layer, &mut pattern_art);
|
||
}
|
||
LockedArtboards { rects, symbols: self.symbol_boxes(), new_art: self.new_art.get(), pattern_art, held: Default::default() }
|
||
}
|
||
|
||
/// Is `id` on a locked artboard ([`LockedArtboards::holds_id`])? A layer or sublayer is on none.
|
||
pub fn on_locked_artboard(&self, id: NodeId) -> bool {
|
||
self.locked_artboards().holds_id(id)
|
||
}
|
||
|
||
/// The object of a layer or sublayer that holds node `id`: `id` itself when it is one, else
|
||
/// the group or other object around it right inside a layer. None for a layer or sublayer.
|
||
pub fn layer_object(&self, id: NodeId) -> Option<&Node> {
|
||
self.layer_object_at(&self.index_path(id)?)
|
||
}
|
||
|
||
/// [`Document::layer_object`] for the node at index path `path`.
|
||
pub fn layer_object_at(&self, path: &[usize]) -> Option<&Node> {
|
||
// Down the path to the first node that isn't a layer.
|
||
let mut nodes = &self.layers;
|
||
for &i in path {
|
||
let n = nodes.get(i)?;
|
||
if !n.is_layer() {
|
||
return Some(n.as_ref());
|
||
}
|
||
nodes = n.children()?;
|
||
}
|
||
None
|
||
}
|
||
|
||
pub fn is_visible(&self, id: NodeId) -> bool {
|
||
self.ancestry(id).is_some_and(|a| a.iter().all(|i| self.node(*i).is_some_and(|n| n.visible)))
|
||
}
|
||
|
||
/// The innermost layer or sublayer containing `id` (`id` itself when it is a layer).
|
||
pub fn layer_containing(&self, id: NodeId) -> Option<NodeId> {
|
||
let a = self.ancestry(id)?;
|
||
a.iter().rev().copied().find(|i| self.node(*i).is_some_and(Node::is_layer))
|
||
}
|
||
|
||
/// Every art object the Selection tool can reach (Select All): the visible, unlocked objects
|
||
/// of the visible, unlocked, non-template layers, looking through sublayers (bottom first),
|
||
/// except those on a locked artboard.
|
||
pub fn selectable_art(&self) -> Vec<NodeId> {
|
||
let locks = self.locked_artboards();
|
||
self.layers
|
||
.iter()
|
||
.filter(|l| l.visible && !l.locked && !l.is_template())
|
||
.flat_map(|l| l.layer_art(true))
|
||
.filter(|id| locks.is_empty() || !self.node(*id).is_some_and(|n| locks.holds(n)))
|
||
.collect()
|
||
}
|
||
|
||
/// Colour of the innermost layer or sublayer containing `id` (selection highlight colour).
|
||
pub fn layer_color(&self, id: NodeId) -> [u8; 3] {
|
||
let l = self.layer_containing(id).and_then(|l| self.node(l));
|
||
match l.map(|n| &n.kind) {
|
||
Some(NodeKind::Layer { color, .. }) => color.rgb(),
|
||
_ => LAYER_COLORS[0].1,
|
||
}
|
||
}
|
||
}
|