Repository navigation
Description
Context
PR #477 implements #467's canvas text by splitting, positioning, encoding, and joining text in Python, then writing it through an open document transaction. DESIGN.md F6 and section 9 require one operation budget, including allocated bytes, limits, deadline, and cancellation, before input-derived collections grow. _Document::budget() and _Document::edit() currently create separate budgets, and the Python binding does not expose a reusable operation budget. The existing canvas _commit also joins its buffered content before checking the content-size limit.
Goal
Provide bounded, shared operation accounting for canvas content preparation and writing so that canvas text can use the document's limits throughout its work without creating an independent budget for each layout or encode call.
Scope
In: a private binding or equivalent mechanism for creating an operation budget from the document's DocumentLimits and passing it through the canvas preparation and core text operations; fallible, budgeted accounting for input-derived canvas content buffers and their write/commit boundary; typed refusal and cancellation/deadline propagation. Establish and document operation and transaction lifetime semantics consistently with DESIGN.md.
Out: changing DESIGN.md, unrelated drawing features, and new public Python APIs. #467 remains responsible for the actual canvas text implementation and its integration tests. The existing public Font operations from #496 keep their contracts.
Paths
crates/pyropdf-python/src/document.rs,crates/pyropdf-python/src/history.rs,crates/pyropdf-python/src/fonts.rs,crates/pyropdf-python/src/lib.rs, and narrowly related binding modules as needed;crates/pyropdf-store/src/budget/for budget mechanics if required;python/pyropdf/_canvas.py,python/pyropdf/_core.pyi;tests/test_canvas*.py,tests/test_limits*.py, and Rust binding/store tests for budget invariants.
Acceptance
- A canvas operation obtains its budget from the actual document limits and call overrides once; its input validation, temporary buffers, layout/encoding, generated content, and writing use that same accounting, without input-sized allocations before charging.
- Repeated calls made within one canvas operation accumulate charges; distinct operations do not leak charges. Low memory and element budgets refuse with typed
LimitError, without partial writes or an undo step. - Deadline and cancellation are observed during long canvas preparation and writing, and tests demonstrate both.
- Content size is checked or charged before joining input-derived buffered bytes, including at canvas commit; invalid inputs and allocation failures do not panic.
- Regression tests cover existing path/image canvas behavior, rollback, and Python
str.splitlinescompatibility if line splitting is moved into Rust, including CRLF, vertical separators, trailing breaks, and empty lines. - Full repository checks and current-head CI pass.
Notes
A private budget handle shared across the binding and Python is one candidate; moving preparation and PDF operator generation into one bounded Rust call is another. Evaluate either against the operation/transaction boundary and existing document limits before implementation. A handle for layout/encoding alone does not solve the delayed commit or Python intermediate allocations. No upstream or DESIGN.md change is assumed.
Activity
yhay81 commentedon Oct 31, 2026
Handoff: branch
codex/500-canvas-operation-budget, commit5f1add2; no PR yet. The local and pushed worktree is a draft and must not be merged.It shares operation budget meters across Canvas preparation, Font encode/layout and transaction writes; checks/polls before bounded path, image input and content growth; handles nested abort/rollback and writer-wait cancellation; and reads a placed page's content and resources from one source snapshot. Tests cover these paths, including concurrent source edits.
python3 tools/check.py --locked: 6,723 Python tests passed, 5 skipped, andtests/test_canvas.py::test_pages_of_the_same_document_are_stored_oncefailed because the current whole-version cache key creates three Forms. Ruff, Ruff format, ty, cargo fmt, workspace clippy and workspace cargo tests passed. The earlier targeted Canvas/history/image/font/cancellation run passed apart from the same known failure.Next: complete blockers #501 (budgeted/fallible image conversion and compression), #502 (fallible store value copying), and #503 (dependency-aware page Form cache). Replace the temporary version-only cache key with the #503 solution, verify normal reuse and resource-change invalidation, rerun the full checks, then open a PR. Do not infer the current precharged PNG/Pixmap path or
copy_for_editclone is fully budgeted or fallible. No auto-merge is enabled.yhay81 commentedon Nov 2, 2026
Claimed on branch
claude/500-canvas-operation-budget, from main (#505 and #503 merged). It builds on #505's_OperationBudgetand selectively ports the rest ofcodex/500-canvas-operation-budget(5f1add2).yhay81 commentedon Nov 2, 2026
Handoff: branch
claude/500-canvas-operation-budget, PR #578 (not merged).begin_canvasoperation._Document::budget()returns this thread's canvas operation, andcall_budgetletsmax_*options tighten it.encode,layoutandshapetakeoperation=._line_countcounts lines beforesplitlines; splitting stays in Python.python3 tools/check.py --lockedpasses locally. The new tests aretests/test_canvas_operation.py(35).Share an operation budget across canvas preparation and writing (#500) (
DESIGN.md: describe the designs that grew in implementation, and reti…