diff --git a/crates/xfa/src/layout.rs b/crates/xfa/src/layout.rs
index 72ab40fc6..6de989bf4 100644
--- a/crates/xfa/src/layout.rs
+++ b/crates/xfa/src/layout.rs
@@ -1062,6 +1062,14 @@ impl Layouter<'_> {
}
}
+ /// The face a field's text is laid out in: at its horizontal scale when the layout draws
+ /// the text itself (a non-interactive field), as designed when the forms layer draws it in
+ /// a widget, whose appearances have no horizontal scaling.
+ fn field_text_face(f: &Field, font: &Font) -> Face {
+ let face = Face::of(font);
+ if f.access == Access::NonInteractive { face } else { face.unscaled() }
+ }
+
/// The height the text of a multi-line text field takes (its value in the data, else the
/// template's), for a field without a fixed height, which grows to hold its text. `ctx`
/// is the field's own (its SOM ends in the field). Zero for anything else.
@@ -1078,7 +1086,7 @@ impl Layouter<'_> {
};
let w = (inner_w - beside - f.ui_margin.horizontal() - 4.0).max(1.0);
let font = ctx.font.apply(&f.common.font);
- text::measure_height(&text::plain(value.trim_end()), w, &ctx.para, &Face::of(&font).unscaled(), &|_| String::new())
+ text::measure_height(&text::plain(value.trim_end()), w, &ctx.para, &Self::field_text_face(f, &font), &|_| String::new())
}
fn group_height(&self, g: &ExclGroup, inner_w: f64, ctx: &Ctx, depth: usize) -> f64 {
@@ -1583,7 +1591,9 @@ impl Layouter<'_> {
right: f.ui_margin.right + 2.0,
};
let para = Para { v_align: VAlign::Top, ..fctx.para.clone() };
- let block = text::layout(&text::plain(value.trim_end()), ui.inset(&m), ¶, &Face::of(&fctx.font), &|_| String::new());
+ // In the face it was measured in, so it takes the lines it was given.
+ let face = Self::field_text_face(f, &fctx.font);
+ let block = text::layout(&text::plain(value.trim_end()), ui.inset(&m), ¶, &face, &|_| String::new());
return self.push_block(block);
}
}
diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs
index 215b1cf3a..41a6781f4 100644
--- a/crates/xfa/src/tests.rs
+++ b/crates/xfa/src/tests.rs
@@ -80,6 +80,37 @@ fn horizontally_scaled_text_is_measured_and_drawn_narrower() {
assert!((half.face.width("half") - full.face.width("half") / 2.0).abs() < 1e-9);
}
+#[test]
+fn a_non_interactive_field_is_measured_at_the_scale_its_text_is_drawn() {
+ // A growing, non-interactive text field (drawn as text by the layout, not as a widget),
+ // then a draw below it. However condensed the field's text is, the draw sits the same
+ // distance below its last line: the field is as tall as the lines it shows.
+ // Long enough to take more than a multi-line field's three lines at either scale.
+ const TEXT: &str = "Remarks that run over several lines of a narrow box, and then over several more lines again, until the box has grown well past the three lines it starts with";
+ let gap = |scale: u32| {
+ let xdp = format!(
+ r##"
+
+
+ {TEXT}
+ AFTER
+
+"##
+ );
+ let form = layout_xml(&xdp).unwrap();
+ let spans: Vec<&text::Span> = form.pages[0].items.iter().filter_map(|i| if let Item::Text(s) = i { Some(s) } else { None }).collect();
+ let after = spans.iter().find(|s| s.text.contains("AFTER")).expect("the draw").baseline;
+ let lines: std::collections::BTreeSet =
+ spans.iter().filter(|s| !s.text.contains("AFTER")).map(|s| (s.baseline * 100.0).round() as i64).collect();
+ let last = *lines.iter().next_back().expect("the field's lines") as f64 / 100.0;
+ (lines.len(), after - last)
+ };
+ let (full_lines, full_gap) = gap(100);
+ let (narrow_lines, narrow_gap) = gap(60);
+ assert!(narrow_lines < full_lines, "condensed, the text takes fewer lines: {narrow_lines} vs {full_lines}");
+ assert!((narrow_gap - full_gap).abs() < 0.01, "the field shrinks with its text: gap {narrow_gap} vs {full_gap}");
+}
+
#[test]
fn horizontally_scaled_text_is_written_with_tz_inside_its_own_graphics_state() {
let (xdp, _) = scaled_text_template();