Files
vectorcraft/crates/doc/src/depth.rs
T

589 lines
26 KiB
Rust

//! How deep objects may nest.
//!
//! The tree is walked by recursion everywhere (drawing, bounds, hit tests, saving, exporting), so
//! a tree of unbounded depth overflows the stack: an abort no guard catches. No document deeper
//! than [`MAX_DEPTH`] is ever kept, nor one whose symbols and patterns draw deeper than
//! [`MAX_DRAWN_DEPTH`] (a symbol holding an instance of itself, or a pattern painting its own
//! tile, draws without end): importers and the native reader merge deeper objects into the group
//! around them and leave such instances and paints out
//! ([`Document::limit_depth`], with [`merged_note`] and [`symbols_note`] as their warnings), and
//! the engine refuses an edit that would nest deeper ([`Document::too_deep`]).
use std::collections::HashMap;
use std::sync::Arc;
use vectorcraft_color::Paint;
use crate::{Document, Node, NodeKind};
/// The deepest an object may be: a top-level layer is 1 deep, what it holds 2, and so on (the art
/// of an opacity mask is one deeper than the object it masks). A symbol's or pattern's art counts
/// from 1 again.
///
/// A native file writes three levels of JSON for each level of objects, and its reader stops at
/// 128 levels: 32 leaves the deepest object 29 of them for its own data, so every document that
/// can be made can be saved and opened again.
pub const MAX_DEPTH: usize = 32;
/// The deepest an object may be drawn: as [`MAX_DEPTH`], the art of a symbol counted under its
/// instance and a pattern's tile under the object it paints (and what those use, under them).
/// Symbols a few objects deep nest in one another dozens deep within it.
pub const MAX_DRAWN_DEPTH: usize = 4 * MAX_DEPTH;
/// The warning of a file whose objects were nested deeper than [`MAX_DEPTH`].
pub fn merged_note() -> String {
format!("objects nested more than {MAX_DEPTH} deep were merged into the group around them")
}
/// The warning of a file with symbols or patterns that use themselves or are drawn deeper than
/// [`MAX_DRAWN_DEPTH`].
pub fn symbols_note() -> String {
format!(
"symbol instances and pattern paints inside their own symbol or pattern, or drawn more than {MAX_DRAWN_DEPTH} objects deep, were left out"
)
}
/// Run `f` where recursion has room: on a thread of its own with a 16 MB stack (the main thread
/// has 1 MB on Windows), or here where there are no threads (the web) or none can be started. For
/// readers whose recursion is capped but heavy; a panic in `f` goes on to the caller.
pub fn with_deep_stack<T: Send>(f: impl Fn() -> T + Sync) -> T {
#[cfg(not(target_arch = "wasm32"))]
{
let threaded = std::thread::scope(|s| {
let thread = std::thread::Builder::new().stack_size(16 << 20).spawn_scoped(s, &f).ok()?;
Some(thread.join().unwrap_or_else(|panic| std::panic::resume_unwind(panic)))
});
if let Some(done) = threaded {
return done;
}
}
f()
}
/// Is anything in `now` (each `depth` deep) deeper than [`MAX_DEPTH`], or `over` (given its
/// depth)? Without recursion: the trees may be of any depth. With `before` (the same list before
/// an edit, within the caps), only what the edit changed is looked at: a subtree still shared with
/// it, in the same place, is as it was.
fn deeper(now: &[Arc<Node>], before: Option<&[Arc<Node>]>, depth: usize, over: &dyn Fn(&Node, usize) -> bool) -> bool {
type Pending<'a> = Vec<(&'a Node, Option<&'a Node>, usize)>;
fn push<'a>(stack: &mut Pending<'a>, now: &'a [Arc<Node>], before: Option<&'a [Arc<Node>]>, depth: usize) {
for (i, n) in now.iter().enumerate() {
let was = before.and_then(|b| b.get(i));
if !was.is_some_and(|w| Arc::ptr_eq(w, n)) {
stack.push((n, was.map(|w| &**w), depth));
}
}
}
let mut stack = vec![];
push(&mut stack, now, before, depth);
while let Some((n, was, d)) = stack.pop() {
if d > MAX_DEPTH || over(n, d) {
return true;
}
if let Some(ch) = n.children() {
push(&mut stack, ch, was.and_then(Node::children).map(Vec::as_slice), d + 1);
}
if let Some(m) = &n.extras().mask {
let was = was.and_then(|w| w.extras().mask.as_ref()).map(|m| std::slice::from_ref(&m.art));
push(&mut stack, std::slice::from_ref(&m.art), was, d + 1);
}
}
false
}
/// [`deeper`] for the nesting alone.
fn nested_deeper(now: &[Arc<Node>], depth: usize) -> bool {
deeper(now, None, depth, &|_, _| false)
}
/// Merge what is deeper than [`MAX_DEPTH`] in `nodes` (each `depth` deep) into the group around
/// it: a container at the cap is replaced by what it holds (a clipping group's clipping path and
/// an opacity mask too deep are left out); what it held stays hidden or locked as it was. Returns
/// whether anything changed. Recurses no deeper than the cap.
fn limit(nodes: &mut Vec<Arc<Node>>, depth: usize) -> bool {
let mut changed = false;
let mut i = 0;
while let Some(n) = nodes.get_mut(i) {
if !nested_deeper(std::slice::from_ref(n), depth) {
i += 1;
continue;
}
changed = true;
let n = Arc::make_mut(n);
if n.extras().mask.as_ref().is_some_and(|m| nested_deeper(std::slice::from_ref(&m.art), depth + 1)) {
n.extras_mut().mask = None;
}
if depth < MAX_DEPTH {
if let Some(ch) = n.children_mut() {
limit(ch, depth + 1);
}
i += 1;
continue;
}
let clip = matches!(n.kind, NodeKind::Group { clip: true, .. } | NodeKind::Layer { clip: true, .. });
let (visible, locked) = (n.visible, n.locked);
let mut held = n.children_mut().map(std::mem::take).unwrap_or_default();
if clip && !held.is_empty() {
held.remove(0);
}
for c in &mut held {
if (!visible && c.visible) || (locked && !c.locked) {
let c = Arc::make_mut(c);
c.visible &= visible;
c.locked |= locked;
}
}
// What it held takes its place, and is looked at in its turn.
nodes.splice(i..=i, held);
}
changed
}
/// A symbol (`false`) or a pattern (`true`) of the document, by name: what art draws by name, an
/// instance its symbol's art and a pattern paint its tile's.
type Def<'a> = (bool, &'a str);
/// How deep each symbol's and pattern's art is drawn, the art of the symbols and patterns it uses
/// included (drawn under the object that uses them): `None` for one that uses itself, in its art
/// or through what that uses, or is drawn deeper than [`MAX_DRAWN_DEPTH`].
type Heights<'a> = HashMap<Def<'a>, Option<usize>>;
/// What `n` itself draws by name: the symbol it is an instance of, the patterns that paint it.
fn uses(n: &Node) -> impl Iterator<Item = Def<'_>> {
let symbol = match &n.kind {
NodeKind::SymbolInstance { symbol, .. } => Some((false, symbol.as_str())),
_ => None,
};
let patterns = crate::swatches::node_paints(n).filter_map(|p| match p {
Paint::Pattern { pattern, .. } => Some((true, pattern.as_str())),
_ => None,
});
symbol.into_iter().chain(patterns)
}
/// [`Heights`] of the document's symbols and patterns. Without recursion: they may use one
/// another in a chain of any length. Their own art is within [`MAX_DEPTH`].
fn heights(doc: &Document) -> Heights<'_> {
// Each one's own depth, and what its art uses with the deepest use of each.
type Own<'a> = (usize, Vec<(Def<'a>, usize)>);
let arts = (doc.symbols.iter().map(|s| ((false, s.name.as_str()), std::slice::from_ref(&s.art))))
.chain(doc.patterns.iter().map(|p| ((true, p.name.as_str()), &p.art[..])));
let mut own: HashMap<Def, Own> = HashMap::new();
for (def, art) in arts {
let (mut deepest, mut used) = (0, HashMap::<Def, usize>::new());
let mut stack: Vec<(&Node, usize)> = art.iter().map(|n| (&**n, 1)).collect();
while let Some((n, d)) = stack.pop() {
deepest = deepest.max(d);
for u in uses(n) {
let at = used.entry(u).or_default();
*at = (*at).max(d);
}
stack.extend(n.children().into_iter().flatten().chain(n.extras().mask.as_ref().map(|m| &m.art)).map(|c| (&**c, d + 1)));
}
// (The first of two of one name is the one drawn.)
own.entry(def).or_insert((deepest, used.into_iter().collect()));
}
// Depth first over what uses what. One met again while it is being worked out (`None` in its
// entry) uses itself.
let mut found: HashMap<Def, Option<Option<usize>>> = HashMap::new();
let starts: Vec<Def> = own.keys().copied().collect();
for start in starts {
if found.contains_key(&start) {
continue;
}
// (which, the next of its uses to look at, the deepest found so far: `None` once it can't be drawn)
let mut path: Vec<(Def, usize, Option<usize>)> = vec![(start, 0, own.get(&start).map(|o| o.0))];
found.insert(start, None);
while let Some(&(def, next, deepest)) = path.last() {
let used = own.get(&def).map_or(&[][..], |o| &o.1);
let Some(&(other, at)) = used.get(next) else {
// Everything it uses is worked out.
let deepest = deepest.filter(|d| *d <= MAX_DRAWN_DEPTH);
found.insert(def, Some(deepest));
path.pop();
if let Some((user, next, held)) = path.last_mut() {
let at = own.get(user).and_then(|o| o.1.get(next.wrapping_sub(1))).map_or(1, |u| u.1);
*held = held.zip(deepest).map(|(h, d)| h.max(at + d));
}
continue;
};
let known = found.get(&other).copied();
if known.is_none() && own.contains_key(&other) {
found.insert(other, None);
}
let Some((_, next, deepest)) = path.last_mut() else { break };
*next += 1;
match known {
// Not in the document: it draws nothing.
None if !own.contains_key(&other) => {}
None => path.push((other, 0, own.get(&other).map(|o| o.0))),
Some(Some(Some(d))) => *deepest = deepest.map(|h| h.max(at + d)),
// It uses itself (it is on the way down here), or can't be drawn.
Some(None | Some(None)) => *deepest = None,
}
}
}
found.into_iter().map(|(def, h)| (def, h.flatten())).collect()
}
/// Can't `used`, by an object `depth` deep, be drawn there?
fn undrawable(heights: &Heights, used: Def, depth: usize) -> bool {
heights.get(&used).is_some_and(|h| h.is_none_or(|h| depth + h > MAX_DRAWN_DEPTH))
}
/// Take out of `nodes` (each `depth` deep) the uses `bad` picks (given the depth of the object):
/// an instance goes, a pattern paint becomes no paint. Returns whether anything changed. For trees
/// within [`MAX_DEPTH`].
fn strip(nodes: &mut Vec<Arc<Node>>, depth: usize, bad: &dyn Fn(Def, usize) -> bool) -> bool {
let any = |n: &Node, d: usize| uses(n).any(|u| bad(u, d));
let before = nodes.len();
nodes.retain(|n| !matches!(&n.kind, NodeKind::SymbolInstance { symbol, .. } if bad((false, symbol.as_str()), depth)));
let mut changed = nodes.len() != before;
for n in nodes.iter_mut() {
if !deeper(std::slice::from_ref(n), None, depth, &any) {
continue;
}
changed = true;
let n = Arc::make_mut(n);
if uses(n).any(|u| bad(u, depth)) {
for p in crate::swatches::node_paints_mut(n) {
if matches!(p, Paint::Pattern { pattern, .. } if bad((true, pattern.as_str()), depth)) {
*p = Paint::None;
}
}
}
// An opacity mask whose art is such an instance goes with it.
let mut mask = n.extras().mask.as_ref().map(|m| vec![m.art.clone()]);
if let Some(art) = &mut mask
&& strip(art, depth + 1, bad)
{
match art.pop() {
Some(art) => {
if let Some(m) = n.extras_mut().mask.as_mut() {
m.art = art;
}
}
None => n.extras_mut().mask = None,
}
}
if let Some(ch) = n.children_mut() {
strip(ch, depth + 1, bad);
}
}
changed
}
impl Document {
/// Is anything nested deeper than [`MAX_DEPTH`], or drawn deeper than [`MAX_DRAWN_DEPTH`]
/// (a symbol or pattern that uses itself is)? With `before` (this document before an edit,
/// itself within the caps), only what the edit changed is looked at.
pub fn too_deep(&self, before: Option<&Document>) -> bool {
fn symbol(d: &Document, i: usize) -> Option<&[Arc<Node>]> {
d.symbols.get(i).map(|s| std::slice::from_ref(&s.art))
}
fn pattern(d: &Document, i: usize) -> Option<&[Arc<Node>]> {
d.patterns.get(i).map(|p| &p.art[..])
}
let nested = |_: &Node, _: usize| false;
if (0..self.symbols.len()).any(|i| symbol(self, i).is_some_and(|art| deeper(art, before.and_then(|b| symbol(b, i)), 1, &nested)))
|| (0..self.patterns.len()).any(|i| pattern(self, i).is_some_and(|art| deeper(art, before.and_then(|b| pattern(b, i)), 1, &nested)))
{
return true;
}
if self.symbols.is_empty() && self.patterns.is_empty() {
return deeper(&self.layers, before.map(|b| &b.layers[..]), 1, &nested);
}
let heights = heights(self);
if heights.values().any(Option::is_none) {
return true;
}
// Symbols and patterns draw as deep as before when none changed; else every use of them
// is looked at again.
let same = |x: &[Arc<Node>], y: &[Arc<Node>]| x.len() == y.len() && x.iter().zip(y).all(|(x, y)| Arc::ptr_eq(x, y));
let before = before.filter(|b| {
b.symbols.len() == self.symbols.len()
&& b.patterns.len() == self.patterns.len()
&& b.symbols.iter().zip(&self.symbols).all(|(x, y)| x.name == y.name && Arc::ptr_eq(&x.art, &y.art))
&& b.patterns.iter().zip(&self.patterns).all(|(x, y)| x.name == y.name && same(&x.art, &y.art))
});
let over = |n: &Node, d: usize| uses(n).any(|u| undrawable(&heights, u, d));
deeper(&self.layers, before.map(|b| &b.layers[..]), 1, &over)
}
/// [`Self::limit_depth`] → whether objects were merged, and whether uses of symbols and
/// patterns were left out.
fn limit_depths(&mut self) -> (bool, bool) {
let mut merged = limit(&mut self.layers, 1);
for s in &mut self.symbols {
// The art itself is 1 deep, so it stays the one object it is.
let mut art = vec![s.art.clone()];
if limit(&mut art, 1)
&& let Some(one) = art.pop()
{
s.art = one;
merged = true;
}
}
for p in &mut self.patterns {
merged |= limit(&mut p.art, 1);
}
if self.symbols.is_empty() && self.patterns.is_empty() {
return (merged, false);
}
// The symbols and patterns that can't be drawn lose what their art uses: each is then as
// deep as its own art, and what uses it can be drawn.
let (symbols, patterns): (Vec<usize>, Vec<usize>) = {
let heights = heights(self);
let bad = |def: Def| heights.get(&def).is_some_and(Option::is_none);
(
(0..self.symbols.len()).filter(|i| self.symbols.get(*i).is_some_and(|s| bad((false, s.name.as_str())))).collect(),
(0..self.patterns.len()).filter(|i| self.patterns.get(*i).is_some_and(|p| bad((true, p.name.as_str())))).collect(),
)
};
let mut left_out = !(symbols.is_empty() && patterns.is_empty());
let all = |_: Def, _: usize| true;
for i in symbols {
let Some(s) = self.symbols.get_mut(i) else { continue };
let mut art = vec![s.art.clone()];
strip(&mut art, 1, &all);
// (An instance alone was the whole art: an empty group stands in.)
s.art = art.pop().unwrap_or_else(|| Arc::new(Node::group(s.art.id, vec![])));
}
for i in patterns {
if let Some(p) = self.patterns.get_mut(i) {
strip(&mut p.art, 1, &all);
}
}
// Then the uses drawn too deep where they are.
let mut layers = self.layers.clone();
left_out |= {
let heights = heights(self);
strip(&mut layers, 1, &|used, depth| undrawable(&heights, used, depth))
};
self.layers = layers;
(merged, left_out)
}
/// Bring the document within the caps: merge the objects nested deeper than [`MAX_DEPTH`]
/// into the group around them, and leave out the uses of symbols and patterns that can't be
/// drawn (inside their own symbol or pattern, or deeper than [`MAX_DRAWN_DEPTH`]). What an
/// importer does to what it read. Returns whether anything changed.
pub fn limit_depth(&mut self) -> bool {
let (merged, left_out) = self.limit_depths();
merged || left_out
}
/// [`Self::limit_depth`], adding [`merged_note`] and [`symbols_note`] to an importer's
/// `warnings` for what it did.
pub fn limit_depth_noting(&mut self, warnings: &mut Vec<String>) {
let (merged, left_out) = self.limit_depths();
warnings.extend(merged.then(merged_note));
warnings.extend(left_out.then(symbols_note));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{NodeId, OpacityMask};
/// One path in `levels` groups, each holding a second path beside the next group.
fn nested(levels: usize) -> Arc<Node> {
let mut id = 0;
let mut next = || {
id += 1;
NodeId(id)
};
let path = |id| Arc::new(Node::path(id, Default::default(), Default::default()));
let mut inner = path(next());
for _ in 0..levels {
inner = Arc::new(Node::group(next(), vec![inner, path(next())]));
}
inner
}
fn doc(art: Arc<Node>) -> Document {
let mut d = Document::new(100.0, 100.0);
if let Some(ch) = d.layers.first_mut().and_then(|l| Arc::make_mut(l).children_mut()) {
ch.push(art);
}
d
}
#[test]
fn a_document_at_the_cap_is_left_alone() {
// The layer, the groups and the path in them: MAX_DEPTH deep.
let mut d = doc(nested(MAX_DEPTH - 2));
let before = d.clone();
assert!(!d.too_deep(None));
assert!(!d.limit_depth());
assert_eq!(d, before);
}
#[test]
fn deeper_objects_are_merged_into_the_group_around_them() {
for levels in [MAX_DEPTH - 1, MAX_DEPTH + 5, 100_000] {
let mut d = doc(nested(levels));
assert!(d.too_deep(None), "{levels}");
assert!(d.limit_depth(), "{levels}");
assert!(!d.too_deep(None), "{levels}");
// Every path is kept, under the layer and the groups down to the cap.
assert_eq!(d.node_count(), 1 + (MAX_DEPTH - 2) + (levels + 1), "{levels}");
assert!(!d.limit_depth(), "nothing more to merge");
}
}
#[test]
fn a_clipping_path_and_a_mask_too_deep_are_left_out_and_hidden_art_stays_hidden() {
let mut inner = Node::new(NodeId(9000), NodeKind::Group { children: vec![nested(0), nested(0)], clip: true });
inner.visible = false;
let mut art = Arc::new(inner);
for i in 0..MAX_DEPTH {
art = Arc::new(Node::group(NodeId(9001 + i as u64), vec![art]));
}
let mut masked = Node::group(NodeId(8000), vec![]);
masked.extras_mut().mask = Some(Box::new(OpacityMask::new((*nested(MAX_DEPTH)).clone(), true)));
let mut d = doc(art);
if let Some(ch) = d.layers.first_mut().and_then(|l| Arc::make_mut(l).children_mut()) {
ch.push(Arc::new(masked));
}
assert!(d.limit_depth());
assert!(!d.too_deep(None));
let mut leaves = vec![];
d.walk(|n| {
if n.children().is_none() {
leaves.push(n.visible);
}
});
assert_eq!(leaves, [false], "the clipping path is gone, what it clipped is still hidden");
assert!(d.node(NodeId(8000)).is_some_and(|n| n.extras().mask.is_none()));
}
#[test]
fn an_edit_is_checked_by_what_it_changed() {
let before = doc(nested(MAX_DEPTH - 2));
let mut wrapped = before.clone();
if let Some(ch) = wrapped.layers.first_mut().and_then(|l| Arc::make_mut(l).children_mut()) {
let all = std::mem::take(ch);
ch.push(Arc::new(Node::group(NodeId(7000), all)));
}
assert!(wrapped.too_deep(Some(&before)), "the same art, one deeper");
let mut beside = before.clone();
if let Some(ch) = beside.layers.first_mut().and_then(|l| Arc::make_mut(l).children_mut()) {
ch.push(nested(3));
}
assert!(!beside.too_deep(Some(&before)));
assert!(!before.too_deep(Some(&before)));
}
#[test]
fn symbols_and_patterns_are_limited_too() {
let mut d = Document::new(10.0, 10.0);
d.symbols.push(crate::Symbol { name: "s".into(), art: nested(MAX_DEPTH + 3) });
assert!(d.too_deep(None));
assert!(d.limit_depth());
assert!(!d.too_deep(None));
assert!(d.symbols.first().is_some_and(|s| s.art.children().is_some()), "still one object");
}
fn instance(id: u64, of: &str) -> Arc<Node> {
Arc::new(Node::new(NodeId(id), NodeKind::SymbolInstance { symbol: of.into(), xf: Default::default() }))
}
/// `count` symbols, each `levels` groups around an instance of the next; the last one's of `last`.
fn symbols(count: usize, levels: usize, last: &str) -> Document {
let mut d = doc(instance(1, "s0"));
for i in 0..count {
let mut art = instance(2, &if i + 1 < count { format!("s{}", i + 1) } else { last.to_string() });
for _ in 0..levels {
art = Arc::new(Node::group(NodeId(3), vec![art]));
}
d.symbols.push(crate::Symbol { name: format!("s{i}"), art });
}
d
}
fn instances(d: &Document) -> usize {
let mut n = 0;
let mut count = |x: &Node| n += usize::from(matches!(x.kind, NodeKind::SymbolInstance { .. }));
d.walk(&mut count);
d.symbols.iter().for_each(|s| s.art.walk(&mut count));
n
}
#[test]
fn symbols_in_symbols_are_drawn_only_so_deep() {
// Three objects deep each (two groups and the instance), under the instance on the layer.
let fits = MAX_DRAWN_DEPTH / 3 - 1;
let mut d = symbols(fits, 2, "none");
assert!(!d.too_deep(None) && !d.limit_depth());
let mut d = symbols(fits + 2, 2, "none");
assert!(d.too_deep(None));
let before = instances(&d);
assert!(d.limit_depth() && !d.too_deep(None));
assert!(instances(&d) < before && instances(&d) > 0, "only the instances drawn too deep go");
// A chain of any length is measured, not walked by recursion.
let mut d = symbols(100_000, 0, "none");
assert!(d.too_deep(None) && d.limit_depth() && !d.too_deep(None));
}
#[test]
fn a_symbol_holding_itself_loses_the_instance() {
for (count, last) in [(1, "s0"), (3, "s0"), (3, "s1")] {
let mut d = symbols(count, 1, last);
assert!(d.too_deep(None), "{count} {last}");
let mut warnings = vec![];
d.limit_depth_noting(&mut warnings);
assert_eq!(warnings, [symbols_note()]);
assert!(!d.too_deep(None), "{count} {last}");
assert!(d.symbols.iter().all(|s| s.art.children().is_some()), "the art is still a group");
}
// An edit that makes one hold itself is seen, whatever else stayed as it was.
let before = symbols(2, 1, "none");
let mut after = before.clone();
after.symbols[1].art = Arc::new(Node::group(NodeId(9), vec![instance(8, "s0")]));
assert!(!before.too_deep(None) && after.too_deep(Some(&before)));
}
/// A path painted with the pattern named `with`.
fn painted(id: u64, with: &str) -> Arc<Node> {
let fill = Paint::Pattern { pattern: with.into(), xf: Default::default() };
Arc::new(Node::path(NodeId(id), Default::default(), crate::Appearance::basic(fill, Paint::None, 0.0)))
}
fn pattern_fills(d: &Document) -> usize {
let mut n = 0;
d.visit_paints(&mut |p| n += usize::from(matches!(p, Paint::Pattern { .. })));
n
}
#[test]
fn a_pattern_painting_its_own_tile_loses_the_paint() {
// One that paints its own tile; two that paint each other's; one in a symbol it holds.
let itself = |d: &mut Document| d.patterns.push(crate::PatternDef::new("p", vec![painted(2, "p")]));
let each_other = |d: &mut Document| {
d.patterns.push(crate::PatternDef::new("p", vec![painted(2, "q")]));
d.patterns.push(crate::PatternDef::new("q", vec![painted(3, "p")]));
};
let through_a_symbol = |d: &mut Document| {
d.patterns.push(crate::PatternDef::new("p", vec![instance(2, "s")]));
d.symbols.push(crate::Symbol { name: "s".into(), art: painted(3, "p") });
};
for make in [&itself as &dyn Fn(&mut Document), &each_other, &through_a_symbol] {
let mut d = doc(painted(1, "p"));
make(&mut d);
assert!(d.too_deep(None));
let mut warnings = vec![];
d.limit_depth_noting(&mut warnings);
assert_eq!(warnings, [symbols_note()]);
assert!(!d.too_deep(None));
assert_eq!(pattern_fills(&d), 1, "the object on the layer keeps its pattern");
}
// A pattern whose tile is painted with another is fine.
let mut d = doc(painted(1, "p"));
d.patterns.push(crate::PatternDef::new("p", vec![painted(2, "q")]));
d.patterns.push(crate::PatternDef::new("q", vec![painted(3, "none")]));
assert!(!d.too_deep(None) && !d.limit_depth());
}
}