use std::collections::HashMap;
use crate::text::FontMetrics;
use crate::types::{BoxModel, LayoutStrategy, TextAlign, VerticalAlign};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FormNodeId(pub usize);
#[derive(Debug)]
pub struct FormTree {
pub nodes: Vec<FormNode>,
pub metadata: Vec<FormNodeMeta>,
pub node_ids: HashMap<String, FormNodeId>,
}
impl FormTree {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
metadata: Vec::new(),
node_ids: HashMap::new(),
}
}
pub fn add_node(&mut self, node: FormNode) -> FormNodeId {
let id = FormNodeId(self.nodes.len());
self.nodes.push(node);
self.metadata.push(FormNodeMeta::default());
id
}
pub fn add_node_with_meta(&mut self, node: FormNode, meta: FormNodeMeta) -> FormNodeId {
let id = FormNodeId(self.nodes.len());
if let Some(ref xfa_id) = meta.xfa_id {
self.node_ids.insert(xfa_id.clone(), id);
}
self.nodes.push(node);
self.metadata.push(meta);
id
}
pub fn get(&self, id: FormNodeId) -> &FormNode {
&self.nodes[id.0]
}
pub fn get_mut(&mut self, id: FormNodeId) -> &mut FormNode {
&mut self.nodes[id.0]
}
pub fn meta(&self, id: FormNodeId) -> &FormNodeMeta {
&self.metadata[id.0]
}
pub fn meta_mut(&mut self, id: FormNodeId) -> &mut FormNodeMeta {
&mut self.metadata[id.0]
}
pub fn find_by_xfa_id(&self, id: &str) -> Option<FormNodeId> {
self.node_ids.get(id).copied()
}
}
impl Default for FormTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct FormNode {
pub name: String,
pub node_type: FormNodeType,
pub box_model: BoxModel,
pub layout: LayoutStrategy,
pub children: Vec<FormNodeId>,
pub occur: Occur,
pub font: FontMetrics,
pub calculate: Option<String>,
pub validate: Option<String>,
pub column_widths: Vec<f64>,
pub col_span: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScriptLanguage {
#[default]
FormCalc,
JavaScript,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EventScript {
pub script: String,
pub language: ScriptLanguage,
pub activity: Option<String>,
pub event_ref: Option<String>,
pub run_at: Option<String>,
}
impl EventScript {
pub fn new(
script: String,
language: ScriptLanguage,
activity: Option<String>,
event_ref: Option<String>,
run_at: Option<String>,
) -> Self {
Self {
script,
language,
activity,
event_ref,
run_at,
}
}
pub fn formcalc(script: impl Into<String>, activity: Option<&str>) -> Self {
Self::new(
script.into(),
ScriptLanguage::FormCalc,
activity.map(str::to_string),
None,
None,
)
}
pub fn javascript(script: impl Into<String>, activity: Option<&str>) -> Self {
Self::new(
script.into(),
ScriptLanguage::JavaScript,
activity.map(str::to_string),
None,
None,
)
}
}
#[derive(Debug, Clone)]
pub enum DrawContent {
Text(String),
Line {
x1: f64,
y1: f64,
x2: f64,
y2: f64,
},
Rectangle {
x: f64,
y: f64,
w: f64,
h: f64,
radius: f64,
},
Arc {
x: f64,
y: f64,
w: f64,
h: f64,
start_angle: f64,
sweep_angle: f64,
},
}
#[derive(Debug, Clone)]
pub enum FormNodeType {
Root,
PageSet,
PageArea { content_areas: Vec<ContentArea> },
Subform,
Area,
ExclGroup,
SubformSet,
Field { value: String },
Draw(DrawContent),
Image { data: Vec<u8>, mime_type: String },
}
#[derive(Debug, Clone)]
pub struct Occur {
pub min: u32,
pub max: Option<u32>,
pub initial: u32,
}
impl Default for Occur {
fn default() -> Self {
Self {
min: 1,
max: Some(1),
initial: 1,
}
}
}
impl Occur {
pub fn once() -> Self {
Self::default()
}
pub fn repeating(min: u32, max: Option<u32>, initial: u32) -> Self {
let initial = initial.max(min);
let initial = match max {
Some(m) => initial.min(m),
None => initial,
};
Self { min, max, initial }
}
pub fn count(&self) -> u32 {
self.initial
}
pub fn is_repeating(&self) -> bool {
match self.max {
Some(m) => m > 1,
None => true,
}
}
}
#[derive(Debug, Clone)]
pub struct ContentArea {
pub name: String,
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
pub leader: Option<FormNodeId>,
pub trailer: Option<FormNodeId>,
}
impl Default for ContentArea {
fn default() -> Self {
Self {
name: String::new(),
x: 0.0,
y: 0.0,
width: 612.0, height: 792.0, leader: None,
trailer: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Presence {
#[default]
Visible,
Hidden,
Invisible,
Inactive,
}
impl Presence {
pub fn is_not_visible(self) -> bool {
!matches!(self, Presence::Visible)
}
pub fn is_layout_hidden(self) -> bool {
matches!(
self,
Presence::Hidden | Presence::Invisible | Presence::Inactive
)
}
}
#[derive(Debug, Clone, Default)]
pub struct FormNodeMeta {
pub xfa_id: Option<String>,
pub presence: Presence,
pub page_break_before: bool,
pub page_break_after: bool,
pub break_target: Option<String>,
pub content_area_break: bool,
pub overflow_leader: Option<String>,
pub overflow_trailer: Option<String>,
pub keep_next_content_area: bool,
pub keep_previous_content_area: bool,
pub keep_intact_content_area: bool,
pub layout_ready_script: Option<String>,
pub event_scripts: Vec<EventScript>,
pub data_bind_ref: Option<String>,
pub data_bind_none: bool,
pub style: FormNodeStyle,
pub field_kind: FieldKind,
pub group_kind: GroupKind,
pub item_value: Option<String>,
pub check_size: Option<f64>,
pub display_items: Vec<String>,
pub save_items: Vec<String>,
pub anchor_type: AnchorType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AnchorType {
#[default]
TopLeft,
TopCenter,
TopRight,
MiddleLeft,
MiddleCenter,
MiddleRight,
BottomLeft,
BottomCenter,
BottomRight,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GroupKind {
#[default]
None,
ExclusiveChoice,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FieldKind {
#[default]
Text,
Checkbox,
Radio,
Button,
Dropdown,
Signature,
DateTimePicker,
NumericEdit,
PasswordEdit,
ImageEdit,
Barcode,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RichTextSpan {
pub text: String,
pub font_size: Option<f64>,
pub font_family: Option<String>,
pub font_weight: Option<String>,
pub font_style: Option<String>,
pub text_color: Option<(u8, u8, u8)>,
pub underline: bool,
pub line_through: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FormNodeStyle {
pub font_family: Option<String>,
pub font_size: Option<f64>,
pub font_weight: Option<String>,
pub font_style: Option<String>,
pub text_color: Option<(u8, u8, u8)>,
pub bg_color: Option<(u8, u8, u8)>,
pub border_color: Option<(u8, u8, u8)>,
pub border_colors: Option<[(u8, u8, u8); 4]>,
pub border_width_pt: Option<f64>,
pub border_widths: Option<[f64; 4]>,
pub space_above_pt: Option<f64>,
pub space_below_pt: Option<f64>,
pub margin_left_pt: Option<f64>,
pub margin_right_pt: Option<f64>,
pub line_height_pt: Option<f64>,
pub text_indent_pt: Option<f64>,
pub inset_top_pt: Option<f64>,
pub inset_bottom_pt: Option<f64>,
pub inset_left_pt: Option<f64>,
pub inset_right_pt: Option<f64>,
pub v_align: Option<VerticalAlign>,
pub h_align: Option<TextAlign>,
pub border_radius_pt: Option<f64>,
pub border_style: Option<String>,
pub border_edges: [bool; 4],
pub generic_family: Option<String>,
pub font_horizontal_scale: Option<f64>,
pub letter_spacing_pt: Option<f64>,
pub caption_text: Option<String>,
pub caption_placement: Option<String>,
pub caption_reserve: Option<f64>,
pub check_button_mark: Option<String>,
pub check_button_on_value: Option<String>,
pub check_button_off_value: Option<String>,
pub check_button_neutral_value: Option<String>,
pub rich_text_spans: Option<Vec<RichTextSpan>>,
pub underline: bool,
pub line_through: bool,
pub format_pattern: Option<String>,
}
impl Default for FormNodeStyle {
fn default() -> Self {
Self {
font_family: None,
font_size: None,
font_weight: None,
font_style: None,
text_color: None,
bg_color: None,
border_color: None,
border_colors: None,
border_width_pt: None,
border_widths: None,
space_above_pt: None,
space_below_pt: None,
margin_left_pt: None,
margin_right_pt: None,
line_height_pt: None,
text_indent_pt: None,
inset_top_pt: None,
inset_bottom_pt: None,
inset_left_pt: None,
inset_right_pt: None,
v_align: None,
h_align: None,
border_radius_pt: None,
border_style: None,
border_edges: [true, true, true, true],
generic_family: None,
font_horizontal_scale: None,
letter_spacing_pt: None,
caption_text: None,
caption_placement: None,
caption_reserve: None,
check_button_mark: None,
check_button_on_value: None,
check_button_off_value: None,
check_button_neutral_value: None,
rich_text_spans: None,
underline: false,
line_through: false,
format_pattern: None,
}
}
}