import sys import csv import argparse try: import pymupdf # noqa: F401 (probed here for a friendly error) except ImportError: sys.exit("Missing dependency. Run: pip install pymupdf") from .measure import measure from .convert import convert def report(label, value, ok=None, bad=""): """One line of the results table. ok=None means purely informational.""" tail = "" if ok is None else (" ok" if ok else f" <-- {bad}") print(f" {label + ' ':.<28} {value}{tail}") def main(): ap = argparse.ArgumentParser( prog="pinion", description="Rebuild a CSV from a spreadsheet that was printed to PDF.") ap.add_argument("pdf") ap.add_argument("out_csv") ap.add_argument("--limit", type=int, default=0, help="only convert the first N batches, for a trial run") args = ap.parse_args() M = measure(args.pdf) n_batches = min(args.limit, M["n_batches"]) if args.limit else M["n_batches"] print("CONVERTING (takes a few minutes)") print("-" * 62) doc = pymupdf.open(args.pdf) with open(args.out_csv, "w", newline="", encoding="utf-8") as fh: stats = convert(doc, M, csv.writer(fh), n_batches) doc.close() expected = n_batches * M["rows_per_page"] - 1 misplaced = stats["text_no_column"] + stats["text_no_row"] tally = {} for _row, letter, _width in stats["blackouts"]: tally[letter] = tally.get(letter, 0) + 1 print(f"\nWritten to {args.out_csv}\n") print("-" * 62) report("rows written", f"{stats['n_rows']:,}") report("rows expected", f"{expected:,}", stats["n_rows"] == expected, "MISMATCH") report("row numbers 1..N unbroken", "yes" if stats["chain_ok"] else "NO", stats["chain_ok"], "GAPS OR DUPLICATES") report("unplaced text", f"{stats['text_no_column']:,} by column, {stats['text_no_row']:,} by row", misplaced == 0, "SOME TEXT LOST") report("unplaced bars", f"{stats['bars_no_row']:,}", stats["bars_no_row"] == 0, "REDACTIONS LOST") report("disagreed numbers", f"{stats['gutter_disagreed']:,}", stats["gutter_disagreed"] == 0, "LAYOUT WRONG") report("row numbers hidden by bars", f"{stats['gutter_unreadable']:,} (placed by spacing instead)") report("cells drawn in pieces", f"{stats['pieces']:,} (rejoined; nothing lost)") print() report("blackout bars found", f"{len(stats['blackouts']):,}") report("rows affected", f"{len(set(b[0] for b in stats['blackouts'])):,}") report("which columns", tally or "none") if stats["readable"]: report("TEXT READABLE UNDER BARS", f"{len(stats['readable']):,}", False, "REDACTION IS COSMETIC, HAHA! TEXT RECOVERABLE") for row, letter, text in stats["readable"][:10]: print(f" row {row:,} column {letter}: {text!r}") else: report("text readable under bars", "none (deleted)") if n_batches * M["rows_per_page"] == 1048576: print("\n WARNING: exactly 1,048,576 rows. That's an Excel magic number") print(" The source was almost certainly truncated.") print(" Go tell the source they owe you more data.")