Files
pdf/docs/engine-reality-audit.md
T
2026-06-10 15:22:34 +05:30

9.2 KiB

Engine Roadmap — Reality Audit & Gap Analysis

Status: Review-only. No code changed to produce this. Question answered: Of the roadmap items marked Done, which are real and which are mocks/stubs? And do the stubs belong to a later phase?

How this was verified (not taken on faith)

  1. Read the C++ engine dispatch in pdfium_document.cpp — specifically applyEdits (line 1432) and every edit-type handler.
  2. Runtime-tested each edit op through the real engine bindings (gateway venv): apply → save → reload → assert the change actually persisted (annotation count, text length, page count).
  3. Read the gateway edit pipeline edits.py (incl. the image preprocessing).
  4. Confirmed deps + artifacts: Pillow 10.4.0 present (needed for images), pdfengine.wasm (60 KB) built, engine pdfium=on, skia=off.

1. Headline findings (the important part)

# Finding Impact
F1 freehand (ink) is an engine STUB. Handler at line 1867 is literally spdlog::info("Parsed freehand edit operation (stub)") — it parses and does nothing. Roadmap says "Freehand / Ink Annotations — Done 100%" — that is false end-to-end. Ink never persists to the saved PDF.
F2 free_text is an engine STUB. Handler at line 1804 is spdlog::info("Parsed free_text edit operation (stub)"). The Text-box tool (which sends free_text) won't persist. (Note: text_overlay is real — see F3.)
F3 Several items marked "Done — 0%" (contradictory) are in fact REAL and working: highlight, comment, Edit-Layer schema. The 0% column is just wrong. Good news — these are done.
F4 redaction (roadmap: In Progress) is functionally complete — it removes overlapping page objects, covers, and regenerates content (line 1566). Verified: page text dropped 312→101 chars. Under-reported; it's effectively done.
F5 Frontend "text selection + copy — Done" was a mock (SelectionLayer returned fake text) until it was wired to real glyph data this session. Now real; flagging the roadmap was wrong.

2. Edit operations — verified truth table

Tested by applying each op to a real corpus PDF and reloading the result.

Edit op Roadmap Engine handler Runtime result Verdict
highlight Done (0%) Real (FPDF_ANNOT_HIGHLIGHT, quadpoints, color, author) annots 0→1 REAL
comment Done (0%) Real (FPDF_ANNOT_TEXT sticky) annots 0→1 REAL
text_overlay Done (100%) Real (draws text objects, font mapping) text 312→321 REAL
image_overlay Done (100%) Real engine + gateway converts data-URI→BGRA→temp file needs gateway path (Pillow ✓) REAL (via gateway)
redaction In Progress Real content removal + cover + regen text 312→101 REAL
page_rotation Done (100%) Real (FPDFPage_SetRotation) saved, bytes changed REAL
page_deletion Done (100%) Real (FPDFPage_Delete, guards last page) pages 100→99 REAL
page_reorder Done (100%) Real (FPDF_MovePages) saved ok REAL
free_text (in schema) STUB — no-op no change STUB
freehand (ink) Done (100%) STUB — no-op no change STUB

8 of 10 edit ops are genuinely real and verified. 2 (freehand, free_text) are stubs.


3. Why the stubs matter right now (frontend consequence)

