waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
/ data-pipeline explore_layout.py
| 1 | # -*- coding: utf-8 -*- |
| 2 | """Vermisst das Tabellenlayout des BVA-Fragenkatalogs. |
| 3 | |
| 4 | Erkundungsskript (kein Teil der Produktivpipeline): ermittelt Spaltengrenzen, |
| 5 | Zeilentrenner, Checkbox-Geometrie und Kapitelkopfzeilen, damit der eigentliche |
| 6 | Parser auf verifizierten Koordinaten aufsetzen kann. |
| 7 | """ |
| 8 | import io |
| 9 | import sys |
| 10 | from collections import Counter |
| 11 | |
| 12 | import fitz |
| 13 | |
| 14 | sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") |
| 15 | |
| 16 | PDF = r"C:\Antigravity\Waffensachkundeprüfung\Fragenkatalog_sachkunde_mitAntworten.pdf" |
| 17 | |
| 18 | doc = fitz.open(PDF) |
| 19 | print(f"Seiten: {doc.page_count} Metadaten: {doc.metadata.get('title')!r} / {doc.metadata.get('author')!r}") |
| 20 | |
| 21 | page = doc[5] # erste Fragenseite |
| 22 | print(f"\nSeitengröße: {page.rect}") |
| 23 | |
| 24 | drawings = page.get_drawings() |
| 25 | print(f"Vektorobjekte auf Seite 6: {len(drawings)}") |
| 26 | |
| 27 | vert_lines = Counter() |
| 28 | horiz_lines = Counter() |
| 29 | rects = [] |
| 30 | for d in drawings: |
| 31 | for item in d["items"]: |
| 32 | if item[0] == "l": # Linie |
| 33 | p1, p2 = item[1], item[2] |
| 34 | if abs(p1.x - p2.x) < 0.6: |
| 35 | vert_lines[round(p1.x)] += 1 |
| 36 | elif abs(p1.y - p2.y) < 0.6: |
| 37 | horiz_lines[round(p1.y)] += 1 |
| 38 | elif item[0] == "re": # Rechteck |
| 39 | r = item[1] |
| 40 | rects.append(r) |
| 41 | if r.width < 1.5 and r.height > 3: |
| 42 | vert_lines[round(r.x0)] += 1 |
| 43 | elif r.height < 1.5 and r.width > 3: |
| 44 | horiz_lines[round(r.y0)] += 1 |
| 45 | |
| 46 | print(f"\nVertikale Trenner (x -> Anzahl): {sorted(vert_lines.items())}") |
| 47 | print(f"Horizontale Trenner (y): {sorted(horiz_lines.items())[:40]}") |
| 48 | |
| 49 | # Checkbox-Kandidaten: annähernd quadratische Rechtecke |
| 50 | boxes = [r for r in rects if 6 < r.width < 20 and 6 < r.height < 20 and abs(r.width - r.height) < 4] |
| 51 | print(f"\nCheckbox-Kandidaten: {len(boxes)}") |
| 52 | for b in boxes[:6]: |
| 53 | print(f" x0={b.x0:.1f} y0={b.y0:.1f} w={b.width:.1f} h={b.height:.1f}") |
| 54 | |
| 55 | # Textblöcke mit Spaltenzuordnung |
| 56 | print("\n--- Textzeilen Seite 6 mit x-Positionen ---") |
| 57 | d = page.get_text("dict") |
| 58 | for block in d["blocks"]: |
| 59 | if block["type"] != 0: |
| 60 | continue |
| 61 | for line in block["lines"]: |
| 62 | txt = "".join(s["text"] for s in line["spans"]).strip() |
| 63 | if not txt: |
| 64 | continue |
| 65 | x0 = line["bbox"][0] |
| 66 | y0 = line["bbox"][1] |
| 67 | flags = {s["flags"] for s in line["spans"]} |
| 68 | fonts = {s["font"] for s in line["spans"]} |
| 69 | print(f" x={x0:6.1f} y={y0:6.1f} {txt[:62]!r} fonts={fonts} flags={flags}") |