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 = impl Into<Object>>) -> Self {
55 values.into_iter().map(Into::into).collect()
56 }
57
58 pub fn push(&mut self, value: impl Into<Object>) {
62 self.0.push(value.into());
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
256impl AsRef<[Object]> for Array {
257 fn as_ref(&self) -> &[Object] {
258 self.as_slice()
259 }
260}
261
262impl From<Vec<Object>> for Array {
263 fn from(values: Vec<Object>) -> Self {
264 Self(values)
265 }
266}
267
268impl From<Array> for Vec<Object> {
269 fn from(array: Array) -> Self {
270 array.0
271 }
272}
273
274impl FromIterator<Object> for Array {
275 fn from_iter<I: IntoIterator<Item = Object>>(iter: I) -> Self {
276 Self(iter.into_iter().collect())
277 }
278}
279
280impl IntoIterator for Array {
281 type Item = Object;
282 type IntoIter = std::vec::IntoIter<Object>;
283
284 fn into_iter(self) -> Self::IntoIter {
285 self.0.into_iter()
286 }
287}
288
289impl<'a> IntoIterator for &'a Array {
290 type Item = &'a Object;
291 type IntoIter = std::slice::Iter<'a, Object>;
292
293 fn into_iter(self) -> Self::IntoIter {
294 self.0.iter()
295 }
296}
297
298#[cfg(test)]
299#[expect(
300 clippy::float_cmp,
301 reason = "these assertions pin exact bit patterns the oracle produces"
302)]
303mod tests {
304 use pdfrum_common::kurbo::{Affine, Rect};
305
306 use super::Array;
307 use crate::test_resolve::TestStore;
308 use crate::{Dict, Name, NoResolve, ObjRef, Object, PdfString, Stream, names};
309
310 #[test]
311 fn from_and_into_vec_are_the_elements() {
312 let values = vec![Object::Int(1), Object::Int(2)];
313 let array = Array::from(values);
314 assert_eq!(array.as_ref(), &[Object::Int(1), Object::Int(2)]);
315 let back: Vec<Object> = array.into();
316 assert_eq!(back, [Object::Int(1), Object::Int(2)]);
317 }
318
319 #[test]
320 fn into_iterator_yields_the_objects() {
321 let array = Array::of([Object::Int(1), Object::Name("x".into())]);
322 let got: Vec<_> = array.into_iter().collect();
323 assert_eq!(got, [Object::Int(1), Object::Name("x".into())]);
324 }
325
326 #[test]
328 fn boolean_accessor_rejects_integers() {
329 let a = Array::of([
330 Object::Bool(true),
331 Object::Bool(false),
332 Object::Int(0),
333 Object::Int(1),
334 ]);
335 assert_eq!(a.bool_at(0), Some(true));
336 assert_eq!(a.bool_at(1), Some(false));
337 assert_eq!(a.bool_at(2), None);
338 assert_eq!(a.bool_at(3), None);
339 assert_eq!(a.bool_at(100), None);
340 }
341
342 #[test]
343 fn out_of_range_is_absence_everywhere() {
344 let a = Array::of([Object::Int(1)]);
345 assert_eq!(a.raw_at(9), None);
346 assert_eq!(a.int_at(9), None);
347 assert_eq!(a.number_at(9), None);
348 assert_eq!(a.number_at_or_zero(9), 0.0);
349 assert_eq!(a.name_at(9), None);
350 assert_eq!(a.string_at(9), None);
351 assert_eq!(a.byte_string_at(9), None);
352 assert_eq!(a.text_at(9), None);
353 assert_eq!(a.reference_at(9), None);
354 assert!(a.dict_at(9, &NoResolve).is_none());
355 assert!(a.array_at(9, &NoResolve).is_none());
356 assert!(a.stream_at(9, &NoResolve).is_none());
357 }
358
359 #[test]
360 fn scalar_accessors_do_not_resolve_but_composite_ones_do() {
361 let inner = Dict::from_pairs([(names::TYPE.clone(), Object::Name(names::PAGE.clone()))]);
362 let store = TestStore::from_pairs([
363 (1, Object::Int(42)),
364 (2, Object::Dict(inner.clone())),
365 (3, Object::Array(Array::of([Object::Int(1)]))),
366 (
367 4,
368 Object::Stream(Box::new(Stream::new(inner.clone(), b"xyz".to_vec().into()))),
369 ),
370 ]);
371 let a = Array::of([
372 Object::Ref(ObjRef::new(1, 0)),
373 Object::Ref(ObjRef::new(2, 0)),
374 Object::Ref(ObjRef::new(3, 0)),
375 Object::Ref(ObjRef::new(4, 0)),
376 ]);
377
378 assert_eq!(a.name_at(0), None);
380 assert_eq!(a.bool_at(0), None);
381 assert_eq!(a.number_obj_at(0), None);
383
384 assert_eq!(a.dict_at(1, &store), Some(inner.clone()));
385 assert_eq!(a.array_at(2, &store).map(|x| x.len()), Some(1));
386 assert!(a.stream_at(3, &store).is_some());
387 assert_eq!(a.dict_at(3, &store), Some(inner));
389 }
390
391 #[test]
393 fn rect_and_matrix_need_exactly_the_right_element_count() {
394 let numbers = |n: usize| {
395 (1..=n)
396 .map(|i| Object::Real(f32::from(u8::try_from(i).unwrap_or(0))))
397 .collect::<Array>()
398 };
399 assert_eq!(numbers(4).as_rect(), Rect::new(1.0, 2.0, 3.0, 4.0));
400 assert_eq!(numbers(3).as_rect(), Rect::new(0.0, 0.0, 0.0, 0.0));
401 assert_eq!(numbers(5).as_rect(), Rect::new(0.0, 0.0, 0.0, 0.0));
402 assert_eq!(Array::new().as_rect(), Rect::new(0.0, 0.0, 0.0, 0.0));
403
404 assert_eq!(
405 numbers(6).as_matrix(),
406 Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
407 );
408 assert_eq!(numbers(5).as_matrix(), Affine::IDENTITY);
409 assert_eq!(numbers(7).as_matrix(), Affine::IDENTITY);
410 }
411
412 #[test]
413 fn rect_is_not_normalized() {
414 let inverted = Array::of([
416 Object::Int(612),
417 Object::Int(792),
418 Object::Int(0),
419 Object::Int(0),
420 ]);
421 assert_eq!(inverted.as_rect(), Rect::new(612.0, 792.0, 0.0, 0.0));
422 }
423
424 #[test]
425 fn malformed_geometry_elements_read_as_zero() {
426 let a = Array::of([
427 Object::Int(1),
428 Object::Name(Name::from("nope")),
429 Object::Null,
430 Object::Real(4.0),
431 ]);
432 assert_eq!(a.as_rect(), Rect::new(1.0, 0.0, 0.0, 4.0));
433 }
434
435 #[test]
437 fn byte_string_spelling_per_element_type() {
438 let a = Array::of([
439 Object::Bool(false),
440 Object::Bool(true),
441 Object::Int(1245),
442 Object::Real(9.003_45),
443 Object::Str(PdfString::literal(b"A simple test")),
444 Object::Name(Name::from("space")),
445 Object::Array(Array::new()),
446 Object::Dict(Dict::new()),
447 Object::Null,
448 ]);
449 let spellings: Vec<Vec<u8>> = (0..a.len())
450 .map(|i| a.byte_string_at(i).unwrap_or_default())
451 .collect();
452 assert_eq!(
453 spellings,
454 [
455 &b"false"[..],
456 b"true",
457 b"1245",
458 b"9.00345",
459 b"A simple test",
460 b"space",
461 b"",
462 b"",
463 b"",
464 ]
465 );
466 }
467
468 #[test]
472 fn membership_is_structural() {
473 let a = Array::of([Object::Int(1), Object::Name(Name::from("x"))]);
474 assert!(a.iter().any(|o| o == &Object::Int(1)));
475 assert!(!a.iter().any(|o| o == &Object::Int(2)));
476 assert_eq!(a.as_slice().len(), 2);
477 }
478
479 #[test]
480 fn array_insert_shifts_and_appends_at_len() {
481 let mut a = Array::new();
482 a.push(Object::Int(1));
483 a.push(Object::Int(3));
484 a.insert(1, Object::Int(2));
485 assert_eq!(
486 a.as_slice(),
487 [Object::Int(1), Object::Int(2), Object::Int(3)]
488 );
489 a.insert(3, Object::Int(4));
490 assert_eq!(a.raw_at(3), Some(&Object::Int(4)));
491 }
492
493 #[test]
494 fn array_remove_out_of_range_is_none() {
495 let mut a = Array::of([Object::Int(1), Object::Int(2)]);
496 assert_eq!(a.remove(5), None);
497 assert_eq!(a.remove(0), Some(Object::Int(1)));
498 assert_eq!(a.as_slice(), [Object::Int(2)]);
499 }
500}