fontist 1.21.4 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +4 -0
  3. data/Gemfile +11 -0
  4. data/README.adoc +77 -11
  5. data/docs/guide/api-ruby.md +10 -11
  6. data/fontist.gemspec +14 -24
  7. data/formula_filename_index.yml +6210 -0
  8. data/formula_index.yml +2568 -0
  9. data/lib/fontist/cli.rb +16 -14
  10. data/lib/fontist/config.rb +75 -14
  11. data/lib/fontist/errors.rb +2 -0
  12. data/lib/fontist/extract.rb +25 -0
  13. data/lib/fontist/font.rb +6 -8
  14. data/lib/fontist/font_collection.rb +16 -0
  15. data/lib/fontist/font_installer.rb +4 -4
  16. data/lib/fontist/font_model.rb +15 -0
  17. data/lib/fontist/font_path.rb +1 -1
  18. data/lib/fontist/font_style.rb +37 -0
  19. data/lib/fontist/formula.rb +169 -112
  20. data/lib/fontist/helpers.rb +5 -4
  21. data/lib/fontist/import/create_formula.rb +2 -1
  22. data/lib/fontist/import/files/collection_file.rb +5 -15
  23. data/lib/fontist/import/font_style.rb +22 -0
  24. data/lib/fontist/import/formula_serializer.rb +4 -4
  25. data/lib/fontist/import/google_import.rb +1 -1
  26. data/lib/fontist/import/otf/font_file.rb +8 -3
  27. data/lib/fontist/import/otf_style.rb +3 -1
  28. data/lib/fontist/import/recursive_extraction.rb +9 -4
  29. data/lib/fontist/index.rb +47 -8
  30. data/lib/fontist/indexes/default_family_font_index.rb +28 -9
  31. data/lib/fontist/indexes/filename_index.rb +45 -8
  32. data/lib/fontist/indexes/font_index.rb +3 -3
  33. data/lib/fontist/indexes/formula_key_to_path.rb +35 -0
  34. data/lib/fontist/indexes/index_mixin.rb +109 -0
  35. data/lib/fontist/indexes/preferred_family_font_index.rb +28 -9
  36. data/lib/fontist/manifest.rb +144 -2
  37. data/lib/fontist/manifest_request.rb +64 -0
  38. data/lib/fontist/manifest_response.rb +66 -0
  39. data/lib/fontist/repo.rb +1 -1
  40. data/lib/fontist/resources/archive_resource.rb +3 -2
  41. data/lib/fontist/system_font.rb +7 -25
  42. data/lib/fontist/system_index.rb +137 -126
  43. data/lib/fontist/utils/cache.rb +54 -4
  44. data/lib/fontist/utils/downloader.rb +6 -4
  45. data/lib/fontist/version.rb +1 -1
  46. data/lib/fontist.rb +33 -13
  47. metadata +23 -148
  48. data/lib/fontist/indexes/base_index.rb +0 -92
  49. data/lib/fontist/indexes/index_formula.rb +0 -36
  50. data/lib/fontist/manifest/install.rb +0 -35
  51. data/lib/fontist/manifest/locations.rb +0 -84
@@ -1,23 +1,104 @@
1
- require "fontist/index"
2
- require "fontist/helpers"
3
- require "fontist/update"
1
+ require "lutaml/model"
2
+ require_relative "font_model"
3
+ require_relative "font_collection"
4
+ require_relative "extract"
5
+ require_relative "index"
6
+ require_relative "helpers"
7
+ require_relative "update"
4
8
  require "git"
5
9
 
6
10
  module Fontist
7
- class Formula
11
+ require "lutaml/model"
12
+
13
+ class Resource < Lutaml::Model::Serializable
14
+ attribute :name, :string
15
+ attribute :source, :string
16
+ attribute :urls, :string, collection: true
17
+ attribute :sha256, :string, collection: true
18
+ attribute :file_size, :integer
19
+ attribute :family, :string
20
+ attribute :files, :string, collection: true
21
+
22
+ def empty?
23
+ Array(urls).empty? && Array(files).empty?
24
+ end
25
+ end
26
+
27
+ class ResourceCollection < Lutaml::Model::Collection
28
+ instances :resources, Resource
29
+
30
+ key_value do
31
+ root "resources"
32
+ map to: :resources
33
+ map_key to_instance: :name
34
+ end
35
+
36
+ def empty?
37
+ resources.nil? || Array(resources).all?(&:empty?)
38
+ end
39
+ end
40
+
41
+ class Formula < Lutaml::Model::Serializable
8
42
  NAMESPACES = {
9
43
  "sil" => "SIL",
10
44
  "macos" => "macOS",
11
45
  }.freeze
