docvortex_core/
rule_graphics.rs1pub type CodeRuleWindow = (usize, usize, Vec<usize>);
5
6pub fn overlap_member_groups(
8 members: &[[f64; 4]],
9 regions: &[[f64; 4]],
10 threshold: f64,
11) -> Vec<Vec<usize>> {
12 regions
13 .iter()
14 .map(|®ion| {
15 members
16 .iter()
17 .enumerate()
18 .filter(|(_, bbox)| code_overlap(**bbox, region, false) >= threshold)
19 .map(|(i, _)| i)
20 .collect()
21 })
22 .collect()
23}
24
25fn code_overlap(first: [f64; 4], second: [f64; 4], smaller: bool) -> f64 {
27 let width = 0.0_f64.max(first[2].min(second[2]) - first[0].max(second[0]));
28 let height = 0.0_f64.max(first[3].min(second[3]) - first[1].max(second[1]));
29 let area = if smaller {
30 ((first[2] - first[0]) * (first[3] - first[1]))
31 .min((second[2] - second[0]) * (second[3] - second[1]))
32 } else {
33 0.0_f64.max(first[2] - first[0]) * 0.0_f64.max(first[3] - first[1])
34 };
35 if area > 0.0 {
36 width * height / area
37 } else {
38 0.0
39 }
40}
41
42pub fn code_rule_windows(
44 horizontal: &[[f64; 4]],
45 vertical: &[[f64; 4]],
46 lines: &[[f64; 4]],
47 excluded: &[[f64; 4]],
48 em: f64,
49 page_height: f64,
50) -> Vec<CodeRuleWindow> {
51 let tolerance = 2.0_f64.max(0.75 * em);
52 let mut output = Vec::new();
53 for (top_index, top) in horizontal.iter().enumerate() {
54 for (bottom_index, bottom) in horizontal.iter().enumerate().skip(top_index + 1) {
55 if (top[0] - bottom[0]).abs() > tolerance || (top[2] - bottom[2]).abs() > tolerance {
56 continue;
57 }
58 let bbox = [
59 top[0].min(bottom[0]),
60 top[1].min(bottom[1]),
61 top[2].max(bottom[2]),
62 top[3].max(bottom[3]),
63 ];
64 let height = bbox[3] - bbox[1];
65 if !(6.0 * em <= height && height <= 0.5 * page_height)
66 || excluded
67 .iter()
68 .any(|&other| code_overlap(bbox, other, true) >= 0.5)
69 || horizontal.iter().any(|&rule| {
70 top[3] + 0.5 * em < rule[1]
71 && rule[1] < bottom[1] - 0.5 * em
72 && code_overlap(rule, bbox, false) >= 0.8
73 && rule[2] - rule[0] >= 0.6 * (bbox[2] - bbox[0])
74 })
75 || vertical.iter().any(|rule| {
76 let center = (rule[0] + rule[2]) / 2.0;
77 bbox[0] + em < center
78 && center < bbox[2] - em
79 && 0.0_f64.max(rule[3].min(bbox[3]) - rule[1].max(bbox[1]))
80 / 0.0_f64.max(height)
81 >= 0.6
82 })
83 {
84 continue;
85 }
86 let members = lines
87 .iter()
88 .enumerate()
89 .filter(|(_, line)| {
90 let center_x = (line[0] + line[2]) / 2.0;
91 let center_y = (line[1] + line[3]) / 2.0;
92 bbox[0] - 0.5 * em <= center_x
93 && center_x <= bbox[2] + 0.5 * em
94 && top[3] <= center_y
95 && center_y <= bottom[1]
96 })
97 .map(|(i, _)| i)
98 .collect();
99 output.push((top_index, bottom_index, members));
100 }
101 }
102 output
103}
104
105pub type GlyphFontGroup = (
107 String,
108 i32,
109 Option<f64>,
110 usize,
111 Vec<(String, Vec<[f64; 4]>)>,
112);
113
114pub fn glyph_font_groups(
116 data: &crate::text_snapshot::TextSnapshot,
117 rows: &[Vec<usize>],
118 bold: &[bool],
119) -> Vec<GlyphFontGroup> {
120 let mut groups: Vec<GlyphFontGroup> = Vec::new();
121 let mut font_keys = std::collections::HashMap::new();
122 let mut font_groups = vec![None; data.fonts.len()];
123 let mut letters: Vec<[Option<usize>; 128]> = Vec::new();
124 for &index in rows.iter().flatten() {
125 let ch = &data.chars[index];
126 if ch.text.len() != 1 || !ch.text.as_bytes()[0].is_ascii_alphabetic() {
127 continue;
128 }
129 let font = &data.fonts[ch.font];
130 if font.flags & (1 << 6) != 0 || bold[ch.font] {
131 continue;
132 }
133 let Some(bbox) = ch.tight else {
134 continue;
135 };
136 let group = if let Some(group) = font_groups[ch.font] {
137 group
138 } else {
139 let bytes = font.name.as_bytes();
140 let name = if bytes.len() >= 7
141 && bytes[..6].iter().all(u8::is_ascii_uppercase)
142 && bytes[6] == b'+'
143 {
144 font.name[7..].to_owned()
145 } else {
146 font.name.clone()
147 };
148 let weight = font.weight.max(0);
149 let key = (name.clone(), font.flags, weight);
150 let group = if let Some(&group) = font_keys.get(&key) {
151 group
152 } else {
153 let group = groups.len();
154 groups.push((
155 name,
156 font.flags,
157 (weight > 0).then_some(weight as f64),
158 0,
159 Vec::new(),
160 ));
161 letters.push([None; 128]);
162 font_keys.insert(key, group);
163 group
164 };
165 font_groups[ch.font] = Some(group);
166 group
167 };
168 groups[group].3 += 1;
169 let letter = ch.text.as_bytes()[0] as usize;
170 let letter_group = if let Some(position) = letters[group][letter] {
171 position
172 } else {
173 let position = groups[group].4.len();
174 groups[group].4.push((ch.text.clone(), Vec::new()));
175 letters[group][letter] = Some(position);
176 position
177 };
178 let samples = &mut groups[group].4[letter_group].1;
179 if samples.len() < 3 {
180 samples.push(bbox);
181 }
182 }
183 groups
184}
185
186pub fn compound_baselines(groups: &[Vec<[f64; 4]>], em: f64) -> Vec<(bool, bool)> {
188 groups
189 .iter()
190 .map(|boxes| {
191 let shared = |edge: usize| {
192 boxes.len() <= 1
193 || boxes.iter().any(|seed| {
194 [seed[edge], seed[edge + 2]].iter().any(|baseline| {
195 boxes
196 .iter()
197 .filter(|b| {
198 (b[edge] - baseline)
199 .abs()
200 .min((b[edge + 2] - baseline).abs())
201 <= 0.35 * em
202 })
203 .count() as f64
204 >= 0.75 * boxes.len() as f64
205 })
206 })
207 };
208 (shared(0), shared(1))
209 })
210 .collect()
211}
212
213pub fn raster_axis_groups(lines: &[([f64; 4], f64, f64)]) -> Vec<(bool, Vec<usize>, f64)> {
215 let mut output = Vec::new();
216 for vertical in [true, false] {
217 let coordinate = |i: usize| {
218 if vertical {
219 lines[i].0[2]
220 } else {
221 (lines[i].0[1] + lines[i].0[3]) / 2.0
222 }
223 };
224 let position = |i: usize| {
225 if vertical {
226 (lines[i].0[1] + lines[i].0[3]) / 2.0
227 } else {
228 (lines[i].0[0] + lines[i].0[2]) / 2.0
229 }
230 };
231 let mut order: Vec<_> = (0..lines.len()).collect();
232 order.sort_by(|&a, &b| coordinate(a).partial_cmp(&coordinate(b)).unwrap());
233 let mut groups: Vec<Vec<usize>> = Vec::new();
234 for i in order {
235 let append = groups.last().is_some_and(|prior| {
236 let anchor = crate::median(prior.iter().map(|&j| coordinate(j)).collect());
237 let mut heights: Vec<_> = prior.iter().map(|&j| lines[j].1).collect();
238 heights.push(lines[i].1);
239 (coordinate(i) - anchor).abs() <= 0.4 * crate::median(heights)
240 });
241 if append {
242 groups.last_mut().unwrap().push(i);
243 } else {
244 groups.push(vec![i]);
245 }
246 }
247 for mut ticks in groups {
248 if ticks.len() < 5 {
249 continue;
250 }
251 ticks.sort_by(|&a, &b| position(a).partial_cmp(&position(b)).unwrap());
252 let em = crate::median(ticks.iter().map(|&j| lines[j].1).collect());
253 let steps: Vec<_> = ticks
254 .windows(2)
255 .map(|p| lines[p[1]].2 - lines[p[0]].2)
256 .collect();
257 let gaps: Vec<_> = ticks
258 .windows(2)
259 .map(|p| position(p[1]) - position(p[0]))
260 .collect();
261 if steps[0].abs() < 1e-8
262 || steps
263 .iter()
264 .any(|s| (s - steps[0]).abs() > 0.01 * steps[0].abs())
265 || gaps.iter().copied().reduce(f64::max).unwrap()
266 - gaps.iter().copied().reduce(f64::min).unwrap()
267 > 0.4 * em
268 || gaps.iter().any(|&g| g < em)
269 || ticks
270 .iter()
271 .any(|&j| lines[j].1 < 0.8 * em || lines[j].1 > 1.2 * em)
272 {
273 continue;
274 }
275 output.push((vertical, ticks, em));
276 }
277 }
278 output
279}
280
281pub fn isolated_path_groups(boxes: &[[f64; 4]], em: f64) -> Option<Vec<Vec<usize>>> {
283 let mut groups: Vec<Vec<usize>> = Vec::new();
284 for (i, b) in boxes.iter().enumerate() {
285 let mut retained = Vec::new();
286 let mut merged = vec![i];
287 for group in groups {
288 let mut connected = false;
289 for &j in &group {
290 let a = boxes[j];
291 let x = (b[0] - a[2]).max(a[0] - b[2]).max(0.0);
292 let y = (b[1] - a[3]).max(a[1] - b[3]).max(0.0);
293 let distance = x.hypot(y);
294 let limit = 0.35 * em;
295 if (distance - limit).abs() <= 8.0 * f64::EPSILON * distance.abs().max(limit.abs())
297 {
298 return None;
299 }
300 if distance <= limit {
301 connected = true;
302 break;
303 }
304 }
305 if connected {
306 merged.extend(group);
307 } else {
308 retained.push(group);
309 }
310 }
311 retained.push(merged);
312 groups = retained;
313 }
314 Some(groups)
315}
316
317pub fn overpainted_addresses(
319 texts: &[(usize, usize, [f64; 4])],
320 covers: &[(usize, [f64; 4])],
321) -> Vec<usize> {
322 texts
323 .iter()
324 .filter(|(order, _, b)| {
325 covers.iter().any(|(later, c)| {
326 later > order && c[0] <= b[0] && c[1] <= b[1] && c[2] >= b[2] && c[3] >= b[3]
327 })
328 })
329 .map(|(_, a, _)| *a)
330 .collect()
331}
332
333pub fn year_header_groups(lines: &[[f64; 4]], rules: &[[f64; 4]], em: f64) -> Vec<Vec<usize>> {
335 rules
336 .iter()
337 .map(|r| {
338 lines
339 .iter()
340 .enumerate()
341 .filter_map(|(i, b)| {
342 (r[0] - 0.3 * em <= b[0]
343 && b[2] <= r[2] + 0.3 * em
344 && 0.0 <= r[1] - b[3]
345 && r[1] - b[3] <= 0.8 * em)
346 .then_some(i)
347 })
348 .collect()
349 })
350 .collect()
351}