Both stubs are wired into the live UI, so they look like they work but silently fail on save:

  • Draw / Ink tool → emits freehand → engine ignores it. In live (non-mock) mode the stroke renders locally, then disappears after the save round-trip (the reloaded annotation list from extract_annotations won't contain it). It will not be in the exported PDF.
  • Text-box tool → emits free_text → same: text vanishes after reload; not in export.

These didn't show up earlier because we've been in mock mode (engine flag off), where edits don't actually round-trip. They'll surface the moment the engine is enabled.

Cheap mitigations (for later, your call):

  • Text-box: re-point it from free_texttext_overlay (already real). ~1-line change in the frontend handler.
  • Ink: needs a real engine freehand handler (write a PDF Ink annotation, FPDF_ANNOT_INK, from the paths) — a small, well-scoped C++ addition, not a future-phase feature.

4. Roadmap status, corrected

Phase 0 — Infra (8 items, all Done)

All real and consistent. CMake/vcpkg/CI, PDFium/Skia/FreeType/HarfBuzz builds, FastAPI + React scaffold, WASM hello-world, frozen contracts. No issues.

Phase 1 — Core engine + viewer (13 items, all Done)

Mostly real. Corrections:

  • "React: text selection + copy — Done" → was a mock in the live viewer; wired to real glyphs this session. Engine-side glyph extraction was real all along.
  • render, text extraction w/ bounds, DPI/coordinate transforms, font load/substitution, thumbnails, incremental save — verified real.
  • "C++ Engine → WASM Facade — Done"pdfengine.wasm is built (60 KB) and wasmLoader loads it, but falls back to a JS mock on failure, and the live app renders server-side (/render), not via WASM. So the facade exists; it isn't the primary render path.

Phase 2 — Editing (status mixed)

  • Real & done: text_overlay, highlight, image_overlay, comment, page_rotation, page_deletion, page_reorder, Edit-Layer schema. (Several mislabeled 0%.)
  • Marked Done but STUB: freehand/ink (F1). ← the one genuinely wrong "Done".
  • Schema-only / STUB: free_text (F2).
  • In Progress but effectively real: redaction.
  • Genuinely In Progress / Not Started (correctly labeled):
    • Annotation reader (read exists via extract_annotations; "write/edit existing" not there).
    • React annotation toolbar/editing UI (built this session, but edit/delete of existing annots has no engine op).
    • Form field viewing — Not Started (no engine API).
    • Export full-save — engine save_full works; gateway endpoint exists; treat as functionally real.
    • WASM render path + Web Worker — In Progress / Not Started (secondary).

Phase 3 — Advanced (16 items, all Not Started) accurate

Content-stream analyzer, edit existing text / replace / reflow, underline/decoration, font embedding for edits, custom path/Skia rasterizer, glyph-accurate hit-testing, form filling, encryption/password, regression suite, fuzzing, native SDK packages. None started — correctly labeled. This is where "true PDF editing" (retype existing text), forms, and password PDFs live.

Phase 4 — Perf/enterprise (gated, Not Started) accurate

Custom glyph pipeline, tiling, GPU, reflow, accessibility, digital (cryptographic) signatures, advanced forms. Out of v1 scope, correctly labeled.


5. Are the mocks/stubs "part of upcoming phases"?

Two different cases — important distinction:

Stub Belongs to a future phase? Reality
freehand (ink) No. It's a Phase 2 item marked done. The real impl (write an Ink annotation) is small and overdue, not future work. Incomplete Phase-2 task mislabeled "Done".
free_text Partly. A simple FreeText annotation is small Phase-2-grade work. True editable/reflowing text is Phase 3 (Text replacement engine, Not Started). The basic annotation is a quick win; full text editing is correctly future.
Edit existing annotations (delete/update) Not on any phase. No edit op exists. Net-new small backend ticket.
Outline/bookmarks, forms, password PDFs, edit-existing-text Yes — Phase 2/3, Not Started. Correctly future; the UI already stubs these as "coming soon".

6. Bottom line

  • Engine completion is genuinely high for v1-style editing: 8/10 edit ops real and verified, render/text/search/pages/redaction/export all real.
  • The roadmap over-claims exactly two things: freehand/ink is "Done" but a stub, and the "Done 0%" rows are mislabeled (they're actually real). It also under-claims redaction.
  • The only end-to-end-broken user-facing features are Ink and Text-box (both ride the two stubs). Both have cheap fixes; neither requires Phase 3.
  • Everything labeled Phase 3/4 "Not Started" is accurate — that's the real remaining work (edit existing text, forms, encryption, perf, certified signatures).

7. Suggested next steps (for review — nothing changed yet)

  1. Correct the roadmap: freehand = In Progress (stub); highlight/comment/schema = Done (fix the 0%); redaction = Done.
  2. Decide the two cheap fixes: (a) re-point Text-box tool to text_overlay now; (b) implement the engine freehand → Ink-annotation handler.
  3. Add two small backend tickets already flagged by the UI: delete/update_annotation op, and an outline endpoint.
  4. Leave Phase 3/4 as-is — correctly scoped future work.