mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
fix(forms): a multiline field rejects a glyph wider than the field (#4447)
TextBox::fits checked only the height of a wrapped multi-line value. Long words break by character, so a single character wider than the box still overflows it, and the appearance reports Overflow::Wide for a value fits had accepted. The multi-line branch now also requires every wrapped line to fit the width.
This commit is contained in:
@@ -133,8 +133,11 @@ impl TextBox {
|
||||
// As the appearance draws it: the first baseline is 0.85 em below the padded top and
|
||||
// a line is drawn while its descent (0.207 em) stays inside the border, so the
|
||||
// padding below the last line is not needed: `inner_h` plus 2 pt.
|
||||
let above = wrap_with(&shown, size, self.inner_w, width_of).len().saturating_sub(1) as f64;
|
||||
above * size * 1.15 + 1.057 * size <= self.inner_h + 2.0 + 0.01
|
||||
// Long words break by character, so a line is wider than the box only when it is a
|
||||
// single character wider than the box, which the appearance still cuts at the sides.
|
||||
let lines = wrap_with(&shown, size, self.inner_w, width_of);
|
||||
let above = lines.len().saturating_sub(1) as f64;
|
||||
above * size * 1.15 + 1.057 * size <= self.inner_h + 2.0 + 0.01 && lines.iter().all(|l| width_of(l, size) <= self.inner_w + 0.01)
|
||||
} else {
|
||||
// A line break in a single-line value is drawn as a space.
|
||||
let line: String = shown.chars().map(|c| if matches!(c, '\n' | '\r') { ' ' } else { c }).collect();
|
||||
|
||||
@@ -471,6 +471,32 @@ fn a_short_multiline_field_takes_the_one_line_it_draws() {
|
||||
assert!(!tb("boxed").fits("one line"));
|
||||
}
|
||||
|
||||
/// A multi-line field narrower than one glyph: wrapping can't break inside a character, so the
|
||||
/// appearance cuts that character at the sides (`Overflow::Wide`), and `fits` must say so too.
|
||||
#[test]
|
||||
fn a_multiline_field_rejects_a_glyph_wider_than_the_field() {
|
||||
// 12 x 40 pt with no border: 8 pt inside the 2 pt padding. At 12 pt Helvetica a W is 11.3 pt
|
||||
// wide and an i 2.7 pt.
|
||||
let objs: Vec<String> = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 600 800] >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [6 0 R] >>".into(),
|
||||
"<< /Fields [6 0 R] /DA (/Helv 12 Tf 0 g) /DR << /Font << /Helv 5 0 R >> >> >>".into(),
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>".into(),
|
||||
format!(
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (thin) /V (W) /DA (/Helv 12 Tf 0 g) /Ff {} /Rect [50 660 62 700] /P 3 0 R >>",
|
||||
flags::MULTILINE
|
||||
),
|
||||
];
|
||||
let doc = document(&objs);
|
||||
let all = fields(&doc);
|
||||
let f = field(&all, "thin");
|
||||
let tb = appearance::text_box(&doc, f, &f.widgets[0]);
|
||||
assert!(tb.fits("i") && tb.fits("i\ni"));
|
||||
assert!(matches!(appearance::value_overflow(&doc, f, &f.widgets[0]), Some(appearance::Overflow::Wide { .. })));
|
||||
assert!(!tb.fits("W") && !tb.fits("iW"));
|
||||
}
|
||||
|
||||
/// An edit to a field that doesn't scroll keeps as much of what it inserted as fits, wherever the
|
||||
/// caret was, and always keeps a deletion.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user