fontist 3.0.23 → 3.0.25

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 437b60367c3601897b3f22af4c3e00a2ffec30d74120df9fff64ad76291b7624
4
- data.tar.gz: 49206f8ffe89f3110c8d97513b4b263d6cfc8c47566bf39283464a46b34b94cd
3
+ metadata.gz: 7e161f1ace760cebffefd73f61835fef3abb2875972836c3ad7e82324a465d11
4
+ data.tar.gz: 85f425e3a050b0e9674e32b413d750bde0d40e4e34f3c261c6138dadc571e0a5
5
5
  SHA512:
6
- metadata.gz: b95402fae1199140efe5a00a54ce59207bd15268ee28803a3e830942d812ba3d0d8bedddf800a7ebcea6c736666d8f183f68b1df98a74714137da2ebd1fcae86
7
- data.tar.gz: 42d80c546ae290865441aa1ebaf9ac04278ee99dfaad92863dd61b92fb3594511434957d950fbb6551344708ec06da5cf3812fe06e3009c0a36a92cb473c6cef
6
+ metadata.gz: 282670c9e6c9ebc53dedbaa927e80259a489f44bc33392d388c977ad909209b721b354b7922a2190f0e7554221a63ca3c15a234a55b100e27bdce92cca1e496e
7
+ data.tar.gz: 46ca6895943eefe3a7dce37a6e4cf08241530b6dfacc9fef5233f259ea091ab9d94aac2d4bdd903cc468948d3783a6ceff66219fd04f3514576f88a4a62048ef
@@ -14,7 +14,7 @@ module Fontist
14
14
 
15
15
  def formulas
16
16
  @formulas ||= if fontist_font?
17
- Indexes::FilenameIndex.from_file.load_index_formulas(File.basename(@path)).flat_map(&:name)
17
+ Indexes::FilenameIndex.lookup_index.load_index_formulas(File.basename(@path)).flat_map(&:name)
18
18
  else
19
19
  []
20
20
  end
@@ -122,15 +122,15 @@ module Fontist
122
122
  end
123
123
 
124
124
  def find(font_name)
125
- Indexes::FontIndex.from_file.load_formulas(font_name).first
125
+ Indexes::FontIndex.lookup_index.load_formulas(font_name).first
126
126
  end
127
127
 
128
128
  def find_many(font_name)
129
- Indexes::FontIndex.from_file.load_formulas(font_name)
129
+ Indexes::FontIndex.lookup_index.load_formulas(font_name)
130
130
  end
131
131
 
132
132
  def find_fonts(font_name)
133
- formulas = Indexes::FontIndex.from_file.load_formulas(font_name)
133
+ formulas = Indexes::FontIndex.lookup_index.load_formulas(font_name)
134
134
 
135
135
  formulas.map do |formula|
136
136
  formula.all_fonts.select do |f|
@@ -140,7 +140,7 @@ module Fontist
140
140
  end
141
141
 
142
142
  def find_styles(font_name, style_name)
143
- formulas = Indexes::FontIndex.from_file.load_formulas(font_name)
143
+ formulas = Indexes::FontIndex.lookup_index.load_formulas(font_name)
144
144
 
145
145
  formulas.map do |formula|
146
146
  formula.all_fonts.map do |f|
@@ -173,7 +173,7 @@ module Fontist
173
173
  end
174
174
 
175
175
  def find_by_font_file(font_file)
176
- key = Indexes::FilenameIndex.from_file
176
+ key = Indexes::FilenameIndex.lookup_index
177
177
  .load_index_formulas(File.basename(font_file))
178
178
  .flat_map(&:name)
179
179
  .first
@@ -122,6 +122,7 @@ module Fontist
122
122
  return if collection.fonts.nil?
123
123
 
124
124
  collection.fonts.delete_if { |f| f.path == path }
125
+ collection.invalidate_lookups!
125
126
  collection.to_file(index_path)
126
127
  end
127
128
 
@@ -8,6 +8,14 @@ module Fontist
8
8
  DefaultFamilyFontIndex.from_file
9
9
  end
