diff --git a/crates/svg/src/export/nodes.rs b/crates/svg/src/export/nodes.rs index 7be748391..adcd3fd25 100644 --- a/crates/svg/src/export/nodes.rs +++ b/crates/svg/src/export/nodes.rs @@ -2,8 +2,8 @@ use super::output::{sanitize_id, valid_xml_value, xml_attr_escape, xml_attr_vali use super::paint::{StrokePlan, has_raster, pixel_gradient}; use super::text::OUTLINE_TOLERANCE; use super::{ - Affine, AppearanceItem, BezPath, BlendMode, Document, FillRule, Node, NodeKind, Paint, PathData, Props, Rect, StrokeLayer, Writer, Written, - WrittenShape, blend_css, css, fmt_num, stroke, xml_escape, + Affine, AppearanceItem, BezPath, BlendMode, Document, FillRule, Node, NodeKind, Paint, PathData, Props, Rect, StrokeAlign, StrokeLayer, Writer, + Written, WrittenShape, blend_css, css, fmt_num, stroke, xml_escape, }; /// A symbol's `` def and the instance transforms that can `` it. @@ -32,6 +32,22 @@ pub(crate) struct Reuse { /// How deep symbols may sit in one another's art (deeper instances are left out). const MAX_SYMBOL_NEST: u32 = 8; +/// Gradient placement and outside-aligned stroke masks need the shape's geometric box. +/// Solid colours and patterns do not, so avoid walking their path geometry for a box. +fn export_shape_bounds(n: &Node) -> Option { + n.appearance + .items + .iter() + .any(|item| match item { + AppearanceItem::Fill(f) => f.visible && matches!(f.paint, Paint::Gradient(_)), + AppearanceItem::Stroke(s) => { + s.visible && s.width > 0.0 && !s.paint.is_none() && (matches!(s.paint, Paint::Gradient(_)) || s.align == StrokeAlign::Outside) + } + }) + .then(|| n.geometric_bounds()) + .flatten() +} + impl Reuse { const NONE: Self = Self { moves: false, turns: false, scales: false }; @@ -578,7 +594,7 @@ impl Writer<'_> { let dashed = n.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Stroke(s) if s.dash.as_ref().is_some_and(|d| d.is_dashed()))); let basic = self.basic_shape(path, self.xf, dashed); - self.shape(n, &d, basic, &[path], *rule, n.geometric_bounds()); + self.shape(n, &d, basic, &[path], *rule, export_shape_bounds(n)); } NodeKind::Compound { children, rule } => { let paths: Vec<&PathData> = children.iter().filter(|c| c.visible).filter_map(|c| c.path_data()).filter(|p| !p.is_empty()).collect(); @@ -586,7 +602,7 @@ impl Writer<'_> { return; } let d: Vec = paths.iter().map(|p| self.path_d(p, self.xf)).collect(); - self.shape(n, &d.join(" "), None, &paths, *rule, n.geometric_bounds()); + self.shape(n, &d.join(" "), None, &paths, *rule, export_shape_bounds(n)); } NodeKind::Text(t) => self.text_node(n, t), NodeKind::Image(im) => { diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 01a33eef3..f05706e48 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Layers panel: a layer whose Layer Options › Print is off shows its name italic, #5739; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (SVG export skips unused shape bounds; output, scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -31,6 +31,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-11-05 | minor | Large drawings of solid-colour or pattern-filled paths and compound paths export to SVG faster when their unused geometric bounds are skipped. Gradients and outside-aligned stroke masks retain their placement; SVG output, scores and estimates are unchanged. | | 2026-11-05 | minor | Selection Tools add-on: Select Linked, Embedded, Missing and Modified select the images and placed documents with that link status (Object › Plug-ins › Selection Tools and the Links panel menu; Shift adds), and Select by Link Status… lists them by name, status and format to pick from, optionally zooming to them; what is hidden, locked, on a template layer or outside the isolated group is left out and the status bar says how many (#5526, a VectorCraft addition); scores and estimates unchanged (estimated). | | 2026-11-05 | minor | Layers panel: a layer whose Layer Options › Print is off shows its name italic, as the reference application shows non-printing layers, and keeps it while the row is dragged (#5739); a printing layer's name is upright, and Print on again makes it upright again; scores and estimates unchanged. | | 2026-11-05 | minor | G12: Hyphenation › Default Language sets the language of new type unless Document Setup chose one, stored as a tag; each run hyphenates in its own language, English only so far, so 2 Preferences options stay unused (#394) |