text: refactoring: introduce faceWithCache

This commit is contained in:
Hajime Hoshi 2023-05-26 01:44:13 +09:00
parent 8de08295e9
commit e374ca0ac3
3 changed files with 146 additions and 78 deletions

View File

@ -1,38 +0,0 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package text
import (
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
var glyphAdvanceCache = map[font.Face]map[rune]fixed.Int26_6{}
func glyphAdvance(face font.Face, r rune) fixed.Int26_6 {
m, ok := glyphAdvanceCache[face]
if !ok {
m = map[rune]fixed.Int26_6{}
glyphAdvanceCache[face] = m
}
a, ok := m[r]
if !ok {
a, _ = face.GlyphAdvance(r)
m[r] = a
}
return a
}

114
text/cache.go Normal file
View File

@ -0,0 +1,114 @@
// Copyright 2023 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package text
import (
"image"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
type glyphBoundsCacheValue struct {
bounds fixed.Rectangle26_6
advance fixed.Int26_6
ok bool
}
type glyphAdvanceCacheValue struct {
advance fixed.Int26_6
ok bool
}
type faceWithCache struct {
f font.Face
glyphBoundsCache map[rune]glyphBoundsCacheValue
glyphAdvanceCache map[rune]glyphAdvanceCacheValue
}
func (f *faceWithCache) Close() error {
if err := f.f.Close(); err != nil {
return err
}
f.glyphBoundsCache = nil
f.glyphAdvanceCache = nil
return nil
}
var faceWithCacheCache map[font.Face]*faceWithCache
func faceWithCacheFromFace(face font.Face) *faceWithCache {
if f, ok := faceWithCacheCache[face]; ok {
return f
}
f := &faceWithCache{
f: face,
}
if faceWithCacheCache == nil {
faceWithCacheCache = map[font.Face]*faceWithCache{}
}
faceWithCacheCache[face] = f
return f
}
func (f *faceWithCache) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
// TODO: Move glyphImageCache to here.
return f.f.Glyph(dot, r)
}
func (f *faceWithCache) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
if v, ok := f.glyphBoundsCache[r]; ok {
return v.bounds, v.advance, v.ok
}
bounds, advance, ok = f.f.GlyphBounds(r)
if f.glyphBoundsCache == nil {
f.glyphBoundsCache = map[rune]glyphBoundsCacheValue{}
}
f.glyphBoundsCache[r] = glyphBoundsCacheValue{
bounds: bounds,
advance: advance,
ok: ok,
}
return
}
func (f *faceWithCache) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
if v, ok := f.glyphAdvanceCache[r]; ok {
return v.advance, v.ok
}
advance, ok = f.f.GlyphAdvance(r)
if f.glyphAdvanceCache == nil {
f.glyphAdvanceCache = map[rune]glyphAdvanceCacheValue{}
}
f.glyphAdvanceCache[r] = glyphAdvanceCacheValue{
advance: advance,
ok: ok,
}
return
}
func (f *faceWithCache) Kern(r0, r1 rune) fixed.Int26_6 {
// TODO: Cache the values (#2673).
return f.f.Kern(r0, r1)
}
func (f *faceWithCache) Metrics() font.Metrics {
return f.f.Metrics()
}

View File

