text/emoji/sequence.go
Functions
func GetFlagCode
func GetFlagCode(seq Sequence) string {
if seq.Type != SequenceFlag || len(seq.Codepoints) != 2 {
return ""
}
a := seq.Codepoints[0]
b := seq.Codepoints[1]
if !IsRegionalIndicator(a) || !IsRegionalIndicator(b) {
return ""
}
// Regional indicators A-Z map to U+1F1E6-U+1F1FF
// Convert to ASCII letters
letterA := 'A' + (a - 0x1F1E6)
letterB := 'A' + (b - 0x1F1E6)
return string([]rune{letterA, letterB})
}
func GetSkinTone
GetSkinTone returns the skin tone from a modifier rune.
Returns SkinToneNone if the rune is not a skin tone modifier.
func GetSkinTone(r rune) SkinTone {
switch r {
case 0x1F3FB:
return SkinToneLight
case 0x1F3FC:
return SkinToneMediumLight
case 0x1F3FD:
return SkinToneMedium
case 0x1F3FE:
return SkinToneMediumDark
case 0x1F3FF:
return SkinToneDark
default:
return SkinToneNone
}
}
func GetTagSequenceCode
GetTagSequenceCode extracts the subdivision code from a tag sequence.
Returns empty string if not a valid tag sequence.
func GetTagSequenceCode(seq Sequence) string {
if seq.Type != SequenceTag || len(seq.Codepoints) < 3 {
return ""
}
// Skip black flag and cancel tag, extract middle tag characters
code := make([]rune, 0, len(seq.Codepoints)-2)
for i := 1; i < len(seq.Codepoints)-1; i++ {
r := seq.Codepoints[i]
if !IsTagCharacter(r) {
return ""
}
// Convert tag character to ASCII (E0020-E007E -> 0020-007E)
code = append(code, r-0xE0000)
}
return string(code)
}
func HasModifier
HasModifier returns true if the sequence has a skin tone modifier.
func (s Sequence) HasModifier() bool {
return s.Modifier != 0
}
func IsValidSequence
IsValidSequence returns true if the sequence is well-formed according
to Unicode emoji specification.
func IsValidSequence(seq Sequence) bool {
if len(seq.Codepoints) == 0 {
return false
}
switch seq.Type {
case SequenceSimple:
return len(seq.Codepoints) == 1 && IsEmoji(seq.Codepoints[0])
case SequenceFlag:
return len(seq.Codepoints) == 2 &&
IsRegionalIndicator(seq.Codepoints[0]) &&
IsRegionalIndicator(seq.Codepoints[1])
case SequenceKeycap:
if len(seq.Codepoints) < 2 || len(seq.Codepoints) > 3 {
return false
}
if !IsKeycapBase(seq.Codepoints[0]) {
return false
}
last := seq.Codepoints[len(seq.Codepoints)-1]
return IsCombiningEnclosingKeycap(last)
case SequenceTag:
if len(seq.Codepoints) < 3 {
return false
}
if !IsBlackFlag(seq.Codepoints[0]) {
return false
}
if !IsCancelTag(seq.Codepoints[len(seq.Codepoints)-1]) {
return false
}
// All middle characters must be tags
for i := 1; i < len(seq.Codepoints)-1; i++ {
if !IsTagCharacter(seq.Codepoints[i]) {
return false
}
}
return true
case SequenceModified:
if len(seq.Codepoints) < 2 {
return false
}
if !IsEmojiModifierBase(seq.Codepoints[0]) {
return false
}
return seq.HasModifier()
case SequenceZWJ:
// Must contain at least one ZWJ
hasZWJ := false
for _, r := range seq.Codepoints {
if IsZWJ(r) {
hasZWJ = true
break
}
}
return hasZWJ
case SequencePresentation:
// Must have variation selector
hasVS := false
for _, r := range seq.Codepoints {
if IsVariationSelector(r) {
hasVS = true
break
}
}
return hasVS
default:
return false
}
}
func Len
Len returns the number of codepoints in the sequence.
func (s Sequence) Len() int {
return len(s.Codepoints)
}
func Normalize
Normalize normalizes an emoji sequence by:
- Removing text variation selectors (U+FE0E)
- Ensuring emoji variation selector (U+FE0F) where needed
- Validating skin tone modifiers are only on valid bases
func Normalize(seq Sequence) Sequence {
if len(seq.Codepoints) == 0 {
return seq
}
normalized := make([]rune, 0, len(seq.Codepoints))
prevBase := rune(0)
for _, r := range seq.Codepoints {
// Skip text variation selector
if IsTextPresentation(r) {
continue
}
// Skip emoji variation selector (we'll add it back if needed)
if IsEmojiVariation(r) {
// Add FE0F for text-presentation emoji that need it
if isTextPresentationEmoji(prevBase) {
normalized = append(normalized, r)
}
continue
}
// Skip invalid modifiers
if IsEmojiModifier(r) {
if !IsEmojiModifierBase(prevBase) {
continue
}
}
normalized = append(normalized, r)
if !IsZWJ(r) && !IsEmojiModifier(r) && !IsVariationSelector(r) {
prevBase = r
}
}
return Sequence{
Codepoints: normalized,
Type: seq.Type,
BaseCodepoint: seq.BaseCodepoint,
Modifier: seq.Modifier,
}
}
func Parse
Parse parses emoji sequences from a slice of runes.
Returns a slice of Sequence values, where each represents
a complete emoji (possibly multi-codepoint).
func Parse(runes []rune) []Sequence {
if len(runes) == 0 {
return nil
}
sequences := make([]Sequence, 0, len(runes))
i := 0
for i < len(runes) {
seq, consumed := parseSequenceAt(runes[i:])
if consumed > 0 {
sequences = append(sequences, seq)
i += consumed
} else {
// Skip non-emoji character
i++
}
}
return sequences
}
func ParseString
ParseString is a convenience function that parses emoji sequences from a string.
func ParseString(text string) []Sequence {
return Parse([]rune(text))
}
func SkinToneRune
SkinToneRune returns the rune for a skin tone.
Returns 0 for SkinToneNone.
func SkinToneRune(tone SkinTone) rune {
switch tone {
case SkinToneLight:
return 0x1F3FB
case SkinToneMediumLight:
return 0x1F3FC
case SkinToneMedium:
return 0x1F3FD
case SkinToneMediumDark:
return 0x1F3FE
case SkinToneDark:
return 0x1F3FF
default:
return 0
}
}
func String
String returns the string name of the sequence type.
func (t SequenceType) String() string {
if int(t) < len(sequenceTypeNames) {
return sequenceTypeNames[t]
}
return unknownStrSeq
}
func String
String returns the string name of the skin tone.
func (t SkinTone) String() string {
if int(t) < len(skinToneNames) {
return skinToneNames[t]
}
return unknownStrSeq
}
func String
String returns a string representation of the sequence.
func (s Sequence) String() string {
return string(s.Codepoints)
}
Structs
type Sequence struct
Sequence represents an emoji sequence (single or multi-codepoint).
type Sequence struct {
// Codepoints contains all runes forming this emoji.
Codepoints []rune
// Type indicates the sequence type.
Type SequenceType
// BaseCodepoint is the primary emoji character (for Modified sequences).
// For other types, this equals Codepoints[0].
BaseCodepoint rune
// Modifier is the skin tone modifier, if present.
// Zero if no modifier is applied.
Modifier rune
}
GetFlagCode extracts the two-letter country code from a flag sequence.
Returns empty string if not a valid flag sequence.