font.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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. "strings"
  15. )
  16. // Font represents a TrueType font face.
  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 // [0,1] relative to font height
  25. fg *image.Uniform
  26. bg *image.Uniform
  27. hinting font.Hinting
  28. changed bool
  29. }
  30. // Font hinting types.
  31. const (
  32. HintingNone = font.HintingNone
  33. HintingVertical = font.HintingVertical
  34. HintingFull = font.HintingFull
  35. )
  36. // NewFont creates and returns a new font object using the specified 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 specified data.
  46. func NewFontFromData(fontData []byte) (*Font, error) {
  47. // Parses the font data
  48. ttf, err := truetype.Parse(fontData)
  49. if err != nil {
  50. return nil, err
  51. }
  52. f := new(Font)
  53. f.ttf = ttf
  54. f.fontSize = 12
  55. f.fontDPI = 72
  56. f.lineSpacing = 1.0
  57. f.hinting = font.HintingNone
  58. f.SetFgColor4(&math32.Color4{0, 0, 0, 1})
  59. f.SetBgColor4(&math32.Color4{1, 1, 1, 0})
  60. f.changed = false
  61. // Creates font face
  62. f.face = truetype.NewFace(f.ttf, &truetype.Options{
  63. Size: f.fontSize,
  64. DPI: f.fontDPI,
  65. Hinting: f.hinting,
  66. })
  67. return f, nil
  68. }
  69. // SetSize sets the size of the font.
  70. func (f *Font) SetSize(size float64) {
  71. if size == f.fontSize {
  72. return
  73. }
  74. f.fontSize = size
  75. f.changed = true
  76. }
  77. // Size returns the current font size.
  78. func (f *Font) Size() float64 {
  79. return f.fontSize
  80. }
  81. // SetDPI sets the current DPI of the font.
  82. func (f *Font) SetDPI(dpi float64) {
  83. if dpi == f.fontDPI {
  84. return
  85. }
  86. f.fontDPI = dpi
  87. f.changed = true
  88. }
  89. // DPI returns the current font DPI.
  90. func (f *Font) DPI() float64 {
  91. return f.fontDPI
  92. }
  93. // SetLineSpacing sets the spacing between lines.
  94. func (f *Font) SetLineSpacing(spacing float64) {
  95. f.lineSpacing = spacing
  96. }
  97. // LineSpacing returns the current spacing between lines.
  98. func (f *Font) LineSpacing() float64 {
  99. return f.lineSpacing
  100. }
  101. // SetHinting sets the hinting type.
  102. func (f *Font) SetHinting(hinting font.Hinting) {
  103. if hinting == f.hinting {
  104. return
  105. }
  106. f.hinting = hinting
  107. f.changed = true
  108. }
  109. // Hinting returns the current hinting type.
  110. func (f *Font) Hinting() font.Hinting {
  111. return f.hinting
  112. }
  113. // SetFgColor sets the current foreground color of the font.
  114. // The alpha value is set to 1 (opaque).
  115. func (f *Font) SetFgColor(color *math32.Color) {
  116. f.fgColor.FromColor(color, 1.0)
  117. f.fg = image.NewUniform(Color4RGBA(&f.fgColor))
  118. }
  119. // SetFgColor4 sets the current foreground color of the font.
  120. func (f *Font) SetFgColor4(color *math32.Color4) {
  121. f.fgColor = *color
  122. f.fg = image.NewUniform(Color4RGBA(color))
  123. }
  124. // FgColor4 returns the current foreground color.
  125. func (f *Font) FgColor4() math32.Color4 {
  126. return f.fgColor
  127. }
  128. // SetBgColor sets the current foreground color of the font.
  129. // The alpha value is set to 1 (opaque).
  130. func (f *Font) SetBgColor(color *math32.Color) {
  131. f.bgColor.FromColor(color, 1.0)
  132. f.bg = image.NewUniform(Color4RGBA(&f.fgColor))
  133. }
  134. // SetBgColor4 sets the current background color of the font.
  135. func (f *Font) SetBgColor4(color *math32.Color4) {
  136. f.bgColor = *color
  137. f.bg = image.NewUniform(Color4RGBA(color))
  138. }
  139. // BgColor4 returns the current background color.
  140. func (f *Font) BgColor4() math32.Color4 {
  141. return f.bgColor
  142. }
  143. // updateFace updates the font face if parameters have changed.
  144. func (f *Font) updateFace() {
  145. if f.changed {
  146. f.face = truetype.NewFace(f.ttf, &truetype.Options{
  147. Size: f.fontSize,
  148. DPI: f.fontDPI,
  149. Hinting: f.hinting,
  150. })
  151. f.changed = false
  152. }
  153. }
  154. // MeasureText returns the minimum width and height in pixels
  155. // necessary for an image to contain the specified text.
  156. // The supplied text string can contain line break escape sequences (\n).
  157. func (f *Font) MeasureText(text string) (int, int) {
  158. // Create font drawer
  159. f.updateFace()
  160. d := &font.Drawer{Dst: nil, Src: f.fg, Face: f.face}
  161. // Draw text
  162. var width, height int
  163. metrics := f.face.Metrics()
  164. lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
  165. lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
  166. lines := strings.Split(text, "\n")
  167. for i, s := range lines {
  168. d.Dot = fixed.P(0, height)
  169. lineWidth := d.MeasureString(s).Ceil()
  170. if lineWidth > width {
  171. width = lineWidth
  172. }
  173. height += lineHeight
  174. if i > 1 {
  175. height += lineGap
  176. }
  177. }
  178. return width, height
  179. }
  180. // Metrics returns the font metrics
  181. func (f *Font) Metrics() font.Metrics {
  182. f.updateFace()
  183. return f.face.Metrics()
  184. }
  185. // Canvas is an image to draw text
  186. type Canvas struct {
  187. RGBA *image.RGBA
  188. bgColor *image.Uniform
  189. }
  190. // NewCanvas creates and returns a pointer to a new canvas with the
  191. // specified width and height in pixels and background color
  192. func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas {
  193. c := new(Canvas)
  194. c.RGBA = image.NewRGBA(image.Rect(0, 0, width, height))
  195. // Creates the image.Uniform for the background color
  196. c.bgColor = image.NewUniform(Color4RGBA(bgColor))
  197. // Draw image
  198. draw.Draw(c.RGBA, c.RGBA.Bounds(), c.bgColor, image.ZP, draw.Src)
  199. return c
  200. }
  201. // DrawText draws text at the specified position (in pixels)
  202. // of this canvas, using the specified font.
  203. // The supplied text string can contain line break escape sequences (\n).
  204. func (c Canvas) DrawText(x, y int, text string, f *Font) {
  205. // Creates drawer
  206. f.updateFace()
  207. d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
  208. // Draw text
  209. metrics := f.face.Metrics()
  210. py := y + metrics.Ascent.Round()
  211. lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
  212. lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
  213. lines := strings.Split(text, "\n")
  214. for i, s := range lines {
  215. d.Dot = fixed.P(x, py)
  216. d.DrawString(s)
  217. py += lineHeight
  218. if i > 1 {
  219. py += lineGap
  220. }
  221. }
  222. }
  223. // DrawTextCaret draws text at the specified position (in pixels)
  224. // of this canvas, using the specified font, and also a caret at
  225. // the specified line and column.
  226. // The supplied text string can contain line break escape sequences (\n).
  227. func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) error {
  228. // Creates drawer
  229. f.updateFace()
  230. d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
  231. // Draw text
  232. metrics := f.face.Metrics()
  233. py := y + metrics.Ascent.Round()
  234. lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
  235. lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
  236. lines := strings.Split(text, "\n")
  237. for l, s := range lines {
  238. d.Dot = fixed.P(x, py)
  239. d.DrawString(s)
  240. // Checks for caret position
  241. if l == line && col <= StrCount(s) {
  242. width, _ := f.MeasureText(StrPrefix(s, col))
  243. // Draw caret vertical line
  244. caretH := int(f.fontSize) + 2
  245. //caretY := int(pt.Y>>6) - int(f.fontSize) + 2
  246. caretY := int(d.Dot.Y>>6) - int(f.fontSize) + 2
  247. fgCol := f.FgColor4()
  248. color := Color4RGBA(&fgCol)
  249. for j := caretY; j < caretY+caretH; j++ {
  250. c.RGBA.Set(x+width, j, color)
  251. }
  252. }
  253. py += lineHeight
  254. if l > 1 {
  255. py += lineGap
  256. }
  257. }
  258. // pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.fontSize)>>6))
  259. // for l, s := range lines {
  260. // // Draw string
  261. // _, err := font.ctx.DrawString(s, pt)
  262. // if err != nil {
  263. // return err
  264. // }
  265. // // Checks for caret position
  266. // if l == line && col <= StrCount(s) {
  267. // width, _, err := font.MeasureText(StrPrefix(s, col))
  268. // if err != nil {
  269. // return err
  270. // }
  271. // // Draw caret vertical line
  272. // caretH := int(font.fontSize) + 2
  273. // caretY := int(pt.Y>>6) - int(font.fontSize) + 2
  274. // color := Color4RGBA(&math32.Color4{0, 0, 0, 1})
  275. // for j := caretY; j < caretY+caretH; j++ {
  276. // c.RGBA.Set(x+width, j, color)
  277. // }
  278. // }
  279. // // Increment y coordinate
  280. // pt.Y += font.ctx.PointToFixed(font.fontSize * font.lineSpacing)
  281. // }
  282. return nil
  283. }
  284. // Color4RGBA converts a math32.Color4 to Go's color.RGBA
  285. func Color4RGBA(c *math32.Color4) color.RGBA {
  286. red := uint8(c.R * 0xFF)
  287. green := uint8(c.G * 0xFF)
  288. blue := uint8(c.B * 0xFF)
  289. al := uint8(c.A * 0xFF)
  290. return color.RGBA{red, green, blue, al}
  291. }