package fontface
import (
"bytes"
"crypto/sha256"
"fmt"
"sync"
gotextfont "github.com/go-text/typesetting/font"
)
const maxBundledFaceSources = 16
type bundledFaceEntry struct {
size int
digest [sha256 .Size ]byte
mu sync .RWMutex
data []byte
sources []BundledFaceSource
err error
}
func (e *bundledFaceEntry ) load (candidate []byte ) error {
e .mu .RLock ()
loaded := e .sources != nil || e .err != nil
err := e .err
e .mu .RUnlock ()
if loaded {
return err
}
owned := bytes .Clone (candidate )
if len (owned ) != e .size || sha256 .Sum256 (owned ) != e .digest {
return fmt .Errorf ("bundled font resource is not authenticated" )
}
e .mu .Lock ()
defer e .mu .Unlock ()
if e .sources != nil || e .err != nil {
return e .err
}
collection , err := parseFaceCollectionWithLimitUsingParserAndDigest (owned , maxParsedFontFaces , gotextfont .ParseTTC , e .digest )
if err != nil {
e .err = err
return err
}
sources := make ([]BundledFaceSource , collection .NumFaces ())
for index := range sources {
face , err := collection .Face (index )
if err != nil {
e .err = err
return err
}
face .source .bundledOutline = true
sources [index ].face = face
}
e .data = owned
e .sources = sources
return nil
}
var bundledFaceRegistry struct {
sync .RWMutex
entries []*bundledFaceEntry
byDigest map [[sha256 .Size ]byte ]*bundledFaceEntry
bySize map [int ][]*bundledFaceEntry
}
func RegisterBundledFace (data []byte , digest [sha256 .Size ]byte ) error {
if len (data ) == 0 || digest == ([sha256 .Size ]byte {}) {
return fmt .Errorf ("invalid bundled font registration" )
}
bundledFaceRegistry .Lock ()
defer bundledFaceRegistry .Unlock ()
if bundledFaceRegistry .byDigest == nil {
bundledFaceRegistry .byDigest = make (map [[sha256 .Size ]byte ]*bundledFaceEntry )
bundledFaceRegistry .bySize = make (map [int ][]*bundledFaceEntry )
}
if existing := bundledFaceRegistry .byDigest [digest ]; existing != nil {
if len (data ) == existing .size {
return nil
}
return fmt .Errorf ("duplicate bundled font digest has size %d, want %d" , len (data ), existing .size )
}
if len (bundledFaceRegistry .entries ) >= maxBundledFaceSources {
return fmt .Errorf ("bundled font registry exceeds limit %d" , maxBundledFaceSources )
}
entry := &bundledFaceEntry {size : len (data ), digest : digest }
bundledFaceRegistry .entries = append (bundledFaceRegistry .entries , entry )
bundledFaceRegistry .byDigest [digest ] = entry
bundledFaceRegistry .bySize [len (data )] = append (bundledFaceRegistry .bySize [len (data )], entry )
return nil
}
func RegisteredBundledFace (data []byte , faceIndex uint16 ) (*BundledFaceSource , bool , error ) {
entry := registeredBundledFaceEntry (data , nil )
return registeredBundledFaceSource (entry , data , faceIndex )
}
func RegisteredBundledFaceDigest (data []byte , faceIndex uint16 , digest [sha256 .Size ]byte ) (*BundledFaceSource , bool , error ) {
entry := registeredBundledFaceEntry (data , &digest )
return registeredBundledFaceSource (entry , data , faceIndex )
}
func RegisteredBundledFaceBackingDigest (data []byte ) ([sha256 .Size ]byte , bool ) {
if len (data ) == 0 {
return [sha256 .Size ]byte {}, false
}
bundledFaceRegistry .RLock ()
defer bundledFaceRegistry .RUnlock ()
for _ , candidate := range bundledFaceRegistry .bySize [len (data )] {
candidate .mu .RLock ()
matches := sameSlice (data , candidate .data )
candidate .mu .RUnlock ()
if matches {
return candidate .digest , true
}
}
return [sha256 .Size ]byte {}, false
}
func registeredBundledFaceEntry(data []byte , knownDigest *[sha256 .Size ]byte ) *bundledFaceEntry {
if len (data ) == 0 {
return nil
}
var digest [sha256 .Size ]byte
if knownDigest == nil {
bundledFaceRegistry .RLock ()
for _ , candidate := range bundledFaceRegistry .bySize [len (data )] {
candidate .mu .RLock ()
loadedData := candidate .data
candidate .mu .RUnlock ()
if loadedData != nil && (sameSlice (data , loadedData ) || bytes .Equal (data , loadedData )) {
bundledFaceRegistry .RUnlock ()
return candidate
}
}
bundledFaceRegistry .RUnlock ()
digest = sha256 .Sum256 (data )
} else {
digest = *knownDigest
}
bundledFaceRegistry .RLock ()
entry := bundledFaceRegistry .byDigest [digest ]
if entry == nil || len (data ) != entry .size {
entry = nil
}
bundledFaceRegistry .RUnlock ()
if entry != nil && knownDigest == nil {
entry .mu .RLock ()
loadedData := entry .data
entry .mu .RUnlock ()
if loadedData != nil && !sameSlice (data , loadedData ) && !bytes .Equal (data , loadedData ) {
entry = nil
}
}
return entry
}
func registeredBundledFaceSource(entry *bundledFaceEntry , data []byte , faceIndex uint16 ) (*BundledFaceSource , bool , error ) {
if entry == nil {
return nil , false , nil
}
if err := entry .load (data ); err != nil {
return nil , true , err
}
entry .mu .RLock ()
defer entry .mu .RUnlock ()
if int (faceIndex ) >= len (entry .sources ) {
return nil , true , fmt .Errorf ("load font face %d: collection has %d faces" , faceIndex , len (entry .sources ))
}
return &entry .sources [faceIndex ], true , nil
}
func sameSlice(left , right []byte ) bool {
return len (left ) == len (right ) && (len (left ) == 0 || &left [0 ] == &right [0 ])
}
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 .