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}