diff --git a/crates/forms/src/appearance.rs b/crates/forms/src/appearance.rs index e5fe07eb9..23d00e21f 100644 --- a/crates/forms/src/appearance.rs +++ b/crates/forms/src/appearance.rs @@ -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 { "" })); diff --git a/crates/forms/src/tests.rs b/crates/forms/src/tests.rs index 4255cbac8..8aa34f0e1 100644 --- a/crates/forms/src/tests.rs +++ b/crates/forms/src/tests.rs @@ -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 = 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