fontist 3.0.33 → 3.0.35

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: 9138df3c46f325e2909f1312ffbe906d3b6bd6b70b695a751af1576f68cd8903
4
- data.tar.gz: a496ef4bac70d7b1c414f17d07514bece411a7fc9e8dd1e4901e8fcc8e9d55b5
3
+ metadata.gz: 1ade396e3811d8a2df5ae15cce0af89df56bf723bc9c915f42f72b487073de39
4
+ data.tar.gz: ab738ce85235d7cdf811c0cf51ec23b28488637d09f7c9bc77ca9b91c8afb2e6
5
5
  SHA512:
6
- metadata.gz: 6a179916eb34c002c5635b8e7e6ee9ec6602e8aa1a7a6ab02174fdd6f25a5f183ed307f4db04911f62901a5f44d034850a4225827062503bf606587593918ec0
7
- data.tar.gz: 23e184310fc3ab3da8b99fa9dc5c3f506f29962d837c36a46f09211873014eadc46379bea2f5788cfdbec7e5cdc80d610458266c9a33985c8fba217875d38d16
6
+ metadata.gz: 69e7e9ef3fd5200939a9c2820cd58e19af2cef6890ee4f91b317c227c5e33b46d5ae71046663e565c4883a89bda147a39f9c723a51383ab03203c8e59ac73fcf
7
+ data.tar.gz: 3e08dc9b127f6e313ddf2f6fcae9fa174832d57c0ae0bc4bb7730094f2fbc05a0171966ce1d793f1af87ef08808b88b0771630acf374347210dcb6ac8bc10f41
@@ -43,9 +43,19 @@ jobs:
43
43
  with:
44
44
  path: "fontist"
45
45
 
46
- # Append rather than overwrite: metanorma's own Gemfile.devel carries
47
- # the taste-stack gems its compile specs need (metanorma/issues/630)
48
- - run: 'echo ''gem "fontist", path: "./fontist"'' >> Gemfile.devel'
46
+ # Point any existing fontist line at this checkout, appending only
47
+ # when the host Gemfile declares none (metanorma/issues/630 keeps
48
+ # the taste-stack gems it carries; since metanorma#652 its
49
+ # Gemfile.devel also pins fontist, which a blind append would
50
+ # duplicate into a version-conflict error).
51
+ - run: |
52
+ if grep -q '^gem "fontist"' Gemfile.devel Gemfile 2>/dev/null; then
53
+ sed -i.bak 's/^gem "fontist".*/gem "fontist", path: ".\/fontist"/' Gemfile.devel Gemfile 2>/dev/null || true
54
+ rm -f Gemfile.devel.bak Gemfile.bak
55
+ else
56
+ echo 'gem "fontist", path: "./fontist"' >> Gemfile.devel
57
+ fi
58
+ shell: bash
49
59
 
50
60
  - uses: ruby/setup-ruby@v1
51
61
  with:
data/fontist.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency "fontisan", "~> 0.4", ">= 0.4.47"
37
37
  spec.add_dependency "fuzzy_match", "~> 2.1"
38
38
  spec.add_dependency "git", "~> 6.0"
39
- spec.add_dependency "lutaml-model", "~> 0.8.162"
39
+ spec.add_dependency "lutaml-model", "~> 0.8.164"
40
40
  spec.add_dependency "marcel", "~> 1.0"
41
41
  spec.add_dependency "moxml", "~> 0.6"
42
42
  spec.add_dependency "octokit", "~> 10.0"
@@ -148,10 +148,12 @@ module Fontist
148
148
  class SystemIndexMetadata < Lutaml::Model::Serializable
149
149
  attribute :last_scan_time, :integer, default: -> { 0 }
150
150
  attribute :directory_mtimes, :string, collection: true, default: -> { [] }
151
+ attribute :format_version, :integer, default: -> { 0 }
151
152
 
152
153
  key_value do
153
154
  map "last_scan_time", to: :last_scan_time
154
155
  map "directory_mtimes", to: :directory_mtimes
156
+ map "format_version", to: :format_version
155
157
  end
156
158
 
157
159
  def self.load(path)
@@ -173,9 +175,12 @@ module Fontist
173
175
  instances :fonts, SystemIndexFont
174
176
  attr_accessor :path, :paths_loader
175
177
 
176
- # Metadata for smart change detection
178
+ # Metadata for smart change detection. The format generation lives
179
+ # only in the companion metadata file (see restore_metadata); it is
180
+ # never serialized into the entry list itself.
177
181
  attribute :last_scan_time, :integer, default: -> { 0 }
178
182
  attribute :directory_mtimes, :string, collection: true, default: -> { [] }
183
+ attribute :format_version, :integer, default: -> { 0 }
179
184
 
180
185
  key_value do
181
186
  # A bare entry list: the scan metadata lives in a companion
@@ -188,6 +193,13 @@ module Fontist
188
193
  # Don't rebuild index more frequently than this (in seconds)
189
194
  INDEX_REBUILD_THRESHOLD = 30 * 60 # 30 minutes
190
195
 
