-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathfindfonts.jl
173 lines (143 loc) · 5.74 KB
/
findfonts.jl
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
if Sys.isapple()
function _font_paths()
return [
"/Library/Fonts", # Additional fonts that can be used by all users. This is generally where fonts go if they are to be used by other applications.
joinpath(homedir(), "Library/Fonts"), # Fonts specific to each user.
"/Network/Library/Fonts", # Fonts shared for users on a network
"/System/Library/Fonts", # System specific fonts
"/System/Library/Fonts/Supplemental", # new location since Catalina
]
end
elseif Sys.iswindows()
function _font_paths()
return [
joinpath(get(ENV, "SYSTEMROOT", "C:\\Windows"), "Fonts"),
joinpath(homedir(), "AppData", "Local", "Microsoft", "Windows", "Fonts"),
]
end
else
function add_recursive(result, path)
for p in readdir(path)
pabs = joinpath(path, p)
if isdir(pabs)
push!(result, pabs)
add_recursive(result, pabs)
end
end
end
function _font_paths()
result = String[]
for p in (
"/usr/share/fonts",
joinpath(homedir(), ".fonts"),
joinpath(homedir(), ".local/share/fonts"),
"/usr/local/share/fonts"
)
if isdir(p)
push!(result, p)
add_recursive(result, p)
end
end
return result
end
end
family_name(x::FTFont) = lowercase(x.family_name)
style_name(x::FTFont) = lowercase(x.style_name)
const REGULAR_STYLES = ("regular", "normal", "medium", "standard", "roman", "book")
"""
Match a font using the user-specified search string. Each part of the search string
is searched in the family name first which has to match once to include the font
in the candidate list. For fonts with a family match the style
name is matched next. For fonts with the same family and style name scores, regular
fonts are preferred (any font that is "regular", "normal", "medium", "standard" or "roman")
and as a last tie-breaker, shorter overall font names are preferred.
Example:
If we had only four fonts:
- Helvetica Italic
- Helvetica Regular
- Helvetica Neue Regular
- Helvetica Neue Light
Then this is how this function would match different search strings:
- "helvetica" => Helvetica Regular
- "helv" => Helvetica Regular
- "HeLvEtIcA" => Helvetica Regular
- "helvetica italic" => Helvetica Italic
- "helve ita" => Helvetica Italic
- "helvetica neue" => Helvetica Neue Regular
- "tica eue" => Helvetica Neue Regular
- "helvetica light" => Helvetica Neue Light
- "light" => Helvetica Neue Light
- "helvetica bold" => Helvetica Regular
- "helvetica neue bold" => Helvetica Neue Regular
- "times" => no match
- "arial" => no match
"""
function match_font(face::FTFont, searchparts)::Tuple{Int, Int, Bool, Int}
fname = family_name(face)
sname = style_name(face)
is_regular_style = any(occursin(s, sname) for s in REGULAR_STYLES)
fontlength_penalty = -(length(fname) + length(sname))
family_matches = any(occursin(part, fname) for part in searchparts)
# return early if family name doesn't have a match
family_matches || return (0, 0, is_regular_style, fontlength_penalty)
family_score = sum(length(part) for part in searchparts if occursin(part, fname))
# now enhance the score with style information
remaining_parts = filter(part -> !occursin(part, fname), searchparts)
if isempty(remaining_parts)
return (family_score, 0, is_regular_style, fontlength_penalty)
end
# check if any parts match the style name, otherwise return early
if !any(occursin(part, sname) for part in remaining_parts)
return (family_score, 0, is_regular_style, fontlength_penalty)
end
style_score = sum(length(part) for part in remaining_parts if occursin(part, sname))
return (family_score, style_score, is_regular_style, fontlength_penalty)
end
function try_load(fpath)
try
return FTFont(fpath)
catch e
return nothing
end
end
fontname(ft::FTFont) = "$(family_name(ft)) $(style_name(ft))"
function findfont(
searchstring::String;
italic::Bool=false, # this is unused in the new implementation
bold::Bool=false, # and this as well
additional_fonts::String=""
)
font_folders = copy(fontpaths())
isempty(additional_fonts) || pushfirst!(font_folders, additional_fonts)
# \W splits at all groups of non-word characters (like space, -, ., etc)
searchparts = unique(split(lowercase(searchstring), r"\W+", keepempty=false))
best_score_so_far = (0, 0, false, typemin(Int))
best_font = nothing
for folder in font_folders
for font in readdir(folder)
fpath = joinpath(folder, font)
face = try_load(fpath)
face === nothing && continue
score = match_font(face, searchparts)
# we can compare all four tuple elements of the score at once
# in order of importance:
# 1. number of family match characters
# 2. number of style match characters
# 3. is font a "regular" style variant?
# 4. the negative length of the font name, the shorter the better
family_match_score = score[1]
if family_match_score > 0 && score > best_score_so_far
# finalize previous best font to close the font file
if !isnothing(best_font)
finalize(best_font)
end
# new candidate
best_font = face
best_score_so_far = score
else
finalize(face)
end
end
end
return best_font
end