12
46
 
47
+ attribute :name, :string
48
+ attribute :path, :string
49
+ attribute :description, :string
50
+ attribute :homepage, :string
51
+ attribute :display_progress_bar, :boolean
52
+ attribute :repository, :string
53
+ attribute :copyright, :string
54
+ attribute :license_url, :string
55
+ attribute :open_license, :string
56
+ attribute :requires_license_agreement, :string
57
+ attribute :platforms, :string, collection: true
58
+ attribute :min_fontist, :string
59
+ attribute :digest, :string
60
+ attribute :instructions, :string
61
+ attribute :resources, ResourceCollection, collection: true
62
+ attribute :font_collections, FontCollection, collection: true
63
+ attribute :fonts, FontModel, collection: true, default: []
64
+ attribute :extract, Extract, collection: true
65
+ attribute :command, :string
66
+
67
+ key_value do
68
+ map "name", to: :name
69
+ map "description", to: :description
70
+ map "homepage", to: :homepage
71
+ map "display_progress_bar", to: :display_progress_bar
72
+ map "repository", to: :repository
73
+ map "platforms", to: :platforms
74
+ map "resources", to: :resources, value_map: {
75
+ to: { empty: :empty, omitted: :omitted, nil: :nil },
76
+ }
77
+ map "digest", to: :digest
78
+ map "instructions", to: :instructions
79
+ map "font_collections", to: :font_collections
80
+ map "fonts", to: :fonts
81
+ map "extract", to: :extract, value_map: {
82
+ to: { empty: :empty, omitted: :omitted, nil: :nil },
83
+ }
84
+ map "min_fontist", to: :min_fontist
85
+ map "copyright", to: :copyright
86
+ map "requires_license_agreement", to: :requires_license_agreement
87
+ map "license_url", to: :license_url
88
+ map "open_license", to: :open_license
89
+ map "command", to: :command
90
+ end
91
+
13
92
  def self.update_formulas_repo
14
93
  Update.call
15
94
  end
16
95
 
17
96
  def self.all
18
- Dir[Fontist.formulas_path.join("**/*.yml").to_s].map do |path|
19
- Formula.new_from_file(path)
97
+ formulas = Dir[Fontist.formulas_path.join("**/*.yml").to_s].map do |path|
98
+ Formula.from_file(path)
20
99
  end
100
+
101
+ FormulaCollection.new(formulas)
21
102
  end
22
103
 
23
104
  def self.all_keys
@@ -27,28 +108,28 @@ module Fontist
27
108
  end
28
109
 
29
110
  def self.find(font_name)
30
- Indexes::FontIndex.from_yaml.load_formulas(font_name).first
111
+ Indexes::FontIndex.from_file.load_formulas(font_name).first
31
112
  end
32
113
 
33
114
  def self.find_many(font_name)
34
- Indexes::FontIndex.from_yaml.load_formulas(font_name)
115
+ Indexes::FontIndex.from_file.load_formulas(font_name)
35
116
  end
36
117
 
37
118
  def self.find_fonts(font_name)
38
- formulas = Indexes::FontIndex.from_yaml.load_formulas(font_name)
119
+ formulas = Indexes::FontIndex.from_file.load_formulas(font_name)
39
120
 
40
121
  formulas.map do |formula|
41
- formula.fonts.select do |f|
122
+ formula.all_fonts.select do |f|
42
123
  f.name.casecmp?(font_name)
43
124
  end
44
125
  end.flatten
45
126
  end
46
127
 
47
128
  def self.find_styles(font_name, style_name)
48
- formulas = Indexes::FontIndex.from_yaml.load_formulas(font_name)
129
+ formulas = Indexes::FontIndex.from_file.load_formulas(font_name)
49
130
 
50
131
  formulas.map do |formula|
51
- formula.fonts.map do |f|
132
+ formula.all_fonts.map do |f|
52
133
  f.styles.select do |s|
