diff --git a/crates/idml/src/import.rs b/crates/idml/src/import.rs index 4c78c5a0e..e41bce376 100644 --- a/crates/idml/src/import.rs +++ b/crates/idml/src/import.rs @@ -112,6 +112,15 @@ pub fn import_idml_report(bytes: &[u8], read_link: &dyn Fn(&str) -> Option 0 { + warnings.push(format!( + "tab lists repaired to fit the limits (at most {} stops, finite positions up to {} in, leaders of at most {} characters, one stop per position): {}", + designcraft_doc::MAX_TAB_STOPS, + designcraft_doc::MAX_TAB_POSITION / 72.0, + designcraft_doc::MAX_TAB_LEADER, + im.repaired_tab_lists, + )); + } let document = im.finish(&root).map_err(|e| match e { // Without its spread parts there is nothing to open: say which parts were missing. IdmlError::Invalid(msg) if !warnings.is_empty() => IdmlError::Invalid(format!("{msg} ({})", warnings.join("; "))), @@ -121,6 +130,19 @@ pub fn import_idml_report(bytes: &[u8], read_link: &dyn Fn(&str) -> Option bool { + let kept = |t: &TabStop| { + sanitized.iter().any(|o| { + o.position == t.position + && o.leader == t.leader + && (o.align_on == t.align_on || (t.align_on.is_empty() && o.align == designcraft_doc::TabAlign::Char)) + }) + }; + read.len() != sanitized.len() || !read.iter().all(kept) +} + /// A package part name in the form the lookup compares: InDesign and other IDML writers differ /// in percent-encoding, case, path separators and a leading `./` or `/`. fn normalize_part_name(name: &str) -> String { @@ -233,6 +255,8 @@ struct Importer<'r> { /// Creation and modification times (Unix seconds) from the package metadata. created: Option, modified: Option, + /// Tab lists [`TabStop::sanitized_list`] had to repair (an import warning). + repaired_tab_lists: usize, } fn lab_to_color(l: f32, a: f32, b: f32) -> Color { @@ -374,6 +398,7 @@ impl<'r> Importer<'r> { text_var_ids: HashMap::new(), created: None, modified: None, + repaired_tab_lists: 0, } } @@ -1593,7 +1618,11 @@ impl<'r> Importer<'r> { align_on: get("AlignmentCharacter").filter(|c| c != ".").unwrap_or_default(), }); } - a.tabs = Some(TabStop::sanitized_list(tabs)); + let sanitized = TabStop::sanitized_list(tabs.clone()); + if tab_list_repaired(&tabs, &sanitized) { + self.repaired_tab_lists = self.repaired_tab_lists.saturating_add(1); + } + a.tabs = Some(sanitized); } a.list_type = e.prop("BulletsAndNumberingListType").map(|v| match v.trim() { "BulletList" => ListType::Bullets, diff --git a/crates/idml/src/tests.rs b/crates/idml/src/tests.rs index 06e0f7237..4a3a940c5 100644 --- a/crates/idml/src/tests.rs +++ b/crates/idml/src/tests.rs @@ -2626,10 +2626,24 @@ fn hostile_tab_lists_import_within_the_limits() { Many tabs "# ); - let doc = import_idml(&fixture_with_story(&story)).unwrap(); + let report = import_idml_report(&fixture_with_story(&story), &|_| None).unwrap(); + assert!(report.warnings.iter().any(|w| w.starts_with("tab lists repaired to fit the limits") && w.ends_with("): 1")), "{:?}", report.warnings); + let doc = report.document; let tabs = doc.stories.values().find(|s| s.text.contains("Many tabs")).unwrap().paras[0].para.tabs.clone().unwrap(); assert_eq!(tabs.len(), designcraft_doc::MAX_TAB_STOPS); assert!(tabs.iter().all(|t| t.position.is_finite() && t.leader.chars().count() <= designcraft_doc::MAX_TAB_LEADER)); assert!(tabs.windows(2).all(|w| w[0].position < w[1].position), "sorted"); assert_eq!((tabs[0].position, tabs[99].position), (0.0, 297.0), "the first stops by position"); + // A list within the limits, unsorted and with a char stop on `.`, imports without a warning. + let fine = format!( + r#" + + {}{}CharacterAlign.40 + Few tabs + "#, + item("90", "."), + item("12", "") + ); + let report = import_idml_report(&fixture_with_story(&fine), &|_| None).unwrap(); + assert!(report.warnings.is_empty(), "{:?}", report.warnings); }