Skip to content

Add complex-scripts feature for CJK and Southeast Asian line/word breaking - #621

Merged
nicoburns merged 1 commit into
linebender:mainfrom
Linktime:feature/complex-scripts
May 24, 2026
Merged

Add complex-scripts feature for CJK and Southeast Asian line/word breaking#621
nicoburns merged 1 commit into
linebender:mainfrom
Linktime:feature/complex-scripts

Conversation

@Linktime

Copy link
Copy Markdown
Contributor

Summary

Adds a complex-scripts Cargo feature that switches the line and word segmenters from the lightweight new_for_non_complex_scripts() to new_dictionary(), enabling proper dictionary-based line and word breaking for complex scripts:

  • CJK (Chinese, Japanese) — uses the cjdict dictionary for word-boundary detection
  • Southeast Asian (Thai, Khmer, Lao, Myanmar) — uses per-language dictionaries

When the feature is disabled (default), behavior is unchanged: the lightweight segmenter is used, which falls back to character-level breaks for the above scripts.

Motivation

Without this feature, rendering CJK text produces a runtime warning from ICU4X:

ICU4X data error: No segmentation model for language: ja

This occurs because new_for_non_complex_scripts() does not include the cjdict dictionary data. CJK text still renders, but line breaks are character-level rather than word-level.

Usage

parley = { features = ["complex-scripts"] }

Changes

  • parley/Cargo.toml: add complex-scripts feature flag
  • parley/src/analysis/mod.rs: cfg-gate line_segmenter and word_segmenter to use LineSegmenter::new_dictionary() / WordSegmenter::new_dictionary() when enabled
  • CHANGELOG.md: document new feature

Testing

Both configurations compile cleanly:

  • cargo check -p parley — default (no feature)
  • cargo check -p parley --features complex-scripts — with feature enabled

…word breaking

When enabled, uses ICU4X dictionary-based segmenters (LineSegmenter::new_dictionary,
WordSegmenter::new_dictionary) that include CJK (Chinese, Japanese) and Southeast Asian
(Thai, Khmer, Lao, Myanmar) dictionaries for proper line and word breaking.

When disabled (default), the lightweight non-complex segmenter is used, which falls back
to character-level breaks for those scripts.

Usage:
  parley = { features = ["complex-scripts"] }
@Linktime Linktime changed the title Add feature for CJK and Southeast Asian line/word breaking Add complex-scripts feature for CJK and Southeast Asian line/word breaking May 14, 2026

@nicoburns nicoburns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks! This looks great. Sorry for the delayed review: most of the Parley developers were at RustWeek last week.

Comment on lines 58 to 74
match word_break_strength {
WordBreak::Normal => {
const {
let mut opt = LineBreakOptions::default();
opt.word_option = Some(LineBreakWordOption::Normal);
LineSegmenter::new_for_non_complex_scripts(opt)
}
let mut opt = LineBreakOptions::default();
opt.word_option = Some(LineBreakWordOption::Normal);
line_segmenter_impl(opt)
}
WordBreak::BreakAll => {
const {
let mut opt = LineBreakOptions::default();
opt.word_option = Some(LineBreakWordOption::BreakAll);
LineSegmenter::new_for_non_complex_scripts(opt)
}
let mut opt = LineBreakOptions::default();
opt.word_option = Some(LineBreakWordOption::BreakAll);
line_segmenter_impl(opt)
}
WordBreak::KeepAll => {
const {
let mut opt = LineBreakOptions::default();
opt.word_option = Some(LineBreakWordOption::KeepAll);
LineSegmenter::new_for_non_complex_scripts(opt)
}
let mut opt = LineBreakOptions::default();
opt.word_option = Some(LineBreakWordOption::KeepAll);
line_segmenter_impl(opt)
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This could probably be simpliflied to:

#[inline(always)]
fn line_segmenter(&self, word_break_strength: WordBreak) -> LineSegmenterBorrowed<'static> {
    let mut opt = LineBreakOptions::default();
    opt.word_option = Some(match word_break_strength {
        WordBreak::Normal => LineBreakWordOption::Normal,
        WordBreak::BreakAll => LineBreakWordOption::BreakAll,
        WordBreak::KeepAll => LineBreakWordOption::KeepAll,
    });
    line_segmenter_impl(opt)
}

But I'm not going to require it as part of this PR seeing it was a pre-existing issue.

@nicoburns
nicoburns added this pull request to the merge queue May 24, 2026
Merged via the queue into linebender:main with commit b95564a May 24, 2026
45 of 48 checks passed
@robertbastian

Copy link
Copy Markdown
Contributor

There was an existing PR for this that would allow adding models more granularly, and at runtime: #533

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.

3 participants