TesseractInvoiceExtractor.php
Pfad: src/Infrastructure/Rechnung/TesseractInvoiceExtractor.php
Ext: php
Größe: 2668 Bytes
Geändert: 2026-07-16 16:57:10+02
<?php
declare(strict_types=1);
namespace Demo\Infrastructure\Rechnung;
use Demo\Domain\Rechnung\ExtractedInvoiceData;
use Demo\Domain\Rechnung\TextExtractionPort;
use RuntimeException;
/**
* Rasterisiert PDF-Seiten und extrahiert Text lokal mit Tesseract.
*/
final class TesseractInvoiceExtractor implements TextExtractionPort
{
/** Bindet OCR und Rasterizer. */
public function __construct(private readonly string $binary = 'tesseract', private readonly string $rasterBinary = 'pdftoppm', private readonly ?InvoiceTextParser $parser = null)
{
}
/** Rasterisiert alle Seiten, fuehrt OCR aus und parst den Gesamttext. */
public function extract(string $pdfPath): ExtractedInvoiceData
{
if (!is_file($pdfPath)) {
throw new RuntimeException('PDF fuer OCR fehlt');
}
$base = tempnam(sys_get_temp_dir(), 'invoice-ocr-');
if ($base === false) {
throw new RuntimeException('OCR-Temp-Prefix fehlt');
}
@unlink($base);
$images = [];
try {
[$status, , $error] = $this->run([$this->rasterBinary, '-png', '-r', '300', $pdfPath, $base]);
$images = glob($base . '-*.png') ?: [];
if ($status !== 0 || $images === []) {
throw new RuntimeException('PDF-Rasterisierung fehlgeschlagen: ' . trim($error));
}
$text = '';
foreach ($images as $image) {
[$ocrStatus, $stdout, $ocrError] = $this->run([$this->binary, $image, 'stdout']);
if ($ocrStatus !== 0) {
throw new RuntimeException('Tesseract fehlgeschlagen: ' . trim($ocrError));
}
$text .= "\n" . $stdout;
}
return ($this->parser ?? new InvoiceTextParser())->parse($text, 0.60);
} finally {
foreach ($images as $image) {
@unlink($image);
}
}
}
/**
* @param list<string> $command Prozessargumente ohne Shell.
* @return array{int,string,string} Exitcode, stdout, stderr.
*/
private function run(array $command): array
{
$pipes = [];
$process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
if (!is_resource($process)) {
throw new RuntimeException('OCR-Prozess kann nicht gestartet werden');
}
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
return [proc_close($process), $stdout === false ? '' : $stdout, $stderr === false ? '' : $stderr];
}
}
Frühere Versionen
| Version | Zeitpunkt | Operation |
|---|---|---|
| 28 | 2026-07-16 19:01:40.815449+02 | UPDATE |