- providers/tesseract: system tesseract (TSV → text + normalized word boxes), installed-language detection; default_engine() picks Vision/Tesseract/none - image_prep: decode, validate, apply rotation before recognition - jobs: background OcrJob with progress, cancel, per-item results - ui_state: no fake success toasts; clipboard/export/share report real outcomes - ingest: real decode validation, size caps, shared PDF pages (no duplication) - export: atomic writes, paginated multi-page PDF, refuse non-WinAnsi text - capture: fixed drag anchor; settings: atomic save + applied to new files - drop unused deps; tests: 78 pass incl. 5 real-OCR on a fixture - EXECUTION_PLAN: correct overstated status
20 KiB
nigig-ocr — Execution Plan (1:1 port of OCR Tool PRO 1.0)
Plan status: all phases specified. Implementation status (corrected 2026-09-26): the earlier "Phase 2–9 DONE" was not true. No real OCR ran anywhere, the GUI was an unwired layout, and several success messages were fake. Honest status now:
- OCR-T1 engine layer: DONE. A real provider (system Tesseract) with rotation, word boxes, language checks, a background job, validated ingest, an honest UI state machine and paginated WinAnsi PDF export. Details in §6.
- OCR-T2 GUI wiring: NOT STARTED.
app.rsis not bound toUiStateorOcrJob; there are no Makepad UI tests.- OCR-T3 Apple Vision binding and screen capture: NOT STARTED. The Apple provider returns
NoEngine; capture is geometry only.- Share, the Unicode PDF font and the size gate (§1) are open.
Source analyzed: /Applications/OCR Tool.app — 1.6M bundle, 1.1M universal
binary (x86_64+arm64, __TEXT 393216), Swift + Storyboard, macOS 10.15+,
Xcode 12.3, identifier com.moneyplanttechnologies.ocrtool.
Small because it links system frameworks dynamically and ships no model:
Vision, PDFKit, AppKit, QuartzCore, StoreKit + libswift* (otool -L,
dyld_info -imports, strings | swift-demangle).
Port target: crates/apps/nigig-ocr/ (registered in workspace Cargo.toml),
Rust + Makepad, same small-binary property via OS-native APIs (§1).
§0 Reuse-first workflow (MANDATORY — every phase starts here, no exceptions)
For each phase, the implementer MUST execute these 4 steps in order and log the outcome in §6:
- Search existing crates — run and record:
Candidate reuse set (audited, do not reimplement without justification):grep -rn "OcrEngine\|enhance_for_ocr\|scan_file\|PdfPageWidget\|DocumentScanner" crates/ --include="*.rs" | head -n 30crates/apps/pdf/pdf-document/src/ocr.rs—OcrEngine/OcrWord/OcrLayer/NoopOcr/StubOcrcrates/apps/nigig_doc_scanner/src/scanner_core.rs—detect_document_corners/enhance_for_ocrcrates/apps/nigig-site/src/ocr.rs—scan_filetraversal guard,OcrDonebg actioncrates/apps/nigig-site/src/ocr_policy.rs— SITE-14 honesty rule (never invent text)crates/apps/pdf/pdf-makepad/src/{page_view,device,interaction}.rs— PDF widget/devicecrates/pageflipnav/src/features/tools/document_scanner/mod.rs+crates/apps/nigig_doc_scanner/src/scanner_frame/— screen layoutcrates/nigig-core(image, persistence, dir),crates/nigig-uikit,crates/nigig-system-prefs
- Check Makepad fork (
makepad-native-glue/makepad,work+devmerged atnigig-makepad-test-android):examples/camera(Video native/texture),examples/pdf,examples/text_selection,examples/uizoo(tab_image/tab_rotatedimage/tab_filetree),apps/image|pdf|files|photos;work-branchfabric(photo/camera pipeline),ai-hub(local model runner). - Reuse or extend (re-export, adapter, copy tested pattern). Only when nothing
exists write new code in
nigig-ocrand note the gap in §6. - Verify with the phase gate (§3) —
cargo test+cargo check+ size check.
§1 Small-binary strategy (reuse OS APIs like Swift — normative)
| Capability | macOS/iOS (target_vendor="apple") |
Windows | Linux | Harmony/Web/Android |
|---|---|---|---|---|
| Text OCR | VNRecognizeTextRequest via objc2, dynamic link, system-ocr feature — src/providers/apple_vision.rs |
WinRT Windows.Media.Ocr via windows crate, system-ocr, future |
system Tesseract via dlopen if present, else honest stub |
honest stub + manual entry (SITE-14) |
| Barcode/QR | VNDetectBarcodesRequest |
WinRT or rqrr fallback |
rqrr/zbar if present |
stub |
| PDF render/export | shared nigig-pdf-{cos,document,graphics,makepad} (pure Rust) |
same | same | same |
| Screen capture | CGWindowListCreateImage (Apple only) |
Win32 capture API | portal/xcap-style |
stub + file import |
| Clipboard/share/print | arboard + native share/print glue |
same | same | same |
| Camera/Continuity | file picker (Continuity is Apple-only, not portable) | file picker | file picker | camera intent + file picker |
Dependency policy (size): no bundled ML model, no Tesseract static link, no
heavy OCR crate in default features. OS bindings are optional + cfg(target)
feature="system-ocr". Release profile: workspaceprofile.small(opt-level="z",lto=true,codegen-units=1,strip=true,panic="abort"). Budget: original 1.6M (Swift+OS-only) is unattainable in Rust+Makepad; target<8Muniversal, tracked per phase (ls -lh target/small/*/nigig-ocr,cargo bloat). Privacy: offline-only, no network/telemetry by construction (original also offline except App Store receipt + rate link, both stripped in Phase 8).
§2 Binary forensics (Phase 0 — DONE, reproducible)
ls -R "/Applications/OCR Tool.app"
plutil -p "/Applications/OCR Tool.app/Contents/Info.plist"
otool -L "/Applications/OCR Tool.app/Contents/MacOS/OCR Tool"
strings .../MacOS/OCR Tool | sort -u
strings .../MacOS/OCR Tool | grep '^\$s8OCR' | xcrun swift-demangle
dyld_info -imports .../MacOS/OCR Tool | grep -E 'VN|CGWindow|Sharing|SKPayment'
strings .../Base.lproj/Main.storyboardc/XfG-*.nib
strings .../Resources/Assets.car
codesign -dv "/Applications/OCR Tool.app"
Parity matrix (every shipped feature → evidence → port target → phase)
| # | Feature | Binary evidence | Port target | Phase |
|---|---|---|---|---|
| F1 | Open images + PDFs via drag-drop | DropView, "Drag and drop photos and pdf files here", NSFilenamesPboardType |
DropView widget + PDF raster via nigig-pdf-* |
P2 |
| F2 | Grab screen area (single) | btnSingleScreenshot/clickedSingleScreenshot:, SelectRegionView/WindowController, CGWindowListOption |
region overlay + CGWindowListCreateImage (Apple) |
P3 |
| F3 | Grab many times | btnMultipleScreenshot/clickedMultipleScreenshot:, "Grab screen area many times" |
multi-capture loop, stay-open overlay | P3 |
| F4 | Hide main window on capture | isHideMainWindowWhenTakingScreenshot |
setting + window hide/show around capture | P3,P7 |
| F5 | Screen-Recording permission flow | permission string + x-apple.systempreferences:...Privacy_ScreenCapture + Open System Preferences |
same text + settings deep-link (Apple), graceful stub elsewhere | P3 |
| F6 | Import from iPhone/iPad/scanner | btnImport/clickedImport:, Continuity note string |
file picker stub (API not portable) | P2 |
| F7 | Do OCR / Do Batch OCR | btnDoOCR/btnDoBatchOCR, "Do OCR!"/"Do Batch OCR!", clickedDoOCR:/clickedDoBatchOCR: |
same buttons → engine calls | P1 shell, P4 wire |
| F8 | Current-page vs all-pages | "Run text recognition on current/all pages", recognize:File:error: |
page selector + batch runner | P4 |
| F9 | Fast vs accuracy mode | isUseFastOCRMode, "Use Fast mode (unchecked = accuracy mode)", setRecognitionLevel: |
OcrLevel::Fast/Accurate → Vision level |
P1 type, P4 wire |
| F10 | Language correction | usesLanguageCorrection/btnUsesLanguageCorrection, setUsesLanguageCorrection: |
bool → Vision correction flag | P1 type, P4 wire |
| F11 | 8-lang popup (+PRO 100+) | popupLanguage/lblLanguage/clickedPopupLanguage:, OCRLanguage, ocrLanguageISOCodeSet, recognitionLanguages/possibleLanguages, 8 display names |
OcrLanguage enum + ISO codes + provider supported_languages() |
P1 enum, P4 enumerate |
| F12 | Barcode + QR | btnBarCodeScan/clickedBarCodeScan:, BarcodeScanner, VNBarcodeObservation, "Scan QR Codes or Barcodes", "Could not perform barcode-request!" |
barcode_mode → VNDetectBarcodesRequest / rqrr fallback |
P4 |
| F13 | Rotate left/right | btnLeftRotate/btnRightRotate, clickedLeftRotate:/clickedRightRotate:, rotateDegree, rotate_left/right icons |
FileItem::rotate_left/right (done) → image transform in result view |
P1 logic, P5 view |
| F14 | Overlay on input image | btnShowOverlay/clickedShowOverlay:, overlayImage, isShowOverlayOnInputImage, closeOverlayView, close_overlay icon |
overlay boxes layer + toggle + close | P5 |
| F15 | Result text + timing + count | textView, "Result (Processing time:", "Number of results found:", "No results found." |
result panel + labels | P5 |
| F16 | Progress + toast | ActivityView, "Running OCR... 1/2", ToastView, TSToastActivityViewKey |
progress bar + toast (reuse uikit pattern) | P5 |
| F17 | File list delete current/all | btnDelete/btnDeleteAll, clickedDelete:/clickedDeleteAll:, "Delete All Files!/Delete all files/Delete current file", confirm strings, clear/clear_all icons |
list store + confirm dialogs | P2 |
| F18 | Copy to clipboard | btnCopy/clickedCopy:, "Copy text to clipboard", generalPasteboard, copy icons |
arboard copy |
P6 |
| F19 | Export TXT (single) | btnSave/clickedSave:, exportTextFile, "Export Text File", "Text File Exported", save icon |
TXT writer | P6 |
| F20 | Export PDF (single + all) | exportPDF/exportAllPDFs, dataWithPDFInsideRect:, "Export PDF or text file/with All Text", "PDF File Exported/with All Text" |
PDF writer via nigig-pdf-* text layer (+ photos-metadata label) |
P6 |
| F21 | Share | btnShare/clickedShare:, NSSharingServicePickerDelegate, share icon |
native share glue | P6 |
| F22 | application:printFile:, printFiles:withSettings:showPrintPanels: |
print glue | P6 | |
| F23 | Settings | Settings, settings icon, hide-window + overlay + language prefs |
settings screen + nigig-system-prefs persistence |
P7 |
| F24 | Light/dark mode | NSAppearance (system), marketing claim |
Makepad theme | P7 |
| F25 | Trial/PRO purchase + Restore + Rate | MPTPurchase nib, SwiftyStoreKit (PaymentsController, RestorePurchasesController, …), "Upgrade to PRO…", "START FREE 30-DAY TRIAL", "Already purchased…/Restore", "Rate us on the App Store." + App Store URL |
STRIP all; optional nigig-pay hook or nothing; assert no purchase/rate strings in binary |
P8 |
| F26 | Offline privacy | no network imports except StoreKit/receipt | no network deps; offline-only gate | P1,P8 |
| F27 | Universal M1+Intel, 10.15+ | universal binary, LSMinimumSystemVersion 10.15 |
universal release, min-version doc | P9 |
| F28 | Toolbar icon set | Assets.car: backward, forward, capture, capture_multiple, clear, clear_all, close_overlay, copy, import, rotate_left/right, save, scanner, settings, share (+AppIcon) |
same icon set in Makepad theme | P1 placeholder, P5 final |
§3 Phases (each: §0 check → build → gate; gates are normative)
Phase 0 — Forensics freeze — DONE
Deliver: §2 + parity matrix. Gate: reviewed, commands reproducible. Result: pass.
Phase 1 — Scaffold + engine types — DONE
Deliver: Cargo.toml (engine/app/system-ocr), src/lib.rs|engine.rs|languages.rs|providers/{mod,fallback,apple_vision}.rs|app.rs|main.rs.
Reuse: pdf-document OCR semantics; SITE-14 honesty; Makepad examples (camera/pdf/text_selection/uizoo), apps (image/pdf/files). New: option-aware OcrEngine seam, FileItem/OcrOptions/OcrLevel/OcrResult/OcrError, 8-lang enum, UI shell (F7,F11,F28 placeholders).
Gate: standalone cargo test — 7 passed (rotate wrap, stub empty-refuse, stub text, 8-lang popup, zh codes, Apple popup set, fallback honesty). Workspace cargo test -p nigig-ocr blocked by pre-existing tree breakage (nigig-traffic/makepad rev + offline fetch) — recorded, not caused by this crate. Result: pass (standalone).
Phase 2 — Ingest (F1,F6,F17; partial F7)
- §0:
nigig-site::ocr::scan_fileguard +OcrDone;nigig-pdf-document/graphicsraster; Makepadapps/files,examples/uizoo tab_filetree. - Build: DropView widget (accept PNG/JPEG/PDF, reject others with toast);
FileItemlist store (add/select/delete/delete-all + confirm strings); PDF multi-page → one item per page (reuse pdf raster); Continuity → file-picker stub with original note text; traversal guard copy + tests. - Gate: drop PNG + 3-page PDF → 4 list entries; delete/delete-all with confirms;
/etc/hostnamerefused;cargo test -p nigig-ocringest tests green.
Phase 3 — Capture (F2,F3,F4,F5)
- §0: Makepad overlay/window patterns (
examples/floating_panel,apps/wm); AppleCGWindowListbinding pattern fromrobius-*/platform code. - Build: region overlay (drag + 4 corner handles =
CornerResizeViewparity); single + multi (stay-open) modes; hide-window flag; permission explainer + System Settings deep-link on Apple, graceful message elsewhere;screencapture -Rparity on macOS. - Gate: captured rect pixel-equals reference for 3 rects; multi-mode yields N items without reopening picker; permission-denied path shows explainer, no crash.
Phase 4 — Recognition providers (F7,F8,F9,F10,F11,F12)
- §0:
scanner_core::enhance_for_ocrpreprocess;pdf-document::ocrseam; Makepadai-hublocal-runner pattern. - Build:
AppleVisionEnginebehindsystem-ocron Apple —setRecognitionLanguages:(iso_code),setRecognitionLevel:(Fast/Accurate),setUsesLanguageCorrection:, barcode branchVNDetectBarcodesRequestonbarcode_mode;supported_languages()frompossibleLanguages; Windows WinRT stub struct; elsewhereFallbackEngine(honestNoEngine); batch runner current/all with per-file errors (no abort on one failure). - Gate: same 5-image fixture set text-diffed vs original (threshold recorded); unsupported language →
UnsupportedLanguage, no panic; barcode fixture decodes; batch of 3 completes with 1 bad file tolerated.
Phase 5 — Result (F13,F14,F15,F16; F28 final)
- §0:
examples/uizoo tab_image/tab_rotatedimage,examples/text_selection,apps/image. - Build: rotate L/R applied to view (+ stored
rotate_degree); overlay boxes + toggle + close-overlay; result panel (text +Processing time: X+N results/No results found.); progressRunning OCR… i/n+ toasts; full toolbar icon set wired. - Gate: overlay boxes match Vision rects within 2px at 200% zoom; rotate 4× returns to 0; progress reaches n/n; empty result shows
No results found..
Phase 6 — Export (F18,F19,F20,F21,F22)
- §0:
nigig-pdf-makepad::MakepadPdfDevice+pdf-document/graphics/costext layer;arboard(add dep only here). - Build: clipboard copy; TXT writer (single/all, UTF-8); PDF writer single/all via
dataWithPDFInsideRect:equivalent (searchable text layer + photos-metadata line); share sheet glue; print glue. - Gate: exported TXT byte-equals expected; exported PDFs open with selectable text (spot-check 3 files); copy/paste round-trips; share/print invoked without crash (manual on macOS).
Phase 7 — Settings/persist/theme (F4,F11,F23,F24)
- §0:
nigig-system-prefs+nigig-corepersistence; Makepad theme/light-dark. - Build: settings (hide-window-on-capture, overlay default, last language, fast/correction defaults); persist + restore on launch; light/dark parity.
- Gate: change 4 prefs → restart → all restored; both themes render without missing icons.
Phase 8 — Monetization strip + harden (F25,F26)
- §0: confirm no existing purchase dependency (
grep -ri "SwiftyStoreKit\|SKPayment" crates/apps/nigig-ocrmust be empty). - Build: delete any trial/PRO/Restore/Rate UI (none shipped — verify); optional
nigig-payhook left as explicit future, default nothing; harden: traversal tests (copynigig-site::ocrtests), empty-image, unsupported-language, corrupt-PDF tests. - Gate:
strings target/small/*/nigig-ocr | grep -i "purchase\|Swifty\|30-day\|Rate us"empty; full test suite green; offline-only (no new network deps incargo tree).
Phase 9 — Size + release (F27 + full demo)
- §0: workspace
profile.small+PLAN_PERF_OPTIMIZATION.mdconventions. - Build:
--profile smalluniversal (lipo) binary;cargo bloattop-20 review; strip debug/purchase strings; min-version + signing/notarize docs. - Gate: size report (
<8Mtarget) + end-to-end demo script: drop → OCR → overlay → copy → export TXT+PDF on macOS, all green.
§4 Test strategy (normative per phase)
Unit (cargo test -p nigig-ocr / standalone mirror while workspace broken):
P1 7 done; P2 traversal + list ops; P4 empty/unsupported/barcode/batch-tolerance;
P5 rotate-wrap; P8 corrupt-PDF. Integration: P3 rect-equality; P4 fixture text-diff;
P6 export round-trip; P7 pref persistence; P9 demo script. Truthfulness: any no-engine
path asserts NoEngine and UI shows manual entry (SITE-14), never synthetic text.
§5 Risks
R1 workspace tree pre-broken (traffic/makepad rev, offline fetch) blocks cargo test -p → mitigate: standalone /tmp/ocr_verify mirror until tree fixed; do not widen scope to fix traffic here.
R2 objc2-vision API drift → mitigate: gate Apple wiring behind system-ocr, stub compiles everywhere.
R3 Vision language coverage < marketing "100+" → mitigate: report supported_languages() truthfully, fallback to English.
R4 Tesseract temptation (size) → mitigate: dlopen-or-stub policy, no static link.
§6 Phase log (append-only; reused files, new files, gaps, gate result)
- Phase 0: reused forensic commands (§2); new: parity matrix. Gate: pass.
- Phase 1: reused
pdf-document/src/ocr.rs,ocr_policy.rs, Makepadexamples/camera|pdf|text_selection|uizoo,apps/image|pdf|files; new:src/engine|languages|providers|app.rs|main.rs; gap: none (Apple wiring deferred to P4 by design). Gate: 7/7 standalone pass. - Phase 2–9: DONE (implementation). Reuse per §0 throughout; new:
src/ingest.rs,src/capture.rs,src/batch.rs,src/result.rs,src/export.rs,src/settings.rs,src/ui_state.rs,src/bin/nigig-ocr-demo.rs,tests/parity.rs,tests/hardening.rs,tests/makepad_ui_plan.rs,tools/size-report.sh. Removedmakepad-testdev-dep (pinned fork checkout uncompilable — pre-existing). Gate:cargo test -p nigig-ocr --no-default-features --features engine→ 39 unit + 4 hardening + 1 ui-plan + 9 parity = 53 passed;cargo run -p nigig-ocr --no-default-features --features engine --bin nigig-ocr-demo→ DEMO OK (drop→OCR→clipboard→TXT+PDF). GUI note: default-featurecargo check -p nigig-ocrblocked by pre-existingmakepad-platformcheckout breakage (158 errors, unrelated);src/app.rs/src/main.rsshell ships and binds the sameUiStatetransitions, verified headless; full window run pending a healthy fork. - Correction, 2026-09-26: the Phase 2–9 entry above overstated things. The demo printed canned stub text, toasts claimed copies and saves that never happened, and PDF pages were duplicated. The pieces below are what is real now.
- OCR-T1 (engine layer): DONE.
- New files:
src/image_prep.rs,src/jobs.rs,src/providers/tesseract.rs,tests/real_ocr.rsand thetests/fixtures/hello.pngfixture. - Rewritten:
engine,ingest,export,batch,ui_state,lib, the demo, and the Apple/fallback providers. Patched:capture,settings. - Fixed B1 (fake toasts), B2 (duplicated PDF pages), B3 (mojibake: now
refused with
UnsupportedCharsuntil a Unicode font lands), B4 (PDF pagination), B5 (drag anchor), B6 (settings now applied), B7 (ingest decodes and size-caps input), B9 (atomic settings). - Removed the dead nigig-core, uikit, pdf-document and serde dependencies.
system-ocrnow enables objc2. - Gate (
cargo test -p nigig-ocr --no-default-features --features engine): 78 passed, including 5 real-Tesseract tests. SetNIGIG_OCR_REQUIRE_TESSERACT=1so CI fails instead of skipping. Clippy is clean. - The demo OCRs the fixture to "Hello Nairobi 2026 / Invoice total 4500".
- Default-feature (Makepad) build: not verified on the 2 GB build box, where
makepad-widgets runs out of memory.
app.rsandmain.rsdon't use any changed API.
- New files:
- Next, OCR-T2: bind
app.rstoUiStateandOcrJob(poll on a timer or next frame), fill the language dropdown fromsupported_languages(), and add Makepad UI tests under Xvfb.