M12: a round check button's underline border stays an underline

This commit is contained in:
Eric Kaden
2026-10-23 17:03:53 -04:00
parent 21f3241e6d
commit ac181479ef
2 changed files with 18 additions and 7 deletions
+9 -3
View File
@@ -358,8 +358,8 @@ fn is_round(doc: &Document, wd: &Dict) -> bool {
/// [`frame`] for a round check button: the `/MK /BG` background as a disc and the `/MK /BC`
/// border as a circle, both centred in the `w` × `h` box and as wide as its shorter side. A
/// dashed border stays dashed; any other style is drawn solid, and a border as wide as the
/// circle fills it. Returns the border width.
/// dashed border stays dashed and an underline stays an underline; any other style is drawn
/// solid, and a border as wide as the circle fills it. Returns the border width.
fn round_frame(doc: &Document, wd: &Dict, mk: Option<&Dict>, w: f64, h: f64) -> (String, f64) {
let mut c = String::new();
let (cx, cy, r) = (w / 2.0, h / 2.0, w.min(h).max(0.0) / 2.0);
@@ -368,14 +368,20 @@ fn round_frame(doc: &Document, wd: &Dict, mk: Option<&Dict>, w: f64, h: f64) ->
}
let bs = wd.get(b"BS").map(|b| doc.resolve(b)).and_then(|b| b.as_dict().cloned());
let bw = bs.as_ref().and_then(|b| b.get(b"W")).and_then(|x| doc.resolve(x).as_f64()).unwrap_or(1.0).max(0.0);
let style = bs.as_ref().and_then(|b| b.name(b"S").map(<[u8]>::to_vec)).unwrap_or_default();
match color_array(doc, mk, b"BC") {
// An underline stays one: only the bottom edge, as for any widget.
Some(bc) if bw > 0.0 && style == b"U" => {
c.push_str(&format!("{}\n{} w\n0 {} m {} {} l S\n", stroke_op(&bc), n(bw), n(bw / 2.0), n(w), n(bw / 2.0)));
(c, bw)
}
// A border as wide as the circle fills it: a disc in the border's colour.
Some(bc) if bw > 0.0 && bw >= 2.0 * r => {
c.push_str(&format!("{bc}\n{}f\n", circle_path(cx, cy, r)));
(c, 2.0 * r)
}
Some(bc) if bw > 0.0 => {
let dashed = bs.as_ref().and_then(|b| b.name(b"S")) == Some(b"D");
let dashed = style == b"D";
// The stroke is centred on the path: inset by half its width, it stays inside.
let path = circle_path(cx, cy, r - bw / 2.0);
c.push_str(&format!("{}\n{} w\n{}{path}S\n[] 0 d\n", stroke_op(&bc), n(bw), if dashed { "[3] 0 d\n" } else { "" }));
+9 -4
View File
@@ -389,7 +389,8 @@ fn opt_export_values_select_button_states() {
/// A check box laid out for a round XFA check button (`/PCXfaRound true`) draws its background
/// and border as a circle inside the widget, inset by half the border so the stroke stays in,
/// or a disc when the border is as wide as the circle; one without the key keeps its rectangle.
/// or a disc when the border is as wide as the circle (an underline stays one); one without the
/// key keeps its rectangle.
#[test]
fn a_round_xfa_check_button_draws_a_round_background_and_border() {
let widget = |name: &str, extra: &str| {
@@ -400,15 +401,16 @@ fn a_round_xfa_check_button_draws_a_round_background_and_border() {
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 [5 0 R 6 0 R 7 0 R 8 0 R] >>".into(),
"<< /Fields [5 0 R 6 0 R 7 0 R 8 0 R] >>".into(),
"<< /Type /Page /Parent 2 0 R /Annots [5 0 R 6 0 R 7 0 R 8 0 R 9 0 R] >>".into(),
"<< /Fields [5 0 R 6 0 R 7 0 R 8 0 R 9 0 R] >>".into(),
widget("round", " /PCXfaRound true"),
widget("square", ""),
widget("dashed", " /PCXfaRound true /BS << /W 2 /S /D >>").replace("/BS << /W 1 /S /S >>", ""),
widget("thick", " /PCXfaRound true /BS << /W 30 /S /S >>").replace("/BS << /W 1 /S /S >>", ""),
widget("underlined", " /PCXfaRound true /BS << /W 1 /S /U >>").replace("/BS << /W 1 /S /S >>", ""),
];
let mut doc = document(&objs);
for name in ["round", "square", "dashed", "thick"] {
for name in ["round", "square", "dashed", "thick", "underlined"] {
redraw_field(&mut doc, name).unwrap();
}
// The Off state: only the frame.
@@ -432,6 +434,9 @@ fn a_round_xfa_check_button_draws_a_round_background_and_border() {
// A border wider than the 20pt circle fills it: a disc in the border's colour, no stroke.
let thick = off("thick");
assert!(thick.contains("1 0 0 rg\n20 10 m") && !thick.contains(" S\n"), "a border as wide as the circle is a disc: {thick}");
// An underline stays the bottom edge, under a round background.
let underlined = off("underlined");
assert!(underlined.contains("1 0 0 RG\n1 w\n0 0.5 m 20 0.5 l S"), "an underline stays an underline: {underlined}");
}
/// Issue #1323: a text field without a format keeps a whitespace-only value as typed; only an