10
10
  end
11
+
12
+ def self.lookup_index
13
+ if Fontist.preferred_family?
14
+ PreferredFamilyFontIndex.lookup_index
15
+ else
16
+ DefaultFamilyFontIndex.lookup_index
17
+ end
18
+ end
11
19
  end
12
20
  end
13
21
  end
@@ -1,5 +1,9 @@
1
+ require "json"
2
+
1
3
  module Fontist
2
4
  module Indexes
5
+ autoload :LookupIndex, "#{__dir__}/lookup_index"
6
+
3
7
  # IndexMixin provides common functionality for font index classes.
4
8
  #
5
9
  # == Performance Optimization (Tech Debt)
@@ -72,9 +76,38 @@ module Fontist
72
76
  # to force a fresh parse on next access
73
77
  Fontist::FromFileCache.clear
74
78
 
75
- # Delete the index file to force rebuild on next access
76
- # This is important for tests to ensure clean state
79
+ # Delete the index file and its lookup sidecar to force a
80
+ # rebuild on next access. This is important for tests to
81
+ # ensure clean state
77
82
  FileUtils.rm_f(path)
83
+ FileUtils.rm_f("#{path}.json")
84
+ end
85
+
86
+ # Read-only lookups over the sidecar JSON beside the index
87
+ # file, which skips binding every entry into model objects. The
88
+ # sidecar is (re)written whenever the index file is written,
89
+ # and regenerated here when it is missing or stale.
90
+ def lookup_index
91
+ entries_hash = sidecar_entries || rebuild_sidecar_entries
92
+ normalizer = new.method(:normalize_key)
93
+ LookupIndex.new(entries_hash, normalizer: normalizer)
94
+ end
95
+
96
+ def sidecar_entries
97
+ sidecar_path = "#{path}.json"
98
+ return unless File.exist?(sidecar_path)
99
+ return if File.exist?(path) &&
100
+ File.mtime(sidecar_path) < File.mtime(path)
101
+
102
+ JSON.parse(File.read(sidecar_path))["entries"]
103
+ rescue JSON::ParserError, TypeError
104
+ nil
105
+ end
106
+
107
+ def rebuild_sidecar_entries
108
+ entries_hash = from_file(path).entries_hash
109
+ File.write("#{path}.json", JSON.dump("entries" => entries_hash))
110
+ entries_hash
78
111
  end
79
112
  end
80
113
 
@@ -158,6 +191,19 @@ module Fontist
158
191
 
159
192
  FileUtils.mkdir_p(File.dirname(file_path))
160
193
  File.write(file_path, to_yaml)
194
+ write_lookup_sidecar(file_path)
195
+ end
196
+
197
+ def write_lookup_sidecar(file_path)
198
+ File.write("#{file_path}.json",
199
+ JSON.dump("entries" => entries_hash))
200
+ end
201
+
202
+ # key -> formula paths, in the sidecar's JSON-friendly shape
203
+ def entries_hash
204
+ Array(entries).to_h do |entry|
205
+ [normalize_key(entry.key), entry.formula_path]
206
+ end
161
207
  end
162
208
 
163
209
  private
@@ -209,15 +255,29 @@ module Fontist
209
255
  def create_and_add_entry(key, paths)
210
256
  entry = FormulaKeyToPath.new(key: key, formula_path: paths)
211
257
  entries << entry
258
+ @entries_lookup_id = nil
212
259
  @index_build_cache[key] = entry if @index_build_cache
213
260
  end
214
261
 
262
+ # O(1) key lookup over the entries; rebuilt whenever the entries
263
+ # array identity changes (rebuilds parse fresh instances, and
264
+ # in-place additions drop the cached hash)
265
+ def entries_lookup
266
+ entries_id = Array(entries).object_id
267
+ if @entries_lookup_id != entries_id
268
+ @entries_lookup_id = entries_id
269
+ @entries_lookup = Array(entries)
270
+ .group_by { |entry| normalize_key(entry.key) }
271
+ end
272
+ @entries_lookup
273
+ end
274
+
215
275
  def index_formula(key)
