///|
fn is_regional_indicator(code : Int) -> Bool {
code >= 0x1F1E6 && code <= 0x1F1FF
}
///|
let zwj = 0x200d
///|
/// Returns a next grapheme cluster break _after_ (not equal to) `pos`,
/// if `forward` is true, or before otherwise. Returns `pos` itself if no
/// further cluster break is available in the string. Moves across
/// surrogate pairs, extending characters (when `include_extending` is
/// true, which is the default), characters joined with zero-width joiners,
/// and flag emoji.
pub fn find_cluster_break(
str : String,
pos : Int,
forward? : Bool = true,
include_extending? : Bool = true,
) -> Int {
if forward {
next_cluster_break(str, pos, include_extending)
} else {
prev_cluster_break(str, pos, include_extending)
}
}
///|
fn next_cluster_break(str : String, pos : Int, include_extending : Bool) -> Int {
let mut pos = pos
if pos == str.length() {
return pos
}
// If pos is in the middle of a surrogate pair, move to its start
if pos > 0 &&
surrogate_low(char_code_at(str, pos)) &&
surrogate_high(char_code_at(str, pos - 1)) {
pos = pos - 1
}
let mut prev = code_point_at(str, pos)
pos = pos + code_point_size(prev)
while pos < str.length() {
let next = code_point_at(str, pos)
if prev == zwj ||
next == zwj ||
(include_extending && is_extending_char(next)) {
pos = pos + code_point_size(next)
prev = next
} else if is_regional_indicator(next) {
let mut count_before = 0
let mut i = pos - 2
while i >= 0 && is_regional_indicator(code_point_at(str, i)) {
count_before = count_before + 1
i = i - 2
}
if count_before % 2 == 0 {
break
} else {
pos = pos + 2
}
} else {
break
}
}
pos
}
///|
fn prev_cluster_break(str : String, pos : Int, include_extending : Bool) -> Int {
let mut pos = pos
while pos > 0 {
let found = next_cluster_break(str, pos - 2, include_extending)
if found < pos {
return found
}
pos = pos - 1
}
0
}
///|
fn char_code_at(str : String, pos : Int) -> Int {
str[pos].to_int() // Returns Int (character code) directly
}
///|
fn code_point_at(str : String, pos : Int) -> Int {
let code0 = char_code_at(str, pos)
if !surrogate_high(code0) || pos + 1 == str.length() {
return code0
}
let code1 = char_code_at(str, pos + 1)
if !surrogate_low(code1) {
return code0
}
(code0 - 0xd800) * 1024 + (code1 - 0xdc00) + 0x10000 // Using * instead of << 10
}
///|
fn surrogate_low(ch : Int) -> Bool {
ch >= 0xDC00 && ch < 0xE000
}
///|
fn surrogate_high(ch : Int) -> Bool {
ch >= 0xD800 && ch < 0xDC00
}
///|
fn code_point_size(code : Int) -> Int {
if code < 0x10000 {
1
} else {
2
}
}