Skip to main content

oxidize_pdf/parser/
xref_types.rs

1//! XRef Entry Type Definitions
2//!
3//! This module defines all valid XRef entry types according to the PDF specification.
4//! It provides a comprehensive handling of cross-reference entry types beyond the basic 'n' and 'f'.
5
6// Module for XRef type definitions - no parsing errors needed here
7
8/// XRef entry type enumeration covering all valid types in PDF specification
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum XRefEntryType {
11    /// Type 0: Free object (f)
12    Free,
13    /// Type 1: Normal uncompressed object (n)
14    Uncompressed,
15    /// Type 2: Compressed object in an object stream
16    Compressed,
17    /// Other valid but less common types
18    /// Some PDF writers use custom types for internal purposes
19    Custom(u8),
20}
21
22impl XRefEntryType {
23    /// Parse entry type from a numeric value
24    pub fn from_value(value: u64) -> Self {
25        match value {
26            0 => XRefEntryType::Free,
27            1 => XRefEntryType::Uncompressed,
28            2 => XRefEntryType::Compressed,
29            n if n <= 255 => XRefEntryType::Custom(n as u8),
30            _ => XRefEntryType::Free, // Invalid types default to free
31        }
32    }
33
34    /// Check if this type represents an in-use object
35    pub fn is_in_use(&self) -> bool {
36        match self {
37            XRefEntryType::Free => false,
38            XRefEntryType::Uncompressed => true,
39            XRefEntryType::Compressed => true,
40            // Custom types: treat as in-use unless proven otherwise
41            // This is safer than discarding potentially valid objects
42            XRefEntryType::Custom(_) => true,
43        }
44    }
45
46    /// Get the numeric value for this type
47    pub fn to_value(&self) -> u8 {
48        match self {
49            XRefEntryType::Free => 0,
50            XRefEntryType::Uncompressed => 1,
51            XRefEntryType::Compressed => 2,
52            XRefEntryType::Custom(n) => *n,
53        }
54    }
55}
56
57/// Extended XRef entry information
58#[derive(Debug, Clone, PartialEq)]
59pub struct XRefEntryInfo {
60    /// Entry type
61    pub entry_type: XRefEntryType,
62    /// Field 2 interpretation depends on type:
63    /// - Free: next free object number
64    /// - Uncompressed: byte offset
65    /// - Compressed: object stream number
66    pub field2: u64,
67    /// Field 3 interpretation depends on type:
68    /// - Free: generation number
69    /// - Uncompressed: generation number
70    /// - Compressed: index within object stream
71    pub field3: u64,
72}
73
74impl XRefEntryInfo {
75    /// Create a new XRef entry info
76    pub fn new(entry_type: XRefEntryType, field2: u64, field3: u64) -> Self {
77        Self {
78            entry_type,
79            field2,
80            field3,
81        }
82    }
83
84    /// Get byte offset for uncompressed objects
85    pub fn get_offset(&self) -> Option<u64> {
86        match self.entry_type {
87            XRefEntryType::Uncompressed => Some(self.field2),
88            _ => None,
89        }
90    }
91
92    /// Get generation number
93    pub fn get_generation(&self) -> u16 {
94        match self.entry_type {
95            XRefEntryType::Free | XRefEntryType::Uncompressed => self.field3 as u16,
96            _ => 0,
97        }
98    }
99
100    /// Get compressed object info (stream number, index)
101    pub fn get_compressed_info(&self) -> Option<(u32, u32)> {
102        match self.entry_type {
103            XRefEntryType::Compressed => Some((self.field2 as u32, self.field3 as u32)),
104            _ => None,
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_xref_entry_type_from_value() {
115        assert_eq!(XRefEntryType::from_value(0), XRefEntryType::Free);
116        assert_eq!(XRefEntryType::from_value(1), XRefEntryType::Uncompressed);
117        assert_eq!(XRefEntryType::from_value(2), XRefEntryType::Compressed);
118        assert_eq!(XRefEntryType::from_value(53), XRefEntryType::Custom(53));
119        assert_eq!(XRefEntryType::from_value(255), XRefEntryType::Custom(255));
120        // Values > 255 default to Free
121        assert_eq!(XRefEntryType::from_value(256), XRefEntryType::Free);
122    }
123
124    #[test]
125    fn test_is_in_use() {
126        assert!(!XRefEntryType::Free.is_in_use());
127        assert!(XRefEntryType::Uncompressed.is_in_use());
128        assert!(XRefEntryType::Compressed.is_in_use());
129        assert!(XRefEntryType::Custom(53).is_in_use());
130    }
131
132    #[test]
133    fn test_entry_info() {
134        let info = XRefEntryInfo::new(XRefEntryType::Uncompressed, 1234, 5);
135        assert_eq!(info.get_offset(), Some(1234));
136        assert_eq!(info.get_generation(), 5);
137        assert_eq!(info.get_compressed_info(), None);
138
139        let compressed = XRefEntryInfo::new(XRefEntryType::Compressed, 10, 20);
140        assert_eq!(compressed.get_offset(), None);
141        assert_eq!(compressed.get_generation(), 0);
142        assert_eq!(compressed.get_compressed_info(), Some((10, 20)));
143    }
144
145    #[test]
146    fn test_to_value() {
147        assert_eq!(XRefEntryType::Free.to_value(), 0);
148        assert_eq!(XRefEntryType::Uncompressed.to_value(), 1);
149        assert_eq!(XRefEntryType::Compressed.to_value(), 2);
150        assert_eq!(XRefEntryType::Custom(53).to_value(), 53);
151        assert_eq!(XRefEntryType::Custom(255).to_value(), 255);
152    }
153
154    #[test]
155    fn test_free_entry_info() {
156        let free = XRefEntryInfo::new(XRefEntryType::Free, 100, 65535);
157        assert_eq!(free.get_offset(), None);
158        assert_eq!(free.get_generation(), 65535);
159        assert_eq!(free.get_compressed_info(), None);
160    }
161
162    #[test]
163    fn test_custom_entry_info() {
164        let custom = XRefEntryInfo::new(XRefEntryType::Custom(99), 500, 10);
165        assert_eq!(custom.get_offset(), None);
166        assert_eq!(custom.get_generation(), 0);
167        assert_eq!(custom.get_compressed_info(), None);
168    }
169}