53
134
  f.name.casecmp?(font_name) && s.type.casecmp?(style_name)
54
135
  end
@@ -64,7 +145,7 @@ module Fontist
64
145
  path = Fontist.formulas_path.join("#{key}.yml")
65
146
  return unless File.exist?(path)
66
147
 
67
- new_from_file(path)
148
+ from_file(path)
68
149
  end
69
150
 
70
151
  def self.find_by_name(name)
@@ -78,30 +159,32 @@ module Fontist
78
159
  end
79
160
 
80
161
  def self.find_by_font_file(font_file)
81
- key = Indexes::FilenameIndex
82
- .from_yaml
162
+ key = Indexes::FilenameIndex.from_file
83
163
  .load_index_formulas(File.basename(font_file))
84
- .map(&:name)
164
+ .flat_map(&:name)
85
165
  .first
86
166
 
87
167
  find_by_key(key)
88
168
  end
89
169
 
90
- def self.new_from_file(path)
91
- data = YAML.safe_load(
92
- File.read(path),
93
- permitted_classes: [Date, Symbol, Time]
94
- )
95
- new(data, path)
96
- end
170
+ def self.from_file(path)
171
+ unless File.exist?(path)
172
+ raise Fontist::Errors::FormulaCouldNotBeFoundError,
173
+ "Formula file not found: #{path}"
174
+ end
175
+
176
+ content = File.read(path)
97
177
 
98
- def initialize(data, path)
99
- @data = data
100
- @path = real_path(path)
178
+ from_yaml(content).tap do |formula|
179
+ formula.path = path
180
+ formula.name = titleize(formula.key_from_path) if formula.name.nil?
181
+ end
101
182
  end
102
183
 
103
- def to_index_formula
104
- Indexes::IndexFormula.new(path)
184
+ def self.titleize(str)
185
+ str.split("/").map do |part|
186
+ part.tr("_", " ").split.map(&:capitalize).join(" ")
187
+ end.join("/")
105
188
  end
106
189
 
107
190
  def manual?
@@ -109,118 +192,81 @@ module Fontist
109
192
  end
110
193
 
111
194
  def downloadable?
112
- @data.key?("resources")
195
+ !resources.nil? && !resources.empty?
113
196
  end
114
197
 
115
198
  def source
116
- return unless @data["resources"]
199
+ return nil if resources.empty?
117
200
 
118
- @data["resources"].values.first["source"]
119
- end
120
-
121
- def path
122
- @path
201
+ resources.first.source
123
202
  end
124
203
 
125
204
  def key
126
- @key ||= {}
127
- @key[@path] ||= key_from_path
128
- end
129
-
130
- def name
131
- @name ||= {}
132
- @name[key] ||= namespace.empty? ? base_name : "#{namespace}/#{base_name}"
133
- end
134
-
135
- def description
136
- @data["description"]
137
- end
138
-
139
- def homepage
140
- @data["homepage"]
205
+ @key ||= key_from_path
141
206
  end
142
207
 
143
- def copyright
144
- @data["copyright"]
145
- end
208
+ def key_from_path
209
+ return "" unless @path
146
210
 
147
- def license_url
148
- @data["license_url"]
211
+ escaped = Regexp.escape("#{Fontist.formulas_path}/")
212
+ @path.sub(Regexp.new("^#{escaped}"), "").sub(/\.yml$/, "").to_s
149
213
  end
150
214
 
151
215
  def license
152
- @data["open_license"] || @data["requires_license_agreement"]
153
- end
154
-
155
- def license_required
156
- @data["requires_license_agreement"] ? true : false
216
+ open_license || requires_license_agreement
157
217
  end
158
218
 
159
- def platforms
160
- @data["platforms"]
161
- end
162
-
163
- def min_fontist
164
- @data["min_fontist"]
165
- end
166
-
167
- def extract
168
- Helpers.parse_to_object(@data["extract"])
219
+ def license_required?
220
+ requires_license_agreement ? true : false
169
221
  end
170
222
 
171
223
  def file_size
172
- return unless @data["resources"]
224
+ return nil if resources.nil? || resources.empty?
173
225
 
174
- @data["resources"].values.first["file_size"]&.to_i
175
- end
176
-
177
- def resources
178
- Helpers.parse_to_object(@data["resources"]&.values)
179
- end
180
-
181
- def instructions
182
- @data["instructions"]
226
+ resources.first.file_size
183
227
  end
