Skip to main content

justpdf_core/content/
operator.rs

1/// A single operand value in a content stream.
2#[derive(Debug, Clone, PartialEq)]
3pub enum Operand {
4    Integer(i64),
5    Real(f64),
6    Bool(bool),
7    Null,
8    Name(Vec<u8>),
9    String(Vec<u8>),
10    Array(Vec<Operand>),
11    Dict(Vec<(Vec<u8>, Operand)>),
12    /// Inline image data (from BI ... ID ... EI).
13    InlineImage {
14        dict: Vec<(Vec<u8>, Operand)>,
15        data: Vec<u8>,
16    },
17}
18
19impl Operand {
20    pub fn as_i64(&self) -> Option<i64> {
21        match self {
22            Self::Integer(v) => Some(*v),
23            _ => None,
24        }
25    }
26
27    pub fn as_f64(&self) -> Option<f64> {
28        match self {
29            Self::Real(v) => Some(*v),
30            Self::Integer(v) => Some(*v as f64),
31            _ => None,
32        }
33    }
34
35    pub fn as_name(&self) -> Option<&[u8]> {
36        match self {
37            Self::Name(v) => Some(v),
38            _ => None,
39        }
40    }
41
42    pub fn as_str(&self) -> Option<&[u8]> {
43        match self {
44            Self::String(v) => Some(v),
45            _ => None,
46        }
47    }
48
49    pub fn as_array(&self) -> Option<&[Operand]> {
50        match self {
51            Self::Array(v) => Some(v),
52            _ => None,
53        }
54    }
55}
56
57/// A single content stream operation: operands followed by an operator.
58#[derive(Debug, Clone)]
59pub struct ContentOp {
60    /// The operator keyword (e.g., "cm", "Tf", "Tj", "q", "Q").
61    pub operator: Vec<u8>,
62    /// Operands that precede the operator.
63    pub operands: Vec<Operand>,
64}
65
66impl ContentOp {
67    /// Get the operator as a string.
68    pub fn operator_str(&self) -> &str {
69        std::str::from_utf8(&self.operator).unwrap_or("?")
70    }
71}
72
73impl std::fmt::Display for ContentOp {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        for (i, op) in self.operands.iter().enumerate() {
76            if i > 0 {
77                write!(f, " ")?;
78            }
79            write!(f, "{}", format_operand(op))?;
80        }
81        if !self.operands.is_empty() {
82            write!(f, " ")?;
83        }
84        write!(f, "{}", self.operator_str())
85    }
86}
87
88fn format_operand(op: &Operand) -> String {
89    match op {
90        Operand::Integer(v) => v.to_string(),
91        Operand::Real(v) => format!("{v}"),
92        Operand::Bool(v) => v.to_string(),
93        Operand::Null => "null".into(),
94        Operand::Name(n) => format!("/{}", std::str::from_utf8(n).unwrap_or("?")),
95        Operand::String(s) => match std::str::from_utf8(s) {
96            Ok(text) => format!("({text})"),
97            Err(_) => {
98                let hex: String = s.iter().map(|b| format!("{b:02X}")).collect();
99                format!("<{hex}>")
100            }
101        },
102        Operand::Array(items) => {
103            let inner: Vec<String> = items.iter().map(format_operand).collect();
104            format!("[{}]", inner.join(" "))
105        }
106        Operand::Dict(entries) => {
107            let inner: Vec<String> = entries
108                .iter()
109                .map(|(k, v)| {
110                    format!(
111                        "/{} {}",
112                        std::str::from_utf8(k).unwrap_or("?"),
113                        format_operand(v)
114                    )
115                })
116                .collect();
117            format!("<< {} >>", inner.join(" "))
118        }
119        Operand::InlineImage { .. } => "<inline-image>".into(),
120    }
121}