From a4264efbb24661c16947ce83eabfb83bdc706df1 Mon Sep 17 00:00:00 2001 From: mtlcl410 <2368091+mtlcl410@users.noreply.github.com> Date: Sun, 25 Oct 2026 15:21:03 +0800 Subject: [PATCH] M12: measure each instance of a repeating XFA container with its own rows Instances of one repeating subform can hold different numbers of rows in the data (XFA 3.3, "Tables", p. 327; Chapter 9, p. 336). Measurement used instance 0 for all of them and cached by template node only, so a later instance with more rows ran into what followed it. - size_at measures instance `index` under its own SOM path, and the measurement cache keys on that path when data drives the layout. - Top-to-bottom, left-to-right, table and positioned measurement and drawing take each instance's own size. - A repeating subform that isn't split across content areas (a positioned one) in flowed content now gets one block per instance, as in positioned content (XFA 3.3, "Repeating Elements using Occurrence Limits", p. 263), with the rows past the limit reported. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC --- crates/xfa/src/layout.rs | 5 +- crates/xfa/src/layout/draw.rs | 12 ++-- crates/xfa/src/layout/flow.rs | 24 +++++-- crates/xfa/src/layout/measure.rs | 103 ++++++++++++++++----------- crates/xfa/src/table_repeat_tests.rs | 61 ++++++++++++++++ parity/acrobat-features.toml | 4 +- 6 files changed, 155 insertions(+), 54 deletions(-) diff --git a/crates/xfa/src/layout.rs b/crates/xfa/src/layout.rs index 4c66361c85..595849dcc5 100644 --- a/crates/xfa/src/layout.rs +++ b/crates/xfa/src/layout.rs @@ -90,8 +90,9 @@ impl Ctx { } } -/// (address of a subform or area in the template, available width bits, depth). -type MeasureKey = (usize, u64, usize); +/// (address of a subform or area in the template, available width bits, depth, SOM path of the +/// instance measured when data drives the layout). +type MeasureKey = (usize, u64, usize, String); /// A cached size, with the inherited text defaults it was measured with. struct Measured { diff --git a/crates/xfa/src/layout/draw.rs b/crates/xfa/src/layout/draw.rs index 2b3ccd0971..1cd7fa1771 100644 --- a/crates/xfa/src/layout/draw.rs +++ b/crates/xfa/src/layout/draw.rs @@ -33,7 +33,7 @@ impl Layouter<'_> { if !c.presence.occupies() { return Ok(()); } - let (w, h) = self.size_of(k, (inner.w - c.x.unwrap_or(0.0)).max(1.0), ctx, depth + 1); + let (w, h) = self.size_at(k, (inner.w - c.x.unwrap_or(0.0)).max(1.0), ctx, depth + 1, sib); let (x, y) = c.top_left(w, h); self.draw_node(k, Rect::new(inner.x + x, inner.y + y, w, h), ctx, depth + 1, sib) } @@ -95,13 +95,15 @@ impl Layouter<'_> { if !k.common().presence.occupies() { continue; } - let (w, h) = self.size_of(k, inner.w, &ctx, depth + 1); // A repeating subform here (a table in an area of a positioned page, // say) gets its instances, as in a flowed one, one under another, - // and the rows past them are reported as there. + // each as tall as its own content, and the rows past them are + // reported as there. let instances = self.laid_out_instances(k, &ctx.som); for instance in 0..instances { - self.draw_node(k, Rect::new(inner.x, y, w, h), &ctx, depth + 1, sibs.at(i) + instance)?; + let sib = sibs.at(i).saturating_add(instance); + let (w, h) = self.size_at(k, inner.w, &ctx, depth + 1, sib); + self.draw_node(k, Rect::new(inner.x, y, w, h), &ctx, depth + 1, sib)?; y += h; } } @@ -140,7 +142,7 @@ impl Layouter<'_> { y += h; } } else { - let (w, h) = self.size_of(k, inner.w, &ctx, depth + 1); + let (w, h) = self.size_at(k, inner.w, &ctx, depth + 1, csib); self.draw_node(k, Rect::new(inner.x, y, w, h), &ctx, depth + 1, csib)?; y += h; } diff --git a/crates/xfa/src/layout/flow.rs b/crates/xfa/src/layout/flow.rs index 422902b61b..1ec19849d8 100644 --- a/crates/xfa/src/layout/flow.rs +++ b/crates/xfa/src/layout/flow.rs @@ -170,7 +170,7 @@ impl Layouter<'_> { let w = clamp_opt(sf.common.w.unwrap_or(w), sf.common.min_w, sf.common.max_w).min(w.max(1.0)); if !matches!(sf.layout, Layout::Tb | Layout::Table) || sf.common.h.is_some() { // Not splittable: one block. - let (_, h) = self.size_of(node, w, ctx_parent, depth); + let (_, h) = self.size_at(node, w, ctx_parent, depth, sib + i); self.flow_block(node, x, (w, h), ctx_parent, depth, sib + i)?; continue; } @@ -201,8 +201,7 @@ impl Layouter<'_> { self.flow_subform(child, Some((inner_x, inner_w)), &ctx, depth + 1, csib)?; continue; } - let (cw, ch) = self.size_of(child, inner_w, &ctx, depth + 1); - self.flow_block(child, inner_x, (cw, ch), &ctx, depth + 1, csib)?; + self.flow_instances(child, inner_x, inner_w, &ctx, depth + 1, csib)?; } } } @@ -268,8 +267,23 @@ impl Layouter<'_> { self.flow_subform(child, Some((x, inner_w)), ctx, depth + 1, csib)?; continue; } - let (cw, ch) = self.size_of(child, inner_w, ctx, depth + 1); - self.flow_block(child, x, (cw, ch), ctx, depth + 1, csib)?; + self.flow_instances(child, x, inner_w, ctx, depth + 1, csib)?; + } + Ok(()) + } + + /// Flow `node`, which isn't split across content areas, as one block per instance (a + /// repeating positioned subform gets one for each row the data or its `occur` gives it, XFA + /// 3.3, "Repeating Elements using Occurrence Limits", p. 263), each as tall as its own + /// content; the data rows past them are reported. + fn flow_instances(&mut self, node: &Node, x: f64, inner_w: f64, ctx: &Ctx, depth: usize, sib: usize) -> Result<(), XfaError> { + for instance in 0..self.laid_out_instances(node, &ctx.som) { + // Every instance spends budget, so nested repeats that place nothing still stop. + self.measures.set(self.measures.get().saturating_add(1)); + self.check_measures()?; + let sib = sib.saturating_add(instance); + let size = self.size_at(node, inner_w, ctx, depth, sib); + self.flow_block(node, x, size, ctx, depth, sib)?; } Ok(()) } diff --git a/crates/xfa/src/layout/measure.rs b/crates/xfa/src/layout/measure.rs index 2ab5f66727..5c2ca82b32 100644 --- a/crates/xfa/src/layout/measure.rs +++ b/crates/xfa/src/layout/measure.rs @@ -1,36 +1,47 @@ use super::common::clamp_opt; use super::field::auto_check_reserve; use super::{ - Access, Area, Ctx, ExclGroup, Face, Field, Font, Layout, Layouter, MAX_DEPTH, MAX_MEASURES, Measured, Node, Para, Placement, Rect, Subform, Ui, - Value, data_path, text, + Access, Area, Ctx, ExclGroup, Face, Field, Font, Layout, Layouter, MAX_DEPTH, MAX_MEASURES, Measured, Node, Para, Placement, Rect, SomIndices, + Subform, Ui, Value, data_path, text, }; impl Layouter<'_> { - /// The width and height `node` takes in `avail_w`: its own size, else what its content needs - /// (containers are measured once per width and depth, and measuring stops at its caps). + /// [`Self::size_at`] for the first instance of `node` (one that doesn't repeat). pub(super) fn size_of(&self, node: &Node, avail_w: f64, ctx: &Ctx, depth: usize) -> (f64, f64) { + self.size_at(node, avail_w, ctx, depth, 0) + } + + /// The width and height instance `index` of `node` under `ctx` takes in `avail_w`: its own + /// size, else what its content needs (containers are measured once per width, depth and + /// instance, and measuring stops at its caps). Instances differ when the data gives each its + /// own rows (XFA 3.3, "Tables", p. 327; Chapter 9, p. 336), so with data each is measured + /// under its own SOM path. + pub(super) fn size_at(&self, node: &Node, avail_w: f64, ctx: &Ctx, depth: usize, index: usize) -> (f64, f64) { let c = node.common(); let spent = self.measures.get().saturating_add(1); self.measures.set(spent); if depth > MAX_DEPTH || spent > MAX_MEASURES { return (c.w.unwrap_or(avail_w), c.h.unwrap_or(0.0)); } + let own = ctx.with_som(c, index); // Only containers recurse; their addresses are stable for the whole layout (the - // template is borrowed, the root and page areas are held by the layouter). + // template is borrowed, the root and page areas are held by the layouter). Without data + // every instance is alike, so one measurement serves them all. + let som = || if self.data.is_some() { own.som.clone() } else { String::new() }; let key = match node { - Node::Subform(s) => Some((&**s as *const Subform as usize, avail_w.to_bits(), depth)), - Node::Area(a) => Some((&**a as *const Area as usize, avail_w.to_bits(), depth)), + Node::Subform(s) => Some((&**s as *const Subform as usize, avail_w.to_bits(), depth, som())), + Node::Area(a) => Some((&**a as *const Area as usize, avail_w.to_bits(), depth, som())), _ => None, }; - if let Some(key) = key + if let Some(key) = &key && let Ok(cache) = self.measured.try_borrow() - && let Some(m) = cache.get(&key) + && let Some(m) = cache.get(key) && m.font == ctx.font && m.para == ctx.para { return m.size; } - let size = self.measure(node, avail_w, ctx, depth); + let size = self.measure(node, avail_w, &own, depth); if let Some(key) = key && let Ok(mut cache) = self.measured.try_borrow_mut() { @@ -39,15 +50,13 @@ impl Layouter<'_> { size } - /// The size (width, height) of `node` within `avail_w`, uncached ([`Self::size_of`] caches it). + /// The size (width, height) of `node` within `avail_w`, uncached ([`Self::size_at`] caches it). /// Without a width, a container, a text draw or a button is as wide as its content (at most /// `avail_w`) and anything else takes `avail_w`; without a height, it is as tall as its content. + /// `ctx` holds the SOM path of the instance measured: its repeating children count their + /// instances under it, and a growing text field finds its value at it. fn measure(&self, node: &Node, avail_w: f64, ctx: &Ctx, depth: usize) -> (f64, f64) { let c = node.common(); - // The node's own SOM: its repeating children count their instances under it, and a - // growing text field finds its value at it. Measurements are cached per template node, - // so instance 0 stands for all of them. - let ctx = ctx.with_som(c, 0); let som = ctx.som.clone(); // A container without a width is as wide as its content (a radio group beside its // question, say), and so is a text label or a button (as wide as its caption), within @@ -57,7 +66,7 @@ impl Layouter<'_> { None => match node { Node::Subform(_) | Node::ExclGroup(_) | Node::Area(_) => { let inner = (avail_w - c.margin.horizontal()).max(1.0); - (self.content_width(node, inner, &ctx, depth) + c.margin.horizontal()).min(avail_w) + (self.content_width(node, inner, ctx, depth) + c.margin.horizontal()).min(avail_w) } Node::Draw(d) => self.natural_width(&d.value, &ctx.font, &ctx.para).map_or(avail_w, |t| (t + c.margin.horizontal()).min(avail_w)), Node::Field(f) if f.ui == Ui::Button => f @@ -74,35 +83,40 @@ impl Layouter<'_> { None => { let inner_w = (w - c.margin.horizontal()).max(1.0); let content = match node { - Node::Draw(d) => self.value_height(&d.value, inner_w, &ctx), - Node::Field(f) => self.field_height(f, inner_w, &ctx), - Node::ExclGroup(g) => self.group_height(g, inner_w, &ctx, depth), - Node::Area(a) => self.positioned_height(&a.children, inner_w, &ctx, depth), + Node::Draw(d) => self.value_height(&d.value, inner_w, ctx), + Node::Field(f) => self.field_height(f, inner_w, ctx), + Node::ExclGroup(g) => self.group_height(g, inner_w, ctx, depth), + Node::Area(a) => self.positioned_height(&a.children, inner_w, ctx, depth), Node::Subform(s) => match s.layout { - Layout::Positioned => self.positioned_height(&s.children, inner_w, &ctx, depth), - // Each instance of a repeating child takes its height again. - Layout::Tb => s - .children - .iter() - .filter(|k| k.common().presence.occupies()) - .map(|k| self.size_of(k, inner_w, &ctx, depth + 1).1 * self.instances(k, &som) as f64) - .sum(), - Layout::LrTb | Layout::RlTb => self.pack_instances(&s.children, inner_w, &ctx, depth, &som).1, - Layout::Table => { - let cols = self.columns(s, inner_w); + Layout::Positioned => self.positioned_height(&s.children, inner_w, ctx, depth), + // Each instance of a repeating child takes its own height. + Layout::Tb => { + let sibs = SomIndices::of(&s.children); s.children .iter() - .filter(|k| k.common().presence.occupies()) - .map(|k| match k { + .enumerate() + .filter(|(_, k)| k.common().presence.occupies()) + .map(|(i, k)| self.instances_height(k, inner_w, ctx, depth + 1, &som, sibs.at(i))) + .sum() + } + Layout::LrTb | Layout::RlTb => self.pack_instances(&s.children, inner_w, ctx, depth, &som).1, + Layout::Table => { + let cols = self.columns(s, inner_w); + let sibs = SomIndices::of(&s.children); + s.children + .iter() + .enumerate() + .filter(|(_, k)| k.common().presence.occupies()) + .map(|(i, k)| match k { // A repeating row takes its height once per instance, as drawn. Node::Subform(r) if r.layout == Layout::Row => { - self.row_height(r, &cols, inner_w, &ctx, depth + 1) * self.instances(k, &som) as f64 + self.row_height(r, &cols, inner_w, ctx, depth + 1) * self.instances(k, &som) as f64 } - _ => self.size_of(k, inner_w, &ctx, depth + 1).1, + _ => self.size_at(k, inner_w, ctx, depth + 1, sibs.at(i)).1, }) .sum() } - Layout::Row => self.row_height(s, &self.columns(s, inner_w), inner_w, &ctx, depth + 1), + Layout::Row => self.row_height(s, &self.columns(s, inner_w), inner_w, ctx, depth + 1), }, }; content + c.margin.vertical() @@ -117,6 +131,12 @@ impl Layouter<'_> { (w, clamp_opt(h, min_h, c.max_h)) } + /// The height `k` takes with all its instances under the container at `som`, one under + /// another, the first at SOM index `sib` among its siblings: each instance takes its own. + fn instances_height(&self, k: &Node, inner_w: f64, ctx: &Ctx, depth: usize, som: &str, sib: usize) -> f64 { + (0..self.instances(k, som)).map(|i| self.size_at(k, inner_w, ctx, depth, sib.saturating_add(i)).1).sum() + } + /// The natural width of a container's content within `avail_w`. fn content_width(&self, node: &Node, avail_w: f64, ctx: &Ctx, depth: usize) -> f64 { let group: Vec; @@ -294,12 +314,14 @@ impl Layouter<'_> { } fn positioned_height(&self, children: &[Node], inner_w: f64, ctx: &Ctx, depth: usize) -> f64 { + let sibs = SomIndices::of(children); children .iter() - .filter(|k| k.common().presence.occupies()) - .map(|k| { + .enumerate() + .filter(|(_, k)| k.common().presence.occupies()) + .map(|(i, k)| { let c = k.common(); - let (w, h) = self.size_of(k, (inner_w - c.x.unwrap_or(0.0)).max(1.0), ctx, depth + 1); + let (w, h) = self.size_at(k, (inner_w - c.x.unwrap_or(0.0)).max(1.0), ctx, depth + 1, sibs.at(i)); c.top_left(w, h).1 + h }) .fold(0.0, f64::max) @@ -310,13 +332,14 @@ impl Layouter<'_> { /// container at `som`. pub(super) fn pack_instances(&self, children: &[Node], inner_w: f64, ctx: &Ctx, depth: usize, som: &str) -> (Vec<(usize, usize, Rect)>, f64) { let mut out = Vec::new(); + let sibs = SomIndices::of(children); let (mut x, mut y, mut line_h) = (0.0, 0.0, 0.0); for (i, k) in children.iter().enumerate() { if !k.common().presence.occupies() { continue; } - let (w, h) = self.size_of(k, inner_w, ctx, depth + 1); for instance in 0..self.instances(k, som) { + let (w, h) = self.size_at(k, inner_w, ctx, depth + 1, sibs.at(i).saturating_add(instance)); if x > 0.0 && x + w > inner_w + 0.05 { y += line_h; x = 0.0; diff --git a/crates/xfa/src/table_repeat_tests.rs b/crates/xfa/src/table_repeat_tests.rs index 5897cc6ca5..22c3cad708 100644 --- a/crates/xfa/src/table_repeat_tests.rs +++ b/crates/xfa/src/table_repeat_tests.rs @@ -186,3 +186,64 @@ fn hostile_occur_values_and_data_floods_lay_a_positioned_table_out_within_bounds let warning = cut_warning(&form).unwrap_or_else(|| panic!("{:?}", form.warnings)); assert!(warning.contains(&format!("{rows} rows")) && warning.contains(&format!("first {n}")), "{warning}"); } + +/// A repeating positioned `entry` subform, each instance holding a positioned table whose `item` +/// rows repeat, with a field after the entries; the data gives entry `i` `rows[i]` rows. +fn nested_form(place: Place, rows: &[usize]) -> String { + let entries: String = rows + .iter() + .enumerate() + .map(|(e, n)| { + let items: String = (1..=*n).map(|i| format!("{e}.{i}")).collect(); + format!("{items}
") + }) + .collect(); + let entry = r#" + + + "#; + let after = r#""#; + let (page, open, close) = match place { + Place::PositionedFlow => ( + format!( + r#"{entry}{after}"# + ), + "", + "", + ), + _ => (format!(r#"{entry}{after}"#), "", ""), + }; + format!( + r#" + +
{open}{entries}{close}
+
"# + ) +} + +#[test] +fn each_instance_of_a_repeating_container_is_measured_with_its_own_rows() { + // Instances of one repeating subform hold different numbers of rows in the data (XFA 3.3, + // "Tables", p. 327; Chapter 9, p. 336): each takes the height of its own rows, so the rows + // of one never run into the next instance or what follows. + for place in [Place::Flowed, Place::PositionedFlow] { + for rows in [[1, 5], [5, 1], [2, 3]] { + let what = format!("{place:?} {rows:?}"); + let form = layout_xml(&nested_form(place, &rows)).unwrap(); + let all = laid_out(&form); + let qty = named(&all, "qty"); + let want: Vec = rows.iter().enumerate().flat_map(|(e, n)| (1..=*n).map(move |i| format!("{e}.{i}"))).collect(); + assert_eq!(qty.iter().map(|w| w.2.clone().unwrap_or_default()).collect::>(), want, "{what}"); + let top = qty[0].3.y; + for (i, w) in qty.iter().enumerate() { + assert!((w.3.y - (top + 20.0 * i as f64)).abs() < 0.01, "{what}: row {i} at {:?}, first at {top}", w.3); + } + let after = named(&all, "after")[0].3; + let last = qty[qty.len() - 1].3; + assert!((after.y - last.bottom()).abs() < 0.01, "{what}: after {after:?}, last row {last:?}"); + } + } +} diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index f8dbdc6256..ff3daa069d 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -5332,8 +5332,8 @@ milestone = "M12" status = "partial" commands = ["file.open", "file.save", "form.export_data", "form.import_data"] tools = ["doc_open", "form_fill", "doc_save", "doc_export_data", "doc_import_data", "form_set_image"] -evidence = ["crates/xfa/src/tests.rs::the_datasets_packet_is_written_from_the_fields_and_read_back", "crates/xfa/src/tests.rs::a_date_is_read_from_the_data_in_its_data_picture", "crates/xfa/src/tests.rs::scripts_read_a_date_as_iso_whatever_its_data_picture_writes", "crates/engine/src/tests.rs::xfa_dates_are_written_and_read_back_in_their_data_picture", "crates/engine/src/tests.rs::each_xfa_date_field_is_written_in_its_own_data_picture_and_a_partial_one_keeps_iso", "crates/xfa/src/tests.rs::som_paths_name_the_template_field_their_numbers_count_to", "crates/xfa/src/tests.rs::a_refreshed_field_takes_its_value_from_its_own_template_field", "crates/xfa/src/tests.rs::a_date_its_data_picture_cant_hold_is_written_as_iso", "crates/xfa/src/tests.rs::data_fills_the_fields_and_adds_rows", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally", "crates/xfa/src/tests.rs::data_paths_leave_areas_out", "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::data_exports_as_datasets_xml_or_an_xdp_and_imports_from_any_of_them", "crates/xfa/src/tests.rs::a_picture_chosen_for_an_image_field_is_kept_in_the_data_with_its_content_type", "crates/engine/src/tests.rs::xfa_data_exports_imports_and_pictures_round_trip_through_the_session", "crates/engine/src/tests.rs::xfa_data_import_adds_and_removes_rows_of_a_dynamic_form", "crates/automation/tests/it/automation.rs::xfa_data_files_export_and_import_through_tools", "crates/xfa/src/repeat_tests.rs::every_data_row_past_fifty_is_laid_out_and_paginated", "crates/xfa/src/repeat_tests.rs::occur_max_still_stops_the_rows_and_says_so", "crates/xfa/src/repeat_tests.rs::rows_past_the_instance_limit_are_cut_with_a_warning", "crates/xfa/src/repeat_tests.rs::an_initial_count_lays_out_whole_up_to_the_limit", "crates/engine/tests/it/xfa_repeat.rs::a_button_adding_rows_past_fifty_lays_them_all_out", "crates/xfa/src/table_repeat_tests.rs::a_table_in_positioned_content_gets_a_row_per_data_row", "crates/xfa/src/table_repeat_tests.rs::occur_max_bounds_a_positioned_table_and_the_rows_cut_are_reported", "crates/xfa/src/table_repeat_tests.rs::a_positioned_table_lays_out_the_rows_a_flowed_one_does", "crates/engine/tests/it/xfa_repeat.rs::a_table_in_a_positioned_subform_gets_a_row_per_data_row_up_to_its_max"] -notes = "the datasets packet inside the PDF: read on open (values, dates in ISO form, or in the field's data picture (``, such as date{YYYYMMDD}) when it has one holding a whole date (one without a day, such as date{YYYYMM}, keeps ISO, as does a date the picture can't hold, such as 2050 in a two-digit year), each field its own even beside same-named siblings, which saving writes them in too, while scripts still read the ISO date as rawValue, as another viewer gives it, check and radio states, extra rows for repeating subforms) and rewritten after every form edit from the fields' SOM paths (/PCSom), with areas left out of data paths as Adobe binds them. Export Data writes it as XML (the data alone, rooted at the form's root data node, as Acrobat does) or an XDP with the whole datasets and a reference to the PDF; Import Data takes an XDP, datasets or data XML file (or a bare data document) as the form's whole data, keeping data descriptions, lays a dynamic form out again from it (rows added and removed: a repeating subform or table row, in a flowed or a positioned container, lays out every row up to its occur max, or up to 1,000 when it has none, and the rows past that are reported) and runs its calculations, undoable; a static form's fields take the data and the rest are emptied; the file's data is re-serialized with the packet's own prefix and every packet rewrite is parsed back before it is written; XFDF/FDF/CSV still go the AcroForm way. Files are UTF-8, UTF-16 or ISO-8859-1, at most 16 MB and 64 levels deep; pictures at most 8 MB (PNG, JPEG, GIF, BMP, TIFF). Pictures chosen for image fields are kept in the data as base64 with xfa:contentType, as Designer saves them. Missing: explicit bind refs, data descriptions are kept but not checked, no data connection (WSDL/ODBC)" +evidence = ["crates/xfa/src/tests.rs::the_datasets_packet_is_written_from_the_fields_and_read_back", "crates/xfa/src/tests.rs::a_date_is_read_from_the_data_in_its_data_picture", "crates/xfa/src/tests.rs::scripts_read_a_date_as_iso_whatever_its_data_picture_writes", "crates/engine/src/tests.rs::xfa_dates_are_written_and_read_back_in_their_data_picture", "crates/engine/src/tests.rs::each_xfa_date_field_is_written_in_its_own_data_picture_and_a_partial_one_keeps_iso", "crates/xfa/src/tests.rs::som_paths_name_the_template_field_their_numbers_count_to", "crates/xfa/src/tests.rs::a_refreshed_field_takes_its_value_from_its_own_template_field", "crates/xfa/src/tests.rs::a_date_its_data_picture_cant_hold_is_written_as_iso", "crates/xfa/src/tests.rs::data_fills_the_fields_and_adds_rows", "crates/engine/src/tests.rs::dynamic_xfa_forms_are_laid_out_on_open_filled_and_saved_incrementally", "crates/xfa/src/tests.rs::data_paths_leave_areas_out", "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::data_exports_as_datasets_xml_or_an_xdp_and_imports_from_any_of_them", "crates/xfa/src/tests.rs::a_picture_chosen_for_an_image_field_is_kept_in_the_data_with_its_content_type", "crates/engine/src/tests.rs::xfa_data_exports_imports_and_pictures_round_trip_through_the_session", "crates/engine/src/tests.rs::xfa_data_import_adds_and_removes_rows_of_a_dynamic_form", "crates/automation/tests/it/automation.rs::xfa_data_files_export_and_import_through_tools", "crates/xfa/src/repeat_tests.rs::every_data_row_past_fifty_is_laid_out_and_paginated", "crates/xfa/src/repeat_tests.rs::occur_max_still_stops_the_rows_and_says_so", "crates/xfa/src/repeat_tests.rs::rows_past_the_instance_limit_are_cut_with_a_warning", "crates/xfa/src/repeat_tests.rs::an_initial_count_lays_out_whole_up_to_the_limit", "crates/engine/tests/it/xfa_repeat.rs::a_button_adding_rows_past_fifty_lays_them_all_out", "crates/xfa/src/table_repeat_tests.rs::a_table_in_positioned_content_gets_a_row_per_data_row", "crates/xfa/src/table_repeat_tests.rs::occur_max_bounds_a_positioned_table_and_the_rows_cut_are_reported", "crates/xfa/src/table_repeat_tests.rs::a_positioned_table_lays_out_the_rows_a_flowed_one_does", "crates/engine/tests/it/xfa_repeat.rs::a_table_in_a_positioned_subform_gets_a_row_per_data_row_up_to_its_max", "crates/xfa/src/table_repeat_tests.rs::each_instance_of_a_repeating_container_is_measured_with_its_own_rows"] +notes = "the datasets packet inside the PDF: read on open (values, dates in ISO form, or in the field's data picture (``, such as date{YYYYMMDD}) when it has one holding a whole date (one without a day, such as date{YYYYMM}, keeps ISO, as does a date the picture can't hold, such as 2050 in a two-digit year), each field its own even beside same-named siblings, which saving writes them in too, while scripts still read the ISO date as rawValue, as another viewer gives it, check and radio states, extra rows for repeating subforms) and rewritten after every form edit from the fields' SOM paths (/PCSom), with areas left out of data paths as Adobe binds them. Export Data writes it as XML (the data alone, rooted at the form's root data node, as Acrobat does) or an XDP with the whole datasets and a reference to the PDF; Import Data takes an XDP, datasets or data XML file (or a bare data document) as the form's whole data, keeping data descriptions, lays a dynamic form out again from it (rows added and removed: a repeating subform or table row, in a flowed or a positioned container, lays out every row up to its occur max, each instance of a repeating container as tall as its own rows,, or up to 1,000 when it has none, and the rows past that are reported) and runs its calculations, undoable; a static form's fields take the data and the rest are emptied; the file's data is re-serialized with the packet's own prefix and every packet rewrite is parsed back before it is written; XFDF/FDF/CSV still go the AcroForm way. Files are UTF-8, UTF-16 or ISO-8859-1, at most 16 MB and 64 levels deep; pictures at most 8 MB (PNG, JPEG, GIF, BMP, TIFF). Pictures chosen for image fields are kept in the data as base64 with xfa:contentType, as Designer saves them. Missing: explicit bind refs, data descriptions are kept but not checked, no data connection (WSDL/ODBC)" [[feature]] id = "form.xfa-flatten"