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