Read IDML page margins through shared model

This commit is contained in:
Dylan M. Taylor committed 2026-11-13 04:52:05 -05:00
1 parent ababbf02ac
commit a6f8be4a38
5 files changed
+42 -36

No files matched your search

Generated
+17 -17
View File
@@ -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",
+2 -2
View File
@@ -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" }
+2 -2
View File
@@ -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" => {
+20 -14
View File
@@ -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<Vec<f64>> {
let want = 2 * count as usize;
let pos = s.split_whitespace().take(want + 1).map(|v| v.parse::<f64>().ok()).collect::<Option<Vec<f64>>>()?;
fn custom_columns(pos: &[f64], count: u32, gutter: f64) -> Option<Vec<f64>> {
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::<Vec<_>>().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::<Vec<_>>().join(" ")
+1 -1
View File
@@ -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);