Skip to content

Commit f88efbf

Browse files
artizclaude
andcommitted
feat(pdf): band-aware reading order for two-column pages
Full-width regions (title, wide figures/tables spanning most of the page) now break the two-column flow into horizontal bands: within a band the left column reads fully then the right, and a full-width region reads after the band above it and before the band below. Previously a full-width title was assigned to a column by its center and emitted in the middle of the page (the DocLayNet title landed 12th instead of first). Band break requires spanning >70% of the width (a merely wide column region doesn't count), which keeps it from mis-ordering dense layouts. Net −12 diff-lines (normal_4pages 86→78, right_to_left_03 70→66, 2206 276→274); the 4 exact PDFs are unchanged. The table papers remain dominated by finer reading-order + table-content differences. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dee04c5 commit f88efbf

1 file changed

Lines changed: 29 additions & 3 deletions

File tree

crates/fleischwolf-pdf/src/assemble.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,37 @@ fn order_regions<T>(items: &mut [T], page_w: f32, reg: impl Fn(&T) -> &Region) {
7373
&& items.iter().any(|t| reg(t).r <= cx)
7474
&& items.iter().any(|t| reg(t).l >= cx);
7575
if two_col {
76+
// Full-width regions (title, figures, wide tables spanning both columns)
77+
// break the two-column flow into horizontal bands: within a band the left
78+
// column reads fully then the right, and a full-width region reads after
79+
// the band above it and before the band below. Band index = number of
80+
// full-width regions above a region's top; column 1=left, 2=right,
81+
// 3=full-width (so it sorts after that band's columns).
82+
// Only a region spanning *most* of the page width is a band break (a
83+
// title, a full-width figure/table) — a merely wide column region is not.
84+
let full_band = page_w * 0.2;
85+
let is_full = |r: &Region| r.l < cx - full_band && r.r > cx + full_band;
86+
let full_tops: Vec<f32> = items
87+
.iter()
88+
.map(&reg)
89+
.filter(|r| is_full(r))
90+
.map(|r| r.t)
91+
.collect();
92+
let key = |r: &Region| -> (usize, u8) {
93+
let bnd = full_tops.iter().filter(|&&ft| ft < r.t - 1.0).count();
94+
let col = if is_full(r) {
95+
3
96+
} else if (r.l + r.r) / 2.0 >= cx {
97+
2
98+
} else {
99+
1
100+
};
101+
(bnd, col)
102+
};
76103
items.sort_by(|a, b| {
77104
let (a, b) = (reg(a), reg(b));
78-
let ca = ((a.l + a.r) / 2.0) >= cx;
79-
let cb = ((b.l + b.r) / 2.0) >= cx;
80-
ca.cmp(&cb)
105+
key(a)
106+
.cmp(&key(b))
81107
.then(a.t.total_cmp(&b.t))
82108
.then(a.l.total_cmp(&b.l))
83109
});

0 commit comments

Comments
 (0)