Files
harheimertc/server/utils/pdf-form-filler.js

229 lines
7.8 KiB
JavaScript

/**
* PDF Form Filler - Handles filling PDF form fields
* Clean Code: Single Responsibility Principle
*/
import { PDFDocument, StandardFonts, rgb } 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()
const firstPage = pages[0]
const helveticaFont = 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)
}
}