mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-06 10:47:27 +00:00
Merge pull request #5476 from KenM76/fix/xfa-check-button-width
M12: an XFA check box without a width is as wide as its box and caption
This commit is contained in:
2 files changed
+65
-1
No files matched your search
@@ -51,7 +51,8 @@ impl Layouter<'_> {
|
||||
let som = ctx.som.clone();
|
||||
// A container without a width is as wide as its content (a radio group beside its
|
||||
// question, say), and so is a text label or a button (as wide as its caption), within
|
||||
// what is available; any other field without one takes what is available.
|
||||
// what is available, and so is a check box (its box and caption); any other field without
|
||||
// one takes what is available.
|
||||
let w = match c.w {
|
||||
Some(w) => w,
|
||||
None => match node {
|
||||
@@ -65,6 +66,7 @@ impl Layouter<'_> {
|
||||
.as_ref()
|
||||
.and_then(|cap| self.natural_width(&cap.value, &ctx.font.apply(&cap.font), &ctx.para.apply(&cap.para)))
|
||||
.map_or(avail_w, |t| (t + c.margin.horizontal() + f.ui_margin.horizontal()).min(avail_w)),
|
||||
Node::Field(f) if f.ui == Ui::CheckButton => (self.check_width(f, &ctx) + c.margin.horizontal()).min(avail_w),
|
||||
Node::Field(_) => avail_w,
|
||||
},
|
||||
};
|
||||
@@ -149,6 +151,22 @@ impl Layouter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// How wide check box `f` is without a width of its own (XFA 3.3 Ch.8, growable fields): its
|
||||
/// box and widget margins, plus its caption beside it (its reserve, else its text), or the
|
||||
/// wider of the two when the caption is above or below.
|
||||
fn check_width(&self, f: &Field, ctx: &Ctx) -> f64 {
|
||||
let check = f.check_size.unwrap_or(10.0) + f.ui_margin.horizontal();
|
||||
let Some(cap) = f.caption.as_ref().filter(|c| c.presence.occupies() && c.placement != Placement::Inline) else {
|
||||
return check;
|
||||
};
|
||||
// The 2pt matches `caption_reserve`'s room for a caption without a reserve.
|
||||
let text = || self.natural_width(&cap.value, &ctx.font.apply(&cap.font), &ctx.para.apply(&cap.para)).map_or(0.0, |t| t + 2.0);
|
||||
match cap.placement {
|
||||
Placement::Top | Placement::Bottom => check.max(text()),
|
||||
_ => check + cap.reserve.unwrap_or_else(text),
|
||||
}
|
||||
}
|
||||
|
||||
/// How wide text `v` is unwrapped, in `font` and `para`, with a hair to spare so that laying
|
||||
/// it out at that width keeps it on its lines; `None` for what isn't text, or text that draws
|
||||
/// nothing.
|
||||
|
||||
@@ -2997,6 +2997,52 @@ fn a_radio_button_sits_where_a_check_box_would() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A check box without a width is as wide as its box and caption, at least its minW, not the rest
|
||||
/// of its group: two choices side by side in a positioned radio group keep their own boxes.
|
||||
#[test]
|
||||
fn a_radio_choice_without_a_width_is_as_wide_as_its_content() {
|
||||
let choice = |name: &str, x: &str, item: &str| {
|
||||
format!(
|
||||
r#"<field name="{name}" x="{x}" y="0mm" h="5.906mm" minW="20.32mm"><ui><checkButton shape="round"/></ui><caption reserve="-5.08mm"><value><text>{name}</text></value><para hAlign="center"/></caption><font typeface="Arial" size="10pt"/><items><text>{item}</text></items></field>"#
|
||||
)
|
||||
};
|
||||
let body = format!(
|
||||
r#"<subform name="body" layout="position" w="7.5in" h="1in"><exclGroup name="pick" x="0mm" y="0mm" w="40.64mm">{}{}</exclGroup></subform>"#,
|
||||
choice("Yes", "0mm", "Y"),
|
||||
choice("No", "20.308mm", "N"),
|
||||
);
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, &body)).unwrap();
|
||||
let laid = widgets(&form.pages[0]);
|
||||
let x = |name: &str| laid.iter().find(|w| w.name == name).map(|w| w.rect.x - 36.0).unwrap_or_else(|| panic!("no radio button {name}"));
|
||||
let mm = 72.0 / 25.4;
|
||||
// A negative reserve (as Designer writes) sizes the caption to its text, so content is
|
||||
// narrower than minW and each box sits at the right end of its own 20.32mm-wide choice.
|
||||
assert!((x("Yes") - (20.32 * mm - 10.0)).abs() < 0.01, "Yes box at {} pt", x("Yes"));
|
||||
assert!((x("No") - ((20.308 + 20.32) * mm - 10.0)).abs() < 0.01, "No box at {} pt", x("No"));
|
||||
}
|
||||
|
||||
/// Without a width or minW, a check box is exactly its caption's reserve plus its box, whether
|
||||
/// placed (its right-aligned box ends there) or flowed (two fit side by side in an lr-tb row).
|
||||
#[test]
|
||||
fn a_check_box_without_a_width_is_its_caption_and_box() {
|
||||
let check = |name: &str, at: &str| {
|
||||
format!(
|
||||
r#"<field name="{name}" {at} h="0.25in"><ui><checkButton size="10pt"/></ui><caption reserve="0.5in"><value><text>{name}</text></value></caption><para hAlign="right"/><items><integer>1</integer><integer>0</integer></items></field>"#
|
||||
)
|
||||
};
|
||||
let box_at = |body: String, name: &str| {
|
||||
let form = layout_xml(&one_area_form(r#"layout="tb""#, &body)).unwrap();
|
||||
widgets(&form.pages[0]).iter().find(|w| w.name == name).map(|w| (w.rect.x - 36.0, w.rect.y - 36.0)).unwrap()
|
||||
};
|
||||
let placed = format!(r#"<subform name="body" layout="position" w="7.5in" h="1in">{}</subform>"#, check("placed", r#"x="0in" y="0in""#));
|
||||
let (x, _) = box_at(placed, "placed");
|
||||
assert!((x - 36.0).abs() < 0.01, "the box ends the 46pt field, after the 36pt caption: {x}");
|
||||
|
||||
let flowed = format!(r#"<subform name="body" layout="lr-tb" w="3in">{}{}</subform>"#, check("first", ""), check("second", ""));
|
||||
let (x, y) = box_at(flowed, "second");
|
||||
assert!((x - 82.0).abs() < 0.01 && y.abs() < 0.01, "the second check box follows the first on its row: ({x}, {y})");
|
||||
}
|
||||
|
||||
/// A caption without a reserve leaves room for the box at the size it is drawn, a box larger than
|
||||
/// its field shrinking to fit; and a field without a height is as tall as that caption drawn in
|
||||
/// all the box leaves, not at some narrower width.
|
||||
|
||||
Reference in new issue
Block a user