M6: the engine checks that the XFA data layer and forms spell state names alike

pdfcraft-xfa keeps its own copy of pdfcraft_forms::name_text (it can't
depend on forms), and a radio group's value only finds its button while the
two agree. xfa_and_forms_spell_names_alike compares them, and checks that
forms' name_bytes reads the copy's spelling back, for UTF-8, '#', Shift-JIS
and stray bytes. With the copy writing lowercase hex it fails at the
Shift-JIS case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
This commit is contained in:
mtlcl410
2026-11-02 10:53:08 +08:00
co-authored by Claude Opus 5.5
parent 0e70564cec
commit 2b74366c64
3 changed files with 14 additions and 4 deletions
+11
View File
@@ -6146,6 +6146,17 @@ fn a_static_xfa_radio_group_with_numeric_items_keeps_the_picked_button() {
}
}
/// The XFA data layer keeps its own copy of the forms crate's name spelling (it can't depend on
/// forms), and a radio group's value only finds its button while the two agree: UTF-8 as
/// itself, `#` and bytes that aren't UTF-8 as `#XX`.
#[test]
fn xfa_and_forms_spell_names_alike() {
for bytes in [&b"Yes"[..], b"", b"a b", b"A#1", "はい".as_bytes(), b"\x82\xcd\x82\xa2", b"\xff#\x00", b"\x7f\x20~"] {
assert_eq!(pdfcraft_xfa::data::name_text(bytes), pdfcraft_forms::name_text(bytes), "{bytes:?}");
assert_eq!(pdfcraft_forms::name_bytes(&pdfcraft_xfa::data::name_text(bytes)), bytes, "{bytes:?}");
}
}
/// Taking the XFA out of a radio group whose buttons carry `/PCOn` as well as `/Opt` (the item
/// saving writes ahead of the `/Opt` entry) notes no lost data when the data holds that item:
/// one equal to the on state, and one equal to neither the state nor the `/Opt` entry.
+1 -3
View File
@@ -36,9 +36,7 @@ pub use import::{DataImport, ImportNode, export_data, import_data, parse_data_fi
pub use passwords::{clear_password_values, has_password_values};
pub(crate) use path::template_field;
pub use path::{data_path, parse_datasets, som_to_path};
#[cfg(test)]
pub(crate) use values::name_text;
pub use values::{FieldData, FieldDatum, is_on, radio_dataset_value, radio_state_for_item, read_values, read_values_with};
pub use values::{FieldData, FieldDatum, is_on, name_text, radio_dataset_value, radio_state_for_item, read_values, read_values_with};
#[cfg(test)]
pub(crate) use write::ENSURED;
pub use write::{
+2 -1
View File
@@ -76,7 +76,8 @@ fn radio_widgets(doc: &Document, f: &FieldDatum) -> Vec<(Option<String>, Option<
/// written `#XX`, as in PDF name syntax. This is `pdfcraft_forms::name_text`, which this crate
/// can't depend on; the value saving gets is read through it, and the state opening returns is
/// written back through its inverse, `pdfcraft_forms::name_bytes`.
pub(crate) fn name_text(bytes: &[u8]) -> String {
/// The engine checks that the two spell names alike (`xfa_and_forms_spell_names_alike`).
pub fn name_text(bytes: &[u8]) -> String {
let escape = |b: u8| format!("#{b:02X}");
match std::str::from_utf8(bytes) {
Ok(s) => s.replace('#', "#23"),