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}