@@ -8,13 +8,32 @@ import (
88 "html/template"
99 "path"
1010 "strings"
11+ "sync"
1112
1213 gitea_html "code.gitea.io/gitea/modules/htmlutil"
1314 "code.gitea.io/gitea/modules/log"
1415 "code.gitea.io/gitea/modules/public"
1516)
1617
17- var svgIcons map [string ]string
18+ type svgIconItem struct {
19+ html string
20+ mocking bool
21+ }
22+
23+ type svgCacheKey struct {
24+ icon string
25+ size int
26+ class string
27+ }
28+
29+ var (
30+ svgIcons map [string ]svgIconItem
31+
32+ svgCacheMu sync.Mutex
33+ svgCache sync.Map
34+ svgCacheCount int
35+ svgCacheLimit = 10000
36+ )
1837
1938const defaultSize = 16
2039
@@ -26,7 +45,7 @@ func Init() error {
2645 return err
2746 }
2847
29- svgIcons = make (map [string ]string , len (files ))
48+ svgIcons = make (map [string ]svgIconItem , len (files ))
3049 for _ , file := range files {
3150 if path .Ext (file ) != ".svg" {
3251 continue
@@ -35,18 +54,21 @@ func Init() error {
3554 if err != nil {
3655 log .Error ("Failed to read SVG file %s: %v" , file , err )
3756 } else {
38- svgIcons [file [:len (file )- 4 ]] = string (Normalize (bs , defaultSize ))
57+ svgIcons [file [:len (file )- 4 ]] = svgIconItem { html : string (Normalize (bs , defaultSize ))}
3958 }
4059 }
4160 return nil
4261}
4362
4463func MockIcon (icon string ) func () {
4564 if svgIcons == nil {
46- svgIcons = make (map [string ]string )
65+ svgIcons = make (map [string ]svgIconItem )
4766 }
4867 orig , exist := svgIcons [icon ]
49- svgIcons [icon ] = fmt .Sprintf (`<svg class="svg %s" width="%d" height="%d"></svg>` , icon , defaultSize , defaultSize )
68+ svgIcons [icon ] = svgIconItem {
69+ html : fmt .Sprintf (`<svg class="svg %s" width="%d" height="%d"></svg>` , icon , defaultSize , defaultSize ),
70+ mocking : true ,
71+ }
5072 return func () {
5173 if exist {
5274 svgIcons [icon ] = orig
@@ -58,11 +80,28 @@ func MockIcon(icon string) func() {
5880
5981// RenderHTML renders icons - arguments icon name (string), size (int), class (string)
6082func RenderHTML (icon string , others ... any ) template.HTML {
83+ result , _ := renderHTML (icon , others ... )
84+ return result
85+ }
86+
87+ func renderHTML (icon string , others ... any ) (_ template.HTML , usingCache bool ) {
6188 if icon == "" {
62- return ""
89+ return "" , false
6390 }
6491 size , class := gitea_html .ParseSizeAndClass (defaultSize , "" , others ... )
65- if svgStr , ok := svgIcons [icon ]; ok {
92+ if svgItem , ok := svgIcons [icon ]; ok {
93+ svgStr := svgItem .html
94+ // fast path for default size and no classes
95+ if size == defaultSize && class == "" {
96+ return template .HTML (svgStr ), false
97+ }
98+
99+ cacheKey := svgCacheKey {icon , size , class }
100+ cachedHTML , cached := svgCache .Load (cacheKey )
101+ if cached && ! svgItem .mocking {
102+ return cachedHTML .(template.HTML ), true
103+ }
104+
66105 // the code is somewhat hacky, but it just works, because the SVG contents are all normalized
67106 if size != defaultSize {
68107 svgStr = strings .Replace (svgStr , fmt .Sprintf (`width="%d"` , defaultSize ), fmt .Sprintf (`width="%d"` , size ), 1 )
@@ -71,8 +110,24 @@ func RenderHTML(icon string, others ...any) template.HTML {
71110 if class != "" {
72111 svgStr = strings .Replace (svgStr , `class="` , fmt .Sprintf (`class="%s ` , class ), 1 )
73112 }
74- return template .HTML (svgStr )
113+ result := template .HTML (svgStr )
114+
115+ if ! svgItem .mocking {
116+ // no need to double-check, the rendering is fast enough and the cache is just an optimization
117+ svgCacheMu .Lock ()
118+ if svgCacheCount >= svgCacheLimit {
119+ svgCache .Clear ()
120+ svgCacheCount = 0
121+ }
122+ svgCacheCount ++
123+ svgCache .Store (cacheKey , result )
124+ svgCacheMu .Unlock ()
125+ }
126+
127+ return result , false
75128 }
129+
76130 // during test (or something wrong happens), there is no SVG loaded, so use a dummy span to tell that the icon is missing
77- return template .HTML (fmt .Sprintf ("<span>%s(%d/%s)</span>" , template .HTMLEscapeString (icon ), size , template .HTMLEscapeString (class )))
131+ dummy := template .HTML (fmt .Sprintf ("<span>%s(%d/%s)</span>" , template .HTMLEscapeString (icon ), size , template .HTMLEscapeString (class )))
132+ return dummy , false
78133}
0 commit comments