Files
skrift-programme/Docker/signature-service/README.md
Lucas Orth 0341c4eaea Initiale Ablage der umsortierten Skrift-Programme
Struktur: Docker/ (skrift-backend, signature-service), Webseite/
(skrift-configurator, skrift-job-manager), Produktion/ (skrift-produktion,
AutoRecover). Aufgeräumt: signature-to-svg entfernt, Backend-PayPal-Service
entfernt (PayPal läuft client-seitig im Configurator), Dev-/Test-Artefakte
und Deployment-Dokus bereinigt, .dockerignore repariert. CLAUDE.md mit
Architektur ergänzt. Secrets (.env, config.json) via .gitignore ausgeschlossen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 12:21:08 +02:00

198 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Signatur-Plotter-Service
Zustandsloser FastAPI-Service, der für einen Schreibroboter **A4-SVGs** erzeugt,
auf denen handschriftliche Unterschriften an exakt der richtigen Position sitzen.
Im Druck-PDF stehen an jeder Signaturstelle unsichtbare Marker-Token in
transparenter Schrift (z.B. `§§SIG1§§`). Der Service sucht jeden Token im PDF,
liest dessen Koordinaten aus und platziert die zugeordnete SVG-Unterschrift
relativ dazu. Der Plotter druckt nur die erzeugte SVG – das bereits gedruckte
Dokument liefert den Rest.
> **Keine Plotter-Kalibrierung im Service.** Mechanischer Versatz wird komplett in
> der Druckersoftware behandelt. Es gibt keine Offset-Logik und keine ENV-Variablen
> für Versatz.
## Funktionsweise / Geometrie
- PDF-Punkte werden in mm umgerechnet (`1 pt = 25.4/72 mm`).
- PyMuPDF nutzt den Ursprung oben links, Y wächst nach unten.
- Referenzpunkt ist die **linke Oberkante** des Marker-Tokens (`rect.x0`, `rect.y0`).
- `position: "genau"` → `y_sig = y_token` (Unterschrift sitzt **direkt** am Marker, **Standard**)
- `position: "ueber"` → `y_sig = y_token - abstand_mm - signatur_hoehe`
- `position: "unter"` → `y_sig = y_token + abstand_mm`
- Skalierung gleichmäßig: `skala = breite_mm / svg_originalbreite`
(Originalbreite aus der **viewBox**, nicht aus `width`/`height`).
- Eingebettet wird per `<g transform="translate(x,y) scale(skala)">`.
Die Vorschau legt die fertige A4-Signatur-SVG vektoriell (via PyMuPDF
`convert_to_pdf` + `show_pdf_page`) über eine Kopie der betroffenen Originalseiten
und gibt ein zusammengeführtes PDF zurück. Begründung siehe Kommentar in
[`core.py`](core.py) (`build_preview_pdf`): voll vektoriell, gleiche Geometrie wie
`/render`, ohne zusätzliche Pillow-Abhängigkeit.
## Job-Definition
Pro Eintrag. Ein Token darf **mehrfach** im PDF vorkommen (z.B. dieselbe Unterschrift
auf jeder Seite) – jedes Vorkommen wird platziert:
| Feld | Typ | Default | Bedeutung |
|--------------|-------|---------|--------------------------------------------------|
| `token` | str | – | Unsichtbarer Token im PDF, z.B. `§§SIG1§§` |
| `svg_id` | str | – | Dateiname der hochgeladenen SVG |
| `position` | str | `genau` | `genau` (direkt am Marker), `ueber` oder `unter` |
| `breite_mm` | float | `45` | Zielbreite der Unterschrift |
| `abstand_mm` | float | `8` | Abstand zum Token (nur bei `ueber`/`unter` relevant) |
| `versatz_x_mm` | float | `0` | Feinjustierung links/rechts (+ = rechts, − = links) |
| `versatz_y_mm` | float | `0` | Feinjustierung oben/unten (+ = unten, − = oben) |
| `linienstaerke_mm` | float | `0.35` | Linienstärke der Kontur (Signaturen werden immer als Single-Stroke gezeichnet) |
Beispiel (`job.json`):
```json
[
{ "token": "§§SIG1§§", "svg_id": "unterschrift_a.svg", "position": "unter", "breite_mm": 45, "abstand_mm": 8 },
{ "token": "§§SIG2§§", "svg_id": "unterschrift_b.svg", "position": "ueber" }
]
```
## Endpunkte
### `POST /render`
Findet **jedes Vorkommen** jedes Tokens und platziert die zugeordnete SVG an jeder
Stelle. **Rückgabe:** ZIP-Download mit **einer A4-SVG pro betroffener Seite**
(`210×297mm`, `viewBox "0 0 210 297"`), Dateiname `seite_<n>.svg`. Jede Seiten-SVG
enthält alle Signaturen dieser Seite – nur die Unterschriften, Rest leer.
### `POST /preview`
Gleiche Platzierungslogik. **Rückgabe:** zusammengeführtes PDF mit allen
betroffenen Seiten und der Unterschrift als Overlay – nur zur Sichtkontrolle.
Übertragung jeweils als `multipart/form-data`: `pdf` als Datei, `svgs` als Dateien
(max. 3) und `job` als JSON-Form-Feld.
## Fehlerbehandlung
Es wird nie still übersprungen – Fehler kommen als klare Liste zurück:
- Token gar nicht gefunden (0 Treffer) → **422** (Mehrfachtreffer sind erlaubt)
- derselbe Token mehrfach in der Job-Definition → **422**
- `svg_id` ohne hochgeladene Datei → **422**
- SVG ohne `viewBox` → **422**
- Mehr als 3 SVGs → **400**
- Ungültiges Job-JSON / fehlendes/leeres PDF → **400**
- Ungültige Felder (`position`, `breite_mm` …) → **422**
Antwort-Format bei Fehlern: `{ "detail": [ "…", "…" ] }`.
## Beispiel-curl
`/render` (lädt das ZIP herunter):
```bash
curl -X POST http://localhost:8000/render \
-F "pdf=@dokument.pdf;type=application/pdf" \
-F "svgs=@unterschrift_a.svg;type=image/svg+xml" \
-F "svgs=@unterschrift_b.svg;type=image/svg+xml" \
-F 'job=[{"token":"§§SIG1§§","svg_id":"unterschrift_a.svg","position":"unter","breite_mm":45,"abstand_mm":8},{"token":"§§SIG2§§","svg_id":"unterschrift_b.svg","position":"ueber"}]' \
-o signaturen.zip
```
`/preview` (lädt das Vorschau-PDF herunter):
```bash
curl -X POST http://localhost:8000/preview \
-F "pdf=@dokument.pdf;type=application/pdf" \
-F "svgs=@unterschrift_a.svg;type=image/svg+xml" \
-F 'job=[{"token":"§§SIG1§§","svg_id":"unterschrift_a.svg","position":"unter"}]' \
-o vorschau.pdf
```
## Lokal starten
```bash
pip install -r requirements.txt
uvicorn app:app --host 0.0.0.0 --port 8000
```
## Docker (lokal)
```bash
docker build -t signatur-plotter .
docker run --rm -p 8000:8000 signatur-plotter
```
## Deployment hinter Nginx Proxy Manager
Der Service läuft im selben Docker-Netz wie der NPM (`nginx-proxy-manager_default`)
und wird per Domain **`api-sign.skrift.de`** erreichbar gemacht. Start via Compose:
```bash
docker compose up -d --build
```
Die [`docker-compose.yml`](docker-compose.yml) hängt den Container ins externe Netz
`nginx-proxy-manager_default` (kein Host-Port – NPM proxyt intern). Anschließend im
NPM einen **Proxy Host** anlegen:
| Feld | Wert |
|------------------|-------------------------------|
| Domain Names | `api-sign.skrift.de` |
| Scheme | `http` |
| Forward Hostname | `skrift-signature-service` |
| Forward Port | `8000` |
| SSL | Let's Encrypt-Zertifikat aktivieren |
Im Frontend wird die Backend-URL dann auf `https://api-sign.skrift.de` gesetzt
(siehe unten).
## Tests
```bash
pip install pytest
pytest
```
Die Tests decken Token-Suche, Geometrie (`ueber`/`unter`, viewBox-Offset) und die
Fehlerfälle (Token fehlt/mehrfach, SVG fehlt, keine viewBox) ab.
## Frontend-Tab in der Skrift-Anwendung
Der Upload-Bereich ist als eigener Tab **„Signaturen“** in die bestehende
Skrift-FTP-Anwendung integriert (`app/static/index.html`). Er ist über die
Hauptnavigation neben „Manuell drucken“ erreichbar und bietet:
- Upload für genau **ein PDF** und bis zu **3 SVG-Unterschriften**,
- pro SVG eine Zuordnungszeile (Marker-Token, Position **genau** (Standard) / über / unter,
Breite, Abstand, **X-/Y-Versatz** zur Feinjustierung sowie **Linienstärke**;
Signaturen werden immer als Single-Stroke gezeichnet),
- die zuletzt verwendeten Werte werden **pro Signatur** (Dateiname) im Browser
gespeichert und beim erneuten Hochladen automatisch wiederhergestellt,
- ein **Standard-Template** für den Direktdruck ist in den App-Einstellungen unter
*Zuordnung → Signaturen* festlegbar (wird im Tab vorausgewählt),
- Buttons **Vorschau** (zeigt das Vorschau-PDF inline) und **Rendern** (ZIP-Download),
- **Direkt drucken**: Template + Maschine auswählen und die gerenderten A4-Signaturen
sofort an den Schreibroboter senden,
- eine gut sichtbare Fehlerliste (Token nicht/mehrfach gefunden, fehlende Zuordnung …).
### Direktdruck-Pfad
`Direkt drucken` ruft den Skrift-Backend-Endpunkt `POST /api/signature/print` auf.
Dieser rendert die Signaturen serverseitig über den Signatur-Service (`/render`),
entpackt das ZIP und schickt die A4-SVGs mit den Parametern des gewählten Templates
über den vorhandenen Maschinen-Client an den Plotter (gleicher Ablauf wie
„Manuell drucken“, inkl. Formatwechsel-Prüfung und Bestätigung im Tab
„Druckvorgang“). Da dies über den gleichen Origin läuft, ist hierfür kein CORS nötig.
### Backend-URL setzen
Die URL des Signatur-Service ist konfigurierbar (Default `http://localhost:8000`,
hinter dem Nginx Proxy Manager z.B. `https://api-sign.skrift.de`):
- **Im Tab:** Feld *„Signatur-Service (Backend-URL)“* ausfüllen und *Speichern*.
- Persistiert wird in `config.json` unter `app.signature_backend_url`
(Endpunkte `GET`/`POST /api/signature/backend-url` der Skrift-App).
Damit das Frontend (Port `8765`) den Service (Port `8000`) erreichen darf, ist im
Service **CORS** offen konfiguriert.