1pub type NoiseLine = (
5 [f64; 4],
6 f64,
7 bool,
8 bool,
9 usize,
10 bool,
11 bool,
12 bool,
13 bool,
14 bool,
15);
16
17pub fn numeric_noise_candidates(
19 lines: &[NoiseLine],
20 visual: &[[f64; 4]],
21) -> Vec<(usize, Vec<usize>)> {
22 let mut groups = std::collections::HashMap::<usize, Vec<usize>>::new();
23 for (i, line) in lines.iter().enumerate() {
24 groups.entry(line.4).or_default().push(i);
25 }
26 let mut result = Vec::new();
27 for (i, &(bbox, height, _, _, font, _, _, _, _, candidate)) in lines.iter().enumerate() {
28 if !candidate {
29 continue;
30 }
31 let gutter = visual.iter().any(|a| {
32 visual.iter().any(|b| {
33 a[2] + 0.2 * height <= bbox[0]
34 && bbox[2] <= b[0] - 0.2 * height
35 && a[1].max(b[1]) <= bbox[1]
36 && bbox[3] <= a[3].min(b[3])
37 && a[3] - a[1] >= 8.0 * height
38 && b[3] - b[1] >= 8.0 * height
39 })
40 });
41 if gutter {
42 let refs: Vec<_> = lines
43 .iter()
44 .enumerate()
45 .filter(|(_, p)| p.4 != font && p.6)
46 .map(|(j, _)| j)
47 .collect();
48 if refs.len() >= 3 {
49 result.push((i, refs));
50 continue;
51 }
52 }
53 let same = &groups[&font];
54 if same.len() > 4 || same.iter().any(|&j| !lines[j].5) {
56 continue;
57 }
58 let prose: Vec<_> = lines
59 .iter()
60 .enumerate()
61 .filter(|(_, p)| {
62 p.2 && p.3
63 && p.4 != font
64 && 0.7 * p.1 <= height
65 && height <= 1.05 * p.1
66 && p.7
67 && p.0[0] <= bbox[0]
68 && bbox[2] <= p.0[2]
69 && ((p.0[1] + p.0[3] - bbox[1] - bbox[3]) / 2.0).abs() <= 3.0 * p.1
70 })
71 .map(|(j, _)| j)
72 .collect();
73 if prose.len() >= 2 {
74 let mut union = lines[prose[0]].0;
75 for &j in &prose[1..] {
76 let b = lines[j].0;
77 union = [
78 union[0].min(b[0]),
79 union[1].min(b[1]),
80 union[2].max(b[2]),
81 union[3].max(b[3]),
82 ];
83 }
84 let area = (bbox[2] - bbox[0]).max(0.0) * (bbox[3] - bbox[1]).max(0.0);
85 let overlap = (bbox[2].min(union[2]) - bbox[0].max(union[0])).max(0.0)
86 * (bbox[3].min(union[3]) - bbox[1].max(union[1])).max(0.0);
87 if area > 0.0 && overlap / area >= 0.95 {
88 result.push((i, prose));
89 continue;
90 }
91 }
92 let mut entries: Vec<_> = lines
93 .iter()
94 .enumerate()
95 .filter(|(_, p)| {
96 p.2 && p.3
97 && p.4 != font
98 && height <= 0.5 * p.1
99 && p.8
100 && p.0[0] <= bbox[0]
101 && bbox[2] <= p.0[2]
102 })
103 .map(|(j, _)| j)
104 .collect();
105 if entries.len() >= 3 {
106 let em = entries
107 .iter()
108 .map(|&j| lines[j].1)
109 .reduce(|a, b| if b < a { b } else { a })
110 .unwrap();
111 entries.sort_by(|&a, &b| lines[a].0[1].partial_cmp(&lines[b].0[1]).unwrap());
112 let left_max = entries
113 .iter()
114 .map(|&j| lines[j].0[0])
115 .reduce(|a, b| if b > a { b } else { a })
116 .unwrap();
117 let left_min = entries
118 .iter()
119 .map(|&j| lines[j].0[0])
120 .reduce(|a, b| if b < a { b } else { a })
121 .unwrap();
122 if left_max - left_min <= 0.3 * em
123 && entries.windows(2).any(|p| {
124 lines[p[0]].0[3] + 0.1 * em <= bbox[1] && bbox[3] <= lines[p[1]].0[1] - 0.1 * em
125 })
126 {
127 result.push((i, entries));
128 }
129 }
130 }
131 result
132}
133
134pub type FormulaLine = ([f64; 4], [f64; 4], f64, i64, i64);
136
137pub fn detached_formula_neighbors(
139 boxes: &[[f64; 4]],
140 operators: &[bool],
141 em: f64,
142) -> Vec<Vec<usize>> {
143 let mut neighbors = vec![Vec::new(); boxes.len()];
144 for (i, a) in boxes.iter().enumerate() {
145 for (j, b) in boxes.iter().enumerate() {
146 if i == j {
147 continue;
148 }
149 if a[2] - a[0] > 3.0 * em
150 && b[2] - b[0] > 3.0 * em
151 && operators[i]
152 && operators[j]
153 && ((a[1] + a[3]) / 2.0 - (b[1] + b[3]) / 2.0).abs() > 0.75 * em
154 {
155 continue;
156 }
157 let xgap = (a[0] - b[2]).max(b[0] - a[2]).max(0.0);
158 let ygap = (a[1] - b[3]).max(b[1] - a[3]).max(0.0);
159 let shorter_x = (a[2] - a[0]).min(b[2] - b[0]);
160 let shorter_y = (a[3] - a[1]).min(b[3] - b[1]);
161 let xoverlap = if shorter_x > 0.0 {
162 (a[2].min(b[2]) - a[0].max(b[0])).max(0.0) / shorter_x
163 } else {
164 0.0
165 };
166 let yoverlap = if shorter_y > 0.0 {
167 (a[3].min(b[3]) - a[1].max(b[1])).max(0.0) / shorter_y
168 } else {
169 0.0
170 };
171 if ygap <= 0.85 * em && xoverlap > 0.1 || xgap <= 2.0 * em && yoverlap >= 0.2 {
172 neighbors[i].push(j);
173 }
174 }
175 }
176 neighbors
177}
178
179fn formula_connected(a: &FormulaLine, b: &FormulaLine, tables: &[[f64; 4]]) -> bool {
181 if a.3 != b.3 {
182 return false;
183 }
184 let ac = ((a.1[0] + a.1[2]) / 2.0, (a.1[1] + a.1[3]) / 2.0);
185 let bc = ((b.1[0] + b.1[2]) / 2.0, (b.1[1] + b.1[3]) / 2.0);
186 let connector = [
187 ac.0.min(bc.0) - 0.1,
188 ac.1.min(bc.1) - 0.1,
189 ac.0.max(bc.0) + 0.1,
190 ac.1.max(bc.1) + 0.1,
191 ];
192 if tables.iter().any(|t| {
193 connector[0] < t[2] && t[0] < connector[2] && connector[1] < t[3] && t[1] < connector[3]
194 }) {
195 return false;
196 }
197 let height = a.2.max(b.2);
198 let vshort = (a.0[3] - a.0[1]).min(b.0[3] - b.0[1]);
199 let vo = if vshort > 0.0 {
200 (a.0[3].min(b.0[3]) - a.0[1].max(b.0[1])).max(0.0) / vshort
201 } else {
202 0.0
203 };
204 let vg = (a.0[1] - b.0[3]).max(b.0[1] - a.0[3]).max(0.0);
205 if vo < 0.2 && vg > 0.6 * height {
206 return false;
207 }
208 let hshort = (a.0[2] - a.0[0]).min(b.0[2] - b.0[0]);
209 let ho = if hshort > 0.0 {
210 (a.0[2].min(b.0[2]) - a.0[0].max(b.0[0])).max(0.0) / hshort
211 } else {
212 0.0
213 };
214 let hg = (a.0[0] - b.0[2]).max(b.0[0] - a.0[2]).max(0.0);
215 ho > 0.0 || hg <= 1.5 * height
216}
217
218pub fn grow_formula_component(
220 lines: &[FormulaLine],
221 seed_count: usize,
222 tables: &[[f64; 4]],
223) -> Vec<usize> {
224 let mut members: Vec<_> = (0..seed_count.min(lines.len())).collect();
225 let mut sources: std::collections::HashSet<_> = members.iter().map(|&i| lines[i].4).collect();
226 loop {
227 let before = members.len();
228 for i in seed_count..lines.len() {
229 if !sources.contains(&lines[i].4)
230 && members
231 .iter()
232 .any(|&j| formula_connected(&lines[j], &lines[i], tables))
233 {
234 members.push(i);
235 sources.insert(lines[i].4);
236 }
237 }
238 if before == members.len() {
239 break;
240 }
241 }
242 members.into_iter().skip(seed_count).collect()
243}
244
245pub type TailLine = (
247 [f64; 4],
248 f64,
249 Option<usize>,
250 f64,
251 Option<f64>,
252 Option<f64>,
253 bool,
254 Option<i64>,
255 i64,
256 usize,
257);
258
259fn tail_accepts(a: &TailLine, b: &TailLine, lane: (f64, f64), median: f64) -> bool {
261 let height = a.1.max(b.1).max(median);
262 let width = (lane.1 - lane.0).max(0.1);
263 let fonts = a.2.is_some() && b.2.is_some() && a.3 >= 0.75 && b.3 >= 0.75 && a.2 != b.2;
264 let weights = match (a.4, b.4) {
265 (Some(x), Some(y)) => (x - y).abs() >= 100.0 && x.max(y) >= 1.15 * x.min(y),
266 _ => false,
267 };
268 if a.0[2] - a.0[0] < 0.65 * width
269 || b.0[2] - b.0[0] > 0.85 * width
270 || b.0[0] < lane.0 - 0.75 * height
271 || b.0[2] > lane.1 + 0.75 * height
272 || (b.0[0] - a.0[0]).abs() > 0.75 * height
273 || fonts
274 || weights
275 {
276 return false;
277 }
278 let mut gap = if a.6 {
279 (b.0[1] - a.0[3]).max(-0.25 * a.1)
280 } else {
281 b.0[1] - (a.0[1] + a.1)
282 };
283 if let (Some(x), Some(y)) = (a.5, b.5) {
284 if y > x {
285 let pitch = y - x;
286 if 0.5 * a.1.min(b.1) <= pitch && pitch <= 3.0 * a.1.max(b.1) {
287 gap = pitch - a.1;
288 }
289 }
290 }
291 if gap < -0.25 * height || gap > 0.9 * height {
292 return false;
293 }
294 if let (Some(x), Some(y)) = (a.7, b.7) {
295 let delta = i128::from(y) - i128::from(x);
296 if delta <= 0 || delta > 2 {
297 return false;
298 }
299 }
300 true
301}
302
303pub fn short_tail_destinations(
305 lines: &[TailLine],
306 lanes: &[(f64, f64)],
307 median: f64,
308) -> Vec<(usize, usize)> {
309 let mut order: Vec<_> = (0..lines.len()).collect();
310 order.sort_by(|&a, &b| {
311 lines[a].0[1]
312 .partial_cmp(&lines[b].0[1])
313 .unwrap()
314 .then_with(|| lines[a].0[0].partial_cmp(&lines[b].0[0]).unwrap())
315 .then_with(|| lines[a].8.cmp(&lines[b].8))
316 });
317 let mut previous: Vec<Option<usize>> = vec![None; lanes.len()];
318 let mut moves = Vec::new();
319 let mut cursor = 0;
320 while cursor < order.len() {
321 let mut end = cursor + 1;
322 while end < order.len() && lines[order[end]].0[1] == lines[order[cursor]].0[1] {
323 end += 1;
324 }
325 let mut settled = Vec::new();
326 for &i in &order[cursor..end] {
327 let matches: Vec<_> = previous
328 .iter()
329 .enumerate()
330 .filter(|(lane, p)| {
331 p.is_some_and(|j| tail_accepts(&lines[j], &lines[i], lanes[*lane], median))
332 })
333 .map(|(lane, _)| lane)
334 .collect();
335 let target = if matches.len() == 1 {
336 matches[0]
337 } else {
338 lines[i].9
339 };
340 if target != lines[i].9 {
341 moves.push((i, target));
342 }
343 settled.push((i, target));
344 }
345 for (i, target) in settled {
346 let newer = previous[target].is_none_or(|j| {
347 lines[i].0[1] > lines[j].0[1]
348 || lines[i].0[1] == lines[j].0[1]
349 && (lines[i].0[0] > lines[j].0[0]
350 || lines[i].0[0] == lines[j].0[0] && lines[i].8 < lines[j].8)
351 });
352 if newer {
353 previous[target] = Some(i);
354 }
355 }
356 cursor = end;
357 }
358 moves
359}
360
361pub fn fragment_row_groups<E>(
363 items: &[(f64, f64, Option<i64>)],
364 tolerance: f64,
365 mut mean: impl FnMut(&[usize]) -> Result<f64, E>,
366) -> Result<Vec<Vec<usize>>, E> {
367 let mut seeds = Vec::<Vec<usize>>::new();
368 let mut native = std::collections::HashMap::new();
369 let mut geometric = Vec::new();
370 for (i, item) in items.iter().enumerate() {
371 if let Some(id) = item.2 {
372 let next = seeds.len();
373 let group = *native.entry(id).or_insert_with(|| {
374 seeds.push(Vec::new());
375 next
376 });
377 seeds[group].push(i);
378 } else {
379 geometric.push(vec![i]);
380 }
381 }
382 seeds.extend(geometric);
383 let mut prepared = Vec::new();
384 for group in seeds {
385 let center = mean(&group)?;
386 let left = group
387 .iter()
388 .map(|&i| items[i].1)
389 .reduce(|a, b| if b < a { b } else { a })
390 .unwrap();
391 prepared.push((group, center, left));
392 }
393 prepared.sort_by(|a, b| {
394 a.1.partial_cmp(&b.1)
395 .unwrap()
396 .then_with(|| a.2.partial_cmp(&b.2).unwrap())
397 });
398 let mut groups = Vec::<Vec<usize>>::new();
399 let mut centers = Vec::<f64>::new();
400 for (seed, center, _) in prepared {
401 if let Some(target) = centers
402 .iter()
403 .position(|&other| (center - other).abs() <= tolerance)
404 {
405 groups[target].extend(seed);
406 centers[target] = mean(&groups[target])?;
407 } else {
408 groups.push(seed);
409 centers.push(center);
410 }
411 }
412 Ok(groups)
413}
414
415pub type ProseRuleDraft = (f64, f64, f64, [f64; 4], Vec<i64>);
417pub type ProseRuleLine = ([f64; 4], i64, bool);
419
420pub fn prose_rule_conflicts(
422 drafts: &[ProseRuleDraft],
423 grids: &[[f64; 4]],
424 rules: &[([f64; 4], bool)],
425 lines: &[ProseRuleLine],
426) -> Vec<bool> {
427 let mut geometry = std::collections::HashMap::<(usize, u64), Option<Vec<(i64, f64)>>>::new();
428 drafts
429 .iter()
430 .map(|draft| {
431 let (em, first, last, boundary, members) = draft;
432 let members: std::collections::HashSet<_> = members.iter().copied().collect();
433 for (grid_index, core) in grids.iter().enumerate() {
434 let width = core[2] - core[0];
435 let height = core[3] - core[1];
436 if width < 8.0 * em
437 || height < 4.0 * em
438 || core[1] - first < 5.0 * em
439 || !(core[1] - em <= *last && *last <= core[3] + em)
440 || overlap_ratio(boundary, core, 0) < 0.9
441 {
442 continue;
443 }
444 let evidence = geometry
445 .entry((grid_index, em.to_bits()))
446 .or_insert_with(|| {
447 let mut verticals: Vec<_> = rules
448 .iter()
449 .filter(|(b, vertical)| {
450 let x = (b[0] + b[2]) / 2.0;
451 *vertical
452 && core[0] - 0.5 * em <= x
453 && x <= core[2] + 0.5 * em
454 && b[1] <= core[1] + 0.5 * em
455 && b[3] >= core[3] - 0.5 * em
456 })
457 .map(|(b, _)| (b[0] + b[2]) / 2.0)
458 .collect();
459 verticals.sort_by(|a, b| a.partial_cmp(b).unwrap());
460 let mut tracks = Vec::new();
461 for x in verticals {
462 if tracks.last().is_none_or(|&last| x - last > 0.3 * em) {
463 tracks.push(x);
464 }
465 }
466 if tracks.len() < 3
467 || ![core[1], core[3]].into_iter().all(|y| {
468 rules.iter().any(|(b, vertical)| {
469 !*vertical
470 && overlap_ratio(b, core, 0) >= 0.9
471 && ((b[1] + b[3]) / 2.0 - y).abs() <= 0.5 * em
472 })
473 })
474 {
475 return None;
476 }
477 Some(
478 lines
479 .iter()
480 .filter(|(b, _, eligible)| {
481 *eligible
482 && b[2] - b[0] >= 0.65 * width
483 && overlap_ratio(b, core, 0) >= 0.9
484 })
485 .map(|(b, source, _)| (*source, (b[1] + b[3]) / 2.0))
486 .collect(),
487 )
488 });
489 let Some(prose) = evidence else {
490 continue;
491 };
492 let mut centers: Vec<_> = prose
493 .iter()
494 .filter(|(source, y)| {
495 members.contains(source) && first + 0.5 * em < *y && *y < core[1] - em
496 })
497 .map(|(_, y)| *y)
498 .collect();
499 centers.sort_by(|a, b| a.partial_cmp(b).unwrap());
500 let mut rows = Vec::new();
501 for y in centers {
502 if rows.last().is_none_or(|&last| y - last > 0.6 * em) {
503 rows.push(y);
504 }
505 }
506 if rows.len() >= 3 {
507 return true;
508 }
509 }
510 false
511 })
512 .collect()
513}
514
515fn overlap_ratio(a: &[f64; 4], b: &[f64; 4], axis: usize) -> f64 {
517 let overlap = (a[axis + 2].min(b[axis + 2]) - a[axis].max(b[axis])).max(0.0);
518 let shorter = (a[axis + 2] - a[axis]).min(b[axis + 2] - b[axis]);
519 if shorter > 0.0 {
520 overlap / shorter
521 } else {
522 0.0
523 }
524}
525
526pub fn prose_rule_eligible(drafts: &[ProseRuleDraft], grids: &[[f64; 4]]) -> Vec<bool> {
528 drafts
529 .iter()
530 .map(|(em, first, last, boundary, _)| {
531 grids.iter().any(|core| {
532 core[2] - core[0] >= 8.0 * em
533 && core[3] - core[1] >= 4.0 * em
534 && core[1] - first >= 5.0 * em
535 && core[1] - em <= *last
536 && *last <= core[3] + em
537 && overlap_ratio(boundary, core, 0) >= 0.9
538 })
539 })
540 .collect()
541}
542
543pub type LaneGeometry = ([f64; 4], f64, bool, bool);
545
546pub fn supported_lane_intervals(
548 lines: &[LaneGeometry],
549 width: f64,
550 median: f64,
551) -> Vec<(f64, f64, usize)> {
552 let tolerance = 3.0f64.max(0.75 * median);
553 let mut regular: Vec<_> = (0..lines.len())
554 .filter(|&i| {
555 lines[i].2 && lines[i].0[2] - lines[i].0[0] >= (4.0 * lines[i].1).max(0.15 * width)
556 })
557 .collect();
558 regular.sort_by(|&a, &b| lines[a].0[0].partial_cmp(&lines[b].0[0]).unwrap());
559 let mut clusters = Vec::<Vec<usize>>::new();
560 for &index in ®ular {
561 if let Some(cluster) = clusters.last_mut() {
562 let n = cluster.len();
563 let center = if n % 2 == 1 {
564 lines[cluster[n / 2]].0[0]
565 } else {
566 (lines[cluster[n / 2 - 1]].0[0] + lines[cluster[n / 2]].0[0]) / 2.0
567 };
568 if (lines[index].0[0] - center).abs() <= tolerance {
569 cluster.push(index);
570 continue;
571 }
572 }
573 clusters.push(vec![index]);
574 }
575 let mut supported = Vec::new();
576 for cluster in clusters.iter().filter(|c| c.len() >= 3) {
577 let left = crate::median(cluster.iter().map(|&i| lines[i].0[0]).collect());
578 let right = crate::median(cluster.iter().map(|&i| lines[i].0[2]).collect());
579 supported.push((left, right, cluster.len()));
580 }
581 supported.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
582 let mut filtered: Vec<(f64, f64, usize)> = Vec::new();
583 for interval in supported {
584 if let Some(previous) = filtered.last_mut() {
585 if interval.0 - previous.1 >= 6.0f64.max(0.75 * median) {
586 filtered.push(interval);
587 } else if interval.2 > previous.2 {
588 *previous = interval;
589 }
590 } else {
591 filtered.push(interval);
592 }
593 }
594 if filtered.is_empty() && !lines.is_empty() {
595 let source: Vec<_> = if regular.is_empty() {
596 (0..lines.len()).collect()
597 } else {
598 regular
599 };
600 let left = source
601 .iter()
602 .map(|&i| lines[i].0[0])
603 .reduce(|a, b| if b < a { b } else { a })
604 .unwrap();
605 let right = source
606 .iter()
607 .map(|&i| lines[i].0[2])
608 .reduce(|a, b| if b > a { b } else { a })
609 .unwrap();
610 filtered.push((left, right, source.len()));
611 }
612 filtered
613}
614
615pub fn lane_assignments(
617 lines: &[LaneGeometry],
618 lanes: &[(f64, f64)],
619 tolerance: f64,
620 nested: Option<(f64, f64)>,
621) -> Vec<Option<usize>> {
622 let mut ordered: Vec<_> = (0..lanes.len()).collect();
623 ordered.sort_by(|&a, &b| lanes[a].0.partial_cmp(&lanes[b].0).unwrap());
624 lines
625 .iter()
626 .map(|line| {
627 let bbox = line.0;
628 if nested.is_some_and(|(top, bottom)| {
629 line.3
630 || !((top <= (bbox[1] + bbox[3]) / 2.0)
631 && ((bbox[1] + bbox[3]) / 2.0 <= bottom))
632 }) {
633 return Some(lanes.len());
634 }
635 let width = 0.1f64.max(bbox[2] - bbox[0]);
636 let mut best = 0;
637 let mut first = -1.0;
638 let mut second = -1.0;
639 for (i, &(left, right)) in lanes.iter().enumerate() {
640 let coverage = (bbox[2].min(right) - bbox[0].max(left)).max(0.0) / width;
641 if coverage > first {
642 second = first;
643 first = coverage;
644 best = i;
645 } else if coverage > second {
646 second = coverage;
647 }
648 }
649 let position = ordered.iter().position(|&i| i == best).unwrap();
650 let fit = !(position > 0 && bbox[0] < lanes[ordered[position - 1]].1 - tolerance
651 || position + 1 < ordered.len()
652 && bbox[2] > lanes[ordered[position + 1]].0 - 0.25 * tolerance)
653 && lanes[best].0 - tolerance <= (bbox[0] + bbox[2]) / 2.0
654 && (bbox[0] + bbox[2]) / 2.0
655 <= lanes[best].1 + tolerance.max(bbox[2] - lanes[best].1);
656 if second >= 0.2 && !fit {
657 None
658 } else if fit
659 || if nested.is_some() {
660 first >= 0.5
661 } else {
662 lanes.len() == 1
663 }
664 {
665 Some(best)
666 } else {
667 None
668 }
669 })
670 .collect()
671}
672
673pub fn fraction_members(chars: &[([f64; 4], bool)], rules: &[[f64; 4]], scale: f64) -> Vec<usize> {
675 let mut members = vec![false; chars.len()];
676 for rule in rules {
677 let width = rule[2] - rule[0];
678 let height = rule[3] - rule[1];
679 if width < 2.0f64.max(0.45 * scale)
680 || width > 12.0 * scale
681 || height > 1.25f64.max(0.25 * scale)
682 || width > 8.0 * scale
683 {
684 continue;
685 }
686 let y = (rule[1] + rule[3]) / 2.0;
687 let mut above = Vec::new();
688 let mut below = Vec::new();
689 for (i, &(b, alnum)) in chars.iter().enumerate() {
690 if !alnum || ((b[1] + b[3]) / 2.0 - y).abs() > 2.25 * scale {
691 continue;
692 }
693 let overlap = 0.0f64.max(b[2].min(rule[2]) - b[0].max(rule[0]));
694 if !(overlap > 0.0
695 || rule[0] - 0.25 * scale <= (b[0] + b[2]) / 2.0
696 && (b[0] + b[2]) / 2.0 <= rule[2] + 0.25 * scale)
697 {
698 continue;
699 }
700 if b[3] <= y + 0.2 * scale && y - b[3] <= 1.75 * scale {
701 above.push(i);
702 }
703 if b[1] >= y - 0.2 * scale && b[1] - y <= 1.75 * scale {
704 below.push(i);
705 }
706 }
707 if !above.is_empty() && !below.is_empty() {
708 for i in above.into_iter().chain(below) {
709 members[i] = true;
710 }
711 }
712 }
713 members
714 .into_iter()
715 .enumerate()
716 .filter_map(|(i, v)| v.then_some(i))
717 .collect()
718}