This commit is contained in:
azeeee05
2026-06-16 10:57:01 +05:30
22 changed files with 1821 additions and 72 deletions
+16
View File
@@ -298,6 +298,22 @@ def get_font_bytes(document_id: str, internal_font_id: str) -> Response:
)
@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")
class SearchRect(BaseModel):
x: float
y: float
+38 -2
View File
@@ -223,6 +223,41 @@ class ReplaceTextOperation(BaseModel):
data: ReplaceTextData
class ReflowRun(BaseModel):
text: str
internalFontId: str
fontSize: float
color: str = "#000000"
class ReflowParagraphData(BaseModel):
objectIndices: list[int]
runs: list[ReflowRun]
columnLeft: float
columnRight: float
firstBaselineY: float
leading: float
oldLineCount: int = 1
align: Literal["left", "justify"] = "left"
# Push-down column left edge (defaults to columnLeft). For a bullet item the text reflows from
# a hanging indent (columnLeft) but the push-down spans the full width from pushColumnLeft so
# bullet markers and items below move together.
pushColumnLeft: float | None = None
# Optional WYSIWYG mode: exact visual line breaks from the live editor (one inner list of
# styled fragments per line). When present the engine emits these breaks verbatim.
lines: list[list[ReflowRun]] | None = None
# Optional WYSIWYG mode: exact visual line breaks from the live editor (one inner list of
# styled fragments per line). When present the engine emits these breaks verbatim.
lines: list[list[ReflowRun]] | None = None
class ReflowParagraphOperation(BaseModel):
id: str
type: Literal["reflow_paragraph"]
pageIndex: int = Field(..., ge=0)
data: ReflowParagraphData
class DecorationData(BaseModel):
# Text-markup annotation (underline/strikeout/squiggly) over one or more text
# lines — quadpoints in PDF top-down space, mirroring HighlightData.
@@ -264,6 +299,7 @@ EditOperation = Annotated[
| DeleteAnnotationOperation
| UpdateAnnotationOperation
| ReplaceTextOperation
| ReflowParagraphOperation
| UnderlineOperation
| StrikeoutOperation
| SquigglyOperation,
@@ -283,7 +319,7 @@ _OP_PERMISSION = {
"squiggly": "canAnnotate", "comment": "canAnnotate", "freehand": "canAnnotate",
"free_text": "canAnnotate", "text_overlay": "canAnnotate", "image_overlay": "canAnnotate",
"delete_annotation": "canAnnotate", "update_annotation": "canAnnotate",
"replace_text": "canModify", "redaction": "canModify",
"replace_text": "canModify", "reflow_paragraph": "canModify", "redaction": "canModify",
"update_field": "canFillForms",
"page_rotation": "canAssemble", "page_deletion": "canAssemble", "page_reorder": "canAssemble",
}
@@ -367,7 +403,7 @@ def apply_edits_impl(document_id: str, request: EditsRequest):
# Redaction and in-place text rewrites mutate existing objects, which do
# not round-trip cleanly through an incremental save — force a full save.
full_save_types = {"redaction", "replace_text"}
full_save_types = {"redaction", "replace_text", "reflow_paragraph"}
needs_full = any(op.get("type") in full_save_types for op in req_dict.get("operations", []))
new_bytes = doc_copy.save_full() if needs_full else doc_copy.save_incremental()