font.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. package painter
  2. import (
  3. "bytes"
  4. "image"
  5. "image/color"
  6. "image/draw"
  7. "math"
  8. "sync"
  9. "github.com/go-text/typesetting/di"
  10. gotext "github.com/go-text/typesetting/font"
  11. "github.com/go-text/typesetting/shaping"
  12. "github.com/goki/freetype"
  13. "github.com/goki/freetype/truetype"
  14. "golang.org/x/image/font"
  15. "golang.org/x/image/math/fixed"
  16. "fyne.io/fyne/v2"
  17. "fyne.io/fyne/v2/internal/cache"
  18. "fyne.io/fyne/v2/theme"
  19. )
  20. const (
  21. // DefaultTabWidth is the default width in spaces
  22. DefaultTabWidth = 4
  23. // TextDPI is a global constant that determines how text scales to interface sizes
  24. TextDPI = 78
  25. fontTabSpaceSize = 10
  26. )
  27. // CachedFontFace returns a font face held in memory. These are loaded from the current theme.
  28. func CachedFontFace(style fyne.TextStyle, fontDP float32, texScale float32) (font.Face, gotext.Face) {
  29. key := faceCacheKey{float32ToFixed266(fontDP), float32ToFixed266(texScale)}
  30. val, ok := fontCache.Load(style)
  31. if !ok {
  32. var f1, f2 *truetype.Font
  33. switch {
  34. case style.Monospace:
  35. f1 = loadFont(theme.TextMonospaceFont())
  36. f2 = loadFont(theme.DefaultTextMonospaceFont())
  37. case style.Bold:
  38. if style.Italic {
  39. f1 = loadFont(theme.TextBoldItalicFont())
  40. f2 = loadFont(theme.DefaultTextBoldItalicFont())
  41. } else {
  42. f1 = loadFont(theme.TextBoldFont())
  43. f2 = loadFont(theme.DefaultTextBoldFont())
  44. }
  45. case style.Italic:
  46. f1 = loadFont(theme.TextItalicFont())
  47. f2 = loadFont(theme.DefaultTextItalicFont())
  48. case style.Symbol:
  49. f2 = loadFont(theme.DefaultSymbolFont())
  50. default:
  51. f1 = loadFont(theme.TextFont())
  52. f2 = loadFont(theme.DefaultTextFont())
  53. }
  54. if f1 == nil {
  55. f1 = f2
  56. }
  57. val = &fontCacheItem{font: f1, fallback: f2, faces: make(map[faceCacheKey]font.Face),
  58. measureFaces: make(map[faceCacheKey]gotext.Face)}
  59. fontCache.Store(style, val)
  60. }
  61. comp := val.(*fontCacheItem)
  62. comp.facesMutex.RLock()
  63. face := comp.faces[key]
  64. measureFace := comp.measureFaces[key]
  65. comp.facesMutex.RUnlock()
  66. if face == nil {
  67. var opts truetype.Options
  68. opts.Size = float64(fontDP)
  69. opts.DPI = float64(TextDPI * texScale)
  70. f1 := truetype.NewFace(comp.font, &opts)
  71. f2 := truetype.NewFace(comp.fallback, &opts)
  72. face = newFontWithFallback(f1, f2, comp.font, comp.fallback)
  73. switch {
  74. case style.Monospace:
  75. measureFace = loadMeasureFont(theme.TextMonospaceFont())
  76. if measureFace == nil {
  77. measureFace = loadMeasureFont(theme.DefaultTextMonospaceFont())
  78. }
  79. case style.Bold:
  80. if style.Italic {
  81. measureFace = loadMeasureFont(theme.TextBoldItalicFont())
  82. if measureFace == nil {
  83. measureFace = loadMeasureFont(theme.DefaultTextBoldItalicFont())
  84. }
  85. } else {
  86. measureFace = loadMeasureFont(theme.TextBoldFont())
  87. if measureFace == nil {
  88. measureFace = loadMeasureFont(theme.DefaultTextBoldFont())
  89. }
  90. }
  91. case style.Italic:
  92. measureFace = loadMeasureFont(theme.TextItalicFont())
  93. if measureFace == nil {
  94. measureFace = loadMeasureFont(theme.DefaultTextItalicFont())
  95. }
  96. case style.Symbol:
  97. measureFace = loadMeasureFont(theme.DefaultSymbolFont())
  98. default:
  99. measureFace = loadMeasureFont(theme.TextFont())
  100. if measureFace == nil {
  101. measureFace = loadMeasureFont(theme.DefaultTextFont())
  102. }
  103. }
  104. comp.facesMutex.Lock()
  105. comp.faces[key] = face
  106. comp.measureFaces[key] = measureFace
  107. comp.facesMutex.Unlock()
  108. }
  109. return face, measureFace
  110. }
  111. // ClearFontCache is used to remove cached fonts in the case that we wish to re-load font faces
  112. func ClearFontCache() {
  113. fontCache.Range(func(_, val interface{}) bool {
  114. item := val.(*fontCacheItem)
  115. for _, face := range item.faces {
  116. if face == nil {
  117. continue
  118. }
  119. err := face.Close()
  120. if err != nil {
  121. fyne.LogError("failed to close font face", err)
  122. return false
  123. }
  124. }
  125. return true
  126. })
  127. fontCache = &sync.Map{}
  128. }
  129. // DrawString draws a string into an image.
  130. func DrawString(dst draw.Image, s string, color color.Color, f font.Face, face gotext.Face, fontSize, scale float32,
  131. height int, tabWidth int) {
  132. src := &image.Uniform{C: color}
  133. dot := freetype.Pt(0, height-f.Metrics().Descent.Ceil())
  134. walkString(face, s, float32ToFixed266(fontSize), tabWidth, &dot.X, scale, func(g gotext.GID) {
  135. dr, mask, maskp, _, ok := f.(truetype.IndexableFace).GlyphAtIndex(dot, truetype.Index(g))
  136. if !ok {
  137. dr, mask, maskp, _, ok = f.Glyph(dot, 0xfffd)
  138. }
  139. if ok {
  140. draw.DrawMask(dst, dr, src, image.Point{}, mask, maskp, draw.Over)
  141. }
  142. })
  143. }
  144. func loadMeasureFont(data fyne.Resource) gotext.Face {
  145. loaded, err := gotext.ParseTTF(bytes.NewReader(data.Content()))
  146. if err != nil {
  147. fyne.LogError("font load error", err)
  148. return nil
  149. }
  150. return loaded
  151. }
  152. // MeasureString returns how far dot would advance by drawing s with f.
  153. // Tabs are translated into a dot location change.
  154. func MeasureString(f gotext.Face, s string, textSize float32, tabWidth int) (size fyne.Size, advance fixed.Int26_6) {
  155. return walkString(f, s, float32ToFixed266(textSize), tabWidth, &advance, 1, func(gotext.GID) {})
  156. }
  157. // RenderedTextSize looks up how big a string would be if drawn on screen.
  158. // It also returns the distance from top to the text baseline.
  159. func RenderedTextSize(text string, fontSize float32, style fyne.TextStyle) (size fyne.Size, baseline float32) {
  160. size, base := cache.GetFontMetrics(text, fontSize, style)
  161. if base != 0 {
  162. return size, base
  163. }
  164. size, base = measureText(text, fontSize, style)
  165. cache.SetFontMetrics(text, fontSize, style, size, base)
  166. return size, base
  167. }
  168. func fixed266ToFloat32(i fixed.Int26_6) float32 {
  169. return float32(float64(i) / (1 << 6))
  170. }
  171. func float32ToFixed266(f float32) fixed.Int26_6 {
  172. return fixed.Int26_6(float64(f) * (1 << 6))
  173. }
  174. func loadFont(data fyne.Resource) *truetype.Font {
  175. loaded, err := truetype.Parse(data.Content())
  176. if err != nil {
  177. fyne.LogError("font load error", err)
  178. }
  179. return loaded
  180. }
  181. func measureText(text string, fontSize float32, style fyne.TextStyle) (fyne.Size, float32) {
  182. _, face := CachedFontFace(style, fontSize, 1)
  183. size, base := MeasureString(face, text, fontSize, style.TabWidth)
  184. return size, fixed266ToFloat32(base)
  185. }
  186. func newFontWithFallback(chosen, fallback font.Face, chosenFont, fallbackFont ttfFont) font.Face {
  187. return &compositeFace{chosen: chosen, fallback: fallback, chosenFont: chosenFont, fallbackFont: fallbackFont}
  188. }
  189. func tabStop(spacew, x fixed.Int26_6, tabWidth int) fixed.Int26_6 {
  190. if tabWidth <= 0 {
  191. tabWidth = DefaultTabWidth
  192. }
  193. tabw := spacew * fixed.Int26_6(tabWidth)
  194. tabs, _ := math.Modf(float64((x + tabw) / tabw))
  195. return tabw * fixed.Int26_6(tabs)
  196. }
  197. func walkString(f gotext.Face, s string, textSize fixed.Int26_6, tabWidth int, advance *fixed.Int26_6, scale float32, cb func(g gotext.GID)) (size fyne.Size, base fixed.Int26_6) {
  198. runes := []rune(s)
  199. in := shaping.Input{
  200. Text: []rune{' '},
  201. RunStart: 0,
  202. RunEnd: 1,
  203. Direction: di.DirectionLTR,
  204. Face: f,
  205. Size: textSize,
  206. }
  207. shaper := &shaping.HarfbuzzShaper{}
  208. out := shaper.Shape(in)
  209. spacew := float32ToFixed266(scale) * fontTabSpaceSize
  210. in.Text = runes
  211. in.RunStart = 0
  212. in.RunEnd = len(runes)
  213. ins := shaping.SplitByFontGlyphs(in, []gotext.Face{f}) // TODO provide fallback...
  214. for _, in := range ins {
  215. out = shaper.Shape(in)
  216. var c rune
  217. nextRuneIndex := 0
  218. last := -1
  219. for _, g := range out.Glyphs {
  220. if g.ClusterIndex != last {
  221. c = in.Text[nextRuneIndex]
  222. nextRuneIndex += g.RuneCount
  223. last = g.ClusterIndex
  224. }
  225. if c == '\r' {
  226. continue
  227. }
  228. if c == '\t' {
  229. *advance = tabStop(spacew, *advance, tabWidth)
  230. } else {
  231. cb(g.GlyphID)
  232. *advance += float32ToFixed266(fixed266ToFloat32(g.XAdvance) * scale)
  233. }
  234. }
  235. }
  236. return fyne.NewSize(fixed266ToFloat32(*advance), fixed266ToFloat32(out.LineBounds.LineHeight())),
  237. out.LineBounds.Ascent
  238. }
  239. var _ truetype.IndexableFace = (*compositeFace)(nil)
  240. type compositeFace struct {
  241. sync.Mutex
  242. chosen, fallback font.Face
  243. chosenFont, fallbackFont ttfFont
  244. }
  245. func (c *compositeFace) Close() (err error) {
  246. c.Lock()
  247. defer c.Unlock()
  248. if c.chosen != nil {
  249. err = c.chosen.Close()
  250. }
  251. err2 := c.fallback.Close()
  252. if err2 != nil {
  253. return err2
  254. }
  255. return
  256. }
  257. func (c *compositeFace) Glyph(dot fixed.Point26_6, r rune) (
  258. dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
  259. c.Lock()
  260. defer c.Unlock()
  261. if c.containsGlyph(c.chosenFont, r) {
  262. return c.chosen.Glyph(dot, r)
  263. }
  264. if c.containsGlyph(c.fallbackFont, r) {
  265. return c.fallback.Glyph(dot, r)
  266. }
  267. return
  268. }
  269. func (c *compositeFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
  270. c.Lock()
  271. defer c.Unlock()
  272. if c.containsGlyph(c.chosenFont, r) {
  273. return c.chosen.GlyphAdvance(r)
  274. }
  275. if c.containsGlyph(c.fallbackFont, r) {
  276. return c.fallback.GlyphAdvance(r)
  277. }
  278. return
  279. }
  280. func (c *compositeFace) GlyphAtIndex(dot fixed.Point26_6, g truetype.Index) (dr image.Rectangle, mask image.Image, maskp image.Point,
  281. advance fixed.Int26_6, ok bool) {
  282. if g == 0 {
  283. return image.Rectangle{}, nil, image.Point{}, 0, false
  284. }
  285. c.Lock()
  286. defer c.Unlock()
  287. dr, mask, maskp, advance, ok = c.chosen.(truetype.IndexableFace).GlyphAtIndex(dot, g)
  288. if ok {
  289. return
  290. }
  291. return c.fallback.(truetype.IndexableFace).GlyphAtIndex(dot, g)
  292. }
  293. func (c *compositeFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
  294. c.Lock()
  295. defer c.Unlock()
  296. if c.containsGlyph(c.chosenFont, r) {
  297. return c.chosen.GlyphBounds(r)
  298. }
  299. if c.containsGlyph(c.fallbackFont, r) {
  300. return c.fallback.GlyphBounds(r)
  301. }
  302. return
  303. }
  304. func (c *compositeFace) Kern(r0, r1 rune) fixed.Int26_6 {
  305. c.Lock()
  306. defer c.Unlock()
  307. if c.containsGlyph(c.chosenFont, r0) && c.containsGlyph(c.chosenFont, r1) {
  308. return c.chosen.Kern(r0, r1)
  309. }
  310. return c.fallback.Kern(r0, r1)
  311. }
  312. func (c *compositeFace) Metrics() font.Metrics {
  313. c.Lock()
  314. defer c.Unlock()
  315. return c.chosen.Metrics()
  316. }
  317. func (c *compositeFace) containsGlyph(font ttfFont, r rune) bool {
  318. return font != nil && font.Index(r) != 0
  319. }
  320. type ttfFont interface {
  321. Index(rune) truetype.Index
  322. }
  323. type faceCacheKey struct {
  324. size, scale fixed.Int26_6
  325. }
  326. type fontCacheItem struct {
  327. font, fallback *truetype.Font
  328. faces map[faceCacheKey]font.Face
  329. measureFaces map[faceCacheKey]gotext.Face
  330. facesMutex sync.RWMutex
  331. }
  332. var fontCache = &sync.Map{} // map[fyne.TextStyle]*fontCacheItem