From d292dd2acd0cff545ab7144bee46c0464abad2fa Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:04:39 +0200 Subject: [PATCH 1/2] IDML export: write the graphic placed in a table cell (#1551) --- crates/idml/src/export.rs | 32 +++++++++++++++++++++++++++++++- crates/idml/src/tests.rs | 33 +++++++++++++++++++++++++++++++++ docs/target-app-parity.md | 2 +- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/crates/idml/src/export.rs b/crates/idml/src/export.rs index 99dcb383e..eb30d533c 100644 --- a/crates/idml/src/export.rs +++ b/crates/idml/src/export.rs @@ -2797,7 +2797,23 @@ impl<'a> Ex<'a> { ce.set(&format!("{side}StrokePriority"), priority); } } - for psr in self.story_paras(&cell.text) { + let mut paras = self.story_paras(&cell.text); + // A graphic cell holds its image as an inline object in the cell's first paragraph. + if let Some(g) = &cell.graphic + && let Some(object) = self.cell_graphic_el(g) + { + let mut range = self.csr_el(&CharFormat::default()); + range.push(object); + match paras.first_mut() { + Some(first) => first.push(range), + None => { + let mut psr = self.psr_el(&ParaFormat::default()); + psr.push(range); + paras.push(psr); + } + } + } + for psr in paras { ce.push(psr); } el.push(ce); @@ -2806,6 +2822,20 @@ impl<'a> Ex<'a> { el } + /// The frame of a table cell's graphic: an inline `Rectangle` around the shown image, or `None` + /// when the graphic's asset is missing. + fn cell_graphic_el(&mut self, g: &designcraft_doc::Graphic) -> Option { + self.d.assets.get(&g.asset)?; + let shown = g.xf.transform_rect_bbox(designcraft_geom::Rect::new(0.0, 0.0, g.size.0, g.size.1)); + let layer = self.d.layers.first().map(|l| l.id).unwrap_or(designcraft_doc::ids::LayerId(0)); + let mut item = Item::new(designcraft_doc::ids::ItemId(0), layer, Shape::Rectangle, designcraft_geom::shapes::rectangle(shown)); + item.content = Content::Graphic(g.clone()); + let mut el = self.item_el(&item, Affine::IDENTITY); + el.set("Self", self.fresh()); + el.push(El::new("AnchoredObjectSetting").attr("AnchoredPosition", "InlinePosition").attr("AnchorYoffset", "0")); + Some(el) + } + fn psr_el(&mut self, pf: &ParaFormat) -> El { let mut el = El::new("ParagraphStyleRange").attr("AppliedParagraphStyle", names::style_self("ParagraphStyle", PARA_BUILTINS, &pf.style)); let mut props = Vec::new(); diff --git a/crates/idml/src/tests.rs b/crates/idml/src/tests.rs index 37fb2af50..7fce003f4 100644 --- a/crates/idml/src/tests.rs +++ b/crates/idml/src/tests.rs @@ -4368,3 +4368,36 @@ fn cell_first_baseline_comes_from_cell_styles_table_regions_and_cells_and_round_ let s2 = back.styles.cell.iter().find(|c| c.name == "Cap").unwrap(); assert_eq!(s2.first_baseline, Some(FirstBaseline::CapHeight)); } + +/// A graphic placed in a table cell is written into the cell, not dropped. +#[test] +fn exports_graphic_in_table_cell() { + let mut d = Document::new(&NewDocument::default()); + let lid = d.default_layer(); + let (_, sid) = d.add_text_frame(SpreadRef::Doc(0), Rect::new(40.0, 40.0, 400.0, 400.0), lid, "Intro", ParaFormat::default()).unwrap(); + let asset = designcraft_doc::AssetId(d.alloc()); + let id = d.add_asset(designcraft_doc::Asset { + id: asset, + name: "tick.png".into(), + mime: "image/png".into(), + data: std::sync::Arc::new(vec![1, 2, 3]), + ..Default::default() + }); + let mut t = designcraft_doc::Table::new(d.alloc(), 2, 2, 0, 0, 200.0); + t.cell_mut(1, 0).unwrap().graphic = Some(designcraft_doc::Graphic { + asset: id, + size: (24.0, 24.0), + xf: designcraft_geom::Affine::IDENTITY, + clip: None, + auto_fit: Default::default(), + fit_align: 4, + crop: [0.0; 4], + wrap: Default::default(), + fill: Default::default(), + }); + d.story_mut(sid).unwrap().insert_table(5, t); + let story = zip_text(&export_idml(&d), "Stories/"); + let cell = story.split("").next().unwrap_or_default(); + assert!(cell.contains(" Date: Fri, 23 Oct 2026 16:08:12 +0200 Subject: [PATCH 2/2] IDML export: clip a table cell's graphic to the cell (#1551) --- crates/idml/src/export.rs | 2 ++ crates/idml/src/tests.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/idml/src/export.rs b/crates/idml/src/export.rs index eb30d533c..df5a1a243 100644 --- a/crates/idml/src/export.rs +++ b/crates/idml/src/export.rs @@ -2802,6 +2802,8 @@ impl<'a> Ex<'a> { if let Some(g) = &cell.graphic && let Some(object) = self.cell_graphic_el(g) { + // The cell clips its graphic on screen and in PDF; IDML does so only when asked. + ce.set("ClipContentToCell", bool_s(true)); let mut range = self.csr_el(&CharFormat::default()); range.push(object); match paras.first_mut() { diff --git a/crates/idml/src/tests.rs b/crates/idml/src/tests.rs index 7fce003f4..1a9734a6b 100644 --- a/crates/idml/src/tests.rs +++ b/crates/idml/src/tests.rs @@ -4400,4 +4400,5 @@ fn exports_graphic_in_table_cell() { let cell = story.split("").next().unwrap_or_default(); assert!(cell.contains("