|
| 1 | +# BigOCRPDF maintenance guide |
| 2 | + |
| 3 | +This document records durable implementation contracts. Temporary plans, audit |
| 4 | +checklists, command logs, screenshots, and generated benchmark exports do not |
| 5 | +belong in the source tree. |
| 6 | + |
| 7 | +## Ownership map |
| 8 | + |
| 9 | +- `src/bigocrpdf/application.py`, `window.py`, and `image_application.py` own |
| 10 | + application lifecycle and top-level GTK state. |
| 11 | +- `src/bigocrpdf/ui/` owns presentation, focus, actions, and main-loop delivery. |
| 12 | +- `src/bigocrpdf/services/settings.py` owns the flat settings API consumed by the UI. |
| 13 | +- `src/bigocrpdf/services/processor.py` owns queue and OCR orchestration. |
| 14 | +- `src/bigocrpdf/services/rapidocr_service/` owns OCR, PDF inspection, geometry, |
| 15 | + text-layer generation, compression, and worker protocols. |
| 16 | +- `src/bigocrpdf/services/export_service.py` and the ODF/Markdown owners implement |
| 17 | + document exports. |
| 18 | +- `src/bigocrpdf/utils/config_manager.py`, `durable_writes.py`, history, and |
| 19 | + checkpoint modules own durable application state. |
| 20 | +- `usr/share/`, `tools/stage-data.sh`, `pkgbuild/PKGBUILD`, and `default.nix` own |
| 21 | + installed desktop integration. |
| 22 | + |
| 23 | +Keep ownership names domain-specific. Do not add generic `manager`, `helper`, or |
| 24 | +`util` modules to avoid choosing an owner. |
| 25 | + |
| 26 | +## Runtime contract |
| 27 | + |
| 28 | +BigOCRPDF is a Linux GTK 4.22+/libadwaita application on Python 3.12+. Python remains responsible for |
| 29 | +GTK state, asynchronous delivery, OCR orchestration, and PDF/document models. |
| 30 | +Shell is limited to validation, staging, and packaging. |
| 31 | + |
| 32 | +The base runtime includes GTK4, libadwaita, RapidOCR with the PP-OCRv6 small |
| 33 | +model, OpenVINO, Poppler tools, pikepdf, Pillow, OpenCV, NumPy, SciPy, ReportLab, |
| 34 | +and odfpy. ONNX Runtime is an optional inference backend. `jbig2enc` is optional; |
| 35 | +when it is absent, bilevel output must continue through CCITT Group 4. |
| 36 | + |
| 37 | +Subprocess calls use direct argv. A zero exit status is authoritative success; |
| 38 | +nonzero status, timeout, or signal is an explicit error/cancellation result. |
| 39 | + |
| 40 | +## OCR and PDF invariants |
| 41 | + |
| 42 | +- CPU OCR remains fully functional. |
| 43 | +- The supported recognition model is PP-OCRv6 small with its unified language |
| 44 | + coverage. Do not reconstruct legacy language-model selection. |
| 45 | +- OCR configuration, including engine, DPI, thresholds, batch size, and render |
| 46 | + limits, travels through `OCRConfig`; do not rebuild a partial settings object. |
| 47 | +- Unicode searchable PDFs use real extractable text. Validation must include |
| 48 | + `pdftotext`, not only inspection of internal strings. |
| 49 | +- The text layer preserves page geometry and keeps OCR/native-text detection |
| 50 | + working after crop, deskew, dewarp, rotation, and mixed-content processing. |
| 51 | +- PDF resource limits and hostile-input checks remain enforced. |
| 52 | +- Structured `*.bigocr.json` sidecars are the preferred source for TXT, |
| 53 | + Markdown, and ODT export. Version 2 binds the payload to the PDF byte size and |
| 54 | + SHA-256 and records either `document` or `unavailable`; a stale fingerprint or |
| 55 | + explicit unavailable marker requires PDF fallback. Version 1 can be decoded |
| 56 | + only through the explicit `allow_unverified_legacy` compatibility opt-in; it |
| 57 | + is never authoritative by default. Fallback behavior must remain explicit. |
| 58 | +- Positioned ODT keeps editable text at page coordinates; reflowable ODT keeps |
| 59 | + paragraphs, tables, and columns; empty pages remain present. |
| 60 | + |
| 61 | +OCR or geometry changes require a focused regression plus a representative |
| 62 | +benchmark. File-size or line-count pressure alone is not a reason to rewrite an |
| 63 | +algorithm. |
| 64 | + |
| 65 | +## Durable state |
| 66 | + |
| 67 | +Configuration, history, checkpoints, and published output are user data. |
| 68 | + |
| 69 | +- Writes use a temporary file on the destination filesystem, flush and fsync the |
| 70 | + file, replace atomically, fsync the directory, and clean up on failure. |
| 71 | +- Published files use private reflink/copy snapshots, so a staged path that cannot |
| 72 | + be removed can never remain a mutable hardlink to user-visible output. |
| 73 | +- Multi-file publication is restricted to one canonical destination directory. |
| 74 | + Its private `PREPARING` -> `PREPARED` -> `COMMITTED`/`ROLLED_BACK` journal makes |
| 75 | + interrupted batches converge to the complete old or new set. Recovery runs |
| 76 | + before the next publication in that directory and is also available through |
| 77 | + `recover_pending_publications()`. |
| 78 | +- Every OCR PDF is published in the same transaction as its versioned sidecar. |
| 79 | + A split output publishes all PDF/sidecar pairs as one set. Sidecar generation |
| 80 | + failure fails the input rather than exposing a PDF with stale metadata. |
| 81 | +- Non-overwriting multi-file publication applies one collision counter to the |
| 82 | + complete batch. Domain callers that derive companion names provide their |
| 83 | + candidate family under the destination-directory lock. |
| 84 | +- POSIX cannot make several destination names visible as one atomic operation. |
| 85 | + Readers may observe a mixed set during the interruption window, but journal |
| 86 | + recovery never accepts that mixed set as the final state. |
| 87 | +- Rollback currently restores destination names, bytes, and regular access mode. |
| 88 | + Extended attributes, ACLs, and timestamps are not part of that recovery |
| 89 | + contract; callers must not use publication to preserve those metadata. |
| 90 | +- Existing configuration keys and migrations remain readable unless a documented |
| 91 | + migration with rollback replaces them. |
| 92 | +- Corrupt or partial input must not destroy the last readable state. |
| 93 | +- Destructive operations are idempotent and scoped to product-owned temporary |
| 94 | + paths or an explicitly confirmed user target. |
| 95 | + |
| 96 | +## UI contract |
| 97 | + |
| 98 | +The main journey is queue -> settings -> processing -> results. The image journey |
| 99 | +is source selection/capture -> processing -> copyable result. Visible state comes |
| 100 | +from the queue, dependency probe, worker outcome, and editor model; the UI must not |
| 101 | +announce readiness or success before those sources resolve. |
| 102 | + |
| 103 | +Render information already held by the caller immediately. File page counts, |
| 104 | +Poppler metadata, thumbnails, and other I/O must not block the GTK main loop. |
| 105 | +Worker results may update only a still-live row/document. |
| 106 | + |
| 107 | +Required controls are keyboard reachable and have stable AT-SPI names. Icon-only |
| 108 | +actions have a matching accessible name and native tooltip. Status changes need a |
| 109 | +real accessible event; an invisible label is not proof of an announcement. |
| 110 | + |
| 111 | +Use native GTK/libadwaita components and theme tokens. Do not add visual decoration |
| 112 | +or custom transient widgets when the toolkit already owns the interaction. |
| 113 | + |
| 114 | +The supported floor is GTK 4.14 with libadwaita 1.5, which is what an AppImage |
| 115 | +built on Ubuntu 24.04 carries; it is not the version we develop against. Widgets |
| 116 | +introduced after that floor go through `utils/adw_compat.py`, which probes with |
| 117 | +`hasattr` -- a symbol can be missing because the library is old or because its |
| 118 | +typelib was bundled incompletely -- and falls back to an equivalent that renders |
| 119 | +the same. `tests/test_adw_compat.py` exercises both branches and fails if such a |
| 120 | +widget is used directly anywhere else. |
| 121 | + |
| 122 | +Every symbolic icon the interface references is bundled under |
| 123 | +`src/bigocrpdf/resources/icontheme/` and registered as a private icon theme by |
| 124 | +`utils/icons.py` during application startup, so the interface renders identically |
| 125 | +on any host and inside an AppImage. Adding an `icon-name` means adding the |
| 126 | +matching SVG; `tests/test_icons.py` enforces this in both directions. The theme |
| 127 | +inherits Adwaita and hicolor, so unbundled names still resolve. |
| 128 | + |
| 129 | +Changed user-visible journeys require all three forms of evidence: |
| 130 | + |
| 131 | +1. a rendered 1280x720 screenshot review; |
| 132 | +2. AT-SPI roles, names, states, and actions; |
| 133 | +3. the real action's observable side effect. |
| 134 | + |
| 135 | +Measure 25, 100, and 500 queue/editor objects before considering list |
| 136 | +virtualization. Keep the current structure when it meets the measured budget. |
| 137 | + |
| 138 | +## Internationalization and desktop metadata |
| 139 | + |
| 140 | +`locale/bigocrpdf.pot` and `locale/*.po` are the gettext text sources. Compiled |
| 141 | +MO files are build artifacts produced by `tools/stage-data.sh` and are not |
| 142 | +versioned. |
| 143 | + |
| 144 | +Translations embedded in `.desktop`, `.desktop.in`, and Nemo metadata remain in |
| 145 | +those files. They are not extracted to or generated from PO catalogs. Staging |
| 146 | +copies this content unchanged, then renames KIO `.desktop.in` files in the |
| 147 | +destination only. |
| 148 | + |
| 149 | +Every PO must validate without fuzzy, obsolete, or untranslated entries. Preserve |
| 150 | +placeholders, plural forms, Pango/XML markup, and accelerators. |
| 151 | + |
| 152 | +`utils/i18n.py` resolves the catalog directory at runtime, in this order: |
| 153 | +`BIGOCRPDF_LOCALE_DIR`, `$APPDIR/usr/share/locale`, `$TEXTDOMAINDIR` (exported by |
| 154 | +the AppRun that appimage-creator generates), directories walked up from the |
| 155 | +installed package, then `sys.prefix` and `/usr/share/locale`. A candidate only |
| 156 | +counts when it actually holds `*/LC_MESSAGES/bigocrpdf.mo`, so a relocatable |
| 157 | +build never silently falls through to a stale host catalog. The domain is bound |
| 158 | +on both `gettext` and the C library, the latter covering strings GLib translates |
| 159 | +on our behalf. |
| 160 | + |
| 161 | +## Benchmarks |
| 162 | + |
| 163 | +Benchmark sources and commands live in `benchmarks/prepare_benchmark_datasets.py`, |
| 164 | +`benchmarks/ocr_benchmark.py`, `benchmarks/validate_text_layer.py`, and |
| 165 | +`benchmarks/compare_benchmarks.py`. Raw datasets and generated PDF/ODT/TXT/Markdown |
| 166 | +exports stay outside the repository. Keep only compact READMEs, manifests, JSONL, |
| 167 | +CSV, and JSON summaries needed to interpret or reproduce a run. |
| 168 | + |
| 169 | +The retained July 2026 baselines establish the following reference points: |
| 170 | + |
| 171 | +- all 36 RapidOCR v6 CPU matrix runs completed and produced text layers; |
| 172 | +- ONNX Runtime small was fastest in that six-page run, but startup/model loading |
| 173 | + was included and the sample is not a universal engine ranking; |
| 174 | +- complex FUNSD pages exposed recognition and layout limits; |
| 175 | +- structured sidecars improved export token recall and restored table structures |
| 176 | + in two of three dense forms; |
| 177 | +- the form separator refinement preserved recall and table counts. |
| 178 | + |
| 179 | +Do not report a roadmap, read document, or generated artifact as implemented |
| 180 | +behavior. A new performance claim includes command, dataset manifest, environment, |
| 181 | +before/after metrics, and failure count. |
| 182 | + |
| 183 | +## Validation and release |
| 184 | + |
| 185 | +Run the canonical local gate: |
| 186 | + |
| 187 | +```bash |
| 188 | +bash tools/validate.sh |
| 189 | +``` |
| 190 | + |
| 191 | +The gate covers Python compilation, Ruff, formatting, Pyright, gettext freshness |
| 192 | +and completeness, staged desktop/AppStream data, ShellCheck, shfmt, pytest, and the |
| 193 | +frozen CLI/settings compatibility snapshot in `tools/compatibility-baseline.json`. |
| 194 | +CI runs that gate in Arch Linux so its Python and GTK versions satisfy the declared |
| 195 | +Python 3.12 and GTK 4.22 runtime floors. |
| 196 | + |
| 197 | +Before release, also build and inspect the Arch package, run `nix flake check` and |
| 198 | +`nix build` in a working Nix environment, inspect installed file ownership and |
| 199 | +permissions, and exercise the installed package in a disposable VM. Verify KIO, |
| 200 | +Nemo, Nautilus, desktop launchers, OCR without `jbig2enc`, and a real Unicode |
| 201 | +`pdftotext` round trip. |
| 202 | + |
| 203 | +## Reduction policy |
| 204 | + |
| 205 | +Prefer deletion, direct imports, and existing owners. A new file or abstraction is |
| 206 | +acceptable only when it removes more duplicated behavior and shortens callers. |
| 207 | +Tests cover distinct observable regressions, not getters, constants, wrappers, |
| 208 | +private call order, or toolkit behavior. A compiler, type checker, authoritative |
| 209 | +metadata validator, or existing public-flow test is sufficient for mechanical |
| 210 | +changes it completely covers. |
0 commit comments