fontist 3.0.27 → 3.0.29

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.
@@ -75,6 +75,7 @@ module Fontist
75
75
  # Drop the memoized parsed instance along with the index file,
76
76
  # to force a fresh parse on next access
77
77
  Fontist::FromFileCache.clear
78
+ reset_sidecar_cache
78
79
 
79
80
  # Delete the index file and its lookup sidecar to force a
80
81
  # rebuild on next access. This is important for tests to
@@ -96,10 +97,12 @@ module Fontist
96
97
  def sidecar_entries
97
98
  sidecar_path = "#{path}.json"
98
99
  return unless File.exist?(sidecar_path)
99
- return if File.exist?(path) &&
100
- File.mtime(sidecar_path) < File.mtime(path)
100
+ return if sidecar_stale?(sidecar_path)
101
101
 
102
- JSON.parse(File.read(sidecar_path))["entries"]
102
+ entries = cached_sidecar_entries(sidecar_path)
103
+ return entries if entries
104
+
105
+ parse_sidecar_entries(sidecar_path)
103
106
  rescue JSON::ParserError, TypeError
104
107
  nil
105
108
  end
@@ -109,6 +112,51 @@ module Fontist
109
112
  File.write("#{path}.json", JSON.dump("entries" => entries_hash))
110
113
  entries_hash
111
114
  end
115
+
116
+ private
117
+
118
+ def sidecar_stale?(sidecar_path)
119
+ File.exist?(path) && File.mtime(sidecar_path) < File.mtime(path)
120
+ end
121
+
122
+ def cached_sidecar_entries(sidecar_path)
123
+ mtime = File.mtime(sidecar_path)
124
+ entry = nil
125
+ sidecar_cache_mutex.synchronize do
126
+ entry = sidecar_cache[sidecar_path]
127
+ end
128
+ entry[:entries] if entry&.fetch(:mtime, nil) == mtime
129
+ end
130
+
131
+ # Parsing the sidecar costs tens of milliseconds per call and
132
+ # lookups re-enter many times per operation (every printed font
133
+ # path consults the filename sidecar), so parsed entries are
134
+ # memoized per sidecar mtime and shared between callers; treat
135
+ # as read-only
136
+ def parse_sidecar_entries(sidecar_path)
137
+ mtime = File.mtime(sidecar_path)
138
+ entries = JSON.parse(File.read(sidecar_path))["entries"]
139
+
140
+ sidecar_cache_mutex.synchronize do
141
+ sidecar_cache[sidecar_path] = { mtime: mtime, entries: entries }
142
+ end
143
+
144
+ entries
145
+ end
146
+
147
+ # Class-level cache of parsed sidecar entries, invalidated by
148
+ # sidecar mtime so `fontist update` rewrites are picked up
149
+ def sidecar_cache
150
+ @sidecar_cache ||= {}
151
+ end
152
+
153
+ def sidecar_cache_mutex
154
+ @sidecar_cache_mutex ||= Mutex.new
155
+ end
156
+
157
+ def reset_sidecar_cache
158
+ sidecar_cache_mutex.synchronize { sidecar_cache.clear }
159
+ end
112
160
  end
113
161
 
114
162
  # Build index by loading all formulas from disk.
@@ -18,6 +18,12 @@ module Fontist
18
18
  # Collection options
19
19
  attribute :collection_index, :integer
20
20
 
21
+ # Font operations (subsetting, conversion failure handling)
22
+ attribute :subset_unicodes, :string
23
+ attribute :subset_text, :string
24
+ attribute :fallback_to_original, :boolean, default: false
25
+ attribute :instance, :string
26
+
21
27
  # Build FormatSpec from attributes
22
28
  def format_spec
23
29
  FormatSpec.new(
@@ -27,6 +33,10 @@ module Fontist
27
33
  transcode_path: transcode_path,
28
34
  keep_original: keep_original,
29
35
  collection_index: collection_index,
36
+ subset_unicodes: subset_unicodes,
37
+ subset_text: subset_text,
38
+ fallback_to_original: fallback_to_original,
39
+ instance: instance,
30
40
  )
31
41
  end
32
42
 
@@ -129,6 +139,57 @@ module Fontist
129
139
  }
130
140
  end
131
141
 
