diff --git a/crates/xfa/src/layout/measure.rs b/crates/xfa/src/layout/measure.rs
index 2fffb3dfa0..6532f6511e 100644
--- a/crates/xfa/src/layout/measure.rs
+++ b/crates/xfa/src/layout/measure.rs
@@ -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,21 @@ 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;
+ };
+ 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.
diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs
index e424be5441..ff7c43fdbc 100644
--- a/crates/xfa/src/tests.rs
+++ b/crates/xfa/src/tests.rs
@@ -2997,6 +2997,29 @@ 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#"{name}{item}"#
+ )
+ };
+ let body = format!(
+ r#"{}{}"#,
+ 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;
+ // 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"));
+}
+
/// 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.