1use pdfrum_common::kurbo::{Affine, Rect};
10
11use crate::{Dict, Name, ObjRef, Object, PdfString, Resolve, Resolved, Stream};
12
13#[derive(Debug, Clone, Default, PartialEq)]
42pub struct Array(Vec<Object>);
43
44impl Array {
45 #[must_use]
47 pub fn new() -> Self {
48 Self(Vec::new())
49 }
50
51 #[must_use]
54 pub fn of(values: impl IntoIterator<Item = Object>) -> Self {
55 values.into_iter().collect()
56 }
57
58 pub fn push(&mut self, value: Object) {
62 self.0.push(value);
63 }
64
65 pub fn insert(&mut self, index: usize, value: Object) {
77 self.0.insert(index, value);
78 }
79
80 pub fn remove(&mut self, index: usize) -> Option<Object> {
82 (index < self.0.len()).then(|| self.0.remove(index))
83 }
84
85 #[must_use]
87 pub fn len(&self) -> usize {
88 self.0.len()
89 }
90
91 #[must_use]
93 pub fn is_empty(&self) -> bool {
94 self.0.is_empty()
95 }
96
97 pub fn iter(&self) -> impl Iterator<Item = &Object> {
99 self.0.iter()
100 }
101
102 #[must_use]
104 pub fn as_slice(&self) -> &[Object] {
105 &self.0
106 }
107
108 #[must_use]
112 pub fn raw_at(&self, index: usize) -> Option<&Object> {
113 self.0.get(index)
114 }
115
116 #[must_use]
119 pub fn int_at(&self, index: usize) -> Option<i64> {
120 self.raw_at(index)?.as_int()
121 }
122
123 #[must_use]
125 pub fn number_at(&self, index: usize) -> Option<f32> {
126 self.raw_at(index)?.number()
127 }
128
129 #[must_use]
132 pub fn number_at_or_zero(&self, index: usize) -> f32 {
133 self.number_at(index).unwrap_or(0.0)
134 }
135
136 #[must_use]
138 pub fn bool_at(&self, index: usize) -> Option<bool> {
139 self.raw_at(index)?.as_bool()
140 }
141
142 #[must_use]
144 pub fn name_at(&self, index: usize) -> Option<&Name> {
145 self.raw_at(index)?.as_name()
146 }
147
148 #[must_use]
150 pub fn string_at(&self, index: usize) -> Option<&PdfString> {
151 self.raw_at(index)?.as_string()
152 }
153
154 #[must_use]
159 pub fn number_obj_at(&self, index: usize) -> Option<&Object> {
160 self.raw_at(index)?.as_number()
161 }
162
163 #[must_use]
165 pub fn byte_string_at(&self, index: usize) -> Option<Vec<u8>> {
166 Some(self.raw_at(index)?.to_byte_string())
167 }
168
169 #[must_use]
171 pub fn text_at(&self, index: usize) -> Option<String> {
172 Some(self.raw_at(index)?.to_text())
173 }
174
175 #[must_use]
177 pub fn reference_at(&self, index: usize) -> Option<ObjRef> {
178 self.raw_at(index)?.as_ref_id()
179 }
180
181 #[must_use]
185 pub fn get<'a>(&'a self, index: usize, r: &impl Resolve) -> Option<Resolved<'a>> {
186 self.raw_at(index)?.resolve(r).ok()
187 }
188
189 #[must_use]
192 pub fn dict_at(&self, index: usize, r: &impl Resolve) -> Option<Dict> {
193 self.get(index, r)?.as_direct()?.as_dict().cloned()
194 }
195
196 #[must_use]
198 pub fn array_at(&self, index: usize, r: &impl Resolve) -> Option<Array> {
199 self.get(index, r)?.as_direct()?.as_array().cloned()
200 }
201
202 #[must_use]
204 pub fn stream_at(&self, index: usize, r: &impl Resolve) -> Option<Stream> {
205 self.get(index, r)?.as_direct()?.as_stream().cloned()
206 }
207
208 #[must_use]
226 pub fn as_rect(&self) -> Rect {
227 if self.len() != 4 {
228 return Rect::new(0.0, 0.0, 0.0, 0.0);
229 }
230 Rect::new(
231 f64::from(self.number_at_or_zero(0)),
232 f64::from(self.number_at_or_zero(1)),
233 f64::from(self.number_at_or_zero(2)),
234 f64::from(self.number_at_or_zero(3)),
235 )
236 }
237
238 #[must_use]
241 pub fn as_matrix(&self) -> Affine {
242 if self.len() != 6 {
243 return Affine::IDENTITY;
244 }
245 Affine::new([
246 f64::from(self.number_at_or_zero(0)),
247 f64::from(self.number_at_or_zero(1)),
248 f64::from(self.number_at_or_zero(2)),
249 f64::from(self.number_at_or_zero(3)),
250 f64::from(self.number_at_or_zero(4)),
251 f64::from(self.number_at_or_zero(5)),
252 ])
253 }
254
255 #[must_use]
258 pub fn to_numbers(&self) -> Vec<f32> {
259 (0..self.len()).map(|i| self.number_at_or_zero(i)).collect()
260 }
261}
262
263impl FromIterator<Object> for Array {
264 fn from_iter<I: IntoIterator<Item = Object>>(iter: I) -> Self {
265 let mut array = Self::new();
266 for value in iter {
267 array.push(value);
268 }
269 array
270 }
271}
272
273impl<'a> IntoIterator for &'a Array {
274 type Item = &'a Object;
275 type IntoIter = std::slice::Iter<'a, Object>;
276
277 fn into_iter(self) -> Self::IntoIter {
278 self.0.iter()
279 }
280}
281
282#[cfg(test)]
283#[expect(
284 clippy::float_cmp,
285 reason = "these assertions pin exact bit patterns the oracle produces"
286)]
287mod tests {
288 use pdfrum_common::kurbo::{Affine, Rect};
289
290 use super::Array;
291 use crate::test_resolve::TestStore;
292 use crate::{Dict, Name, NoResolve, ObjRef, Object, PdfString, Stream, names};
293
294 #[test]
296 fn boolean_accessor_rejects_integers() {
297 let a = Array::of([
298 Object::Bool(true),
299 Object::Bool(false),
300 Object::Int(0),
301 Object::Int(1),
302 ]);
303 assert_eq!(a.bool_at(0), Some(true));
304 assert_eq!(a.bool_at(1), Some(false));
305 assert_eq!(a.bool_at(2), None);
306 assert_eq!(a.bool_at(3), None);
307 assert_eq!(a.bool_at(100), None);
308 }
309
310 #[test]
311 fn out_of_range_is_absence_everywhere() {
312 let a = Array::of([Object::Int(1)]);
313 assert_eq!(a.raw_at(9), None);
314 assert_eq!(a.int_at(9), None);
315 assert_eq!(a.number_at(9), None);
316 assert_eq!(a.number_at_or_zero(9), 0.0);
317 assert_eq!(a.name_at(9), None);
318 assert_eq!(a.string_at(9), None);
319 assert_eq!(a.byte_string_at(9), None);
320 assert_eq!(a.text_at(9), None);
321 assert_eq!(a.reference_at(9), None);
322 assert!(a.dict_at(9, &NoResolve).is_none());
323 assert!(a.array_at(9, &NoResolve).is_none());
324 assert!(a.stream_at(9, &NoResolve).is_none());
325 }
326
327 #[test]
328 fn scalar_accessors_do_not_resolve_but_composite_ones_do() {
329 let inner = Dict::from_pairs([(names::TYPE.clone(), Object::Name(names::PAGE.clone()))]);
330 let store = TestStore::from_pairs([
331 (1, Object::Int(42)),
332 (2, Object::Dict(inner.clone())),
333 (3, Object::Array(Array::of([Object::Int(1)]))),
334 (
335 4,
336 Object::Stream(Box::new(Stream::new(inner.clone(), b"xyz".to_vec().into()))),
337 ),
338 ]);
339 let a = Array::of([
340 Object::Ref(ObjRef::new(1, 0)),
341 Object::Ref(ObjRef::new(2, 0)),
342 Object::Ref(ObjRef::new(3, 0)),
343 Object::Ref(ObjRef::new(4, 0)),
344 ]);
345
346 assert_eq!(a.name_at(0), None);
348 assert_eq!(a.bool_at(0), None);
349 assert_eq!(a.number_obj_at(0), None);
351
352 assert_eq!(a.dict_at(1, &store), Some(inner.clone()));
353 assert_eq!(a.array_at(2, &store).map(|x| x.len()), Some(1));
354 assert!(a.stream_at(3, &store).is_some());
355 assert_eq!(a.dict_at(3, &store), Some(inner));
357 }
358
359 #[test]
361 fn rect_and_matrix_need_exactly_the_right_element_count() {
362 let numbers = |n: usize| {
363 (1..=n)
364 .map(|i| Object::Real(f32::from(u8::try_from(i).unwrap_or(0))))
365 .collect::<Array>()
366 };
367 assert_eq!(numbers(4).as_rect(), Rect::new(1.0, 2.0, 3.0, 4.0));
368 assert_eq!(numbers(3).as_rect(), Rect::new(0.0, 0.0, 0.0, 0.0));
369 assert_eq!(numbers(5).as_rect(), Rect::new(0.0, 0.0, 0.0, 0.0));
370 assert_eq!(Array::new().as_rect(), Rect::new(0.0, 0.0, 0.0, 0.0));
371
372 assert_eq!(
373 numbers(6).as_matrix(),
374 Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
375 );
376 assert_eq!(numbers(5).as_matrix(), Affine::IDENTITY);
377 assert_eq!(numbers(7).as_matrix(), Affine::IDENTITY);
378 }
379
380 #[test]
381 fn rect_is_not_normalized() {
382 let inverted = Array::of([
384 Object::Int(612),
385 Object::Int(792),
386 Object::Int(0),
387 Object::Int(0),
388 ]);
389 assert_eq!(inverted.as_rect(), Rect::new(612.0, 792.0, 0.0, 0.0));
390 }
391
392 #[test]
393 fn malformed_geometry_elements_read_as_zero() {
394 let a = Array::of([
395 Object::Int(1),
396 Object::Name(Name::from("nope")),
397 Object::Null,
398 Object::Real(4.0),
399 ]);
400 assert_eq!(a.as_rect(), Rect::new(1.0, 0.0, 0.0, 4.0));
401 assert_eq!(a.to_numbers(), [1.0, 0.0, 0.0, 4.0]);
402 }
403
404 #[test]
406 fn byte_string_spelling_per_element_type() {
407 let a = Array::of([
408 Object::Bool(false),
409 Object::Bool(true),
410 Object::Int(1245),
411 Object::Real(9.003_45),
412 Object::Str(PdfString::literal(b"A simple test")),
413 Object::Name(Name::from("space")),
414 Object::Array(Array::new()),
415 Object::Dict(Dict::new()),
416 Object::Null,
417 ]);
418 let spellings: Vec<Vec<u8>> = (0..a.len())
419 .map(|i| a.byte_string_at(i).unwrap_or_default())
420 .collect();
421 assert_eq!(
422 spellings,
423 [
424 &b"false"[..],
425 b"true",
426 b"1245",
427 b"9.00345",
428 b"A simple test",
429 b"space",
430 b"",
431 b"",
432 b"",
433 ]
434 );
435 }
436
437 #[test]
441 fn membership_is_structural() {
442 let a = Array::of([Object::Int(1), Object::Name(Name::from("x"))]);
443 assert!(a.iter().any(|o| o == &Object::Int(1)));
444 assert!(!a.iter().any(|o| o == &Object::Int(2)));
445 assert_eq!(a.as_slice().len(), 2);
446 }
447
448 #[test]
449 fn array_insert_shifts_and_appends_at_len() {
450 let mut a = Array::new();
451 a.push(Object::Int(1));
452 a.push(Object::Int(3));
453 a.insert(1, Object::Int(2));
454 assert_eq!(
455 a.as_slice(),
456 [Object::Int(1), Object::Int(2), Object::Int(3)]
457 );
458 a.insert(3, Object::Int(4));
459 assert_eq!(a.raw_at(3), Some(&Object::Int(4)));
460 }
461
462 #[test]
463 fn array_remove_out_of_range_is_none() {
464 let mut a = Array::of([Object::Int(1), Object::Int(2)]);
465 assert_eq!(a.remove(5), None);
466 assert_eq!(a.remove(0), Some(Object::Int(1)));
467 assert_eq!(a.as_slice(), [Object::Int(2)]);
468 }
469}