Repository navigation
Description
Main previously rechecked 2026-10-29 at e7178e0 (Cargo.lock unchanged, pdf-inspector 1.14.2). Current PyPI release: firecrawl-anydoc 0.2.4. The current-main result below was reproduced with the Rust core example, not a rebuilt Python wheel.
Latest release independently rechecked 2026-10-30: firecrawl-anydoc 0.2.4 installed into a new isolated Python 3.14.4 venv. Running check_rowdrop.py reproduced both noheader modes missing 008/016, while both repeated-header controls retained all 24 rows. Main remains e7178e0; its earlier source-build evidence was not rerun.
Problem
A table across three table-only PDF pages has its header on page 1 only. Conversion silently removes the first data row on pages 2 and 3. Repeating the header on each page prevents the loss. Both ruled and unruled tables show the same result.
Minimal reproduction
Save the inline stdlib-only generator below as mkpdf.py. All fixture data are synthetic.
python3 mkpdf.py noheader.pdf noheader
python3 mkpdf.py header.pdf header
python3 mkpdf.py noheader_unruled.pdf noheader_unruled
python3 mkpdf.py header_unruled.pdf header_unruled
cargo build --locked --example convert
target/debug/examples/convert noheader.pdf
Expected: all 24 rows, Widget 000 through Widget 023, exactly once.
Observed on current main:
noheader: 22 rows; missing Widget 008 and Widget 016
header: 24 rows; none missing
noheader_unruled: 22 rows; missing Widget 008 and Widget 016
header_unruled: 24 rows; none missing
No warning or conversion error was emitted.
Likely owning component
src/formats/pdf.rs delegates Markdown extraction to pdf-inspector; this PDF path bypasses anydoc's shared table/header model. In pdf-inspector 1.14.2, src/markdown/convert.rs, merge_continuation_tables (lines 598-607) unconditionally skips the first two Markdown lines of each continuation table. The first line can be data rather than a repeated header. This matches the deterministic row loss above.
A possible repair is to keep the first row when it does not match the original table header, while removing the Markdown separator. A dependency update may be the right integration route if this is already repaired upstream; newer pdf-inspector versions were not tested here.
Duplicate check (2026-10-30)
Reviewed the complete paginated anydoc issue/PR inventory: 208 unique records, including 126 open. No matching continuation-row-loss report was found in titles/bodies. Issue #173 concerns multi-column reconstruction, not this first-row loss. Merged PR #196 changes shared CSV/xlsx/ods header voting and does not affect the PDF path. Open PRs #175 and #181 update pdf-inspector for RTL extraction; they were not built or claimed to fix this bug.
Synthetic PDF generator
"""Write a synthetic multi-page ruled table PDF (stdlib only, no PII).
usage: mkpdf.py OUT MODE MODE: noheader | header | noheader_unruled | header_unruled"""
import sys
out, mode = sys.argv[1], sys.argv[2]
ruled = 'unruled' not in mode; rep_header = mode.startswith('header')
COLS = [50, 170, 260, 400, 520]; W = 560; RH = 24
HEAD = ['Item', 'Version', 'Region', 'Start', 'End']
def row(n): return [f'Widget {n:03d}', f'{1+n%3}.{n%7}', 'XX', f'{1+n%12}/{1+n%28}/2025', f'{1+(n+5)%12}/{1+n%28}/2026']
pages=[]; n=0
for p in range(3):
rows=[HEAD] if (p==0 or rep_header) else []
for _ in range(8): rows.append(row(n)); n+=1
ops=['BT /F1 10 Tf ET']; y=740
for r in rows:
if ruled:
for i,x in enumerate(COLS):
x2 = COLS[i+1] if i+1<len(COLS) else W
ops.append(f'{x-4} {y-7} {x2-x} {RH} re S')
for x,c in zip(COLS,r): ops.append(f'BT /F1 10 Tf {x} {y} Td ({c}) Tj ET')
y-=RH
pages.append('\n'.join(ops).encode())
objs=[]
def add(b): objs.append(b); return len(objs)
font=add(b'<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>')
pages_id=len(objs)+1+2*len(pages)
kids=[]
for c in pages:
cid=add(b'<< /Length %d >>\nstream\n'%len(c)+c+b'\nendstream')
kids.append(add(b'<< /Type /Page /Parent %d 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 %d 0 R >> >> /Contents %d 0 R >>'%(pages_id,font,cid)))
assert add(b'<< /Type /Pages /Kids [%s] /Count %d >>'%(b' '.join(b'%d 0 R'%k for k in kids),len(kids)))==pages_id
cat=add(b'<< /Type /Catalog /Pages %d 0 R >>'%pages_id)
buf=bytearray(b'%PDF-1.4\n'); offs=[]
for i,o in enumerate(objs,1):
offs.append(len(buf)); buf+=b'%d 0 obj\n'%i+o+b'\nendobj\n'
x=len(buf); buf+=b'xref\n0 %d\n0000000000 65535 f \n'%(len(objs)+1)
for o in offs: buf+=b'%010d 00000 n \n'%o
buf+=b'trailer\n<< /Size %d /Root %d 0 R >>\nstartxref\n%d\n%%%%EOF\n'%(len(objs)+1,cat,x)
open(out,'wb').write(buf)Python release reproduction: install firecrawl-anydoc==0.2.4, generate each fixture as above, then call anydoc.to_markdown on each PDF.
Update 2026-10-30: pdf-inspector main bcf387d (latest release v1.25.2) still has the same unconditional skip in merge_continuation_tables (src/markdown/convert.rs, the line_idx >= 2 loop near line 670). This is from reading the code only. I did not build or run the newer version.
Activity
Aschenbath commentedon Oct 31, 2026
The row loss comes from
merge_continuation_tablesin pdf-inspector. It is still on pdf-inspector main, so updating the dependency alone will not fix it. Fix proposed in firecrawl/pdf-inspector#648: a continuation page's first row is now dropped only when it matches the first page's header. With your generator,noheader.pdfnow gives all 24 rows. Once that is released, anydoc will need to bump pdf-inspector.