1use kurbo::Rect;
13use pdfrum_object::Array;
14
15use crate::geom;
16
17#[must_use]
32pub fn quad_point_count(array: Option<&Array>) -> usize {
33 array.map_or(0, |a| a.len() / 8)
34}
35
36#[must_use]
39pub(crate) fn rect_from_quad_points_array(array: &Array, index: usize) -> Rect {
40 let base = index * 8;
41 let at = |offset: usize| array.number_at_or_zero(base + offset);
42 geom::rect(at(4), at(5), at(2), at(3))
43}
44
45#[must_use]
60pub fn rect_from_quad_points(array: Option<&Array>, index: usize) -> Rect {
61 let array = array.filter(|a| index < a.len() / 8);
62 array.map_or(Rect::ZERO, |a| rect_from_quad_points_array(a, index))
63}
64
65#[must_use]
71pub(crate) fn bounding_rect_from_quad_points(array: Option<&Array>) -> Rect {
72 let count = quad_point_count(array);
73 let Some(array) = array.filter(|_| count > 0) else {
74 return Rect::ZERO;
75 };
76 let mut bounds = rect_from_quad_points_array(array, 0);
77 for index in 1..count {
78 bounds = geom::union(bounds, rect_from_quad_points_array(array, index));
79 }
80 bounds
81}
82
83#[cfg(test)]
84mod tests {
85 use super::{
86 bounding_rect_from_quad_points, quad_point_count, rect_from_quad_points,
87 rect_from_quad_points_array,
88 };
89 use crate::geom;
90 use kurbo::Rect;
91 use pdfrum_object::{Array, Object};
92
93 fn quads(count: usize) -> Array {
96 Array::of((0..count * 8).map(|n| Object::Int(i64::try_from(n).unwrap_or(0))))
97 }
98
99 #[test]
100 fn the_derived_rectangle_is_index_arithmetic_not_a_normalization() {
101 let array = quads(2);
104 assert_eq!(
105 rect_from_quad_points_array(&array, 0),
106 geom::rect(4.0, 5.0, 2.0, 3.0)
107 );
108 assert_eq!(
109 rect_from_quad_points_array(&array, 1),
110 geom::rect(12.0, 13.0, 10.0, 11.0)
111 );
112 }
113
114 #[test]
115 fn one_quadrilateral_is_never_normalized_but_two_are() {
116 let one = quads(1);
117 assert_eq!(
118 bounding_rect_from_quad_points(Some(&one)),
119 geom::rect(4.0, 5.0, 2.0, 3.0)
120 );
121 let three = quads(3);
122 assert_eq!(
123 bounding_rect_from_quad_points(Some(&three)),
124 geom::rect(2.0, 3.0, 20.0, 21.0)
125 );
126 }
127
128 #[test]
129 fn no_quadrilaterals_gives_the_zero_rectangle() {
130 assert_eq!(bounding_rect_from_quad_points(None), Rect::ZERO);
131 assert_eq!(
132 bounding_rect_from_quad_points(Some(&Array::of([Object::Int(1), Object::Int(2)]))),
133 Rect::ZERO
134 );
135 }
136
137 #[test]
138 fn an_out_of_range_index_gives_zero_rather_than_the_last_quadrilateral() {
139 let array = quads(2);
140 assert_eq!(rect_from_quad_points(Some(&array), 2), Rect::ZERO);
141 assert_eq!(
142 rect_from_quad_points(Some(&array), 1),
143 geom::rect(12.0, 13.0, 10.0, 11.0)
144 );
145 }
146
147 #[test]
148 fn the_count_ignores_a_trailing_partial_quadrilateral() {
149 for len in 0..8 {
150 let array = Array::of((0..len).map(Object::Int));
151 assert_eq!(quad_point_count(Some(&array)), 0, "len {len}");
152 }
153 for len in 8..16 {
154 let array = Array::of((0..len).map(Object::Int));
155 assert_eq!(quad_point_count(Some(&array)), 1, "len {len}");
156 }
157 let array = Array::of((0..65).map(Object::Int));
158 assert_eq!(quad_point_count(Some(&array)), 8);
159 }
160}