diff --git a/engine/src/parser/pdfium_edit_annotations.cpp b/engine/src/parser/pdfium_edit_annotations.cpp index 2061214..7f4351f 100644 --- a/engine/src/parser/pdfium_edit_annotations.cpp +++ b/engine/src/parser/pdfium_edit_annotations.cpp @@ -129,6 +129,7 @@ std::expected PdfiumDocument::applyOp_decoration(const nlohma int subtype = FPDF_ANNOT_UNDERLINE; if (type == "strikeout") subtype = FPDF_ANNOT_STRIKEOUT; else if (type == "squiggly") subtype = FPDF_ANNOT_SQUIGGLY; + else if (type == "highlight") subtype = FPDF_ANNOT_HIGHLIGHT; FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, subtype); if (!annot) { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 65b1921..70a841c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -270,7 +270,7 @@ function App() { if (!can('canAnnotate')) { denyToast('Text decorations'); return; } const quadPointsBackend = lines.map((line) => { const lx = line.x / zoom, ly = line.y / zoom, lw = line.width / zoom, lh = line.height / zoom; - return { x1: lx, y1: ly + lh, x2: lx + lw, y2: ly + lh, x3: lx + lw, y3: ly, x4: lx, y4: ly }; + return { x1: lx, y1: ly, x2: lx + lw, y2: ly, x3: lx, y3: ly + lh, x4: lx + lw, y4: ly + lh }; }); let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; @@ -280,7 +280,7 @@ function App() { if (ly < minY) minY = ly; if (lx + lw > maxX) maxX = lx + lw; if (ly + lh > maxY) maxY = ly + lh; - return [{ x: lx, y: ly + lh }, { x: lx + lw, y: ly + lh }, { x: lx + lw, y: ly }, { x: lx, y: ly }]; + return [{ x: lx, y: ly }, { x: lx + lw, y: ly }, { x: lx, y: ly + lh }, { x: lx + lw, y: ly + lh }]; }); const newAnno = { diff --git a/frontend/src/lib/textSelection.ts b/frontend/src/lib/textSelection.ts index 923ce82..13f14a6 100644 --- a/frontend/src/lib/textSelection.ts +++ b/frontend/src/lib/textSelection.ts @@ -194,8 +194,10 @@ export class TextSelectionModel { } const rects: SelRect[] = []; for (const arr of byLine.values()) { - const x = Math.min(...arr.map((g) => g.x)); - const right = Math.max(...arr.map((g) => g.right)); + const nonSpace = arr.filter(g => !/^\s+$/.test(g.text)); + const measureArr = nonSpace.length > 0 ? nonSpace : arr; + const x = Math.min(...measureArr.map((g) => g.x)); + const right = Math.max(...measureArr.map((g) => g.right)); const band = this.lines[arr[0].line]; rects.push({ x, y: band.top, w: right - x, h: band.bottom - band.top }); } diff --git a/frontend/src/viewer/AnnotationLayer.tsx b/frontend/src/viewer/AnnotationLayer.tsx index 651e89f..360c39c 100644 --- a/frontend/src/viewer/AnnotationLayer.tsx +++ b/frontend/src/viewer/AnnotationLayer.tsx @@ -115,7 +115,7 @@ export const AnnotationLayer: React.FC = ({ onPointerDown={(e) => handlePointerDown(e, anno)} onPointerMove={(e) => handlePointerMove(e, anno.id)} onPointerUp={(e) => handlePointerUp(e, anno)} - className={`absolute ${isDraggable(anno.type) ? 'cursor-move' : 'cursor-pointer'} rounded-[2px] transition-[opacity,box-shadow] duration-150 hover:shadow-[0_2px_8px_rgba(16,24,40,0.18)] type-${anno.type} ${isDragging ? 'shadow-lg z-50' : ''}`} + className={`absolute ${isDraggable(anno.type) ? 'cursor-move' : 'cursor-pointer'} rounded-[2px] transition-[opacity,box-shadow] duration-150 ${['highlight', 'strikeout', 'underline', 'squiggly'].includes(anno.type) ? '' : 'hover:shadow-[0_2px_8px_rgba(16,24,40,0.18)]'} type-${anno.type} ${isDragging ? 'shadow-lg z-50' : ''}`} style={{ left: `${scaledBbox.x + currentOffset.x * zoom}px`, top: `${scaledBbox.y + currentOffset.y * zoom}px`, diff --git a/gateway/app/routers/edits.py b/gateway/app/routers/edits.py index 12a44ea..b2e750e 100644 --- a/gateway/app/routers/edits.py +++ b/gateway/app/routers/edits.py @@ -283,6 +283,20 @@ class SquigglyOperation(BaseModel): pageIndex: int = Field(..., ge=0) data: DecorationData +class SignatureData(BaseModel): + x: float + y: float + width: float + height: float + image_data: str | None = None + author: str = "Signer" + +class SignatureOperation(BaseModel): + id: str + type: Literal["signature"] + pageIndex: int = Field(..., ge=0) + data: SignatureData + EditOperation = Annotated[ TextOverlayOperation | RedactionOperation @@ -301,7 +315,9 @@ EditOperation = Annotated[ | ReflowParagraphOperation | UnderlineOperation | StrikeoutOperation - | SquigglyOperation, + | SquigglyOperation + | SignatureOperation + , Field(discriminator="type"), ]