Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ jobs:
- name: Build (Release)
run: cmake --build build --config Release

# Builds the mmap cache for both stock dictionaries (plus a synthetic one
# with a multi-page entry), maps it, and checks every query against the
# text parse, along with stale/truncated/corrupt cache handling.
- name: Test dictionary cache
run: build\Release\canto-dicttest.exe traditional.txt simplified.txt

# Inno Setup is not preinstalled on windows-latest; Chocolatey is.
- name: Install Inno Setup
run: choco install innosetup --no-progress -y
Expand All @@ -48,6 +54,7 @@ jobs:
build\Release\CantoIME-SG.dll, `
build\Release\traditional.txt, `
build\Release\simplified.txt, `
build\Release\canto-mkcache.exe, `
installer\Output\CantoIME-Setup.exe dist\

- name: Upload artifact
Expand Down
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,18 @@ add_canto_variant(TW) # Taiwan (Traditional)
add_canto_variant(MO) # Macao SAR (Traditional)
add_canto_variant(CN) # China (Simplified)
add_canto_variant(SG) # Singapore (Simplified)

# Command-line tools sharing Dictionary.cpp, built into the same Release folder.
# canto-mkcache ships with the app: the installer runs it for the stock
# dictionaries and the options dialog for a custom one, so the
# IME maps a prebuilt cache instead of parsing text.
# canto-dicttest is the cache check CI runs (not shipped).
add_executable(canto-mkcache src/mkcache.cpp src/Dictionary.cpp)
add_executable(canto-dicttest src/dicttest.cpp src/Dictionary.cpp)
foreach(tool canto-mkcache canto-dicttest)
target_include_directories(${tool} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_compile_definitions(${tool} PRIVATE UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX)
if(MSVC)
target_compile_options(${tool} PRIVATE /W4 /permissive-)
endif()
endforeach()
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,29 @@ The dictionary is parsed on a **background thread**, so switching to CantoIME (o
changing the dictionary/overrides) never blocks the UI. Until the load finishes
— a fraction of a second — typing simply produces no candidates yet.

### Dictionary cache (mmap)

Parsing the text dictionary is the slow part of a load, so each dictionary also
gets a binary cache laid out for memory mapping. The IME maps it read-only and
answers lookups in place: a binary search over a small page index picks the one
4 KB page that holds a key, and only the pages actually touched are read from
disk. Layout (see `src/Dictionary.cpp`):

- **Header page:** magic, format version, the source file's size + last-write
time (a mismatch means stale), entry/block/page counts, region offsets.
- **Page index:** one entry per data block (first page, page count, first key),
page-aligned.
- **Data pages:** sorted entries packed into 4 KB pages, never straddling a
page, each page with its own entry count and offset table. An entry too big
for one page gets a run of whole pages to itself.

Stock dictionaries are cached next to the file (`traditional.txt.cache`) by the
installer via `canto-mkcache.exe`. Custom dictionaries are cached per user at
`%LOCALAPPDATA%\CantoIME\cache\<hash>.cache` (prebuilt when locked in through
the options dialog). A missing, stale, or damaged cache is never trusted: the
IME falls back to parsing the text and rewrites the cache for next time.
Overrides are kept in a small in-memory overlay consulted on top of the mapping.

---

## Repository layout
Expand All @@ -169,7 +192,9 @@ CantoIME/
├── EditSession.h # reusable ITfEditSession adapter (lambda callback)
├── CandidateWindow.h/.cpp # owner-drawn GDI popup, sizable CJK font
├── LangBarButton.h/.cpp # language-bar buttons (粵 icon, 中/英 mode toggle)
├── Dictionary.h/.cpp # UTF-8 loader + lookup + reload
├── Dictionary.h/.cpp # UTF-8 loader, mmap cache, lookup
├── mkcache.cpp # canto-mkcache.exe: prebuilds dictionary caches
├── dicttest.cpp # canto-dicttest.exe: cache check run in CI
├── Settings.h/.cpp # per-user options in HKCU\Software\CantoIME
├── OptionsDialog.h/.cpp # "Keyboard options" dialog (ITfFnConfigure::Show)
├── resource.h # dialog/control IDs
Expand Down
18 changes: 18 additions & 0 deletions installer/setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ Source: "..\build\Release\CantoIME-SG.dll"; DestDir: "{app}"; Check: IsVariant('
; reads; simplified.txt ships for future use. Removed with the app on uninstall.
Source: "..\traditional.txt"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\simplified.txt"; DestDir: "{app}"; Flags: ignoreversion
; Cache builder. Run post-install to prebuild the stock dictionaries' mmap
; caches, and by the options dialog when a custom dictionary is locked in.
Source: "..\build\Release\canto-mkcache.exe"; DestDir: "{app}"; Flags: ignoreversion 64bit

[Registry]
; Clean up the per-user options key on uninstall (not created at install time;
Expand All @@ -80,6 +83,21 @@ Filename: "{sys}\regsvr32.exe"; Parameters: "/s ""{app}\CantoIME-CN.dll"""; \
Filename: "{sys}\regsvr32.exe"; Parameters: "/s ""{app}\CantoIME-SG.dll"""; \
Check: IsVariant('SG'); StatusMsg: "Registering Singapore (Simplified) input method..."; \
Flags: runhidden waituntilterminated
; Prebuild the mmap cache for BOTH stock dictionaries. They are machine-wide,
; so the caches live next to them and serve every user; the IME maps them
; instead of parsing 10+ MB of text. Custom dictionaries are per-user and are
; cached under %LOCALAPPDATA% (by the options dialog, or on first load).
Filename: "{app}\canto-mkcache.exe"; \
Parameters: """{app}\traditional.txt"" ""{app}\simplified.txt"""; \
StatusMsg: "Preparing dictionaries..."; Flags: runhidden waituntilterminated

[UninstallDelete]
; Caches are generated after install (not tracked as installed files), so remove
; them explicitly, including any ".old" left from replacing a mapped cache, plus
; the per-user cache folder.
Type: files; Name: "{app}\*.cache"
Type: files; Name: "{app}\*.cache.old"
Type: filesandordirs; Name: "{localappdata}\CantoIME"

[UninstallRun]
; Unregister BEFORE files are removed. Only the variant DLL actually present in
Expand Down
Loading