fix(pdf-server): radio + dropdown in fill_form/save (#577)
* fix(pdf-server): fail fast on never-polled viewer + surface batch errors
interact's get_screenshot/get_text waited the full 45s for a viewer that
never mounted (host went idle before iframe reached startPolling), then
timed out with a generic message. Now tracks viewsPolled and fails in ~8s
with a recovery hint. Batch errors were also ordered success-first —
hosts that flatten isError to content[0] showed "Queued: Filled N fields"
and dropped the actual failure entirely. Viewer side: poll even when PDF
load throws, and catch connect() rejection instead of silently blanking.
* fix(pdf-server): radio button + dropdown handling in fill_form and save
Three related bugs around non-text form fields:
Viewer fill_form (mcp-app.ts): writing a string to annotationStorage on
every radio widget trips pdf.js's inverted string coercion
(`value !== buttonValue` → true) — the first-rendered widget checks
itself and clears the rest, so whatever you ask for, you get the first
option. Fix: extract a setFieldInStorage() helper that writes
{value: true} only on the widget whose buttonValue matches, {value: false}
on siblings, matching pdf.js's own change handler. Also covers the
syncFormValuesToStorage() path (localStorage restore).
Save (pdf-annotations.ts): buildAnnotatedPdfBytes only knew about
getTextField/getCheckBox — dropdowns and radios threw in the try block
and fell through the catch, so they were silently dropped on save.
Fix: instanceof dispatch on getFieldMaybe(). For radios, accept either
the option label or a numeric buttonValue (indexed into getOptions()),
since the viewer stores the latter for PDFs with /Opt.
display_pdf output (server.ts): radio groups showed as N identical
`name [Btn]` lines with no way to tell them apart; dropdowns didn't
list their options. Fix: surface buttonValue as exportValue and the
options array from the annotation data already in hand.
This commit is contained in:
parent
f987bc43b2
commit
38700569e7
1 changed files with 434 additions and 72 deletions
|
|
@ -18,6 +18,10 @@ import {
|
|||
PDFString,
|
||||
PDFHexString,
|
||||
StandardFonts,
|
||||
PDFTextField,
|
||||
PDFCheckBox,
|
||||
PDFDropdown,
|
||||
PDFRadioGroup,
|
||||
} from "pdf-lib";
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -810,26 +814,45 @@ export async function buildAnnotatedPdfBytes(
|
|||
// Add proper PDF annotation objects
|
||||
await addAnnotationDicts(pdfDoc, annotations);
|
||||
|
||||
// Apply form fills
|
||||
// Apply form fills. Dispatch on actual field type — getTextField(name) throws
|
||||
// for dropdowns/radios, so the old try/catch silently dropped those on save.
|
||||
if (formFields.size > 0) {
|
||||
try {
|
||||
const form = pdfDoc.getForm();
|
||||
for (const [name, value] of formFields) {
|
||||
try {
|
||||
if (typeof value === "boolean") {
|
||||
const checkbox = form.getCheckBox(name);
|
||||
if (value) checkbox.check();
|
||||
else checkbox.uncheck();
|
||||
const field = form.getFieldMaybe(name);
|
||||
if (!field) continue;
|
||||
|
||||
if (field instanceof PDFCheckBox) {
|
||||
if (value) field.check();
|
||||
else field.uncheck();
|
||||
} else if (field instanceof PDFRadioGroup) {
|
||||
// The viewer stores pdf.js's buttonValue, which for PDFs with an
|
||||
// /Opt array is a numeric index ("0","1","2") rather than the
|
||||
// option label pdf-lib's select() expects. Try the label first,
|
||||
// then fall back to indexing into getOptions().
|
||||
const opts = field.getOptions();
|
||||
const s = String(value);
|
||||
if (opts.includes(s)) {
|
||||
field.select(s);
|
||||
} else {
|
||||
const textField = form.getTextField(name);
|
||||
textField.setText(value);
|
||||
const idx = Number(s);
|
||||
if (Number.isInteger(idx) && idx >= 0 && idx < opts.length) {
|
||||
field.select(opts[idx]);
|
||||
}
|
||||
// else: value is neither label nor index — leave unset
|
||||
}
|
||||
} catch {
|
||||
// Field not found or wrong type — skip
|
||||
} else if (field instanceof PDFDropdown) {
|
||||
// select() auto-enables edit mode for values outside getOptions(),
|
||||
// so this works for both enumerated and free-text combos.
|
||||
field.select(String(value));
|
||||
} else if (field instanceof PDFTextField) {
|
||||
field.setText(String(value));
|
||||
}
|
||||
// PDFButton, PDFOptionList, PDFSignature: no fill_form support yet
|
||||
}
|
||||
} catch {
|
||||
// Form not available — skip
|
||||
// pdfDoc.getForm() throws if the PDF has no AcroForm
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue