Skip to content

Adding support for Mokuro OCR files - #59

Open
jdrobert wants to merge 3 commits into
classicsc:mainfrom
jdrobert:mokuro-ocr
Open

jdrobert wants to merge 3 commits into
classicsc:mainfrom
jdrobert:mokuro-ocr

Conversation

@jdrobert

Copy link
Copy Markdown

This PR adds support for Mokuro OCR files.

I used Claude for this change but reviewed all the code and did manual verification.

Screenshots

Gif

Simulator Screen Recording - iPhone 17 - 2026-09-13 at 07 10 22

iPad

Simulator Screenshot - iPad (A16) - 2026-09-13 at 09 28 49 Simulator Screenshot - iPad (A16) - 2026-09-13 at 09 38 35

Testing

A. Database migration — no data loss

This is the highest-consequence risk on the branch. If lightweight migration
fails, loadPersistentStores calls fatalError, so the app hard-crashes on
launch
with no recovery path. There is an automated V2→V3 test, but it uses a
synthetic store — this section verifies a real one.

A0. Setup — do this first, or migration is never exercised

Running this branch on a clean simulator creates a V3 store directly and proves
nothing. You must upgrade over an existing V2 store.

  • Check out main (or any commit before this branch) and run the app
  • Import at least 2 manga, ideally 3+ (one large, one small)
  • Read a few pages into each so lastReadPage is non-zero
  • Change reading direction on one manga (e.g. to left-to-right)
  • Edit metadata (title/author) on one manga
  • Note down, for each manga: title, author, current page number, reading direction
  • Do NOT delete the app — deleting it wipes the store and destroys the test
  • Now build and run this branch onto the same simulator

A1. Migration succeeds

  • App launches without crashing (a crash here = migration failure)
  • Launch is not pathologically slow / no watchdog kill on first launch
  • Library list shows the same manga, same count as before

A2. No data lost

For each manga noted in A0:

  • Title matches
  • Author matches
  • Cover image still displays (not a placeholder)
  • Page count matches
  • Reading progress ("N / M") matches what it was
  • Opening it resumes on the same page
  • Reading direction preference survived
  • Edited metadata survived (and is still flagged as user-edited, not re-extracted)

A3. Post-migration writes work

  • Read further in a migrated manga, close, reopen → new progress persisted
  • Change reading direction on a migrated manga → persists across app restart
  • Import a new manga after migrating → works and persists
  • Attach a mokuro file to a migrated manga (not just a freshly imported one)
  • Delete a migrated manga → disappears and stays gone after restart

A4. Second launch

  • Quit and relaunch → still fine (proves the store was actually migrated, not
    re-migrated or left in a half-state each launch)

B. Existing functionality — regressions

These surfaces were touched by the TextCluster refactor (.observations
.lines) or by the file-picker consolidation. All three OCR surfaces consume the
same TextCluster type, so a mistake in the refactor shows up in more than one.

B1. Archive import — HIGHEST regression risk

