Skip to content

word_segmenter: rename string_tokenizer and segment words on every platform - #548

Merged
boyan01 merged 1 commit into
mainfrom
word_segmenter/system-word-boundaries
Oct 20, 2026
Merged

boyan01 merged 1 commit into
mainfrom
word_segmenter/system-word-boundaries

Conversation

@boyan01

@boyan01 boyan01 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Summary

string_tokenizer is taken on pub.dev and only ran on macOS/iOS. This renames it to word_segmenter and makes it segment words on every platform with the operating system's dictionaries, which Flutter's bundled ICU leaves out (TextPainter.getWordBoundary splits Chinese per character). mixin_markdown_widget will use it for CJK double-click selection in a follow-up PR.

-packages/string_tokenizer/            # macOS, iOS
-  lib/string_tokenizer.dart           # tokenize(text, options) → List<String>
-  lib/src/*_bindings_generated.dart   # ffigen
-  ffigen.yaml
+packages/word_segmenter/              # all platforms
+  lib/word_segmenter.dart             # wordBoundaries(text) → List<TextRange>?
+  lib/src/segmenter_native.dart       # dart:ffi dispatch
+    core_foundation.dart              #   macOS/iOS: CFStringTokenizer
+    icu.dart                          #   Windows icu.dll, Linux libicuuc.so.N, Android libicu.so
+  lib/src/segmenter_web.dart          # Intl.Segmenter
+  lib/src/segmenter_unsupported.dart  # null
  • Segments are contiguous UTF-16 ranges covering the text; whitespace and punctuation are their own segments. null means no segmenter (e.g. Android < 31), so callers fall back.
  • Linux ICU exports versioned symbols (ubrk_open_74); the loader probes libicuuc.so.120 down to .50, with and without the suffix.
  • Bindings are hand-written lookups (5 functions per backend) instead of ffigen; this also fixes a leaked toNativeUtf8() buffer per call.
  • Example regenerated for all six platforms; CI gets Windows/macOS/Linux integration targets and a Chrome target for the unit tests.
  • Version and CHANGELOG untouched; publishing word_segmenter is a separate release step.

Migration for flutter-app (pinned to 66a8ed5, unaffected until it bumps):

-string_tokenizer.tokenize(e, options: [string_tokenizer.TokenizerUnit.wordBoundary])
+word_segmenter.wordBoundaries(e)!.map((r) => r.textInside(e))

Evidence

wordBoundaries('Hello, 世界和平') → [Hello, ",", " ", 世界, 和平] (macOS and Chrome).

Platform Run Result
macOS 27.0.1 flutter test, integration test -d macos 5/5, 2/2
Chrome 155 flutter test --platform chrome (JS and --wasm) 5/5, 5/5
Windows NT 10.0.26300 (icu.dll 72.1) remote-test unit + integration 5/5, 2/2
Ubuntu 22.04 (WSL) remote-test unit 5/5
Ubuntu 26.04 (VM, GTK app) remote-test integration 2/2
iOS Simulator (iPhone 16 Pro) integration test 2/2
Android 17 emulator (API 37) integration test 2/2

Tests assert contiguous coverage, Hello, world! → Hello | , | " " | world | !, 我们今天讨论一下 contains 我们 / 今天 / 讨论, 👍🏽 kept whole, empty text → [].

Before: the web build failed: Chrome threw TypeError: Cannot convert undefined or null to object in new Intl.Segmenter(null, …).
After: locales passed as an empty array; Chrome passes 5/5.

Not verified: Android below API 31 returning null (no image locally), Windows 10 1703–1809 icuuc.dll.

Merge Danger

Door: two-way

The package was never published; the pub.dev name is only claimed at release time.

Blast Radius: none

No published consumer. flutter-app depends on a pinned git commit of packages/string_tokenizer and keeps building; moving it to word_segmenter is a code change there (see migration). Touching root pubspec.yaml makes this PR run every integration target once.

…atform

`string_tokenizer` is taken on pub.dev and only covered macOS/iOS, returning
token strings without positions. Rename it to `word_segmenter` and replace the
API with `wordBoundaries(text)`, which returns contiguous UTF-16 ranges and
null when no segmenter is available.

Backends, all through the operating system so CJK text is split with its
dictionaries (Flutter's bundled ICU omits them):
- macOS/iOS: CFStringTokenizer
- Windows: icu.dll (icuuc.dll on 1703-1809)
- Linux: libicuuc.so.N with versioned symbols
- Android: NDK libicu.so (API 31+)
- web: Intl.Segmenter

The ffigen bindings are replaced by a few hand-written lookups, which also
fixes a leaked UTF-8 buffer per call. The example now targets all platforms
and runs an integration test on Windows, macOS and Linux in CI, plus the unit
tests in Chrome.

Migration: `tokenize(text, options: [TokenizerUnit.wordBoundary])` becomes
`wordBoundaries(text)!.map((r) => r.textInside(text))`. The other
CFStringTokenizer units are gone.
@boyan01
boyan01 merged commit 13071a4 into main Oct 20, 2026
16 checks passed
@boyan01
boyan01 deleted the word_segmenter/system-word-boundaries branch October 20, 2026 13:12
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.

1 participant