M12: XFA border corners not listed are drawn the way edges are

XFA 3.3 p. 38 gives a border's corners in the order top-left, top-right,
bottom-right, bottom-left, as its edges go top, right, bottom, left, and
says that when fewer than four are listed the last one listed is reused
for the rest. Readers don't do that: every reader tested draws a side not
listed from the edge or corner on the same side, one listed serving all
four sides, two alternating (0, 1, 0, 1), three reusing the second for
the fourth (0, 1, 2, 1). The layout already drew edges the readers' way,
but drew corners as p. 38's text says, so with corners [round, square]
only the top-left was round where readers round the top-left and the
bottom-right.

Corners now follow the readers too, through one helper, drawn_side, that
edges and corners share: with [round, square] the top-left and
bottom-right are round and the top-right and bottom-left square. Edges
are drawn as before. The scripting host's reads of a corner past those
listed (from the previous commit) mirror the same pattern, so a script
reads the corner the page draws.

Real forms (358 public forms with XFA, templates read through the
engine): 337 borders in 51 forms list two or three corners that differ,
all only in a square corner's presence, which the layout doesn't draw
differently; none has a rounded or inverted corner that would move, so
no rendered page changes.

Tests: one, two and three corners, two in both orders, and the
rounded-corners test's two-corner box (now [r1, r2, r1, r2]) fail with
the change reverted; a script test reads corners past those listed by
the same pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBVyQEwtmEw3mS1MoAiwrK
This commit is contained in:
mtlcl410andClaude Opus 5.5 committed 2026-10-29 17:56:53 +08:00
1 parent 34f3716f5e
commit 8d40450d22
5 files changed
+79 -21

No files matched your search

