mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
5fcfc40ab8
Signed-off-by: Elias Naur <mail@eliasnaur.com>
184 lines
3.6 KiB
Go
184 lines
3.6 KiB
Go
// SPDX-License-Identifier: Unlicense OR MIT
|
|
|
|
package text
|
|
|
|
import (
|
|
"image"
|
|
"sync/atomic"
|
|
|
|
giofont "gioui.org/font"
|
|
"gioui.org/io/system"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
"gioui.org/op/paint"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// entry holds a single key-value pair for an LRU cache.
|
|
type entry[K comparable, V any] struct {
|
|
next, prev *entry[K, V]
|
|
key K
|
|
v V
|
|
}
|
|
|
|
// lru is a generic least-recently-used cache.
|
|
type lru[K comparable, V any] struct {
|
|
m map[K]*entry[K, V]
|
|
head, tail *entry[K, V]
|
|
}
|
|
|
|
// Get fetches the value associated with the given key, if any.
|
|
func (l *lru[K, V]) Get(k K) (V, bool) {
|
|
if lt, ok := l.m[k]; ok {
|
|
l.remove(lt)
|
|
l.insert(lt)
|
|
return lt.v, true
|
|
}
|
|
var v V
|
|
return v, false
|
|
}
|
|
|
|
// Put inserts the given value with the given key, evicting old
|
|
// cache entries if necessary.
|
|
func (l *lru[K, V]) Put(k K, v V) {
|
|
if l.m == nil {
|
|
l.m = make(map[K]*entry[K, V])
|
|
l.head = new(entry[K, V])
|
|
l.tail = new(entry[K, V])
|
|
l.head.prev = l.tail
|
|
l.tail.next = l.head
|
|
}
|
|
val := &entry[K, V]{key: k, v: v}
|
|
l.m[k] = val
|
|
l.insert(val)
|
|
if len(l.m) > maxSize {
|
|
oldest := l.tail.next
|
|
l.remove(oldest)
|
|
delete(l.m, oldest.key)
|
|
}
|
|
}
|
|
|
|
// remove cuts e out of the lru linked list.
|
|
func (l *lru[K, V]) remove(e *entry[K, V]) {
|
|
e.next.prev = e.prev
|
|
e.prev.next = e.next
|
|
}
|
|
|
|
// insert adds e to the lru linked list.
|
|
func (l *lru[K, V]) insert(e *entry[K, V]) {
|
|
e.next = l.head
|
|
e.prev = l.head.prev
|
|
e.prev.next = e
|
|
e.next.prev = e
|
|
}
|
|
|
|
type bitmapCache = lru[GlyphID, bitmap]
|
|
|
|
type bitmap struct {
|
|
img paint.ImageOp
|
|
size image.Point
|
|
}
|
|
|
|
type layoutCache = lru[layoutKey, document]
|
|
|
|
type glyphValue[V any] struct {
|
|
v V
|
|
glyphs []glyphInfo
|
|
}
|
|
|
|
type glyphLRU[V any] struct {
|
|
seed uint64
|
|
cache lru[uint64, glyphValue[V]]
|
|
}
|
|
|
|
var seed uint32
|
|
|
|
// hashGlyphs computes a hash key based on the ID and X offset of
|
|
// every glyph in the slice.
|
|
func (c *glyphLRU[V]) hashGlyphs(gs []Glyph) uint64 {
|
|
if c.seed == 0 {
|
|
c.seed = uint64(atomic.AddUint32(&seed, 3900798947))
|
|
}
|
|
if len(gs) == 0 {
|
|
return 0
|
|
}
|
|
|
|
h := c.seed
|
|
firstX := gs[0].X
|
|
for _, g := range gs {
|
|
h += uint64(g.X - firstX)
|
|
h *= 6585573582091643
|
|
h += uint64(g.ID)
|
|
h *= 3650802748644053
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
func (c *glyphLRU[V]) Get(key uint64, gs []Glyph) (V, bool) {
|
|
if v, ok := c.cache.Get(key); ok && gidsEqual(v.glyphs, gs) {
|
|
return v.v, true
|
|
}
|
|
var v V
|
|
return v, false
|
|
}
|
|
|
|
func (c *glyphLRU[V]) Put(key uint64, glyphs []Glyph, v V) {
|
|
gids := make([]glyphInfo, len(glyphs))
|
|
firstX := fixed.I(0)
|
|
for i, glyph := range glyphs {
|
|
if i == 0 {
|
|
firstX = glyph.X
|
|
}
|
|
// Cache glyph X offsets relative to the first glyph.
|
|
gids[i] = glyphInfo{ID: glyph.ID, X: glyph.X - firstX}
|
|
}
|
|
val := glyphValue[V]{
|
|
glyphs: gids,
|
|
v: v,
|
|
}
|
|
c.cache.Put(key, val)
|
|
}
|
|
|
|
type pathCache = glyphLRU[clip.PathSpec]
|
|
|
|
type bitmapShapeCache = glyphLRU[op.CallOp]
|
|
|
|
type glyphInfo struct {
|
|
ID GlyphID
|
|
X fixed.Int26_6
|
|
}
|
|
|
|
type layoutKey struct {
|
|
ppem fixed.Int26_6
|
|
maxWidth, minWidth int
|
|
maxLines int
|
|
str string
|
|
truncator string
|
|
locale system.Locale
|
|
font giofont.Font
|
|
forceTruncate bool
|
|
wrapPolicy WrapPolicy
|
|
lineHeight fixed.Int26_6
|
|
lineHeightScale float32
|
|
}
|
|
|
|
const maxSize = 1000
|
|
|
|
func gidsEqual(a []glyphInfo, glyphs []Glyph) bool {
|
|
if len(a) != len(glyphs) {
|
|
return false
|
|
}
|
|
firstX := fixed.Int26_6(0)
|
|
for i := range a {
|
|
if i == 0 {
|
|
firstX = glyphs[i].X
|
|
}
|
|
// Cache glyph X offsets relative to the first glyph.
|
|
if a[i].ID != glyphs[i].ID || a[i].X != (glyphs[i].X-firstX) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|