const EPS: f32 = 1.0e-3;
const DILATION_THRESHOLD_NORM: f32 = 0.15;
const ROW_IOU: f32 = 0.8;
#[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 strictly_left_of(&self, o: &Bl) -> bool {
(self.r + EPS) < o.l
}
fn overlaps_v_iou(&self, o: &Bl, iou: f32) -> bool {
if self.t <= o.b || o.t <= self.b {
return false;
}
let (u0, u1) = (self.b.min(o.b), self.t.max(o.t));
let (i0, i1) = (self.b.max(o.b), self.t.min(o.t));
(i1 - i0) / (u1 - u0) > iou
}
fn before(&self, o: &Bl) -> bool {
if self.overlaps_h(o) {
self.b > o.b
} else {
self.l < o.l
}
}
}
fn init_l2r(elems: &[Bl], cids: &[usize]) -> (Vec<Option<usize>>, Vec<Option<usize>>) {
let n = elems.len();
let mut l2r = vec![None; n];
let mut r2l = vec![None; n];
for i in 0..n {
for j in 0..n {
if cids[i] + 1 == cids[j]
&& elems[i].strictly_left_of(&elems[j])
&& elems[i].overlaps_v_iou(&elems[j], ROW_IOU)
{
l2r[i] = Some(j);
r2l[j] = Some(i);
}
}
}
(l2r, r2l)
}
fn init_ud(
elems: &[Bl],
l2r: &[Option<usize>],
r2l: &[Option<usize>],
) -> (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 {
if let Some(left) = r2l[j] {
if !dn[left].contains(&j) {
dn[left].push(j);
}
if !up[j].contains(&left) {
up[j].push(left);
}
}
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;
}
let mut k = i;
while let Some(next) = l2r[k] {
k = next;
}
dn[k].push(j);
up[j].push(k);
}
}
(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], cids: &[usize], page_w: f32) -> Vec<usize> {
let n = orig.len();
if n == 0 {
return Vec::new();
}
let (l2r, r2l) = init_l2r(orig, cids);
let (up0, dn0) = init_ud(orig, &l2r, &r2l);
let dil = dilate(orig, &up0, &dn0, page_w);
let (up, mut dn) = init_ud(&dil, &l2r, &r2l);
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)],
cids: &[usize],
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();
let group_cids: Vec<usize> = group.iter().map(|&i| cids[i]).collect();
for local in predict(&bl, &group_cids, page_w) {
out.push(group[local]);
}
}
out
}
#[cfg(test)]
mod tests {
use super::order_page;
fn copyright_rows() -> Vec<(f32, f32, f32, f32)> {
vec![
(36.9, 575.5, 85.3, 583.2), (258.2, 575.5, 300.7, 583.2), (36.9, 588.5, 181.5, 596.2), (36.9, 601.5, 388.4, 609.2), ]
}
#[test]
fn a_right_hand_item_reads_before_the_next_row_when_it_follows_in_source_order() {
let boxes = copyright_rows();
let flags = vec![false; boxes.len()];
let order = order_page(&boxes, &[10, 11, 12, 13], &flags, &flags, 517.6, 666.4);
assert_eq!(order, [0, 1, 2, 3]);
let order = order_page(&boxes, &[10, 20, 11, 12], &flags, &flags, 517.6, 666.4);
assert_eq!(order, [0, 2, 3, 1]);
}
#[test]
fn a_row_link_needs_vertical_overlap() {
let boxes = vec![
(36.9, 575.5, 85.3, 583.2),
(258.2, 590.0, 300.7, 597.7), (36.9, 601.5, 388.4, 609.2),
];
let flags = vec![false; 3];
let linked = order_page(&boxes, &[0, 1, 2], &flags, &flags, 517.6, 666.4);
let unlinked = order_page(&boxes, &[0, 5, 1], &flags, &flags, 517.6, 666.4);
assert_eq!(linked, unlinked);
assert_eq!(linked, [0, 1, 2]);
}
}