142
+ # Font options a manifest entry can carry next to its styles:
143
+ #
144
+ # Roboto:
145
+ # styles: [Regular, Bold]
146
+ # format: woff2
147
+ #
148
+ # The plain form `Roboto: [Regular, Bold]` stays valid.
149
+ FONT_OPTIONS = %w[format variable_axes prefer_variable transcode_path
150
+ keep_original collection_index subset_unicodes
151
+ subset_text fallback_to_original instance].freeze
152
+
153
+ def self.from_yaml(content, options = {})
154
+ data = YAML.safe_load(content)
155
+ return super unless data.is_a?(Hash) && data.values.any?(Hash)
156
+
157
+ plain, font_options = split_font_options(data)
158
+ super(plain.to_yaml, options).tap do |manifest|
159
+ manifest.apply_font_options(font_options)
160
+ end
161
+ end
162
+
163
+ def self.split_font_options(data)
164
+ font_options = {}
165
+ plain = data.to_h do |name, value|
166
+ next [name, value] unless value.is_a?(Hash)
167
+
168
+ font_options[name] = font_options_of(name, value)
169
+ [name, value["styles"] || value[:styles]]
170
+ end
171
+ [plain, font_options]
172
+ end
173
+
174
+ def self.font_options_of(name, value)
175
+ value = value.transform_keys(&:to_s)
176
+ unknown = value.keys - FONT_OPTIONS - ["styles"]
177
+ unless unknown.empty?
178
+ raise Fontist::Errors::ManifestCouldNotBeReadError,
179
+ "Unknown option(s) for '#{name}': #{unknown.join(', ')}"
180
+ end
181
+
182
+ value.slice(*FONT_OPTIONS)
183
+ end
184
+
185
+ def apply_font_options(font_options)
186
+ Array(fonts).each do |font|
187
+ font_options.fetch(font.name, {}).each do |key, value|
188
+ font.public_send(:"#{key}=", value)
189
+ end
190
+ end
191
+ end
192
+
132
193
  def self.from_file(path, locations: false)
133
194
  Fontist.ui.debug("Manifest: #{path}")
134
195
 
@@ -159,8 +220,10 @@ module Fontist
159
220
 
160
221
  def self.from_hash(data, options = {})
161
222
  locations = options.delete(:locations) || false
223
+ data, font_options = split_font_options(data) if data.is_a?(Hash)
162
224
 
163
225
  model = super
226
+ model.apply_font_options(font_options) if font_options
164
227
 
165
228
  # Use performance optimizations for faster manifest compilation
166
229
  with_performance_optimizations do
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.27".freeze
2
+ VERSION = "3.0.29".freeze
3
3
  end
data/lib/fontist.rb CHANGED
@@ -17,6 +17,7 @@ require_relative "fontist/yaml_backend"
17
17
  module Fontist
18
18
  # Core classes
19
19
  autoload :Config, "#{__dir__}/fontist/config"
20
+ autoload :Css, "#{__dir__}/fontist/css"
20
21
  autoload :Font, "#{__dir__}/fontist/font"
21
22
  autoload :FontCollection, "#{__dir__}/fontist/font_collection"
22
23
  autoload :FontFile, "#{__dir__}/fontist/font_file"
@@ -25,6 +26,7 @@ module Fontist
25
26
  autoload :FontPath, "#{__dir__}/fontist/font_path"
26
27
  autoload :FontStyle, "#{__dir__}/fontist/font_style"
27
28
  autoload :Fontconfig, "#{__dir__}/fontist/fontconfig"
29
+ autoload :FontOperations, "#{__dir__}/fontist/font_operations"
28
30
  autoload :FormatSpec, "#{__dir__}/fontist/format_spec"
29
31
  autoload :Formula, "#{__dir__}/fontist/formula"
30
32
  autoload :FormulaCollection, "#{__dir__}/fontist/formula"
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.27
4
+ version: 3.0.29
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-02 00:00:00.000000000 Z
11
+ date: 2026-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -53,7 +53,7 @@ dependencies:
53
53
  version: '0.4'
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
- version: 0.4.9
56
+ version: 0.4.47
57
57
  type: :runtime
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
@@ -63,7 +63,7 @@ dependencies:
63
63
  version: '0.4'
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
- version: 0.4.9
66
+ version: 0.4.47
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: fuzzy_match
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -357,6 +357,10 @@ files:
357
357
  - lib/fontist/collection_file.rb
358
358
  - lib/fontist/config.rb
359
359
  - lib/fontist/config_cli.rb
360
+ - lib/fontist/css.rb
361
+ - lib/fontist/css/google_family.rb
362
+ - lib/fontist/css/local.rb
363
+ - lib/fontist/css/styles.rb
360
364
  - lib/fontist/errors.rb
361
365
  - lib/fontist/exclude.yml
362
366
  - lib/fontist/extract.rb
@@ -366,6 +370,12 @@ files:
366
370
  - lib/fontist/font_finder.rb
367
371
  - lib/fontist/font_installer.rb
368
372
  - lib/fontist/font_model.rb
373
+ - lib/fontist/font_operations.rb
374
+ - lib/fontist/font_operations/convert.rb
375
+ - lib/fontist/font_operations/extract.rb
376
+ - lib/fontist/font_operations/instance.rb
377
+ - lib/fontist/font_operations/pack.rb
378
+ - lib/fontist/font_operations/subset.rb
369
379
  - lib/fontist/font_path.rb
370
380
  - lib/fontist/font_style.rb
371
381
  - lib/fontist/fontconfig.rb
@@ -414,6 +424,7 @@ files:
414
424
  - lib/fontist/import/google/models/font_variant.rb
415
425
  - lib/fontist/import/google/models/metadata.rb
416
426
  - lib/fontist/import/google/models/source_metadata.rb
427
+ - lib/fontist/import/google/variable_font_source.rb
417
428
  - lib/fontist/import/google_fonts_importer.rb
418
429
  - lib/fontist/import/google_import.rb
419
430
  - lib/fontist/import/helpers.rb