const EPS: f32 = 1.0e-3;
const DILATION_THRESHOLD_NORM: f32 = 0.15;
#[derive(Clone, Copy)]
struct Bl {
l: f32,
b: f32,
r: f32,
t: f32,
}
impl Bl {
fn overlaps_h(&self, o: &Bl) -> bool {
!(self.r <= o.l || o.r <= self.l)
}
fn strictly_above(&self, o: &Bl) -> bool {
(self.b + EPS) > o.t
}
fn before(&self, o: &Bl) -> bool {
if self.overlaps_h(o) {
self.b > o.b
} else {
self.l < o.l
}
}
}
fn init_ud(elems: &[Bl]) -> (Vec<Vec<usize>>, Vec<Vec<usize>>) {
let n = elems.len();
let mut up = vec![Vec::new(); n];
let mut dn = vec![Vec::new(); n];
for j in 0..n {
for i in 0..n {
if i == j {
continue;
}
if !(elems[i].strictly_above(&elems[j]) && elems[i].overlaps_h(&elems[j])) {
continue;
}
if has_interruption(elems, i, j) {
continue;
}
dn[i].push(j);
up[j].push(i);
}
}
(up, dn)
}
fn has_interruption(elems: &[Bl], i: usize, j: usize) -> bool {
for (w, ew) in elems.iter().enumerate() {
if w == i || w == j {
continue;
}
if (elems[i].overlaps_h(ew) || elems[j].overlaps_h(ew))
&& elems[i].strictly_above(ew)
&& ew.strictly_above(&elems[j])
{
return true;
}
}
false
}
fn dilate(orig: &[Bl], up: &[Vec<usize>], dn: &[Vec<usize>], page_w: f32) -> Vec<Bl> {
let th = DILATION_THRESHOLD_NORM * page_w;
let mut dil = orig.to_vec();
for i in 0..orig.len() {
let mut x0 = orig[i].l;
let mut x1 = orig[i].r;
let mut skip = false;
if let Some(&u) = up[i].first() {
let x0d = x0.min(orig[u].l);
let x1d = x1.max(orig[u].r);
if (x0 - x0d) > th || (x1d - x1) > th {
skip = true;
} else {
x0 = x0d;
x1 = x1d;
}
}
if !skip {
if let Some(&d) = dn[i].first() {
let x0d = x0.min(orig[d].l);
let x1d = x1.max(orig[d].r);
if (x0 - x0d) > th || (x1d - x1) > th {
skip = true;
} else {
x0 = x0d;
x1 = x1d;
}
}
}
if !skip {
dil[i].l = x0;
dil[i].r = x1;
}
}
dil
}
fn dfs_up(j: usize, up: &[Vec<usize>], visited: &[bool]) -> usize {
let mut k = j;
loop {
let mut moved = false;
for &ind in &up[k] {
if !visited[ind] {
k = ind;
moved = true;
break;
}
}
if !moved {
return k;
}
}
}
fn dfs_down(
start: usize,
up: &[Vec<usize>],
dn: &[Vec<usize>],
order: &mut Vec<usize>,
visited: &mut [bool],
) {
let mut stack: Vec<(usize, usize)> = vec![(start, 0)];
while let Some(&(node, off)) = stack.last() {
let mut found = false;
let mut o = off;
while o < dn[node].len() {
let k = dfs_up(dn[node][o], up, visited);
if !visited[k] {
order.push(k);
visited[k] = true;
stack.last_mut().unwrap().1 = o + 1;
stack.push((k, 0));
found = true;
break;
}
o += 1;
}
if !found {
stack.pop();
}
}
}
fn predict(orig: &[Bl], page_w: f32) -> Vec<usize> {
let n = orig.len();
if n == 0 {
return Vec::new();
}
let (up0, dn0) = init_ud(orig);
let dil = dilate(orig, &up0, &dn0, page_w);
let (up, mut dn) = init_ud(&dil);
let by_geom = |a: usize, b: usize| orig[a].before(&orig[b]);
let mut heads: Vec<usize> = (0..n).filter(|&i| up[i].is_empty()).collect();
py_sort(&mut heads, by_geom);
for children in dn.iter_mut() {
py_sort(children, by_geom);
}
let mut order = Vec::with_capacity(n);
let mut visited = vec![false; n];
for &h in &heads {
if !visited[h] {
order.push(h);
visited[h] = true;
dfs_down(h, &up, &dn, &mut order, &mut visited);
}
}
if order.len() != n {
let mut rest: Vec<usize> = (0..n).filter(|&i| !visited[i]).collect();
py_sort(&mut rest, by_geom);
order.extend(rest);
}
order
}
fn py_sort(v: &mut [usize], mut lt: impl FnMut(usize, usize) -> bool) {
let n = v.len();
if n < 2 {
return;
}
let mut run = 1;
if lt(v[1], v[0]) {
while run + 1 < n && lt(v[run + 1], v[run]) {
run += 1;
}
v[..=run].reverse();
} else {
while run + 1 < n && !lt(v[run + 1], v[run]) {
run += 1;
}
}
for i in run + 1..n {
let pivot = v[i];
let (mut lo, mut hi) = (0, i);
while lo < hi {
let mid = (lo + hi) / 2;
if lt(pivot, v[mid]) {
hi = mid;
} else {
lo = mid + 1;
}
}
v.copy_within(lo..i, lo + 1);
v[lo] = pivot;
}
}
pub fn predict_merges(
boxes: &[(f32, f32, f32, f32)],
texts: &[String],
is_text: &[bool],
is_skip: &[bool],
) -> Vec<Vec<usize>> {
let n = boxes.len();
let mut merges = vec![Vec::new(); n];
let mut curr: isize = -1;
for ind in 0..n {
if ind as isize <= curr || !is_text[ind] {
continue;
}
let mut check = ind;
loop {
let mut p1 = check + 1;
while p1 < n && is_skip[p1] {
p1 += 1;
}
if p1 < n
&& is_text[p1]
&& strictly_left_of(boxes[ind], boxes[p1])
&& ends_mergeable(&texts[check])
&& starts_mergeable(&texts[p1])
{
merges[ind].push(p1);
curr = p1 as isize;
check = p1;
} else {
break;
}
}
}
merges
}
fn strictly_left_of(a: (f32, f32, f32, f32), b: (f32, f32, f32, f32)) -> bool {
a.2 + EPS < b.0
}
fn ends_mergeable(t: &str) -> bool {
let s = t.trim_end();
s.chars().count() >= 2
&& matches!(
s.chars().next_back(),
Some('a'..='z' | ',' | '-' | '\u{ad}')
)
}
fn starts_mergeable(t: &str) -> bool {
let s = t.trim_start();
let mut ch = s.chars();
match ch.next() {
Some(c) if c.is_ascii_alphabetic() || ('\u{c0}'..='\u{24f}').contains(&c) => {
ch.next().is_some()
}
_ => false,
}
}
pub fn order_page(
boxes: &[(f32, f32, f32, f32)],
is_header: &[bool],
is_footer: &[bool],
page_w: f32,
page_h: f32,
) -> Vec<usize> {
let mut groups: [Vec<usize>; 3] = [Vec::new(), Vec::new(), Vec::new()];
for i in 0..boxes.len() {
let g = if is_header[i] {
0
} else if is_footer[i] {
2
} else {
1
};
groups[g].push(i);
}
let mut out = Vec::with_capacity(boxes.len());
for group in groups {
let bl: Vec<Bl> = group
.iter()
.map(|&i| {
let (l, t, r, b) = boxes[i];
Bl {
l,
r,
t: page_h - t,
b: page_h - b,
}
})
.collect();
for local in predict(&bl, page_w) {
out.push(group[local]);
}
}
out
}