diff --git a/emoji.go b/emoji.go new file mode 100644 index 0000000..5ab1be5 --- /dev/null +++ b/emoji.go @@ -0,0 +1,155 @@ +package freetype + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "unicode/utf8" +) + +// Emoji is a node of an EmojiTable +type Emoji struct { + Codepoint []rune + IsEmoji bool + Path string + Sub EmojiTable +} + +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 +} + +// EmojiTable is a table of detected Unicode codepoints sequences for which an emoticon is available +type EmojiTable map[rune]Emoji + +// Find checks if a given strings begins with an emoji made by one or more sequential runes +func (em EmojiTable) 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 EmojiTable) IsEmoji(cp rune) bool { + _, ok := em[cp] + return ok +} + +func (em EmojiTable) 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 EmojiTable) 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 EmojiTable, err error) { + tab = make(EmojiTable) + 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(EmojiTable) + } + } + (*curtab)[cprune] = newemo + curtab = &newemo.Sub + } + return nil + }) + return tab, err +}