mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
text: refactoring: introduce faceWithCache
This commit is contained in:
parent
8de08295e9
commit
e374ca0ac3
@ -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
114
text/cache.go
Normal 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()
|
||||
}
|
72
text/text.go
72
text/text.go
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user