From ababbf02acb8fe15b2643f25fdec79bd23519bd1 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 13 Nov 2026 04:48:32 -0500 Subject: [PATCH] Import IDML page and spread values through model --- Cargo.lock | 20 ++++---- Cargo.toml | 4 +- crates/idml/src/import/spread.rs | 82 ++++++++++++-------------------- 3 files changed, 42 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21385de2a..aa813576f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1225,10 +1225,10 @@ dependencies = [ [[package]] name = "craft-idml" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5#356ddacd1aac03c285b90a0596a7320ddf5eebb5" +source = "git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca#584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca" dependencies = [ - "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5)", - "craft-zip 0.1.0 (git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", + "craft-zip 0.1.0 (git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", "thiserror 2.0.21", ] @@ -1578,7 +1578,7 @@ dependencies = [ [[package]] name = "craft-xml" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5#356ddacd1aac03c285b90a0596a7320ddf5eebb5" +source = "git+https://github.com/storytold/craft-libs?rev=40525913633942f84b06d4837a8d90534a55d63f#40525913633942f84b06d4837a8d90534a55d63f" dependencies = [ "quick-xml 0.41.0", "thiserror 2.0.21", @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "craft-xml" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=40525913633942f84b06d4837a8d90534a55d63f#40525913633942f84b06d4837a8d90534a55d63f" +source = "git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca#584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca" dependencies = [ "quick-xml 0.41.0", "thiserror 2.0.21", @@ -1633,7 +1633,7 @@ dependencies = [ [[package]] name = "craft-zip" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5#356ddacd1aac03c285b90a0596a7320ddf5eebb5" +source = "git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca#584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca" dependencies = [ "flate2", "thiserror 2.0.21", @@ -1914,7 +1914,7 @@ dependencies = [ "craft-prepress", "craft-print", "craft-svg 0.1.0 (git+https://github.com/dylanmtaylor/craft-libs?rev=2e670cf9377b523112f3fe4b409399772c745895)", - "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=f23b0f33f0d00b157476533a43cbbcfd901aa1f1)", "designcraft-color", "designcraft-compose", @@ -1998,8 +1998,8 @@ dependencies = [ name = "designcraft-idml" version = "0.20.1" dependencies = [ - "craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5)", - "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5)", + "craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", "designcraft-color", "designcraft-compose", "designcraft-doc", @@ -2116,7 +2116,7 @@ version = "0.20.1" dependencies = [ "craft-docx", "craft-file-formats", - "craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=356ddacd1aac03c285b90a0596a7320ddf5eebb5)", + "craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", "craft-rtf", "craft-wordmodel", "craft-xlsx", diff --git a/Cargo.toml b/Cargo.toml index 9bbc5cd8a..20b2e635b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,10 @@ craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "4052591363 craft-document-bundle = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" } craft-eps = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" } 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 = "356ddacd1aac03c285b90a0596a7320ddf5eebb5", version = "=0.1.0" } +craft-idml = { git = "https://github.com/storytold/craft-libs", rev = "584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca", 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-xml = { git = "https://github.com/storytold/craft-libs", rev = "356ddacd1aac03c285b90a0596a7320ddf5eebb5", version = "=0.1.0" } +craft-xml = { git = "https://github.com/storytold/craft-libs", rev = "584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca", 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" } craft-xlsx = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" } diff --git a/crates/idml/src/import/spread.rs b/crates/idml/src/import/spread.rs index 2ec04c8f3..41c6c55b5 100644 --- a/crates/idml/src/import/spread.rs +++ b/crates/idml/src/import/spread.rs @@ -1,29 +1,8 @@ use super::frame::{fit_columns, margins_of}; -use super::values::{nums, parse_affine}; use super::{ Affine, Arc, Columns, El, Guide, Importer, ItemId, Margins, Orientation, Page, PageId, PageSide, ParentInfo, Point, Rect, Spread, SpreadId, names, }; -/// View › Rotate Spread from a `Spread` element's `ItemTransform`: quarter turns clockwise on -/// screen (IDML y points down, so `0 1 -1 0` is one clockwise turn). The spread's items and -/// pages stay in unrotated spread coordinates, so only the rotation part is read. Anything other -/// than an exact quarter turn (identity, scale, other angles, malformed numbers) is no rotation. -fn spread_view_rotation(s: Option<&str>) -> u8 { - let parsed: std::result::Result, _> = s.unwrap_or("").split_whitespace().take(7).map(str::parse::).collect(); - let Some([a, b, c, d, _, _]) = parsed.ok().and_then(|v| <[f64; 6]>::try_from(v).ok()) else { return 0 }; - let is = |x: f64, y: f64| (x - y).abs() < 1e-6; - let quarter = |m: [f64; 4]| is(a, m[0]) && is(b, m[1]) && is(c, m[2]) && is(d, m[3]); - if quarter([0.0, 1.0, -1.0, 0.0]) { - 1 - } else if quarter([-1.0, 0.0, 0.0, -1.0]) { - 2 - } else if quarter([0.0, -1.0, 1.0, 0.0]) { - 3 - } else { - 0 - } -} - impl<'r> Importer<'r> { // ---------- spreads and items ---------- @@ -55,22 +34,28 @@ impl<'r> Importer<'r> { } /// The page rectangles of a spread element, in IDML spread coordinates. - fn page_rects<'a>(&self, e: &'a El) -> Vec<(Rect, &'a El)> { - let mut pages: Vec<(Rect, &'a El)> = Vec::new(); - for p in e.find_all("Page") { - let gb = nums(p.get("GeometricBounds").unwrap_or("")); - let (t, l, bt, r) = - if gb.len() == 4 { (gb[0], gb[1], gb[2], gb[3]) } else { (0.0, 0.0, self.settings.page_height, self.settings.page_width) }; - let xf = parse_affine(p.get("ItemTransform")); + fn page_rects(&self, spread: &craft_idml::model::Spread) -> Vec<(Rect, craft_idml::model::Page)> { + let mut pages: Vec<(Rect, craft_idml::model::Page)> = Vec::new(); + for p in spread.pages() { + let gb = p.geometric_bounds().unwrap_or([0.0, 0.0, self.settings.page_height, self.settings.page_width]); + let [t, l, bt, r] = gb; + let xf = Affine::new(p.item_transform()); let a = xf * Point::new(l, t); let c = xf * Point::new(r, bt); - pages.push((Rect::from_points(a, c), p)); + pages.push((Rect::from_points(a, c), p.clone())); } pages } /// A page's margins and columns, and whether they are local (differ from its parent page's). - fn page_margins(&self, p: &El, r: &Rect, master: bool, parent: Option, (side, index): (PageSide, usize)) -> (Margins, Columns, bool) { + fn page_margins( + &self, + p: &craft_idml::model::Page, + r: &Rect, + master: bool, + parent: Option, + (side, index): (PageSide, usize), + ) -> (Margins, Columns, bool) { // A document page's margins are local when they differ from its parent page's; a // page without its own takes the parent page's. let from_parent = if master { @@ -81,7 +66,8 @@ impl<'r> Importer<'r> { .and_then(|sp| sp.pages.get(sp.parent_page_at(side, index)?)) .map(|q| (q.margins, q.columns.clone())) }; - let (margins, columns) = match (p.find("MarginPreference"), &from_parent) { + let preference = p.margin_preference().map(|v| crate::xml::from_craft(v.element("MarginPreference"))); + let (margins, columns) = match (preference.as_ref(), &from_parent) { (Some(m), _) => { let (margins, columns) = margins_of(m); (margins, fit_columns(columns, &margins, r.width())) @@ -97,9 +83,10 @@ impl<'r> Importer<'r> { } pub(super) fn spread(&mut self, e: &El, id: SpreadId, master: bool) -> Spread { + let model_spread = craft_idml::model::Spread::from_el(&crate::xml::to_craft(e)); let facing = self.settings.facing_pages; // Page rectangles in IDML spread coordinates. - let pages = self.page_rects(e); + let pages = self.page_rects(&model_spread); let min_x = pages.iter().map(|(r, _)| r.x0).fold(f64::INFINITY, f64::min); let min_y = pages.iter().map(|(r, _)| r.y0).fold(f64::INFINITY, f64::min); let (min_x, min_y) = if min_x.is_finite() { (min_x, min_y) } else { (0.0, 0.0) }; @@ -107,13 +94,13 @@ impl<'r> Importer<'r> { // The spine: between a left and a right page (else the spread's left edge). let spine = pages.iter().map(|(r, _)| r.x0 - min_x).filter(|x| *x > 1e-6).fold(f64::INFINITY, f64::min); let spine = if spine.is_finite() && facing { spine } else { 0.0 }; - let show = e.get("ShowMasterItems") != Some("false"); + let show = model_spread.show_master_items(); let first_abs: usize = self.spreads.iter().map(|s| s.pages.len()).sum(); - let view_rotation = if master { 0 } else { spread_view_rotation(e.get("ItemTransform")) }; + let view_rotation = model_spread.view_rotation(); let mut out_pages = Vec::new(); for (i, (r, p)) in pages.iter().enumerate() { let pid = PageId(self.alloc()); - if let Some(s) = p.get("Self") + if let Some(s) = p.self_id() && !master { self.page_index.insert(s.to_string(), first_abs + i); @@ -125,7 +112,7 @@ impl<'r> Importer<'r> { } else { PageSide::Right }; - let parent = p.get("AppliedMaster").and_then(|m| self.parent_ids.get(m)).copied(); + let parent = p.applied_master().and_then(|m| self.parent_ids.get(m)).copied(); let (margins, columns, local_margins) = self.page_margins(p, r, master, parent, (side, i)); out_pages.push(Page { id: pid, @@ -138,20 +125,15 @@ impl<'r> Importer<'r> { parent, side, overridden: vec![], - guides: self.guides_of( - &craft_idml::model::Page::from_el(&crate::xml::to_craft(p)), - Rect::new(r.x0 - min_x, r.y0 - min_y, r.x1 - min_x, r.y1 - min_y), - spine, - ), + guides: self.guides_of(p, Rect::new(r.x0 - min_x, r.y0 - min_y, r.x1 - min_x, r.y1 - min_y), spine), show_parent_items: show, - liquid: names::liquid_rule_in(p.get("LayoutRule").unwrap_or("Off")), + liquid: names::liquid_rule_in(p.layout_rule()), transition: None, view_rotation: if i == 0 { view_rotation } else { 0 }, show_parent_overlay: false, }); } - let overrides: Vec> = - pages.iter().map(|(_, p)| p.get("OverrideList").unwrap_or("").split_whitespace().map(str::to_string).collect()).collect(); + let overrides: Vec> = pages.iter().map(|(_, p)| p.override_list().into_iter().map(str::to_string).collect()).collect(); let mut items = Vec::new(); for c in e.elements() { if let Some(it) = self.item(c, shift) { @@ -162,15 +144,11 @@ impl<'r> Importer<'r> { pg.overridden = ov.iter().filter_map(|s| self.item_ids.get(s)).copied().filter(|i| self.is_parent_item(*i)).collect(); } let parent = if master { - let prefix = - e.get("NamePrefix").map(str::to_string).unwrap_or_else(|| e.get("Name").unwrap_or("A").split('-').next().unwrap_or("A").to_string()); - let name = e.get("BaseName").map(str::to_string).unwrap_or_else(|| { - let n = e.get("Name").unwrap_or("Parent"); - n.split_once('-').map(|(_, b)| b.to_string()).unwrap_or_else(|| n.to_string()) - }); + let prefix = model_spread.parent_prefix(); + let name = model_spread.base_name(); // A parent spread has one base: its first page's. Pages that disagree lose theirs. let applied: Vec> = - pages.iter().map(|(_, p)| p.get("AppliedMaster").and_then(|m| self.parent_ids.get(m)).copied()).collect(); + pages.iter().map(|(_, p)| p.applied_master().and_then(|m| self.parent_ids.get(m)).copied()).collect(); let label = format!("{prefix}-{name}"); if applied.windows(2).any(|w| w[0] != w[1]) { self.warnings.push(format!("parent {label}: its pages are based on different parents; only the first page's is kept")); @@ -189,7 +167,7 @@ impl<'r> Importer<'r> { p.parent = None; } } - Spread { id, pages: out_pages, items, parent, allow_shuffle: e.get("AllowPageShuffle") != Some("false") } + Spread { id, pages: out_pages, items, parent, allow_shuffle: model_spread.allow_shuffle() } } pub(super) fn is_parent_item(&self, id: ItemId) -> bool {