From 82a07d2e6e0c27755ace155b4fd52e45d7642a23 Mon Sep 17 00:00:00 2001 From: azeeee05 Date: Thu, 9 Jul 2026 10:38:26 +0530 Subject: [PATCH] feat: implement PDF annotation extraction and inspector API endpoints via C++ bindings --- bindings/python/pdfengine_py.cpp | 1 + engine/include/pdfengine/pdf_document.hpp | 1 + engine/src/parser/pdfium_edit_annotations.cpp | 8 ++++++++ engine/src/parser/pdfium_page_model.cpp | 15 +++++++++++++++ frontend/src/components/InspectorPanel.tsx | 2 +- gateway/app/routers/documents/content.py | 1 + gateway/app/schemas/annotation.py | 1 + 7 files changed, 28 insertions(+), 1 deletion(-) diff --git a/bindings/python/pdfengine_py.cpp b/bindings/python/pdfengine_py.cpp index d650a5a..ea48988 100644 --- a/bindings/python/pdfengine_py.cpp +++ b/bindings/python/pdfengine_py.cpp @@ -341,6 +341,7 @@ PYBIND11_MODULE(pdfengine, m) { .def_readonly("content", &pdfengine::PdfPage::AnnotationInfo::content) .def_readonly("timestamp", &pdfengine::PdfPage::AnnotationInfo::timestamp) .def_readonly("page_index", &pdfengine::PdfPage::AnnotationInfo::pageIndex) + .def_readonly("thickness", &pdfengine::PdfPage::AnnotationInfo::thickness) .def_readonly("paths", &pdfengine::PdfPage::AnnotationInfo::paths) .def_readonly("field_name", &pdfengine::PdfPage::AnnotationInfo::fieldName) .def_readonly("field_value", &pdfengine::PdfPage::AnnotationInfo::fieldValue) diff --git a/engine/include/pdfengine/pdf_document.hpp b/engine/include/pdfengine/pdf_document.hpp index 30d3b5e..5190ab8 100644 --- a/engine/include/pdfengine/pdf_document.hpp +++ b/engine/include/pdfengine/pdf_document.hpp @@ -198,6 +198,7 @@ public: std::string content; std::string timestamp; int pageIndex = 0; + double thickness = 0.0; std::vector> paths; std::vector> quadPoints; diff --git a/engine/src/parser/pdfium_edit_annotations.cpp b/engine/src/parser/pdfium_edit_annotations.cpp index 7f4351f..037cf2d 100644 --- a/engine/src/parser/pdfium_edit_annotations.cpp +++ b/engine/src/parser/pdfium_edit_annotations.cpp @@ -384,6 +384,10 @@ std::expected PdfiumDocument::applyOp_freehand(const nlohmann float thickness = static_cast(data.value("thickness", 2.0)); FPDFAnnot_SetBorder(annot, 0.0f, 0.0f, thickness); + std::string tStr = std::to_string(thickness); + auto tUtf16 = utf8_to_utf16le(tStr); + FPDFAnnot_SetStringValue(annot, "CustomThickness", reinterpret_cast(tUtf16.data())); + float minX = 1e9f, minY = 1e9f, maxX = -1e9f, maxY = -1e9f; bool anyPoints = false; @@ -550,6 +554,10 @@ std::expected PdfiumDocument::applyOp_updateAnnotation(const if (data.contains("thickness")) { float thickness = static_cast(data["thickness"].get()); FPDFAnnot_SetBorder(targetAnnot, 0.0f, 0.0f, thickness); + + std::string tStr = std::to_string(thickness); + auto tUtf16 = utf8_to_utf16le(tStr); + FPDFAnnot_SetStringValue(targetAnnot, "CustomThickness", reinterpret_cast(tUtf16.data())); } if (data.contains("text")) { diff --git a/engine/src/parser/pdfium_page_model.cpp b/engine/src/parser/pdfium_page_model.cpp index 46f9a80..1d15c26 100644 --- a/engine/src/parser/pdfium_page_model.cpp +++ b/engine/src/parser/pdfium_page_model.cpp @@ -600,6 +600,21 @@ std::expected, EngineError> PdfiumPage::ext } if (subtype == FPDF_ANNOT_INK) { + float h_radius = 0, v_radius = 0, border_width = 1.0f; + info.thickness = 2.0; // default + + unsigned long ctLen = FPDFAnnot_GetStringValue(annot, "CustomThickness", nullptr, 0); + if (ctLen > 2) { + std::vector ctBuf(ctLen / 2); + FPDFAnnot_GetStringValue(annot, "CustomThickness", reinterpret_cast(ctBuf.data()), ctLen); + std::string ctStr = utf16le_to_utf8(ctBuf.data(), ctBuf.size()); + try { + info.thickness = std::stod(ctStr); + } catch (...) {} + } else if (FPDFAnnot_GetBorder(annot, &h_radius, &v_radius, &border_width)) { + info.thickness = border_width; + } + const double pageH = height(); unsigned long strokeCount = FPDFAnnot_GetInkListCount(annot); for (unsigned long s = 0; s < strokeCount; ++s) { diff --git a/frontend/src/components/InspectorPanel.tsx b/frontend/src/components/InspectorPanel.tsx index 77b1744..e414860 100644 --- a/frontend/src/components/InspectorPanel.tsx +++ b/frontend/src/components/InspectorPanel.tsx @@ -208,7 +208,7 @@ const NotesTab: React.FC<{ annotations: Annotation[]; onNavigate: (a: Annotation {a.author} - {onUpdate && (a.type === 'highlight' || a.type === 'ink' || a.type === 'comment') && ( + {onUpdate && (['highlight', 'ink', 'comment', 'strikeout', 'underline', 'squiggly'].includes(a.type)) && (
e.stopPropagation()}> list[AnnotationResponse]: content=a.content, timestamp=getattr(a, "timestamp", None), pageIndex=a.page_index, + thickness=getattr(a, "thickness", None), paths=[[{"x": p.x, "y": p.y} for p in stroke] for stroke in getattr(a, "paths", [])], quadPoints=getattr(a, "quad_points", []), fieldName=getattr(a, "field_name", None), diff --git a/gateway/app/schemas/annotation.py b/gateway/app/schemas/annotation.py index d64de6a..e1294fb 100644 --- a/gateway/app/schemas/annotation.py +++ b/gateway/app/schemas/annotation.py @@ -13,6 +13,7 @@ class AnnotationResponse(BaseModel): content: str timestamp: str | None = None pageIndex: int + thickness: float | None = None paths: list[list[dict[str, float]]] = [] quadPoints: list[list[dict[str, float]]] = []