mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-23 23:10:17 +05:30
feat(pdf): add form field detection, fill, and flatten
Adds pdfGetFormFields (lists AcroForm fields with name/type/value) and pdfFillForm (fills text fields by name, optionally flattens) to PDFOperations.js, dispatched via 'formFields'/'fillForm' in executeOperation. pdfFillForm skips unknown/non-text fields per-field (logs + continues) rather than failing the whole batch, matching the partial-success precedent set by pdfExtractImages. Wires a "Fill Form" entry into the PDF editor dialog: selecting a PDF fetches its fields via a new get-pdf-form-fields/pdf-form-fields IPC round trip and renders one text input per field, plus a flatten checkbox, following the same structure as the crop/pageNumbers dialogs. Amit Haridas
This commit is contained in:
@@ -589,6 +589,70 @@ async function pdfExtractImages(data) {
|
||||
}
|
||||
}
|
||||
|
||||
async function pdfGetFormFields(data) {
|
||||
try {
|
||||
const pdfBytes = fs.readFileSync(data.inputPath);
|
||||
const pdf = await PDFDocument.load(pdfBytes);
|
||||
const form = pdf.getForm();
|
||||
|
||||
const fields = form.getFields().map((field) => {
|
||||
let value;
|
||||
try {
|
||||
if (typeof field.getText === 'function') {
|
||||
value = field.getText();
|
||||
} else if (typeof field.isChecked === 'function') {
|
||||
value = field.isChecked();
|
||||
} else if (typeof field.getSelected === 'function') {
|
||||
value = field.getSelected();
|
||||
}
|
||||
} catch {
|
||||
// Some field types throw when read in an unexpected state; leave value undefined.
|
||||
value = undefined;
|
||||
}
|
||||
return { name: field.getName(), type: field.constructor.name, value };
|
||||
});
|
||||
|
||||
return { success: true, fields };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function pdfFillForm(data) {
|
||||
try {
|
||||
const pdfBytes = fs.readFileSync(data.inputPath);
|
||||
const pdf = await PDFDocument.load(pdfBytes);
|
||||
const form = pdf.getForm();
|
||||
|
||||
const values = data.values || {};
|
||||
let filledCount = 0;
|
||||
|
||||
for (const [name, value] of Object.entries(values)) {
|
||||
try {
|
||||
const field = form.getTextField(name);
|
||||
field.setText(value !== null && value !== undefined ? String(value) : '');
|
||||
filledCount++;
|
||||
} catch (fieldError) {
|
||||
// Batch-of-independent-fields: a field that doesn't exist or isn't a text
|
||||
// field shouldn't fail the whole fill — skip it and keep going (same
|
||||
// partial-success precedent as pdfExtractImages).
|
||||
console.warn(`pdfFillForm: skipping field "${name}": ${fieldError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.flatten) {
|
||||
form.flatten();
|
||||
}
|
||||
|
||||
const filledPdfBytes = await pdf.save();
|
||||
fs.writeFileSync(data.outputPath, filledPdfBytes);
|
||||
|
||||
return { success: true, message: `Successfully filled ${filledCount} form field(s)` };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
function executeOperation(operation, data) {
|
||||
switch (operation) {
|
||||
case 'merge':
|
||||
@@ -619,6 +683,10 @@ function executeOperation(operation, data) {
|
||||
return pdfCrop(data);
|
||||
case 'extractImages':
|
||||
return pdfExtractImages(data);
|
||||
case 'formFields':
|
||||
return pdfGetFormFields(data);
|
||||
case 'fillForm':
|
||||
return pdfFillForm(data);
|
||||
default:
|
||||
return Promise.resolve({ success: false, error: `Unknown operation: ${operation}` });
|
||||
}
|
||||
@@ -647,6 +715,8 @@ module.exports = {
|
||||
pdfAddPageNumbers,
|
||||
pdfCrop,
|
||||
pdfExtractImages,
|
||||
pdfGetFormFields,
|
||||
pdfFillForm,
|
||||
executeOperation,
|
||||
getPageCount,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user