From d2c429f047a5dd61e8b7073ae2036a2b75f1c331 Mon Sep 17 00:00:00 2001 From: azeeee05 Date: Wed, 10 Jun 2026 19:29:11 +0530 Subject: [PATCH] done React: form field viewing (Widget annotations) with saving issues fixed --- engine/src/parser/pdfium_document.cpp | 43 +++++++++++++++++++++++++ frontend/src/App.tsx | 8 +++++ frontend/src/viewer/AnnotationLayer.tsx | 9 ++++++ frontend/src/viewer/PDFViewer.tsx | 3 ++ gateway/app/routers/documents.py | 25 ++++++++++++++ gateway/app/routers/edits.py | 17 ++++++++-- 6 files changed, 103 insertions(+), 2 deletions(-) diff --git a/engine/src/parser/pdfium_document.cpp b/engine/src/parser/pdfium_document.cpp index 5dab1e5..87932a0 100644 --- a/engine/src/parser/pdfium_document.cpp +++ b/engine/src/parser/pdfium_document.cpp @@ -1695,6 +1695,49 @@ std::expected PdfiumDocument::applyEdits(const std::string& e } FPDF_ClosePage(page); + } else if (type == "update_field") { + if (!op.contains("data") || !op["data"].is_object()) { + spdlog::error("update_field operation missing 'data' object"); + return std::unexpected(EngineError::InvalidFormat); + } + auto data = op["data"]; + std::string value; + if (data["value"].is_boolean()) { + value = data["value"].get() ? "Yes" : "Off"; + } else if (data["value"].is_string()) { + value = data["value"].get(); + } else { + spdlog::error("update_field value must be a string or boolean"); + return std::unexpected(EngineError::InvalidFormat); + } + + std::string id = op.value("id", ""); + int annotIndex = -1; + size_t lastUnderscore = id.find_last_of('_'); + if (lastUnderscore != std::string::npos) { + try { + annotIndex = std::stoi(id.substr(lastUnderscore + 1)); + } catch (...) { + annotIndex = -1; + } + } + + if (annotIndex >= 0) { + FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex); + if (!page) { + spdlog::error("Failed to load page index {} for field update", pageIndex); + return std::unexpected(EngineError::Unknown); + } + FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, annotIndex); + if (annot) { + auto utf16 = utf8_to_utf16le(value); + FPDFAnnot_SetStringValue(annot, "V", reinterpret_cast(utf16.data())); + + FPDFPage_GenerateContent(page); + FPDFPage_CloseAnnot(annot); + } + FPDF_ClosePage(page); + } } else if (type == "image_overlay") { if (!op.contains("data") || !op["data"].is_object()) { spdlog::error("image_overlay operation missing 'data' object"); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 10b98fc..f755c05 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -480,6 +480,14 @@ function App() { hasSignature={!!pendingSignature} activeStamp={activeStamp?.label ?? null} annotations={annotations} + onFieldChange={(id, value, i) => { + applyOps([{ + id, + type: 'update_field', + pageIndex: i, + data: { value } + } as any]); + }} searchQuery={searchQuery} searchResults={searchResults} searchCurrentMatch={searchCurrentMatch} diff --git a/frontend/src/viewer/AnnotationLayer.tsx b/frontend/src/viewer/AnnotationLayer.tsx index a7d572b..f8ca812 100644 --- a/frontend/src/viewer/AnnotationLayer.tsx +++ b/frontend/src/viewer/AnnotationLayer.tsx @@ -29,6 +29,7 @@ interface AnnotationLayerProps { zoom: number; annotations: Annotation[]; onAnnotationClick?: (annotation: Annotation) => void; + onFieldChange?: (id: string, value: string | boolean, pageIndex: number) => void; } export const AnnotationLayer: React.FC = ({ @@ -38,6 +39,7 @@ export const AnnotationLayer: React.FC = ({ zoom, annotations, onAnnotationClick, + onFieldChange, }) => { return (
= ({ { + if (e.target.value !== (anno.fieldValue || '')) { + onFieldChange?.(anno.id, e.target.value, pageIndex); + } + }} className="w-full h-full bg-blue-50/50 border border-blue-400/50 text-sm px-1 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded-sm" /> )} @@ -109,12 +116,14 @@ export const AnnotationLayer: React.FC = ({ onFieldChange?.(anno.id, e.target.checked, pageIndex)} className="w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500" /> )} {anno.fieldType === 'Ch' && (