184
228
 
185
229
  def font_by_name(name)
186
- fonts.find do |font|
230
+ all_fonts.find do |font|
187
231
  font.name.casecmp?(name)
188
232
  end
189
233
  end
190
234
 
191
235
  def fonts_by_name(name)
192
- fonts.select do |font|
236
+ all_fonts.select do |font|
193
237
  font.name.casecmp?(name)
194
238
  end
195
239
  end
196
240
 
197
- def fonts
198
- @fonts ||= Helpers.parse_to_object(fonts_by_family)
241
+ def all_fonts
242
+ Array(fonts) + collection_fonts
199
243
  end
200
244
 
201
- def digest
202
- @data["digest"]
245
+ def collection_fonts
246
+ Array(font_collections).flat_map do |c|
247
+ c.fonts.flat_map do |f|
248
+ f.styles.each do |s|
249
+ s.font = c.filename
250
+ s.source_font = c.source_filename
251
+ end
252
+ f
253
+ end
254
+ end
203
255
  end
204
256
 
205
257
  def style_override(font)
206
- fonts
258
+ all_fonts
207
259
  .map(&:styles)
208
260
  .flatten
209
- .detect { |s| s.family_name == font }
210
- &.dig(:override) || {}
261
+ .detect { |s| s.family_name == font }&.override || {}
211
262
  end
212
263
 
213
264
  private
214
265
 
215
- def real_path(path)
266
+ def real_path
216
267
  Dir.glob(path).first
217
268
  end
218
269
 
219
- def key_from_path
220
- escaped = Regexp.escape("#{Fontist.formulas_path}/")
221
- @path.sub(Regexp.new("^#{escaped}"), "").sub(/\.yml$/, "").to_s
222
- end
223
-
224
270
  def namespace
225
271
  namespace_from_mappings || namespace_from_key
226
272
  end
@@ -248,14 +294,14 @@ module Fontist
248
294
  .split.map(&:capitalize).join(" ")
249
295
  end
250
296
 
251
- def fonts_by_family
252
- return hash_all_fonts unless Fontist.preferred_family?
297
+ def fonts_by_family(data)
298
+ return hash_all_fonts(data) unless Fontist.preferred_family?
253
299
 
254
- preferred_family_fonts
300
+ preferred_family_fonts(data)
255
301
  end
256
302
 
257
- def preferred_family_fonts
258
- groups = preferred_family_styles.group_by do |style|
303
+ def preferred_family_fonts(data)
304
+ groups = preferred_family_styles(data).group_by do |style|
259
305
  style["family_name"]
260
306
  end
261
307
 
@@ -264,8 +310,8 @@ module Fontist
264
310
  end
265
311
  end
266
312
 
267
- def preferred_family_styles
268
- hash_all_fonts.flat_map do |font|
313
+ def preferred_family_styles(data)
314
+ hash_all_fonts(data).flat_map do |font|
269
315
  font["styles"].map do |style|
270
316
  style.merge(preferred_style(style))
271
317
  end
@@ -273,20 +319,22 @@ module Fontist
273
319
  end
274
320
 
275
321
  def preferred_style(style)
276
- { "family_name" => style["preferred_family_name"] || style["family_name"],
322
+ {
323
+ "family_name" => style["preferred_family_name"] || style["family_name"],
277
324
  "type" => style["preferred_type"] || style["type"],
278
325
  "default_family_name" => style["family_name"],
279
- "default_type" => style["type"] }
326
+ "default_type" => style["type"],
327
+ }
280
328
  end
281
329
 
282
- def hash_all_fonts
283
- hash_collection_fonts + hash_fonts
330
+ def hash_all_fonts(data)
331
+ hash_collection_fonts(data) + hash_fonts(data)
284
332
  end
285
333
 
286
- def hash_collection_fonts
287
- return [] unless @data["font_collections"]
334
+ def hash_collection_fonts(data)
335
+ return [] unless data["font_collections"]
288
336
 
289
- @data["font_collections"].flat_map do |coll|
337
+ data["font_collections"].flat_map do |coll|
290
338
  filenames = { "font" => coll["filename"],
291
339
  "source_font" => coll["source_filename"] }
