Files
pdf/gateway/app/routers/edits.py
T

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)