package freetype import ( "fmt" "image" "os" "path/filepath" "strconv" "strings" "unicode/utf8" _ "image/png" // Add PNG file loading support "golang.org/x/image/draw" "golang.org/x/image/math/fixed" ) 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" } func (e emoji) Length() int { total := 0 for _, r := range e.Codepoint { total += utf8.RuneLen(r) } return total } type emojiTable map[rune]emoji 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 } 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) } 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 } const emojiScale = fixed.Int26_6(100) func loadIconAtSize(path string, size fixed.Int26_6) (image.Image, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() img, _, err := image.Decode(file) if err != nil { return nil, err } scale := size.Mul(emojiScale).Round() scaled := image.NewRGBA(image.Rect(0, 0, scale, scale)) draw.BiLinear.Scale(scaled, scaled.Bounds(), img, img.Bounds(), draw.Over, nil) return scaled, nil }