oxipdf-ir 0.1.0

Intermediate representation types for the oxipdf PDF engine
Documentation
pub mod content;

pub use content::*;

use crate::semantic::SemanticRole;
use crate::style::ResolvedStyle;

/// A stable, unique identifier for a node in the `StyledTree`.
///
/// `NodeId` values are assigned during tree construction and must persist
/// unchanged through layout, fragmentation, and emission. This invariant
/// is critical for link/annotation emission (§6.5).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NodeId(u32);

impl NodeId {
    /// Create a `NodeId` from a raw index.
    #[must_use]
    pub const fn from_raw(index: u32) -> Self {
        Self(index)
    }

    /// Return the raw index.
    #[must_use]
    pub const fn raw(self) -> u32 {
        self.0
    }
}

impl std::fmt::Display for NodeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "node#{}", self.0)
    }
}

/// A node in the styled IR tree.
#[derive(Debug, Clone)]
pub struct Node {
    pub id: NodeId,
    pub content: ContentVariant,
    pub style: ResolvedStyle,
    /// Ordered child node IDs. Empty for leaf nodes.
    pub children: Vec<NodeId>,
    pub semantic_role: Option<SemanticRole>,
    /// Optional element ID for cross-reference resolution (§5.3).
    pub element_id: Option<String>,
}

/// The content variant of an IR node (§5.1).
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ContentVariant {
    Text(TextContent),
    Svg(SvgContent),
    Math(MathContent),
    Image(ImageContent),
    Table(TableContent),
    Container,
    Link(LinkContent),
    Form(FormContent),
    /// A footnote. Renders as a superscript marker inline; children are
    /// the footnote body, placed at the page bottom during emission.
    Footnote(FootnoteContent),
    /// An invisible index entry marker. Produces no visible output; the
    /// engine collects these after fragmentation to generate the index.
    IndexEntry(IndexEntryContent),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn node_id_ordering() {
        assert!(NodeId::from_raw(0) < NodeId::from_raw(1));
    }

    #[test]
    fn node_id_display() {
        assert_eq!(NodeId::from_raw(42).to_string(), "node#42");
    }
}