feat: implemented multi page reflow

This commit is contained in:
Furqan-14
2026-06-18 16:48:29 +05:30
parent ff0962b111
commit a124a43877
23 changed files with 1559 additions and 890 deletions
+1 -16
View File
@@ -228,8 +228,6 @@ class ReflowRun(BaseModel):
internalFontId: str
fontSize: float
color: str = "#000000"
# Original per-character advance (PDF units) of UNCHANGED source text, for pixel-perfect
# reflow spacing. Omitted for edited/new runs (engine re-measures those).
advances: list[float] | None = None
@@ -243,9 +241,8 @@ class ReflowParagraphData(BaseModel):
oldLineCount: int = 1
align: Literal["left", "justify", "center", "right"] = "left"
pushColumnLeft: float | None = None
paraId: str | None = None
lines: list[list[ReflowRun]] | None = None
# Original per-line baseline + left anchor (parallel to `lines`), so unchanged lines reproduce
# the source's exact vertical spacing and left edge instead of a uniform fallback.
lineBaselineY: list[float] | None = None
lineX: list[float] | None = None
@@ -308,9 +305,6 @@ class EditsRequest(BaseModel):
version: Literal["1.0"]
operations: list[EditOperation]
# Which PDF permission each edit operation requires. Unencrypted / owner-unlocked
# docs report every flag True (in the engine), so this never blocks them.
_OP_PERMISSION = {
"highlight": "canAnnotate", "underline": "canAnnotate", "strikeout": "canAnnotate",
"squiggly": "canAnnotate", "comment": "canAnnotate", "freehand": "canAnnotate",
@@ -357,10 +351,8 @@ def apply_edits_impl(document_id: str, request: EditsRequest):
import os
import tempfile
# pyrefly: ignore [missing-import]
from PIL import Image
# Remove data URI header if present
if "," in img_data_str:
img_data_str = img_data_str.split(",", 1)[1]
@@ -368,13 +360,11 @@ def apply_edits_impl(document_id: str, request: EditsRequest):
img = Image.open(io.BytesIO(raw_bytes))
img_rgba = img.convert("RGBA")
# Convert RGBA to BGRA
r, g, b, a = img_rgba.split()
img_bgra = Image.merge("RGBA", (b, g, r, a))
bgra_bytes = img_bgra.tobytes()
# Create a temporary binary file to hold raw pixel data
fd, temp_path = tempfile.mkstemp(suffix=".bin", prefix="pdf_pixel_")
created_temp_files.append(temp_path)
try:
@@ -388,7 +378,6 @@ def apply_edits_impl(document_id: str, request: EditsRequest):
op["data"]["pixelWidth"] = img.width
op["data"]["pixelHeight"] = img.height
# Delete base64 strings to keep JSON payload tiny
if "imageData" in op["data"]:
del op["data"]["imageData"]
@@ -396,16 +385,12 @@ def apply_edits_impl(document_id: str, request: EditsRequest):
doc_copy = pdfengine.PdfDocument.load_from_memory(doc_info["bytes_data"])
doc_copy.apply_edits(edits_json)
# 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", "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()
new_doc = pdfengine.PdfDocument.load_from_memory(new_bytes)
# Carry the original permissions forward — the saved bytes are decrypted, so
# a fresh load would report full access and defeat enforcement.
new_info = document_store.add_document(
filename=doc_info["filename"], bytes_data=new_bytes, doc_instance=new_doc,
permissions=doc_info.get("permissions"),