export function stringToCodepoints ( string ) { const result = [ ] ; for ( let i = 0 ; i < string . length ; i ++ ) { let code = string . charCodeAt ( i ) ; if ( code === 0x0d && string . charCodeAt ( i + 1 ) === 0x0a ) { code = 0x0a ; i ++ ; } if ( code === 0x0d || code === 0x0c ) code = 0x0a ; if ( code === 0x00 ) code = 0xfffd ; if ( code >= 0xd800 && code <= 0xdbff ) { const nextCode = string . charCodeAt ( i + 1 ) ; if ( nextCode >= 0xdc00 && nextCode <= 0xdfff ) { const lead = code - 0xd800 ; const trail = nextCode - 0xdc00 ; code = 2 ** 16 + lead * 2 ** 10 + trail ; i ++ ; } } result . push ( code ) ; } return result ; } const ZWJ = 0x200d ; const EMOJI_COMBINATOR = [ [ ZWJ , 1 ] , [ 0xfe0e , 2 ] , [ 0x1f3fb , 5 ] , [ 0x1f9b0 , 4 ] , [ 0xe0020 , 96 ] , ] ; let emojiCombinator ; const REGIONAL_INDICATOR = [ 0x1f1e6 , 0x1f1ff ] ; function isEmojiCombinator ( code ) { if ( emojiCombinator === undefined ) { emojiCombinator = { } ; for ( const x of EMOJI_COMBINATOR ) { for ( let i = x [ 0 ] ; i <= x [ 0 ] + x [ 1 ] - 1 ; i ++ ) { emojiCombinator [ i ] = true ; } } } return emojiCombinator [ code ] ?? false ; } function isRegionalIndicator ( code ) { return code >= REGIONAL_INDICATOR [ 0 ] && code <= REGIONAL_INDICATOR [ 1 ] ; } /**
 * Return a string or an array of graphemes.
 * This includes:
 * - emoji with skin and hair modifiers
 * - emoji combination (for example "female pilot")
 * - text emoji with an emoji presentation style modifier
 *      - U+1F512 U+FE0E 🔒︎
 *      - U+1F512 U+FE0F 🔒️
 * - flags represented as two regional indicator codepoints
 * - flags represented as a flag emoji + zwj + an emoji tag
 * - other combinations (for example, rainbow flag)
 */ export function splitGraphemes ( string ) { if ( /^[\u0020-\u00FF]*$/ . test ( string ) ) return string ; const result = [ ] ; const codePoints = stringToCodepoints ( string ) ; let index = 0 ; while ( index < codePoints . length ) { const code = codePoints [ index ++ ] ; const next = codePoints [ index ] ; if ( next === ZWJ ) { const baseIndex = index - 1 ; index += 2 ; while ( codePoints [ index ] === ZWJ ) { index += 2 ; } result . push ( String . fromCodePoint ( ... codePoints . slice ( baseIndex , index - baseIndex + 1 ) ) ) ; } else if ( isEmojiCombinator ( next ) ) { const baseIndex = index - 1 ; while ( isEmojiCombinator ( codePoints [ index ] ) ) { index += codePoints [ index ] === ZWJ ? 2 : 1 ; } result . push ( String . fromCodePoint ( ... codePoints . slice ( baseIndex , 2 * index - baseIndex - 1 ) ) ) ; } else if ( isRegionalIndicator ( code ) ) { index += 1 ; result . push ( String . fromCodePoint ( ... codePoints . slice ( index - 2 , 2 ) ) ) ; } else { result . push ( String . fromCodePoint ( code ) ) ; } } return result ; }