1use crate::geometry::{self, Box4};
4use std::collections::HashMap;
5pub type Rule = (u8, f64, f64, f64);
6pub type Query = (u8, Vec<f64>, f64, f64, f64);
7pub type GridIndex = (Vec<f64>, Vec<f64>, Vec<Vec<usize>>);
8type CoverageCache = HashMap<(u8, Vec<u64>, u64), Vec<(f64, f64)>>;
9pub type ComponentSpecs = (Vec<usize>, Option<Vec<(usize, usize, usize, usize)>>);
10
11fn intersection(a: Box4, b: Box4) -> Option<Box4> {
13 let c = [
14 a[0].max(b[0]),
15 a[1].max(b[1]),
16 a[2].min(b[2]),
17 a[3].min(b[3]),
18 ];
19 if c[2] > c[0] && c[3] > c[1] {
20 Some(c)
21 } else {
22 None
23 }
24}
25
26pub fn select_boxes(
28 values: Vec<Option<Box4>>,
29 table: Box4,
30 angle: i32,
31) -> Vec<(usize, Box4, Option<Box4>)> {
32 let mut result = Vec::new();
33 for (i, b) in values.into_iter().enumerate() {
34 let Some(b) = geometry::normalize(b, false) else {
35 continue;
36 };
37 let x = (b[0] + b[2]) / 2.0;
38 let y = (b[1] + b[3]) / 2.0;
39 if !(table[0] <= x && x <= table[2] && table[1] <= y && y <= table[3]) {
40 continue;
41 }
42 let local = intersection(b, table).map(|b| {
43 geometry::rotate(
44 [
45 b[0] - table[0],
46 b[1] - table[1],
47 b[2] - table[0],
48 b[3] - table[1],
49 ],
50 [table[2] - table[0], table[3] - table[1]],
51 angle,
52 )
53 });
54 result.push((i, b, local));
55 }
56 result
57}
58
59fn coverage(intervals: &[(f64, f64)], start: f64, end: f64) -> f64 {
61 if end <= start {
62 return 0.0;
63 }
64 let mut clipped: Vec<_> = intervals
65 .iter()
66 .filter_map(|(a, b)| {
67 let left = start.max(a.min(*b));
68 let right = end.min(a.max(*b));
69 if right > left {
70 Some((left, right))
71 } else {
72 None
73 }
74 })
75 .collect();
76 clipped.sort_by(|a, b| a.partial_cmp(b).unwrap());
77 let Some(&(mut left, mut right)) = clipped.first() else {
78 return 0.0;
79 };
80 let mut covered = 0.0;
81 for &(a, b) in &clipped[1..] {
82 if a <= right {
83 right = right.max(b);
84 } else {
85 covered += right - left;
86 left = a;
87 right = b;
88 }
89 }
90 covered += right - left;
91 1.0_f64.min(covered / (end - start))
92}
93
94pub fn coverage_batch(rules: Vec<Rule>, queries: Vec<Query>) -> Option<Vec<f64>> {
96 if rules
97 .iter()
98 .any(|r| ![r.1, r.2, r.3].iter().all(|v| v.is_finite()))
99 || queries.iter().any(|q| {
100 q.1.iter()
101 .chain([q.2, q.3, q.4].iter())
102 .any(|v| !v.is_finite())
103 })
104 {
105 return None;
106 }
107 let mut cache = CoverageCache::new();
108 Some(
109 queries
110 .into_iter()
111 .map(|(orientation, aliases, start, end, tolerance)| {
112 let key = (
113 orientation,
114 aliases.iter().map(|v| v.to_bits()).collect(),
115 tolerance.to_bits(),
116 );
117 let intervals = cache.entry(key).or_insert_with(|| {
118 rules
119 .iter()
120 .filter(|r| {
121 r.0 == orientation
122 && aliases.iter().any(|a| (r.1 - *a).abs() <= tolerance)
123 })
124 .map(|r| (r.2, r.3))
125 .collect()
126 });
127 coverage(intervals, start, end)
128 })
129 .collect(),
130 )
131}
132
133pub fn merge_rules(
135 rules: Vec<Rule>,
136 coordinates: Vec<(u8, Vec<f64>)>,
137 tolerance: f64,
138 join: f64,
139) -> Option<Vec<Rule>> {
140 if rules
141 .iter()
142 .any(|r| ![r.1, r.2, r.3].iter().all(|v| v.is_finite()))
143 || coordinates
144 .iter()
145 .flat_map(|r| &r.1)
146 .any(|v| !v.is_finite())
147 {
148 return None;
149 }
150 let mut output = Vec::new();
151 for (orientation, coords) in coordinates {
152 for coordinate in coords {
153 let mut intervals: Vec<_> = rules
154 .iter()
155 .filter(|r| r.0 == orientation && (r.1 - coordinate).abs() <= tolerance)
156 .map(|r| (r.2.min(r.3), r.2.max(r.3)))
157 .collect();
158 intervals.sort_by(|a, b| a.partial_cmp(b).unwrap());
159 let Some(&(mut start, mut end)) = intervals.first() else {
160 continue;
161 };
162 for &(a, b) in &intervals[1..] {
163 if a <= end + join {
164 end = end.max(b);
165 } else {
166 output.push((orientation, coordinate, start, end));
167 start = a;
168 end = b;
169 }
170 }
171 output.push((orientation, coordinate, start, end));
172 }
173 }
174 Some(output)
175}
176
177fn overlap(glyph: Box4, cell: Box4) -> f64 {
179 let area = (glyph[2] - glyph[0]) * (glyph[3] - glyph[1]);
180 let Some(b) = intersection(glyph, cell) else {
181 return 0.0;
182 };
183 if area <= 0.0 {
184 0.0
185 } else {
186 1.0_f64.min((b[2] - b[0]) * (b[3] - b[1]) / area)
187 }
188}
189
190fn contains(cell: Box4, glyph: Box4) -> bool {
192 let x = (glyph[0] + glyph[2]) / 2.0;
193 let y = (glyph[1] + glyph[3]) / 2.0;
194 cell[0] <= x && x <= cell[2] && cell[1] <= y && y <= cell[3]
195}
196
197fn track_index(tracks: &[f64], value: f64, right: bool) -> usize {
199 let at = if right {
200 tracks.partition_point(|v| *v <= value)
201 } else {
202 tracks.partition_point(|v| *v < value)
203 };
204 at.saturating_sub(1).min(tracks.len() - 2)
205}
206
207pub fn assign_cells(
209 glyphs: Vec<Box4>,
210 specs: Vec<Box4>,
211 index: Option<GridIndex>,
212) -> Option<Vec<(Option<usize>, bool)>> {
213 if specs.is_empty()
214 || glyphs
215 .iter()
216 .chain(&specs)
217 .flatten()
218 .any(|v| !v.is_finite())
219 {
220 return None;
221 }
222 if let Some((x, y, owners)) = &index {
223 if x.len() < 2
224 || y.len() < 2
225 || x.iter().chain(y).any(|v| !v.is_finite())
226 || owners.len() != y.len() - 1
227 || owners
228 .iter()
229 .any(|row| row.len() != x.len() - 1 || row.iter().any(|i| *i >= specs.len()))
230 {
231 return None;
232 }
233 }
234 Some(
235 glyphs
236 .into_iter()
237 .map(|glyph| {
238 let candidates = if let Some((x, y, owners)) = &index {
239 let left = track_index(x, glyph[0], true);
240 let right = track_index(x, glyph[2], false);
241 let top = track_index(y, glyph[1], true);
242 let bottom = track_index(y, glyph[3], false);
243 if left == right && top == bottom {
244 let i = owners[top][left];
245 return (
246 if overlap(glyph, specs[i]) > 0.0 || contains(specs[i], glyph) {
247 Some(i)
248 } else {
249 None
250 },
251 false,
252 );
253 }
254 let mut indices: Vec<_> = (top..=bottom)
255 .flat_map(|r| (left..=right).map(move |c| owners[r][c]))
256 .collect();
257 indices.sort_unstable();
258 indices.dedup();
259 indices
260 } else {
261 (0..specs.len()).collect()
262 };
263 let mut overlaps: Vec<_> = candidates
264 .iter()
265 .map(|i| (overlap(glyph, specs[*i]), *i))
266 .collect();
267 overlaps.sort_by(|a, b| b.partial_cmp(a).unwrap());
268 let Some(&(ratio, i)) = overlaps.first() else {
269 return (None, false);
270 };
271 if ratio <= 0.0 {
272 let containing: Vec<_> = candidates
273 .into_iter()
274 .filter(|j| contains(specs[*j], glyph))
275 .collect();
276 return (
277 if containing.len() == 1 {
278 Some(containing[0])
279 } else {
280 None
281 },
282 false,
283 );
284 }
285 (
286 Some(i),
287 overlaps.len() > 1
288 && overlaps[1].0 >= 0.45
289 && (ratio - overlaps[1].0).abs() <= 0.02,
290 )
291 })
292 .collect(),
293 )
294}
295
296fn find(parents: &mut [usize], index: usize) -> usize {
298 let mut root = index;
299 while parents[root] != root {
300 root = parents[root];
301 }
302 let mut i = index;
303 while parents[i] != i {
304 let next = parents[i];
305 parents[i] = root;
306 i = next;
307 }
308 root
309}
310
311pub fn grid_parents(count: usize, pairs: Vec<(usize, usize)>) -> Vec<usize> {
313 let mut parents: Vec<_> = (0..count).collect();
314 for (a, b) in pairs {
315 let a = find(&mut parents, a);
316 let b = find(&mut parents, b);
317 if a != b {
318 parents[b] = a;
319 }
320 }
321 parents
322}
323
324pub fn component_specs(mut parents: Vec<usize>, rows: usize, cols: usize) -> ComponentSpecs {
326 let mut groups: HashMap<usize, (usize, usize, usize, usize, usize)> = HashMap::new();
327 for r in 0..rows {
328 for c in 0..cols {
329 let root = find(&mut parents, r * cols + c);
330 let g = groups.entry(root).or_insert((r, c, r, c, 0));
331 g.0 = g.0.min(r);
332 g.1 = g.1.min(c);
333 g.2 = g.2.max(r);
334 g.3 = g.3.max(c);
335 g.4 += 1;
336 }
337 }
338 let mut specs = Vec::new();
339 for (r, c, last_r, last_c, n) in groups.into_values() {
340 if (last_r - r + 1) * (last_c - c + 1) != n {
341 return (parents, None);
342 }
343 specs.push((r, c, last_r - r + 1, last_c - c + 1));
344 }
345 specs.sort_unstable();
346 (parents, Some(specs))
347}
348
349pub fn visual_rows(
351 boxes: Vec<Box4>,
352 ids: Vec<usize>,
353 median_height: f64,
354) -> Option<Vec<Vec<usize>>> {
355 if boxes.len() != ids.len()
356 || !median_height.is_finite()
357 || boxes
358 .iter()
359 .flatten()
360 .any(|v| !v.is_finite() || v.abs() > 1e150)
361 {
362 return None;
363 }
364 let mut order: Vec<_> = (0..boxes.len()).collect();
365 order.sort_by(|&a, &b| {
366 let aa = boxes[a];
367 let bb = boxes[b];
368 ((aa[1] + aa[3]) / 2.0)
369 .partial_cmp(&((bb[1] + bb[3]) / 2.0))
370 .unwrap()
371 .then_with(|| aa[0].partial_cmp(&bb[0]).unwrap())
372 .then_with(|| ids[a].cmp(&ids[b]))
373 });
374 let mut rows: Vec<Vec<usize>> = Vec::new();
375 let mut bounds: Vec<Box4> = Vec::new();
376 let mut prefix: Vec<f64> = Vec::new();
377 let tolerance = 0.75_f64.max(median_height * 0.40);
378 for index in order {
379 let g = boxes[index];
380 let cy = (g[1] + g[3]) / 2.0;
381 let mut best = None;
382 let mut best_distance = f64::INFINITY;
383 for i in (0..bounds.len()).rev() {
384 let r = bounds[i];
385 let ry = (r[1] + r[3]) / 2.0;
386 let distance = (cy - ry).abs();
387 let overlap = g[3].min(r[3]) - g[1].max(r[1]);
388 let height = (g[3] - g[1]).min(r[3] - r[1]);
389 let ratio = if overlap <= 0.0 || height <= 0.0 {
390 0.0
391 } else {
392 1.0_f64.min(overlap / height)
393 };
394 if (ratio >= 0.45 || distance <= tolerance) && distance < best_distance {
395 best = Some(i);
396 best_distance = distance;
397 }
398 if cy >= ry && cy - ry > tolerance && g[1] > prefix[i] {
399 break;
400 }
401 }
402 if let Some(i) = best {
403 rows[i].push(index);
404 let r = bounds[i];
405 bounds[i] = [
406 r[0].min(g[0]),
407 r[1].min(g[1]),
408 r[2].max(g[2]),
409 r[3].max(g[3]),
410 ];
411 for j in i..bounds.len() {
412 prefix[j] = if j == 0 {
413 bounds[j][3]
414 } else {
415 prefix[j - 1].max(bounds[j][3])
416 };
417 }
418 } else {
419 rows.push(vec![index]);
420 bounds.push(g);
421 prefix.push(prefix.last().copied().unwrap_or(g[3]).max(g[3]));
422 }
423 }
424 let mut row_order: Vec<_> = (0..rows.len()).collect();
425 row_order.sort_by(|&a, &b| {
426 bounds[a][1]
427 .partial_cmp(&bounds[b][1])
428 .unwrap()
429 .then_with(|| bounds[a][0].partial_cmp(&bounds[b][0]).unwrap())
430 });
431 Some(
432 row_order
433 .into_iter()
434 .map(|i| {
435 let mut row = std::mem::take(&mut rows[i]);
436 row.sort_by(|&a, &b| {
437 boxes[a][0]
438 .partial_cmp(&boxes[b][0])
439 .unwrap()
440 .then_with(|| boxes[a][1].partial_cmp(&boxes[b][1]).unwrap())
441 .then_with(|| ids[a].cmp(&ids[b]))
442 });
443 row
444 })
445 .collect(),
446 )
447}
448
449pub fn row_occupancy(rows: Vec<Vec<f64>>, tracks: Vec<f64>) -> Option<Vec<Vec<usize>>> {
451 if rows
452 .iter()
453 .flatten()
454 .chain(tracks.iter())
455 .any(|v| !v.is_finite())
456 {
457 return None;
458 }
459 Some(
460 rows.into_iter()
461 .map(|row| {
462 let mut occupied = vec![false; tracks.len().saturating_sub(1)];
463 for center in row {
464 if let Some(i) = tracks
465 .windows(2)
466 .position(|t| t[0] <= center && center <= t[1])
467 {
468 occupied[i] = true;
469 }
470 }
471 occupied
472 .into_iter()
473 .enumerate()
474 .filter_map(|(i, yes)| yes.then_some(i))
475 .collect()
476 })
477 .collect(),
478 )
479}
480
481pub fn cell_visual_groups(
483 boxes: Vec<Box4>,
484 ids: Vec<usize>,
485 visual: Vec<i64>,
486 median_height: f64,
487) -> Option<Vec<Vec<usize>>> {
488 if boxes.len() != ids.len()
489 || boxes.len() != visual.len()
490 || !median_height.is_finite()
491 || boxes
492 .iter()
493 .flatten()
494 .any(|v| !v.is_finite() || v.abs() > 1e100)
495 {
496 return None;
497 }
498 let mut groups = std::collections::BTreeMap::<i64, Vec<usize>>::new();
499 for (index, row) in visual.into_iter().enumerate() {
500 groups.entry(row).or_default().push(index);
501 }
502 let mut refined = Vec::new();
503 for original in groups.into_values() {
504 if original.len() < 4 {
506 refined.push(original);
507 continue;
508 }
509 let local = visual_rows(
510 original.iter().map(|i| boxes[*i]).collect(),
511 original.iter().map(|i| ids[*i]).collect(),
512 median_height,
513 )?;
514 let coherent = local.iter().all(|row| {
515 let centers: Vec<f64> = row
516 .iter()
517 .map(|i| {
518 let b = boxes[original[*i]];
519 (b[1] + b[3]) / 2.0
520 })
521 .collect();
522 let low = centers.iter().copied().reduce(f64::min).unwrap();
523 let high = centers.iter().copied().reduce(f64::max).unwrap();
524 high - low <= 0.3 * median_height
525 });
526 if local.len() > 1
527 && coherent
528 && local.iter().all(|row| {
529 let height = crate::median(
530 row.iter()
531 .map(|i| {
532 let b = boxes[original[*i]];
533 b[3] - b[1]
534 })
535 .collect(),
536 );
537 row.len() >= 2 && 0.85 * median_height <= height && height <= 1.25 * median_height
538 })
539 {
540 refined.extend(
541 local
542 .into_iter()
543 .map(|row| row.into_iter().map(|i| original[i]).collect()),
544 );
545 } else {
546 refined.push(original);
547 }
548 }
549 Some(refined)
550}