1
0

text.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. // Copyright 2017 The Ebiten Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package text offers functions to draw texts on an Ebitengine's image.
  15. //
  16. // For the example using a TTF font, see font package in the examples.
  17. //
  18. // Deprecated: as of v2.7. Use text/v2 instead.
  19. package text
  20. import (
  21. "image"
  22. "image/color"
  23. "sync"
  24. "golang.org/x/image/font"
  25. "golang.org/x/image/math/fixed"
  26. "github.com/hajimehoshi/ebiten/v2"
  27. "github.com/hajimehoshi/ebiten/v2/internal/hook"
  28. )
  29. var (
  30. monotonicClock int64
  31. )
  32. func now() int64 {
  33. return monotonicClock
  34. }
  35. func init() {
  36. hook.AppendHookOnBeforeUpdate(func() error {
  37. monotonicClock++
  38. return nil
  39. })
  40. }
  41. func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
  42. return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
  43. }
  44. func adjustOffsetGranularity(x fixed.Int26_6) fixed.Int26_6 {
  45. return x / (1 << 4) * (1 << 4)
  46. }
  47. func drawGlyph(dst *ebiten.Image, img *ebiten.Image, topleft fixed.Point26_6, op *ebiten.DrawImageOptions) {
  48. if img == nil {
  49. return
  50. }
  51. op2 := &ebiten.DrawImageOptions{}
  52. if op != nil {
  53. *op2 = *op
  54. op2.GeoM.Reset()
  55. }
  56. op2.GeoM.Translate(fixed26_6ToFloat64(topleft.X), fixed26_6ToFloat64(topleft.Y))
  57. if op != nil {
  58. op2.GeoM.Concat(op.GeoM)
  59. }
  60. dst.DrawImage(img, op2)
  61. }
  62. type glyphImageCacheKey struct {
  63. rune rune
  64. xoffset fixed.Int26_6
  65. }
  66. type glyphImageCacheEntry struct {
  67. image *ebiten.Image
  68. atime int64
  69. }
  70. var (
  71. glyphImageCache = map[*faceWithCache]map[glyphImageCacheKey]*glyphImageCacheEntry{}
  72. )
  73. func getGlyphImage(face *faceWithCache, r rune, offset fixed.Point26_6) *ebiten.Image {
  74. if _, ok := glyphImageCache[face]; !ok {
  75. glyphImageCache[face] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
  76. }
  77. key := glyphImageCacheKey{
  78. rune: r,
  79. xoffset: offset.X,
  80. }
  81. if e, ok := glyphImageCache[face][key]; ok {
  82. e.atime = now()
  83. return e.image
  84. }
  85. b, _, _ := face.GlyphBounds(r)
  86. w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
  87. if w == 0 || h == 0 {
  88. glyphImageCache[face][key] = &glyphImageCacheEntry{
  89. image: nil,
  90. atime: now(),
  91. }
  92. return nil
  93. }
  94. if b.Min.X&((1<<6)-1) != 0 {
  95. w++
  96. }
  97. if b.Min.Y&((1<<6)-1) != 0 {
  98. h++
  99. }
  100. rgba := image.NewRGBA(image.Rect(0, 0, w, h))
  101. d := font.Drawer{
  102. Dst: rgba,
  103. Src: image.White,
  104. Face: face,
  105. }
  106. x, y := -b.Min.X, -b.Min.Y
  107. x += offset.X
  108. y += offset.Y
  109. d.Dot = fixed.Point26_6{X: x, Y: y}
  110. d.DrawString(string(r))
  111. img := ebiten.NewImageFromImage(rgba)
  112. glyphImageCache[face][key] = &glyphImageCacheEntry{
  113. image: img,
  114. atime: now(),
  115. }
  116. return img
  117. }
  118. var textM sync.Mutex
  119. // Draw draws a given text on a given destination image dst.
  120. //
  121. // face is the font for text rendering.
  122. // (x, y) represents the origin position in this figure:
  123. // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png.
  124. // Be careful that this doesn't represent upper-left corner position.
  125. //
  126. // clr is the color for text rendering.
  127. //
  128. // The '\n' newline character puts the following text on the next line.
  129. // Line height is based on Metrics().Height of the font.
  130. //
  131. // Glyphs used for rendering are cached in least-recently-used way.
  132. // Then old glyphs might be evicted from the cache.
  133. // As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at Draw are cached.
  134. // The cache is shared with CacheGlyphs.
  135. //
  136. // It is OK to call Draw with a same text and a same face at every frame in terms of performance.
  137. //
  138. // Draw/DrawWithOptions and CacheGlyphs are implemented like this:
  139. //
  140. // Draw = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
  141. // + Draw them onto the destination by `(*ebiten.Image).DrawImage`
  142. // CacheGlyphs = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
  143. //
  144. // Be careful that the passed font face is held by this package and is never released.
  145. // This is a known issue (#498).
  146. //
  147. // Draw is concurrent-safe.
  148. func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
  149. op := &ebiten.DrawImageOptions{}
  150. op.GeoM.Translate(float64(x), float64(y))
  151. op.ColorScale.ScaleWithColor(clr)
  152. DrawWithOptions(dst, text, face, op)
  153. }
  154. // DrawWithOptions draws a given text on a given destination image dst.
  155. //
  156. // face is the font for text rendering.
  157. // options is the options to draw glyph images.
  158. // The origin point is the origin position in this figure:
  159. // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png.
  160. // Be careful that the origin point is not upper-left corner position of dst.
  161. // The default glyph color is white. options' ColorScale adjusts the color.
  162. //
  163. // The '\n' newline character puts the following text on the next line.
  164. // Line height is based on Metrics().Height of the font.
  165. //
  166. // Glyphs used for rendering are cached in least-recently-used way.
  167. // Then old glyphs might be evicted from the cache.
  168. // As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at DrawWithOptions are cached.
  169. // The cache is shared with CacheGlyphs.
  170. //
  171. // It is OK to call DrawWithOptions with a same text and a same face at every frame in terms of performance.
  172. //
  173. // Draw/DrawWithOptions and CacheGlyphs are implemented like this:
  174. //
  175. // Draw = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
  176. // + Draw them onto the destination by `(*ebiten.Image).DrawImage`
  177. // CacheGlyphs = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
  178. //
  179. // Be careful that the passed font face is held by this package and is never released.
  180. // This is a known issue (#498).
  181. //
  182. // DrawWithOptions is concurrent-safe.
  183. func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *ebiten.DrawImageOptions) {
  184. textM.Lock()
  185. defer textM.Unlock()
  186. fc := faceWithCacheFromFace(face)
  187. var dx, dy fixed.Int26_6
  188. prevR := rune(-1)
  189. faceHeight := fc.Metrics().Height
  190. for _, r := range text {
  191. if prevR >= 0 {
  192. dx += fc.Kern(prevR, r)
  193. }
  194. if r == '\n' {
  195. dx = 0
  196. dy += faceHeight
  197. prevR = rune(-1)
  198. continue
  199. }
  200. // Adjust the position to the integers.
  201. // The current glyph images assume that they are rendered on integer positions so far.
  202. b, a, _ := fc.GlyphBounds(r)
  203. offset := fixed.Point26_6{
  204. X: (adjustOffsetGranularity(dx) + b.Min.X) & ((1 << 6) - 1),
  205. Y: b.Min.Y & ((1 << 6) - 1),
  206. }
  207. img := getGlyphImage(fc, r, offset)
  208. drawGlyph(dst, img, fixed.Point26_6{
  209. X: dx + b.Min.X - offset.X,
  210. Y: dy + b.Min.Y - offset.Y,
  211. }, options)
  212. dx += a
  213. prevR = r
  214. }
  215. // cacheSoftLimit indicates the soft limit of the number of glyphs in the cache.
  216. // If the number of glyphs exceeds this soft limits, old glyphs are removed.
  217. // Even after cleaning up the cache, the number of glyphs might still exceed the soft limit, but
  218. // this is fine.
  219. const cacheSoftLimit = 512
  220. // Clean up the cache.
  221. if len(glyphImageCache[fc]) > cacheSoftLimit {
  222. for r, e := range glyphImageCache[fc] {
  223. // 60 is an arbitrary number.
  224. if e.atime < now()-60 {
  225. delete(glyphImageCache[fc], r)
  226. }
  227. }
  228. }
  229. }
  230. // BoundString returns the measured size of a given string using a given font.
  231. // This method will return the exact size in pixels that a string drawn by Draw will be.
  232. // The bound's origin point indicates the origin position in this figure:
  233. // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png.
  234. //
  235. // BoundString behaves almost exactly like golang.org/x/image/font's BoundString,
  236. // but newline characters '\n' in the input string move the text position to the following line.
  237. //
  238. // face is the font for text rendering.
  239. // text is the string that's being measured.
  240. //
  241. // Be careful that the passed font face is held by this package and is never released.
  242. // This is a known issue (#498).
  243. //
  244. // BoundString is concurrent-safe.
  245. //
  246. // Deprecated: as of v2.6. Use golang.org/x/image/font.BoundString instead, or use a face's Metrics (Ascent and Descent) and font.MeasureString instead.
  247. func BoundString(face font.Face, text string) image.Rectangle {
  248. textM.Lock()
  249. defer textM.Unlock()
  250. fc := faceWithCacheFromFace(face)
  251. m := fc.Metrics()
  252. faceHeight := m.Height
  253. fx, fy := fixed.I(0), fixed.I(0)
  254. prevR := rune(-1)
  255. var bounds fixed.Rectangle26_6
  256. for _, r := range text {
  257. if prevR >= 0 {
  258. fx += fc.Kern(prevR, r)
  259. }
  260. if r == '\n' {
  261. fx = fixed.I(0)
  262. fy += faceHeight
  263. prevR = rune(-1)
  264. continue
  265. }
  266. b, a, _ := fc.GlyphBounds(r)
  267. b.Min.X += fx
  268. b.Max.X += fx
  269. b.Min.Y += fy
  270. b.Max.Y += fy
  271. bounds = bounds.Union(b)
  272. fx += a
  273. prevR = r
  274. }
  275. return image.Rect(
  276. bounds.Min.X.Floor(),
  277. bounds.Min.Y.Floor(),
  278. bounds.Max.X.Ceil(),
  279. bounds.Max.Y.Ceil(),
  280. )
  281. }
  282. // CacheGlyphs precaches the glyphs for the given text and the given font face into the cache.
  283. //
  284. // Glyphs used for rendering are cached in least-recently-used way.
  285. // Then old glyphs might be evicted from the cache.
  286. // As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at CacheGlyphs are cached.
  287. // The cache is shared with Draw.
  288. //
  289. // Draw/DrawWithOptions and CacheGlyphs are implemented like this:
  290. //
  291. // Draw = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
  292. // + Draw them onto the destination by `(*ebiten.Image).DrawImage`
  293. // CacheGlyphs = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
  294. //
  295. // Draw automatically creates and caches necessary glyphs, so usually you don't have to call CacheGlyphs
  296. // explicitly. However, for example, when you call Draw for each rune of one big text, Draw tries to create the glyph
  297. // cache and render it for each rune. This is very inefficient because creating a glyph image and rendering it are
  298. // different operations (`(*ebiten.Image).WritePixels` and `(*ebiten.Image).DrawImage`) and can never be merged as
  299. // one draw call. CacheGlyphs creates necessary glyphs without rendering them so that these operations are likely
  300. // merged into one draw call regardless of the size of the text.
  301. //
  302. // If a rune's glyph is already cached, CacheGlyphs does nothing for the rune.
  303. //
  304. // One rune can have multiple variations of glyphs due to sub-pixels in X direction.
  305. // CacheGlyphs creates all such variations for one rune, while Draw creates only necessary glyphs.
  306. func CacheGlyphs(face font.Face, text string) {
  307. textM.Lock()
  308. defer textM.Unlock()
  309. fc := faceWithCacheFromFace(face)
  310. var dx fixed.Int26_6
  311. prevR := rune(-1)
  312. for _, r := range text {
  313. if prevR >= 0 {
  314. dx += fc.Kern(prevR, r)
  315. }
  316. if r == '\n' {
  317. dx = 0
  318. continue
  319. }
  320. b, a, _ := fc.GlyphBounds(r)
  321. // Cache all 4 variations for one rune (#2528).
  322. for i := 0; i < 4; i++ {
  323. offset := fixed.Point26_6{
  324. X: (fixed.Int26_6(i*(1<<4)) + b.Min.X) & ((1 << 6) - 1),
  325. Y: b.Min.Y & ((1 << 6) - 1),
  326. }
  327. getGlyphImage(fc, r, offset)
  328. }
  329. dx += a
  330. prevR = r
  331. }
  332. }
  333. // FaceWithLineHeight returns a font.Face with the given lineHeight in pixels.
  334. // The returned face will otherwise have the same glyphs and metrics as face.
  335. func FaceWithLineHeight(face font.Face, lineHeight float64) font.Face {
  336. return faceWithLineHeight{
  337. face: face,
  338. lineHeight: fixed.Int26_6(lineHeight * (1 << 6)),
  339. }
  340. }
  341. type faceWithLineHeight struct {
  342. face font.Face
  343. lineHeight fixed.Int26_6
  344. }
  345. func (f faceWithLineHeight) Close() error {
  346. return f.face.Close()
  347. }
  348. func (f faceWithLineHeight) Glyph(origin fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
  349. return f.face.Glyph(origin, r)
  350. }
  351. func (f faceWithLineHeight) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
  352. return f.face.GlyphBounds(r)
  353. }
  354. func (f faceWithLineHeight) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
  355. return f.face.GlyphAdvance(r)
  356. }
  357. func (f faceWithLineHeight) Kern(r0, r1 rune) fixed.Int26_6 {
  358. return f.face.Kern(r0, r1)
  359. }
  360. func (f faceWithLineHeight) Metrics() font.Metrics {
  361. m := f.face.Metrics()
  362. m.Height = f.lineHeight
  363. return m
  364. }
  365. // Glyph is information to render one glyph.
  366. type Glyph struct {
  367. // Rune is a character for this glyph.
  368. Rune rune
  369. // Image is an image for this glyph.
  370. // Image is a grayscale image i.e. RGBA values are the same.
  371. // Image should be used as a render source and should not be modified.
  372. Image *ebiten.Image
  373. // X is the X position to render this glyph.
  374. // The position is determined in a sequence of characters given at AppendGlyphs.
  375. // The position's origin is the first character's origin position.
  376. X float64
  377. // Y is the Y position to render this glyph.
  378. // The position is determined in a sequence of characters given at AppendGlyphs.
  379. // The position's origin is the first character's origin position.
  380. Y float64
  381. }
  382. // AppendGlyphs appends the glyph information to glyphs.
  383. // You can render each glyphs as you like. See examples/text for an example of AppendGlyphs.
  384. func AppendGlyphs(glyphs []Glyph, face font.Face, text string) []Glyph {
  385. textM.Lock()
  386. defer textM.Unlock()
  387. fc := faceWithCacheFromFace(face)
  388. var pos fixed.Point26_6
  389. prevR := rune(-1)
  390. faceHeight := fc.Metrics().Height
  391. for _, r := range text {
  392. if prevR >= 0 {
  393. pos.X += fc.Kern(prevR, r)
  394. }
  395. if r == '\n' {
  396. pos.X = 0
  397. pos.Y += faceHeight
  398. prevR = rune(-1)
  399. continue
  400. }
  401. b, a, _ := fc.GlyphBounds(r)
  402. offset := fixed.Point26_6{
  403. X: (adjustOffsetGranularity(pos.X) + b.Min.X) & ((1 << 6) - 1),
  404. Y: b.Min.Y & ((1 << 6) - 1),
  405. }
  406. if img := getGlyphImage(fc, r, offset); img != nil {
  407. // Adjust the position to the integers.
  408. // The current glyph images assume that they are rendered on integer positions so far.
  409. glyphs = append(glyphs, Glyph{
  410. Rune: r,
  411. Image: img,
  412. X: fixed26_6ToFloat64(pos.X + b.Min.X - offset.X),
  413. Y: fixed26_6ToFloat64(pos.Y + b.Min.Y - offset.Y),
  414. })
  415. }
  416. pos.X += a
  417. prevR = r
  418. }
  419. return glyphs
  420. }