mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 03:27:21 -06:00
M12: a non-interactive XFA field is measured at its text's horizontal scale
The layout draws a non-interactive field's text itself, condensed, but measured its height at full width, so a condensed value reserved lines it didn't use. Fields now measure and draw their text in one face: scaled when the layout draws it, as designed when the forms layer does (the over-page fallback included).
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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##"<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/"><template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
|
||||
<subform name="form" layout="tb">
|
||||
<pageSet><pageArea name="front"><contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/><medium short="8.5in" long="11in"/></pageArea></pageSet>
|
||||
<field name="note" w="100pt" access="nonInteractive"><ui><textEdit multiLine="1"/></ui><value><text>{TEXT}</text></value><font typeface="Arial" size="10pt" fontHorizontalScale="{scale}%"/></field>
|
||||
<draw name="after" w="100pt" h="20pt"><value><text>AFTER</text></value><font typeface="Arial" size="10pt"/></draw>
|
||||
</subform>
|
||||
</template></xdp:xdp>"##
|
||||
);
|
||||
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<i64> =
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user