use dxpdf::render::layout::draw_command::{DrawCommand, LayoutedPage};
use dxpdf::render::resolve_and_layout;
const MAX_BORDER_THICKNESS: f32 = 3.0;
const EPS: f32 = 0.001;
type Rect = (f32, f32, f32, f32);
const FIXTURES: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/test-files");
fn border_rects(page: &LayoutedPage) -> Vec<Rect> {
page.commands
.iter()
.filter_map(|c| match c {
DrawCommand::Rect { rect, .. } => {
let (w, h) = (rect.size.width.raw(), rect.size.height.raw());
(w.min(h) <= MAX_BORDER_THICKNESS && w.min(h) > 0.0).then(|| {
(
rect.origin.x.raw(),
rect.origin.x.raw() + w,
rect.origin.y.raw(),
rect.origin.y.raw() + h,
)
})
}
_ => None,
})
.collect()
}
fn covered(square: Rect, rects: &[Rect]) -> bool {
let (sx0, sx1, sy0, sy1) = square;
let mut xs = vec![sx0, sx1];
for (x0, x1, ..) in rects {
for x in [*x0, *x1] {
if x > sx0 && x < sx1 {
xs.push(x);
}
}
}
xs.sort_by(f32::total_cmp);
for pair in xs.windows(2) {
let (a, b) = (pair[0], pair[1]);
if b - a <= EPS {
continue;
}
let mid = (a + b) * 0.5;
let mut spans: Vec<(f32, f32)> = rects
.iter()
.filter(|(x0, x1, ..)| *x0 <= mid && mid <= *x1)
.map(|(_, _, y0, y1)| (*y0, *y1))
.collect();
spans.sort_by(|p, q| p.0.total_cmp(&q.0));
let mut reached = sy0;
for (y0, y1) in spans {
if y0 > reached + EPS {
break;
}
reached = reached.max(y1);
}
if reached < sy1 - EPS {
return false;
}
}
true
}
fn junctions(rects: &[Rect]) -> (usize, Vec<Rect>) {
let (vertical, horizontal): (Vec<_>, Vec<_>) = rects
.iter()
.copied()
.partition(|(x0, x1, y0, y1)| x1 - x0 < y1 - y0);
let mut checked = 0usize;
let mut missing: Vec<Rect> = Vec::new();
for (vx0, vx1, vy0, vy1) in vertical {
for (hx0, hx1, hy0, hy1) in horizontal.iter().copied() {
if vx1 < hx0 - EPS || vx0 > hx1 + EPS || hy1 < vy0 - EPS || hy0 > vy1 + EPS {
continue;
}
checked += 1;
let square = (vx0, vx1, hy0, hy1);
if !covered(square, rects) && !missing.contains(&square) {
missing.push(square);
}
}
}
(checked, missing)
}
fn overlaps(a: Rect, b: Rect) -> bool {
let (ax0, ax1, ay0, ay1) = a;
let (bx0, bx1, by0, by1) = b;
ax1 - bx0 > EPS && bx1 - ax0 > EPS && ay1 - by0 > EPS && by1 - ay0 > EPS
}
fn is_parallel_crowding(a: Rect, b: Rect) -> bool {
let same = |p: (f32, f32), q: (f32, f32)| (p.0 - q.0).abs() <= EPS && (p.1 - q.1).abs() <= EPS;
let (ax, ay) = ((a.0, a.1), (a.2, a.3));
let (bx, by) = ((b.0, b.1), (b.2, b.3));
let long_is_x = |r: Rect| r.1 - r.0 >= r.3 - r.2;
let stacked = |shared_is_x: bool| long_is_x(a) == shared_is_x && long_is_x(b) == shared_is_x;
(same(ax, bx) && !same(ay, by) && stacked(true))
|| (same(ay, by) && !same(ax, bx) && stacked(false))
}
fn overlapping(rects: &[Rect]) -> (usize, Vec<Rect>) {
let mut examined = 0usize;
let mut bad = Vec::new();
for (i, a) in rects.iter().copied().enumerate() {
for b in rects[i + 1..].iter().copied() {
examined += 1;
if overlaps(a, b) && !is_parallel_crowding(a, b) {
let square = (a.0.max(b.0), a.1.min(b.1), a.2.max(b.2), a.3.min(b.3));
if !bad.contains(&square) {
bad.push(square);
}
}
}
}
(examined, bad)
}
fn collinear_gaps(rects: &[Rect]) -> (usize, Vec<Rect>) {
let mut examined = 0usize;
let mut bad: Vec<Rect> = Vec::new();
let vertical = |r: &Rect| r.1 - r.0 < r.3 - r.2;
for want_vertical in [true, false] {
let group: Vec<Rect> = rects
.iter()
.copied()
.filter(|r| vertical(r) == want_vertical)
.collect();
for (i, a) in group.iter().copied().enumerate() {
for b in group[i + 1..].iter().copied() {
let (across_a, across_b, along_a, along_b) = if want_vertical {
((a.0, a.1), (b.0, b.1), (a.2, a.3), (b.2, b.3))
} else {
((a.2, a.3), (b.2, b.3), (a.0, a.1), (b.0, b.1))
};
let share = across_a.1.min(across_b.1) - across_a.0.max(across_b.0);
if share <= EPS {
continue;
}
let (lo, hi) = if along_a.0 <= along_b.0 {
(along_a, along_b)
} else {
(along_b, along_a)
};
let gap = hi.0 - lo.1;
let thickness = (across_a.1 - across_a.0).max(across_b.1 - across_b.0);
if gap <= EPS || gap >= thickness {
continue;
}
examined += 1;
let hole = if want_vertical {
(
across_a.0.max(across_b.0),
across_a.1.min(across_b.1),
lo.1,
hi.0,
)
} else {
(
lo.1,
hi.0,
across_a.0.max(across_b.0),
across_a.1.min(across_b.1),
)
};
if !covered(hole, rects) && !bad.contains(&hole) {
bad.push(hole);
}
}
}
}
(examined, bad)
}
type PageCheck = fn(&[Rect]) -> (usize, Vec<Rect>);
fn audit(path: &std::path::Path, check: PageCheck) -> (usize, usize, Vec<String>) {
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
let doc =
dxpdf::docx::parse(&bytes).unwrap_or_else(|e| panic!("parse {}: {e}", path.display()));
let (_, pages) = resolve_and_layout(doc);
let mut total_checked = 0usize;
let mut failures = Vec::new();
for (i, page) in pages.iter().enumerate() {
let (checked, missing) = check(&border_rects(page));
total_checked += checked;
if !missing.is_empty() {
failures.push(format!(
"{} page {}: {missing:?}",
path.file_name().unwrap_or_default().to_string_lossy(),
i + 1
));
}
}
(pages.len(), total_checked, failures)
}
fn corpus(dir: &str) -> Vec<std::path::PathBuf> {
let Ok(entries) = std::fs::read_dir(dir) else {
return Vec::new();
};
let mut v: Vec<_> = entries
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|e| e == "docx"))
.filter(|p| !is_word_owner_file(p))
.collect();
v.sort();
v
}
fn is_word_owner_file(p: &std::path::Path) -> bool {
p.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.starts_with("~$"))
}
fn audit_corpus(dir: &str, unit: &str, check: PageCheck) -> (usize, Vec<String>) {
let mut failures = Vec::new();
let mut checked_total = 0usize;
for path in corpus(dir) {
let (pages, checked, mut bad) = audit(&path, check);
checked_total += checked;
println!(
"{:>7} {unit} {:>3} pages {:>2} bad {}",
checked,
pages,
bad.len(),
path.file_name().unwrap_or_default().to_string_lossy()
);
failures.append(&mut bad);
}
println!(
"{dir}: {checked_total} {unit} checked, {} bad",
failures.len()
);
(checked_total, failures)
}
#[test]
fn no_committed_fixture_has_an_unpainted_border_junction() {
let (checked, failures) = audit_corpus(FIXTURES, "junctions", junctions);
assert!(checked > 100, "audit examined only {checked} junctions");
assert!(
failures.is_empty(),
"border junctions painted by nobody:\n{}",
failures.join("\n")
);
}
#[test]
fn no_local_corpus_document_has_an_unpainted_border_junction() {
if corpus("test-cases").is_empty() {
eprintln!("SKIPPED: test-cases/ not present");
return;
}
let (_, failures) = audit_corpus("test-cases", "junctions", junctions);
assert!(
failures.is_empty(),
"border junctions painted by nobody:\n{}",
failures.join("\n")
);
}
#[test]
fn no_committed_fixture_paints_a_border_square_twice() {
let (checked, failures) = audit_corpus(FIXTURES, "rect pairs", overlapping);
assert!(checked > 1000, "audit examined only {checked} pairs");
assert!(
failures.is_empty(),
"border rects overlap (intersections):\n{}",
failures.join("\n")
);
}
#[test]
fn no_committed_fixture_breaks_a_border_line_by_less_than_its_width() {
let (_, failures) = audit_corpus(FIXTURES, "collinear gaps", collinear_gaps);
assert!(
failures.is_empty(),
"border lines broken by a sub-width gap:\n{}",
failures.join("\n")
);
}
#[test]
fn no_local_corpus_document_breaks_a_border_line_by_less_than_its_width() {
if corpus("test-cases").is_empty() {
eprintln!("SKIPPED: test-cases/ not present");
return;
}
let (_, failures) = audit_corpus("test-cases", "collinear gaps", collinear_gaps);
assert!(
failures.is_empty(),
"border lines broken by a sub-width gap:\n{}",
failures.join("\n")
);
}
#[test]
fn ip05_trenches_has_no_unpainted_border_junction() {
let path = std::path::Path::new("test-cases/IP 05 Trenches_Bad Harzburg_03-06-2026.docx");
if !path.exists() {
eprintln!("SKIPPED: {} not present", path.display());
return;
}
let bytes = std::fs::read(path).expect("read fixture");
let doc = dxpdf::docx::parse(&bytes).expect("parse fixture");
let (_, pages) = resolve_and_layout(doc);
assert!(!pages.is_empty(), "expected at least one page");
let mut total_checked = 0usize;
let mut failures: Vec<String> = Vec::new();
for (i, page) in pages.iter().enumerate() {
let (checked, missing) = junctions(&border_rects(page));
total_checked += checked;
if !missing.is_empty() {
failures.push(format!("page {}: {missing:?}", i + 1));
}
}
assert!(
total_checked > 100,
"expected the audit to find junctions to check, got {total_checked}"
);
assert!(
failures.is_empty(),
"border junctions painted by nobody:\n{}",
failures.join("\n")
);
}