1use crate::grapheme::{analyze_grapheme, is_joining_type};
4use icu_properties::props::JoiningType;
5use icu_segmenter::GraphemeClusterSegmenter;
6
7#[derive(Clone, Copy, Debug, Default)]
23pub struct JoinedRunSegmenter;
24
25impl JoinedRunSegmenter {
26 pub const fn new() -> Self {
28 Self
29 }
30
31 pub fn segment_str(self, input: &str) -> impl Iterator<Item = usize> {
33 let grapheme_boundaries: Vec<usize> =
34 GraphemeClusterSegmenter::new().segment_str(input).collect();
35 let mut boundaries = vec![0];
36 let mut previous_type: Option<JoiningType> = None;
37 for pair in grapheme_boundaries.windows(2) {
38 let grapheme = analyze_grapheme(&input[pair[0]..pair[1]]);
39 if grapheme.joining_type == JoiningType::Transparent {
41 continue;
42 }
43 let can_join = is_joining_type(grapheme.joining_type);
45 let continues_run = can_join
46 && previous_type.is_some_and(|previous| {
47 previous != JoiningType::RightJoining
48 && grapheme.joining_type != JoiningType::LeftJoining
49 });
50 if !continues_run && pair[0] > 0 {
51 boundaries.push(pair[0]);
52 }
53 previous_type = can_join.then_some(grapheme.joining_type);
54 }
55 if !input.is_empty() {
56 boundaries.push(input.len());
57 }
58 boundaries.into_iter()
59 }
60
61 pub fn segment_utf16(self, input: &[u16]) -> impl Iterator<Item = usize> {
65 let text: String = char::decode_utf16(input.iter().copied())
66 .map(|c| c.unwrap_or(char::REPLACEMENT_CHARACTER))
67 .collect();
68 let mut chars = text.chars();
70 let (mut byte_offset, mut code_unit_offset) = (0, 0);
71 let boundaries: Vec<usize> = self
72 .segment_str(&text)
73 .map(|boundary| {
74 while byte_offset < boundary {
75 let c = chars.next().expect("boundaries fall between characters");
76 byte_offset += c.len_utf8();
77 code_unit_offset += c.len_utf16();
78 }
79 code_unit_offset
80 })
81 .collect();
82 boundaries.into_iter()
83 }
84}