Skip to content

Commit e246067

Browse files
authored
Retro-compatible switch for public/avatar to an SVG output by default (#4520)
2 parents 7c2010a + 1c62ba8 commit e246067

File tree

10 files changed

+576
-37
lines changed

10 files changed

+576
-37
lines changed

docs/public.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@ Returns an image chosen by the user as their avatar. If no image has been
1212
chosen, a fallback will be used, depending of the `fallback` parameter in the
1313
query-string:
1414

15-
- `default`: a default image that shows the Cozy Cloud logo, but it can be
16-
overriden by dynamic assets per context
17-
- `initials`: a generated image with the initials of the owner's public name
18-
- `404`: just a 404 - Not found error.
15+
- `default` or no `fallback` parameter: a default image that shows the Cozy
16+
Cloud logo, but it can be overriden by dynamic assets per context (always a png)
17+
- `404`: just a HTTP 404 - Not found error.
18+
- `anonymous`: a generic user icon without initials visible
19+
- `initials`: a generated image with the initials of
20+
the owner's public name, with the following special arguments:
21+
- `format=png`: request a PNG response, otherwise defaults to SVG
22+
- `fx=translucent`: if SVG, make the output partially transparent
23+
- `as=unconfirmed`: if SVG, make the output grayscale
24+
- `q=high`: if SVG, embed the required fonts in the file (otherwise use `<inline>`)
1925

2026
## Prelogin
2127

model/vfs/vfsafero/avatar.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ func (a *avatarFS) AvatarExists() (bool, error) {
7777
func (a *avatarFS) ServeAvatarContent(w http.ResponseWriter, req *http.Request) error {
7878
s, err := a.fs.Stat(AvatarFilename)
7979
if err != nil {
80+
if os.IsNotExist(err) {
81+
return os.ErrNotExist
82+
}
8083
return err
8184
}
8285
if s.Size() == 0 {

pkg/avatar/service.go

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import (
1313
type Options int
1414

1515
const (
16-
cacheTTL = 30 * 24 * time.Hour // 1 month
17-
contentType = "image/png"
16+
cacheTTL = 30 * 24 * time.Hour // 1 month
17+
contentTypePNG = "image/png"
1818

19-
// GreyBackground is an option to force a grey background
2019
GreyBackground Options = 1 + iota
20+
Translucent Options = 1 + iota
21+
FormatPNG Options = 1 + iota // If this option is absent, SVG is assumed
22+
EmbedFont Options = 1 + iota
2123
)
2224

2325
// Initials is able to generate initial avatar.
@@ -41,21 +43,38 @@ func NewService(cache cache.Cache, cmd string) *Service {
4143

4244
// GenerateInitials an image with the initials for the given name (and the
4345
// 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)
46+
func (s *Service) GenerateInitials(publicName string, fontLoader avatarFontProvider, opts ...Options) ([]byte, string, error) {
47+
info := extractInfo(strings.TrimSpace(publicName))
48+
isPNG, isGrayscale, isTranslucent, embedFont := false, false, false, false
4749
for _, opt := range opts {
4850
if opt == GreyBackground {
4951
info.color = charcoalGrey
52+
isGrayscale = true
53+
} else if opt == Translucent {
54+
isTranslucent = true
55+
} else if opt == FormatPNG {
56+
isPNG = true
57+
} else if opt == EmbedFont {
58+
embedFont = true
5059
}
5160
}
5261

53-
key := "initials:" + info.initials + info.color
62+
if !embedFont {
63+
fontLoader = nil
64+
}
65+
if !isPNG {
66+
return SvgForAvatar(info.initials, uint(info.colorHash), isGrayscale, isTranslucent, fontLoader)
67+
}
68+
initials := info.initials
69+
if initials == "" {
70+
initials = "?"
71+
}
72+
key := "initials:" + initials + info.color
5473
if bytes, ok := s.cache.Get(key); ok {
55-
return bytes, contentType, nil
74+
return bytes, contentTypePNG, nil
5675
}
5776

58-
bytes, err := s.initials.Generate(info.initials, info.color)
77+
bytes, err := s.initials.Generate(initials, info.color)
5978
if err != nil {
6079
return nil, "", err
6180
}
@@ -87,14 +106,16 @@ var colors = []string{
87106
var charcoalGrey = "#32363F"
88107

89108
type info struct {
90-
initials string
91-
color string
109+
initials string
110+
colorHash int
111+
color string
92112
}
93113

94114
func extractInfo(name string) info {
95115
initials := strings.ToUpper(getInitials(name))
96-
color := getColor(name)
97-
return info{initials: initials, color: color}
116+
colorHash := getInitialsColorHash(name)
117+
color := getColorFromHash(colorHash)
118+
return info{initials: initials, colorHash: colorHash, color: color}
98119
}
99120

100121
func getInitials(name string) string {
@@ -108,18 +129,26 @@ func getInitials(name string) string {
108129
}
109130
switch len(initials) {
110131
case 0:
111-
return "?"
132+
return ""
112133
case 1:
113134
return string(initials)
114135
default:
115136
return string(initials[0]) + string(initials[len(initials)-1])
116137
}
117138
}
118139

119-
func getColor(name string) string {
140+
func getInitialsColorHash(name string) int {
120141
sum := 0
121142
for i := 0; i < len(name); i++ {
122143
sum += int(name[i])
123144
}
124-
return colors[sum%len(colors)]
145+
return sum
146+
}
147+
148+
func getColorFromHash(hash int) string {
149+
return colors[hash%len(colors)]
150+
}
151+
152+
func getColor(name string) string {
153+
return getColorFromHash(getInitialsColorHash(name))
125154
}

pkg/avatar/service_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import (
1010
)
1111

1212
func TestGetInitials(t *testing.T) {
13-
assert.Equal(t, "?", getInitials(" "))
13+
assert.Equal(t, "", getInitials(""))
14+
assert.Equal(t, "", getInitials(" "))
1415
assert.Equal(t, "P", getInitials("Pierre"))
1516
assert.Equal(t, "FP", getInitials("François Pignon"))
1617
assert.Equal(t, "П", getInitials("Пьер"))

0 commit comments

Comments
 (0)