229 lines
7.8 KiB
JavaScript
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)
|
|
}
|
|
}
|