Merge pull request #7449 from dotCooCoo/fix/links-stored-brush-art

Links: placed images in stored brush art get Links rows
This commit is contained in:
BFlat
2026-11-14 21:55:20 +07:00
committed by GitHub
22 changed files with 916 additions and 111 deletions
+2
View File
@@ -79,6 +79,7 @@ impl Document {
index: _,
guide_cache: _,
inline_cycles: _,
mounted_art: _,
} = self;
*version == other.version
&& *title == other.title
@@ -264,6 +265,7 @@ impl Document {
index: Default::default(),
guide_cache: Default::default(),
inline_cycles: Default::default(),
mounted_art: vec![],
};
let id = d.alloc_id();
d.layers.push(Arc::new(Node::layer(id, "Layer 1", LayerColor::Preset(0))));
+4 -2
View File
@@ -128,8 +128,8 @@ impl Document {
}
/// The roots of every tree of the document's art: the layers, symbol definitions, graph designs,
/// pattern swatches and the original of the pattern being edited. Opacity masks hang off nodes
/// in these trees.
/// pattern swatches, the original of the pattern being edited and [`Self::mounted_art`].
/// Opacity masks hang off nodes in these trees.
pub fn art_roots(&self) -> impl Iterator<Item = &Arc<Node>> {
self.layers
.iter()
@@ -137,6 +137,7 @@ impl Document {
.chain(self.graph_designs.iter().map(|g| &g.art))
.chain(self.patterns.iter().flat_map(|p| p.art.iter()))
.chain(self.pattern_edit.iter().filter_map(|pe| pe.original.as_ref()).flat_map(|p| p.art.iter()))
.chain(self.mounted_art.iter().map(|(n, _)| n))
}
/// [`Self::art_roots`], to change.
@@ -147,6 +148,7 @@ impl Document {
.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()))
.chain(self.mounted_art.iter_mut().map(|(n, _)| n))
}
/// Does `wants` hold for a node anywhere in the document's art ([`Self::art_roots`]) or its
+6
View File
@@ -367,6 +367,12 @@ pub struct Document {
/// saved.
#[serde(skip)]
inline_cycles: inline::InlineCycles,
/// Art kept elsewhere in the document's data (the art of the stored brushes, which
/// `vectorcraft-brush` reads from [`Self::unknown`]) while it is set here, each root with the
/// `owner` the Links panel names for it, so the stored-art lookups ([`StoredRoot::Mounted`])
/// find its objects too. Empty otherwise; never saved.
#[serde(skip)]
pub mounted_art: Vec<(Arc<Node>, serde_json::Value)>,
}
fn ppi72() -> f64 {
+9 -5
View File
@@ -248,14 +248,15 @@ fn walk_all<'a>(n: &'a Node, f: &mut impl FnMut(&'a Node)) {
impl Document {
/// Visit every image object: in the layers, then in symbol definitions and pattern swatches,
/// opacity-mask art included.
/// then in [`Document::mounted_art`], opacity-mask art included.
pub fn visit_images<'a>(&'a self, mut f: impl FnMut(&'a Node, &'a ImageObject)) {
let roots = self
.layers
.iter()
.chain(self.symbols.iter().map(|s| &s.art))
.chain(self.graph_designs.iter().map(|g| &g.art))
.chain(self.patterns.iter().flat_map(|p| &p.art));
.chain(self.patterns.iter().flat_map(|p| &p.art))
.chain(self.mounted_art.iter().map(|(n, _)| n));
for root in roots {
walk_all(root, &mut |n| {
if let NodeKind::Image(im) = &n.kind {
@@ -384,7 +385,8 @@ impl Align {
impl Document {
/// Rewrite linked file locations in both raster images and placed VectorCraft documents,
/// including those inside symbols, patterns and opacity masks. Used to make packages portable.
/// including those inside symbols, patterns, opacity masks and [`Document::mounted_art`]. Used to
/// make packages portable.
pub fn update_links(&mut self, mut f: impl FnMut(&mut LinkInfo)) {
fn walk(n: &mut Arc<Node>, f: &mut dyn FnMut(&mut LinkInfo)) {
let n = Arc::make_mut(n);
@@ -409,7 +411,8 @@ impl Document {
.iter_mut()
.chain(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| &mut p.art));
.chain(self.patterns.iter_mut().flat_map(|p| &mut p.art))
.chain(self.mounted_art.iter_mut().map(|(n, _)| n));
for root in roots {
walk(root, &mut f);
}
@@ -471,7 +474,8 @@ impl Document {
.iter_mut()
.chain(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| &mut p.art));
.chain(self.patterns.iter_mut().flat_map(|p| &mut p.art))
.chain(self.mounted_art.iter_mut().map(|(n, _)| n));
for root in roots {
update(root, &pick, &mut f);
}
+4 -1
View File
@@ -166,13 +166,15 @@ fn walk_all<'a>(n: &'a Node, f: &mut impl FnMut(&'a Node)) {
}
}
/// The roots of `d`'s art: layers, symbol definitions and pattern swatches.
/// The roots of `d`'s art: layers, symbol definitions, pattern swatches and
/// [`Document::mounted_art`].
fn roots(d: &Document) -> impl Iterator<Item = &Arc<Node>> {
d.layers
.iter()
.chain(d.symbols.iter().map(|s| &s.art))
.chain(d.graph_designs.iter().map(|g| &g.art))
.chain(d.patterns.iter().flat_map(|p| &p.art))
.chain(d.mounted_art.iter().map(|(n, _)| n))
}
fn roots_mut(d: &mut Document) -> impl Iterator<Item = &mut Arc<Node>> {
@@ -181,6 +183,7 @@ fn roots_mut(d: &mut Document) -> impl Iterator<Item = &mut Arc<Node>> {
.chain(d.symbols.iter_mut().map(|s| &mut s.art))
.chain(d.graph_designs.iter_mut().map(|g| &mut g.art))
.chain(d.patterns.iter_mut().flat_map(|p| &mut p.art))
.chain(d.mounted_art.iter_mut().map(|(n, _)| n))
}
impl Document {
+57 -5
View File
@@ -20,6 +20,8 @@ pub enum StoredRoot {
GraphDesign(usize),
/// Object `art` of pattern swatch `pattern` of [`Document::patterns`].
Pattern { pattern: usize, art: usize },
/// Root `index` of [`Document::mounted_art`].
Mounted(usize),
}
/// Where a stored object is: its root and, inside opacity-mask art, the object that mask belongs
@@ -170,10 +172,12 @@ impl Document {
.iter()
.enumerate()
.flat_map(|(p, def)| def.art.iter().enumerate().map(move |(a, n)| (n, StoredRoot::Pattern { pattern: p, art: a })));
symbols.chain(designs).chain(patterns)
let mounted = self.mounted_art.iter().enumerate().map(|(i, (n, _))| (n, StoredRoot::Mounted(i)));
symbols.chain(designs).chain(patterns).chain(mounted)
}
/// Every root of stored art: the layers, then the art of symbols, graph designs and patterns.
/// Every root of stored art: the layers, then the art of symbols, graph designs and patterns,
/// then [`Document::mounted_art`].
fn stored_roots(&self) -> impl Iterator<Item = (&Arc<Node>, StoredRoot)> {
self.layers.iter().enumerate().map(|(i, l)| (l, StoredRoot::Layer(i))).chain(self.definition_roots())
}
@@ -185,12 +189,13 @@ impl Document {
StoredRoot::Symbol(i) => self.symbols.get_mut(i).map(|s| &mut s.art),
StoredRoot::GraphDesign(i) => self.graph_designs.get_mut(i).map(|g| &mut g.art),
StoredRoot::Pattern { pattern, art } => self.patterns.get_mut(pattern)?.art.get_mut(art),
StoredRoot::Mounted(i) => self.mounted_art.get_mut(i).map(|(n, _)| n),
}
}
/// Visit every stored object with its place: the layer tree with the opacity-mask art of its
/// objects, then the art of symbols, graph designs and pattern swatches (their definitions,
/// once each, never the copies instances and tiles draw).
/// once each, never the copies instances and tiles draw), then [`Document::mounted_art`].
pub fn visit_stored<'a>(&'a self, mut f: impl FnMut(&'a Node, StoredPlace)) {
self.visit_stored_arcs(|n, place| f(n, place));
}
@@ -238,6 +243,29 @@ impl Document {
/// visited before them (the layer tree's own objects first) a new id, so every stored object
/// can be named by its id. Returns how many changed; the id counter then stands above them.
pub(crate) fn renumber_stored_duplicates(&mut self) -> usize {
self.renumber_duplicates_in(|_| true)
}
/// Raises the id counter above the ids of [`Document::mounted_art`]'s objects, then gives those
/// objects whose id is 0, past the id range, or already taken by an object visited before them
/// (every other stored object first) a new id, as [`Self::renumber_stored_duplicates`] gives
/// them. Returns how many changed.
pub fn number_mounted_art(&mut self) -> usize {
let mut max = 0;
for (i, (root, _)) in self.mounted_art.iter().enumerate() {
visit(root, StoredPlace { root: StoredRoot::Mounted(i), mask_of: None }, &mut |n, _| {
if n.id.0 < MAX_NEXT_ID {
max = max.max(n.id.0);
}
});
}
self.raise_next_id(max.saturating_add(1));
self.renumber_duplicates_in(|r| matches!(r, StoredRoot::Mounted(_)))
}
/// [`Self::renumber_stored_duplicates`] for the roots `pick` holds for only; the objects of the
/// others still count as taken.
fn renumber_duplicates_in(&mut self, pick: impl Fn(StoredRoot) -> bool) -> usize {
let mut seen: HashSet<NodeId> = HashSet::new();
for l in &self.layers {
l.walk(&mut |n| {
@@ -250,9 +278,9 @@ impl Document {
// Per root, the visiting indices of the nodes that take a new id.
let mut fresh: Vec<(StoredRoot, HashSet<usize>)> = vec![];
for (root, r) in self.stored_roots() {
let (mut i, mut here) = (0, HashSet::new());
let (mut i, mut here, take) = (0, HashSet::new(), pick(r));
visit(root, StoredPlace { root: r, mask_of: None }, &mut |n, place| {
if !place.in_layers() && (n.id.0 == 0 || n.id.0 >= MAX_NEXT_ID || !seen.insert(n.id)) {
if !place.in_layers() && (n.id.0 == 0 || n.id.0 >= MAX_NEXT_ID || !seen.insert(n.id)) && take {
here.insert(i);
}
i += 1;
@@ -355,6 +383,30 @@ mod tests {
assert_eq!(d, again);
}
/// Mounted art is found and changed by id after the other stored art, and numbering it gives
/// its objects that repeat another stored object's id, or have none, new ids above the counter,
/// leaving every other object's id and its own unique ones as they are.
#[test]
fn mounted_art_is_found_and_numbered_after_the_rest() {
let mut d = doc([12, 13, 14]);
let owner = serde_json::json!({ "kind": "brush" });
d.mounted_art = vec![(Arc::new(masked(10, rect(0))), owner.clone()), (Arc::new(rect(15)), owner)];
assert_eq!(d.stored_node(NodeId(15)).map(|(_, p)| p.root), Some(StoredRoot::Mounted(1)));
let before = d.clone();
assert_eq!(d.number_mounted_art(), 2);
let mut ids = vec![];
d.visit_stored(|n, _| ids.push(n.id.0));
let unique: HashSet<u64> = ids.iter().copied().collect();
assert_eq!(unique.len(), ids.len(), "{ids:?}");
assert_eq!(&ids[..6], &[d.layers[0].id.0, 10, 11, 12, 13, 14], "the other art keeps its ids");
assert_eq!(ids[8], 15, "a unique id is kept");
assert!(ids.iter().all(|id| *id < d.peek_next_id()), "{ids:?} {}", d.peek_next_id());
assert!(Arc::ptr_eq(&d.symbols[0].art, &before.symbols[0].art) && Arc::ptr_eq(&d.mounted_art[1].0, &before.mounted_art[1].0));
d.stored_node_mut(NodeId(15)).unwrap().name = Some("changed".into());
assert_eq!(d.mounted_art[1].0.name.as_deref(), Some("changed"));
assert_eq!(d.number_mounted_art(), 0, "a second pass changes nothing");
}
#[test]
fn renumbered_objects_on_path_keep_their_path_and_members() {
// A symbol's Objects on Path group whose path repeats the layer's object 10 and whose