Skip to main content

docvortex_core/
annotation_geometry.rs

1//! 表注片段按原顺序筛选并聚合,只返回原坐标来源,不创建新的浮点对象。
2use crate::geometry::Box4;
3use std::collections::{HashMap, HashSet};
4
5pub type Fragment = (usize, Box4, Box4);
6pub type Output = (Vec<(usize, [usize; 4], usize)>, Option<[usize; 4]>);
7
8pub struct AnnotationGeometry {
9    fragments: Vec<Fragment>,
10}
11
12impl AnnotationGeometry {
13    /// 保存走廊内只读片段,限制有限数值幅度以保证中间运算安全。
14    pub fn new(fragments: Vec<Fragment>) -> Option<Self> {
15        if fragments.iter().any(|f| {
16            f.1.iter()
17                .chain(f.2.iter())
18                .any(|v| !v.is_finite() || v.abs() > 1e100)
19        }) {
20            return None;
21        }
22        Some(Self { fragments })
23    }
24
25    /// 严格比较保留先出现的坐标来源,包括相等值与正负零。
26    fn combine(&self, a: [usize; 4], b: [usize; 4]) -> [usize; 4] {
27        let mut result = a;
28        for k in 0..4 {
29            let av = self.fragments[a[k]].1[k];
30            let bv = self.fragments[b[k]].1[k];
31            if (k < 2 && bv < av) || (k >= 2 && bv > av) {
32                result[k] = b[k];
33            }
34        }
35        result
36    }
37
38    /// 按当前候选的原片段序处理排除与重复来源,错误索引明确拒绝。
39    pub fn aggregate(
40        &self,
41        selected: Vec<usize>,
42        excluded: Vec<usize>,
43        local: Option<Box4>,
44    ) -> Option<Output> {
45        if selected.iter().any(|i| *i >= self.fragments.len())
46            || local.is_some_and(|b| b.iter().any(|v| !v.is_finite() || v.abs() > 1e100))
47        {
48            return None;
49        }
50        let excluded: HashSet<_> = excluded.into_iter().collect();
51        let mut positions = HashMap::new();
52        let mut lines: Vec<(usize, [usize; 4], usize)> = Vec::new();
53        for i in selected {
54            let (source, _, b) = self.fragments[i];
55            if excluded.contains(&source) {
56                continue;
57            }
58            if let Some(region) = local {
59                let smaller = (b[2] - b[0]).min(region[2] - region[0]);
60                let overlap = 0.0_f64.max(b[2].min(region[2]) - b[0].max(region[0]));
61                let ratio = if smaller > 0.0 {
62                    overlap / smaller
63                } else {
64                    0.0
65                };
66                if b[3] > region[1] && ratio >= 0.05 {
67                    continue;
68                }
69            }
70            if let Some(&position) = positions.get(&source) {
71                let line: &mut (usize, [usize; 4], usize) = &mut lines[position];
72                line.1 = self.combine(line.1, [i; 4]);
73                line.2 += 1;
74            } else {
75                positions.insert(source, lines.len());
76                lines.push((source, [i; 4], 1));
77            }
78        }
79        let union = lines.iter().map(|v| v.1).reduce(|a, b| self.combine(a, b));
80        Some((lines, union))
81    }
82}