FontAtlasProsessor.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. package giu
  2. import (
  3. "fmt"
  4. "log"
  5. "runtime"
  6. "strings"
  7. "sync"
  8. "github.com/AllenDang/go-findfont"
  9. "github.com/AllenDang/imgui-go"
  10. )
  11. var (
  12. shouldRebuildFontAtlas bool
  13. defaultFontSize float32 = 14
  14. stringMap sync.Map // key is rune, value indicates whether it's a new rune.
  15. defaultFonts []FontInfo
  16. extraFonts []FontInfo
  17. extraFontMap map[string]*imgui.Font
  18. )
  19. const (
  20. preRegisterString = " \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
  21. windows = "windows"
  22. )
  23. // FontInfo represents a giu implementation of imgui font.
  24. type FontInfo struct {
  25. fontName string
  26. fontPath string
  27. fontByte []byte
  28. size float32
  29. }
  30. func (f *FontInfo) String() string {
  31. return fmt.Sprintf("%s:%.2f", f.fontName, f.size)
  32. }
  33. func (f *FontInfo) SetSize(size float32) *FontInfo {
  34. result := *f
  35. result.size = size
  36. for _, i := range extraFonts {
  37. if i.String() == result.String() {
  38. return &result
  39. }
  40. }
  41. extraFonts = append(extraFonts, result)
  42. shouldRebuildFontAtlas = true
  43. return &result
  44. }
  45. func initFontAtlasProcessor() {
  46. extraFontMap = make(map[string]*imgui.Font)
  47. // Pre register numbers
  48. tStr(preRegisterString)
  49. // Pre-register fonts
  50. os := runtime.GOOS
  51. switch os {
  52. case "darwin":
  53. // English font
  54. registerDefaultFont("Menlo", defaultFontSize)
  55. // Chinese font
  56. registerDefaultFont("STHeiti", defaultFontSize-1)
  57. // Jananese font
  58. registerDefaultFont("ヒラギノ角ゴシック W0", defaultFontSize+3)
  59. // Korean font
  60. registerDefaultFont("AppleSDGothicNeo", defaultFontSize+2)
  61. case windows:
  62. // English font
  63. registerDefaultFont("Calibri", defaultFontSize+2)
  64. // Chinese font
  65. registerDefaultFont("MSYH", defaultFontSize+2)
  66. // Japanese font
  67. registerDefaultFont("MSGOTHIC", defaultFontSize+2)
  68. // Korean font
  69. registerDefaultFont("MALGUNSL", defaultFontSize+2)
  70. case "linux":
  71. // English fonts
  72. registerDefaultFonts([]FontInfo{
  73. {
  74. fontName: "FreeSans.ttf",
  75. size: defaultFontSize + 1,
  76. },
  77. {
  78. fontName: "FiraCode-Medium",
  79. size: defaultFontSize + 1,
  80. },
  81. {
  82. fontName: "sans",
  83. size: defaultFontSize + 1,
  84. },
  85. })
  86. }
  87. }
  88. // SetDefaultFontSize sets the default font size. Invoke this before MasterWindow.NewMasterWindow(..).
  89. func SetDefaultFontSize(size float32) {
  90. defaultFontSize = size
  91. }
  92. // SetDefaultFont changes default font.
  93. func SetDefaultFont(fontName string, size float32) {
  94. fontPath, err := findfont.Find(fontName)
  95. if err != nil {
  96. log.Fatalf("Cannot find font %s", fontName)
  97. return
  98. }
  99. fontInfo := FontInfo{fontName: fontName, fontPath: fontPath, size: size}
  100. defaultFonts = append([]FontInfo{fontInfo}, defaultFonts...)
  101. }
  102. // SetDefaultFontFromBytes changes default font by bytes of the font file.
  103. func SetDefaultFontFromBytes(fontBytes []byte, size float32) {
  104. defaultFonts = append([]FontInfo{
  105. {
  106. fontByte: fontBytes,
  107. size: size,
  108. },
  109. }, defaultFonts...)
  110. }
  111. func GetDefaultFonts() []FontInfo {
  112. return defaultFonts
  113. }
  114. // AddFont adds font by name, if the font is found, return *FontInfo, otherwise return nil.
  115. // To use added font, use giu.Style().SetFont(...).
  116. func AddFont(fontName string, size float32) *FontInfo {
  117. fontPath, err := findfont.Find(fontName)
  118. if err != nil {
  119. fmt.Printf("[Warning]Cannot find font %s at system, related text will not be rendered.\n", fontName)
  120. return nil
  121. }
  122. fi := FontInfo{
  123. fontName: fontName,
  124. fontPath: fontPath,
  125. size: size,
  126. }
  127. extraFonts = append(extraFonts, fi)
  128. return &fi
  129. }
  130. // AddFontFromBytes does similar to AddFont, but using data from memory.
  131. func AddFontFromBytes(fontName string, fontBytes []byte, size float32) *FontInfo {
  132. fi := FontInfo{
  133. fontName: fontName,
  134. fontByte: fontBytes,
  135. size: size,
  136. }
  137. extraFonts = append(extraFonts, fi)
  138. return &fi
  139. }
  140. func registerDefaultFont(fontName string, size float32) {
  141. fontPath, err := findfont.Find(fontName)
  142. if err != nil {
  143. return
  144. }
  145. fontInfo := FontInfo{fontName: fontName, fontPath: fontPath, size: size}
  146. defaultFonts = append(defaultFonts, fontInfo)
  147. }
  148. func registerDefaultFonts(fontInfos []FontInfo) {
  149. var firstFoundFont *FontInfo
  150. for _, fi := range fontInfos {
  151. fontPath, err := findfont.Find(fi.fontName)
  152. if err == nil {
  153. firstFoundFont = &FontInfo{fontName: fi.fontName, fontPath: fontPath, size: fi.size}
  154. break
  155. }
  156. }
  157. if firstFoundFont != nil {
  158. defaultFonts = append(defaultFonts, *firstFoundFont)
  159. }
  160. }
  161. // Register string to font atlas builder.
  162. // Note only register strings that will be displayed on the UI.
  163. func tStr(str string) string {
  164. for _, s := range str {
  165. if _, ok := stringMap.Load(s); !ok {
  166. stringMap.Store(s, false)
  167. shouldRebuildFontAtlas = true
  168. }
  169. }
  170. return str
  171. }
  172. // Register string pointer to font atlas builder.
  173. // Note only register strings that will be displayed on the UI.
  174. func tStrPtr(str *string) *string {
  175. tStr(*str)
  176. return str
  177. }
  178. func tStrSlice(str []string) []string {
  179. for _, s := range str {
  180. tStr(s)
  181. }
  182. return str
  183. }
  184. // Rebuild font atlas when necessary.
  185. func rebuildFontAtlas() {
  186. if !shouldRebuildFontAtlas {
  187. return
  188. }
  189. fonts := Context.IO().Fonts()
  190. fonts.Clear()
  191. var sb strings.Builder
  192. stringMap.Range(func(k, v any) bool {
  193. stringMap.Store(k, true)
  194. if ks, ok := k.(rune); ok {
  195. sb.WriteRune(ks)
  196. }
  197. return true
  198. })
  199. ranges := imgui.NewGlyphRanges()
  200. builder := imgui.NewFontGlyphRangesBuilder()
  201. // Because we pre-regestered numbers, so default string map's length should greater then 11.
  202. if sb.Len() > len(preRegisterString) {
  203. builder.AddText(sb.String())
  204. } else {
  205. builder.AddRanges(fonts.GlyphRangesDefault())
  206. }
  207. builder.BuildRanges(ranges)
  208. if len(defaultFonts) > 0 {
  209. fontConfig := imgui.NewFontConfig()
  210. fontConfig.SetOversampleH(2)
  211. fontConfig.SetOversampleV(2)
  212. fontConfig.SetRasterizerMultiply(1.5)
  213. for i, fontInfo := range defaultFonts {
  214. if i > 0 {
  215. fontConfig.SetMergeMode(true)
  216. }
  217. // Scale font size with DPI scale factor
  218. if runtime.GOOS == windows {
  219. fontInfo.size *= Context.GetPlatform().GetContentScale()
  220. }
  221. if len(fontInfo.fontByte) == 0 {
  222. fonts.AddFontFromFileTTFV(fontInfo.fontPath, fontInfo.size, fontConfig, ranges.Data())
  223. } else {
  224. fonts.AddFontFromMemoryTTFV(fontInfo.fontByte, fontInfo.size, fontConfig, ranges.Data())
  225. }
  226. }
  227. // Fall back if no font is added
  228. if fonts.GetFontCount() == 0 {
  229. fonts.AddFontDefault()
  230. }
  231. } else {
  232. fonts.AddFontDefault()
  233. }
  234. // Add extra fonts
  235. for _, fontInfo := range extraFonts {
  236. // Scale font size with DPI scale factor
  237. if runtime.GOOS == windows {
  238. fontInfo.size *= Context.GetPlatform().GetContentScale()
  239. }
  240. // Store imgui.Font for PushFont
  241. var f imgui.Font
  242. if len(fontInfo.fontByte) == 0 {
  243. f = fonts.AddFontFromFileTTFV(fontInfo.fontPath, fontInfo.size, imgui.DefaultFontConfig, ranges.Data())
  244. } else {
  245. f = fonts.AddFontFromMemoryTTFV(fontInfo.fontByte, fontInfo.size, imgui.DefaultFontConfig, ranges.Data())
  246. }
  247. extraFontMap[fontInfo.String()] = &f
  248. }
  249. fontTextureImg := fonts.TextureDataRGBA32()
  250. Context.renderer.SetFontTexture(fontTextureImg)
  251. shouldRebuildFontAtlas = false
  252. }