vole_document/adapter/pdf/
span.rs1use crate::error::{Error, Result};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SpanKind {
14 Whitespace,
16 Comment,
18 LiteralString,
20 HexString,
22 DictOpen,
24 DictClose,
26 ArrayOpen,
28 ArrayClose,
30 BraceOpen,
32 BraceClose,
34 Name,
36 Regular,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct Span {
43 pub start: u64,
45 pub len: u64,
47 pub kind: SpanKind,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Default)]
53pub struct SpanSet {
54 pub spans: Vec<Span>,
56}
57
58impl SpanSet {
59 pub fn total_len(&self) -> u64 {
61 self.spans
62 .iter()
63 .fold(0u64, |acc, s| acc.saturating_add(s.len))
64 }
65
66 pub fn validate(&self, declared_len: u64) -> Result<()> {
74 let mut cursor: u64 = 0;
75 for (i, span) in self.spans.iter().enumerate() {
76 if span.len == 0 {
77 return Err(Error::invalid_pdf_structure(format!(
78 "span {i} has zero length at offset {}",
79 span.start
80 )));
81 }
82 if span.start != cursor {
83 let why = if span.start < cursor {
84 "overlap"
85 } else {
86 "gap"
87 };
88 return Err(Error::coverage_violation(format!(
89 "span {i} {why}: expected start {cursor}, found {}",
90 span.start
91 )));
92 }
93 cursor = cursor.checked_add(span.len).ok_or_else(|| {
94 Error::coverage_violation("span lengths overflow the address space")
95 })?;
96 }
97 if cursor != declared_len {
98 return Err(Error::coverage_violation(format!(
99 "cover ends at {cursor}, declared length is {declared_len}"
100 )));
101 }
102 Ok(())
103 }
104
105 pub fn span_at(&self, offset: u64) -> Option<Span> {
107 let idx = self.spans.partition_point(|s| s.start <= offset);
108 if idx == 0 {
109 return None;
110 }
111 let span = self.spans[idx - 1];
112 if offset < span.start.saturating_add(span.len) {
113 Some(span)
114 } else {
115 None
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::error::ErrorClass;
124
125 fn s(start: u64, len: u64, kind: SpanKind) -> Span {
126 Span { start, len, kind }
127 }
128
129 #[test]
130 fn empty_set_validates_zero() {
131 let set = SpanSet::default();
132 assert_eq!(set.total_len(), 0);
133 assert!(set.validate(0).is_ok());
134 assert_eq!(set.span_at(0), None);
135 }
136
137 #[test]
138 fn contiguous_set_validates_and_totals() {
139 let set = SpanSet {
140 spans: vec![s(0, 3, SpanKind::Whitespace), s(3, 2, SpanKind::Regular)],
141 };
142 assert_eq!(set.total_len(), 5);
143 assert!(set.validate(5).is_ok());
144 }
145
146 #[test]
147 fn gap_is_coverage_violation() {
148 let set = SpanSet {
149 spans: vec![s(0, 1, SpanKind::Regular), s(2, 1, SpanKind::Regular)],
150 };
151 let e = set.validate(3).unwrap_err();
152 assert_eq!(e.class(), ErrorClass::CoverageViolation);
153 }
154
155 #[test]
156 fn overlap_is_coverage_violation() {
157 let set = SpanSet {
158 spans: vec![s(0, 2, SpanKind::Regular), s(1, 2, SpanKind::Regular)],
159 };
160 let e = set.validate(3).unwrap_err();
161 assert_eq!(e.class(), ErrorClass::CoverageViolation);
162 }
163
164 #[test]
165 fn short_cover_is_coverage_violation() {
166 let set = SpanSet {
167 spans: vec![s(0, 2, SpanKind::Regular)],
168 };
169 let e = set.validate(3).unwrap_err();
170 assert_eq!(e.class(), ErrorClass::CoverageViolation);
171 }
172
173 #[test]
174 fn zero_length_span_is_invalid_structure() {
175 let set = SpanSet {
176 spans: vec![s(0, 0, SpanKind::Regular)],
177 };
178 let e = set.validate(0).unwrap_err();
179 assert_eq!(e.class(), ErrorClass::InvalidPdfStructure);
180 }
181
182 #[test]
183 fn span_at_finds_the_right_span() {
184 let set = SpanSet {
185 spans: vec![
186 s(0, 2, SpanKind::Regular),
187 s(2, 3, SpanKind::Whitespace),
188 s(5, 1, SpanKind::Name),
189 ],
190 };
191 assert_eq!(set.span_at(0), Some(s(0, 2, SpanKind::Regular)));
192 assert_eq!(set.span_at(1), Some(s(0, 2, SpanKind::Regular)));
193 assert_eq!(set.span_at(2), Some(s(2, 3, SpanKind::Whitespace)));
194 assert_eq!(set.span_at(4), Some(s(2, 3, SpanKind::Whitespace)));
195 assert_eq!(set.span_at(5), Some(s(5, 1, SpanKind::Name)));
196 assert_eq!(set.span_at(6), None);
197 assert_eq!(set.span_at(u64::MAX), None);
198 }
199}