Skip to content

Commit b20bc6b

Browse files
committed
change match_fonts so it heuristically chooses the best font by full name
1 parent 3704b48 commit b20bc6b

File tree

1 file changed

+40
-15
lines changed

1 file changed

+40
-15
lines changed

src/findfonts.jl

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,38 @@ function style_name(x::FTFont)
3939
lowercase(x.style_name)
4040
end
4141

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)
4349
fname = family_name(face)
4450
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
5174
end
5275

5376
function try_load(fpath)
@@ -60,22 +83,24 @@ end
6083

6184
function findfont(
6285
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=""
6489
)
6590
font_folders = copy(fontpaths())
66-
normalized_name = family_name(name)
91+
# normalized_name = family_name(name)
6792
isempty(additional_fonts) || pushfirst!(font_folders, additional_fonts)
68-
candidates = Pair{FTFont, Int}[]
93+
94+
candidates = Pair{FTFont, Float64}[]
6995
for folder in font_folders
7096
for font in readdir(folder)
7197
fpath = joinpath(folder, font)
7298
face = try_load(fpath)
7399
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
79104
push!(candidates, face => score)
80105
else
81106
finalize(face) # help gc a bit!

0 commit comments

Comments
 (0)