diff --git a/crates/forms/src/appearance.rs b/crates/forms/src/appearance.rs index 651f8e3208..9b52c5a33e 100644 --- a/crates/forms/src/appearance.rs +++ b/crates/forms/src/appearance.rs @@ -460,6 +460,8 @@ pub enum Overflow { Wide { width: f64, room: f64, size: f64 }, /// `lines` wrapped lines, of which the box shows `room`, at `size` points. Tall { lines: usize, room: usize, size: f64 }, + /// One line `height` points high (ascent to descent), with `room` for it, at `size` points. + High { height: f64, room: f64, size: f64 }, } impl std::fmt::Display for Overflow { @@ -469,6 +471,9 @@ impl std::fmt::Display for Overflow { write!(f, "the value does not fit: {} pt wide at {} pt, room for {} pt", fmt(*width), fmt(*size), fmt(*room)) } Self::Tall { lines, room, size } => write!(f, "the value does not fit: {lines} lines at {} pt, room for {room}", fmt(*size)), + Self::High { height, room, size } => { + write!(f, "the value does not fit: {} pt high at {} pt, room for {} pt", fmt(*height), fmt(*size), fmt(*room)) + } } } } @@ -589,6 +594,9 @@ fn field_appearance_fit(doc: &Document, f: &Field, w: &Widget, values: &[String] } if room < lines.len() { overflow = Some(Overflow::Tall { lines: lines.len(), room, size }); + } else if let Some(tw) = lines.iter().map(|l| width_of(l, size)).filter(|tw| *tw > inner_w + 0.01).reduce(f64::max) { + // A character wider than the box is a line of its own, still cut at the sides. + overflow = Some(Overflow::Wide { width: tw, room: inner_w, size }); } } else { if size == 0.0 { @@ -601,6 +609,11 @@ fn field_appearance_fit(doc: &Document, f: &Field, w: &Widget, values: &[String] } // Centre Helvetica's ascent (0.718) and descent (0.207) vertically. let y = (height - 0.925 * size) / 2.0 + 0.207 * size; + // The clip box is inside the border; a line taller than it is cut at the top and bottom. + let room_h = height - 2.0 * bw; + if 0.925 * size > room_h + 0.01 { + overflow = Some(Overflow::High { height: 0.925 * size, room: room_h, size }); + } if comb { let cells = f.max_len.unwrap_or(1).max(1); let cell = width / cells as f64; @@ -610,7 +623,7 @@ fn field_appearance_fit(doc: &Document, f: &Field, w: &Widget, values: &[String] } } else { let tw = width_of(&text.replace(['\n', '\r'], " "), size); - if tw > inner_w + 0.01 { + if overflow.is_none() && tw > inner_w + 0.01 { overflow = Some(Overflow::Wide { width: tw, room: inner_w, size }); } show(&mut body, x_for(&text, size), y, &text); @@ -643,6 +656,8 @@ fn field_appearance_fit(doc: &Document, f: &Field, w: &Widget, values: &[String] d.set(b"Subtype".to_vec(), Object::name("Form")); set_form_box(&mut d, place); d.set(b"Resources".to_vec(), Object::Dict(res)); + // Nothing drawn is nothing cut, however small the box. + let overflow = overflow.filter(|_| !text.is_empty()); (Stream::flate(d, &content), unencodable, overflow) } diff --git a/crates/forms/src/tests.rs b/crates/forms/src/tests.rs index 84ce879847..237b9d1092 100644 --- a/crates/forms/src/tests.rs +++ b/crates/forms/src/tests.rs @@ -3018,3 +3018,50 @@ fn a_value_wider_or_taller_than_its_box_is_reported() { assert_eq!(overflow("wide"), None); assert_eq!(overflow("short"), None); } + +/// #4018: a single line taller than its box is cut at the top and bottom, and a character wider +/// than a multiline box is a line of its own that is still cut at the sides; both are reported. +/// An empty value draws nothing, so nothing is cut however small the box. +#[test] +fn a_line_taller_than_its_box_or_a_character_wider_than_it_is_reported() { + let field = |name: &str, rect: &str, extra: &str, value: &str| { + format!("<< /Type /Annot /Subtype /Widget /FT /Tx /T ({name}) /V ({value}) /Rect [{rect}] /P 3 0 R {extra} >>") + }; + let doc = document(&[ + "<< /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 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R] >>".into(), + "<< /Fields [6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R] /DA (/Helv 12 Tf 0 g) /DR << /Font << /Helv 5 0 R >> >> >>".into(), + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>".into(), + field("low", "50 700 190 708", "", "Ada"), + field("comb", "50 650 190 658", "/Ff 16777216 /MaxLen 5", "Ada"), + field("narrow", "50 500 56 560", "/Ff 4096", "WW"), + field("roomy", "50 400 190 418", "", "Ada"), + field("empty", "50 350 190 358", "", ""), + field("empty-multi", "50 300 190 304", "/Ff 4096", ""), + ]); + let overflow = |name: &str| { + let f = fields(&doc).into_iter().find(|f| f.name == name).unwrap(); + appearance::value_overflow(&doc, &f, &f.widgets[0]) + }; + for name in ["low", "comb"] { + match overflow(name) { + Some(o @ appearance::Overflow::High { height, room, size }) => { + assert_eq!(size, 12.0); + assert!((height - 11.1).abs() < 0.01 && room <= 8.0, "{name}: {height} in {room}"); + assert!(o.to_string().starts_with("the value does not fit: 11.1 pt high at 12 pt"), "{o}"); + } + other => panic!("{name}: {other:?}"), + } + } + match overflow("narrow") { + Some(appearance::Overflow::Wide { width, room, size }) => { + assert_eq!(size, 12.0); + assert!(width > room && (width - 11.32).abs() < 0.1, "{width} in {room}"); + } + other => panic!("narrow: {other:?}"), + } + assert_eq!(overflow("roomy"), None); + assert_eq!(overflow("empty"), None); + assert_eq!(overflow("empty-multi"), None); +}