github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/font_properties.go (about) 1 package etxt 2 3 import "golang.org/x/image/font/sfnt" 4 import "sync/atomic" 5 import "errors" 6 7 var ErrNotFound = errors.New("font property not found or empty") 8 9 // We allocate one sfnt.Buffer so it can be used in FontProperty() calls. 10 // These buffers can't be used concurrently though, so sfntBuffer will only 11 // be used if no one else is using it at the moment. We don't bother creating 12 // a pool because that would be waaay overkill, and this simple trick already 13 // makes calls to FontProperty() fast in 99% use-cases for a reasonably small 14 // price. 15 var sfntBuffer sfnt.Buffer 16 var usingSfntBuffer uint32 = 0 17 18 func getSfntBuffer() *sfnt.Buffer { 19 if !atomic.CompareAndSwapUint32(&usingSfntBuffer, 0, 1) { 20 return nil 21 } 22 return &sfntBuffer 23 } 24 func releaseSfntBuffer(buffer *sfnt.Buffer) { 25 if buffer != nil { 26 atomic.StoreUint32(&usingSfntBuffer, 0) 27 } 28 } 29 30 // Returns the requested font property for the given font. 31 // The returned property string might be empty even when error is nil. 32 func FontProperty(font *Font, property sfnt.NameID) (string, error) { 33 buffer := getSfntBuffer() 34 str, err := font.Name(buffer, property) 35 releaseSfntBuffer(buffer) 36 if err != nil { 37 return "", err 38 } 39 return str, nil 40 } 41 42 // Returns the family name of the given font. If the information is 43 // missing, [ErrNotFound] will be returned. Other errors are also 44 // possible (e.g., if the font naming table is invalid). 45 func FontFamily(font *Font) (string, error) { 46 value, err := FontProperty(font, sfnt.NameIDFamily) 47 if err == sfnt.ErrNotFound || value == "" { 48 err = ErrNotFound 49 } 50 return value, err 51 } 52 53 // Returns the subfamily name of the given font. If the information 54 // is missing, [ErrNotFound] will be returned. Other errors are also 55 // possible (e.g., if the font naming table is invalid). 56 // 57 // In most cases, the subfamily value will be one of: 58 // - Regular, Italic, Bold, Bold Italic 59 func FontSubfamily(font *Font) (string, error) { 60 value, err := FontProperty(font, sfnt.NameIDSubfamily) 61 if err == sfnt.ErrNotFound || value == "" { 62 err = ErrNotFound 63 } 64 return value, err 65 } 66 67 // Returns the name of the given font. If the information is missing, 68 // [ErrNotFound] will be returned. Other errors are also possible (e.g., 69 // if the font naming table is invalid). 70 func FontName(font *Font) (string, error) { 71 value, err := FontProperty(font, sfnt.NameIDFull) 72 if err == sfnt.ErrNotFound || value == "" { 73 err = ErrNotFound 74 } 75 return value, err 76 } 77 78 // Returns the identifier of the given font. If the information is missing, 79 // [ErrNotFound] will be returned. Other errors are also possible (e.g., 80 // if the font naming table is invalid). 81 func FontIdentifier(font *Font) (string, error) { 82 value, err := FontProperty(font, sfnt.NameIDUniqueIdentifier) 83 if err == sfnt.ErrNotFound || value == "" { 84 err = ErrNotFound 85 } 86 return value, err 87 } 88 89 // Returns the runes in the given text that can't be represented by the 90 // font. If runes are repeated in the input text, the returned slice may 91 // contain them multiple times too. 92 // 93 // If you load fonts dynamically, it is good practice to use this function 94 // to make sure that the fonts include all the glyphs that you require. 95 func GetMissingRunes(font *Font, text string) ([]rune, error) { 96 buffer := getSfntBuffer() 97 defer releaseSfntBuffer(buffer) 98 99 missing := make([]rune, 0) 100 for _, codePoint := range text { 101 index, err := font.GlyphIndex(buffer, codePoint) 102 if err != nil { 103 return missing, err 104 } 105 if index == 0 { 106 missing = append(missing, codePoint) 107 } 108 } 109 return missing, nil 110 }