//! The template model: what the layout engine needs from an XFA template (XFA 3.3 part 2, //! "Template Specification"). Every measurement is in points, every position is top-left //! based, as in XFA itself. /// Whether an object is shown and whether it takes space. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Presence { #[default] Visible, /// Takes space, draws nothing. Invisible, /// Takes no space. Hidden, /// Hidden, and its scripts don't run. Inactive, } impl Presence { pub fn parse(s: Option<&str>) -> Presence { match s.map(str::trim) { Some("invisible") => Presence::Invisible, Some("hidden") => Presence::Hidden, Some("inactive") => Presence::Inactive, _ => Presence::Visible, } } /// Takes space in the layout. pub fn occupies(self) -> bool { matches!(self, Presence::Visible | Presence::Invisible) } } /// How a subform places its children. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Layout { #[default] Positioned, /// Top to bottom. Tb, /// Left to right, then top to bottom. LrTb, /// Right to left, then top to bottom (laid out like `LrTb`, mirrored). RlTb, Table, Row, } impl Layout { pub fn parse(s: Option<&str>) -> Layout { match s.map(str::trim) { Some("tb") => Layout::Tb, Some("lr-tb") => Layout::LrTb, Some("rl-tb") => Layout::RlTb, Some("table") => Layout::Table, Some("row") => Layout::Row, _ => Layout::Positioned, } } } #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Margin { pub top: f64, pub right: f64, pub bottom: f64, pub left: f64, } impl Margin { pub fn horizontal(&self) -> f64 { self.left + self.right } pub fn vertical(&self) -> f64 { self.top + self.bottom } } /// An RGB colour, components 0–1. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Color(pub [f64; 3]); impl Color { pub const BLACK: Color = Color([0.0, 0.0, 0.0]); pub const WHITE: Color = Color([1.0, 1.0, 1.0]); /// `value="r,g,b"` with components 0–255. pub fn parse(s: &str) -> Option { let mut parts = s.split(',').map(|p| p.trim().parse::().ok()); let (r, g, b) = (parts.next()??, parts.next()??, parts.next()??); let c = |v: f64| if v.is_finite() { (v / 255.0).clamp(0.0, 1.0) } else { 0.0 }; Some(Color([c(r), c(g), c(b)])) } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Stroke { #[default] Solid, Dashed, Dotted, /// Raised, lowered, etched and embossed: drawn as a solid grey line. Relief, } /// One side of a border. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Edge { pub presence: Presence, pub thickness: f64, pub color: Color, pub stroke: Stroke, } impl Default for Edge { fn default() -> Self { Edge { presence: Presence::Visible, thickness: 0.5, color: Color::BLACK, stroke: Stroke::Solid } } } impl Edge { pub fn visible(&self) -> bool { self.presence == Presence::Visible && self.thickness > 0.0 } } /// A box border: four edges (top, right, bottom, left) and a fill. #[derive(Clone, Debug, Default, PartialEq)] pub struct Border { pub presence: Presence, pub edges: [Edge; 4], pub fill: Option, } impl Border { pub fn visible_edges(&self) -> [bool; 4] { if self.presence != Presence::Visible { return [false; 4]; } [self.edges[0].visible(), self.edges[1].visible(), self.edges[2].visible(), self.edges[3].visible()] } pub fn fill(&self) -> Option { if self.presence == Presence::Visible { self.fill } else { None } } } /// Font properties as written on an element; `None` inherits from the ancestors. #[derive(Clone, Debug, Default, PartialEq)] pub struct FontSpec { pub typeface: Option, pub size: Option, pub bold: Option, pub italic: Option, pub underline: Option, pub color: Option, } /// A font with every property resolved. #[derive(Clone, Debug, PartialEq)] pub struct Font { pub typeface: String, pub size: f64, pub bold: bool, pub italic: bool, pub underline: bool, pub color: Color, } impl Default for Font { /// XFA's default font: Courier 10 pt. fn default() -> Self { Font { typeface: "Courier".into(), size: 10.0, bold: false, italic: false, underline: false, color: Color::BLACK } } } impl Font { pub fn apply(&self, spec: &FontSpec) -> Font { Font { typeface: spec.typeface.clone().unwrap_or_else(|| self.typeface.clone()), size: spec.size.unwrap_or(self.size), bold: spec.bold.unwrap_or(self.bold), italic: spec.italic.unwrap_or(self.italic), underline: spec.underline.unwrap_or(self.underline), color: spec.color.unwrap_or(self.color), } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum HAlign { #[default] Left, Center, Right, Justify, } impl HAlign { pub fn parse(s: Option<&str>) -> Option { match s.map(str::trim) { Some("left") => Some(HAlign::Left), Some("center") => Some(HAlign::Center), Some("right") => Some(HAlign::Right), Some("justify" | "justifyAll") => Some(HAlign::Justify), _ => None, } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum VAlign { #[default] Top, Middle, Bottom, } impl VAlign { pub fn parse(s: Option<&str>) -> Option { match s.map(str::trim) { Some("top") => Some(VAlign::Top), Some("middle") => Some(VAlign::Middle), Some("bottom") => Some(VAlign::Bottom), _ => None, } } } /// Which point of a positioned object's box its `x` and `y` give (`anchorType`). #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Anchor { /// Fraction of the width left of the point: 0 left, 0.5 centre, 1 right. pub h: f64, /// Fraction of the height above the point: 0 top, 0.5 middle, 1 bottom. pub v: f64, } impl Anchor { pub fn parse(s: Option<&str>) -> Anchor { let s = s.map(str::trim).unwrap_or(""); let v = if s.starts_with("middle") { 0.5 } else if s.starts_with("bottom") { 1.0 } else { 0.0 }; let h = if s.ends_with("Center") { 0.5 } else if s.ends_with("Right") { 1.0 } else { 0.0 }; Anchor { h, v } } } /// Paragraph properties as written on an element; `None` inherits. #[derive(Clone, Debug, Default, PartialEq)] pub struct ParaSpec { pub h_align: Option, pub v_align: Option, pub line_height: Option, pub margin_left: Option, pub margin_right: Option, pub space_above: Option, pub space_below: Option, pub text_indent: Option, } #[derive(Clone, Debug, Default, PartialEq)] pub struct Para { pub h_align: HAlign, pub v_align: VAlign, /// `None`: 1.15 × the font size. pub line_height: Option, pub margin_left: f64, pub margin_right: f64, pub space_above: f64, pub space_below: f64, pub text_indent: f64, } impl Para { pub fn apply(&self, spec: &ParaSpec) -> Para { Para { h_align: spec.h_align.unwrap_or(self.h_align), v_align: spec.v_align.unwrap_or(self.v_align), line_height: spec.line_height.or(self.line_height), margin_left: spec.margin_left.unwrap_or(self.margin_left), margin_right: spec.margin_right.unwrap_or(self.margin_right), space_above: spec.space_above.unwrap_or(self.space_above), space_below: spec.space_below.unwrap_or(self.space_below), text_indent: spec.text_indent.unwrap_or(self.text_indent), } } } /// What every container and leaf shares: name, box, presence, margins, border, text defaults. #[derive(Clone, Debug, Default, PartialEq)] pub struct Common { pub name: Option, pub id: Option, pub x: Option, pub y: Option, pub anchor: Anchor, pub w: Option, pub h: Option, pub min_w: Option, pub max_w: Option, pub min_h: Option, pub max_h: Option, pub presence: Presence, pub margin: Margin, pub border: Option, pub font: FontSpec, pub para: ParaSpec, /// Table cells: how many columns this cell spans. pub col_span: usize, } #[derive(Clone, Debug, PartialEq)] pub enum Node { Subform(Box), Area(Box), Field(Box), Draw(Box), ExclGroup(Box), } impl Common { /// The top-left corner of a positioned box of `w` by `h`, from `x`, `y` and the anchor. pub fn top_left(&self, w: f64, h: f64) -> (f64, f64) { (self.x.unwrap_or(0.0) - w * self.anchor.h, self.y.unwrap_or(0.0) - h * self.anchor.v) } } impl Node { pub fn common(&self) -> &Common { match self { Node::Subform(s) => &s.common, Node::Area(a) => &a.common, Node::Field(f) => &f.common, Node::Draw(d) => &d.common, Node::ExclGroup(g) => &g.common, } } } pub(crate) struct SomIndices(Vec); impl SomIndices { pub(crate) fn of(children: &[Node]) -> Self { let mut seen = std::collections::HashMap::new(); SomIndices( children .iter() .map(|c| { c.common().name.as_deref().map_or(0, |name| { let n = seen.entry(name).or_insert(0); *n += 1; *n - 1 }) }) .collect(), ) } pub(crate) fn at(&self, i: usize) -> usize { self.0.get(i).copied().unwrap_or(0) } } /// How often a subform occurs when no data drives it. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Occur { pub min: usize, /// `None`: unbounded. pub max: Option, pub initial: usize, } impl Default for Occur { fn default() -> Self { Occur { min: 1, max: Some(1), initial: 1 } } } #[derive(Clone, Debug, Default, PartialEq)] pub struct Subform { pub common: Common, pub layout: Layout, pub children: Vec, /// `