292
340
 
@@ -296,10 +344,19 @@ module Fontist
296
344
  end
297
345
  end
298
346
 
299
- def hash_fonts
300
- return [] unless @data["fonts"]
347
+ def hash_fonts(data)
348
+ return [] unless data["fonts"]
349
+
350
+ data["fonts"]
351
+ end
352
+ end
353
+
354
+ class FormulaCollection < Lutaml::Model::Collection
355
+ instances :formulas, Formula
301
356
 
302
- @data["fonts"]
357
+ key_value do
358
+ map "name", to: :name
359
+ map "formula", to: :formula
303
360
  end
304
361
  end
305
362
  end
@@ -1,9 +1,10 @@
1
- require "ostruct"
2
-
3
1
  module Fontist
4
2
  module Helpers
5
- def self.parse_to_object(data)
6
- JSON.parse(data.to_json, object_class: OpenStruct)
3
+ def self.url_object(request)
4
+ return request unless request.include?("\"url\"")
5
+
6
+ obj = JSON.parse(request.gsub("=>", ":"))
7
+ Struct.new(:url, :headers).new(obj["url"], obj["headers"])
7
8
  end
8
9
 
9
10
  def self.run(command)
@@ -111,7 +111,8 @@ module Fontist
111
111
  @extractor ||=
112
112
  RecursiveExtraction.new(archive,
113
113
  subdir: @options[:subdir],
114
- file_pattern: @options[:file_pattern])
114
+ file_pattern: @options[:file_pattern],
115
+ name_prefix: @options[:name_prefix])
115
116
  end
116
117
 
117
118
  def archive
@@ -8,8 +8,9 @@ module Fontist
8
8
  class CollectionFile
9
9
  attr_reader :fonts
10
10
 
11
- def initialize(path)
11
+ def initialize(path, name_prefix: nil)
12
12
  @path = path
13
+ @name_prefix = name_prefix
13
14
  @fonts = read
14
15
  @extension = detect_extension
15
16
  end
@@ -25,26 +26,15 @@ module Fontist
25
26
  private
26
27
 
27
28
  def read
28
- switch_to_temp_dir do |tmp_dir|
29
+ Dir.mktmpdir do |tmp_dir|
29
30
  extract_ttfs(tmp_dir)
30
- .map { |path| Otf::FontFile.new(path) }
31
+ .map { |path| Otf::FontFile.new(path, name_prefix: @name_prefix) }
31
32
  .reject { |font_file| hidden_or_pua_encoded?(font_file) }
32
33
  end
33
34
  end
34
35
 
35
- def switch_to_temp_dir
36
- Dir.mktmpdir do |tmp_dir|
37
- Dir.chdir(tmp_dir) do
38
- yield tmp_dir
39
- end
40
- end
41
- end
42
-
43
36
  def extract_ttfs(tmp_dir)
44
- filenames = ExtractTtc.extract(@path)
45
- filenames.map do |filename|
46
- File.join(tmp_dir, filename)
47
- end
37
+ ExtractTtc.extract(@path, output_dir: tmp_dir)
48
38
  end
49
39
 
50
40
  def hidden_or_pua_encoded?(font_file)
@@ -0,0 +1,22 @@
1
+ module Fontist
2
+ module Import
3
+ class FontStyle
4
+ attr_reader :family_name, :style, :full_name, :post_script_name,
5
+ :version, :description, :filename, :copyright,
6
+ :preferred_family_name, :preferred_style
7
+
8
+ def initialize(attributes = {})
9
+ @family_name = attributes[:family_name]
10
+ @style = attributes[:style]
11
+ @full_name = attributes[:full_name]
12
+ @post_script_name = attributes[:post_script_name]
13
+ @version = attributes[:version]
14
+ @description = attributes[:description]
15
+ @filename = attributes[:filename]
16
+ @copyright = attributes[:copyright]
17
+ @preferred_family_name = attributes[:preferred_family_name]
18
+ @preferred_style = attributes[:preferred_style]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -26,8 +26,8 @@ module Fontist
26
26
  def fonts_attributes
27
27
  { display_progress_bar: formula_progress_bar(@formula),
28
28
  resources: @formula.resources,
29
- font_collections: font_collections(@formula.fonts),
30
- fonts: standalone_fonts(@formula.fonts),
29
+ font_collections: font_collections(@formula.all_fonts),
30
+ fonts: standalone_fonts(@formula.all_fonts),
31
31
  extract: extract_type(@code) }