@ -71,22 +71,6 @@ func drawGlyph(dst *ebiten.Image, img *ebiten.Image, topleft fixed.Point26_6, op
dst.DrawImage(img, op2)
}
var (
glyphBoundsCache = map[font.Face]map[rune]fixed.Rectangle26_6{}
)
func getGlyphBounds(face font.Face, r rune) fixed.Rectangle26_6 {
if _, ok := glyphBoundsCache[face]; !ok {
glyphBoundsCache[face] = map[rune]fixed.Rectangle26_6{}
}
if b, ok := glyphBoundsCache[face][r]; ok {
return b
}
b, _, _ := face.GlyphBounds(r)
glyphBoundsCache[face][r] = b
return b
}
type glyphImageCacheKey struct {
rune rune
xoffset fixed.Int26_6
@ -98,10 +82,10 @@ type glyphImageCacheEntry struct {
}
var (
glyphImageCache = map[font.Face]map[glyphImageCacheKey]*glyphImageCacheEntry{}
glyphImageCache = map[*faceWithCache]map[glyphImageCacheKey]*glyphImageCacheEntry{}
)
func getGlyphImage(face font.Face, r rune, offset fixed.Point26_6) *ebiten.Image {
func getGlyphImage(face *faceWithCache, r rune, offset fixed.Point26_6) *ebiten.Image {
if _, ok := glyphImageCache[face]; !ok {
glyphImageCache[face] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
}
@ -115,7 +99,7 @@ func getGlyphImage(face font.Face, r rune, offset fixed.Point26_6) *ebiten.Image
return e.image
}
b := getGlyphBounds(face, r)
b, _, _ := face.GlyphBounds(r)
w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
if w == 0 || h == 0 {
glyphImageCache[face][key] = &glyphImageCacheEntry{
@ -235,14 +219,16 @@ func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *eb
textM.Lock()
defer textM.Unlock()
fc := faceWithCacheFromFace(face)
var dx, dy fixed.Int26_6
prevR := rune(-1)
faceHeight := face.Metrics().Height
faceHeight := fc.Metrics().Height
for _, r := range text {
if prevR >= 0 {
dx += face.Kern(prevR, r)
dx += fc.Kern(prevR, r)
}
if r == '\n' {
dx = 0
@ -253,17 +239,17 @@ func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *eb
// Adjust the position to the integers.
// The current glyph images assume that they are rendered on integer positions so far.
b := getGlyphBounds(face, r)
b, a, _ := fc.GlyphBounds(r)
offset := fixed.Point26_6{
X: (adjustOffsetGranularity(dx) + b.Min.X) & ((1 << 6) - 1),
Y: b.Min.Y & ((1 << 6) - 1),
}
img := getGlyphImage(face, r, offset)
img := getGlyphImage(fc, r, offset)
drawGlyph(dst, img, fixed.Point26_6{
X: dx + b.Min.X - offset.X,
Y: dy + b.Min.Y - offset.Y,
}, options)
dx += glyphAdvance(face, r)
dx += a
prevR = r
}
@ -275,11 +261,11 @@ func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *eb
const cacheSoftLimit = 512
// Clean up the cache.
if len(glyphImageCache[face]) > cacheSoftLimit {
for r, e := range glyphImageCache[face] {
if len(glyphImageCache[fc]) > cacheSoftLimit {
for r, e := range glyphImageCache[fc] {
// 60 is an arbitrary number.
if e.atime < now()-60 {
delete(glyphImageCache[face], r)
delete(glyphImageCache[fc], r)
}
}
}
@ -304,7 +290,9 @@ func BoundString(face font.Face, text string) image.Rectangle {
textM.Lock()
defer textM.Unlock()
m := face.Metrics()
fc := faceWithCacheFromFace(face)
m := fc.Metrics()
faceHeight := m.Height
fx, fy := fixed.I(0), fixed.I(0)
@ -313,7 +301,7 @@ func BoundString(face font.Face, text string) image.Rectangle {
var bounds fixed.Rectangle26_6
for _, r := range text {
if prevR >= 0 {
fx += face.Kern(prevR, r)
fx += fc.Kern(prevR, r)
}
if r == '\n' {
fx = fixed.I(0)
@ -322,14 +310,14 @@ func BoundString(face font.Face, text string) image.Rectangle {
continue
}
b := getGlyphBounds(face, r)
b, a, _ := fc.GlyphBounds(r)
b.Min.X += fx
b.Max.X += fx
b.Min.Y += fy
b.Max.Y += fy
bounds = bounds.Union(b)
fx += glyphAdvance(face, r)
fx += a
prevR = r
}
@ -369,17 +357,19 @@ func CacheGlyphs(face font.Face, text string) {
textM.Lock()
defer textM.Unlock()
fc := faceWithCacheFromFace(face)
var dx fixed.Int26_6
prevR := rune(-1)
for _, r := range text {
if prevR >= 0 {
dx += face.Kern(prevR, r)
dx += fc.Kern(prevR, r)
}
if r == '\n' {
dx = 0
continue
}
b := getGlyphBounds(face, r)
b, a, _ := fc.GlyphBounds(r)
// Cache all 4 variations for one rune (#2528).
for i := 0; i < 4; i++ {
@ -387,10 +377,10 @@ func CacheGlyphs(face font.Face, text string) {
X: (fixed.Int26_6(i*(1<<4)) + b.Min.X) & ((1 << 6) - 1),
Y: b.Min.Y & ((1 << 6) - 1),
}
getGlyphImage(face, r, offset)
getGlyphImage(fc, r, offset)
}
dx += glyphAdvance(face, r)
dx += a
prevR = r
}
}
@ -462,14 +452,16 @@ func AppendGlyphs(glyphs []Glyph, face font.Face, text string) []Glyph {
textM.Lock()
defer textM.Unlock()
fc := faceWithCacheFromFace(face)
var pos fixed.Point26_6
prevR := rune(-1)
faceHeight := face.Metrics().Height
faceHeight := fc.Metrics().Height
for _, r := range text {
if prevR >= 0 {
pos.X += face.Kern(prevR, r)
pos.X += fc.Kern(prevR, r)
}
if r == '\n' {
pos.X = 0
@ -478,12 +470,12 @@ func AppendGlyphs(glyphs []Glyph, face font.Face, text string) []Glyph {
continue
}
b := getGlyphBounds(face, r)
b, a, _ := fc.GlyphBounds(r)
offset := fixed.Point26_6{
X: (adjustOffsetGranularity(pos.X) + b.Min.X) & ((1 << 6) - 1),
Y: b.Min.Y & ((1 << 6) - 1),
}
if img := getGlyphImage(face, r, offset); img != nil {
if img := getGlyphImage(fc, r, offset); img != nil {
// Adjust the position to the integers.
// The current glyph images assume that they are rendered on integer positions so far.
glyphs = append(glyphs, Glyph{
@ -493,7 +485,7 @@ func AppendGlyphs(glyphs []Glyph, face font.Face, text string) []Glyph {
Y: fixed26_6ToFloat64(pos.Y + b.Min.Y - offset.Y),
})
}
pos.X += glyphAdvance(face, r)
pos.X += a
prevR = r
}