188 lines
4.4 KiB
Python
188 lines
4.4 KiB
Python
import json
|
|
from typing import List, Any
|
|
from fastapi import APIRouter, HTTPException, status
|
|
from pydantic import BaseModel
|
|
|
|
from app.services import engine
|
|
from app.services.store import document_store
|
|
|
|
router = APIRouter(prefix="/documents/{document_id}/edits", tags=["edits"])
|
|
from typing import List, Literal, Union, Optional, Annotated
|
|
from pydantic import BaseModel, Field
|
|
|
|
compat_router = APIRouter(tags=["edits"])
|
|
|
|
class TextOverlayData(BaseModel):
|
|
text: str
|
|
x: float
|
|
y: float
|
|
width: float
|
|
height: float
|
|
fontSize: float
|
|
fontFamily: str
|
|
color: str
|
|
|
|
class RedactionData(BaseModel):
|
|
x: float
|
|
y: float
|
|
width: float
|
|
height: float
|
|
fillColor: str = "#000000"
|
|
|
|
class ImageOverlayData(BaseModel):
|
|
x: float
|
|
y: float
|
|
width: float
|
|
height: float
|
|
imageData: str
|
|
|
|
class HighlightQuadPoint(BaseModel):
|
|
x1: float
|
|
y1: float
|
|
x2: float
|
|
y2: float
|
|
x3: float
|
|
y3: float
|
|
x4: float
|
|
y4: float
|
|
|
|
class HighlightData(BaseModel):
|
|
quadPoints: List[HighlightQuadPoint]
|
|
color: str
|
|
opacity: float = 1.0
|
|
author: str
|
|
content: Optional[str] = None
|
|
|
|
class FreeTextData(BaseModel):
|
|
x: float
|
|
y: float
|
|
width: float
|
|
height: float
|
|
text: str
|
|
fontSize: float = 12.0
|
|
color: str = "#000000"
|
|
|
|
class StickyNoteData(BaseModel):
|
|
x: float
|
|
y: float
|
|
author: str
|
|
content: str
|
|
|
|
class FreehandPoint(BaseModel):
|
|
x: float
|
|
y: float
|
|
|
|
class FreehandData(BaseModel):
|
|
paths: List[List[FreehandPoint]]
|
|
color: str
|
|
thickness: float
|
|
|
|
class PageRotationData(BaseModel):
|
|
rotation: Literal[0, 90, 180, 270]
|
|
|
|
class TextOverlayOperation(BaseModel):
|
|
id: str
|
|
type: Literal["text_overlay"]
|
|
pageIndex: int
|
|
data: TextOverlayData
|
|
|
|
class RedactionOperation(BaseModel):
|
|
id: str
|
|
type: Literal["redaction"]
|
|
pageIndex: int
|
|
data: RedactionData
|
|
|
|
class ImageOverlayOperation(BaseModel):
|
|
id: str
|
|
type: Literal["image_overlay"]
|
|
pageIndex: int
|
|
data: ImageOverlayData
|
|
|
|
class HighlightOperation(BaseModel):
|
|
id: str
|
|
type: Literal["highlight"]
|
|
pageIndex: int
|
|
data: HighlightData
|
|
|
|
class FreeTextOperation(BaseModel):
|
|
id: str
|
|
type: Literal["free_text"]
|
|
pageIndex: int
|
|
data: FreeTextData
|
|
|
|
class CommentOperation(BaseModel):
|
|
id: str
|
|
type: Literal["comment"]
|
|
pageIndex: int
|
|
data: StickyNoteData
|
|
|
|
class FreehandOperation(BaseModel):
|
|
id: str
|
|
type: Literal["freehand"]
|
|
pageIndex: int
|
|
data: FreehandData
|
|
|
|
class PageRotationOperation(BaseModel):
|
|
id: str
|
|
type: Literal["page_rotation"]
|
|
pageIndex: int
|
|
data: PageRotationData
|
|
|
|
EditOperation = Annotated[
|
|
Union[
|
|
TextOverlayOperation,
|
|
RedactionOperation,
|
|
ImageOverlayOperation,
|
|
HighlightOperation,
|
|
FreeTextOperation,
|
|
CommentOperation,
|
|
FreehandOperation,
|
|
PageRotationOperation
|
|
],
|
|
Field(discriminator="type")
|
|
]
|
|
|
|
class EditsRequest(BaseModel):
|
|
version: Literal["1.0"]
|
|
operations: List[EditOperation]
|
|
|
|
def apply_edits_impl(document_id: str, request: EditsRequest):
|
|
if not engine.is_available():
|
|
raise HTTPException(
|
|
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
|
detail="Engine bridge (bindings/python) not yet available."
|
|
)
|
|
|
|
doc_info = document_store.get_document(document_id)
|
|
if not doc_info:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found")
|
|
|
|
try:
|
|
pdfengine = engine.require()
|
|
doc = doc_info["doc_instance"]
|
|
|
|
edits_json = json.dumps(request.model_dump())
|
|
|
|
doc.apply_edits(edits_json)
|
|
|
|
new_bytes = doc.save_incremental()
|
|
|
|
new_doc = pdfengine.PdfDocument.load_from_memory(new_bytes)
|
|
|
|
new_info = document_store.add_document(
|
|
filename=doc_info["filename"],
|
|
bytes_data=new_bytes,
|
|
doc_instance=new_doc
|
|
)
|
|
|
|
return {"success": True, "newDocumentId": new_info["id"]}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
@router.post("")
|
|
def apply_edits(document_id: str, request: EditsRequest):
|
|
return apply_edits_impl(document_id, request)
|
|
|
|
@compat_router.post("/edits/{document_id}")
|
|
def apply_edits_compat(document_id: str, request: EditsRequest):
|
|
return apply_edits_impl(document_id, request) |