mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 14:08:23 -06:00
M6: form_fill also warns about lines too tall or too wide for their box (#4018)
Two cases still drew a value cut off without saying so. A single-line value at a fixed font size taller than the box (12 pt text in an 8 pt field) was checked only for width, so it fit sideways and was cut at the top and bottom. In a multiline field, a character wider than the box becomes a line of its own, which fits vertically but is cut at the sides. The single-line and comb branch now compares the line's ascent to descent against the clip box (a new `Overflow::High`), and the multiline branch reports the widest wrapped line that is still wider than the box. An empty value draws nothing, so it is never reported, however small the box. Fixes #4018
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user