diff --git a/crates/js/src/xfa/props.rs b/crates/js/src/xfa/props.rs index 9fb5c563bb..aefc08b812 100644 --- a/crates/js/src/xfa/props.rs +++ b/crates/js/src/xfa/props.rs @@ -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 { match listed { // A border has four sides: edges and corners occur at most four times (p. 629). diff --git a/crates/js/src/xfa/props_tests.rs b/crates/js/src/xfa/props_tests.rs index 781d37d3d9..94a1a8c5b8 100644 --- a/crates/js/src/xfa/props_tests.rs +++ b/crates/js/src/xfa/props_tests.rs @@ -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 `` element, as readers /// read it, and `visible` when it has one that doesn't say (XFA 3.3 p. 629). #[test] diff --git a/crates/xfa/src/parse.rs b/crates/xfa/src/parse.rs index 7ff006ca2a..d99f875368 100644 --- a/crates/xfa/src/parse.rs +++ b/crates/xfa/src/parse.rs @@ -136,26 +136,36 @@ fn parse_fill(n: Option) -> Option { Some(parse_color(child(n, "color")).unwrap_or(Color::WHITE)) } -/// ``: 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. +/// ``: 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) -> Option { let n = n?; - let edges: Vec = 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 = 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 { + match listed { + 0 => None, + _ if index < listed => Some(index), + 1 => Some(0), + 2 => Some(index % 2), + _ => Some(1), + } +} + /// The `` 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 = 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()) } /// ``: typeface, size, weight, posture, underline, fill color and horizontal scale. diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs index e424be5441..692613b0d1 100644 --- a/crates/xfa/src/tests.rs +++ b/crates/xfa/src/tests.rs @@ -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#"d{corners}"# + ); + 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#""#, r#""#); + 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#""#), + 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] diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 404de35d37..a680318623 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -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/tests.rs::a_rounded_box_cut_by_a_page_break_keeps_its_corners_on_each_page", "crates/xfa/src/tests.rs::rounded_boxes_are_written_as_curved_paths", "crates/xfa/src/tests.rs::a_flowed_subform_is_drawn_with_its_border_under_its_content", "crates/xfa/src/tests.rs::a_check_box_sits_where_its_margins_caption_and_alignment_put_it", "crates/xfa/src/tests.rs::a_check_box_caption_without_a_reserve_takes_the_rest_of_the_field", "crates/xfa/src/tests.rs::a_radio_button_sits_where_a_check_box_would", "crates/xfa/src/tests.rs::a_check_box_caption_without_a_reserve_is_measured_as_it_is_drawn", "crates/js/src/tests.rs::message_box_keeps_its_title_icon_and_buttons", "crates/js/src/tests.rs::app_alert_in_an_xfa_script_reads_app_alerts_arguments", "crates/engine/src/tests.rs::xfa_message_boxes_keep_their_title_icon_and_buttons", "crates/xfa/src/tests.rs::signature_fields_are_named_by_their_som_path", "crates/xfa/src/tests.rs::a_signature_on_a_laid_out_box_survives_a_new_layout", "crates/js/src/xfa/props_tests.rs::unspecified_properties_read_as_the_spec_defaults", "crates/js/src/xfa/props_tests.rs::widget_border_and_validate_reads_throw_no_type_error", "crates/js/src/xfa/props_tests.rs::writes_are_read_back_in_the_run_and_have_no_effect", "crates/js/src/xfa/props_tests.rs::a_calculation_reads_its_override_script_and_message", "crates/js/src/xfa/props_tests.rs::a_fill_type_the_fill_does_not_have_throws_and_leaves_the_fill_as_it_was", "crates/xfa/src/props_tests.rs::a_calculation_is_kept_with_its_override_message_and_script", "crates/xfa/src/props_tests.rs::hostile_property_trees_are_cut_to_the_limits", "crates/engine/src/xfa.rs::a_click_script_reads_field_properties", "crates/engine/src/xfa.rs::snapshots_share_property_trees_across_runs_and_events"] -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 listed, the defaults: thickness 0.5pt, solid, square cap, black, a corner's radius 0in, square join, not inverted, pp. 663, 687), made as a copy of it when a script reaches it so that a write changes only that side (copies have their own budget, apart from the script's writes); colours (a border fill white, a font fill, edges and corners black); font typeface, size and weight; para hAlign and vAlign; validate nullTest, scriptTest and formatTest; occur min, max and initial; a fill's type solid and a field's widget textEdit; margin insets; break before and after; bind ref; a comb's numberOfCells; a calculation's override, disabled where the template has no calculate element and error where it has one, pp. 641-642), other unspecified attributes reading as an empty value that compares as \"\" and never throws (further defaults are added as forms need them): border with its edges, corners and fill (colour, linear/radial/pattern/stipple/solid), font, para, margin, ui and its widget as oneOfChild (with the widget's own border and margin), validate with its message texts and picture, occur, break/breakBefore/breakAfter, assist with toolTip and speak, format with its picture, the value element's content type, caption, items, bind, desc, the object's extras, calculate with its script and message, and keep (intact, next, previous; intact defaults by container, pp. 762-763), with className, oneOfChild, children by name (desc.vMandatory, message.scriptTest) and by class (ui["#textEdit"]), getElement(name, index), isPropertySpecified, getAttribute, setAttribute, rawValue (a desc's first text), a validate's script text, resolveNode(s), somExpression and nodes (length/item/namedItem) on them, SOM expressions from a node that run into its properties (resolveNode("ui.#textEdit"), "border.edge[1]", "extras.arrayGroup[*].groupName", "ui.*"), getAttribute, getElement and isPropertySpecified on nodes, and a node's mandatory, validationMessage, formatMessage and mandatoryMessage read from its validate (a single unnamed message text serves every test, p. 782, and setting one overwrites it, as readers do; reading validate.message. where no such text exists raises a script error, as in readers), and fillColor, fontColor and borderColor from its border fill, font fill and edge colours (borderColor set colours all four edges); what a script writes to them is read back later in the same script but has no effect on the form yet. The caption (its value as text or rich text, read as its words, by name, class, resolveNode or oneOfChild, XFA 3.3 p. 643) and a field's items (each entry as the list or check button shows it, p. 758) are property objects too; a value content the element doesn't hold reads as null, and a fill type the fill doesn't have (its pattern when it is solid) raises an error the script can catch and leaves the fill as it was, as readers do; exData.loadXML and saveXML do nothing yet. PdfCraft's choice-list methods are not on main yet (addItem, clearItems and boundItem are missing, and selectedIndex reads give an object that takes anything); until they land, items reads follow the template. FormCalc reads none of these properties yet): initialize and calculate on open, exit, validate and calculate after a field changes, click for buttons (through js_run and the app), with rows added and removed, subforms shown and hidden, and the form laid out again, all as one undo step and kept in the file. All the scripts of one event run on one thread, each in a fresh context on the form as the engine holds it then (nothing shared between scripts; the live form kept up to date in place for values, rebuilt only when the structure changed), so hundreds of calculations run within the open budget. Hostile scripts are bounded per event (merged and capped effects, messages and console lines, lower loop limits and a time budget on open and calculate, one datasets rewrite per event, print/save/links/focus from clicks only, a runaway script abandoned and the form's scripts turned off, which the app says in a toast and on the notice bar, with a button to the JavaScript console, and doc_info reports as xfa_scripts_off) and what open-time scripts changed is noted. A script that fails partway keeps what it changed before the error, as JavaScript does (forms often trip over an object an earlier version had after their first lines did the work). Missing: value.text and value.exData (a node's value is still its raw value, and value.#text on a draw resolves to null); properties a field inherits through use/usehref read as defaults until prototypes are resolved (#4662); property writes (border, colours, validate.nullTest, occur, toolTip) taking effect and being saved; node setAttribute, clone, createNode and nodes.append/insert/remove; property reads from FormCalc; a page area's relevant=\"-print\" (on a synthetic form Acrobat still prints the page but leaves it blank; PdfCraft prints its content, #2574); a page break before the first section (Acrobat opens an empty body page first when the first section has breakBefore targetType=pageArea, with or without startNew, as seen on synthetic forms; PdfCraft skips a break on an empty page and lays out one page fewer, #2574); loops inside nested calls are only stopped by abandoning their thread, and script memory is not capped; the change event per keystroke; xfa.template, connections and data descriptions; a hidden instance of a repeating subform hides them all; barcodes are drawn as their text, not bars; keep rules; text is measured at the exact standard-14 widths of the face it is drawn in (bold, italic and Times included), not in the template's own typefaces, and Acrobat fidelity is eyeballed on real forms, not measured (#60). xfa.host.messageBox keeps its title, icon and buttons, and app.alert in an XFA script reads its own argument order; every message box still answers OK (1) (#4291)." +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/tests.rs::corners_not_listed_follow_the_edge_pattern", "crates/xfa/src/tests.rs::a_rounded_box_cut_by_a_page_break_keeps_its_corners_on_each_page", "crates/xfa/src/tests.rs::rounded_boxes_are_written_as_curved_paths", "crates/xfa/src/tests.rs::a_flowed_subform_is_drawn_with_its_border_under_its_content", "crates/xfa/src/tests.rs::a_check_box_sits_where_its_margins_caption_and_alignment_put_it", "crates/xfa/src/tests.rs::a_check_box_caption_without_a_reserve_takes_the_rest_of_the_field", "crates/xfa/src/tests.rs::a_radio_button_sits_where_a_check_box_would", "crates/xfa/src/tests.rs::a_check_box_caption_without_a_reserve_is_measured_as_it_is_drawn", "crates/js/src/tests.rs::message_box_keeps_its_title_icon_and_buttons", "crates/js/src/tests.rs::app_alert_in_an_xfa_script_reads_app_alerts_arguments", "crates/engine/src/tests.rs::xfa_message_boxes_keep_their_title_icon_and_buttons", "crates/xfa/src/tests.rs::signature_fields_are_named_by_their_som_path", "crates/xfa/src/tests.rs::a_signature_on_a_laid_out_box_survives_a_new_layout", "crates/js/src/xfa/props_tests.rs::unspecified_properties_read_as_the_spec_defaults", "crates/js/src/xfa/props_tests.rs::widget_border_and_validate_reads_throw_no_type_error", "crates/js/src/xfa/props_tests.rs::writes_are_read_back_in_the_run_and_have_no_effect", "crates/js/src/xfa/props_tests.rs::a_calculation_reads_its_override_script_and_message", "crates/js/src/xfa/props_tests.rs::a_fill_type_the_fill_does_not_have_throws_and_leaves_the_fill_as_it_was", "crates/xfa/src/props_tests.rs::a_calculation_is_kept_with_its_override_message_and_script", "crates/xfa/src/props_tests.rs::hostile_property_trees_are_cut_to_the_limits", "crates/engine/src/xfa.rs::a_click_script_reads_field_properties", "crates/engine/src/xfa.rs::snapshots_share_property_trees_across_runs_and_events"] +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 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 listed, the defaults: thickness 0.5pt, solid, square cap, black, a corner's radius 0in, square join, not inverted, pp. 663, 687), made as a copy of it when a script reaches it so that a write changes only that side (copies have their own budget, apart from the script's writes); colours (a border fill white, a font fill, edges and corners black); font typeface, size and weight; para hAlign and vAlign; validate nullTest, scriptTest and formatTest; occur min, max and initial; a fill's type solid and a field's widget textEdit; margin insets; break before and after; bind ref; a comb's numberOfCells; a calculation's override, disabled where the template has no calculate element and error where it has one, pp. 641-642), other unspecified attributes reading as an empty value that compares as \"\" and never throws (further defaults are added as forms need them): border with its edges, corners and fill (colour, linear/radial/pattern/stipple/solid), font, para, margin, ui and its widget as oneOfChild (with the widget's own border and margin), validate with its message texts and picture, occur, break/breakBefore/breakAfter, assist with toolTip and speak, format with its picture, the value element's content type, caption, items, bind, desc, the object's extras, calculate with its script and message, and keep (intact, next, previous; intact defaults by container, pp. 762-763), with className, oneOfChild, children by name (desc.vMandatory, message.scriptTest) and by class (ui["#textEdit"]), getElement(name, index), isPropertySpecified, getAttribute, setAttribute, rawValue (a desc's first text), a validate's script text, resolveNode(s), somExpression and nodes (length/item/namedItem) on them, SOM expressions from a node that run into its properties (resolveNode("ui.#textEdit"), "border.edge[1]", "extras.arrayGroup[*].groupName", "ui.*"), getAttribute, getElement and isPropertySpecified on nodes, and a node's mandatory, validationMessage, formatMessage and mandatoryMessage read from its validate (a single unnamed message text serves every test, p. 782, and setting one overwrites it, as readers do; reading validate.message. where no such text exists raises a script error, as in readers), and fillColor, fontColor and borderColor from its border fill, font fill and edge colours (borderColor set colours all four edges); what a script writes to them is read back later in the same script but has no effect on the form yet. The caption (its value as text or rich text, read as its words, by name, class, resolveNode or oneOfChild, XFA 3.3 p. 643) and a field's items (each entry as the list or check button shows it, p. 758) are property objects too; a value content the element doesn't hold reads as null, and a fill type the fill doesn't have (its pattern when it is solid) raises an error the script can catch and leaves the fill as it was, as readers do; exData.loadXML and saveXML do nothing yet. PdfCraft's choice-list methods are not on main yet (addItem, clearItems and boundItem are missing, and selectedIndex reads give an object that takes anything); until they land, items reads follow the template. FormCalc reads none of these properties yet): initialize and calculate on open, exit, validate and calculate after a field changes, click for buttons (through js_run and the app), with rows added and removed, subforms shown and hidden, and the form laid out again, all as one undo step and kept in the file. All the scripts of one event run on one thread, each in a fresh context on the form as the engine holds it then (nothing shared between scripts; the live form kept up to date in place for values, rebuilt only when the structure changed), so hundreds of calculations run within the open budget. Hostile scripts are bounded per event (merged and capped effects, messages and console lines, lower loop limits and a time budget on open and calculate, one datasets rewrite per event, print/save/links/focus from clicks only, a runaway script abandoned and the form's scripts turned off, which the app says in a toast and on the notice bar, with a button to the JavaScript console, and doc_info reports as xfa_scripts_off) and what open-time scripts changed is noted. A script that fails partway keeps what it changed before the error, as JavaScript does (forms often trip over an object an earlier version had after their first lines did the work). Missing: value.text and value.exData (a node's value is still its raw value, and value.#text on a draw resolves to null); properties a field inherits through use/usehref read as defaults until prototypes are resolved (#4662); property writes (border, colours, validate.nullTest, occur, toolTip) taking effect and being saved; node setAttribute, clone, createNode and nodes.append/insert/remove; property reads from FormCalc; a page area's relevant=\"-print\" (on a synthetic form Acrobat still prints the page but leaves it blank; PdfCraft prints its content, #2574); a page break before the first section (Acrobat opens an empty body page first when the first section has breakBefore targetType=pageArea, with or without startNew, as seen on synthetic forms; PdfCraft skips a break on an empty page and lays out one page fewer, #2574); loops inside nested calls are only stopped by abandoning their thread, and script memory is not capped; the change event per keystroke; xfa.template, connections and data descriptions; a hidden instance of a repeating subform hides them all; barcodes are drawn as their text, not bars; keep rules; text is measured at the exact standard-14 widths of the face it is drawn in (bold, italic and Times included), not in the template's own typefaces, and Acrobat fidelity is eyeballed on real forms, not measured (#60). xfa.host.messageBox keeps its title, icon and buttons, and app.alert in an XFA script reads its own argument order; every message box still answers OK (1) (#4291)." [[feature]] id = "form.xfa-formcalc"