mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 07:13:46 +00:00
3D: a symbol's 9-slice guides and overrides count before its art is placed
The check that refuses a symbol instance before its art is placed counted only the symbol's own objects. Placing art through 9-slice guides can make up to 28 objects of each one, and an instance's dynamic-symbol overrides can turn a path that would be cut at the guides into clip groups in every cell it reaches. Such an instance passed the check, and its art was placed in full before the work limit refused it. The check now counts the symbol's own objects first, then the most objects placing makes (NineSliceMap::most_placed) of the art with the instance's overrides applied, and refuses the instance with the same message before anything is placed.
This commit is contained in:
@@ -129,16 +129,23 @@ impl Resolver<'_> {
|
||||
}
|
||||
self.spend(1)?;
|
||||
match &n.kind {
|
||||
NodeKind::SymbolInstance { symbol, .. } => {
|
||||
// Counted before the art is placed, so a symbol past the limit is never copied.
|
||||
let held = self.doc.and_then(|d| d.symbols.iter().find(|s| s.name == *symbol)).map_or(0, |s| s.art.count_with_masks());
|
||||
if self.work.saturating_add(held) > MAX_WORK {
|
||||
NodeKind::SymbolInstance { symbol, xf } => {
|
||||
let missing = || format!("{label} can't build a solid from the instance of symbol \"{symbol}\": the symbol's art isn't available");
|
||||
let doc = self.doc.ok_or_else(missing)?;
|
||||
let sym = doc.symbols.iter().find(|s| s.name == *symbol).ok_or_else(missing)?;
|
||||
// Counted before the art is placed, so a symbol past the limit is never copied:
|
||||
// first its own objects, which the instance's overrides keep, then the most
|
||||
// objects placing makes, with those overrides and the symbol's 9-slice guides.
|
||||
if self.work.saturating_add(sym.art.count_with_masks()) > MAX_WORK {
|
||||
return Err(self.too_much_work());
|
||||
}
|
||||
let art = crate::outline_object(self.doc, n).ok_or_else(|| {
|
||||
format!("{label} can't build a solid from the instance of symbol \"{symbol}\": the symbol's art isn't available")
|
||||
})?;
|
||||
Ok(self.list(kids(&art), depth + 1)?.0)
|
||||
let art = vectorcraft_doc::symbol_overrides::dynamic_art(&sym.art, n);
|
||||
let most = doc.symbol_slice_map(symbol, *xf).map_or(art.count_with_masks() as u64, |m| m.most_placed(&art, doc));
|
||||
if (self.work as u64).saturating_add(most) > MAX_WORK as u64 {
|
||||
return Err(self.too_much_work());
|
||||
}
|
||||
let placed = doc.place_symbol_art(symbol, &art, *xf).ok_or_else(missing)?;
|
||||
Ok(self.list(&[Arc::new(placed)], depth + 1)?.0)
|
||||
}
|
||||
NodeKind::Blend { children, spec } => {
|
||||
// Counted before the steps are made, so a blend past the limit is never expanded.
|
||||
|
||||
@@ -375,6 +375,54 @@ fn a_symbol_holding_more_objects_than_the_work_limit_is_refused_before_its_art_i
|
||||
assert!(work <= 2, "refused before the symbol's art is placed and listed: {work}");
|
||||
}
|
||||
|
||||
/// A gradient fill: a path filled with one is drawn in clip groups where it crosses 9-slice guides.
|
||||
fn gradient() -> Paint {
|
||||
use vectorcraft_doc::color::{Gradient, GradientPaint};
|
||||
Paint::Gradient(Box::new(GradientPaint::new(Gradient::default())))
|
||||
}
|
||||
|
||||
/// A document whose symbol "Sliced" has 9-slice guides and holds `count` paths across all of them,
|
||||
/// each filled with `fill`, and an Extrude on an instance of it at twice its size.
|
||||
fn sliced(count: usize, fill: impl Fn() -> Paint) -> (Document, Node) {
|
||||
let mut d = document();
|
||||
let members = (0..count).map(|i| {
|
||||
Arc::new(Node::path(NodeId(i as u64 + 1), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)), Appearance::basic(fill(), Paint::None, 0.0)))
|
||||
});
|
||||
d.symbols.push(Symbol { dynamic: true, name: "Sliced".into(), art: Arc::new(Node::group(NodeId(0), members.collect())) });
|
||||
d.set_symbol_nine_slice("Sliced", Some(vectorcraft_doc::SymbolNineSlice::default()));
|
||||
let n = with(instance(30, "Sliced", Affine::scale(2.0)), EXTRUDE, json!({}));
|
||||
(d, n)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_symbol_whose_9_slice_guides_would_place_too_many_objects_is_refused_before_its_art_is_placed() {
|
||||
// Each gradient path becomes a group of nine clip groups, so the art placed is far past the
|
||||
// limit while the symbol itself holds less than a tenth of it.
|
||||
let (d, n) = sliced(source::MAX_WORK / 20, gradient);
|
||||
assert!(d.symbols[1].art.count_with_masks() < source::MAX_WORK);
|
||||
let err = validate_three_d_in(Some(&d), &n, EXTRUDE, &json!({})).unwrap_err();
|
||||
assert!(err.contains("hold too many objects"), "{err}");
|
||||
let work = source::WORK.with(std::cell::Cell::get);
|
||||
assert!(work <= 2, "refused before the symbol's art is placed and listed: {work}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_dynamic_symbol_whose_overrides_would_place_too_many_objects_is_refused_before_its_art_is_placed() {
|
||||
// Solid paths are cut at the guides and stay one path each; the instance's overrides fill them
|
||||
// with a gradient, which places each in nine clip groups.
|
||||
let (d, mut n) = sliced(source::MAX_WORK / 20, || Paint::solid(Color::BLACK));
|
||||
n.extras_mut().symbol_overrides = (0..source::MAX_WORK / 20)
|
||||
.map(|i| vectorcraft_doc::SymbolOverride { path: vec![i as u32], fill: Some(gradient()), ..Default::default() })
|
||||
.collect();
|
||||
let err = validate_three_d_in(Some(&d), &n, EXTRUDE, &json!({})).unwrap_err();
|
||||
assert!(err.contains("hold too many objects"), "{err}");
|
||||
let work = source::WORK.with(std::cell::Cell::get);
|
||||
assert!(work <= 2, "refused before the instance's art is placed and listed: {work}");
|
||||
// A few such paths build.
|
||||
let (d, n) = sliced(4, || Paint::solid(Color::BLACK));
|
||||
assert!(solid_art_in(Some(&d), &n).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_sources_and_sources_inside_sources_build_or_refuse_cleanly() {
|
||||
let mut d = document();
|
||||
|
||||
Reference in New Issue
Block a user