The two .fileImporter modifiers were collapsed into one mode-driven importer.
If that's wrong, the existing Import button breaks.

  • Toolbar + Import button opens the file picker
  • Picker lists/permits .cbz and .zip files
  • Selecting a CBZ imports it successfully with cover + page count
  • Import a ZIP as well
  • Cancelling the picker leaves no partial/broken library entry
  • Import twice in a row without restarting (state isn't left stuck after the first)
  • Immediately after attaching a mokuro file, the + Import button still works
    (proves the shared picker's mode resets correctly)

B2. Manga reader — OCR and interaction

  • Open a manga with no mokuro attached → boxes are blue/green as before
  • Vertical text boxes are blue and dashed; horizontal are green and solid
  • Tapping a box opens the dictionary with the right text
  • Tap outside any box → page turn / toolbar toggle behaves as before
  • Pinch zoom, pan, double-tap zoom all still work
  • Page swipe navigation works in right-to-left and left-to-right
  • Toggle bounding boxes off → boxes hide, tapping still opens dictionary
  • Spread mode (landscape) renders and boxes land correctly on both pages —

B3. Other OCR surfaces (same TextCluster type)

  • Screenshot / image OCR (OCR scan view): boxes render, tap-to-lookup works,
    colors are blue/green
  • Web reading mode (MaruWeb overlay): boxes render and tap-to-lookup works
  • Share extension: share an image into the app → OCR results appear correctly

B4. Library management

  • Context menu opens on long-press
  • "Edit Metadata" still opens its sheet and saves
  • "Delete" still deletes, with confirmation
  • After deleting, the archive file and cover are gone from disk (not just the row)
  • Sort by Title / Author / Date Added all still work
  • Switching Books/Manga library tabs works

C. New mokuro functionality

C1. Attach / remove

  • "Attach Mokuro File..." appears in the context menu for every manga
  • Tapping it opens the picker
  • Picker lists .mokuro files rather than greying them all out
  • Attaching succeeds with no error
  • "Remove Mokuro File" appears only after a file is attached
  • Its icon renders
  • "Remove Mokuro File" removes it; the menu item disappears again
  • Re-attaching a different mokuro file to the same manga replaces the first

C2. OCR source is correct

Use a .mokuro generated from the CBZ itself (mokuro "my_manga.cbz" --unzip),
so img_path names match the archive's entry names.

Box colours encode both source and direction:

Colour Source Direction
Red Mokuro Vertical
Purple Mokuro Horizontal
Blue On-device Vertical
Green On-device Horizontal
  • Pages show red/purple boxes → mokuro data is in use
  • Boxes sit over the correct text (spot-check 5+ pages spread through the volume)
  • Tapping a box gives the dictionary entry for that bubble, on that page
  • A page with genuinely no text shows no boxes and doesn't fall back to Vision
  • Remove the mokuro file → boxes go back to blue/green (Vision) on the same pages
  • With a mismatched mokuro file, boxes stay blue/green (safe fallback, no wrong
    text). Pairing is filename-only, so this holds even when the page counts are
    identical

D. Known limitations — expected behaviour, not bugs

Listed so they aren't mistaken for regressions during review.

  • Volume-mismatch handling is log-only. A page-count mismatch logs a warning
    but never blocks an attach. Deliberate: rejecting would block legitimate files
    (e.g. a mokuro run that included a cover the CBZ lacks). It does mean attaching
    the wrong volume of a series can still show wrong text, since generic 001.jpg
    names pair against anything.
  • Pairing is by image filename only. A positional fallback was implemented and
    removed: mokuro orders its pages array alphabetically by img_path, which need
    not be the archive's reading order, so equal page counts do not imply equal
    ordering — a real 166-page volume was still misaligned by one. A file whose names
    don't match falls back to Vision rather than guessing. Generating the .mokuro
    from the CBZ makes the names line up.
  • No test proves Vision was invoked on the fallback path — that would need DI
    to spy on the OCR actor. The pairing contract is unit-tested instead.
  • .mokuro has no declared UTI, so UTType(filenameExtension:) yields a
    dynamic type. Picker filtering is covered by C1.

E. Before opening the PR

  • just test-plan MaruMangaTests green
  • just test-plan MaruVisionTests green
  • just test-plan MaruWebTests green (MaruWebTests.swift was modified by the refactor)
  • just build Debug clean
  • Japanese translations for the 4 new strings render correctly in a ja locale run

@classicsc

Copy link
Copy Markdown
Owner

Will look at this for v1.3.

@classicsc classicsc left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like clearer feedback on attaching a Mokuro file. Test for page matches at that time and reject it if there's no applicable data. If it's a partial match, warn about it.

Comment thread MaruManga/MangaImportManager.swift Outdated
Comment thread MaruManga/MangaImportManager.swift Outdated
Comment thread MaruManga/Views/MangaLibraryView.swift Outdated
Comment thread MaruWebTests/OCRBoundingBoxAppearanceTests.swift Outdated
@jdrobert

Copy link
Copy Markdown
Author

Thanks for your review. I should have time this weekend to fix all everything.

@classicsc

Copy link
Copy Markdown
Owner

Thanks for your review. I should have time this weekend to fix all everything.

I'm going to be away for at least a week or more, so don't worry if it takes a while to hear back on the changes.

@jdrobert
jdrobert requested a review from classicsc September 20, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants