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

109 lines
4.1 KiB
Python

import os
import tempfile
from fastapi import APIRouter, HTTPException
from app.schemas.text_object import TextObjectResponse, UpdateTextObjectRequest
from app.services import engine
from app.services.store import document_store
router = APIRouter(tags=["documents"])
@router.get(
"/{document_id}/pages/{page_index}/text_objects",
response_model=list[TextObjectResponse],
)
def get_text_objects(document_id: str, page_index: int) -> list[TextObjectResponse]:
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")
_perms = doc_info.get("permissions") or {}
if _perms.get("canModify", True) is False:
raise HTTPException(status_code=403, detail="Raw Text editing is not permitted by this document's restrictions (canModify).")
pdfengine = engine.require()
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
tmp.write(doc_info["bytes_data"])
tmp_path = tmp.name
try:
editor = pdfengine.StreamEditor(tmp_path)
objects = editor.extract_text_objects(page_index)
result = []
for obj in objects:
text_str = obj["text"].decode("latin-1") if isinstance(obj["text"], bytes) else obj["text"]
result.append(TextObjectResponse(
text=text_str,
fontName=obj["fontName"],
fontSize=obj["fontSize"],
tm=obj["tm"]
))
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
finally:
if os.path.exists(tmp_path):
os.remove(tmp_path)
@router.put("/{document_id}/pages/{page_index}/text_objects/{object_index}")
def replace_text_object(document_id: str, page_index: int, object_index: int, req: UpdateTextObjectRequest) -> dict:
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")
_perms = doc_info.get("permissions") or {}
if _perms.get("canModify", True) is False:
raise HTTPException(status_code=403, detail="Raw Text editing is not permitted by this document's restrictions (canModify).")
pdfengine = engine.require()
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
tmp.write(doc_info["bytes_data"])
tmp_path = tmp.name
out_path = tmp_path + ".out.pdf"
try:
editor = pdfengine.StreamEditor(tmp_path)
try:
new_text_bytes = req.new_text.encode("latin-1")
except UnicodeEncodeError as enc_err:
raise HTTPException(
status_code=400,
detail="Some characters can't be encoded in this run's font. Raw Text supports same-charset edits only — use Edit text to add new characters.",
) from enc_err
success = editor.replace_text_object(page_index, object_index, new_text_bytes, out_path)
if not success:
raise HTTPException(status_code=400, detail="Failed to replace text object (not found or identical)")
with open(out_path, "rb") as f:
new_bytes = f.read()
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,
permissions=doc_info.get("permissions"),
)
if "remote_context" in doc_info:
new_info["remote_context"] = doc_info["remote_context"]
return {"success": True, "newDocumentId": new_info["id"]}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) from e
finally:
if os.path.exists(tmp_path):
os.remove(tmp_path)
if os.path.exists(out_path):
os.remove(out_path)