Fix #735: Illustrator EPS and .ai files match their page in Fit to Artwork Bounds and the page check

- Fit to Artwork Bounds fits the art exports draw: hidden objects and
  layers, guides and template layers don't count, and the bounds stop at
  a layer's clip.
- The comparison with the page catches an object one draws that the
  other doesn't (pixels whose neighbours all differ, away from type), a
  light colour against white, and, for an EPS whose page is its art's
  box, art the layers print outside it.
- textAs: outlines opens a .ai whose type shows from its PDF part, which
  has the outlines.
- document.open takes editingData: false to open an Illustrator EPS or
  .ai as its page or PDF part only, for print pipelines.

Part of #637.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-10 06:51:50 +07:00
co-authored by Claude Opus 5.5
parent 569b4ac240
commit c599522061
21 changed files with 413 additions and 65 deletions
+2 -2
View File
@@ -86,7 +86,7 @@ impl RasterExportOptions {
self.ppi / 72.0
}
/// What the renderer draws for `format`: template layers left out, over the background
/// What the renderer draws for `format`: template layers and guides left out, over the background
/// (white for a JPEG or a flat PSD without one).
pub fn render_options(&self, format: RasterFormat) -> RenderOptions {
let flat = format == RasterFormat::Jpeg || (format == RasterFormat::Psd && !self.psd.layers);
@@ -193,7 +193,7 @@ fn drawn_bounds(n: &Node) -> Option<Rect> {
}
match &n.kind {
NodeKind::Layer { template: true, .. } | NodeKind::Path { guide: true, .. } => None,
NodeKind::Layer { children, .. } | NodeKind::Group { children, clip: false } => {
NodeKind::Layer { children, clip: false, .. } | 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),
+32
View File
@@ -189,3 +189,35 @@ fn art_bounds_cover_visible_art_only() {
let empty = Document::new(10.0, 10.0);
assert_eq!(art_bounds(&empty), None);
}
/// A black path round `r` in the first layer of `d`, made a guide when `guide`.
fn add_rect(d: &mut Document, r: Rect, guide: bool) {
let id = d.alloc_id();
let mut n = Node::path(id, shapes::rectangle(r), Appearance::basic(Paint::solid(Color::BLACK), Paint::None, 0.0));
if let NodeKind::Path { guide: g, .. } = &mut n.kind {
*g = guide;
}
let l = d.layers[0].id;
d.insert(Some(l), 0, n).unwrap();
}
#[test]
fn guides_dont_count_in_the_arts_bounds() {
let mut d = circle_doc();
add_rect(&mut d, Rect::new(0.0, 15.0, 400.0, 15.0), true);
let b = art_bounds(&d).unwrap();
assert!((b.x0 - 5.3).abs() < 1e-6 && (b.x1 - 31.1).abs() < 1e-6, "the guide doesn't count: {b:?}");
}
#[test]
fn art_bounds_end_at_a_clipping_layers_clip() {
let mut d = Document::new(40.0, 30.0);
// The layer clips its art to its first object (added last): what lies beyond isn't drawn.
add_rect(&mut d, Rect::new(-100.0, -100.0, 200.0, 200.0), false);
add_rect(&mut d, Rect::new(2.0, 3.0, 20.0, 10.0), false);
let layer = std::sync::Arc::make_mut(&mut d.layers[0]);
if let NodeKind::Layer { clip, .. } = &mut layer.kind {
*clip = true;
}
assert_eq!(art_bounds(&d), Some(Rect::new(2.0, 3.0, 20.0, 10.0)));
}