mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
* M0: remove upstream trademark artwork from contributor fork
Use the existing document icon and a plain project label. Remove trademark
asset entries and regenerate attribution as required for modified forks.
Co-authored-by: OpenAI Codex <[email protected]>
* M7: group compatible PDF words and hide source text during editing
Join compatible words across text objects and subset-font spaces, recognize
component colours, and reuse subset fonts with numeric space positioning.
Render a read-only background with the selected glyphs removed while the
inline editor is open. Keep one frame and source line breaks, and reset
the layout width when switching paragraphs. Expose the same preview through page_render.
Add synthetic extraction, save/reopen, preview and UI regressions. Record
the remaining mixed-style paragraph and installed-font limitations.
Co-authored-by: OpenAI Codex <[email protected]>
* M7: preserve rich PDF regions and preview text edits in place
Group mixed source runs into complete visual regions, retain run styles and source encodings, and use validated embedded source faces in the inline editor. Preserve source baselines and show a single live frame as text grows or shrinks, backed by a read-only draft geometry tool.
Validation: strict workspace Clippy, workspace tests, all 30 WASM crates, asset and parity gates, licensed synthetic edit/save/reopen and desktop checks, and an opt-in four-page private-document regression.
Co-authored-by: OpenAI Codex <[email protected]>
* M7: keep cropped artwork from blocking text editing
Track page/path clipping separately from full image placement, use visible selection bounds, and prioritize text hit testing over overlapping artwork. Preserve fixed clips during image transformations and expose visible bounds through the headless tools.
Validation: strict workspace Clippy, workspace tests, 30 WASM crates, asset/parity gates, synthetic engine/tool/UI and native desktop checks, both private-document UI regressions, and all-page local edit/save/reopen/undo on a four-page cropped-artwork PDF.
Co-authored-by: OpenAI Codex <[email protected]>
* M7: resize cropped images with their clipping paths
Keep the visible frame and image pixels under the same transform. Preserve the
opposite corner and respond to diagonal, horizontal-only and vertical-only
corner drags without jumping from an off-centre grip. Refuse shared clipping
scopes that would move unrelated content.
Add synthetic engine/tool/UI regressions and opt-in private-file coverage
for 80 corner gestures and 10 grow/shrink save/reopen/undo render checks.
Co-authored-by: OpenAI Codex <[email protected]>
* M7: complete source font subsets and preserve clipped editing
Add explicit document fonts, CFF embedding, range formatting and bounded contextual shaping; isolate shared image clips and verify private Chinese edits on all pages.
Co-authored-by: OpenAI Codex <[email protected]>
* Revert "M0: remove upstream trademark artwork from contributor fork"
This reverts commit 3aa539b5b6.
* Test: give the text layer up to 4 s before copying (it flaked on a loaded machine)
---------
Co-authored-by: Kamoliddin Fatkhullaev <[email protected]>
Co-authored-by: OpenAI Codex <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
85 lines
4.8 KiB
Markdown
85 lines
4.8 KiB
Markdown
# pdfcraft-engine
|
||
|
||
The façade every frontend talks to. It holds the open documents, their edit history and the tool
|
||
catalogue; frontends never reach past it into the parsing, rendering or editing crates.
|
||
|
||
- **Layer:** L6. It depends on most of L1–L4 and on nothing above itself. **No UI toolkit** —
|
||
`egui`, `winit`, `eframe` and `rfd` are forbidden below L7, and `xtask layers` enforces it.
|
||
- The crate denies `unwrap`/`expect`/`panic!`/`todo!`. Entry points that run untrusted-document
|
||
code keep a last-resort `catch_unwind` guard so an escaped panic becomes an error and the user
|
||
keeps their document.
|
||
|
||
Two frontends exist on top of it today: `pdfcraft-ui-egui` (desktop and web) and
|
||
`pdfcraft-automation` (headless tools, the CLI and MCP). Anything one can do, the other must be
|
||
able to do — see AGENTS.md §3.
|
||
|
||
## The editing model
|
||
|
||
Each open document holds a `pdfcraft_cos::Document`: the object graph plus a copy-on-write overlay.
|
||
|
||
1. An edit runs **on a clone**. On success the previous state is pushed onto the undo stack. Clones
|
||
share all unchanged data, so a snapshot is cheap — this is what makes 100 levels of undo
|
||
affordable on a 200 MB file.
|
||
2. After every edit the **working file** is produced by an incremental write: the original bytes
|
||
plus one appended revision.
|
||
3. The view is refreshed from those bytes, so **what you see is exactly what Save will write**.
|
||
4. Saving rebases onto the written bytes, so the next save appends only new edits.
|
||
|
||
Consequences worth preserving: the original bytes of an opened file are never rewritten in place; a
|
||
full rewrite happens only where it must (redaction, PDF/A conversion, print imposition, a changed
|
||
password); and unknown data survives a round trip because nothing is reconstructed that wasn't
|
||
understood.
|
||
|
||
## API
|
||
|
||
```rust
|
||
let mut s = Session::new();
|
||
let id = s.open("report.pdf", Some(path), bytes, None)?; // -> DocId
|
||
|
||
s.apply(id, Edit::RotatePages { pages: vec![0], degrees: 90 })?;
|
||
let label = s.undo(id)?; // "Rotate pages"
|
||
s.redo(id)?;
|
||
|
||
let doc = s.get(id).ok_or("gone")?; // &Document: queries, never edits
|
||
doc.can_undo(); doc.editable(); doc.allows_printing();
|
||
doc.text_blocks(page); doc.page_boxes(page); doc.security_summary();
|
||
|
||
let bytes = s.save_bytes(id)?; // incremental: keeps signatures over earlier revisions valid
|
||
let bytes = s.save_full_bytes(id)?; // full rewrite, garbage-collected
|
||
s.mark_saved(id, bytes.clone(), path)?;
|
||
```
|
||
|
||
`Edit` is one enum of 75 variants — every undoable change in the product, from
|
||
`RotatePages` to `AddAnnotation`, `SetFieldValue`, `ApplyRedactions` and `AddWatermark`. Adding a
|
||
feature means adding a variant and handling it in `apply`, which is what keeps undo, the dirty
|
||
flag, autosave and the automation tools consistent for free.
|
||
|
||
`Session` also owns the operations that produce a *new* document rather than editing one:
|
||
`create_blank`, `create_from_images`, `create_from_text`, `combine`, `extract`, `split`,
|
||
`split_by_size`, `reduced_bytes`, `optimized_bytes`, `print_pdf`, `export_data`, and the signing
|
||
family (`sign`, `sign_with_timestamp`, `timestamp_document`, `embed_ltv`).
|
||
|
||
## Modules
|
||
|
||
| Module | What it holds |
|
||
|---|---|
|
||
| `commands` | The command registry: id, label, menu, shortcut, and a `Needs` precondition encoding document security. Menus, the ⌘K palette, shortcuts and the control channel all read it, so every surface agrees on what exists and when it is enabled |
|
||
| `catalog` | Acrobat's "All tools" information architecture as data — groups, sections, icons, hues, and an `Availability` of `Ready`, `Planned(milestone)` or `Provider`. The UI renders what is *not* built yet, honestly |
|
||
| `actions` | PDF action dispatch (go-to, URI, named, set-layer-state, JavaScript) |
|
||
| `links` | Link creation, properties, and finding URLs in page text |
|
||
| `js` | The bridge to `pdfcraft-js`. `forms` never depends on `js` directly; the engine injects it, so a build without JavaScript is possible |
|
||
| `xfa`, `ocr`, `compare`, `export` | Façades over the L3/L4 crates of the same names |
|
||
|
||
## Not done here
|
||
|
||
- **Rendering is still borrowed.** `pdfcraft-render` wraps `hayro` and inspects through `lopdf`
|
||
(ADR-0004). The engine only sees `inspect() -> DocInfo`, `RenderPool` and `text::PageText`, and
|
||
that API stays frozen while M2 replaces what is behind it.
|
||
- Performance budgets (`misc.performance-budgets`) are not set or enforced.
|
||
- Some shipped features are still reachable only from the UI, not through an `automation` tool;
|
||
`cargo xtask parity` lists them; closing that gap is tracked in the execution plan (D4).
|
||
|
||
`doc.text_edit_preview(page, block)` prepares a read-only page background with the selected
|
||
paragraph’s glyphs removed, preserving text positioning, images and colours. It shares the
|
||
working document’s render configuration and never enters the undo history.
|