text/emoji/sequence.go

Functions Structs

Functions

func GetFlagCode

GetFlagCode extracts the two-letter country code from a flag sequence.

Returns empty string if not a valid flag sequence.

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
}