259 lines
9.8 KiB
JavaScript
259 lines
9.8 KiB
JavaScript
import fs from 'fs'
|
|
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
|
|
|
|
async function fill() {
|
|
const templatePath = 'server/templates/mitgliedschaft-fillable.pdf'
|
|
if (!fs.existsSync(templatePath)) {
|
|
console.error('Template not found:', templatePath)
|
|
process.exit(1)
|
|
}
|
|
|
|
const existingPdfBytes = fs.readFileSync(templatePath)
|
|
const pdfDoc = await PDFDocument.load(existingPdfBytes)
|
|
const form = pdfDoc.getForm()
|
|
|
|
// Ensure a readable font is embedded and used for field appearances
|
|
const helv = await pdfDoc.embedFont(StandardFonts.Helvetica)
|
|
|
|
// Simple sample data
|
|
const sample = {
|
|
nachname: 'Müller',
|
|
vorname: 'Anna',
|
|
strasse: 'Hauptstr. 12',
|
|
plz_ort: '60389 Frankfurt',
|
|
geburtsdatum: '01.01.1990',
|
|
telefon: '069 123456',
|
|
email: 'anna.mueller@example.de',
|
|
telefon_mobil: '0151 2345678',
|
|
mitglied_aktiv: true,
|
|
mitglied_passiv: false,
|
|
sepa_mitglied: 'Anna Müller',
|
|
sepa_kontoinhaber: 'Anna Müller',
|
|
sepa_strasse: 'Hauptstr. 12',
|
|
sepa_plz_ort: '60389 Frankfurt',
|
|
sepa_bank: 'Sparkasse',
|
|
sepa_iban: 'DE00123456781234567890',
|
|
sepa_bic: 'PBNKDEFF',
|
|
sepa_datum: '23.10.2025',
|
|
sign_datum: '23.10.2025',
|
|
page3_name: 'Müller',
|
|
page3_vorname: 'Anna',
|
|
page3_anschrift: 'Hauptstr. 12, 60389 Frankfurt',
|
|
page3_telefon: '069 123456',
|
|
page3_fax: '069 654321',
|
|
page3_email: 'anna.mueller@example.de',
|
|
page3_datum: '23.10.2025'
|
|
}
|
|
|
|
function safeSetText(name, value) {
|
|
try {
|
|
const f = form.getTextField(name)
|
|
f.setText(value)
|
|
} catch (e) {
|
|
// ignore missing fields
|
|
}
|
|
}
|
|
|
|
// Robust setter: find a field by name (case-insensitive) and set text/checkbox/select accordingly
|
|
function setFieldByName(name, value) {
|
|
try {
|
|
const lower = name.toLowerCase()
|
|
const field = form.getFields().find(f => f.getName() && f.getName().toLowerCase() === lower)
|
|
if (!field) {
|
|
console.log(`DEBUG: Field not found for '${name}'`)
|
|
return false
|
|
}
|
|
// Text field
|
|
if (typeof field.setText === 'function') {
|
|
field.setText(value == null ? '' : String(value))
|
|
return true
|
|
}
|
|
// Check box
|
|
if (typeof field.check === 'function') {
|
|
if (value === true || String(value).toLowerCase() === 'true') field.check()
|
|
else if (typeof field.uncheck === 'function') field.uncheck()
|
|
return true
|
|
}
|
|
// Radio/select (pdf-lib uses select for dropdowns)
|
|
if (typeof field.select === 'function') {
|
|
try { field.select(String(value)) } catch (e) { /* ignore */ }
|
|
return true
|
|
}
|
|
console.log(`DEBUG: Unsupported field type for '${name}'`)
|
|
return false
|
|
} catch (e) {
|
|
console.log(`DEBUG: Error setting field '${name}':`, e.message)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Debug: list all form fields found in the template
|
|
try {
|
|
const allFields = form.getFields().map(f => f.getName())
|
|
console.log('DEBUG: Template form fields:', allFields.join(', '))
|
|
} catch (e) {
|
|
console.log('DEBUG: Could not list form fields:', e.message)
|
|
}
|
|
|
|
setFieldByName('nachname', sample.nachname)
|
|
setFieldByName('vorname', sample.vorname)
|
|
setFieldByName('strasse', sample.strasse)
|
|
setFieldByName('plz_ort', sample.plz_ort)
|
|
setFieldByName('geburtsdatum', sample.geburtsdatum)
|
|
setFieldByName('telefon', sample.telefon)
|
|
setFieldByName('email', sample.email)
|
|
setFieldByName('telefon_mobil', sample.telefon_mobil)
|
|
|
|
// Checkboxes via robust setter
|
|
setFieldByName('mitglied_aktiv', sample.mitglied_aktiv)
|
|
setFieldByName('mitglied_passiv', sample.mitglied_passiv)
|
|
|
|
setFieldByName('sepa_mitglied', sample.sepa_mitglied)
|
|
setFieldByName('sepa_kontoinhaber', sample.sepa_kontoinhaber)
|
|
setFieldByName('sepa_strasse', sample.sepa_strasse)
|
|
setFieldByName('sepa_plz_ort', sample.sepa_plz_ort)
|
|
setFieldByName('sepa_bank', sample.sepa_bank)
|
|
setFieldByName('sepa_iban', sample.sepa_iban)
|
|
setFieldByName('sepa_bic', sample.sepa_bic)
|
|
setFieldByName('sepa_datum', sample.sepa_datum)
|
|
setFieldByName('sign_datum', sample.sign_datum)
|
|
|
|
// page3 fields
|
|
setFieldByName('page3_name', sample.page3_name)
|
|
setFieldByName('page3_vorname', sample.page3_vorname)
|
|
setFieldByName('page3_anschrift', sample.page3_anschrift)
|
|
setFieldByName('page3_telefon', sample.page3_telefon)
|
|
setFieldByName('page3_fax', sample.page3_fax)
|
|
setFieldByName('page3_email', sample.page3_email)
|
|
setFieldByName('page3_datum', sample.page3_datum)
|
|
|
|
// Debug: check which sample keys correspond to actual fields
|
|
try {
|
|
const names = form.getFields().map(f => f.getName().toLowerCase())
|
|
for (const key of Object.keys(sample)) {
|
|
const found = names.includes(key.toLowerCase())
|
|
console.log(`DEBUG: sample key='${key}' -> field present=${found}`)
|
|
}
|
|
} catch (e) {
|
|
console.log('DEBUG: field presence check failed:', e.message)
|
|
}
|
|
|
|
// Debug: read back all field values after setting (before flattening)
|
|
try {
|
|
console.log('DEBUG: Field values after setting:')
|
|
for (const f of form.getFields()) {
|
|
const name = f.getName()
|
|
let val = null
|
|
try {
|
|
if (typeof f.getText === 'function') val = f.getText()
|
|
else if (typeof f.isChecked === 'function') val = f.isChecked()
|
|
else val = '(no getter)'
|
|
} catch (e) {
|
|
val = `(error reading: ${e.message})`
|
|
}
|
|
console.log(` ${name}: ${val}`)
|
|
}
|
|
} catch (e) {
|
|
console.log('DEBUG: Could not read back field values:', e.message)
|
|
}
|
|
|
|
// Debug: print widget rectangles for relevant fields (SEPA and page3)
|
|
try {
|
|
const interesting = ['sepa_mitglied','sepa_kontoinhaber','sepa_strasse','sepa_plz_ort','sepa_bank','sepa_iban','sepa_bic','page3_name','page3_vorname','page3_anschrift','page3_telefon','page3_email']
|
|
for (const fname of interesting) {
|
|
const f = form.getFields().find(x => x.getName && x.getName().toLowerCase() === fname)
|
|
if (!f) { console.log(`DEBUG: no field object for ${fname}`); continue }
|
|
try {
|
|
// attempt to access widget rectangle via low-level acroField
|
|
const acro = f.acroField
|
|
const widgets = acro.getWidgets()
|
|
if (!widgets || widgets.length === 0) { console.log(`DEBUG: no widgets for ${fname}`); continue }
|
|
const rect = widgets[0].getRectangle()
|
|
console.log(`DEBUG: widget rect for ${fname}: ${JSON.stringify(rect)}`)
|
|
} catch (e) {
|
|
console.log(`DEBUG: cannot read widget rect for ${fname}: ${e.message}`)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.log('DEBUG: widget rect inspection failed:', e.message)
|
|
}
|
|
|
|
// Define fallback drawing: draw visible text directly onto pages at widget rect positions
|
|
async function fallbackDraw() {
|
|
try {
|
|
const pages = pdfDoc.getPages()
|
|
// draw SEPA fields on page 2 (index 1)
|
|
const p2 = pages[1]
|
|
const sepaFields = ['sepa_mitglied','sepa_kontoinhaber','sepa_strasse','sepa_plz_ort','sepa_bank','sepa_iban','sepa_bic']
|
|
for (const fname of sepaFields) {
|
|
const f = form.getFields().find(x => x.getName && x.getName().toLowerCase() === fname)
|
|
if (!f) continue
|
|
try {
|
|
const widgets = f.acroField.getWidgets()
|
|
if (!widgets || widgets.length === 0) continue
|
|
const rect = widgets[0].getRectangle()
|
|
const text = (typeof f.getText === 'function') ? f.getText() : ''
|
|
if (text) {
|
|
p2.drawText(String(text), { x: rect.x + 2, y: rect.y + rect.height - 12, size: 11, font: helv })
|
|
console.log(`FALLBACK: drew ${fname} on page2 at ${rect.x},${rect.y}`)
|
|
}
|
|
} catch (e) {
|
|
console.log(`FALLBACK: could not draw ${fname}: ${e.message}`)
|
|
}
|
|
}
|
|
|
|
// draw page3 fields on page 3 (index 2)
|
|
const p3 = pages[2]
|
|
const p3Fields = ['page3_name','page3_vorname','page3_anschrift','page3_telefon','page3_email']
|
|
for (const fname of p3Fields) {
|
|
const f = form.getFields().find(x => x.getName && x.getName().toLowerCase() === fname)
|
|
if (!f) continue
|
|
try {
|
|
const widgets = f.acroField.getWidgets()
|
|
if (!widgets || widgets.length === 0) continue
|
|
const rect = widgets[0].getRectangle()
|
|
const text = (typeof f.getText === 'function') ? f.getText() : ''
|
|
if (text) {
|
|
p3.drawText(String(text), { x: rect.x + 2, y: rect.y + rect.height - 12, size: 11, font: helv })
|
|
console.log(`FALLBACK: drew ${fname} on page3 at ${rect.x},${rect.y}`)
|
|
}
|
|
} catch (e) {
|
|
console.log(`FALLBACK: could not draw ${fname}: ${e.message}`)
|
|
}
|
|
}
|
|
// write fallback copy
|
|
const fallbackBytes = await pdfDoc.save()
|
|
if (!fs.existsSync('temp')) fs.mkdirSync('temp')
|
|
fs.writeFileSync('temp/mitgliedschaft-sample-filled-fallback.pdf', fallbackBytes)
|
|
console.log('Wrote temp/mitgliedschaft-sample-filled-fallback.pdf')
|
|
} catch (e) {
|
|
console.log('FALLBACK drawing failed:', e.message)
|
|
}
|
|
}
|
|
|
|
// Update field appearances so text is visible, then flatten. Run fallback only when enabled.
|
|
try {
|
|
form.updateFieldAppearances(helv)
|
|
const outUnflattened = await pdfDoc.save()
|
|
if (!fs.existsSync('temp')) fs.mkdirSync('temp')
|
|
fs.writeFileSync('temp/mitgliedschaft-sample-filled-unflattened.pdf', outUnflattened)
|
|
form.flatten()
|
|
} catch (e) {
|
|
console.warn('Could not update field appearances:', e.message)
|
|
const enableFallback = process.env.ENABLE_FALLBACK === '1' || (typeof sample.debug !== 'undefined' && sample.debug)
|
|
if (enableFallback) {
|
|
try { await fallbackDraw() } catch (err) { console.warn('Fallback draw failed:', err.message) }
|
|
try { form.flatten() } catch (err) { /* ignore */ }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const out = await pdfDoc.save()
|
|
if (!fs.existsSync('temp')) fs.mkdirSync('temp')
|
|
fs.writeFileSync('temp/mitgliedschaft-sample-filled.pdf', out)
|
|
console.log('Wrote temp/mitgliedschaft-sample-filled.pdf')
|
|
}
|
|
|
|
fill().catch(e => { console.error(e); process.exit(1) })
|