mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 11:17:01 -06:00
M4.33: Export As with Use Artboards (All or a range) and art bounds
File > Export > Export As... (file.exportAs) picks any writable format and Use Artboards: All or a range writes one file per artboard, <file>-<artboard>.<ext> (a PDF keeps them as pages); off, the export covers the bounds of the visible art (an artless document is refused before the save dialog). The format's options dialog follows: PNG/JPEG/WebP Options, SVG Options, or Save PDF for artboards as pages. document.export takes useArtboards for raster formats (true: one file per chosen artboard, default all) and PDF (false: the art's bounds), and the app's exports return every file written and the encoder's warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
157724279f
commit
950a941003
@@ -4,10 +4,10 @@
|
||||
|
||||
pub mod png;
|
||||
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_doc::{Document, Node, NodeKind};
|
||||
use vectorcraft_geom::Rect;
|
||||
|
||||
use crate::{AntiAlias, RenderOptions, Rendered, Renderer};
|
||||
use crate::{AntiAlias, RenderOptions, Rendered, Renderer, fx};
|
||||
|
||||
/// A raster file format.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -109,5 +109,25 @@ pub(crate) fn webp(img: &Rendered) -> Result<Vec<u8>, String> {
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Bounds of the art an export draws: visible objects off template layers (guides left out),
|
||||
/// with their strokes and live effects. `None` when nothing would be drawn.
|
||||
pub fn art_bounds(doc: &Document) -> Option<Rect> {
|
||||
doc.layers.iter().fold(None, |acc, l| vectorcraft_geom::union_opt(acc, drawn_bounds(l)))
|
||||
}
|
||||
|
||||
fn drawn_bounds(n: &Node) -> Option<Rect> {
|
||||
if !n.visible {
|
||||
return None;
|
||||
}
|
||||
match &n.kind {
|
||||
NodeKind::Layer { template: true, .. } | NodeKind::Path { guide: true, .. } => None,
|
||||
NodeKind::Layer { children, .. } | NodeKind::Group { children, clip: false } => {
|
||||
children.iter().fold(None, |acc, c| vectorcraft_geom::union_opt(acc, drawn_bounds(c)))
|
||||
}
|
||||
_ if fx::has_fx(n) => fx::visual_bounds(n),
|
||||
_ => n.visual_bounds(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -171,3 +171,21 @@ fn anti_alias_none_is_the_same_on_every_thread_count() {
|
||||
let worst = st.pixels.iter().zip(&mt.pixels).map(|(a, b)| a.abs_diff(*b)).max().unwrap();
|
||||
assert!(worst <= 4, "max channel difference {worst}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn art_bounds_cover_visible_art_only() {
|
||||
let mut d = circle_doc();
|
||||
let l = d.layers[0].id;
|
||||
let hidden = {
|
||||
let id = d.alloc_id();
|
||||
let mut n =
|
||||
Node::path(id, shapes::rectangle(Rect::new(100.0, 100.0, 120.0, 120.0)), Appearance::basic(Paint::solid(Color::BLACK), Paint::None, 0.0));
|
||||
n.visible = false;
|
||||
n
|
||||
};
|
||||
d.insert(Some(l), 1, hidden).unwrap();
|
||||
let b = art_bounds(&d).unwrap();
|
||||
assert!((b.x0 - 5.3).abs() < 1e-6 && (b.x1 - 31.1).abs() < 1e-6 && (b.y1 - 26.2).abs() < 1e-6, "{b:?}");
|
||||
let empty = Document::new(10.0, 10.0);
|
||||
assert_eq!(art_bounds(&empty), None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user