Skip to main content

xfa_layout_engine/
form.rs

1//! Form node types — the input to the layout engine.
2//!
3//! These represent the merged Form DOM nodes that the layout engine processes.
4//! In a full implementation, these would come from xfa-dom-resolver's merge step.
5
6use std::collections::HashMap;
7
8use crate::text::FontMetrics;
9use crate::types::{BoxModel, LayoutStrategy, TextAlign, VerticalAlign};
10
11/// A unique identifier for a form node.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct FormNodeId(pub usize);
14
15/// The form tree: a node-based representation of the merged template+data.
16#[derive(Debug)]
17pub struct FormTree {
18    /// Nodes in the form tree.
19    pub nodes: Vec<FormNode>,
20    /// Per-node metadata (parallel to `nodes`).
21    pub metadata: Vec<FormNodeMeta>,
22    /// Lookup table: XFA `id` attribute -> `FormNodeId`.
23    pub node_ids: HashMap<String, FormNodeId>,
24    /// XFA 3.3 §5.5 `<variables>` `<script name="X">…</script>` blocks
25    /// gathered at merge time. Each entry is `(subform_scope, name, body)`.
26    /// `subform_scope` is `None` for root-level scripts (globally accessible)
27    /// and `Some(subform_name)` for scripts scoped to a named subform
28    /// (accessible as `subform.variables.scriptName`). Empty in default mode.
29    pub variables_scripts: Vec<(Option<String>, String, String)>,
30    /// XFA 3.3 §5.5.2 `<variables>` `<text name="X">value</text>` data items
31    /// gathered at merge time. Each entry is `(subform_scope, name, initial)`.
32    /// `subform_scope` follows the same convention as
33    /// [`Self::variables_scripts`]. Data items are form-level mutable string
34    /// containers — the canonical Canadian IMM template pattern is
35    /// `<variables><text name="globValidatePressed"/></variables>` referenced
36    /// from event scripts as `globValidatePressed.value = "true";`. Empty in
37    /// default mode.
38    ///
39    /// W3-D RETRY: registering these alongside variables-scripts eliminates
40    /// the post-W2-B `implicit_function` residual for the IMM5709/IMM5257/
41    /// IMM5710 family.
42    pub variables_data_items: Vec<(Option<String>, String, String)>,
43}
44
45impl FormTree {
46    /// Create a new form tree.
47    pub fn new() -> Self {
48        Self {
49            nodes: Vec::new(),
50            metadata: Vec::new(),
51            node_ids: HashMap::new(),
52            variables_scripts: Vec::new(),
53            variables_data_items: Vec::new(),
54        }
55    }
56
57    /// Add a node to the form tree.
58    pub fn add_node(&mut self, node: FormNode) -> FormNodeId {
59        let id = FormNodeId(self.nodes.len());
60        self.nodes.push(node);
61        self.metadata.push(FormNodeMeta::default());
62        id
63    }
64
65    /// Add a node together with its metadata. If the meta has an `xfa_id`,
66    /// it is registered in the `node_ids` lookup table.
67    pub fn add_node_with_meta(&mut self, node: FormNode, meta: FormNodeMeta) -> FormNodeId {
68        let id = FormNodeId(self.nodes.len());
69        if let Some(ref xfa_id) = meta.xfa_id {
70            self.node_ids.insert(xfa_id.clone(), id);
71        }
72        self.nodes.push(node);
73        self.metadata.push(meta);
74        id
75    }
76
77    /// Get a node by ID.
78    pub fn get(&self, id: FormNodeId) -> &FormNode {
79        &self.nodes[id.0]
80    }
81
82    /// Get a mutable reference to a node by ID.
83    pub fn get_mut(&mut self, id: FormNodeId) -> &mut FormNode {
84        &mut self.nodes[id.0]
85    }
86
87    /// Access the metadata for a node.
88    pub fn meta(&self, id: FormNodeId) -> &FormNodeMeta {
89        &self.metadata[id.0]
90    }
91
92    /// Mutably access the metadata for a node.
93    pub fn meta_mut(&mut self, id: FormNodeId) -> &mut FormNodeMeta {
94        &mut self.metadata[id.0]
95    }
96
97    /// Look up a node by its XFA `id` attribute.
98    pub fn find_by_xfa_id(&self, id: &str) -> Option<FormNodeId> {
99        self.node_ids.get(id).copied()
100    }
101}
102
103impl Default for FormTree {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109/// A single node in the Form DOM.
110#[derive(Debug, Clone)]
111pub struct FormNode {
112    /// Node name.
113    pub name: String,
114    /// Node type.
115    pub node_type: FormNodeType,
116    /// Box model.
117    pub box_model: BoxModel,
118    /// Layout strategy.
119    pub layout: LayoutStrategy,
120    /// Child node IDs.
121    pub children: Vec<FormNodeId>,
122    /// Occurrence rules for repeating subforms.
123    pub occur: Occur,
124    /// Font metrics for text measurement (Draw/Field nodes).
125    pub font: FontMetrics,
126    /// FormCalc calculate script (XFA S14.3.2): runs to compute the field's value.
127    pub calculate: Option<String>,
128    /// FormCalc validate script: runs to validate the field's value, returns bool.
129    pub validate: Option<String>,
130    /// Column widths for table-layout subforms (XFA columnWidths attribute).
131    /// Positive values are fixed widths in points; -1.0 means auto-size.
132    /// Empty for non-table nodes.
133    pub column_widths: Vec<f64>,
134    /// Column span for cells inside a table row (XFA colSpan attribute).
135    /// 1 = single column (default), N = span N columns, -1 = span remaining.
136    pub col_span: i32,
137}
138
139/// The scripting language used by an XFA `<script>` element.
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
141pub enum ScriptLanguage {
142    /// FormCalc is the XFA default when `contentType` is omitted.
143    #[default]
144    FormCalc,
145    /// JavaScript event handlers and calculations.
146    JavaScript,
147    /// Any other declared script language (for example VBScript).
148    Other,
149}
150
151/// Script metadata collected from `<event>` / `<calculate>` elements.
152#[derive(Debug, Clone, PartialEq, Eq, Default)]
153pub struct EventScript {
154    /// Script source.
155    pub script: String,
156    /// Script language.
157    pub language: ScriptLanguage,
158    /// Activity event.
159    pub activity: Option<String>,
160    /// Event reference.
161    pub event_ref: Option<String>,
162    /// Run-at location.
163    pub run_at: Option<String>,
164}
165
166impl EventScript {
167    /// Create a new event script.
168    pub fn new(
169        script: String,
170        language: ScriptLanguage,
171        activity: Option<String>,
172        event_ref: Option<String>,
173        run_at: Option<String>,
174    ) -> Self {
175        Self {
176            script,
177            language,
178            activity,
179            event_ref,
180            run_at,
181        }
182    }
183
184    /// Create a FormCalc event script.
185    pub fn formcalc(script: impl Into<String>, activity: Option<&str>) -> Self {
186        Self::new(
187            script.into(),
188            ScriptLanguage::FormCalc,
189            activity.map(str::to_string),
190            None,
191            None,
192        )
193    }
194
195    /// Create a JavaScript event script.
196    pub fn javascript(script: impl Into<String>, activity: Option<&str>) -> Self {
197        Self::new(
198            script.into(),
199            ScriptLanguage::JavaScript,
200            activity.map(str::to_string),
201            None,
202            None,
203        )
204    }
205}
206
207/// Content for draw nodes (static graphic elements).
208///
209/// XFA Spec 3.3 §2.1 (p24) — Draw element: fixed content (boilerplate).
210/// Includes text, lines, rectangles, arcs. Images are handled separately
211/// via `FormNodeType::Image`.
212///
213/// TODO(§2.3): `circle` draw content not implemented (spec allows via arc with
214///   startAngle=0, sweepAngle=360).
215#[derive(Debug, Clone)]
216pub enum DrawContent {
217    /// Text draw content.
218    Text(String),
219    /// Line draw content.
220    Line {
221        /// Start x coordinate.
222        x1: f64,
223        /// Start y coordinate.
224        y1: f64,
225        /// End x coordinate.
226        x2: f64,
227        /// End y coordinate.
228        y2: f64,
229    },
230    /// Rectangle draw content.
231    Rectangle {
232        /// X coordinate.
233        x: f64,
234        /// Y coordinate.
235        y: f64,
236        /// Width.
237        w: f64,
238        /// Height.
239        h: f64,
240        /// Corner radius.
241        radius: f64,
242    },
243    /// Arc draw content.
244    Arc {
245        /// X coordinate.
246        x: f64,
247        /// Y coordinate.
248        y: f64,
249        /// Width.
250        w: f64,
251        /// Height.
252        h: f64,
253        /// Start angle.
254        start_angle: f64,
255        /// Sweep angle.
256        sweep_angle: f64,
257    },
258}
259
260/// The type of form node.
261#[derive(Debug, Clone)]
262pub enum FormNodeType {
263    /// Root subform.
264    Root,
265    /// A page set containing page areas.
266    PageSet,
267    /// A page area (page template) with content areas.
268    PageArea {
269        /// Content areas.
270        content_areas: Vec<ContentArea>,
271    },
272    /// A generic subform container.
273    Subform,
274    /// XFA `<area>` — a positioned container (XFA 3.3 Appendix B).
275    ///
276    /// Semantically identical to a `Subform` with positioned layout: children
277    /// have absolute positions within the area.  The layout engine treats this
278    /// exactly like `Subform` for layout purposes.
279    Area,
280    /// XFA `<exclGroup>` — an exclusive (radio-button) group (XFA 3.3 §7.2).
281    ///
282    /// Contains multiple radio-button `<field>` children where exactly one can
283    /// be selected.  In layout this behaves like a `Subform` with top-to-bottom
284    /// flow; each child field is rendered normally.
285    ExclGroup,
286    /// XFA `<subformSet>` — a transparent set of subforms (XFA 3.3 §7.1).
287    ///
288    /// Used for conditional instantiation.  In layout the set is transparent:
289    /// its children are processed as if they were direct children of the
290    /// containing subform (same data context).
291    SubformSet,
292    /// A form field (text field, checkbox, etc.).
293    Field {
294        /// Field value.
295        value: String,
296    },
297    /// A static draw element (text, image, line, etc.).
298    Draw(DrawContent),
299    /// A static image draw element.
300    Image {
301        /// Image data.
302        data: Vec<u8>,
303        /// Image MIME type.
304        mime_type: String,
305    },
306}
307
308/// Occurrence rules for repeating subforms (XFA S3.3 occur element).
309///
310/// Controls how many instances of a subform are created. The layout engine
311/// expands templates based on the `initial` count, bounded by `min` and `max`.
312#[derive(Debug, Clone)]
313pub struct Occur {
314    /// Minimum number of occurrences (default 1).
315    pub min: u32,
316    /// Maximum number of occurrences (-1 = unlimited). Default 1.
317    /// Using `Option<u32>` where `None` means unlimited.
318    pub max: Option<u32>,
319    /// Initial number of occurrences (default = min).
320    pub initial: u32,
321}
322
323impl Default for Occur {
324    fn default() -> Self {
325        Self {
326            min: 1,
327            max: Some(1),
328            initial: 1,
329        }
330    }
331}
332
333impl Occur {
334    /// Occur rule that means "exactly once" (the default).
335    pub fn once() -> Self {
336        Self::default()
337    }
338
339    /// Occur rule for a repeating subform.
340    pub fn repeating(min: u32, max: Option<u32>, initial: u32) -> Self {
341        let initial = initial.max(min);
342        let initial = match max {
343            Some(m) => initial.min(m),
344            None => initial,
345        };
346        Self { min, max, initial }
347    }
348
349    /// How many instances should be created.
350    pub fn count(&self) -> u32 {
351        self.initial
352    }
353
354    /// Whether the subform can repeat (max > 1 or unlimited).
355    pub fn is_repeating(&self) -> bool {
356        match self.max {
357            Some(m) => m > 1,
358            None => true,
359        }
360    }
361}
362
363/// A content area within a page area.
364#[derive(Debug, Clone)]
365pub struct ContentArea {
366    /// Content area name.
367    pub name: String,
368    /// X coordinate.
369    pub x: f64,
370    /// Y coordinate.
371    pub y: f64,
372    /// Width.
373    pub width: f64,
374    /// Height.
375    pub height: f64,
376    /// Leader (header) node placed at the top of each page's content area.
377    pub leader: Option<FormNodeId>,
378    /// Trailer (footer) node placed at the bottom of each page's content area.
379    pub trailer: Option<FormNodeId>,
380}
381
382impl Default for ContentArea {
383    fn default() -> Self {
384        Self {
385            name: String::new(),
386            x: 0.0,
387            y: 0.0,
388            width: 612.0,  // US Letter width in points
389            height: 792.0, // US Letter height in points
390            leader: None,
391            trailer: None,
392        }
393    }
394}
395
396// ---------------------------------------------------------------------------
397// Metadata, style, and kind types
398// ---------------------------------------------------------------------------
399
400/// XFA `presence` attribute values (XFA 3.3 §2.6 p67-68).
401///
402/// Controls visibility and layout space allocation:
403/// - `Visible` -- all phases: binding, automation, layout, rendering, interaction.
404/// - `Hidden` -- binding + automation only; no layout space, no rendering.
405/// - `Invisible` -- binding + automation + layout; takes up space but not visible.
406/// - `Inactive` -- binding only; completely absent from form.
407///
408/// Note: spec defines `hidden` as "effectively absent" (no space) and `invisible`
409/// as "takes space but not visible". Our `is_layout_hidden` implementation reflects this.
410#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
411pub enum Presence {
412    #[default]
413    /// Visible.
414    Visible,
415    /// Hidden.
416    Hidden,
417    /// Invisible.
418    Invisible,
419    /// Inactive.
420    Inactive,
421}
422
423impl Presence {
424    /// True when the element should not be rendered.
425    pub fn is_not_visible(self) -> bool {
426        !matches!(self, Presence::Visible)
427    }
428
429    /// True when the element should not occupy layout space.
430    ///
431    /// XFA Spec 3.3 §2.6 (p68):
432    /// - `hidden`:   no layout space, no rendering (effectively absent)
433    /// - `invisible`: no layout space in Adobe (spec says "takes space",
434    ///   but empirical testing shows Adobe skips it)
435    /// - `inactive`:  completely absent (no binding, no space)
436    ///
437    /// `Hidden` was previously excluded from this predicate based on an
438    /// incorrect assumption that Adobe reserves space for hidden elements.
439    /// GATE #27 testing proved this wrong: hidden subforms with `<break>`
440    /// elements caused 2-23x overpagination in forms with many
441    /// `presence="hidden"` subforms (fixes #806).
442    pub fn is_layout_hidden(self) -> bool {
443        matches!(
444            self,
445            Presence::Hidden | Presence::Invisible | Presence::Inactive
446        )
447    }
448}
449
450/// Extended metadata for a form node.
451///
452/// Carries XFA attributes that the layout engine and dynamic scripting
453/// system need but that are not part of the core `FormNode` shape.
454#[derive(Debug, Clone, Default)]
455pub struct FormNodeMeta {
456    /// Optional XFA `id` attribute.
457    pub xfa_id: Option<String>,
458    /// XFA presence attribute (visible/hidden/invisible/inactive).
459    pub presence: Presence,
460    /// Whether a page break should be inserted before this node.
461    pub page_break_before: bool,
462    /// Whether a page break should be inserted after this node.
463    pub page_break_after: bool,
464    /// Target page area name/id for the break (e.g. "MP3", "Page4_ID").
465    pub break_target: Option<String>,
466    /// Whether this node targets a specific content area via
467    /// `breakBefore targetType="contentArea"`.  Such nodes should be
468    /// excluded from the primary content flow (they go into small
469    /// decorative areas like "flatten" or "eSign").
470    pub content_area_break: bool,
471    /// Overflow leader reference name.
472    pub overflow_leader: Option<String>,
473    /// Overflow trailer reference name.
474    pub overflow_trailer: Option<String>,
475    /// Keep with next content area.
476    pub keep_next_content_area: bool,
477    /// Keep with previous content area.
478    pub keep_previous_content_area: bool,
479    /// Keep intact within content area.
480    pub keep_intact_content_area: bool,
481    /// Layout-ready script (XFA S14.3).
482    pub layout_ready_script: Option<String>,
483    /// Event scripts collected from `<event>` and `<calculate>` children.
484    pub event_scripts: Vec<EventScript>,
485    /// Explicit XFA data binding ref from `<bind ref="...">`.
486    pub data_bind_ref: Option<String>,
487    /// Whether the node explicitly opts out of data binding via `<bind match="none">`.
488    pub data_bind_none: bool,
489    /// Visual style (font, colors, borders).
490    pub style: FormNodeStyle,
491    /// The kind of field (text, checkbox, radio, etc.).
492    pub field_kind: FieldKind,
493    /// The kind of group (none or exclusive choice).
494    pub group_kind: GroupKind,
495    /// Item value for fields inside an exclGroup.
496    pub item_value: Option<String>,
497    /// Check box / radio button size in points.
498    pub check_size: Option<f64>,
499    /// Display items for choice list fields (XFA 3.3 §7.7).
500    pub display_items: Vec<String>,
501    /// Save items for choice list fields (XFA 3.3 §7.7).
502    pub save_items: Vec<String>,
503    /// Runtime-populated choice list items from sandboxed script `addItem` calls.
504    /// Each tuple is `(display_value, save_value)`. Phase D-β only; layout does
505    /// not yet read this in default mode.
506    pub runtime_listbox_items: Vec<(String, String)>,
507    /// XFA anchorType for positioned layout (XFA 3.3 §2.6, App A p1510).
508    pub anchor_type: AnchorType,
509    /// Raw `DataDom` arena index of the data node this form node was bound to
510    /// during the merge phase. `None` means unbound. Phase D-γ: populated by
511    /// `FormMerger` and consumed by the JS runtime to resolve `$record`.
512    pub bound_data_node: Option<usize>,
513    /// True when this node is a synthetic prototype for a repeating subform
514    /// whose live instance count is currently zero. Used by the JS runtime
515    /// to expose an InstanceManager via `parent._child` even when no data
516    /// bindings produced live rows. Layout treats this as `presence = Hidden`.
517    pub is_zero_instance_prototype: bool,
518    /// True when this pageArea was allocated by the XFA runtime (recorded in
519    /// the form-DOM packet of an Adobe-Reader-saved PDF) rather than declared
520    /// once in the template.
521    ///
522    /// XFA 3.3 §8.6 / §3.1: the form DOM enumerates the runtime page-tree
523    /// state.  When the form DOM lists more `<pageArea>` siblings than the
524    /// template defines, those extra instances were created by the
525    /// `pageSet`/`occur` machinery and each one must emit a layout page even
526    /// when the flowing-body queue is exhausted (it is the runtime's record
527    /// of an already-paginated page).
528    pub runtime_instantiated_page: bool,
529    /// XFA `access` attribute (XFA 3.3 §6.1) when explicitly set on this
530    /// node. `None` means not specified — the effective access is inherited
531    /// from the nearest ancestor container that sets one (default `Open`).
532    pub access: Option<Access>,
533    /// True when the field's `<ui><textEdit>` declares `multiLine="1"`.
534    pub multiline: bool,
535    /// True when the field's `<validate>` declares `nullTest="error"`
536    /// (XFA 3.3 §6.3 — a mandatory field).
537    pub required: bool,
538}
539
540/// XFA `access` attribute values (XFA 3.3 §6.1, `field`/`exclGroup`/
541/// `subform` elements). Controls interactive mutability of a node and,
542/// for containers, of all contained fields.
543#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
544pub enum Access {
545    /// Default: the field is interactive and writable.
546    #[default]
547    Open,
548    /// Visible, value selectable/copyable, but not writable.
549    ReadOnly,
550    /// Not interactive at all (no focus, no writes).
551    Protected,
552    /// Rendered like a regular field but excluded from interaction.
553    NonInteractive,
554}
555
556impl Access {
557    /// Parse the template attribute string. Unknown values map to `Open`
558    /// (spec default).
559    pub fn parse(s: &str) -> Access {
560        match s.trim() {
561            "readOnly" => Access::ReadOnly,
562            "protected" => Access::Protected,
563            "nonInteractive" => Access::NonInteractive,
564            _ => Access::Open,
565        }
566    }
567
568    /// Whether a value write should be rejected for this access level.
569    pub fn denies_writes(self) -> bool {
570        !matches!(self, Access::Open)
571    }
572}
573
574/// XFA `anchorType` attribute (XFA 3.3 §2.6, Appendix A p1510).
575///
576/// Determines which point of an element is placed at the (x,y) coordinate
577/// in positioned layout.  Default is `TopLeft`.
578#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
579pub enum AnchorType {
580    #[default]
581    /// Top-left anchor.
582    TopLeft,
583    /// Top-center anchor.
584    TopCenter,
585    /// Top-right anchor.
586    TopRight,
587    /// Middle-left anchor.
588    MiddleLeft,
589    /// Middle-center anchor.
590    MiddleCenter,
591    /// Middle-right anchor.
592    MiddleRight,
593    /// Bottom-left anchor.
594    BottomLeft,
595    /// Bottom-center anchor.
596    BottomCenter,
597    /// Bottom-right anchor.
598    BottomRight,
599}
600
601/// The kind of group container.
602#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
603pub enum GroupKind {
604    #[default]
605    /// No group.
606    None,
607    /// Exclusive choice group.
608    ExclusiveChoice,
609}
610
611/// The kind of form field.
612#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
613pub enum FieldKind {
614    #[default]
615    /// Text field.
616    Text,
617    /// Checkbox field.
618    Checkbox,
619    /// Radio button field.
620    Radio,
621    /// Button field.
622    Button,
623    /// Dropdown field.
624    Dropdown,
625    /// Signature field.
626    Signature,
627    /// Date/time picker field.
628    DateTimePicker,
629    /// Numeric edit field.
630    NumericEdit,
631    /// Password edit field.
632    PasswordEdit,
633    /// Image edit field.
634    ImageEdit,
635    /// Barcode field.
636    Barcode,
637}
638
639/// A span of rich text with per-span style overrides.
640///
641/// XFA Spec 3.3 §4.2.7 (p155) — `<exData contentType="text/html">` stores
642/// XHTML content with inline CSS. Each span carries its own formatting
643/// (font, color, weight, etc.) that overrides the node-level defaults.
644#[derive(Debug, Clone, PartialEq)]
645pub struct RichTextSpan {
646    /// Text content.
647    pub text: String,
648    /// Font size.
649    pub font_size: Option<f64>,
650    /// Font family.
651    pub font_family: Option<String>,
652    /// Font weight.
653    pub font_weight: Option<String>,
654    /// Font style.
655    pub font_style: Option<String>,
656    /// Text color.
657    pub text_color: Option<(u8, u8, u8)>,
658    /// Underline flag.
659    pub underline: bool,
660    /// Line-through flag.
661    pub line_through: bool,
662}
663
664/// Visual style properties for a form node.
665#[derive(Debug, Clone, PartialEq)]
666pub struct FormNodeStyle {
667    /// Font family.
668    pub font_family: Option<String>,
669    /// Font size.
670    pub font_size: Option<f64>,
671    /// Font weight.
672    pub font_weight: Option<String>,
673    /// Font style.
674    pub font_style: Option<String>,
675    /// Text color.
676    pub text_color: Option<(u8, u8, u8)>,
677    /// Background color.
678    pub bg_color: Option<(u8, u8, u8)>,
679    /// Border color.
680    pub border_color: Option<(u8, u8, u8)>,
681    /// Per-edge border colors (top, right, bottom, left) in RGB 0-255.
682    /// When set, overrides `border_color` for individual edges.
683    pub border_colors: Option<[(u8, u8, u8); 4]>,
684    /// Border width in points.
685    pub border_width_pt: Option<f64>,
686    /// Per-edge border widths (top, right, bottom, left) in points.
687    /// When set, overrides `border_width_pt` for individual edges.
688    pub border_widths: Option<[f64; 4]>,
689    /// Paragraph space above in points (XFA `<para spaceAbove>`).
690    pub space_above_pt: Option<f64>,
691    /// Paragraph space below in points (XFA `<para spaceBelow>`).
692    pub space_below_pt: Option<f64>,
693    /// Paragraph left margin in points (XFA `<para marginLeft>`).
694    pub margin_left_pt: Option<f64>,
695    /// Paragraph right margin in points (XFA `<para marginRight>`).
696    pub margin_right_pt: Option<f64>,
697    /// XFA Spec 3.3 §17 "para" (p803) — lineHeight: baseline-to-baseline
698    /// distance in points. When `None`, use font metrics.
699    pub line_height_pt: Option<f64>,
700    /// XFA Spec 3.3 §17 "para" (p803) — textIndent: indentation of the first
701    /// line of each paragraph in points.
702    pub text_indent_pt: Option<f64>,
703    /// Margin top inset in points (XFA `<margin topInset>`).
704    pub inset_top_pt: Option<f64>,
705    /// Margin bottom inset in points (XFA `<margin bottomInset>`).
706    pub inset_bottom_pt: Option<f64>,
707    /// Margin left inset in points (XFA `<margin leftInset>`).
708    pub inset_left_pt: Option<f64>,
709    /// Margin right inset in points (XFA `<margin rightInset>`).
710    pub inset_right_pt: Option<f64>,
711    /// Vertical text alignment (XFA `<para vAlign>`).
712    pub v_align: Option<VerticalAlign>,
713    /// Horizontal alignment for layout positioning (XFA `<para hAlign>`, §8.3).
714    pub h_align: Option<TextAlign>,
715    /// Border corner radius in points (XFA `<border><corner radius>`).
716    pub border_radius_pt: Option<f64>,
717    /// Border edge stroke style (XFA `<border><edge stroke>`).
718    pub border_style: Option<String>,
719    /// Per-edge visibility: [top, right, bottom, left]. All true when absent.
720    pub border_edges: [bool; 4],
721    /// XFA Spec 3.3 §17 (p716) — genericFamily fallback hint.
722    /// Values: serif, sansSerif, monospaced, decorative, fantasy, cursive.
723    pub generic_family: Option<String>,
724    /// Font horizontal scale factor (XFA `<font fontHorizontalScale>`).
725    /// 1.0 = 100% (default), 0.96 = 96%, etc.
726    pub font_horizontal_scale: Option<f64>,
727    /// Letter spacing in points (XFA `<font letterSpacing>`).
728    /// 0.0 = normal (default). Negative values tighten, positive widen.
729    pub letter_spacing_pt: Option<f64>,
730    /// Caption text (XFA `<caption><value><text>`).
731    pub caption_text: Option<String>,
732    /// Caption placement (left/right/top/bottom/inline).
733    pub caption_placement: Option<String>,
734    /// Caption reserve width/height in points.
735    pub caption_reserve: Option<f64>,
736    /// Caption's own typeface (XFA `<caption><font typeface>`), independent of
737    /// the field's font. When set, the caption label is drawn in this face.
738    pub caption_font_family: Option<String>,
739    /// Caption's own font size in points (XFA `<caption><font size>`).
740    pub caption_font_size: Option<f64>,
741    /// CheckButton mark style (XFA `<checkButton mark="...">`).
742    /// Values: "check", "circle", "cross", "diamond", "square", "star".
743    pub check_button_mark: Option<String>,
744    /// CheckButton on-value (first `<items>` entry, XFA 3.3 §17.8).
745    pub check_button_on_value: Option<String>,
746    /// CheckButton off-value (second `<items>` entry). When omitted, the
747    /// spec default is the null string.
748    pub check_button_off_value: Option<String>,
749    /// CheckButton neutral-value (third `<items>` entry, checkbox only).
750    pub check_button_neutral_value: Option<String>,
751    /// Rich text spans parsed from `<exData contentType="text/html">` XHTML.
752    pub rich_text_spans: Option<Vec<RichTextSpan>>,
753    /// Font underline (XFA `<font underline="1">`).
754    pub underline: bool,
755    /// Font line-through / strikethrough (XFA `<font lineThrough="1">`).
756    pub line_through: bool,
757    /// XFA format picture clause (e.g. `num{z,zzz.99}`).
758    pub format_pattern: Option<String>,
759}
760
761impl Default for FormNodeStyle {
762    fn default() -> Self {
763        Self {
764            font_family: None,
765            font_size: None,
766            font_weight: None,
767            font_style: None,
768            text_color: None,
769            bg_color: None,
770            border_color: None,
771            border_colors: None,
772            border_width_pt: None,
773            border_widths: None,
774            space_above_pt: None,
775            space_below_pt: None,
776            margin_left_pt: None,
777            margin_right_pt: None,
778            line_height_pt: None,
779            text_indent_pt: None,
780            inset_top_pt: None,
781            inset_bottom_pt: None,
782            inset_left_pt: None,
783            inset_right_pt: None,
784            v_align: None,
785            h_align: None,
786            border_radius_pt: None,
787            border_style: None,
788            border_edges: [true, true, true, true],
789            generic_family: None,
790            font_horizontal_scale: None,
791            letter_spacing_pt: None,
792            caption_text: None,
793            caption_placement: None,
794            caption_reserve: None,
795            caption_font_family: None,
796            caption_font_size: None,
797            check_button_mark: None,
798            check_button_on_value: None,
799            check_button_off_value: None,
800            check_button_neutral_value: None,
801            rich_text_spans: None,
802            underline: false,
803            line_through: false,
804            format_pattern: None,
805        }
806    }
807}