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());