216
276
  Array(entries).detect { |f| normalize_key(f.key) == normalize_key(key) }
217
277
  end
218
278
 
219
279
  def index_formulas(key)
220
- Array(entries).select { |f| normalize_key(f.key) == normalize_key(key) }
280
+ entries_lookup[normalize_key(key)] || []
221
281
  end
222
282
 
223
283
  def relative_formula_path(path)
@@ -0,0 +1,34 @@
1
+ module Fontist
2
+ module Indexes
3
+ # Read-only lookups over the JSON sidecar written beside an index
4
+ # file. Binding every index entry into model objects dominates the
5
+ # cost of loading an index; the sidecar holds the key-to-path map
6
+ # as plain data, and FormulaKeyToPath entries are materialized only
7
+ # for the requested key.
8
+ class LookupIndex
9
+ def initialize(entries_hash, normalizer:)
10
+ @entries_hash = entries_hash
11
+ @normalizer = normalizer
12
+ end
13
+
14
+ def load_index_formulas(key)
15
+ [
16
+ FormulaKeyToPath.new(
17
+ key: key,
18
+ formula_path: paths_for(@normalizer.call(key)),
19
+ ),
20
+ ]
21
+ end
22
+
23
+ def load_formulas(key)
24
+ load_index_formulas(key).flat_map(&:to_full)
25
+ end
26
+
27
+ private
28
+
29
+ def paths_for(normalized_key)
30
+ @entries_hash[normalized_key] || []
31
+ end
32
+ end
33
+ end
34
+ end
@@ -209,15 +209,13 @@ module Fontist
209
209
 
210
210
  return nil if current_fonts.nil? || current_fonts.empty?
211
211
 
212
- if style.nil?
213
- found_fonts = current_fonts.select do |file|
214
- file.family_name&.casecmp?(font)
215
- end
216
- else
217
- found_fonts = current_fonts.select do |file|
218
- file.family_name&.casecmp?(font) && file.type&.casecmp?(style)
212
+ bucket = family_lookup(current_fonts)[font.to_s.downcase] || []
213
+ found_fonts =
214
+ if style.nil?
215
+ bucket
216
+ else
217
+ bucket.select { |file| file.type&.casecmp?(style) }
219
218
  end
220
- end
221
219
 
222
220
  # Apply format filtering if specified
223
221
  if format_spec&.has_constraints? && found_fonts
@@ -229,6 +227,25 @@ module Fontist
229
227
  found_fonts.empty? ? nil : found_fonts
230
228
  end
231
229
 
230
+ # Drops the cached family lookup; called when fonts are removed in
231
+ # place (a rebuild replaces the fonts array, which re-keys the
232
+ # lookup by itself)
233
+ def invalidate_lookups!
234
+ @family_lookup_fonts_id = nil
235
+ end
236
+
237
+ # O(1) family-name lookup over the indexed fonts; rebuilt whenever
238
+ # the fonts array identity changes
239
+ def family_lookup(current_fonts)
240
+ fonts_id = Array(current_fonts).object_id
241
+ if @family_lookup_fonts_id != fonts_id
242
+ @family_lookup_fonts_id = fonts_id
243
+ @family_lookup = Array(current_fonts)
244
+ .group_by { |file| file.family_name&.downcase }
245
+ end
246
+ @family_lookup
247
+ end
248
+
232
249
  def index
233
250
  if @read_only_mode && !fonts.nil?
234
251
  return fonts
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.23".freeze
2
+ VERSION = "3.0.25".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontist
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.23
4
+ version: 3.0.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -452,6 +452,7 @@ files:
452
452
  - lib/fontist/indexes/incremental_index_updater.rb
453
453
  - lib/fontist/indexes/incremental_scanner.rb
454
454
  - lib/fontist/indexes/index_mixin.rb
455
+ - lib/fontist/indexes/lookup_index.rb
455
456
  - lib/fontist/indexes/preferred_family_font_index.rb
456
457
  - lib/fontist/indexes/system_index.rb
457
458
  - lib/fontist/indexes/user_index.rb