pub mod canonical_json;
pub mod version;
pub use version::SCHEMA_VERSION;
use crate::types::Rect;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct LayoutNodeId(String);
impl LayoutNodeId {
pub fn root() -> Self {
Self(String::new())
}
pub fn child(&self, index: usize) -> Self {
if self.0.is_empty() {
Self(index.to_string())
} else {
Self(format!("{}/{}", self.0, index))
}
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum NodeKind {
Root,
PageSet,
PageArea,
ContentArea,
Subform,
SubformSet,
Field,
Draw,
ExclGroup,
#[default]
Other,
}
impl NodeKind {
pub fn tag(self) -> &'static str {
match self {
NodeKind::Root => "root",
NodeKind::PageSet => "page_set",
NodeKind::PageArea => "page_area",
NodeKind::ContentArea => "content_area",
NodeKind::Subform => "subform",
NodeKind::SubformSet => "subform_set",
NodeKind::Field => "field",
NodeKind::Draw => "draw",
NodeKind::ExclGroup => "excl_group",
NodeKind::Other => "other",
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum PresenceIR {
#[default]
Visible,
Hidden,
Inactive,
Invisible,
Unknown,
}
impl PresenceIR {
pub fn tag(self) -> &'static str {
match self {
PresenceIR::Visible => "visible",
PresenceIR::Hidden => "hidden",
PresenceIR::Inactive => "inactive",
PresenceIR::Invisible => "invisible",
PresenceIR::Unknown => "unknown",
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum FieldKindIR {
Text,
NumericEdit,
Choice,
DateTime,
Signature,
Barcode,
Image,
Button,
Other,
}
impl FieldKindIR {
pub fn tag(self) -> &'static str {
match self {
FieldKindIR::Text => "text",
FieldKindIR::NumericEdit => "numeric_edit",
FieldKindIR::Choice => "choice",
FieldKindIR::DateTime => "date_time",
FieldKindIR::Signature => "signature",
FieldKindIR::Barcode => "barcode",
FieldKindIR::Image => "image",
FieldKindIR::Button => "button",
FieldKindIR::Other => "other",
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum OverflowState {
#[default]
None,
Split,
DeferredToNextPage,
Clipped,
Unknown,
}
impl OverflowState {
pub fn tag(self) -> &'static str {
match self {
OverflowState::None => "none",
OverflowState::Split => "split",
OverflowState::DeferredToNextPage => "deferred_to_next_page",
OverflowState::Clipped => "clipped",
OverflowState::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct LayoutNode {
pub id: LayoutNodeId,
pub kind: NodeKind,
pub som: Option<String>,
pub page_index: Option<u32>,
pub rect: Option<Rect>,
pub presence: PresenceIR,
pub field_kind: Option<FieldKindIR>,
pub value_hash: Option<String>,
pub overflow: OverflowState,
pub form_node_id: Option<u64>,
pub children: Vec<LayoutNode>,
}
impl LayoutNode {
pub fn new(id: LayoutNodeId, kind: NodeKind) -> Self {
Self {
id,
kind,
..Default::default()
}
}
pub fn push_child(&mut self, child: LayoutNode) -> usize {
self.children.push(child);
self.children.len() - 1
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LayoutTreeIR {
pub schema_version: u32,
pub root: LayoutNode,
}
impl Default for LayoutTreeIR {
fn default() -> Self {
Self {
schema_version: SCHEMA_VERSION,
root: LayoutNode::new(LayoutNodeId::root(), NodeKind::Root),
}
}
}
impl LayoutTreeIR {
pub fn new() -> Self {
Self::default()
}
pub fn to_canonical_json(&self) -> String {
let mut out = String::new();
canonical_json::write_tree(&mut out, self);
out
}
pub fn node_count(&self) -> usize {
fn walk(n: &LayoutNode) -> usize {
1 + n.children.iter().map(walk).sum::<usize>()
}
walk(&self.root)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn root_id_is_empty() {
assert_eq!(LayoutNodeId::root().as_str(), "");
}
#[test]
fn child_id_is_path() {
let r = LayoutNodeId::root();
let c0 = r.child(0);
let c01 = c0.child(1);
assert_eq!(c0.as_str(), "0");
assert_eq!(c01.as_str(), "0/1");
}
#[test]
fn empty_tree_node_count_is_one() {
let tree = LayoutTreeIR::new();
assert_eq!(tree.node_count(), 1);
assert_eq!(tree.schema_version, SCHEMA_VERSION);
}
#[test]
fn synthetic_tree_node_count() {
let mut root = LayoutNode::new(LayoutNodeId::root(), NodeKind::Root);
let mut p0 = LayoutNode::new(root.id.child(0), NodeKind::PageArea);
p0.push_child(LayoutNode::new(p0.id.child(0), NodeKind::ContentArea));
p0.push_child(LayoutNode::new(p0.id.child(1), NodeKind::Field));
root.push_child(p0);
let tree = LayoutTreeIR {
schema_version: SCHEMA_VERSION,
root,
};
assert_eq!(tree.node_count(), 4);
}
#[test]
fn presence_default_is_visible() {
let n = LayoutNode::default();
assert_eq!(n.presence, PresenceIR::Visible);
}
#[test]
fn overflow_default_is_none() {
let n = LayoutNode::default();
assert_eq!(n.overflow, OverflowState::None);
}
}