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##""## + ); + 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();