@@ -39,15 +39,38 @@ function style_name(x::FTFont)
39
39
lowercase (x. style_name)
40
40
end
41
41
42
- function match_font (face:: FTFont , name, italic, bold)
42
+ """
43
+ Match a font using the user-specified search string, by increasing the score
44
+ for each part that appears in the font family + style name, and decreasing it
45
+ for each part that doesn't. The function also prefers shorter font names when
46
+ encountering similar scores.
47
+ """
48
+ function match_font (face:: FTFont , searchstring)
43
49
fname = family_name (face)
44
50
sname = style_name (face)
45
- italic = italic == (sname == " italic" )
46
- bold = bold == (sname == " bold" )
47
- perfect_match = (fname == name) && italic && bold
48
- fuzzy_match = occursin (name, fname)
49
- score = fuzzy_match + bold + italic
50
- return perfect_match, fuzzy_match, score
51
+ full_name = " $fname $sname "
52
+ # \W splits at all groups of non-word characters (like space, -, ., etc)
53
+ searchparts = unique (split (lowercase (searchstring), r" \W +" , keepempty= false ))
54
+ # count letters of parts that occurred in the font name positively and those that didn't negatively.
55
+ # we assume that the user knows at least parts of the name and doesn't misspell them
56
+ # but they might not know the exact name, especially for long font names, or they
57
+ # might simply not want to be forced to spell it out completely.
58
+ # therefore we let each part we can find count towards a font, and each that
59
+ # doesn't match against it, therefore rejecting fonts that mismatch more parts
60
+ # than they match. this heuristic should be good enough to provide a hassle-free
61
+ # font selection experience where most spellings that are expected to work, work.
62
+ match_score = sum (map (part -> sign (occursin (part, full_name)) * length (part), searchparts))
63
+ # give shorter font names that matched equally well a higher score after the decimal point.
64
+ # this should usually pick the "standard" variant of a font as long as it
65
+ # doesn't have a special identifier like "regular", "roman", "book", etc.
66
+ # to be fair, with these fonts the old fontconfig method also often fails because
67
+ # it's not clearly defined what the most normal version is for the user.
68
+ # it's therefore better to just have them specify these parts of the name that
69
+ # they think are important. this is especially important for attributes that
70
+ # fall outside of the standard italic / bold distinction like "condensed",
71
+ # "semibold", "oblique", etc.
72
+ final_score = match_score + (1.0 / length (full_name))
73
+ return final_score
51
74
end
52
75
53
76
function try_load (fpath)
60
83
61
84
function findfont (
62
85
name:: String ;
63
- italic:: Bool = false , bold:: Bool = false , additional_fonts:: String = " "
86
+ italic:: Bool = false , # this is unused in the new implementation
87
+ bold:: Bool = false , # and this as well
88
+ additional_fonts:: String = " "
64
89
)
65
90
font_folders = copy (fontpaths ())
66
- normalized_name = family_name (name)
91
+ # normalized_name = family_name(name)
67
92
isempty (additional_fonts) || pushfirst! (font_folders, additional_fonts)
68
- candidates = Pair{FTFont, Int}[]
93
+
94
+ candidates = Pair{FTFont, Float64}[]
69
95
for folder in font_folders
70
96
for font in readdir (folder)
71
97
fpath = joinpath (folder, font)
72
98
face = try_load (fpath)
73
99
face === nothing && continue
74
- perfect_match, fuzzy_match, score = match_font (
75
- face, normalized_name, italic, bold
76
- )
77
- perfect_match && return face
78
- if fuzzy_match
100
+ score = match_font (face, name)
101
+
102
+ # only take results with net positive character matches into account
103
+ if floor (score) > 0
79
104
push! (candidates, face => score)
80
105
else
81
106
finalize (face) # help gc a bit!
0 commit comments