-
-
Notifications
You must be signed in to change notification settings - Fork 12
/
finder.go
127 lines (105 loc) · 3.11 KB
/
finder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package sysfont
import (
"os"
"path/filepath"
"strings"
"github.com/adrg/strutil"
"github.com/adrg/xdg"
)
// Finder is used to identify installed fonts. It can match fonts based on user
// queries and suggest alternative fonts if the requested fonts are not found.
type Finder struct {
fonts []*Font
}
// FinderOpts contains options for configuring a font finder.
type FinderOpts struct {
// Extensions controls which types of font files the finder reports.
Extensions []string
// SearchPaths is a list of paths to search for fonts.
SearchPaths []string
}
// NewFinder returns a new font finder. If the opts parameter is nil, default
// options are used.
//
// Default options:
// Extensions: []string{".ttf", ".ttc", ".otf"}
// SearchPaths: xdg.FontDirs
//
// NOTE: See https://github.com/adrg/xdg#other-directories for more information
// about the default search paths.
func NewFinder(opts *FinderOpts) *Finder {
if opts == nil {
opts = &FinderOpts{Extensions: []string{".ttf", ".ttc", ".otf"}}
}
if len(opts.SearchPaths) == 0 {
opts.SearchPaths = xdg.FontDirs
}
var fonts []*Font
walker := func(filename string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
// Check file extension.
if extensions := opts.Extensions; len(extensions) > 0 {
extension := filepath.Ext(strings.ToLower(filename))
if !strutil.SliceContains(extensions, extension) {
return nil
}
}
// Attempt to identify fonts by filename.
matches := fontRegistry.matchFontsByFilename(filename)
if len(matches) == 0 {
matches = append(matches, &Font{Filename: filename})
}
fonts = append(fonts, matches...)
return nil
}
// Traverse OS font directories.
for _, dir := range opts.SearchPaths {
if err := filepath.Walk(dir, walker); err != nil {
continue
}
}
return &Finder{
fonts: fonts,
}
}
// List returns the list of installed fonts. The finder attempts to identify
// the name and family of the returned fonts. If identification is not possible,
// only the filename field will be filled.
func (f *Finder) List() []*Font {
fonts := make([]*Font, 0, len(f.fonts))
for _, font := range f.fonts {
fonts = append(fonts, font.clone())
}
return fonts
}
// Match attempts to identify the best matching installed font based on the
// specified query. If no close match is found, alternative fonts are searched.
// If no alternative font is found, a suitable default font is returned.
func (f *Finder) Match(query string) *Font {
font := fontRegistry.matchFont(query, f.fonts)
if font == nil {
font = f.findAlternative(query)
}
return font.clone()
}
func (f *Finder) findAlternative(query string) *Font {
// Identify font family.
family, _ := fontRegistry.matchFamily(query)
// Identify alternate fonts based on the matched family.
alternatives := fontRegistry.getAlternatives(family, f.fonts)
// Identify best alternative.
var maxScore float64
var maxScoreFont *Font
for _, font := range alternatives {
if score := getFontStyleScore(query, font.Name); score > maxScore {
maxScore = score
maxScoreFont = font
}
}
return maxScoreFont
}