github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/avatar/service.go (about) 1 package avatar 2 3 import ( 4 "strings" 5 "time" 6 "unicode" 7 "unicode/utf8" 8 9 "github.com/cozy/cozy-stack/pkg/cache" 10 ) 11 12 // Options can be used to give options for the generated image 13 type Options int 14 15 const ( 16 cacheTTL = 30 * 24 * time.Hour // 1 month 17 contentType = "image/png" 18 19 // GreyBackground is an option to force a grey background 20 GreyBackground Options = 1 + iota 21 ) 22 23 // Initials is able to generate initial avatar. 24 type Initials interface { 25 // Generate will create a new avatar with the given initials and color. 26 Generate(initials, color string) ([]byte, error) 27 ContentType() string 28 } 29 30 // Service handle all the interactions with the initials images. 31 type Service struct { 32 cache cache.Cache 33 initials Initials 34 } 35 36 // NewService instantiate a new [Service]. 37 func NewService(cache cache.Cache, cmd string) *Service { 38 initials := NewPNGInitials(cmd) 39 return &Service{cache, initials} 40 } 41 42 // GenerateInitials an image with the initials for the given name (and the 43 // content-type to use for the HTTP response). 44 func (s *Service) GenerateInitials(publicName string, opts ...Options) ([]byte, string, error) { 45 name := strings.TrimSpace(publicName) 46 info := extractInfo(name) 47 for _, opt := range opts { 48 if opt == GreyBackground { 49 info.color = charcoalGrey 50 } 51 } 52 53 key := "initials:" + info.initials + info.color 54 if bytes, ok := s.cache.Get(key); ok { 55 return bytes, contentType, nil 56 } 57 58 bytes, err := s.initials.Generate(info.initials, info.color) 59 if err != nil { 60 return nil, "", err 61 } 62 s.cache.Set(key, bytes, cacheTTL) 63 return bytes, s.initials.ContentType(), nil 64 } 65 66 // See https://github.com/cozy/cozy-ui/blob/master/react/Avatar/index.jsx#L9-L26 67 // and https://docs.cozy.io/cozy-ui/styleguide/section-settings.html#kssref-settings-colors 68 var colors = []string{ 69 "#1FA8F1", 70 "#FD7461", 71 "#FC6D00", 72 "#F52D2D", 73 "#FF962F", 74 "#FF7F1B", 75 "#6984CE", 76 "#7F6BEE", 77 "#B449E7", 78 "#40DE8E", 79 "#0DCBCF", 80 "#35CE68", 81 "#3DA67E", 82 "#C2ADF4", 83 "#FFC644", 84 "#FC4C83", 85 } 86 87 var charcoalGrey = "#32363F" 88 89 type info struct { 90 initials string 91 color string 92 } 93 94 func extractInfo(name string) info { 95 initials := strings.ToUpper(getInitials(name)) 96 color := getColor(name) 97 return info{initials: initials, color: color} 98 } 99 100 func getInitials(name string) string { 101 parts := strings.Split(name, " ") 102 initials := make([]rune, 0, len(parts)) 103 for _, part := range parts { 104 r, size := utf8.DecodeRuneInString(part) 105 if size > 0 && unicode.IsLetter(r) { 106 initials = append(initials, r) 107 } 108 } 109 switch len(initials) { 110 case 0: 111 return "?" 112 case 1: 113 return string(initials) 114 default: 115 return string(initials[0]) + string(initials[len(initials)-1]) 116 } 117 } 118 119 func getColor(name string) string { 120 sum := 0 121 for i := 0; i < len(name); i++ { 122 sum += int(name[i]) 123 } 124 return colors[sum%len(colors)] 125 }