package fontfallback
import (
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"hash"
"sync"
"github.com/pancsta/d2/d2renderers/d2scene"
"github.com/pancsta/d2/d2renderers/internal/fontface"
)
type Font struct {
Name string
MIMEType string
Data []byte
FaceIndex uint16
}
type Request struct {
Runes []rune
Family string
Style string
Weight int
}
type SceneFont struct {
Font
ID d2scene .AssetID
Face *fontface .ParsedFace
Shared bool
}
type CacheStats struct {
Assets int
Hashes uint64
Copies uint64
CopiedBytes int64
}
type SceneResolveFunc func (context .Context , Request ) ([]SceneFont , error )
type SceneCache struct {
resolve SceneResolveFunc
mu sync .Mutex
trusted *SceneFont
stats CacheStats
}
func NewSceneCache (resolve SceneResolveFunc ) *SceneCache {
return &SceneCache {resolve : resolve }
}
func (c *SceneCache ) sceneFallbackCache () *SceneCache {
return c
}
func (c *SceneCache ) HasTrusted () bool {
if c == nil {
return false
}
c .mu .Lock ()
defer c .mu .Unlock ()
return c .trusted != nil
}
type sceneCacheProvider interface {
sceneFallbackCache() *SceneCache
}
func ResolveForScene (ctx context .Context , resolver any , request Request ) ([]SceneFont , bool , error ) {
provider , ok := resolver .(sceneCacheProvider )
if !ok {
return nil , false , nil
}
cache := provider .sceneFallbackCache ()
if cache == nil || cache .resolve == nil {
return nil , false , nil
}
fonts , err := cache .resolve (ctx , request )
return fonts , true , err
}
func CacheStatsFor (resolver any ) (CacheStats , bool ) {
provider , ok := resolver .(sceneCacheProvider )
if !ok {
return CacheStats {}, false
}
cache := provider .sceneFallbackCache ()
if cache == nil {
return CacheStats {}, false
}
cache .mu .Lock ()
defer cache .mu .Unlock ()
return cache .stats , true
}
func (c *SceneCache ) PrepareTrusted (ctx context .Context , source Font ) (SceneFont , bool , error ) {
if c == nil {
return SceneFont {}, false , fmt .Errorf ("font fallback: nil scene cache" )
}
if ctx == nil {
ctx = context .Background ()
}
if err := ctx .Err (); err != nil {
return SceneFont {}, false , err
}
c .mu .Lock ()
defer c .mu .Unlock ()
if c .trusted != nil {
return *c .trusted , false , nil
}
owned := make ([]byte , len (source .Data ))
id , err := hashFont (ctx , source , owned )
if err != nil {
return SceneFont {}, false , err
}
trusted := SceneFont {
Font : Font {
Name : source .Name , MIMEType : source .MIMEType , Data : owned , FaceIndex : source .FaceIndex ,
},
ID : id ,
Shared : true ,
}
c .trusted = &trusted
c .stats .Assets = 1
c .stats .Hashes ++
c .stats .Copies ++
c .stats .CopiedBytes += int64 (len (owned ))
return trusted , true , nil
}
func AssetID (ctx context .Context , font Font ) (d2scene .AssetID , error ) {
return hashFont (ctx , font , nil )
}
func hashFont(ctx context .Context , font Font , destination []byte ) (d2scene .AssetID , error ) {
if ctx == nil {
ctx = context .Background ()
}
if destination != nil && len (destination ) != len (font .Data ) {
return "" , fmt .Errorf ("font fallback: copy destination length %d differs from source %d" , len (destination ), len (font .Data ))
}
hasher := sha256 .New ()
_, _ = hasher .Write ([]byte (font .MIMEType ))
_, _ = hasher .Write ([]byte {0 })
var face [2 ]byte
binary .BigEndian .PutUint16 (face [:], font .FaceIndex )
_, _ = hasher .Write (face [:])
if err := copyAndHash (ctx , destination , font .Data , hasher ); err != nil {
return "" , err
}
return d2scene .AssetID (fmt .Sprintf ("font:fallback:%x" , hasher .Sum (nil ))), nil
}
func copyAndHash(ctx context .Context , destination , source []byte , hasher hash .Hash ) error {
const chunkBytes = 32 * 1024
for offset := 0 ; offset < len (source ); offset += chunkBytes {
if err := ctx .Err (); err != nil {
return err
}
end := min (offset +chunkBytes , len (source ))
chunk := source [offset :end ]
if destination != nil {
copy (destination [offset :end ], chunk )
chunk = destination [offset :end ]
}
_, _ = hasher .Write (chunk )
}
return ctx .Err ()
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .