1use std::collections::HashMap;
3
4pub type Box4 = [f64; 4];
5pub type Record = (u32, usize, Box4);
6pub type Visibility = (usize, bool, Option<Box4>);
7
8pub fn python_space(code: u32) -> bool {
10 matches!(code, 9..=13 | 28..=32 | 0x85 | 0xa0 | 0x1680 | 0x2000..=0x200a | 0x2028 | 0x2029 | 0x202f | 0x205f | 0x3000)
11}
12
13fn minimum(a: f64, b: f64) -> f64 {
15 if b < a {
16 b
17 } else {
18 a
19 }
20}
21
22fn maximum(a: f64, b: f64) -> f64 {
24 if b > a {
25 b
26 } else {
27 a
28 }
29}
30
31fn area(b: Box4) -> f64 {
33 maximum(0.0, b[2] - b[0]) * maximum(0.0, b[3] - b[1])
34}
35
36fn intersect(a: Box4, b: Box4) -> Box4 {
38 [
39 maximum(a[0], b[0]),
40 maximum(a[1], b[1]),
41 minimum(a[2], b[2]),
42 minimum(a[3], b[3]),
43 ]
44}
45
46fn union_area(boxes: &[Box4]) -> f64 {
48 let valid: Vec<_> = boxes.iter().copied().filter(|b| area(*b) > 0.0).collect();
49 let mut edges: Vec<_> = valid.iter().flat_map(|b| [b[0], b[2]]).collect();
50 edges.sort_by(|a, b| a.partial_cmp(b).unwrap());
51 edges.dedup();
52 let mut total = 0.0;
53 for edge in edges.windows(2) {
54 let (left, right) = (edge[0], edge[1]);
55 let mut intervals: Vec<_> = valid
56 .iter()
57 .filter(|b| b[0] < right && b[2] > left)
58 .map(|b| (b[1], b[3]))
59 .collect();
60 intervals.sort_by(|a, b| a.partial_cmp(b).unwrap());
61 let (mut top, mut height) = (f64::NEG_INFINITY, 0.0);
62 for (bottom, end) in intervals {
63 height += maximum(0.0, end - maximum(bottom, top));
64 top = maximum(top, end);
65 }
66 total += (right - left) * height;
67 }
68 total
69}
70
71fn visual_box(b: Box4, frame: Box4, rotation: i32) -> Box4 {
73 let mut points = Vec::with_capacity(4);
74 for x in [b[0], b[2]] {
75 for y in [b[1], b[3]] {
76 points.push(match rotation {
77 90 => (y - frame[1], x - frame[0]),
78 180 => (frame[2] - x, y - frame[1]),
79 270 => (frame[3] - y, frame[2] - x),
80 _ => (x - frame[0], frame[3] - y),
81 });
82 }
83 }
84 [
85 points.iter().map(|p| p.0).reduce(minimum).unwrap(),
86 points.iter().map(|p| p.1).reduce(minimum).unwrap(),
87 points.iter().map(|p| p.0).reduce(maximum).unwrap(),
88 points.iter().map(|p| p.1).reduce(maximum).unwrap(),
89 ]
90}
91
92pub struct Candidates {
94 pub boxes: Vec<Box4>,
95 pub paints: bool,
96}
97
98pub fn candidates(
100 records: &[Record],
101 frame: Box4,
102 rotation: i32,
103 images: &[Box4],
104 visibility: &[Visibility],
105 overlap: f64,
106) -> Option<Candidates> {
107 if frame
108 .iter()
109 .chain(images.iter().flatten())
110 .any(|v| !v.is_finite())
111 || records.iter().flat_map(|r| r.2).any(|v| !v.is_finite())
112 || visibility
113 .iter()
114 .filter_map(|v| v.2)
115 .flatten()
116 .any(|v| !v.is_finite())
117 || !overlap.is_finite()
118 || ![0, 90, 180, 270].contains(&rotation)
119 {
120 return None;
121 }
122 let (mut width, mut height) = (frame[2] - frame[0], frame[3] - frame[1]);
123 if rotation == 90 || rotation == 270 {
124 std::mem::swap(&mut width, &mut height);
125 }
126 let lookup: HashMap<_, _> = visibility.iter().map(|v| (v.0, (v.1, v.2))).collect();
127 let mut result = Candidates {
128 boxes: Vec::new(),
129 paints: false,
130 };
131 for &(code, address, bounds) in records {
132 if code == 0 || code > 0x10ffff || python_space(code) || address == 0 {
133 continue;
134 }
135 let (visible, clip) = lookup.get(&address).copied().unwrap_or((true, None));
136 let mut b = visual_box(bounds, frame, rotation);
137 if let Some(clip) = clip {
138 b = intersect(b, clip);
139 }
140 b = intersect(b, [0.0, 0.0, width, height]);
141 let a = area(b);
142 if a > 0.0 {
143 let overlaps: Vec<_> = images.iter().map(|image| intersect(b, *image)).collect();
144 if union_area(&overlaps) >= a * overlap {
145 result.boxes.push(b);
146 result.paints |= visible;
147 }
148 }
149 }
150 Some(result)
151}
152
153pub fn painted_count(
155 data: &[u8],
156 width: usize,
157 height: usize,
158 boxes: &[Box4],
159 page_width: f64,
160 page_height: f64,
161) -> usize {
162 let (sx, sy) = (width as f64 / page_width, height as f64 / page_height);
163 boxes
164 .iter()
165 .filter(|b| {
166 let x0 = maximum(0.0, (b[0] * sx).floor()) as usize;
167 let y0 = maximum(0.0, (b[1] * sy).floor()) as usize;
168 let x1 = minimum(width as f64, (b[2] * sx).ceil()) as usize;
169 let y1 = minimum(height as f64, (b[3] * sy).ceil()) as usize;
170 x0 < x1
171 && y0 < y1
172 && x1 <= width
173 && y1 <= height
174 && (y0..y1).any(|y| data[y * width + x0..y * width + x1].iter().any(|v| *v != 0))
175 })
176 .count()
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
185 fn whitespace_surrogates_and_overlap() {
186 let records = vec![
187 (28, 1, [1.0, 1.0, 2.0, 2.0]),
188 (0xd800, 1, [1.0, 1.0, 2.0, 2.0]),
189 (65, 2, [2.0, 2.0, 3.0, 3.0]),
190 ];
191 let result = candidates(
192 &records,
193 [0.0, 0.0, 10.0, 10.0],
194 0,
195 &[[0.0, 0.0, 10.0, 10.0]],
196 &[(1, false, None)],
197 0.8,
198 )
199 .unwrap();
200 assert_eq!(result.boxes.len(), 2);
201 assert!(result.paints);
202 }
203
204 #[test]
206 fn fractional_pixels_are_counted_exactly() {
207 let result = painted_count(
208 &[0, 1, 0, 0],
209 2,
210 2,
211 &[
212 [0.51, 0.0, 1.01, 0.5],
213 [0.0, 1.0, 0.5, 2.0],
214 [0.51, 0.0, 1.01, 0.5],
215 ],
216 2.0,
217 2.0,
218 );
219 assert_eq!(result, 2);
220 }
221}