diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 44d16773..e1187449 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -46,11 +46,22 @@ pub struct RenderOptions { pub proof: Option, /// Overprint Preview (see [`proof`] for what overprints). pub overprint_preview: bool, + /// Trim View: clip the artwork to the artboards (nothing on the pasteboard is drawn). + pub trim: bool, } impl Default for RenderOptions { fn default() -> Self { - Self { outline: false, background: None, artboards: false, hidden: vec![], dim_templates: true, proof: None, overprint_preview: false } + Self { + outline: false, + background: None, + artboards: false, + hidden: vec![], + dim_templates: true, + proof: None, + overprint_preview: false, + trim: false, + } } } @@ -269,11 +280,23 @@ impl Renderer { } } self.stamp += 1; + let trim = opts.trim && !doc.artboards.is_empty(); + if trim { + let mut clip = BezPath::new(); + for ab in &doc.artboards { + clip.extend(ab.rect.path_elements(0.1)); + } + ctx.set_transform(view); + ctx.push_clip_layer(&clip); + } if !self.draw_pattern_edit(&mut ctx, &frame) { for layer in &doc.layers { self.draw_arc(&mut ctx, &frame, layer); } } + if trim { + ctx.pop_layer(); + } // Drop cache entries not seen for a few frames. let g = self.stamp; if self.geom.len() > 1024 { diff --git a/crates/render/src/tests.rs b/crates/render/src/tests.rs index aa1201a0..b723cf26 100644 --- a/crates/render/src/tests.rs +++ b/crates/render/src/tests.rs @@ -257,3 +257,16 @@ fn linked_mask_moves_with_object() { let img = Renderer::new().render(&d, 100, 100, Affine::IDENTITY, &opts); assert_eq!(img.pixel(52, 50), [255, 0, 0, 255], "mask art moved by 5 too"); } + +#[test] +fn trim_view_clips_to_artboards() { + let mut d = Document::new(50.0, 50.0); + let n = rect_node(&mut d, Rect::new(0.0, 0.0, 100.0, 100.0), Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0); + let l = d.layers[0].id; + d.insert(Some(l), 0, n).unwrap(); + let opts = RenderOptions { background: Some([255, 255, 255, 255]), trim: true, ..Default::default() }; + let r = Renderer::new().render(&d, 100, 100, Affine::IDENTITY, &opts); + assert_eq!(r.pixel(25, 25), [255, 0, 0, 255]); + assert_eq!(r.pixel(75, 75), [255, 255, 255, 255]); + assert_eq!(render(&d).pixel(75, 75), [255, 0, 0, 255]); +} diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 521d3762..3b9da0e3 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -175,6 +175,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { w, h, outline: app.ui.view.outline, + trim: app.ui.view.trim_view, ppp, hidden: vec![], rot: v.rotation, @@ -203,6 +204,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let opts = vectorcraft_render::RenderOptions { proof: vectorcraft_render::proof::active_proof(), overprint_preview: vectorcraft_render::proof::overprint_preview_on(), + trim: app.ui.view.trim_view, ..opts }; // Light documents render synchronously (no lag vs overlays); heavy ones go to the worker. diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index d1c7fcd3..c0f5a691 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -90,6 +90,7 @@ pub struct CacheKey { pub w: u32, pub h: u32, pub outline: bool, + pub trim: bool, pub ppp: f32, pub hidden: Vec, pub rot: f64, diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 90c45756..38086766 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -78,6 +78,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ("shortcuts.import", "Import Keyboard Shortcuts…", "", "{path? | data?}"), ("view.outline", "Outline", "Cmd+Y", "{} toggle Outline/Preview"), ("view.pixelPreview", "Pixel Preview", "Cmd+Alt+Y", "{}"), + ("view.trimView", "Trim View", "", "{} toggle: hide everything outside the artboards"), ("view.zoomIn", "Zoom In", "Cmd+=", "{}"), ("view.zoomOut", "Zoom Out", "Cmd+-", "{}"), ("view.fitArtboard", "Fit Artboard in Window", "Cmd+0", "{}"), @@ -223,6 +224,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option flag(&mut app.ui.view.outline), "view.pixelPreview" => flag(&mut app.ui.view.pixel_preview), + "view.trimView" => flag(&mut app.ui.view.trim_view), "view.edges" => flag(&mut app.ui.view.edges), "view.artboards" => flag(&mut app.ui.view.artboards), "view.rulers" => flag(&mut app.ui.view.rulers), @@ -397,6 +399,7 @@ pub fn checked(app: &VectorcraftApp, id: &str, p: &Value) -> Option { Some(match id { "view.outline" => v.outline, "view.pixelPreview" => v.pixel_preview, + "view.trimView" => v.trim_view, "view.smartGuides" => v.smart_guides, "view.grid" => v.grid, "view.snapToGrid" => v.snap_to_grid, @@ -929,7 +932,7 @@ pub fn menu_tree() -> Vec<(&'static str, Vec)> { c("Outline", "view.outline"), c("Overprint Preview", "view.overprintPreview"), c("Pixel Preview", "view.pixelPreview"), - todo("Trim View"), + c("Trim View", "view.trimView"), c("Presentation Mode", "view.presentation"), sub( "Screen Mode", diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 67484023..25a188f5 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -115,6 +115,8 @@ pub struct ViewFlags { pub artboards: bool, pub transparency_grid: bool, pub edges: bool, + /// View → Trim View: hide everything outside the artboards. + pub trim_view: bool, } impl Default for ViewFlags { @@ -132,6 +134,7 @@ impl Default for ViewFlags { artboards: true, transparency_grid: false, edges: true, + trim_view: false, } } }