@@ -37,6 +37,8 @@ function style_name(x::FTFont)
37
37
lowercase (x. style_name)
38
38
end
39
39
40
+ const REGULAR_STYLES = (" regular" , " normal" , " medium" , " standard" , " roman" )
41
+
40
42
41
43
"""
42
44
Match a font using the user-specified search string. Each part of the search string
@@ -70,34 +72,37 @@ Then this is how this function would match different search strings:
70
72
- "times" => no match
71
73
- "arial" => no match
72
74
"""
73
- function match_font_by_family_and_style (face:: FTFont , searchparts):: Tuple{Int, Int}
75
+ function match_font (face:: FTFont , searchparts):: Tuple{Int, Int, Bool , Int}
74
76
75
77
fname = family_name (face)
78
+ sname = style_name (face)
79
+ is_regular_style = any (occursin (s, sname) for s in REGULAR_STYLES)
76
80
77
- family_matches = any ( occursin (part, fname) for part in searchparts )
81
+ fontlength_penalty = - ( length ( fname) + length (sname) )
78
82
79
- # return early if family name doesn't have a match
80
- family_matches || return (0 , 0 )
81
83
82
- # now enhance the score with style information
84
+ family_matches = any ( occursin (part, fname) for part in searchparts)
83
85
84
- remaining_parts = filter (part -> ! occursin (part, fname), searchparts)
86
+ # return early if family name doesn't have a match
87
+ family_matches || return (0 , 0 , is_regular_style, fontlength_penalty)
85
88
86
89
family_score = sum (length (part) for part in searchparts if occursin (part, fname))
87
90
88
- # return early if no parts remain for style scoring
89
- isempty (remaining_parts) && return (family_score, 0 )
90
-
91
- sname = style_name (face)
91
+ # now enhance the score with style information
92
+ remaining_parts = filter (part -> ! occursin (part, fname), searchparts)
92
93
93
- style_name_matches = any (occursin (part, sname) for part in remaining_parts)
94
+ if isempty (remaining_parts)
95
+ return (family_score, 0 , is_regular_style, fontlength_penalty)
96
+ end
94
97
95
- # return early if no remaining part matches the style name
96
- style_name_matches || return (family_score, 0 )
98
+ # check if any parts match the style name, otherwise return early
99
+ if ! any (occursin (part, sname) for part in remaining_parts)
100
+ return (family_score, 0 , is_regular_style, fontlength_penalty)
101
+ end
97
102
98
103
style_score = sum (length (part) for part in remaining_parts if occursin (part, sname))
99
104
100
- (family_score, style_score)
105
+ (family_score, style_score, is_regular_style, fontlength_penalty )
101
106
end
102
107
103
108
function try_load (fpath)
110
115
111
116
fontname (ft:: FTFont ) = " $(family_name (ft)) $(style_name (ft)) "
112
117
118
+
113
119
function findfont (
114
120
searchstring:: String ;
115
121
italic:: Bool = false , # this is unused in the new implementation
@@ -125,89 +131,40 @@ function findfont(
125
131
126
132
candidates = Pair{FTFont, Tuple{Int, Int}}[]
127
133
134
+ best_score_so_far = (0 , 0 , false , typemin (Int))
135
+ best_font = nothing
136
+
128
137
for folder in font_folders
129
138
for font in readdir (folder)
130
139
fpath = joinpath (folder, font)
131
140
face = try_load (fpath)
132
141
face === nothing && continue
133
- family_score, style_score = match_font_by_family_and_style (face, searchparts)
134
- # only take results with net positive character matches into account
135
- if family_score > 0
136
- push! (candidates, face => (family_score, style_score))
137
- else
138
- # help gc a bit! Otherwise, this won't end well with the font keeping tons of open files
139
- finalize (face)
140
- end
141
- end
142
- end
143
142
144
- isempty (candidates) && return nothing
143
+ score = match_font (face, searchparts)
145
144
146
- # sort by family score then style score, highest first
147
- sort! (candidates; by = x -> x[ 2 ], rev = true )
145
+ # we can compare all four tuple elements of the score at once
146
+ # in order of importance:
148
147
148
+ # 1. number of family match characters
149
+ # 2. number of style match characters
150
+ # 3. is font a "regular" style variant?
151
+ # 4. the negative length of the font name, the shorter the better
149
152
150
- # best score for comparison
151
- highscore = candidates[1 ][2 ] # tuple{int, int}
153
+ family_match_score = score[1 ]
154
+ if family_match_score > 0 && score > best_score_so_far
155
+ # finalize previous best font to close the font file
156
+ if ! isnothing (best_font)
157
+ finalize (best_font)
158
+ end
152
159
153
- # remove all candidates that have lesser scores than the first one
154
- for i in 2 : length (candidates)
155
- if candidates[i][2 ][1 ] < highscore[1 ] || candidates[i][2 ][2 ] < highscore[2 ]
156
- # remove fonts from back to front and finalize them to close
157
- # their files
158
- for j in length (candidates): - 1 : i
159
- to_remove = pop! (candidates)[1 ]
160
- finalize (to_remove)
161
- end
162
- # there will be no other i's we need to check after this
163
- break
164
- end
165
- end
166
-
167
- # return early if only one font remains
168
- length (candidates) == 1 && return candidates[1 ][1 ]
169
-
170
- # there is still more than one candidate
171
- # all candidates have the same family and style score
172
-
173
- # prefer regular fonts among the remaining options
174
- regular_styles = (" regular" , " normal" , " medium" , " standard" , " roman" )
175
- regular_matches = filter (candidates) do c
176
- any (occursin (style, style_name (c[1 ])) for style in regular_styles)
177
- end
178
-
179
- # if any fonts match a regular-type style name, choose the overall shortest
180
- if ! isempty (regular_matches)
181
-
182
- sort! (regular_matches,
183
- by = c -> length (fontname (c[1 ])))
184
-
185
- shortest = regular_matches[1 ][1 ]
186
-
187
- # close all non-used font files
188
- for c in candidates
189
- if c[1 ] != = shortest
190
- finalize (c)
160
+ # new candidate
161
+ best_font = face
162
+ best_score_so_far = score
163
+ else
164
+ finalize (face)
191
165
end
192
166
end
193
-
194
- return shortest
195
- end
196
-
197
- # we didn't find any font with a "regular" name
198
- # as a last heuristic, we just choose the font with the shortest name
199
-
200
- sort! (candidates,
201
- by = c -> length (fontname (c[1 ])))
202
-
203
- shortest = candidates[1 ][1 ]
204
-
205
- # close all non-used font files
206
- for c in candidates
207
- if c[1 ] != = shortest
208
- finalize (c)
209
- end
210
167
end
211
168
212
- shortest
169
+ best_font
213
170
end
0 commit comments