+4 -2
View File
@@ -837,8 +837,10 @@ pub(crate) fn node_color(h: &XHost, node: usize, path: &str) -> String {
/// than four. XFA 3.3 p. 38 says the last one listed is reused for the rest; readers draw a
/// side not listed from the edge or corner on the same side instead, and reads follow them:
/// one serves every side, two alternate (0, 1, 0, 1), three reuse the second for the fourth
/// (0, 1, 2, 1). `None` past the four sides, and when none is listed (the side is a default
/// one).
/// (0, 1, 2, 1). `None` past the four sides, and when none is
/// listed (the side is a default one). The layout picks edges and corners with the same
/// pattern (`drawn_side` in `pdfcraft_xfa`, which this crate doesn't depend on); tests on both
/// sides pin it.
fn drawn_index(listed: usize, index: usize) -> Option<usize> {
match listed {
// A border has four sides: edges and corners occur at most four times (p. 629).
+18
View File
@@ -688,6 +688,24 @@ fn border_color_colours_all_four_edges() {
assert_eq!(eval("this.borderColor = '7,7,7'; this.border.getElement('edge', 3).thickness", "styled"), "2pt");
}
/// A corner past those a border lists reads as the corner drawn there: one serves all four,
/// two alternate, three reuse the second for the fourth, as for edges.
#[test]
fn corners_not_listed_read_as_the_corner_drawn_there() {
let corners = |listed: &[&str]| {
let kids = listed.iter().map(|r| p("corner", &[("radius", r)], vec![])).collect();
let f = node("c", XfaKind::Field, &[], vec![p("border", &[], kids)]);
let mut root = form();
root.children.push(f);
let o = go_on(&root, "[0, 1, 2, 3].map(function (i) { return this.border.getElement('corner', i).radius; }, this).join('|')", "c");
assert_eq!(o.error, None, "{o:?}");
o.result.unwrap_or_default()
};
assert_eq!(corners(&["1pt"]), "1pt|1pt|1pt|1pt");
assert_eq!(corners(&["1pt", "2pt"]), "1pt|2pt|1pt|2pt");
assert_eq!(corners(&["1pt", "2pt", "3pt"]), "1pt|2pt|3pt|2pt");
}
/// A border's presence reads `hidden` when the object has no `<border>` element, as readers
/// read it, and `visible` when it has one that doesn't say (XFA 3.3 p. 629).
#[test]
+25 -16
View File
@@ -136,26 +136,36 @@ fn parse_fill(n: Option<XmlNode>) -> Option<Color> {
Some(parse_color(child(n, "color")).unwrap_or(Color::WHITE))
}
/// `<border>`: edges in XFA order. One edge applies to all four; two: top/bottom then
/// left/right; three: top, left/right, bottom; four: top, right, bottom, left.
/// `<border>`: edges top, right, bottom, left (XFA 3.3 p. 38), the sides not listed drawn as
/// readers draw them ([`drawn_side`]), not as p. 38's text says.
fn parse_border(n: Option<XmlNode>) -> Option<Border> {
let n = n?;
let edges: Vec<Edge> = children(n, "edge").map(parse_edge).collect();
let d = Edge::default();
let e = |i: usize| edges.get(i).copied().unwrap_or(d);
let four = match edges.len() {
0 => [d; 4],
1 => [e(0); 4],
2 => [e(0), e(1), e(0), e(1)],
3 => [e(0), e(1), e(2), e(1)],
_ => [e(0), e(1), e(2), e(3)],
};
let edges: Vec<Edge> = children(n, "edge").take(4).map(parse_edge).collect();
let four = std::array::from_fn(|i| drawn_side(edges.len(), i).and_then(|k| edges.get(k).copied()).unwrap_or_default());
Some(Border { presence: Presence::parse(n.attribute("presence")), edges: four, corners: parse_corners(n), fill: parse_fill(child(n, "fill")) })
}
/// Which of the `listed` edges (or corners) of a border is drawn on side `index`: edges go
/// top, right, bottom, left and corners top-left, top-right, bottom-right, bottom-left (XFA 3.3
/// p. 38). For a border that lists fewer than four, p. 38 says the last one listed is reused
/// for the rest; readers draw a side not listed from the edge or corner on the same side
/// instead, and so does this: one serves every side, two alternate (0, 1, 0, 1), three reuse
/// the second for the fourth (0, 1, 2, 1). Edges already followed the readers; corners now do
/// too. The scripting host's reads mirror the same pattern so a script reads what is drawn.
/// `None` when none is listed.
pub(crate) fn drawn_side(listed: usize, index: usize) -> Option<usize> {
match listed {
0 => None,
_ if index < listed => Some(index),
1 => Some(0),
2 => Some(index % 2),
_ => Some(1),
}
}
/// The `<corner>` children of a border or rectangle, top-left, top-right, bottom-right then
/// bottom-left: one applies to all four, and a corner not given is the last one given. Without
/// any, the corners are square.
/// bottom-left, the ones not listed drawn as [`drawn_side`] picks, as edges are. Without any,
/// the corners are square.
fn parse_corners(n: XmlNode) -> [Corner; 4] {
let given: Vec<Corner> = children(n, "corner")
.take(4)
@@ -165,8 +175,7 @@ fn parse_corners(n: XmlNode) -> [Corner; 4] {
inverted: c.attribute("inverted") == Some("1"),
})
.collect();
let Some(last) = given.last().copied() else { return [Corner::default(); 4] };
std::array::from_fn(|i| given.get(i).copied().unwrap_or(last))
std::array::from_fn(|i| drawn_side(given.len(), i).and_then(|k| given.get(k).copied()).unwrap_or_default())
}
/// `<font>`: typeface, size, weight, posture, underline, fill color and horizontal scale.
+30 -1
View File
@@ -2412,8 +2412,9 @@ fn rounded_corners_make_a_border_one_rounded_shape() {
let round = at(0.0).expect("the rounded border is one shape");
assert_eq!((round.rect.w, round.rect.h, round.radii), (144.0, 72.0, [18.0; 4]));
assert_eq!((round.fill, round.edge), (Some([230.0 / 255.0; 3]), Some((1.0, [1.0, 0.0, 0.0], false))));
// Two corners alternate: the first is drawn top-left and bottom-right.
let mixed = at(1.2).expect("the box with two corners given");
assert_eq!((mixed.radii, mixed.inverted), ([7.2, 14.4, 14.4, 14.4], [false, true, true, true]));
assert_eq!((mixed.radii, mixed.inverted), ([7.2, 14.4, 7.2, 14.4], [false, true, false, true]));
assert_eq!(at(2.4).expect("the huge radius").radii, [36.0; 4], "at most half the shorter side");
assert!(at(3.6).is_none() && at(4.8).is_none(), "hidden corners and unlike edges keep a square box");
let fill_only = at(6.0).expect("a fill with hidden edges is rounded too");
@@ -2431,6 +2432,34 @@ fn rounded_corners_make_a_border_one_rounded_shape() {
assert_eq!((lines_at(3.6), lines_at(4.8)), (4, 2));
}
/// Corners not listed are drawn the way edges are, from the corner on the same side, as
/// readers draw them (XFA 3.3 p. 38 gives the order top-left, top-right, bottom-right,
/// bottom-left, and says the last corner listed is reused): one serves all four, two
/// alternate, three reuse the second for the bottom-left.
#[test]
fn corners_not_listed_follow_the_edge_pattern() {
let radii = |corners: &str| {
let body = format!(
r#"<subform name="body" layout="position" w="7.5in" h="9in"><draw name="d" x="0in" y="0in" w="2in" h="1in"><value><text>d</text></value><border><edge/>{corners}</border></draw></subform>"#
);
let form = layout_xml(&one_area_form(r#"layout="tb""#, &body)).unwrap();
rounded(&form.pages[0]).first().map(|r| r.radii)
};
let (round, square) = (r#"<corner radius="0.25in"/>"#, r#"<corner/>"#);
assert_eq!(radii(&format!("{round}{square}")), Some([18.0, 0.0, 18.0, 0.0]), "round top-left and bottom-right");
assert_eq!(radii(&format!("{square}{round}")), Some([0.0, 18.0, 0.0, 18.0]));
assert_eq!(
radii(r#"<corner radius="5pt"/><corner radius="10pt"/><corner radius="15pt"/>"#),
Some([5.0, 10.0, 15.0, 10.0]),
"the second serves the bottom-left"
);
assert_eq!(radii(round), Some([18.0; 4]));
for (listed, sides) in [(1, [0, 0, 0, 0]), (2, [0, 1, 0, 1]), (3, [0, 1, 2, 1]), (4, [0, 1, 2, 3])] {
assert_eq!(std::array::from_fn::<_, 4, _>(|i| crate::parse::drawn_side(listed, i)), sides.map(Some), "{listed}");
}
assert_eq!(crate::parse::drawn_side(0, 2), None);
}
/// A rounded box a page break cuts keeps its rounded corners on each page, square where it was
/// cut, and its edge stops there instead of closing across the cut.
#[test]
+2 -2
View File
@@ -5308,8 +5308,8 @@ milestone = "M12"
status = "partial"
commands = ["file.open"]
tools = ["doc_open", "doc_info", "form_fields", "form_fill", "js_run"]
evidence = ["crates/xfa/src/tests.rs::layout_places_captions_fields_and_sizes_containers_to_their_content", "crates/xfa/src/tests.rs::tables_break_across_pages_and_repeat_their_header_row", "crates/xfa/src/tests.rs::rendering_into_the_pdf_replaces_the_placeholder_page_and_adds_fields", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally", "crates/automation/tests/it/automation.rs::dynamic_xfa_forms_open_render_fill_and_save_through_tools", "crates/ui-egui/tests/it/xfa.rs::a_dynamic_xfa_form_is_laid_out_and_filled", "crates/xfa/src/tests.rs::nested_width_less_subforms_lay_out_in_polynomial_time", "crates/js/src/tests.rs::instance_managers_add_remove_and_count", "crates/xfa/src/tests.rs::the_form_tree_carries_values_presence_instances_and_the_events", "crates/engine/src/tests.rs::xfa_scripts_initialize_calculate_validate_toggle_and_add_rows", "crates/engine/src/tests.rs::xfa_date_arithmetic_reads_the_date_as_its_display_picture_shows_it", "crates/automation/tests/it/automation.rs::xfa_scripts_run_for_buttons_and_field_changes_through_tools", "crates/ui-egui/tests/it/xfa.rs::xfa_buttons_run_their_scripts_in_the_app", "crates/engine/src/tests.rs::hostile_xfa_open_scripts_are_merged_capped_kept_from_printing_and_noted", "crates/engine/src/tests.rs::runaway_xfa_calculations_at_open_end_quickly_with_a_report", "crates/ui-egui/tests/it/xfa.rs::a_form_whose_scripts_were_turned_off_at_open_says_so", "crates/ui-egui/tests/it/xfa.rs::scripts_turned_off_by_a_click_bring_a_closed_notice_bar_back", "crates/automation/tests/it/automation.rs::doc_info_says_when_a_runaway_script_turned_the_forms_scripts_off", "crates/xfa/src/tests.rs::the_live_form_of_nested_repeating_subforms_stops_at_its_node_budget", "crates/xfa/src/tests.rs::data_ops_of_one_event_are_one_datasets_rewrite", "crates/xfa/src/tests.rs::choice_lists_signatures_passwords_and_pictures_become_fields_and_images", "crates/xfa/src/tests.rs::choice_values_come_from_the_data_as_saved_values_and_several_for_lists", "crates/xfa/src/tests.rs::pictures_are_sized_from_their_headers_and_hostile_ones_are_refused_unallocated", "crates/engine/src/tests.rs::xfa_choice_lists_signature_and_picture_fields_open_fill_script_and_save", "crates/xfa/src/tests.rs::repeating_subforms_inside_positioned_content_get_their_instances", "crates/engine/src/tests.rs::xfa_rows_in_areas_are_laid_out_read_calculated_and_written_where_adobe_binds_them", "crates/xfa/src/tests.rs::page_area_without_occur_serves_every_body_page", "apps/pdfcraft-cli/tests/it/xfa.rs::info_reports_the_laid_out_pages_and_fields", "apps/pdfcraft-cli/tests/it/xfa.rs::text_reads_the_layout_including_a_page_absent_from_the_placeholder", "apps/pdfcraft-cli/tests/it/xfa.rs::render_matches_the_engine_layout_on_the_second_page", "apps/pdfcraft-cli/tests/it/xfa.rs::a_laid_out_form_keeps_the_original_copy_permissions", "crates/engine/src/tests.rs::filling_growable_xfa_text_reflows_and_undo_restores_the_layout", "crates/engine/src/tests.rs::xfa_text_growth_refreshes_widget_geometry_but_fixed_height_fields_keep_their_widgets", "crates/automation/tests/it/automation.rs::growable_xfa_text_paginates_after_fill_save_and_print", "crates/engine/src/tests.rs::an_xfa_script_that_fails_partway_keeps_what_it_changed_before_the_error", "crates/engine/src/tests.rs::repeated_xfa_print_buttons_run_their_click_script", "crates/automation/tests/it/automation.rs::repeated_xfa_print_buttons_work_through_tools_and_after_reopen", "crates/xfa/src/tests.rs::rich_text_max_length_reaches_the_field_and_layout", "crates/automation/tests/it/automation.rs::xfa_rich_text_max_length_is_reported_and_enforced_through_tools", "crates/xfa/src/tests.rs::bmp_and_tiff_pictures_are_decoded_and_drawn", "crates/engine/src/tests.rs::active_xfa_does_not_run_pdf_container_scripts_but_keeps_requested_alerts", "crates/engine/src/tests.rs::active_xfa_without_template_scripts_does_not_initialize_pdf_fallback_scripts", "crates/engine/src/tests.rs::inactive_xfa_and_ordinary_pdfs_keep_document_helpers_and_alerts", "crates/automation/tests/it/automation.rs::active_xfa_skips_container_fallback_scripts_after_save_and_reopen", "crates/xfa/src/tests.rs::laying_a_form_out_takes_off_the_usage_rights_it_voids", "crates/engine/src/tests.rs::a_dynamic_xfa_form_is_saved_without_the_usage_rights_its_layout_voided", "crates/xfa/src/tests.rs::faces_measure_text_at_their_standard_14_widths", "crates/xfa/src/tests.rs::rich_text_links_keep_their_address", "crates/xfa/src/tests.rs::links_in_rich_text_become_link_annotations_one_per_line", "crates/xfa/src/tests.rs::a_links_pieces_share_one_address", "crates/xfa/src/tests.rs::a_rule_between_two_pieces_of_a_link_keeps_them_apart", "crates/xfa/src/tests.rs::links_to_anything_but_web_and_email_addresses_stay_plain_text", "crates/automation/tests/it/automation.rs::links_in_xfa_rich_text_are_listed_through_tools", "crates/xfa/src/tests.rs::rounded_corners_make_a_border_one_rounded_shape", "crates/xfa/src/testsLine truncated
notes = "Active XFA layout skips PDF-container document-script initialization for the Acrobat console/field context, so fallback-viewer scripts do not run there. Scripts remain in the file/listing; explicit console/field and XFA alerts still run. Ordinary PDF, static XFA and unlaid-out shells keep document scripts; static-conversion undo restores the XFA policy. Changed growable multiline text (no fixed h) reflows from final datasets after edits, with or without scripts; pages/widgets refresh and undo/redo, save/reopen and print retain all text. Oversized text still uses the existing read-only page-text fallback with a warning; fixed-height fields do not reflow. Repeated page-master buttons resolve their own PDF field to the shared XFA click script; the numPages-based print script requests printing from every copy, including after save/reopen (#2572). Rich-text exData/maxLength is carried into AcroForm MaxLen, reported by form_fields and enforced by form_fill (Unicode characters); limits and accepted values survive save/reopen (#2570). laid out from the template on open by the xfa crate (positioned, tb, lr-tb, table/row, page masters (a page area with no occur repeats without limit, one with occur max=1 is used once, one with occur min=1 the body never reached gets a page after the body, as Acrobat Pro does on a synthetic form), breaks, repeated header rows, borders and fills of flowed subforms (a page break between their children gives each page's part a closed border, the margins repeated at the cut, as measured on a synthetic form in another viewer, and a child's own page break breaks them the same way; one holding an item that doesn't fit on its page, cut across pages or placed past the content area's bottom, is drawn without its border, with a layout warning), captions, rounded and inverted border corners, rich text (its http, https and mailto links clickable), JPEG/PNG/GIF/BMP/TIFF images) into real pages and AcroForm fields (text, password, date with AFDate formats, check boxes and radio groups (each box placed by its field's margins, caption and paragraph alignment as measured on two synthetic forms in another viewer: with no caption at the right of the content area, beside a reserved caption where the alignment puts it, and a caption without a reserve taking the rest of the field; a check box's own margin larger than the box draws a bigger box there, which isn't copied), drop-downs and list boxes with shown/saved item pairs and multi-select, signature fields (each named by its SOM path, under a generated field for each step above it, so that another viewer, which lays the form out again, finds a signature made here, as seen on a synthetic form, and a box signed here keeps its signature, and that name, when a script lays the form out again; a box whose path, or a part of it, the file already gives another field keeps its own name, and its signature isn't found there yet), image fields showing their picture (pictures decoded once per document, at most 20 million pixels, and reused across re-layouts), buttons with reset/print/save/URL), filled from the datasets packet (including rows another viewer added), saved as an incremental revision with the values merged into the datasets so Adobe's viewers show them; usage rights the form carried (/Perms /UR3) are taken off with a note, as the layout voids them and a form with void usage rights opens with every field locked; doc_info reports xfa_layout. CLI info reports the laid-out page/field counts and dimensions while retaining the input file size and raw counts as pdf_pages/acroform_fields; text and render use those pages too, preserving copy permissions and leaving the source file unchanged (#1645). XFA JavaScript and FormCalc (form.xfa-formcalc) run in the js crate's XFA object model (xfa.form, SOM resolution, rawValue, formattedValue in a date display picture's pattern (number and text display pictures still give the raw value), presence, access, instance managers, xfa.host, xfa.layout, xfa.event, and property objects (XFA 3.3 ch. 3 p. 78, ch. 17), every property path the 367 inventoried forms read (the one known gap is value.text and value.exData, read by about 15 of them: a node's value stays its raw value, and changing what value returns is left to a follow-up with a full-corpus regression check): read as the template writes them, with the spec's defaults for what those forms read where it leaves them out (presence of border (hidden when the object has no border element, as readers read it), edge, corner and fill; an edge or corner past those the template lists, up to four (a border's fifth and later are out of reach, as the layout draws four), as the one drawn on that side (XFA 3.3 p. 38 says the last one listed is reused for the sides not listed; readers draw such a side from the edge or corner on the same side instead, two listed alternating, three reusing the second for the fourth, one serving every side, and reads follow the readers; with none Line truncated
evidence = ["crates/xfa/src/tests.rs::layout_places_captions_fields_and_sizes_containers_to_their_content", "crates/xfa/src/tests.rs::tables_break_across_pages_and_repeat_their_header_row", "crates/xfa/src/tests.rs::rendering_into_the_pdf_replaces_the_placeholder_page_and_adds_fields", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally", "crates/automation/tests/it/automation.rs::dynamic_xfa_forms_open_render_fill_and_save_through_tools", "crates/ui-egui/tests/it/xfa.rs::a_dynamic_xfa_form_is_laid_out_and_filled", "crates/xfa/src/tests.rs::nested_width_less_subforms_lay_out_in_polynomial_time", "crates/js/src/tests.rs::instance_managers_add_remove_and_count", "crates/xfa/src/tests.rs::the_form_tree_carries_values_presence_instances_and_the_events", "crates/engine/src/tests.rs::xfa_scripts_initialize_calculate_validate_toggle_and_add_rows", "crates/engine/src/tests.rs::xfa_date_arithmetic_reads_the_date_as_its_display_picture_shows_it", "crates/automation/tests/it/automation.rs::xfa_scripts_run_for_buttons_and_field_changes_through_tools", "crates/ui-egui/tests/it/xfa.rs::xfa_buttons_run_their_scripts_in_the_app", "crates/engine/src/tests.rs::hostile_xfa_open_scripts_are_merged_capped_kept_from_printing_and_noted", "crates/engine/src/tests.rs::runaway_xfa_calculations_at_open_end_quickly_with_a_report", "crates/ui-egui/tests/it/xfa.rs::a_form_whose_scripts_were_turned_off_at_open_says_so", "crates/ui-egui/tests/it/xfa.rs::scripts_turned_off_by_a_click_bring_a_closed_notice_bar_back", "crates/automation/tests/it/automation.rs::doc_info_says_when_a_runaway_script_turned_the_forms_scripts_off", "crates/xfa/src/tests.rs::the_live_form_of_nested_repeating_subforms_stops_at_its_node_budget", "crates/xfa/src/tests.rs::data_ops_of_one_event_are_one_datasets_rewrite", "crates/xfa/src/tests.rs::choice_lists_signatures_passwords_and_pictures_become_fields_and_images", "crates/xfa/src/tests.rs::choice_values_come_from_the_data_as_saved_values_and_several_for_lists", "crates/xfa/src/tests.rs::pictures_are_sized_from_their_headers_and_hostile_ones_are_refused_unallocated", "crates/engine/src/tests.rs::xfa_choice_lists_signature_and_picture_fields_open_fill_script_and_save", "crates/xfa/src/tests.rs::repeating_subforms_inside_positioned_content_get_their_instances", "crates/engine/src/tests.rs::xfa_rows_in_areas_are_laid_out_read_calculated_and_written_where_adobe_binds_them", "crates/xfa/src/tests.rs::page_area_without_occur_serves_every_body_page", "apps/pdfcraft-cli/tests/it/xfa.rs::info_reports_the_laid_out_pages_and_fields", "apps/pdfcraft-cli/tests/it/xfa.rs::text_reads_the_layout_including_a_page_absent_from_the_placeholder", "apps/pdfcraft-cli/tests/it/xfa.rs::render_matches_the_engine_layout_on_the_second_page", "apps/pdfcraft-cli/tests/it/xfa.rs::a_laid_out_form_keeps_the_original_copy_permissions", "crates/engine/src/tests.rs::filling_growable_xfa_text_reflows_and_undo_restores_the_layout", "crates/engine/src/tests.rs::xfa_text_growth_refreshes_widget_geometry_but_fixed_height_fields_keep_their_widgets", "crates/automation/tests/it/automation.rs::growable_xfa_text_paginates_after_fill_save_and_print", "crates/engine/src/tests.rs::an_xfa_script_that_fails_partway_keeps_what_it_changed_before_the_error", "crates/engine/src/tests.rs::repeated_xfa_print_buttons_run_their_click_script", "crates/automation/tests/it/automation.rs::repeated_xfa_print_buttons_work_through_tools_and_after_reopen", "crates/xfa/src/tests.rs::rich_text_max_length_reaches_the_field_and_layout", "crates/automation/tests/it/automation.rs::xfa_rich_text_max_length_is_reported_and_enforced_through_tools", "crates/xfa/src/tests.rs::bmp_and_tiff_pictures_are_decoded_and_drawn", "crates/engine/src/tests.rs::active_xfa_does_not_run_pdf_container_scripts_but_keeps_requested_alerts", "crates/engine/src/tests.rs::active_xfa_without_template_scripts_does_not_initialize_pdf_fallback_scripts", "crates/engine/src/tests.rs::inactive_xfa_and_ordinary_pdfs_keep_document_helpers_and_alerts", "crates/automation/tests/it/automation.rs::active_xfa_skips_container_fallback_scripts_after_save_and_reopen", "crates/xfa/src/tests.rs::laying_a_form_out_takes_off_the_usage_rights_it_voids", "crates/engine/src/tests.rs::a_dynamic_xfa_form_is_saved_without_the_usage_rights_its_layout_voided", "crates/xfa/src/tests.rs::faces_measure_text_at_their_standard_14_widths", "crates/xfa/src/tests.rs::rich_text_links_keep_their_address", "crates/xfa/src/tests.rs::links_in_rich_text_become_link_annotations_one_per_line", "crates/xfa/src/tests.rs::a_links_pieces_share_one_address", "crates/xfa/src/tests.rs::a_rule_between_two_pieces_of_a_link_keeps_them_apart", "crates/xfa/src/tests.rs::links_to_anything_but_web_and_email_addresses_stay_plain_text", "crates/automation/tests/it/automation.rs::links_in_xfa_rich_text_are_listed_through_tools", "crates/xfa/src/tests.rs::rounded_corners_make_a_border_one_rounded_shape", "crates/xfa/src/testsLine truncated
notes = "Active XFA layout skips PDF-container document-script initialization for the Acrobat console/field context, so fallback-viewer scripts do not run there. Scripts remain in the file/listing; explicit console/field and XFA alerts still run. Ordinary PDF, static XFA and unlaid-out shells keep document scripts; static-conversion undo restores the XFA policy. Changed growable multiline text (no fixed h) reflows from final datasets after edits, with or without scripts; pages/widgets refresh and undo/redo, save/reopen and print retain all text. Oversized text still uses the existing read-only page-text fallback with a warning; fixed-height fields do not reflow. Repeated page-master buttons resolve their own PDF field to the shared XFA click script; the numPages-based print script requests printing from every copy, including after save/reopen (#2572). Rich-text exData/maxLength is carried into AcroForm MaxLen, reported by form_fields and enforced by form_fill (Unicode characters); limits and accepted values survive save/reopen (#2570). laid out from the template on open by the xfa crate (positioned, tb, lr-tb, table/row, page masters (a page area with no occur repeats without limit, one with occur max=1 is used once, one with occur min=1 the body never reached gets a page after the body, as Acrobat Pro does on a synthetic form), breaks, repeated header rows, borders and fills of flowed subforms (a page break between their children gives each page's part a closed border, the margins repeated at the cut, as measured on a synthetic form in another viewer, and a child's own page break breaks them the same way; one holding an item that doesn't fit on its page, cut across pages or placed past the content area's bottom, is drawn without its border, with a layout warning), captions, rounded and inverted border corners (XFA 3.3 p. 38 gives the order of edges and corners and says the last one listed is reused for the sides not listed; readers draw an unlisted edge or corner from the one on the same side instead, one serving all four, two alternating, three reusing the second for the bottom-left; edges already followed the readers, and corners now do too), rich text (its http, https and mailto links clickable), JPEG/PNG/GIF/BMP/TIFF images) into real pages and AcroForm fields (text, password, date with AFDate formats, check boxes and radio groups (each box placed by its field's margins, caption and paragraph alignment as measured on two synthetic forms in another viewer: with no caption at the right of the content area, beside a reserved caption where the alignment puts it, and a caption without a reserve taking the rest of the field; a check box's own margin larger than the box draws a bigger box there, which isn't copied), drop-downs and list boxes with shown/saved item pairs and multi-select, signature fields (each named by its SOM path, under a generated field for each step above it, so that another viewer, which lays the form out again, finds a signature made here, as seen on a synthetic form, and a box signed here keeps its signature, and that name, when a script lays the form out again; a box whose path, or a part of it, the file already gives another field keeps its own name, and its signature isn't found there yet), image fields showing their picture (pictures decoded once per document, at most 20 million pixels, and reused across re-layouts), buttons with reset/print/save/URL), filled from the datasets packet (including rows another viewer added), saved as an incremental revision with the values merged into the datasets so Adobe's viewers show them; usage rights the form carried (/Perms /UR3) are taken off with a note, as the layout voids them and a form with void usage rights opens with every field locked; doc_info reports xfa_layout. CLI info reports the laid-out page/field counts and dimensions while retaining the input file size and raw counts as pdf_pages/acroform_fields; text and render use those pages too, preserving copy permissions and leaving the source file unchanged (#1645). XFA JavaScript and FormCalc (form.xfa-formcalc) run in the js crate's XFA object model (xfa.form, SOM resolution, rawValue, formattedValue in a date display picture's pattern (number and text display pictures still give the raw value), presence, access, instance managers, xfa.host, xfa.layout, xfa.event, and property objects (XFA 3.3 ch. 3 p. 78, ch. 17), every property path the 367 inventoried forms read (the one known gap is value.text and value.exData, read by about 15 of them: a node's value stays its raw value, and changing what value returns is left to a follow-up with a full-corpus regression check): read as the template writes them, with the spec's defaults for what those forms read where it leaves them out (presence of border (hidden when the object has no border element, as readers read it), edge, corner and fill; an edge or corner past those the template lists, up to four (a border's fifth and later are out of reaLine truncated
[[feature]]
id = "form.xfa-formcalc"