"""Reading what the PDF actually drew on a page. Small views onto the raw page. Everything downstream is built from these. The key one is all_runs(): it returns each piece of text AS THE PDF DREW IT, one run per spreadsheet cell, which is what dodges Trap 1 -- overlapping cells stay separate because the PDF drew them separately. """ def all_runs(page): """Every piece of text on the page, one run per spreadsheet cell.""" return [r for r in page.get_texttrace() if r["chars"]] def run_start(run): """Where a piece of text begins, as (across, down).""" return run["chars"][0][2] def run_text(run): """The text of a run. Word Wrap would really screw this up; don't let the PDF boomers know.""" return "".join(chr(c[0]) for c in run["chars"]) def solid_regions(page): """Every solid filled region, taken as a whole. A blackout bar can be a plain rectangle OR a filled four-sided path -- visually identical, different in the PDF -- so we measure whole regions, never segments.""" return [d["rect"] for d in page.get_drawings() if d["type"] == "f"] def vertical_rules(page): """x-positions of thin vertical lines: <1.2pt wide, >8pt tall, drawn either as skinny rectangles or as literal line segments.""" xs = set() for d in page.get_drawings(): for item in d["items"]: if item[0] == "re" and item[1].width < 1.2 and item[1].height > 8: xs.add(round(item[1].x0 + item[1].width / 2, 1)) elif item[0] == "l": a, b = item[1], item[2] if abs(a.x - b.x) < 1.2 and abs(a.y - b.y) > 8: xs.add(round((a.x + b.x) / 2, 1)) return xs def row_numbers(runs, heading_font, table_left): """Excel's printed row numbers -> {vertical position: row number}. Digits of one number are drawn separately, so group them by line and read left to right.""" lines = {} for run in runs: if run["font"] != heading_font: continue for ch in run["chars"]: x, y = ch[2] if x < table_left: lines.setdefault(round(y, 1), []).append((x, chr(ch[0]))) out = {} for y, chars in lines.items(): text = "".join(c for _, c in sorted(chars)) if text.isdigit(): out[y] = int(text) return out