From 8dd8df9e9fed199ad506bd37a8860dc6a5bcc488 Mon Sep 17 00:00:00 2001 From: Tim Eertmoed Date: Wed, 9 Sep 2026 23:43:47 +0200 Subject: [PATCH] Version 2.3.3: weiter ausfuellbar oder abgeschlossen --- apps/tesm-license/pyproject.toml | 2 +- packages/tesm-core/pyproject.toml | 2 +- .../tesm-core/src/tesm_core/app_factory.py | 2 +- .../tesm_core/static/tesm_core/js/viewer.js | 69 ++++++++++++------- packages/tesm-licensing-server/pyproject.toml | 4 +- packages/tesm-licensing/pyproject.toml | 2 +- 6 files changed, 50 insertions(+), 31 deletions(-) diff --git a/apps/tesm-license/pyproject.toml b/apps/tesm-license/pyproject.toml index 02cc485..ad69ce9 100644 --- a/apps/tesm-license/pyproject.toml +++ b/apps/tesm-license/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-license" -version = "2.3.2" +version = "2.3.3" description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/pyproject.toml b/packages/tesm-core/pyproject.toml index fbfe914..406e906 100644 --- a/packages/tesm-core/pyproject.toml +++ b/packages/tesm-core/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-core" -version = "2.3.2" +version = "2.3.3" description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/src/tesm_core/app_factory.py b/packages/tesm-core/src/tesm_core/app_factory.py index 603dff5..ab0656f 100644 --- a/packages/tesm-core/src/tesm_core/app_factory.py +++ b/packages/tesm-core/src/tesm_core/app_factory.py @@ -135,7 +135,7 @@ def create_base_app( #: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide #: Anwendungen. auth_tagline: str = "", - app_version: str = "2.3.2", + app_version: str = "2.3.3", built_in_trusted_keys: tuple[str, ...] = (), enable_license: bool = True, template_folder: str | None = None, diff --git a/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js b/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js index 50c9821..3642212 100644 --- a/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js +++ b/packages/tesm-core/src/tesm_core/static/tesm_core/js/viewer.js @@ -215,44 +215,59 @@ async function pdflibLaden(adresse) { return pdflibGeladen; } -/** Stempelt die gewaehlten Fotos in die Bildfelder. +/** Setzt die Fotos ein und schliesst das Formular auf Wunsch ab. * * Eingabe sind die Bytes, die pdf.js nach dem Ausfuellen geschrieben hat -- - * die Formularwerte bleiben also erhalten. + * die Feldwerte bleiben also erhalten. + * + * Das Foto wird das **Aussehen des Bildknopfes** und nicht in die Seite + * gestempelt: so bleibt der Knopf erhalten, das Bild ist trotzdem zu sehen, + * und beim naechsten Oeffnen laesst es sich austauschen. Genau das tut + * Acrobat mit "buttonImportIcon". + * + * ``abschliessen`` rechnet danach alle Felder flach in die Seite: heraus kommt + * ein PDF ohne Formular -- nichts mehr zu verstellen. */ -async function fotosEinsetzen(bytes, bilder, adresse) { +async function nachbearbeiten(bytes, bilder, adresse, { abschliessen = false } = {}) { const PDFLib = await pdflibLaden(adresse); const dokument = await PDFLib.PDFDocument.load(bytes); const formular = dokument.getForm(); + for (const eintrag of bilder) { - const seite = dokument.getPage(eintrag.seite - 1); const jpeg = await dokument.embedJpg(eintrag.foto.bytes); + const knopf = eintrag.feldname ? formular.getFieldMaybe(eintrag.feldname) : null; + if (knopf && typeof knopf.setImage === "function") { + knopf.setImage(jpeg); + continue; + } + // Kein Knopf gefunden: dann eben in die Seite. Damit nichts darueber + // liegt, muss das Feld weichen. + const seite = dokument.getPage(eintrag.seite - 1); const [x1, y1, x2, y2] = eintrag.rechteck; const kastenBreite = Math.abs(x2 - x1); const kastenHoehe = Math.abs(y2 - y1); // Einpassen statt verzerren: das Seitenverhaeltnis des Fotos bleibt. const faktor = Math.min(kastenBreite / jpeg.width, kastenHoehe / jpeg.height); - const breite = jpeg.width * faktor; - const hoehe = jpeg.height * faktor; seite.drawImage(jpeg, { - x: Math.min(x1, x2) + (kastenBreite - breite) / 2, - y: Math.min(y1, y2) + (kastenHoehe - hoehe) / 2, - width: breite, - height: hoehe, + x: Math.min(x1, x2) + (kastenBreite - jpeg.width * faktor) / 2, + y: Math.min(y1, y2) + (kastenHoehe - jpeg.height * faktor) / 2, + width: jpeg.width * faktor, + height: jpeg.height * faktor, }); - // Den Knopf aus der fertigen Kopie nehmen: seine Flaeche liegt ueber dem - // Seiteninhalt und wuerde das Foto verdecken. Zu tun haette er dort auch - // nichts mehr -- seine Aktion fuehrt kein Browser aus, und das Bild steht - // jetzt in der Seite. Die Vorlage bleibt unberuehrt. - try { - const feld = formular.getFieldMaybe(eintrag.feldname); - if (feld) formular.removeField(feld); - } catch (fehler) { - // Bleibt der Knopf liegen, ist das Foto verdeckt -- aber die Datei ist - // heil. Das ist der bessere von zwei schlechten Ausgaengen. - console.warn("Bildknopf liess sich nicht entfernen", fehler); + if (knopf) { + try { + formular.removeField(knopf); + } catch (fehler) { + console.warn("Bildknopf liess sich nicht entfernen", fehler); + } } } + + if (abschliessen) { + // Alle Felder werden Teil der Seite. Danach gibt es kein Formular mehr -- + // gewollt: so kann niemand mehr etwas verstellen. + formular.flatten(); + } return dokument.save(); } @@ -439,10 +454,11 @@ async function pdfBetrachter(behaelter, seiten, url, basis) { * pdf.js schreibt die Feldwerte; gewaehlte Fotos kommen danach mit * pdf-lib hinein -- pdf.js kann nichts in ein PDF zeichnen. */ - speichern: async (pdflibAdresse) => { + speichern: async (pdflibAdresse, { abschliessen = false } = {}) => { const bytes = await dokument.saveDocument(); - if (!bilder.size) return bytes; - return fotosEinsetzen(bytes, [...bilder.values()], pdflibAdresse); + // Ohne Bild und ohne Abschluss braucht es pdf-lib gar nicht. + if (!bilder.size && !abschliessen) return bytes; + return nachbearbeiten(bytes, [...bilder.values()], pdflibAdresse, { abschliessen }); }, /** Wurde ueberhaupt etwas eingetragen? */ veraendert: () => { @@ -482,6 +498,7 @@ export function initFilePreview(wurzel = document) { const ersetzenFeld = stueck("ersetzen"); const jetzt = stueck("jetzt"); const abbrechen = stueck("abbrechen"); + const modusFeld = stueck("modus"); const basis = rumpf.dataset.viewerPdfjs || ""; // Ein Format, das dieser Browser nicht kann (HEIC ausserhalb von Safari), @@ -619,7 +636,9 @@ export function initFilePreview(wurzel = document) { const vorher = jetzt.textContent; jetzt.textContent = "Speichern ..."; try { - const bytes = await pdf.speichern(rumpf.dataset.viewerPdflib || ""); + const bytes = await pdf.speichern(rumpf.dataset.viewerPdflib || "", { + abschliessen: modusFeld?.value === "fertig", + }); const daten = new FormData(); daten.append("pfad", aktuell.pfad); daten.append("ordner", ordnerFeld.value.trim()); diff --git a/packages/tesm-licensing-server/pyproject.toml b/packages/tesm-licensing-server/pyproject.toml index 5bf6320..834e2d5 100644 --- a/packages/tesm-licensing-server/pyproject.toml +++ b/packages/tesm-licensing-server/pyproject.toml @@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-licensing-server" -version = "2.3.2" +version = "2.3.3" description = "Serverhaelfte des TESM-Lizenzprotokolls: ausstellen, erneuern, antworten" requires-python = ">=3.11" -dependencies = ["tesm-licensing==2.3.2", "cryptography>=41"] +dependencies = ["tesm-licensing==2.3.3", "cryptography>=41"] [tool.setuptools.packages.find] where = ["src"] diff --git a/packages/tesm-licensing/pyproject.toml b/packages/tesm-licensing/pyproject.toml index b781074..76b0735 100644 --- a/packages/tesm-licensing/pyproject.toml +++ b/packages/tesm-licensing/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-licensing" -version = "2.3.2" +version = "2.3.3" description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = ["cryptography>=42"]