diff --git a/crates/xfa/README.md b/crates/xfa/README.md index 1a5c2c3ddd..eb5279107d 100644 --- a/crates/xfa/README.md +++ b/crates/xfa/README.md @@ -27,6 +27,23 @@ that warning and keeps the existing layout and accepted widget values. text-field limits (`text/maxChars` and `exData/maxLength`, carried into AcroForm `/MaxLen`), required fields and exclusion groups (``), inline JPEG, PNG and GIF images, lines, rectangles, occurrences, `breakBefore`/`breakAfter`, overflow leaders, `columnWidths` and `colSpan`. +- **Prototypes** (`proto`, XFA 3.3 chapter 6, "Prototypes"): before parsing, every `use` and + `usehref` reference into the same template is resolved and the template is written again + with it merged in, so Designer style sheets (`usehref=".#som($template.#subform.styles.X)"`) + give their fields and draws size, anchor, borders, fills, fonts and captions. The element's + own attributes, content and children win; `id` and `name` are not inherited; children match + by class and position, a matched prototype child being the prototype of the element's own; + a `ui` widget or `value` kind and rich text are taken from the nearest layer that has one. + References are `#id`, `#som(…)` (or `.#…`) and, for `use`, a SOM expression. Loops are cut + where they close, chains merge at most 32 layers, nesting stops at 256 elements, a SOM + expression longer than 1,024 bytes names nothing, SOM look-ups share a budget of 2 million + elements, each element's reference is resolved once however many elements reach it, + merging looks at no more than 64 million children, attributes and namespaces in all + (prototype children that write nothing, such as `proto`, count too), and the written + template may grow to 4 times its size (at least 1 MB, at most 64 MB). Past the nesting, + look-up, merge or growth budget expanding fails as a whole: no partly written template is + used, the template is read as it is written (unresolved), with one warning saying which + budget. A cut loop or chain is a warning too. - **Layout** (`layout`): positioned, `tb`, `lr-tb`/`rl-tb`, `table` and `row` layouts; a container without a width is as wide as its content; flow across content areas and pages with page masters chosen by occurrence; a table's header row repeats after a break; presence @@ -175,6 +192,11 @@ let form = pdfcraft_xfa::layout_xml(template_xml)?; // pages of items, for file's data is held as a tree and written with the packet's own namespace prefix, never as the file's markup, and every rewrite of the packet is parsed back before it is written; the engine then lays a dynamic form out again from it and runs its calculations. +- **Prototypes in other documents** (`usehref="file.xdp#…"`) are not followed: that would + mean reading files or the network on opening a form. The reference is left out with a + warning. SOM references cover names, `#class` and `[n]`, not `[*]`, `..` or relative forms; + traversal declarations are merged like other children, not knitted together as "Resolving + Prototypes with Traversals" describes. - Missing: `keep` constraints, `subformSet` relations, `rl-tb` is mirrored `lr-tb`, font metrics are the approximate Helvetica ones, and line heights are 1.15 × size. - Acrobat cannot be an oracle here (clean-room rules): layout follows the specification, and diff --git a/crates/xfa/src/lib.rs b/crates/xfa/src/lib.rs index b04b0508d3..7954b5001e 100644 --- a/crates/xfa/src/lib.rs +++ b/crates/xfa/src/lib.rs @@ -16,6 +16,7 @@ pub mod packets; pub mod parse; pub mod pdf; pub mod picture; +mod proto; pub mod script; pub mod text; diff --git a/crates/xfa/src/parse.rs b/crates/xfa/src/parse.rs index 2df4f6eb8e..4b19ef6f9d 100644 --- a/crates/xfa/src/parse.rs +++ b/crates/xfa/src/parse.rs @@ -978,8 +978,8 @@ pub fn parse(xml: &str) -> Result<(Template, Vec), XfaError> { crate::MAX_XML_DEPTH ))); } - let opts = roxmltree::ParsingOptions { allow_dtd: false, nodes_limit: MAX_NODES, ..Default::default() }; - let doc = roxmltree::Document::parse_with_options(xml, opts).map_err(|e| XfaError::Malformed(format!("template XML: {e}")))?; + let opts = || roxmltree::ParsingOptions { allow_dtd: false, nodes_limit: MAX_NODES, ..Default::default() }; + let doc = roxmltree::Document::parse_with_options(xml, opts()).map_err(|e| XfaError::Malformed(format!("template XML: {e}")))?; let root = doc.root_element(); // The XDP's config packet has a