font.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package text
  5. import (
  6. "github.com/g3n/engine/math32"
  7. "github.com/golang/freetype/truetype"
  8. "golang.org/x/image/font"
  9. "golang.org/x/image/math/fixed"
  10. "image"
  11. "image/color"
  12. "image/draw"
  13. "io/ioutil"
  14. "math"
  15. "strings"
  16. )
  17. type Font struct {
  18. ttf *truetype.Font
  19. face font.Face
  20. fgColor math32.Color4
  21. bgColor math32.Color4
  22. fontSize float64
  23. fontDPI float64
  24. lineSpacing float64
  25. fg *image.Uniform
  26. bg *image.Uniform
  27. hinting font.Hinting
  28. changed bool
  29. }
  30. const (
  31. HintingNone = font.HintingNone
  32. HintingVertical = font.HintingVertical
  33. HintingFull = font.HintingFull
  34. )
  35. // NewFont creates and returns a new font object using the specified
  36. // truetype font file
  37. func NewFont(fontfile string) (*Font, error) {
  38. // Reads font bytes
  39. fontBytes, err := ioutil.ReadFile(fontfile)
  40. if err != nil {
  41. return nil, err
  42. }
  43. return NewFontFromData(fontBytes)
  44. }
  45. // NewFontFromData creates and returns a new font object from the
  46. // specified data
  47. func NewFontFromData(fontData []byte) (*Font, error) {
  48. // Parses the font data
  49. ff, err := truetype.Parse(fontData)
  50. if err != nil {
  51. return nil, err
  52. }
  53. f := new(Font)
  54. f.ttf = ff
  55. f.fontSize = 12
  56. f.fontDPI = 72
  57. f.lineSpacing = 1.0
  58. f.hinting = font.HintingNone
  59. f.SetFgColor4(math32.NewColor4(0, 0, 0, 1))
  60. f.SetBgColor4(math32.NewColor4(1, 1, 1, 0))
  61. f.changed = false
  62. // Creates font face
  63. f.face = truetype.NewFace(f.ttf, &truetype.Options{
  64. Size: f.fontSize,
  65. DPI: f.fontDPI,
  66. Hinting: f.hinting,
  67. })
  68. return f, nil
  69. }
  70. // SetDPI sets the current DPI for the font
  71. func (f *Font) SetDPI(dpi float64) {
  72. if dpi == f.fontDPI {
  73. return
  74. }
  75. f.fontDPI = dpi
  76. f.changed = true
  77. }
  78. // SetSize sets the size of the font
  79. func (f *Font) SetSize(size float64) {
  80. if size == f.fontSize {
  81. return
  82. }
  83. f.fontSize = size
  84. f.changed = true
  85. }
  86. // SetHinting sets the hinting type
  87. func (f *Font) SetHinting(hinting font.Hinting) {
  88. if hinting == f.hinting {
  89. return
  90. }
  91. f.hinting = hinting
  92. f.changed = true
  93. }
  94. // Size returns the current font size
  95. func (f *Font) Size() float64 {
  96. return f.fontSize
  97. }
  98. // DPI returns the current font DPI
  99. func (f *Font) DPI() float64 {
  100. return f.fontDPI
  101. }
  102. // SetFgColor sets the current foreground color of the font
  103. // The alpha value is set to 1 (opaque)
  104. func (f *Font) SetFgColor(color *math32.Color) {
  105. f.fgColor.R = color.R
  106. f.fgColor.G = color.G
  107. f.fgColor.B = color.B
  108. f.fgColor.A = 1.0
  109. f.fg = image.NewUniform(Color4NRGBA(&f.fgColor))
  110. }
  111. // SetFgColor4 sets the current foreground color of the font
  112. func (f *Font) SetFgColor4(color *math32.Color4) {
  113. f.fgColor = *color
  114. f.fg = image.NewUniform(Color4NRGBA(color))
  115. }
  116. // FgColor returns the current foreground color
  117. func (f *Font) FgColor4() math32.Color4 {
  118. return f.fgColor
  119. }
  120. // SetBgColor sets the current foreground color of the font
  121. // The alpha value is set to 1 (opaque)
  122. func (f *Font) SetBgColor(color *math32.Color) {
  123. f.bgColor.R = color.R
  124. f.bgColor.G = color.G
  125. f.bgColor.B = color.B
  126. f.bgColor.A = 1.0
  127. f.bg = image.NewUniform(Color4NRGBA(&f.fgColor))
  128. }
  129. // SetBgColor sets the current background color of the font
  130. func (f *Font) SetBgColor4(color *math32.Color4) {
  131. f.bgColor = *color
  132. f.bg = image.NewUniform(Color4NRGBA(color))
  133. }
  134. // BgColor returns the current background color
  135. func (f *Font) BgColor4() math32.Color4 {
  136. return f.bgColor
  137. }
  138. // SetLineSpacing sets the spacing between lines
  139. func (f *Font) SetLineSpacing(spacing float64) {
  140. f.lineSpacing = spacing
  141. }
  142. // MeasureText returns the maximum width and height in pixels
  143. // necessary for an image to contain the specified text.
  144. // The supplied text string can contain line break escape sequences (\n).
  145. func (f *Font) MeasureText(text string) (int, int) {
  146. // Creates drawer
  147. f.updateFace()
  148. d := &font.Drawer{Dst: nil, Src: f.fg, Face: f.face}
  149. // Draw text
  150. width := 0
  151. py := int(math.Ceil(f.fontSize * f.fontDPI / 72))
  152. dy := int(math.Ceil(f.fontSize * f.lineSpacing * f.fontDPI / 72))
  153. lines := strings.Split(text, "\n")
  154. for _, s := range lines {
  155. d.Dot = fixed.P(0, py)
  156. lfixed := d.MeasureString(s)
  157. lw := int(lfixed >> 6)
  158. if lw > width {
  159. width = lw
  160. }
  161. py += dy
  162. }
  163. height := py - dy/2
  164. return width, height
  165. }
  166. func (f *Font) Metrics() font.Metrics {
  167. f.updateFace()
  168. return f.face.Metrics()
  169. }
  170. // Canvas is an image to draw text
  171. type Canvas struct {
  172. RGBA *image.RGBA
  173. bgColor *image.Uniform
  174. }
  175. // Update font face if font parameters changed
  176. func (f *Font) updateFace() {
  177. if f.changed {
  178. f.face = truetype.NewFace(f.ttf, &truetype.Options{
  179. Size: f.fontSize,
  180. DPI: f.fontDPI,
  181. Hinting: f.hinting,
  182. })
  183. f.changed = false
  184. }
  185. }
  186. // NewCanvas creates and returns a pointer to a new canvas with the
  187. // specified width and height in pixels and background color
  188. func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas {
  189. c := new(Canvas)
  190. c.RGBA = image.NewRGBA(image.Rect(0, 0, width, height))
  191. // Creates the image.Uniform for the background color
  192. c.bgColor = image.NewUniform(Color4NRGBA(bgColor))
  193. // Draw image
  194. draw.Draw(c.RGBA, c.RGBA.Bounds(), c.bgColor, image.ZP, draw.Src)
  195. return c
  196. }
  197. // DrawText draws text at the specified position (in pixels)
  198. // of this canvas, using the specified font.
  199. // The supplied text string can contain line break escape sequences (\n).
  200. func (c Canvas) DrawText(x, y int, text string, f *Font) {
  201. // Creates drawer
  202. f.updateFace()
  203. d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
  204. // Draw text
  205. py := y + int(math.Ceil(f.fontSize*f.fontDPI/72))
  206. dy := int(math.Ceil(f.fontSize * f.lineSpacing * f.fontDPI / 72))
  207. lines := strings.Split(text, "\n")
  208. for _, s := range lines {
  209. d.Dot = fixed.P(x, py)
  210. d.DrawString(s)
  211. py += dy
  212. }
  213. }
  214. // DrawTextCaret draws text at the specified position (in pixels)
  215. // of this canvas, using the specified font, and also a caret at
  216. // the specified line and column.
  217. // The supplied text string can contain line break escape sequences (\n).
  218. func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) error {
  219. // Creates drawer
  220. f.updateFace()
  221. d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
  222. py := y + int(math.Ceil(f.fontSize*f.fontDPI/72))
  223. dy := int(math.Ceil(f.fontSize * f.lineSpacing * f.fontDPI / 72))
  224. lines := strings.Split(text, "\n")
  225. for l, s := range lines {
  226. d.Dot = fixed.P(x, py)
  227. d.DrawString(s)
  228. // Checks for caret position
  229. if l == line && col <= StrCount(s) {
  230. width, _ := f.MeasureText(StrPrefix(s, col))
  231. // Draw caret vertical line
  232. caretH := int(f.fontSize) + 2
  233. //caretY := int(pt.Y>>6) - int(f.fontSize) + 2
  234. caretY := int(d.Dot.Y>>6) - int(f.fontSize) + 2
  235. color := Color4NRGBA(&math32.Color4{0, 0, 0, 1})
  236. for j := caretY; j < caretY+caretH; j++ {
  237. c.RGBA.Set(x+width, j, color)
  238. }
  239. }
  240. py += dy
  241. }
  242. // pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.fontSize)>>6))
  243. // for l, s := range lines {
  244. // // Draw string
  245. // _, err := font.ctx.DrawString(s, pt)
  246. // if err != nil {
  247. // return err
  248. // }
  249. // // Checks for caret position
  250. // if l == line && col <= StrCount(s) {
  251. // width, _, err := font.MeasureText(StrPrefix(s, col))
  252. // if err != nil {
  253. // return err
  254. // }
  255. // // Draw caret vertical line
  256. // caretH := int(font.fontSize) + 2
  257. // caretY := int(pt.Y>>6) - int(font.fontSize) + 2
  258. // color := Color4NRGBA(&math32.Color4{0, 0, 0, 1})
  259. // for j := caretY; j < caretY+caretH; j++ {
  260. // c.RGBA.Set(x+width, j, color)
  261. // }
  262. // }
  263. // // Increment y coordinate
  264. // pt.Y += font.ctx.PointToFixed(font.fontSize * font.lineSpacing)
  265. // }
  266. return nil
  267. }
  268. // Color4NRGBA converts a math32.Color4 to Go's image/color.NRGBA
  269. // NON pre-multiplied alpha.
  270. func Color4NRGBA(c *math32.Color4) color.NRGBA {
  271. red := uint8(c.R * 0xFF)
  272. green := uint8(c.G * 0xFF)
  273. blue := uint8(c.B * 0xFF)
  274. al := uint8(c.A * 0xFF)
  275. return color.NRGBA{red, green, blue, al}
  276. }