Gedicht-Scans: Zeilen direkt aus Tesseract, Fehler aus pymupdf4llm 1.28

- --keep-lines auf Scanseiten: Zeilen direkt aus Tesseract statt über die
  Layout-Analyse (die verschränkte eng stehende Verse und gab Zeilen
  überlappender Blöcke doppelt aus); Stücke auf gleicher Höhe verbinden,
  Rauschen außerhalb der Textspalte verwerfen, Strophen über den
  Grundlinienabstand, Einrückung aus der x-Position, Gedichttitel aus der
  PDF-Gliederung (richtige Schreibweise statt OCR-Lesart)
- Bildtext im neuen Format von pymupdf4llm 1.28 ("<!-- Start of picture
  text -->") wieder entfernen
- Zeilen überlappender Layout-Blöcke nur einmal ausgeben
- OCR-Seitenzahlen ("= 58 —", "— VIII —") erkennen, auch am Strophenende
- Fraktur: deu_latf aus tessdata_best empfohlen (besser als frk)
- Tests, README, Bedienungsanleitung (Gedicht-/Fraktur-Abschnitt), TODO

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-10-02 22:39:12 +02:00
commit 180d98ae04

View file

@ -6,7 +6,7 @@ import pymupdf
import pytest
import pdf2md as p
from conftest import needs_pandoc
from conftest import needs_pandoc, needs_tesseract
from test_e2e import run
NBSP = " "
@ -285,3 +285,48 @@ def test_clean_ocr_lines_removes_edge_noise_and_splits_baselines():
def test_heading_without_letters_is_dropped():
assert p.normalize_markdown("# ||\n\nText.\n") == "Text.\n"
def test_ocr_rows_join_pieces_but_not_neighbour_lines():
segments = [ # [x0, y0, x1, y1, text, grundlinie]
[120, 227, 256, 239, "Sie kam vom Markte, und", 237],
[264, 227, 364, 239, "sie ging zum Markt,", 237],
[123, 245, 320, 256, "Ein bildhübsch, übermütig junges Ding.", 254],
[208, 414, 366, 436, "Gieb uns ein Lied zum besten", 430], # höherer Kasten
[123, 424, 198, 436, "Bis einer ruft:", 433],
]
rows = [row[4] for row in p._ocr_rows(segments)]
assert rows == [
"Sie kam vom Markte, und sie ging zum Markt,",
"Ein bildhübsch, übermütig junges Ding.",
"Bis einer ruft: Gieb uns ein Lied zum besten",
]
@needs_tesseract
def test_keep_lines_on_scan_uses_ocr_lines_and_outline_title(tmp_path):
doc = pymupdf.open()
page = doc.new_page(width=420, height=595)
y = 90
page.insert_text((150, y), "Unterwegs.", fontname="tiro", fontsize=16)
for line in ["Sie kam vom Markte, und sie ging zum Markt,",
"Ein bildhübsches, übermütig junges Ding.", None,
"Die Laute ihr von der Schulter hing,",
"Ein rotes Tuch sie überm Arme trug."]:
y += 30 if line is None else 16
if line:
page.insert_text((80, y), line, fontname="tiro", fontsize=12)
page.insert_text((10, 300), "|", fontsize=14) # Buchkante
scan = pymupdf.open()
scan.new_page(width=420, height=595).insert_image(
scan[0].rect, pixmap=page.get_pixmap(dpi=300))
scan.set_toc([[1, "Unterwegs", 1]])
scan.save(tmp_path / "gedicht.pdf")
out = tmp_path / "gedicht.md"
run(tmp_path / "gedicht.pdf", out, "--keep-lines")
md = out.read_text(encoding="utf-8")
assert "# Unterwegs\n\n" in md # Titel aus der Gliederung
assert "Markt,\\\nEin bildh" in md # Verse als Zeilen
assert "Ding.\n\nDie Laute" in md # Strophengrenze
assert "|" not in md.split("---", 2)[2]