feat: implemented multi page reflow
This commit is contained in:
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user