32
32
  end
33
33
 
@@ -122,11 +122,11 @@ module Fontist
122
122
  # rubocop:enable Metrics/MethodLength, Metrics/LineLength
123
123
 
124
124
  def requires_license_agreement(formula)
125
- formula.license if formula.license && formula.license_required
125
+ formula.license if formula.license && formula.license_required?
126
126
  end
127
127
 
128
128
  def open_license(formula)
129
- formula.license if formula.license && !formula.license_required
129
+ formula.license if formula.license && !formula.license_required?
130
130
  end
131
131
  end
132
132
  end
@@ -69,7 +69,7 @@ module Fontist
69
69
 
70
70
  def formula(font_name)
71
71
  path = formula_path(font_name)
72
- Formula.new_from_file(path) if File.exist?(path)
72
+ Formula.from_file(path) if File.exist?(path)
73
73
  end
74
74
 
75
75
  def formula_path(name)
@@ -21,8 +21,9 @@ module Fontist
21
21
 
22
22
  attr_reader :path
23
23
 
24
- def initialize(path)
24
+ def initialize(path, name_prefix: nil)
25
25
  @path = path
26
+ @name_prefix = name_prefix
26
27
  @info = read
27
28
  @extension = detect_extension
28
29
  end
@@ -36,7 +37,8 @@ module Fontist
36
37
  end
37
38
 
38
39
  def family_name
39
- info["Family"]
40
+ name = info["Family"]
41
+ @name_prefix ? "#{@name_prefix}#{name}" : name
40
42
  end
41
43
 
42
44
  def type
@@ -44,7 +46,10 @@ module Fontist
44
46
  end
45
47
 
46
48
  def preferred_family_name
47
- info["Preferred family"]
49
+ name = info["Preferred family"]
50
+ return unless name
51
+
52
+ @name_prefix ? "#{@name_prefix}#{name}" : name
48
53
  end
49
54
 
50
55
  def preferred_type
@@ -1,3 +1,5 @@
1
+ require_relative "font_style"
2
+
1
3
  module Fontist
2
4
  module Import
3
5
  class OtfStyle
@@ -24,7 +26,7 @@ module Fontist
24
26
  style[:preferred_style] = @info["Preferred subfamily"]
25
27
  end
26
28
 
27
- OpenStruct.new(style)
29
+ FontStyle.new(style)
28
30
  end
29
31
 
30
32
  private
@@ -8,10 +8,11 @@ module Fontist
8
8
  LICENSE_PATTERN =
9
9
  /(ofl\.txt|ufl\.txt|licenses?\.txt|license(\.md)?|copying)$/i.freeze
10
10
 
11
- def initialize(archive, subdir: nil, file_pattern: nil)
11
+ def initialize(archive, subdir: nil, file_pattern: nil, name_prefix: nil)
12
12
  @archive = archive
13
13
  @subdir = subdir
14
14
  @file_pattern = file_pattern
15
+ @name_prefix = name_prefix
15
16
  @operations = {}
16
17
  @font_files = []
17
18
  @collection_files = []
@@ -80,10 +81,10 @@ module Fontist
80
81
  def match_font(path)
81
82
  case Files::FontDetector.detect(path)
82
83
  when :font
83
- file = Otf::FontFile.new(path)
84
+ file = Otf::FontFile.new(path, name_prefix: @name_prefix)
84
85
  @font_files << file unless already_exist?(file)
85
86
  when :collection
86
- @collection_files << Files::CollectionFile.new(path)
87
+ @collection_files << Files::CollectionFile.new(path, name_prefix: @name_prefix)
87
88
  end
88
89
  end
89
90
 
@@ -97,7 +98,11 @@ module Fontist
97
98
  end
98
99
 
99
100
  def font_candidate?(path)
100
- font_directory?(path) && file_pattern?(path)
101
+ has_font_extension?(path) && font_directory?(path) && file_pattern?(path)
102
+ end
103
+
104
+ def has_font_extension?(path)
105
+ path.match?(/\.(ttf|otf|ttc|woff|woff2)$/i)
101
106
  end
102
107
 
103
108
  def font_directory?(path)