font.go 11 KB

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