Merge PR #2280: Draw the widget border inside /Tx BMC so the reference app keeps it on focus changes (#1613)

This commit is contained in:
aiongg
2026-10-22 01:45:03 +00:00
2 changed files with 52 additions and 14 deletions
+14 -14
View File
@@ -542,20 +542,20 @@ pub fn field_appearance_drawn(doc: &Document, f: &Field, w: &Widget, values: &[S
}
}
}
let mut content = c.into_bytes();
content.extend(
format!(
"/Tx BMC\nq\n{} {} {} {} re W n\nBT\n/{} {} Tf\n{}\n",
n(bw),
n(bw),
n(width - 2.0 * bw),
n(height - 2.0 * bw),
font_name,
n(size),
da.color
)
.bytes(),
);
// Acrobat regenerates only what is inside /Tx BMC when it redraws a field (e.g. on focus
// changes), discarding what came before it (issues #1613, #633). The background and border
// therefore go inside the marked-content block too, so a redraw keeps them with the text.
let mut content = format!(
"/Tx BMC\nq\n{c}{} {} {} {} re W n\nBT\n/{} {} Tf\n{}\n",
n(bw),
n(bw),
n(width - 2.0 * bw),
n(height - 2.0 * bw),
font_name,
n(size),
da.color
)
.into_bytes();
content.extend(body);
content.extend_from_slice(b"ET\nQ\nEMC\n");
let mut fonts = Dict::new();
+38
View File
@@ -379,6 +379,44 @@ fn text_fields_get_new_appearances() {
assert!(raw.windows(8).any(|x| x == b"(Z\xfcrich)"));
}
/// Acrobat regenerates only the `/Tx BMC` block when it redraws a text field, so background and
/// border must live inside it rather than immediately before it (#1613).
fn marked_content_borders_field(appearance: &str, label: &str) {
let start = appearance.find("/Tx BMC").expect("no /Tx BMC");
let marked = appearance[start..].split_once("EMC").expect("no matching EMC").0;
assert!(marked.contains("1 1 0.9 rg"), "background inside /Tx BMC: {appearance} {label}");
assert!(marked.contains("0 0 0 RG\n1 w\n0.5 0.5 199 19 re S"), "border inside /Tx BMC: {appearance} {label}");
}
#[test]
fn text_and_date_fields_keep_background_and_border_inside_marked_content() {
let mut doc = one_page();
add_field(&mut doc, 0, [50.0, 700.0, 250.0, 720.0], &NewField::Text { multiline: false }, Some("name")).unwrap();
add_field(&mut doc, 0, [50.0, 660.0, 250.0, 680.0], &NewField::Date, Some("due")).unwrap();
let props = || FieldProps {
look: Some(Look {
border: Some([0.0; 3]),
fill: Some([1.0, 1.0, 0.9]),
width: 1.0,
style: BorderStyle::Solid,
text: [0.0, 0.0, 0.0],
font: FieldFont::Helvetica,
}),
..FieldProps::default()
};
set_props(&mut doc, "name", &props()).unwrap();
set_props(&mut doc, "due", &props()).unwrap();
set_value(&mut doc, "name", &FieldValue::Text("Ada".into())).unwrap();
set_value(&mut doc, "due", &FieldValue::Text("10/14/2026".into())).unwrap();
let name_ap = ap(&doc, &field(&fields(&doc), "name").widgets[0]);
let due_ap = ap(&doc, &field(&fields(&doc), "due").widgets[0]);
marked_content_borders_field(&name_ap, "text field");
marked_content_borders_field(&due_ap, "date field");
assert!(name_ap.contains("(Ada) Tj"), "{name_ap}");
assert!(due_ap.contains("(10/14/2026) Tj"), "{due_ap}");
assert!(!need_appearances(&doc), "drawable ASCII fields do not ask other viewers to redraw");
}
#[test]
fn existing_field_font_encoding_keeps_italian_punctuation_visible() {
// ReportLab-style Helvetica: an indirect encoding dictionary with PDFDoc-like Differences.