diff --git a/text/text.go b/text/text.go index 57c5a7fdd..61d4b6d40 100644 --- a/text/text.go +++ b/text/text.go @@ -57,7 +57,11 @@ var ( colorMCache = map[colorMCacheKey]*colorMCacheEntry{} ) -func drawGlyph(dst *ebiten.Image, face font.Face, r rune, x, y fixed.Int26_6, clr color.Color) { +func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *glyphImage, x, y fixed.Int26_6, clr color.Color) { + if img == nil { + return + } + // RGBA() is in [0 - 0xffff]. Adjust them in [0 - 0xff]. cr, cg, cb, ca := clr.RGBA() cr >>= 8 @@ -68,11 +72,6 @@ func drawGlyph(dst *ebiten.Image, face font.Face, r rune, x, y fixed.Int26_6, cl return } - img := getGlyphImage(face, r) - if img == nil { - return - } - b := getGlyphBounds(face, r) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(fixed26_6ToFloat64(x+b.Min.X), fixed26_6ToFloat64(y+b.Min.Y)) @@ -107,8 +106,10 @@ func drawGlyph(dst *ebiten.Image, face font.Face, r rune, x, y fixed.Int26_6, cl colorMCache[key] = e } op.ColorM = e.m + re := image.Rect(img.x, img.y, img.x+img.width, img.y+img.height) + op.SourceRect = &re - _ = dst.DrawImage(img, op) + _ = dst.DrawImage(img.image, op) } var ( @@ -128,8 +129,16 @@ func getGlyphBounds(face font.Face, r rune) *fixed.Rectangle26_6 { return &b } +type glyphImage struct { + image *ebiten.Image + x int + y int + width int + height int +} + type glyphImageCacheEntry struct { - image *ebiten.Image + image *glyphImage atime int64 } @@ -138,7 +147,7 @@ var ( emptyGlyphs = map[font.Face]map[rune]struct{}{} ) -func getGlyphImage(face font.Face, r rune) *ebiten.Image { +func getGlyphImages(face font.Face, runes []rune) []*glyphImage { if _, ok := emptyGlyphs[face]; !ok { emptyGlyphs[face] = map[rune]struct{}{} } @@ -146,49 +155,83 @@ func getGlyphImage(face font.Face, r rune) *ebiten.Image { glyphImageCache[face] = map[rune]*glyphImageCacheEntry{} } - if _, ok := emptyGlyphs[face][r]; ok { - return nil - } - if e, ok := glyphImageCache[face][r]; ok { - e.atime = now() - return e.image - } - - b := getGlyphBounds(face, r) - w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil() - if w == 0 || h == 0 { - emptyGlyphs[face][r] = struct{}{} - return nil - } - - if len(glyphImageCache[face]) > cacheLimit { - oldest := int64(math.MaxInt64) - oldestKey := rune(-1) - for r, e := range glyphImageCache[face] { - if e.atime < oldest { - oldestKey = r - oldest = e.atime - } + imgs := make([]*glyphImage, len(runes)) + neededGlyphs := map[int]*fixed.Rectangle26_6{} + for i, r := range runes { + if _, ok := emptyGlyphs[face][r]; ok { + continue } - glyphImageCache[face][oldestKey].image.Dispose() - delete(glyphImageCache[face], oldestKey) + + if e, ok := glyphImageCache[face][r]; ok { + e.atime = now() + imgs[i] = e.image + continue + } + + b := getGlyphBounds(face, r) + w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil() + if w == 0 || h == 0 { + emptyGlyphs[face][r] = struct{}{} + continue + } + + // TODO: What if len(runes) > cacheLimit? + if len(glyphImageCache[face]) > cacheLimit { + oldest := int64(math.MaxInt64) + oldestKey := rune(-1) + for r, e := range glyphImageCache[face] { + if e.atime < oldest { + oldestKey = r + oldest = e.atime + } + } + delete(glyphImageCache[face], oldestKey) + } + + neededGlyphs[i] = b } - rgba := image.NewRGBA(image.Rect(0, 0, w, h)) - d := font.Drawer{ - Dst: rgba, - Src: image.White, - Face: face, + w2 := 0 + h2 := 0 + for _, b := range neededGlyphs { + w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil() + w2 += w + if h2 < h { + h2 = h + } } - d.Dot = fixed.Point26_6{-b.Min.X, -b.Min.Y} - d.DrawString(string(r)) + rgba := image.NewRGBA(image.Rect(0, 0, w2, h2)) - img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault) - glyphImageCache[face][r] = &glyphImageCacheEntry{ - image: img, - atime: now(), + x := 0 + for i, b := range neededGlyphs { + w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil() + + r := runes[i] + d := font.Drawer{ + Dst: rgba, + Src: image.White, + Face: face, + } + d.Dot = fixed.Point26_6{fixed.I(x) - b.Min.X, -b.Min.Y} + d.DrawString(string(r)) + + img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault) + g := &glyphImage{ + image: img, + x: x, + y: 0, + width: w, + height: h, + } + glyphImageCache[face][r] = &glyphImageCacheEntry{ + image: g, + atime: now(), + } + imgs[i] = g + + x += w } - return img + return imgs } var textM sync.Mutex @@ -214,11 +257,12 @@ func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Co prevR := rune(-1) runes := []rune(text) - for _, r := range runes { + glyphImgs := getGlyphImages(face, runes) + for i, r := range runes { if prevR >= 0 { fx += face.Kern(prevR, r) } - drawGlyph(dst, face, r, fx, fixed.I(y), clr) + drawGlyph(dst, face, r, glyphImgs[i], fx, fixed.I(y), clr) fx += glyphAdvance(face, r) prevR = r