package emoji import ( "fmt" "os" "path/filepath" "strconv" "strings" "unicode/utf8" ) // Emoji is a node of a Table type Emoji struct { Codepoint []rune IsEmoji bool Path string Sub Table } func (e Emoji) String() (str string) { str = "Emoji(" for _, cprune := range e.Codepoint { str += fmt.Sprintf("%U ", cprune) } str = strings.TrimRight(str, " ") + ")" if e.Sub != nil { str += "+" } if e.IsEmoji { str += fmt.Sprintf("\n └ Path: %s", e.Path) } return str + "\n" } // Length returns the lenght of the emoji in bytes func (e Emoji) Length() int { total := 0 for _, r := range e.Codepoint { total += utf8.RuneLen(r) } return total } // Table is a table of detected Unicode codepoints sequences for which an emoticon is available type Table map[rune]Emoji // Find checks if a given strings begins with an emoji made by one or more sequential runes func (em Table) Find(str string) *Emoji { for i, r := range str { e, ok := em[r] if !ok { break } // Check if there are more bytes to check if len(str) > i && e.Sub != nil { rlen := utf8.RuneLen(r) newemj := e.Sub.Find(str[i+rlen:]) if newemj != nil { return newemj } } if e.IsEmoji { return &e } } return nil } // IsEmoji checks whether the given rune is an emoji func (em Table) IsEmoji(cp rune) bool { _, ok := em[cp] return ok } func (em Table) tostring(indent string, nomarker bool) (str string) { counter := len(em) marker := "│ " if nomarker { marker = " " } for r, emo := range em { listr := "├" if counter == 1 { listr = "└" } str += fmt.Sprintf(indent+"%s%s %U", marker, listr, r) if emo.IsEmoji { str += fmt.Sprintf(": %s", emo.Path) } str += "\n" if emo.Sub != nil { str += emo.Sub.tostring(indent+marker, counter == 1) } counter-- } return } func (em Table) String() string { return "Emoji table\n" + em.tostring("", true) } // ScanEmojiDirectory scans a directory for images to use as icons // Pictures must be named in this format: // emoji_uXXXX_XXXX.png // where XXXX are Unicode codepoints // See func ScanEmojiDirectory(emojipath string) (tab Table, err error) { tab = make(Table) err = filepath.Walk(emojipath, func(path string, info os.FileInfo, err error) error { // Ignore non-images if !strings.HasSuffix(strings.ToLower(path), ".png") { return nil } // Get icon filename emojiname := filepath.Base(path) // Strip prefix and suffix extsep := strings.LastIndexByte(emojiname, '.') basesep := strings.IndexByte(emojiname, '_') codepointstr := emojiname[basesep+2 : extsep] // Split codepoints by separator (_) codepointlist := strings.Split(codepointstr, "_") // Parse codepoints to runes curtab := &tab codepoints := []rune{} for cpi, cpstr := range codepointlist { num, err := strconv.ParseInt(cpstr, 16, 32) if err != nil { return fmt.Errorf("malformed icon filename: %s (codepoints are not valid int32)", emojiname) } cprune := rune(num) newemo := (*curtab)[cprune] codepoints = append(codepoints, cprune) newemo.Codepoint = codepoints[:] if len(codepointlist) < cpi+2 { // Set as emoji newemo.IsEmoji = true newemo.Path = path } else { // Add sub-entry if not existant if newemo.Sub == nil { newemo.Sub = make(Table) } } (*curtab)[cprune] = newemo curtab = &newemo.Sub } return nil }) return tab, err }