/** * PDF Form Filler - Handles filling PDF form fields * Clean Code: Single Responsibility Principle */ import { StandardFonts } from 'pdf-lib' import { mapFieldValue, shouldCheckField, shouldCheckByValue } from './pdf-field-mapper.js' /** * Safely sets text field value if it's empty * @param {Object} field - PDF form field * @param {string} value - Value to set */ export function setTextFieldIfEmpty(field, value) { if (typeof field.setText !== 'function') { return } try { // Check if field already has content if (typeof field.getText === 'function') { const currentValue = field.getText() if (currentValue && String(currentValue).trim() !== '') { return // Field already has content, don't overwrite } } } catch (_error) { // Ignore getter errors and proceed to set } if (value != null && String(value).trim() !== '') { field.setText(value) } } /** * Safely sets checkbox field based on membership type * @param {Object} field - PDF form field * @param {string} fieldName - Field name * @param {Object} data - Form data */ export function setCheckboxIfNeeded(field, fieldName, data) { if (!(typeof field.check === 'function' || typeof field.isChecked === 'function')) { return } const lowerName = fieldName.toLowerCase() try { // Handle membership type checkboxes if (lowerName.includes('aktiv') || lowerName.includes('passiv') || lowerName.includes('mitglied')) { if (typeof field.isChecked === 'function' && field.isChecked()) { return // Already checked } if (shouldCheckField(lowerName, data.mitgliedschaftsart)) { field.check && field.check() } return } // Handle other boolean fields const mappedValue = mapFieldValue(data, lowerName) if (shouldCheckByValue(mappedValue)) { try { if (!(typeof field.isChecked === 'function' && field.isChecked())) { field.check && field.check() } } catch (_error) { field.check && field.check() } } } catch (_error) { // Ignore errors } } /** * Fills all form fields in a PDF document * @param {PDFDocument} pdfDoc - PDF document * @param {Object} form - PDF form * @param {Object} data - Form data */ export async function fillFormFields(pdfDoc, form, data) { const fields = form.getFields() for (const field of fields) { const fieldName = field.getName() const lowerName = fieldName.toLowerCase() // Handle text fields if (typeof field.setText === 'function') { const value = mapFieldValue(data, lowerName) setTextFieldIfEmpty(field, value) continue } // Handle checkbox fields if (typeof field.check === 'function' || typeof field.isChecked === 'function') { setCheckboxIfNeeded(field, lowerName, data) continue } } // Update field appearances try { const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) form.updateFieldAppearances(helveticaFont) } catch (_error) { console.warn('Could not update field appearances:', error.message) } } /** * Fills PDF form fields with fallback to positional drawing * @param {PDFDocument} pdfDoc - PDF document * @param {Object} form - PDF form * @param {Object} data - Form data */ export async function fillPdfForm(pdfDoc, form, data) { try { await fillFormFields(pdfDoc, form, data) // Check if PLZ/Ort field on page 1 is empty and fix it await fixPLZOrtField(pdfDoc, data) } catch (_error) { console.warn('Form filling failed, using fallback:', error.message) await fillFormFieldsPositionally(pdfDoc, data) } } /** * Fixes the PLZ/Ort field on page 1 if it's empty * @param {PDFDocument} pdfDoc - PDF document * @param {Object} data - Form data */ async function fixPLZOrtField(pdfDoc, data) { try { const pages = pdfDoc.getPages() await pdfDoc.embedFont(StandardFonts.Helvetica) // Draw PLZ/Ort at the correct position on page 1 const plzOrtText = `${data.plz || ''} ${data.ort || ''}`.trim() // Instead of drawing rectangles, let's find and fix the actual form field const form = pdfDoc.getForm() const fields = form.getFields() // Look for PLZ/Ort related fields and fix them for (const field of fields) { const fieldName = field.getName().toLowerCase() if (fieldName.includes('plz') && fieldName.includes('ort')) { if (typeof field.setText === 'function') { field.setText(plzOrtText) } } else if (fieldName.includes('plz') && !fieldName.includes('vorname') && !fieldName.includes('nachname')) { if (typeof field.setText === 'function') { field.setText(plzOrtText) } } } } catch (_error) { console.warn('Could not fix PLZ/Ort field:', error.message) } } /** * Fallback: Fill form fields by drawing text at specific positions * @param {PDFDocument} pdfDoc - PDF document * @param {Object} data - Form data */ async function fillFormFieldsPositionally(pdfDoc, data) { try { const pages = pdfDoc.getPages() const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) // First page coordinates const firstPage = pages[0] const coords = { nachname: { x: 156, y: 160 }, vorname: { x: 470, y: 160 }, strasse: { x: 156, y: 132 }, plz_ort: { x: 470, y: 132 }, geburtsdatum: { x: 156, y: 104 }, telefon: { x: 470, y: 104 }, email: { x: 156, y: 76 }, telefon_mobil: { x: 470, y: 76 } } // Fill first page fields firstPage.drawText(data.nachname || '', { x: coords.nachname.x, y: coords.nachname.y, size: 11, font: helveticaFont }) firstPage.drawText(data.vorname || '', { x: coords.vorname.x, y: coords.vorname.y, size: 11, font: helveticaFont }) firstPage.drawText(data.strasse || '', { x: coords.strasse.x, y: coords.strasse.y, size: 11, font: helveticaFont }) firstPage.drawText(`${data.plz || ''} ${data.ort || ''}`.trim(), { x: coords.plz_ort.x, y: coords.plz_ort.y, size: 11, font: helveticaFont }) firstPage.drawText(new Date(data.geburtsdatum).toLocaleDateString('de-DE') || '', { x: coords.geburtsdatum.x, y: coords.geburtsdatum.y, size: 11, font: helveticaFont }) firstPage.drawText(data.telefon_privat || data.telefon_mobil || '', { x: coords.telefon.x, y: coords.telefon.y, size: 11, font: helveticaFont }) firstPage.drawText(data.email || '', { x: coords.email.x, y: coords.email.y, size: 11, font: helveticaFont }) firstPage.drawText(data.telefon_mobil || '', { x: coords.telefon_mobil.x, y: coords.telefon_mobil.y, size: 11, font: helveticaFont }) // Bank data on second page const secondPage = pages[1] if (secondPage) { const bankCoords = { kontoinhaber: { x: 156, y: 400 }, iban: { x: 156, y: 370 }, bic: { x: 156, y: 340 }, bank: { x: 156, y: 310 } } secondPage.drawText(data.kontoinhaber || '', { x: bankCoords.kontoinhaber.x, y: bankCoords.kontoinhaber.y, size: 11, font: helveticaFont }) secondPage.drawText(data.iban || '', { x: bankCoords.iban.x, y: bankCoords.iban.y, size: 11, font: helveticaFont }) secondPage.drawText(data.bic || '', { x: bankCoords.bic.x, y: bankCoords.bic.y, size: 11, font: helveticaFont }) secondPage.drawText(data.bank || '', { x: bankCoords.bank.x, y: bankCoords.bank.y, size: 11, font: helveticaFont }) } // Membership checkbox if (data.mitgliedschaftsart === 'aktiv') { firstPage.drawText('X', { x: 116, y: 20, size: 12, font: helveticaFont }) } else if (data.mitgliedschaftsart === 'passiv') { firstPage.drawText('X', { x: 116, y: -8, size: 12, font: helveticaFont }) } } catch (_error) { console.error('Positional filling failed:', error.message) } }