Files
pdf/gateway/app/routers/documents/content.py
T

166 lines
6.7 KiB
Python

from fastapi import APIRouter, HTTPException, Response, status
from app.schemas.annotation import AnnotationResponse
from app.schemas.page_model import PageModelResponse
from app.schemas.search import SearchMatch
from app.services import engine
from app.services.page_model import build_page_model_response
from app.services.search import compute_search_matches
from app.services.store import document_store
router = APIRouter(tags=["documents"])
@router.get("/{document_id}/raw")
def get_document_raw(document_id: str) -> Response:
"""Raw PDF bytes of the current version — loaded into the in-browser WASM engine for the
pixel-identical live-edit preview. Gated on copy permission (same as export)."""
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")
perms = doc_info.get("permissions") or {}
if perms.get("canCopy", True) is False:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not permitted (canCopy).")
data = doc_info.get("bytes_data")
if not data:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document bytes unavailable")
return Response(content=bytes(data), media_type="application/pdf")
@router.get("/{document_id}/search", response_model=list[SearchMatch])
def search_document(
document_id: str,
q: str,
case_sensitive: bool = False,
whole_words: bool = False,
) -> list[SearchMatch]:
if not engine.is_available():
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Engine bridge (bindings/python) not yet available.",
)
if not q:
return []
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:
doc = doc_info["doc_instance"]
return compute_search_matches(doc, q, case_sensitive, whole_words)
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
@router.get("/{document_id}/pages/{page_index}/model", response_model=PageModelResponse)
def get_page_model(document_id: str, page_index: int) -> PageModelResponse:
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:
doc = doc_info["doc_instance"]
page = doc.get_page(page_index)
model = page.extract_document_model()
return build_page_model_response(model)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except IndexError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
@router.get("/{document_id}/annotations", response_model=list[AnnotationResponse])
def get_document_annotations(document_id: str) -> list[AnnotationResponse]:
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:
doc = doc_info["doc_instance"]
all_annots = []
for i in range(doc.page_count):
try:
page = doc.get_page(i)
annots = page.extract_annotations()
for a in annots:
all_annots.append(AnnotationResponse(
id=a.id,
type=a.type,
x=a.x,
y=a.y,
width=a.width,
height=a.height,
color=a.color,
author=a.author,
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),
fieldValue=getattr(a, "field_value", None),
fieldType=getattr(a, "field_type", None),
fieldFlags=getattr(a, "field_flags", None),
fieldOptions=getattr(a, "field_options", None),
))
except Exception:
pass
return all_annots
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
@router.get("/{document_id}/pages/{page_index}/display_list")
def get_display_list(document_id: str, page_index: int):
if not engine.is_available():
raise HTTPException(status_code=501, detail="Engine not available")
doc_info = document_store.get_document(document_id)
if not doc_info:
raise HTTPException(status_code=404, detail="Document not found")
try:
doc = doc_info["doc_instance"]
page = doc.get_page(page_index)
json_str = page.extract_display_list()
return Response(content=json_str, media_type="application/json")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{document_id}/pages/{page_index}/xobjects/{name}")
def get_image_xobject(document_id: str, page_index: int, name: str):
if not engine.is_available():
raise HTTPException(status_code=501, detail="Engine not available")
doc_info = document_store.get_document(document_id)
if not doc_info:
raise HTTPException(status_code=404, detail="Document not found")
try:
doc = doc_info["doc_instance"]
page = doc.get_page(page_index)
img_bytes = page.extract_image_xobject(name)
if not img_bytes:
raise HTTPException(status_code=404, detail="Image not found")
return Response(content=img_bytes, media_type="image/png")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))