# BadNote Local-first Surface Pen note-taking app with PDF/PPT annotation. All notes, documents, search, and OCR run on your device. No server is required to use the app. ## Features - Ink notes with Surface Pen (pressure, stabilizer, undo/redo) - PDF and PPT import with page-level annotation - Full-text search over note titles, typed text, and OCR results - **Local OCR** — pluggable, fully on-device. An embedded ONNX recognition backend (cross-platform, CPU/iGPU) with a graceful fallback to the platform's built-in OCR (Windows). See [Local OCR](#local-ocr). ## Build (Windows) Prerequisites: - Flutter SDK (3.10+) - Visual Studio Build Tools with **Desktop development with C++** - Developer Mode enabled (for Flutter plugin symlinks) ```powershell flutter pub get flutter build windows --release ``` Output: `build\windows\x64\runner\Release\badnote.exe` The `sqlite3` native binary is **vendored** under `vendor/sqlite3/` (configured via `hooks.user_defines` in `pubspec.yaml`), so the build does not download anything from GitHub — it works fully offline / behind a firewall. To add another platform or architecture, drop its official release binary from [sqlite3.dart releases](https://github.com/simolus3/sqlite3.dart/releases) into `vendor/sqlite3/` (the build validates each file's SHA-256). In mainland China, point pub/Flutter at the local mirrors: ```powershell $env:PUB_HOSTED_URL="https://pub.flutter-io.cn" $env:FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn" flutter pub get flutter build windows --release ``` ## Continuous integration `.gitea/workflows/ci.yml` builds a Windows release on a self-hosted **Windows** runner. Format/analyze/test run but are non-blocking, so a usable `.exe` is produced whenever the compile itself succeeds. It is written for runners behind the GFW: the checkout action comes from the `gitea.com` mirror, Flutter is expected to be pre-installed on the runner, pub uses `flutter-io.cn`, and the sqlite3 native binary is vendored. ### Self-hosted runner prerequisites These must hold on the runner machine (they can't be set from the workflow): 1. **Flutter SDK on `PATH`** in the runner's shell (the first build step prints `flutter --version` and fails fast if it isn't). 2. **Visual Studio Build Tools** with **Desktop development with C++** (MSVC + Windows SDK) — required to compile the Windows runner and the ONNX Runtime. 3. **The local proxy running** (default `http://127.0.0.1:7890`) so the `flutter_onnxruntime` build can fetch the ONNX Runtime native lib. Override via repo secrets `HTTP_PROXY` / `HTTPS_PROXY`, or install ONNX Runtime system-wide to skip the download. 4. **`gitea.com` reachable** (for the checkout action). If it isn't, switch to the manual-checkout fallback shown in `ci.yml` (it clones from your own Gitea instance), or set `DEFAULT_ACTIONS_URL=https://gitea.com` in the runner config and use bare `actions/checkout@v4`. 5. **Runner in host mode** with a sane work directory — a malformed workspace path (e.g. `C:\C:\...`) is an `act_runner` config problem, not a workflow one. ## Architecture ``` lib/ ├── screens/ # UI (notes, PDF/PPT annotator, search, settings) ├── services/ # Local business logic │ ├── database_service.dart # SQLite + FTS5 │ ├── ocr_service.dart # Local OCR orchestration │ ├── stroke_rasterizer.dart # Ink → PNG for OCR │ ├── ocr_engine.dart # OCR entry point (delegates to a backend) │ └── ocr/ # Pluggable OCR backends │ ├── ocr_backend.dart # Backend interface │ ├── ocr_backends.dart # Backend selector (ONNX → native) │ ├── onnx_recognition_backend.dart # Embedded ONNX (cross-platform) │ ├── native_ocr_backend.dart # OS OCR (Windows WinRT) │ └── ctc_decoder.dart # Pure-Dart CTC greedy decode ├── providers/ # Riverpod state └── widgets/ # Ink canvas, toolbars, thumbnails ``` OCR flow on save: 1. Extract typed text from text-tool strokes 2. Rasterize handwriting strokes to PNG 3. Recognize via the active local OCR backend (embedded ONNX if a model is bundled, otherwise the platform's native OCR) 4. Merge recognized text into the local FTS index for search ## Local OCR OCR runs entirely on-device through a pluggable backend (`lib/services/ocr/`). `OcrBackends` selects, in order: 1. **`OnnxRecognitionBackend`** — embedded, cross-platform recognition via `flutter_onnxruntime` (CPU/iGPU; suited to low-power APUs). Active only when an ONNX model is bundled. 2. **`NativeOcrBackend`** — the OS built-in OCR (Windows WinRT today). If no backend is available, OCR is a clean no-op — the app still works. ### Enabling the embedded ONNX model The model is **not committed** (it is large). Fetch it onto your dev machine before building so it bundles as an asset: ```bash tool/fetch_ocr_model.sh # downloads PP-OCRv4 rec ONNX + ppocr_keys_v1.txt # into assets/models/ocr/ (proxy hint inside) ``` See [assets/models/ocr/README.md](assets/models/ocr/README.md). The recognition geometry / CTC-blank assumptions (PP-OCRv4 mobile rec, 3×48×W, blank=0) are documented in `onnx_recognition_backend.dart` and should be verified on-device against your exact exported model. > **Windows build note:** the `flutter_onnxruntime` plugin downloads the ONNX > Runtime native library (v1.22.0) from GitHub at build time. Behind a firewall, > set `HTTPS_PROXY` for the build (CMake honours it), or install ONNX Runtime > system-wide and build with `-DUSE_SYSTEM_ONNXRUNTIME=ON > -DONNXRUNTIME_ROOT_DIR=`. ## Optional server The `server/` directory contains an experimental FastAPI backend (sync + EasyOCR). It is **not required** for the desktop app and is kept separately for future multi-device sync experiments. See [server/README.md](server/README.md). ## Development ```bash flutter run -d windows flutter test ```