From 01406c82e58e27f97d727582b0b7adb6fb07d1ed Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 14:13:14 +0000 Subject: [PATCH] =?UTF-8?q?AB-PDF:=20Textleser=20f=C3=BCr=20PDF-Tests=20(T?= =?UTF-8?q?oUnicode,=20nur=20Node-Bordmittel)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- test/auftraegePdfHilfe.js | 66 ++++++++++++++++++++++++++++++++++ test/auftraegePdfLeser.test.js | 32 +++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 test/auftraegePdfLeser.test.js diff --git a/test/auftraegePdfHilfe.js b/test/auftraegePdfHilfe.js index de0dff0..5bc738b 100644 --- a/test/auftraegePdfHilfe.js +++ b/test/auftraegePdfHilfe.js @@ -1,3 +1,5 @@ +import { inflateSync } from 'node:zlib' + /** Beispiel-Auftrag (V3-Form) für die PDF-Tests; `extra` überschreibt einzelne Felder. */ export function beispielAuftrag(extra = {}) { return { @@ -34,3 +36,67 @@ export function alleTexte(wert, aus = []) { } return aus } + +/** Anzahl Seiten eines PDFs (Seitenobjekte, nicht der Seitenbaum). */ +export const seitenZahl = (pdf) => (pdf.toString('latin1').match(/\/Type \/Page(?![s\w])/g) || []).length + +/** + * Text aus einem mit pdfkit erzeugten PDF (eingebettete Schriften, Identity-H, ToUnicode), je Seite eine Liste + * von Textzeilen in Zeichenreihenfolge. Nur für Tests: kennt genau das, was pdfkit schreibt. + */ +export function leseSeiten(pdf) { + const s = pdf.toString('latin1') + const objekte = new Map() + const re = /(\d+) 0 obj\n/g + let m + while ((m = re.exec(s))) { + const start = m.index + m[0].length + const ende = s.indexOf('\nendobj', start) + const koerper = s.slice(start, ende) + const strom = koerper.indexOf('\nstream\n') + let dict = koerper + let daten = null + if (strom >= 0) { + dict = koerper.slice(0, strom) + const laenge = Number(/\/Length (\d+)/.exec(dict)[1]) + daten = pdf.subarray(start + strom + 8, start + strom + 8 + laenge) + if (/\/Filter \/FlateDecode/.test(dict)) daten = inflateSync(daten) + } + objekte.set(Number(m[1]), { dict, daten }) + re.lastIndex = ende + } + const tabellen = new Map() + for (const [id, o] of objekte) { + const tu = /\/ToUnicode (\d+) 0 R/.exec(o.dict) + if (!/\/Subtype \/Type0/.test(o.dict) || !tu) continue + const tabelle = [] + for (const r of objekte.get(Number(tu[1])).daten.toString('latin1').matchAll(/<([0-9a-f]{4})> <([0-9a-f]{4})> \[([^\]]*)\]/g)) { + let i = parseInt(r[1], 16) + for (const e of r[3].matchAll(/<([0-9a-f ]+)>/g)) tabelle[i++] = e[1].split(' ').map((h) => String.fromCharCode(parseInt(h, 16))).join('') + } + tabellen.set(id, tabelle) + } + const schriftNachName = new Map() + for (const o of objekte.values()) { + if (!/\/Font </g)) { + for (let i = 0; i < h[1].length; i += 4) text += tabelle[parseInt(h[1].slice(i, i + 4), 16)] ?? '\u{FFFD}' + } + } + zeilen.push(text) + } + seiten.push(zeilen) + } + return seiten +} diff --git a/test/auftraegePdfLeser.test.js b/test/auftraegePdfLeser.test.js new file mode 100644 index 0000000..3cd14f3 --- /dev/null +++ b/test/auftraegePdfLeser.test.js @@ -0,0 +1,32 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import PDFDocument from 'pdfkit' +import { useTestEnv } from './support/testEnv.js' +import { leseSeiten, seitenZahl } from './auftraegePdfHilfe.js' + +useTestEnv({ appwriteUrl: 'http://127.0.0.1:1', stripeUrl: 'http://127.0.0.1:1' }) +const { ladeSchriften } = await import('../server/services/auftraege/pdf.js') + +// Prüft die Testhilfe selbst an einem kleinen, direkt mit pdfkit gebauten PDF (unabhängig vom AB-Code) +function kleinesPdf(komprimieren) { + const { regular, semibold } = ladeSchriften() + const doc = new PDFDocument({ size: 'A4', compress: komprimieren, info: { CreationDate: new Date('2026-10-06T10:00:00.000Z') } }) + doc.registerFont('Figtree', regular) + doc.registerFont('Figtree-SemiBold', semibold) + const teile = [] + doc.on('data', (teil) => teile.push(teil)) + const fertig = new Promise((resolve) => doc.on('end', () => resolve(Buffer.concat(teile)))) + doc.font('Figtree').fontSize(12).text(`Müller & Söhne – ß € ${String.fromCodePoint(0x2212)}5`, 50, 50, { lineBreak: false }) + doc.font('Figtree-SemiBold').fontSize(12).text('Zweite Zeile „fett“', 50, 80, { lineBreak: false }) + doc.addPage().font('Figtree').fontSize(12).text('Seite zwei', 50, 50, { lineBreak: false }) + doc.end() + return fertig +} + +test('leseSeiten und seitenZahl: Text je Seite aus einem pdfkit-PDF, komprimiert und unkomprimiert', async () => { + for (const komprimieren of [true, false]) { + const pdf = await kleinesPdf(komprimieren) + assert.equal(seitenZahl(pdf), 2) + assert.deepEqual(leseSeiten(pdf), [[`Müller & Söhne – ß € ${String.fromCodePoint(0x2212)}5`, 'Zweite Zeile „fett“'], ['Seite zwei']]) + } +})