From 049f8c396337c035eef0c4a51996a217d72d4268 Mon Sep 17 00:00:00 2001 From: Drew Dibble Date: Mon, 9 Nov 2026 13:10:41 -0700 Subject: [PATCH] Canvas redraw: tests for edits measured under overprint, with Vector Halftone, and to the instance symbol editing came from Review follow-up: these three branches of what a renderer measures had no test. Under Overprint Preview a renderer measures an edit as changed() does and holds none of the 3D art it made for it; a 3D effect with Vector Halftone is measured by the free measure, with no solid built and no art held; and on screen in symbol editing mode, an edit to the instance it was entered from changes nothing. Each test fails with its branch taken out. --- crates/render/src/fx/tests.rs | 15 +++++++ crates/render/src/tests/changed.rs | 70 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/crates/render/src/fx/tests.rs b/crates/render/src/fx/tests.rs index 211d62066f..632f8b7236 100644 --- a/crates/render/src/fx/tests.rs +++ b/crates/render/src/fx/tests.rs @@ -674,6 +674,21 @@ fn the_renderer_measures_3d_art_with_crop_marks_as_drawn() { assert_eq!(Renderer::new().bounds_of(&symbols, &n), Some(free)); } +/// Vector Halftone draws dots in place of a 3D effect's solid: the renderer measures the object as +/// the free measure does, building no solid and holding no art to draw it with. +#[test] +fn the_renderer_measures_3d_art_with_vector_halftone_as_drawn() { + let fx = vec![("threeD.extrude", json!({"depth": 30.0, "position": "isometricTop"})), (crate::effects::addons::halftone::HALFTONE, json!({}))]; + let d = doc_with(Rect::new(40.0, 40.0, 60.0, 60.0), Color::BLACK, fx); + let n = d.layers[0].children().unwrap()[0].clone(); + let symbols = d.symbol_boxes(); + let free = super::cull_bounds(&symbols, &n).unwrap(); + let mut r = Renderer::new(); + assert_eq!(r.bounds_of(&symbols, &n), Some(free)); + assert_eq!(r.stats.three_d_built, 0); + assert_eq!(r.measured_bytes(), 0); +} + /// A renderer measures the art over each artboard as [`crate::encode::art_bounds_on`] does, and /// builds each 3D solid once for every artboard it measures. #[test] diff --git a/crates/render/src/tests/changed.rs b/crates/render/src/tests/changed.rs index 8d2a58bc71..43a265a9ad 100644 --- a/crates/render/src/tests/changed.rs +++ b/crates/render/src/tests/changed.rs @@ -651,3 +651,73 @@ fn an_edit_to_what_the_view_hides_changes_nothing() { assert_eq!(r.changed(&old, &new, &opts), Changed::Nothing); assert!(matches!(changed(&old, &new), Changed::Area(_)), "shown, they change"); } + +/// Overprint Preview and separations draw objects through the inks they print, not through the +/// art a renderer keeps for them: a renderer measures an edit there as [`changed`] does and holds +/// none of the 3D art it made to measure it. +#[test] +fn an_edit_measured_under_overprint_holds_no_art() { + use vectorcraft_doc::{CharStyle, TextObject}; + let mut old = Document::new(110.0, 80.0); + let style = CharStyle { size: 30.0, fill: Paint::solid(Color::rgb(0.1, 0.2, 0.6)), ..Default::default() }; + let mut n = Node::new(old.alloc_id(), NodeKind::Text(Box::new(TextObject::point(Point::new(40.0, 50.0), "K", style)))); + let extrude = |depth: f64| vectorcraft_doc::Effect { + id: "threeD.extrude".into(), + params: serde_json::json!({"depth": depth, "position": "isometricTop"}), + visible: true, + }; + n.appearance.effects.push(extrude(60.0)); + let l = old.layers[0].id; + let id = old.insert(Some(l), 0, n).unwrap(); + let mut new = old.clone(); + new.node_mut(id).unwrap().appearance.effects[0] = extrude(5.0); + let _fonts = crate::tests_fontchange::fonts_steady(); + let held = |opts: &RenderOptions| { + crate::tests_fontchange::with_fonts_unchanged(|| { + let mut r = Renderer::new(); + r.render(&old, W, H, view(), opts); + (area(r.changed(&old, &new, opts)), r.measured_bytes()) + }) + }; + let (_, on_screen) = held(&RenderOptions::default()); + assert!(on_screen > 0, "the screen view holds the art it measured with"); + let (measured, overprinted) = held(&RenderOptions { overprint_preview: true, ..Default::default() }); + assert_eq!(overprinted, 0); + assert_eq!(measured, area(changed(&old, &new)), "as changed measures it"); +} + +/// Symbol editing mode on screen doesn't draw the instance it was entered from: an edit to that +/// instance changes nothing there, and changes where it draws in an export. +#[test] +fn an_edit_to_the_instance_symbol_editing_came_from_changes_nothing_on_screen() { + use vectorcraft_doc::{Symbol, SymbolEdit}; + let mut old = Document::new(W as f64, H as f64); + let art = rect(&mut old, Rect::new(20.0, 20.0, 50.0, 50.0), Color::rgb(0.0, 0.5, 0.0)); + old.symbols.push(Symbol { dynamic: false, name: "Square".into(), art: Arc::new(art.clone()) }); + let inst = Node::new(old.alloc_id(), NodeKind::SymbolInstance { symbol: "Square".into(), xf: Affine::IDENTITY }); + let l = old.layers[0].id; + let inst = old.insert(Some(l), 0, inst).unwrap(); + let layer = old.add_layer(Some("Square")); + let copy = old.reid(&art); + old.insert(Some(layer), usize::MAX, copy).unwrap(); + old.symbol_edit = Some(SymbolEdit { + symbol: "Square".into(), + layer, + instance: Some(inst), + xf: Affine::IDENTITY, + group: false, + changed: false, + back_isolation: None, + back_layer: None, + }); + let mut new = old.clone(); + new.node_mut(inst).unwrap().transform(Affine::translate((30.0, 30.0)), false); + let screen = RenderOptions { isolated: Some(layer), ..Default::default() }; + let mut r = Renderer::new(); + r.render(&old, W, H, view(), &screen); + assert_eq!(r.changed(&old, &new, &screen), Changed::Nothing); + let export = RenderOptions::default(); + let mut r = Renderer::new(); + r.render(&old, W, H, view(), &export); + assert!(matches!(r.changed(&old, &new, &export), Changed::Area(_)), "an export draws it"); +}