feat: implement PDF annotation extraction and inspector API endpoints via C++ bindings
This commit is contained in:
@@ -341,6 +341,7 @@ PYBIND11_MODULE(pdfengine, m) {
|
|||||||
.def_readonly("content", &pdfengine::PdfPage::AnnotationInfo::content)
|
.def_readonly("content", &pdfengine::PdfPage::AnnotationInfo::content)
|
||||||
.def_readonly("timestamp", &pdfengine::PdfPage::AnnotationInfo::timestamp)
|
.def_readonly("timestamp", &pdfengine::PdfPage::AnnotationInfo::timestamp)
|
||||||
.def_readonly("page_index", &pdfengine::PdfPage::AnnotationInfo::pageIndex)
|
.def_readonly("page_index", &pdfengine::PdfPage::AnnotationInfo::pageIndex)
|
||||||
|
.def_readonly("thickness", &pdfengine::PdfPage::AnnotationInfo::thickness)
|
||||||
.def_readonly("paths", &pdfengine::PdfPage::AnnotationInfo::paths)
|
.def_readonly("paths", &pdfengine::PdfPage::AnnotationInfo::paths)
|
||||||
.def_readonly("field_name", &pdfengine::PdfPage::AnnotationInfo::fieldName)
|
.def_readonly("field_name", &pdfengine::PdfPage::AnnotationInfo::fieldName)
|
||||||
.def_readonly("field_value", &pdfengine::PdfPage::AnnotationInfo::fieldValue)
|
.def_readonly("field_value", &pdfengine::PdfPage::AnnotationInfo::fieldValue)
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ public:
|
|||||||
std::string content;
|
std::string content;
|
||||||
std::string timestamp;
|
std::string timestamp;
|
||||||
int pageIndex = 0;
|
int pageIndex = 0;
|
||||||
|
double thickness = 0.0;
|
||||||
std::vector<std::vector<Point2D>> paths;
|
std::vector<std::vector<Point2D>> paths;
|
||||||
std::vector<std::array<Point2D, 4>> quadPoints;
|
std::vector<std::array<Point2D, 4>> quadPoints;
|
||||||
|
|
||||||
|
|||||||
@@ -384,6 +384,10 @@ std::expected<void, EngineError> PdfiumDocument::applyOp_freehand(const nlohmann
|
|||||||
float thickness = static_cast<float>(data.value("thickness", 2.0));
|
float thickness = static_cast<float>(data.value("thickness", 2.0));
|
||||||
FPDFAnnot_SetBorder(annot, 0.0f, 0.0f, thickness);
|
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<FPDF_WIDESTRING>(tUtf16.data()));
|
||||||
|
|
||||||
float minX = 1e9f, minY = 1e9f, maxX = -1e9f, maxY = -1e9f;
|
float minX = 1e9f, minY = 1e9f, maxX = -1e9f, maxY = -1e9f;
|
||||||
bool anyPoints = false;
|
bool anyPoints = false;
|
||||||
|
|
||||||
@@ -550,6 +554,10 @@ std::expected<void, EngineError> PdfiumDocument::applyOp_updateAnnotation(const
|
|||||||
if (data.contains("thickness")) {
|
if (data.contains("thickness")) {
|
||||||
float thickness = static_cast<float>(data["thickness"].get<double>());
|
float thickness = static_cast<float>(data["thickness"].get<double>());
|
||||||
FPDFAnnot_SetBorder(targetAnnot, 0.0f, 0.0f, thickness);
|
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<FPDF_WIDESTRING>(tUtf16.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.contains("text")) {
|
if (data.contains("text")) {
|
||||||
|
|||||||
@@ -600,6 +600,21 @@ std::expected<std::vector<PdfPage::AnnotationInfo>, EngineError> PdfiumPage::ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (subtype == FPDF_ANNOT_INK) {
|
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<char16_t> ctBuf(ctLen / 2);
|
||||||
|
FPDFAnnot_GetStringValue(annot, "CustomThickness", reinterpret_cast<FPDF_WCHAR*>(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();
|
const double pageH = height();
|
||||||
unsigned long strokeCount = FPDFAnnot_GetInkListCount(annot);
|
unsigned long strokeCount = FPDFAnnot_GetInkListCount(annot);
|
||||||
for (unsigned long s = 0; s < strokeCount; ++s) {
|
for (unsigned long s = 0; s < strokeCount; ++s) {
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ const NotesTab: React.FC<{ annotations: Annotation[]; onNavigate: (a: Annotation
|
|||||||
<span className="text-[10px] font-medium text-[#98a1ad]">{a.author}</span>
|
<span className="text-[10px] font-medium text-[#98a1ad]">{a.author}</span>
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
|
|
||||||
{onUpdate && (a.type === 'highlight' || a.type === 'ink' || a.type === 'comment') && (
|
{onUpdate && (['highlight', 'ink', 'comment', 'strikeout', 'underline', 'squiggly'].includes(a.type)) && (
|
||||||
<div className="flex items-center gap-2 mt-1 px-1" onClick={e => e.stopPropagation()}>
|
<div className="flex items-center gap-2 mt-1 px-1" onClick={e => e.stopPropagation()}>
|
||||||
<input
|
<input
|
||||||
type="color"
|
type="color"
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ def get_document_annotations(document_id: str) -> list[AnnotationResponse]:
|
|||||||
content=a.content,
|
content=a.content,
|
||||||
timestamp=getattr(a, "timestamp", None),
|
timestamp=getattr(a, "timestamp", None),
|
||||||
pageIndex=a.page_index,
|
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", [])],
|
paths=[[{"x": p.x, "y": p.y} for p in stroke] for stroke in getattr(a, "paths", [])],
|
||||||
quadPoints=getattr(a, "quad_points", []),
|
quadPoints=getattr(a, "quad_points", []),
|
||||||
fieldName=getattr(a, "field_name", None),
|
fieldName=getattr(a, "field_name", None),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class AnnotationResponse(BaseModel):
|
|||||||
content: str
|
content: str
|
||||||
timestamp: str | None = None
|
timestamp: str | None = None
|
||||||
pageIndex: int
|
pageIndex: int
|
||||||
|
thickness: float | None = None
|
||||||
paths: list[list[dict[str, float]]] = []
|
paths: list[list[dict[str, float]]] = []
|
||||||
quadPoints: list[list[dict[str, float]]] = []
|
quadPoints: list[list[dict[str, float]]] = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user