///|
/// Unicode UAX #29 Grapheme Cluster Break (GCB) category.
/// Assigned to each code point and used for grapheme cluster boundary determination.
priv enum GCBCategory {
Other
CR
LF
Control
Extend
ZWJ
Regional_Indicator
Prepend
SpacingMark
L // Hangul Leading Jamo
V // Hangul Vowel Jamo
T // Hangul Trailing Jamo
LV // Hangul LV Syllable
LVT // Hangul LVT Syllable
Extended_Pictographic
InCB_Consonant
} derive(Eq)
///|
/// Returns the GCB category for a code point using two-stage table lookup.
/// O(1) constant time — two array accesses + bit operations.
///
/// The two-stage table covers Plane 0-2 (U+0000..U+02FFFF).
/// Plane 14 Tags are handled by branch logic:
/// E0000-E001F, E0080-E00FF, E01F0-E0FFF: Control
/// E0020-E007F, E0100-E01EF: Extend
/// Planes 3-13 and 15-16 are all Other.
fn gcb_category(cp : Int) -> GCBCategory {
if cp <= 0x02FFFF {
// Plane 0-2: two-stage table lookup
let block_idx = gcb_stage1[cp >> 8]
let offset = cp & 0xFF
let byte_val = gcb_stage2[block_idx.to_int() * 128 + (offset >> 1)]
let nibble = if (offset & 1) == 0 {
byte_val.to_int() >> 4
} else {
byte_val.to_int() & 0xF
}
match nibble {
1 => CR
2 => LF
3 => Control
4 => Extend
5 => ZWJ
6 => Regional_Indicator
7 => Prepend
8 => SpacingMark
9 => L
10 => V
11 => T
12 => LV
13 => LVT
14 => Extended_Pictographic
15 => InCB_Consonant
_ => Other
}
} else if cp >= 0xE0020 && cp <= 0xE007F {
Extend // Tag characters (emoji tag sequences)
} else if cp >= 0xE0100 && cp <= 0xE01EF {
Extend // Variation selectors supplement
} else if cp >= 0xE0000 && cp <= 0xE0FFF {
Control // Other Plane 14 Tags (E0000-E001F, E0080-E00FF, E01F0-E0FFF)
} else {
Other // Planes 3-13, 15-16
}
}
///|
/// Returns whether the code point is InCB=Linker.
/// Uses packed Bytes table (3 bytes per codepoint, big-endian, binary search).
fn is_incb_linker(cp : Int) -> Bool {
let entry_size = 3
let count = incb_linker_packed.length() / entry_size
let mut lo = 0
let mut hi = count - 1
while lo <= hi {
let mid = (lo + hi) / 2
let base = mid * entry_size
let val = (incb_linker_packed[base].to_int() << 16) |
(incb_linker_packed[base + 1].to_int() << 8) |
incb_linker_packed[base + 2].to_int()
if cp < val {
hi = mid - 1
} else if cp > val {
lo = mid + 1
} else {
return true
}
}
false
}
///|
/// Returns whether the code point is InCB=Extend.
/// Uses packed Bytes table (6 bytes per range: start 3B + end 3B, big-endian, binary search).
fn is_incb_extend(cp : Int) -> Bool {
let entry_size = 6
let count = incb_extend_packed.length() / entry_size
let mut lo = 0
let mut hi = count - 1
while lo <= hi {
let mid = (lo + hi) / 2
let base = mid * entry_size
let start = (incb_extend_packed[base].to_int() << 16) |
(incb_extend_packed[base + 1].to_int() << 8) |
incb_extend_packed[base + 2].to_int()
let end = (incb_extend_packed[base + 3].to_int() << 16) |
(incb_extend_packed[base + 4].to_int() << 8) |
incb_extend_packed[base + 5].to_int()
if cp < start {
hi = mid - 1
} else if cp > end {
lo = mid + 1
} else {
return true
}
}
false
}