From 2f863f8c16c80a563e9d9a7e27786f6fe45a90eb Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 13 Nov 2026 05:00:48 -0500 Subject: [PATCH] Use shared SVG viewBox sizing for placed artwork --- Cargo.lock | 95 ++++++++++++++++++++++------------ Cargo.toml | 2 +- crates/idml/Cargo.toml | 1 + crates/idml/src/import/item.rs | 29 +---------- 4 files changed, 67 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0785343e3..600e32a75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1247,7 +1247,29 @@ name = "craft-image-codecs" version = "0.1.0" source = "git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895#2e670cf9377b523112f3fe4b409399772c745895" dependencies = [ - "craft-jpeg", + "craft-jpeg 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", + "exr", + "flate2", + "half", + "image", + "image-webp", + "jpeg-encoder", + "png", + "rayon", + "serde_json", + "thiserror 2.0.21", + "tiff", + "zune-core", + "zune-jpeg", + "zune-jpegxl", +] + +[[package]] +name = "craft-image-codecs" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f" +dependencies = [ + "craft-jpeg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)", "exr", "flate2", "half", @@ -1280,6 +1302,14 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "craft-jpeg" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f" +dependencies = [ + "thiserror 2.0.21", +] + [[package]] name = "craft-keymap" version = "0.1.0" @@ -1433,21 +1463,6 @@ name = "craft-sheet" version = "0.1.0" source = "git+https://github.com/storytold/craft-libs?rev=ba24fe2435adb3c81ad6057faf36365fbba736cf#ba24fe2435adb3c81ad6057faf36365fbba736cf" -[[package]] -name = "craft-svg" -version = "0.1.0" -source = "git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895#2e670cf9377b523112f3fe4b409399772c745895" -dependencies = [ - "craft-image-codecs", - "craft-xml 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", - "flate2", - "kurbo", - "resvg 0.48.1", - "roxmltree 0.21.2", - "thiserror 2.0.21", - "usvg 0.48.1", -] - [[package]] name = "craft-svg" version = "0.1.0" @@ -1473,6 +1488,21 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "craft-svg" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f" +dependencies = [ + "craft-image-codecs 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)", + "flate2", + "kurbo", + "resvg 0.48.1", + "roxmltree 0.21.2", + "thiserror 2.0.21", + "usvg 0.48.1", +] + [[package]] name = "craft-ui" version = "0.1.0" @@ -1566,15 +1596,6 @@ dependencies = [ "thiserror 2.0.21", ] -[[package]] -name = "craft-xml" -version = "0.1.0" -source = "git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895#2e670cf9377b523112f3fe4b409399772c745895" -dependencies = [ - "quick-xml 0.41.0", - "thiserror 2.0.21", -] - [[package]] name = "craft-xml" version = "0.1.0" @@ -1602,6 +1623,15 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "craft-xml" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f#cc23ffd4d26f3074808e2187d332564817624f8f" +dependencies = [ + "quick-xml 0.41.0", + "thiserror 2.0.21", +] + [[package]] name = "craft-xml" version = "0.1.0" @@ -1842,7 +1872,7 @@ name = "designcraft-cli" version = "0.20.1" dependencies = [ "craft-file-formats", - "craft-image-codecs", + "craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", "designcraft-color", "designcraft-compose", "designcraft-doc", @@ -1913,7 +1943,7 @@ dependencies = [ "craft-indd", "craft-prepress", "craft-print", - "craft-svg 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", + "craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)", "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=f23b0f33f0d00b157476533a43cbbcfd901aa1f1)", "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=fbdd905cdddd2f0de930021a3f92bcf1684973be)", "designcraft-color", @@ -1999,6 +2029,7 @@ name = "designcraft-idml" version = "0.20.1" dependencies = [ "craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=fbdd905cdddd2f0de930021a3f92bcf1684973be)", + "craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)", "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=fbdd905cdddd2f0de930021a3f92bcf1684973be)", "designcraft-color", "designcraft-compose", @@ -2017,9 +2048,9 @@ dependencies = [ "craft-cms", "craft-eps 0.1.0 (git+https://github.com/storytold/craft-libs?rev=ba24fe2435adb3c81ad6057faf36365fbba736cf)", "craft-file-formats", - "craft-image-codecs", + "craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", "craft-psd", - "craft-svg 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", + "craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)", "designcraft-color", "designcraft-fonts", "flate2", @@ -2049,7 +2080,7 @@ version = "0.20.1" dependencies = [ "chrono", "craft-cms", - "craft-image-codecs", + "craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", "craft-pdf", "craft-prepress", "designcraft-color", @@ -2074,7 +2105,7 @@ name = "designcraft-pptx" version = "0.20.1" dependencies = [ "chrono", - "craft-image-codecs", + "craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", "craft-opc 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8abfa9d0f14bafbfc3449fc552ffeafb9ca6a551)", "craft-pptx", "designcraft-color", @@ -2094,7 +2125,7 @@ dependencies = [ name = "designcraft-render" version = "0.20.1" dependencies = [ - "craft-image-codecs", + "craft-image-codecs 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", "craft-pdf", "designcraft-color", "designcraft-compose", diff --git a/Cargo.toml b/Cargo.toml index 18b2e4c22..9f399a2b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ craft-eps = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435 craft-epub = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" } craft-idml = { git = "https://github.com/storytold/craft-libs", rev = "fbdd905cdddd2f0de930021a3f92bcf1684973be", version = "=0.1.0" } craft-indd = { git = "https://github.com/storytold/craft-libs", rev = "47bf5650b0d503b97bfb5e841746c4957897d11f", version = "=0.1.0" } -craft-svg = { git = "https://github.com/dylanmtaylor/craft-libs", rev = "2e670cf9377b523112f3fe4b409399772c745895", version = "=0.1.0", features = ["tree-render", "tree-text"] } +craft-svg = { git = "https://github.com/storytold/craft-libs", rev = "cc23ffd4d26f3074808e2187d332564817624f8f", version = "=0.1.0", features = ["tree-render", "tree-text"] } craft-xml = { git = "https://github.com/storytold/craft-libs", rev = "fbdd905cdddd2f0de930021a3f92bcf1684973be", version = "=0.1.0" } craft-xml-dtd = { package = "craft-xml", git = "https://github.com/storytold/craft-libs", rev = "f23b0f33f0d00b157476533a43cbbcfd901aa1f1", version = "=0.1.0" } craft-rtf = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" } diff --git a/crates/idml/Cargo.toml b/crates/idml/Cargo.toml index 217e29329..7a6a64058 100644 --- a/crates/idml/Cargo.toml +++ b/crates/idml/Cargo.toml @@ -15,6 +15,7 @@ designcraft-doc = { workspace = true } designcraft-images = { workspace = true } craft-idml = { workspace = true } craft-xml = { workspace = true } +craft-svg = { workspace = true } thiserror = { workspace = true } log = { workspace = true } diff --git a/crates/idml/src/import/item.rs b/crates/idml/src/import/item.rs index e47734e52..c9b47c4db 100644 --- a/crates/idml/src/import/item.rs +++ b/crates/idml/src/import/item.rs @@ -7,31 +7,6 @@ use super::{ Story, StoryId, Stroke, SubPath, TextFrame, TextFrameOptions, base64_decode, names, sniff_image, swatch, unescape_id, }; -/// The viewBox size of an SVG whose root has a viewBox but no width/height (percentages count as -/// none): InDesign places such a file at one viewBox unit to the point. -fn svg_viewbox_size(data: &[u8]) -> Option<(f64, f64)> { - if data.iter().find(|b| !b.is_ascii_whitespace()) != Some(&b'<') { - return None; - } - let mut options = craft_xml::Options::default(); - options.limits.max_input = 16 << 20; - let root = options.parse(data).ok()?; - if root.local() != "svg" { - return None; - } - let attr = |name: &str| root.attr(name).map(ToString::to_string); - let absolute = |name: &str| attr(name).is_some_and(|v| !v.trim().ends_with('%')); - if absolute("width") || absolute("height") { - return None; - } - let vb: Vec = - attr("viewBox")?.split(|c: char| c.is_whitespace() || c == ',').filter(|v| !v.is_empty()).filter_map(|v| v.parse().ok()).collect(); - match vb[..] { - [_, _, w, h] if w.is_finite() && h.is_finite() && w > 0.0 && h > 0.0 => Some((w, h)), - _ => None, - } -} - impl<'r> Importer<'r> { pub(super) fn item(&mut self, e: &El, parent: Affine) -> Option { let model_item = craft_idml::model::PageItem::from_el(&crate::xml::to_craft(e))?; @@ -326,7 +301,7 @@ impl<'r> Importer<'r> { // file once. let key = (p.clone(), page, pdf_crop); if let Some(id) = self.linked_assets.get(&key) { - let size = self.assets.get(id).and_then(|a| svg_viewbox_size(&a.data)).unwrap_or(size); + let size = self.assets.get(id).and_then(|a| craft_svg::sniff::viewbox_size(&a.data)).unwrap_or(size); return graphic(*id, size); } if let Some(d) = (self.read_link)(p) { @@ -339,7 +314,7 @@ impl<'r> Importer<'r> { let preview = data.is_empty().then(|| g.get("Self").and_then(|s| self.previews.get(s))).flatten(); // InDesign draws a viewBox-only SVG at one unit to the point: its GraphicBounds are the // art's bounds, which can overhang the viewBox, so they must not size the viewBox. - let size = svg_viewbox_size(&data).unwrap_or(size); + let size = craft_svg::sniff::viewbox_size(&data).unwrap_or(size); let (mime, px) = if preview.is_some() { (None, None) } else { sniff_image(&data) }; let mime = mime.map(str::to_string).unwrap_or_else(|| match link.and_then(|k| k.get("LinkResourceFormat")).unwrap_or("") { f if f.contains("EPS") || g.local() == "EPS" => "application/postscript".into(),