From a6f8be4a3832a75c4408dd87bd5f2c091993d0a5 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 13 Nov 2026 04:52:05 -0500 Subject: [PATCH] Read IDML page margins through shared model --- Cargo.lock | 34 ++++++++++++++++---------------- Cargo.toml | 4 ++-- crates/idml/src/import.rs | 4 ++-- crates/idml/src/import/frame.rs | 34 +++++++++++++++++++------------- crates/idml/src/import/spread.rs | 2 +- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa813576f..d97fc8c3d 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=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca#584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca" +source = "git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5#11511e0d82a2f92ca4092eb02e8ef67cc85b42a5" dependencies = [ - "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)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5)", + "craft-zip 0.1.0 (git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5)", "thiserror 2.0.21", ] @@ -1566,6 +1566,15 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "craft-xml" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5#11511e0d82a2f92ca4092eb02e8ef67cc85b42a5" +dependencies = [ + "quick-xml 0.41.0", + "thiserror 2.0.21", +] + [[package]] name = "craft-xml" version = "0.1.0" @@ -1584,15 +1593,6 @@ dependencies = [ "thiserror 2.0.21", ] -[[package]] -name = "craft-xml" -version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca#584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca" -dependencies = [ - "quick-xml 0.41.0", - "thiserror 2.0.21", -] - [[package]] name = "craft-xml" version = "0.1.0" @@ -1633,7 +1633,7 @@ dependencies = [ [[package]] name = "craft-zip" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca#584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca" +source = "git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5#11511e0d82a2f92ca4092eb02e8ef67cc85b42a5" 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=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5)", "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=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", - "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", + "craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5)", + "craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5)", "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=584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca)", + "craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=11511e0d82a2f92ca4092eb02e8ef67cc85b42a5)", "craft-rtf", "craft-wordmodel", "craft-xlsx", diff --git a/Cargo.toml b/Cargo.toml index 20b2e635b..1bb07e656 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 = "584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca", version = "=0.1.0" } +craft-idml = { git = "https://github.com/storytold/craft-libs", rev = "11511e0d82a2f92ca4092eb02e8ef67cc85b42a5", 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 = "584e00bf1f7e812e4fd204bc9b077c8a96e0d1ca", version = "=0.1.0" } +craft-xml = { git = "https://github.com/storytold/craft-libs", rev = "11511e0d82a2f92ca4092eb02e8ef67cc85b42a5", 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.rs b/crates/idml/src/import.rs index 8a2f49759..5573590db 100644 --- a/crates/idml/src/import.rs +++ b/crates/idml/src/import.rs @@ -33,7 +33,7 @@ mod text_attrs; mod values; use document::IndexTopic; -use frame::margins_of; +use frame::margins_of_el; use package::Package; #[cfg(test)] pub(crate) use package::{percent_decode, uri_to_path}; @@ -349,7 +349,7 @@ impl<'r> Importer<'r> { } } "MarginPreference" => { - let (m, c) = margins_of(e); + let (m, c) = margins_of_el(e); self.default_margins = Some((m, c)); } "ViewPreference" => { diff --git a/crates/idml/src/import/frame.rs b/crates/idml/src/import/frame.rs index 396168127..190428aee 100644 --- a/crates/idml/src/import/frame.rs +++ b/crates/idml/src/import/frame.rs @@ -17,31 +17,37 @@ pub(super) fn text_wrap(w: &El) -> TextWrap { tw } -pub(super) fn margins_of(m: &El) -> (Margins, Columns) { - let g = |k: &str, d: f64| m.num(k).filter(|v| v.is_finite()).unwrap_or(d); - let margins = Margins { top: g("Top", 36.0), bottom: g("Bottom", 36.0), inside: g("Left", 36.0), outside: g("Right", 36.0) }; - let count = g("ColumnCount", 1.0).clamp(1.0, f64::from(designcraft_doc::MAX_COLUMNS)) as u32; - let gutter = g("ColumnGutter", 12.0); - let positions = m.get("ColumnsPositions").and_then(|s| custom_columns(s, count, gutter)); +pub(super) fn margins_of(m: &craft_idml::model::MarginPreference) -> (Margins, Columns) { + let margins = Margins { + top: m.top().unwrap_or(36.0), + bottom: m.bottom().unwrap_or(36.0), + inside: m.left().unwrap_or(36.0), + outside: m.right().unwrap_or(36.0), + }; + let count = m.column_count().unwrap_or(1.0).clamp(1.0, f64::from(designcraft_doc::MAX_COLUMNS)) as u32; + let gutter = m.column_gutter().unwrap_or(12.0); + let positions = m.column_positions(2 * count as usize + 1).and_then(|pos| custom_columns(&pos, count, gutter)); (margins, Columns { count, gutter, positions }) } +pub(super) fn margins_of_el(m: &El) -> (Margins, Columns) { + margins_of(&craft_idml::model::MarginPreference::from_el(&crate::xml::to_craft(m))) +} + /// `ColumnsPositions` when the column guides have been dragged off the even split: `2 × count` /// offsets from the margin box's left edge, valid as [`designcraft_doc::valid_column_positions`] /// over the box they span. InDesign writes the attribute whether or not they have been moved, so /// an even set is dropped: `positions` means "custom", and keeping an even copy would freeze the /// columns against a later change of count or gutter. Anything malformed is the even split; at /// most one number past `2 × count` is parsed, so a long list costs nothing. -fn custom_columns(s: &str, count: u32, gutter: f64) -> Option> { - let want = 2 * count as usize; - let pos = s.split_whitespace().take(want + 1).map(|v| v.parse::().ok()).collect::>>()?; +fn custom_columns(pos: &[f64], count: u32, gutter: f64) -> Option> { let width = *pos.last()?; - if !designcraft_doc::valid_column_positions(&pos, count, width) { + if !designcraft_doc::valid_column_positions(pos, count, width) { return None; } let even = Columns { count, gutter, positions: None }.edges(width); // The tolerance only absorbs float noise in InDesign's arithmetic; a dragged guide moves far more. - pos.iter().zip(&even).any(|(a, b)| (a - b).abs() > 1e-4).then_some(pos) + pos.iter().zip(&even).any(|(a, b)| (a - b).abs() > 1e-4).then(|| pos.to_vec()) } /// `columns` with custom positions only when they fit the margin box of a `page_width`-wide page @@ -351,7 +357,7 @@ mod page_column_import_safety_tests { .attr("Bottom", "-inf") .attr("Left", "NaN") .attr("Right", "1e309"); - let (margins, columns) = margins_of(&m); + let (margins, columns) = margins_of_el(&m); assert_eq!(columns.count, expected); assert_eq!(columns.gutter, 12.); assert_eq!(margins, Margins::uniform(36.)); @@ -364,7 +370,7 @@ mod page_column_import_safety_tests { fn hostile_column_positions_fall_back_to_the_even_split() { let positions = |count: &str, raw: &str| { let m = El::new("MarginPreference").attr("ColumnCount", count).attr("ColumnGutter", "20").attr("ColumnsPositions", raw); - margins_of(&m).1.positions + margins_of_el(&m).1.positions }; assert_eq!(positions("2", "0 150 170 450"), Some(vec![0., 150., 170., 450.])); let huge = (0..10_000).map(|i| i.to_string()).collect::>().join(" "); @@ -391,7 +397,7 @@ mod page_column_import_safety_tests { // makes them custom. let gapless = |raw: &str| { let m = El::new("MarginPreference").attr("ColumnCount", "216").attr("ColumnGutter", "0").attr("ColumnsPositions", raw); - margins_of(&m).1.positions + margins_of_el(&m).1.positions }; let edges = |first_right: &str| { (0..432).map(|i| if i == 1 { first_right.to_string() } else { ((i + 1) / 2).to_string() }).collect::>().join(" ") diff --git a/crates/idml/src/import/spread.rs b/crates/idml/src/import/spread.rs index 41c6c55b5..0ec3f88ed 100644 --- a/crates/idml/src/import/spread.rs +++ b/crates/idml/src/import/spread.rs @@ -66,7 +66,7 @@ impl<'r> Importer<'r> { .and_then(|sp| sp.pages.get(sp.parent_page_at(side, index)?)) .map(|q| (q.margins, q.columns.clone())) }; - let preference = p.margin_preference().map(|v| crate::xml::from_craft(v.element("MarginPreference"))); + let preference = p.margin_settings(); let (margins, columns) = match (preference.as_ref(), &from_parent) { (Some(m), _) => { let (margins, columns) = margins_of(m);