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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
This commit is contained in:
mtlcl410andClaude Opus 5.5 committed 2026-10-29 21:56:26 +08:00
1 parent 7e0f635856
commit a4264efbb2
6 files changed
+155 -54

No files matched your search

+3 -2
View File
@@ -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 {
+7 -5
View File
@@ -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;
}
+19 -5
View File
@@ -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(())
}
+63 -40
View File
@@ -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<Node>;
@@ -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;
+61
View File
@@ -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!("<item><qty>{e}.{i}</qty></item>")).collect();
format!("<entry><table>{items}</table></entry>")
})
.collect();
let entry = r#"<subform name="entry" w="200pt"><occur min="1" max="-1"/>
<subform name="table" layout="table" columnWidths="200pt" x="0pt" y="0pt">
<subform name="item" layout="row"><occur min="1" max="-1"/><field name="qty" w="200pt" h="20pt"><ui><textEdit/></ui></field></subform>
</subform></subform>"#;
let after = r#"<field name="after" w="200pt" h="20pt"><ui><textEdit/></ui></field>"#;
let (page, open, close) = match place {
Place::PositionedFlow => (
format!(
r#"<subform name="page1" w="540pt" h="700pt"><subform name="wrap" layout="tb" w="200pt" x="36pt" y="72pt">{entry}{after}</subform></subform>"#
),
"<page1><wrap>",
"</wrap></page1>",
),
_ => (format!(r#"<subform name="page1" layout="tb" w="540pt">{entry}{after}</subform>"#), "<page1>", "</page1>"),
};
format!(
r#"<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/"><subform name="form" layout="tb">
<pageSet><pageArea name="p"><occur max="-1"/><contentArea x="36pt" y="36pt" w="540pt" h="720pt"/><medium short="612pt" long="792pt"/></pageArea></pageSet>
{page}
</subform></template>
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"><xfa:data><form>{open}{entries}{close}</form></xfa:data></xfa:datasets>
</xdp:xdp>"#
)
}
#[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<String> = 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::<Vec<_>>(), 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:?}");
}
}
}
+2 -2
View File
@@ -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 (`<bind><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 (`<bind><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"