196
+ # Index entry shape generation. v1 stored one entry per font file;
197
+ # v2 stores one entry per named instance of variable fonts (the
198
+ # FiraCode[wght].ttf class of files). Bump when the entry shape
199
+ # changes again: persisted indexes carrying an older generation
200
+ # cannot answer the current lookups and force a rebuild.
201
+ INDEX_FORMAT_VERSION = 2
202
+
191
203
  def metadata_path
192
204
  "#{@path}.metadata.yml"
193
205
  end
@@ -197,19 +209,32 @@ module Fontist
197
209
 
198
210
  SystemIndexMetadata
199
211
  .new(last_scan_time: last_scan_time,
200
- directory_mtimes: directory_mtimes)
212
+ directory_mtimes: directory_mtimes,
213
+ format_version: INDEX_FORMAT_VERSION)
201
214
  .save(metadata_path)
202
215
  end
203
216
 
204
217
  def restore_metadata
205
218
  return unless @path
206
- return unless last_scan_time.to_i.zero?
207
219
 
208
220
  meta = SystemIndexMetadata.load(metadata_path)
209
- return unless meta.last_scan_time.to_i.positive?
210
-
211
221
  self.last_scan_time = meta.last_scan_time
212
222
  self.directory_mtimes = meta.directory_mtimes
223
+ self.format_version = meta.format_version
224
+ end
225
+
226
+ # Whether the persisted index predates the current entry shape. A
227
+ # v1 index answers every lookup of a variable font's non-default
228
+ # families with "missing" (the file registered only under its
229
+ # default instance), and the directory-mtime fast paths would keep
230
+ # trusting it forever, so the generation mismatch itself is the
231
+ # staleness signal. A collection with no companion metadata file is
232
+ # fresh or in-process memory, not a persisted v1 volume: treating
233
+ # it as stale would discard every cached entry on the next rebuild.
234
+ def stale_format?
235
+ return false unless @path && File.exist?(metadata_path.to_s)
236
+
237
+ format_version.to_i != INDEX_FORMAT_VERSION
213
238
  end
214
239
 
215
240
  def set_path(path)
@@ -263,6 +288,7 @@ module Fontist
263
288
  end
264
289
 
265
290
  def find(font, style, format_spec: nil)
291
+ rebuild_if_stale_format!
266
292
  current_fonts = index
267
293
 
268
294
  return nil if current_fonts.nil? || current_fonts.empty?
@@ -285,6 +311,35 @@ module Fontist
285
311
  found_fonts.empty? ? nil : found_fonts
286
312
  end
287
313
 
314
+ # Rebuild once per process when the persisted index predates
315
+ # INDEX_FORMAT_VERSION. Runs even under read_only_mode: a stale
316
+ # generation answers every lookup of a variable font's non-default
317
+ # families with "missing", which is worse than a one-off rebuild.
318
+ # Only a VALID, non-empty persisted index heals: a fresh environment
319
+ # (no file yet) and a corrupt file (the corruption path explains the
320
+ # removal fix) take their normal routes.
321
+ def rebuild_if_stale_format!
322
+ return if @format_check_done
323
+
324
+ @format_check_done = true
325
+ return unless @path && File.exist?(@path.to_s)
326
+ return if fonts.nil? || fonts.empty?
327
+
328
+ # A corrupt index keeps its documented contract: the corruption
329
+ # error with the removal hint, not a silent heal.
330
+ check_index
331
+
332
+ restore_metadata
333
+ return unless stale_format?
334
+
335
+ Fontist.ui.warn(
336
+ "Font index format changed (v#{format_version.to_i} -> " \
337
+ "v#{INDEX_FORMAT_VERSION}); rebuilding once.",
338
+ )
339
+ build(forced: true)
340
+ mark_verified!
341
+ end
342
+
288
343
  # Drops the cached family lookup; called when fonts are removed in
289
344
  # place (a rebuild replaces the fonts array, which re-keys the
290
345
  # lookup by itself)
@@ -573,7 +628,12 @@ module Fontist
573
628
  end
574
629
 
575
630
  def detect_paths(paths, verbose: false, stats: nil)
576
- existing_fonts_by_path = fonts&.group_by(&:path) || {}
631
+ # A generation change invalidates every cached parse in one sweep:
632
+ # v1 entries (one per file) cannot be reused to build v2 lookups
633
+ # (one per named instance), and the per-file cache fast path would
634
+ # otherwise keep serving the stale entry for every unchanged file.
635
+ existing_fonts_by_path =
636
+ stale_format? ? {} : (fonts&.group_by(&:path) || {})
577
637
 
578
638
  # Initialize stats if not provided
579
639
  stats ||= IndexStats.new if verbose
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.33".freeze
2
+ VERSION = "3.0.35".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontist
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.33
4
+ version: 3.0.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-11-04 00:00:00.000000000 Z
11
+ date: 2026-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -98,14 +98,14 @@ dependencies:
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: 0.8.162
101
+ version: 0.8.164
102
102
  type: :runtime
103
103
  prerelease: false
104
104
  version_requirements: !ruby/object:Gem::Requirement
105
105
  requirements:
106
106
  - - "~>"
107
107
  - !ruby/object:Gem::Version
108
- version: 0.8.162
108
+ version: 0.8.164
109
109
  - !ruby/object:Gem::Dependency
110
110
  name: marcel
111
111
  requirement: !ruby/object:Gem::Requirement