font.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. // Attributes must be set prior to drawing.
  18. type Font struct {
  19. ttf *truetype.Font // The TrueType font
  20. face font.Face // The font face
  21. attrib FontAttributes // Internal attribute cache
  22. fg *image.Uniform // Text color cache
  23. bg *image.Uniform // Background color cache
  24. changed bool // Whether attributes have changed and the font face needs to be recreated
  25. }
  26. // FontAttributes contains tunable attributes of a font.
  27. type FontAttributes struct {
  28. PointSize float64 // Point size of the font
  29. DPI float64 // Resolution of the font in dots per inch
  30. LineSpacing float64 // Spacing between lines (in terms of font height)
  31. Hinting font.Hinting // Font hinting
  32. }
  33. // Font Hinting types.
  34. const (
  35. HintingNone = font.HintingNone
  36. HintingVertical = font.HintingVertical
  37. HintingFull = font.HintingFull
  38. )
  39. // NewFont creates and returns a new font object using the specified TrueType font file.
  40. func NewFont(ttfFile string) (*Font, error) {
  41. // Reads font bytes
  42. fontBytes, err := ioutil.ReadFile(ttfFile)
  43. if err != nil {
  44. return nil, err
  45. }
  46. return NewFontFromData(fontBytes)
  47. }
  48. // NewFontFromData creates and returns a new font object from the specified TTF data.
  49. func NewFontFromData(fontData []byte) (*Font, error) {
  50. // Parses the font data
  51. ttf, err := truetype.Parse(fontData)
  52. if err != nil {
  53. return nil, err
  54. }
  55. f := new(Font)
  56. f.ttf = ttf
  57. // Initialize with default values
  58. f.attrib = FontAttributes{}
  59. f.attrib.PointSize = 12
  60. f.attrib.DPI = 72
  61. f.attrib.LineSpacing = 1.0
  62. f.attrib.Hinting = font.HintingNone
  63. f.SetColor(&math32.Color4{0, 0, 0, 1})
  64. // Create font face
  65. f.face = truetype.NewFace(f.ttf, &truetype.Options{
  66. Size: f.attrib.PointSize,
  67. DPI: f.attrib.DPI,
  68. Hinting: f.attrib.Hinting,
  69. })
  70. return f, nil
  71. }
  72. // SetPointSize sets the point size of the font.
  73. func (f *Font) SetPointSize(size float64) {
  74. if size == f.attrib.PointSize {
  75. return
  76. }
  77. f.attrib.PointSize = size
  78. f.changed = true
  79. }
  80. // SetDPI sets the resolution of the font in dots per inches (DPI).
  81. func (f *Font) SetDPI(dpi float64) {
  82. if dpi == f.attrib.DPI {
  83. return
  84. }
  85. f.attrib.DPI = dpi
  86. f.changed = true
  87. }
  88. // SetLineSpacing sets the amount of spacing between lines (in terms of font height).
  89. func (f *Font) SetLineSpacing(spacing float64) {
  90. if spacing == f.attrib.LineSpacing {
  91. return
  92. }
  93. f.attrib.LineSpacing = spacing
  94. f.changed = true
  95. }
  96. // SetHinting sets the hinting type.
  97. func (f *Font) SetHinting(hinting font.Hinting) {
  98. if hinting == f.attrib.Hinting {
  99. return
  100. }
  101. f.attrib.Hinting = hinting
  102. f.changed = true
  103. }
  104. // SetFgColor sets the text color.
  105. func (f *Font) SetFgColor(color *math32.Color4) {
  106. f.fg = image.NewUniform(Color4RGBA(color))
  107. }
  108. // SetBgColor sets the background color.
  109. func (f *Font) SetBgColor(color *math32.Color4) {
  110. f.bg = image.NewUniform(Color4RGBA(color))
  111. }
  112. // SetColor sets the text color to the specified value and makes the background color transparent.
  113. // Note that for perfect transparency in the anti-aliased region it's important that the RGB components
  114. // of the text and background colors match. This method handles that for the user.
  115. func (f *Font) SetColor(fg *math32.Color4) {
  116. f.fg = image.NewUniform(Color4RGBA(fg))
  117. f.bg = image.NewUniform(Color4RGBA(&math32.Color4{fg.R, fg.G, fg.B, 0}))
  118. }
  119. // SetAttributes sets the font attributes.
  120. func (f *Font) SetAttributes(fa *FontAttributes) {
  121. f.SetPointSize(fa.PointSize)
  122. f.SetDPI(fa.DPI)
  123. f.SetLineSpacing(fa.LineSpacing)
  124. f.SetHinting(fa.Hinting)
  125. }
  126. // updateFace updates the font face if parameters have changed.
  127. func (f *Font) updateFace() {
  128. if f.changed {
  129. f.face = truetype.NewFace(f.ttf, &truetype.Options{
  130. Size: f.attrib.PointSize,
  131. DPI: f.attrib.DPI,
  132. Hinting: f.attrib.Hinting,
  133. })
  134. f.changed = false
  135. }
  136. }
  137. // MeasureText returns the minimum width and height in pixels necessary for an image to contain
  138. // the specified text. The supplied text string can contain line break escape sequences (\n).
  139. func (f *Font) MeasureText(text string) (int, int) {
  140. // Create font drawer
  141. f.updateFace()
  142. d := &font.Drawer{Dst: nil, Src: f.fg, Face: f.face}
  143. // Draw text
  144. var width, height int
  145. metrics := f.face.Metrics()
  146. lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
  147. lineGap := int((f.attrib.LineSpacing - float64(1)) * float64(lineHeight))
  148. lines := strings.Split(text, "\n")
  149. for i, s := range lines {
  150. d.Dot = fixed.P(0, height)
  151. lineWidth := d.MeasureString(s).Ceil()
  152. if lineWidth > width {
  153. width = lineWidth
  154. }
  155. height += lineHeight
  156. if i > 1 {
  157. height += lineGap
  158. }
  159. }
  160. return width, height
  161. }
  162. // Metrics returns the font metrics.
  163. func (f *Font) Metrics() font.Metrics {
  164. f.updateFace()
  165. return f.face.Metrics()
  166. }
  167. // DrawText draws the specified text on a new, tightly fitting image, and returns a pointer to the image.
  168. func (f *Font) DrawText(text string) *image.RGBA {
  169. width, height := f.MeasureText(text)
  170. img := image.NewRGBA(image.Rect(0, 0, width, height))
  171. draw.Draw(img, img.Bounds(), f.bg, image.ZP, draw.Src)
  172. f.DrawTextOnImage(text, 0, 0, img)
  173. return img
  174. }
  175. // DrawTextOnImage draws the specified text on the specified image at the specified coordinates.
  176. func (f *Font) DrawTextOnImage(text string, x, y int, dst *image.RGBA) {
  177. f.updateFace()
  178. d := &font.Drawer{Dst: dst, Src: f.fg, Face: f.face}
  179. // Draw text
  180. metrics := f.face.Metrics()
  181. py := y + metrics.Ascent.Round()
  182. lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
  183. lineGap := int((f.attrib.LineSpacing - float64(1)) * float64(lineHeight))
  184. lines := strings.Split(text, "\n")
  185. for i, s := range lines {
  186. d.Dot = fixed.P(x, py)
  187. d.DrawString(s)
  188. py += lineHeight
  189. if i > 1 {
  190. py += lineGap
  191. }
  192. }
  193. }
  194. // Canvas is an image to draw on.
  195. type Canvas struct {
  196. RGBA *image.RGBA
  197. bgColor *image.Uniform
  198. }
  199. // NewCanvas creates and returns a pointer to a new canvas with the
  200. // specified width and height in pixels and background color
  201. func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas {
  202. c := new(Canvas)
  203. c.RGBA = image.NewRGBA(image.Rect(0, 0, width, height))
  204. // Creates the image.Uniform for the background color
  205. c.bgColor = image.NewUniform(Color4RGBA(bgColor))
  206. // Draw image
  207. draw.Draw(c.RGBA, c.RGBA.Bounds(), c.bgColor, image.ZP, draw.Src)
  208. return c
  209. }
  210. // DrawText draws text at the specified position (in pixels)
  211. // of this canvas, using the specified font.
  212. // The supplied text string can contain line break escape sequences (\n).
  213. func (c Canvas) DrawText(x, y int, text string, f *Font) {
  214. f.DrawTextOnImage(text, x, y, c.RGBA)
  215. }
  216. // DrawTextCaret draws text at the specified position (in pixels)
  217. // of this canvas, using the specified font, and also a caret at
  218. // the specified line and column.
  219. // The supplied text string can contain line break escape sequences (\n).
  220. // TODO Implement caret as a gui.Panel in gui.Edit
  221. func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) error {
  222. // Creates drawer
  223. f.updateFace()
  224. d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
  225. // Draw text
  226. metrics := f.face.Metrics()
  227. py := y + metrics.Ascent.Round()
  228. lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
  229. lineGap := int((f.attrib.LineSpacing - float64(1)) * float64(lineHeight))
  230. lines := strings.Split(text, "\n")
  231. for l, s := range lines {
  232. d.Dot = fixed.P(x, py)
  233. d.DrawString(s)
  234. // Checks for caret position
  235. if l == line && col <= StrCount(s) {
  236. width, _ := f.MeasureText(StrPrefix(s, col))
  237. // Draw caret vertical line
  238. caretH := int(f.attrib.PointSize) + 2
  239. caretY := int(d.Dot.Y>>6) - int(f.attrib.PointSize) + 2
  240. color := Color4RGBA(&math32.Color4{0,0,0,1}) // Hardcoded to black
  241. for j := caretY; j < caretY+caretH; j++ {
  242. c.RGBA.Set(x+width, j, color)
  243. }
  244. }
  245. py += lineHeight
  246. if l > 1 {
  247. py += lineGap
  248. }
  249. }
  250. // TODO remove ?
  251. // pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.attrib.PointSize)>>6))
  252. // for l, s := range lines {
  253. // // Draw string
  254. // _, err := font.ctx.DrawString(s, pt)
  255. // if err != nil {
  256. // return err
  257. // }
  258. // // Checks for caret position
  259. // if l == line && col <= StrCount(s) {
  260. // width, _, err := font.MeasureText(StrPrefix(s, col))
  261. // if err != nil {
  262. // return err
  263. // }
  264. // // Draw caret vertical line
  265. // caretH := int(font.PointSize) + 2
  266. // caretY := int(pt.Y>>6) - int(font.PointSize) + 2
  267. // color := Color4RGBA(&math32.Color4{0, 0, 0, 1})
  268. // for j := caretY; j < caretY+caretH; j++ {
  269. // c.RGBA.Set(x+width, j, color)
  270. // }
  271. // }
  272. // // Increment y coordinate
  273. // pt.Y += font.ctx.PointToFixed(font.PointSize * font.LineSpacing)
  274. // }
  275. return nil
  276. }
  277. // Color4RGBA converts a math32.Color4 to Go's color.RGBA.
  278. func Color4RGBA(c *math32.Color4) color.RGBA {
  279. red := uint8(c.R * 0xFF)
  280. green := uint8(c.G * 0xFF)
  281. blue := uint8(c.B * 0xFF)
  282. alpha := uint8(c.A * 0xFF)
  283. return color.RGBA{red, green, blue, alpha}
  284. }