diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 0759685ff6..111696b731 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -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. diff --git a/crates/xfa/src/data.rs b/crates/xfa/src/data.rs index 1455586383..ba17c65ca6 100644 --- a/crates/xfa/src/data.rs +++ b/crates/xfa/src/data.rs @@ -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::{ diff --git a/crates/xfa/src/data/values.rs b/crates/xfa/src/data/values.rs index 294b407540..3fb8b3ca97 100644 --- a/crates/xfa/src/data/values.rs +++ b/crates/xfa/src/data/values.rs @@ -76,7 +76,8 @@ fn radio_widgets(doc: &Document, f: &FieldDatum) -> Vec<(Option, 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"),