Skip to content

Commit 3e02f01

Browse files
artizclaude
andcommitted
docling-pdf: drop logo glyphs painted over each other on the flat path
The reporting invoice's flat text-layer output opened with `" ==` — its T-Mobile mark is drawn with a `TeleLogo` Type1 font, stacking the glyphs encoded as `"` and `==` on top of one another. Nothing in the font metadata gives that away: the file's *text* fonts carry the same symbolic flag, and the logo font names its glyphs `quotedbl` &c. (checked by decrypting the embedded Type1s). The geometry does: two cells with different text where one lies inside the other on the same line is impossible for prose — so both are paint, not text. drop_overpainted_cells applies exactly that containment rule (adjacent words touch but never contain each other; a same-text double-draw is left alone) and runs on pdf_text_pages only — the flat/browser path, where there is no layout model to sink the mark into a `picture` region. The ML pipeline's byte-pinned text layer (pdf_all_cells) is untouched, and the full corpus stays green: docling 117, docling-pdf 41, docling-wasm 7. Refs #157 Signed-off-by: artiz <artem.kustikov@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EY5KAiquN4YpVf2PXEQkVT
1 parent 3f70d97 commit 3e02f01

1 file changed

Lines changed: 91 additions & 1 deletion

File tree

crates/docling-pdf/src/textparse.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,9 @@ pub fn pdf_text_pages(bytes: &[u8]) -> Vec<crate::pdfium_backend::PdfPage> {
10011001
.map(|(_, pid)| {
10021002
let (w, h) = page_size(&doc, pid);
10031003
let glyphs = page_glyphs_cached(&doc, pid, &mut caches);
1004-
let (prose, words) = crate::dp_lines::line_and_word_cells(&glyphs, h, true);
1004+
let (mut prose, mut words) = crate::dp_lines::line_and_word_cells(&glyphs, h, true);
1005+
drop_overpainted_cells(&mut prose);
1006+
drop_overpainted_cells(&mut words);
10051007
crate::pdfium_backend::PdfPage {
10061008
width: w,
10071009
height: h,
@@ -1018,6 +1020,49 @@ pub fn pdf_text_pages(bytes: &[u8]) -> Vec<crate::pdfium_backend::PdfPage> {
10181020
.collect()
10191021
}
10201022

1023+
/// Drop line cells that are *painted over each other* — glyphs used as artwork.
1024+
///
1025+
/// Some generators draw their logo with a symbol font: on the reporting
1026+
/// invoice, a `TeleLogo` Type1 paints the T-Mobile mark by stacking the glyphs
1027+
/// encoded as `"` and `==` on top of one another, and the flat text-layer
1028+
/// output opened with that garbage. Nothing in the font metadata gives it away
1029+
/// (the *text* fonts in the same file are also flagged symbolic, and the logo
1030+
/// font names its glyphs `quotedbl` &c.), but the geometry does: two cells with
1031+
/// different text where one lies inside the other on the same line is
1032+
/// physically impossible for prose — ink from two words never occupies the
1033+
/// same box. Both cells of such a pair are paint, not text.
1034+
///
1035+
/// Containment (not mere overlap) keeps this narrow: adjacent words touch but
1036+
/// never contain each other, and a same-text near-duplicate (double-draw faux
1037+
/// bold) is left alone for the sanitizer's usual handling. Applied on the
1038+
/// flat/browser path only — the ML pipeline's text layer is byte-pinned by the
1039+
/// PDF corpus, and there the layout model already sinks logo marks into
1040+
/// `picture` regions.
1041+
fn drop_overpainted_cells(cells: &mut Vec<crate::pdfium_backend::TextCell>) {
1042+
let mut paint = vec![false; cells.len()];
1043+
for i in 0..cells.len() {
1044+
for j in 0..cells.len() {
1045+
if i == j || cells[i].text == cells[j].text {
1046+
continue;
1047+
}
1048+
let (a, b) = (&cells[i], &cells[j]);
1049+
// Same line band: the vertical overlap covers most of the shorter.
1050+
let vo = (a.b.min(b.b) - a.t.max(b.t)).max(0.0);
1051+
if vo < 0.6 * (a.b - a.t).min(b.b - b.t) {
1052+
continue;
1053+
}
1054+
// `a` horizontally inside `b` (with a small tolerance).
1055+
let ho = (a.r.min(b.r) - a.l.max(b.l)).max(0.0);
1056+
if ho >= 0.8 * (a.r - a.l) && (a.r - a.l) <= (b.r - b.l) {
1057+
paint[i] = true;
1058+
paint[j] = true;
1059+
}
1060+
}
1061+
}
1062+
let mut keep = paint.iter().map(|p| !p);
1063+
cells.retain(|_| keep.next().unwrap());
1064+
}
1065+
10211066
/// The text-state scalars inherited by a Form XObject when it is invoked via
10221067
/// `Do` (the PDF graphics state includes the text parameters, but not the text
10231068
/// matrices, which a form re-establishes inside its own `BT`/`ET`).
@@ -1886,3 +1931,48 @@ mod xref_repair {
18861931
);
18871932
}
18881933
}
1934+
1935+
#[cfg(test)]
1936+
mod overpainted {
1937+
use crate::pdfium_backend::TextCell;
1938+
1939+
fn cell(text: &str, l: f32, t: f32, r: f32, b: f32) -> TextCell {
1940+
TextCell {
1941+
text: text.into(),
1942+
l,
1943+
t,
1944+
r,
1945+
b,
1946+
}
1947+
}
1948+
1949+
/// The reporting invoice's logo: a `"` painted inside a `==` on one band —
1950+
/// artwork drawn with glyphs. Both cells go; the real text on the next
1951+
/// band stays.
1952+
#[test]
1953+
fn stacked_logo_glyphs_are_dropped() {
1954+
let mut cells = vec![
1955+
cell("\"", 72.7, 21.5, 86.4, 31.5),
1956+
cell("==", 59.4, 21.5, 99.6, 31.5),
1957+
cell("Herr", 65.2, 151.3, 81.7, 161.3),
1958+
];
1959+
super::drop_overpainted_cells(&mut cells);
1960+
assert_eq!(cells.len(), 1, "cells: {cells:?}");
1961+
assert_eq!(cells[0].text, "Herr");
1962+
}
1963+
1964+
/// Adjacent words on a line touch but never contain each other — prose is
1965+
/// untouched, and so is a same-text near-duplicate (double-drawn faux
1966+
/// bold), which is not evidence of artwork.
1967+
#[test]
1968+
fn prose_and_double_draw_are_kept() {
1969+
let mut cells = vec![
1970+
cell("Telefon", 354.3, 133.2, 381.5, 143.2),
1971+
cell("0676/2000", 387.3, 133.2, 428.7, 143.2),
1972+
cell("Bold", 100.0, 50.0, 130.0, 60.0),
1973+
cell("Bold", 100.3, 50.0, 130.3, 60.0),
1974+
];
1975+
super::drop_overpainted_cells(&mut cells);
1976+
assert_eq!(cells.len(), 4);
1977+
}
1978+
}

0 commit comments

Comments
 (0)