diff --git a/crates/idml/src/export.rs b/crates/idml/src/export.rs index 9d73562b9..610204d83 100644 --- a/crates/idml/src/export.rs +++ b/crates/idml/src/export.rs @@ -2055,7 +2055,8 @@ impl<'a> Ex<'a> { .attr("VerticalReferencePoint", names::anchor_rel_out(*y_relative, true)) .attr("HorizontalAlignment", ["LeftAlign", "CenterAlign", "RightAlign"][(*ref_point % 3) as usize]) .attr("VerticalAlignment", ["TopAlign", "CenterAlign", "BottomAlign"][(*ref_point / 3).min(2) as usize]) - .attr("AnchorXoffset", num(*x_offset)) + // IDML measures a left-aligned object's X offset leftwards. + .attr("AnchorXoffset", num(if *ref_point % 3 == 0 { 0.0 - *x_offset } else { *x_offset })) .attr("AnchorYoffset", num(*y_offset)) .attr("PinPosition", bool_s(*keep_within_column)), }; diff --git a/crates/idml/src/import.rs b/crates/idml/src/import.rs index ddd51dc06..916f9cc66 100644 --- a/crates/idml/src/import.rs +++ b/crates/idml/src/import.rs @@ -1876,7 +1876,8 @@ impl<'r> Importer<'r> { designcraft_doc::AnchorPosition::Custom { x_relative: names::anchor_rel_in(s("HorizontalReferencePoint")), y_relative: names::anchor_rel_in(s("VerticalReferencePoint")), - x_offset: get("AnchorXoffset"), + // IDML measures a left-aligned object's X offset leftwards. + x_offset: if col == 0 { 0.0 - get("AnchorXoffset") } else { get("AnchorXoffset") }, y_offset: get("AnchorYoffset"), object_point: names::anchor_in(s("AnchorPoint")), ref_point: row * 3 + col, diff --git a/crates/idml/src/tests.rs b/crates/idml/src/tests.rs index 3035d75d3..4a8642afb 100644 --- a/crates/idml/src/tests.rs +++ b/crates/idml/src/tests.rs @@ -822,13 +822,65 @@ fn anchored_objects_take_unset_settings_from_object_styles() { assert_eq!( positions, [ - custom(R::TextFrame, R::Anchor, 5.0, -3.0, 8, true), + custom(R::TextFrame, R::Anchor, -5.0, -3.0, 8, true), custom(R::TextFrame, R::PageEdge, 0.0, 0.0, 0, true), - custom(R::PageMargin, R::Anchor, 2.0, 0.0, 8, false), + custom(R::PageMargin, R::Anchor, -2.0, 0.0, 8, false), ] ); } +/// IDML measures a left-aligned anchored object's X offset leftwards; the document model and +/// composition measure every X offset rightwards. +#[test] +fn left_aligned_anchored_x_offsets_flip_at_the_idml_boundary() { + use designcraft_doc::AnchorPosition; + let rect = |id: &str, align: &str, x: &str| { + format!( + r#" + + + + + "# + ) + }; + let story = format!( + r#" + a{}{}{} + "#, + rect("left", "LeftAlign", "-108"), + rect("zero", "LeftAlign", "0"), + rect("center", "CenterAlign", "116"), + ); + let d = import_idml(&fixture_with_story(&story)).unwrap(); + let x_offsets = |d: &Document| -> Vec { + let s = d.stories.values().find(|s| !s.objects.is_empty()).unwrap(); + s.objects.iter().map(|o| if let AnchorPosition::Custom { x_offset, .. } = o.position { x_offset } else { f64::NAN }).collect() + }; + assert_eq!(x_offsets(&d), [108.0, 0.0, 116.0]); + // The left-aligned object lands 108 pt right of the reference point. + let sid = d.stories.iter().find(|(_, s)| !s.objects.is_empty()).map(|(id, _)| *id).unwrap(); + let cs = designcraft_compose::compose_story(&d, sid, &Default::default()); + let placed: Vec<_> = cs.frames.iter().flat_map(|f| &f.objects).collect(); + let x = |i: usize| placed.iter().find(|o| o.index == i).unwrap().origin.x; + assert!((x(0) - x(1) - 108.0).abs() < 1e-6, "{} {}", x(0), x(1)); + // Export writes the original IDML values back. + let bytes = export_idml(&d); + let mut zip = zip::ZipArchive::new(std::io::Cursor::new(bytes)).unwrap(); + let mut stories = String::new(); + for i in 0..zip.len() { + let mut f = zip.by_index(i).unwrap(); + if f.name().starts_with("Stories/") { + std::io::Read::read_to_string(&mut f, &mut stories).unwrap(); + } + } + for v in [r#"AnchorXoffset="-108""#, r#"AnchorXoffset="0""#, r#"AnchorXoffset="116""#] { + assert!(stories.contains(v), "{v} missing"); + } + assert_eq!(x_offsets(&import_idml(&export_idml(&d)).unwrap()), [108.0, 0.0, 116.0]); +} + #[test] fn round_trips_frames_with_pasted_in_items() { let mut d = Document::new(&NewDocument::default());