From 85d7c161613df14c53759c0094b4ed0b8d9c945f Mon Sep 17 00:00:00 2001 From: moppeeer Date: Thu, 15 Oct 2026 00:49:33 +0200 Subject: [PATCH] IDML: InDesign's default CJK leading model only with a Japanese composer InDesign writes LeadingModel="LeadingModelAkiBelow" on [No paragraph style] of every document, Western ones included, but only its Japanese composers use the leading model. DesignCraft applied it to every paragraph, so a 24 pt paragraph after a 32 pt one sat 34.53 pt below it instead of the Roman 32.8 (4 pt space after + 28.8 auto leading) in Montilla Design's Flex Layout exercise file, and its fitted captions came out overset. The import now drops that default when no part of the package applies a Japanese composer (HL Composer J / HL Single J). Leading models set on text, other styles, or in documents that use a Japanese composer are kept. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 1 + crates/idml/src/import.rs | 29 ++++++++++++++++++++++++++++- crates/idml/src/tests.rs | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9f8168e50..7daa06fa5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -132,6 +132,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m Newest first. +- **2026-10-15** · IDML: the CJK leading model InDesign writes on [No paragraph style] of every document (Aki Below) only applies when the document uses a Japanese composer, as in InDesign; a Western document's paragraphs after a size change no longer sit lower than in InDesign (Montilla Design's Flex Layout file: its captions came out overset). No readiness percentage change. - **2026-10-13** · Object › Fitting has InDesign's shortcuts: Fill Frame Proportionally ⌘⌥⇧C, Fit Content Proportionally ⌘⌥⇧E, Fit Frame to Content ⌘⌥C, Fit Content to Frame ⌘⌥E, Center Content ⌘⇧E. No readiness percentage change. - **2026-10-13** · Gradients between process colours blend as inks, as InDesign shows them: magenta to cyan passes through C 50 M 50 (a light lilac) on screen instead of the darker RGB mix, and a midpoint between CMYK or grey stops no longer turns the PDF shading RGB. No readiness percentage change. - **2026-10-14** · The Swatches panel has InDesign's header: a Fill/Stroke proxy, Formatting Affects Container/Text and the Tint of the colour in front (a tint alone keeps each object's own swatch). Clicking a swatch applies it to the fill or stroke in front (of the text, when formatting affects text), and the bottom row has New Color Group, New Swatch and Delete Swatch. No readiness percentage change. diff --git a/crates/idml/src/import.rs b/crates/idml/src/import.rs index 51dc66d84..a0aa73d00 100644 --- a/crates/idml/src/import.rs +++ b/crates/idml/src/import.rs @@ -106,6 +106,7 @@ pub fn import_idml_report(bytes: &[u8], read_link: &dyn Fn(&str) -> Option Option bool { + const KEY: &[u8] = b"Composer=\""; + let mut rest = data; + while let Some(at) = rest.windows(KEY.len()).position(|w| w == KEY) { + let value = rest.get(at + KEY.len()..).unwrap_or_default(); + let end = value.iter().position(|b| *b == b'"').unwrap_or(value.len()); + let name = String::from_utf8_lossy(value.get(..end).unwrap_or_default()); + if name.ends_with(" J") || name.contains("Japanese") { + return true; + } + rest = value.get(end..).unwrap_or_default(); + } + false +} + /// 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 { @@ -159,6 +177,8 @@ struct ItemCtx { struct Importer<'r> { read_link: &'r dyn Fn(&str) -> Option>, + /// The package applies a Japanese composer somewhere (see [`names_japanese_composer`]). + japanese_composer: bool, next_id: u64, settings: DocSettings, default_margins: Option<(Margins, Columns)>, @@ -275,6 +295,7 @@ impl<'r> Importer<'r> { styles.paragraph.iter_mut().for_each(|s| s.based_on = None); Importer { read_link, + japanese_composer: false, next_id: 0, settings: DocSettings::default(), default_margins: None, @@ -903,7 +924,13 @@ impl<'r> Importer<'r> { let based_on = if name == designcraft_doc::NO_PARA_STYLE { None } else { self.based_on(e, true) }; let next_style = e.get("NextStyle").map(|n| self.para_style_ref(n)).filter(|n| *n != name); let para = self.para_attrs(e); - let chars = self.char_attrs(e); + let mut chars = self.char_attrs(e); + // InDesign writes its CJK leading model (Aki Below) on [No paragraph style] of every + // document; only the Japanese composers use it, so a document set with the Paragraph or + // Single-line Composer spaces its lines baseline to baseline. + if name == designcraft_doc::NO_PARA_STYLE && !self.japanese_composer { + chars.leading_model = None; + } let s = ParagraphStyle { name: name.clone(), based_on, next_style, para, chars, shortcut: String::new() }; match self.styles.para_mut(&name) { Some(slot) => *slot = s, diff --git a/crates/idml/src/tests.rs b/crates/idml/src/tests.rs index 57801e34f..7c9087da1 100644 --- a/crates/idml/src/tests.rs +++ b/crates/idml/src/tests.rs @@ -2169,3 +2169,35 @@ fn frame_preference_property_color_resolves_for_frames_and_object_styles() { assert_eq!(colors, vec!["Near", "Near", "Far", designcraft_color::swatch::NONE]); assert_eq!(d.styles.object_style("Child").unwrap().text_frame.as_ref().unwrap().column_rule_color, "Near"); } + +/// InDesign writes `LeadingModel="LeadingModelAkiBelow"` on [No paragraph style] of every +/// document, but only its Japanese composers use the leading model: with the Paragraph Composer, +/// a 24 pt paragraph after a 32 pt one sits a Roman 28.8 pt below it (Montilla Design's Flex Layout +/// file: frames fitted to that came out overset). +#[test] +fn the_default_cjk_leading_model_applies_only_with_a_japanese_composer() { + let styles = |composer: &str| { + STYLES.replace( + r#"Name="$ID/[No paragraph style]" PointSize="12">"#, + &format!(r#"Name="$ID/[No paragraph style]" PointSize="12" LeadingModel="LeadingModelAkiBelow" Composer="{composer}">"#), + ) + }; + let model = |composer: &str| { + let s = styles(composer); + let pkg = zip_files(&[ + ("designmap.xml", DESIGNMAP), + ("Resources/Graphic.xml", GRAPHIC), + ("Resources/Styles.xml", &s), + ("Resources/Preferences.xml", PREFS), + ("MasterSpreads/MasterSpread_m1.xml", MASTER), + ("Spreads/Spread_sp1.xml", SPREAD), + ("Stories/Story_s1.xml", STORY), + ]); + let d = import_idml(&pkg).unwrap(); + d.styles.para(designcraft_doc::NO_PARA_STYLE).unwrap().chars.leading_model + }; + assert_eq!(model("HL Composer"), None, "the Paragraph Composer leads baseline to baseline"); + assert_eq!(model("HL Single"), None); + assert_eq!(model("$ID/HL Composer J"), Some(designcraft_doc::cjk::LeadingModel::AkiBelow)); + assert_eq!(model("$ID/HL Single J"), Some(designcraft_doc::cjk::LeadingModel::AkiBelow)); +}