fontist 3.0.29 → 3.0.31
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 +4 -4
- data/fontist.gemspec +3 -2
- data/lib/fontist/cache/store.rb +13 -6
- data/lib/fontist/cli.rb +1 -1
- data/lib/fontist/font_file.rb +79 -1
- data/lib/fontist/formula.rb +16 -5
- data/lib/fontist/from_file_cache.rb +4 -0
- data/lib/fontist/helpers.rb +1 -1
- data/lib/fontist/import/convert_formulas.rb +1 -1
- data/lib/fontist/import/google/data_sources/base.rb +3 -3
- data/lib/fontist/import/google/models/font_family.rb +4 -4
- data/lib/fontist/import/helpers/hash_helper.rb +1 -1
- data/lib/fontist/import/v4_to_v5_migrator.rb +1 -1
- data/lib/fontist/index_cli.rb +1 -1
- data/lib/fontist/indexes/index_mixin.rb +4 -4
- data/lib/fontist/json.rb +76 -0
- data/lib/fontist/resource.rb +6 -1
- data/lib/fontist/system_index.rb +39 -0
- data/lib/fontist/validate_cli.rb +1 -1
- data/lib/fontist/version.rb +1 -1
- data/lib/fontist/yaml_backend.rb +18 -5
- data/lib/fontist.rb +1 -0
- metadata +5 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e9016d35c6100b87e2a7585f7193a8fbac76a1a32ab01e178a2d95bef017fa5
|
|
4
|
+
data.tar.gz: 6f02798721c34a5402bf413c9e1c003c3e968d1e01de44ae3562b726cf65bdb4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b2a062de85a9d66e0ec4e6e6d726c1d6716f8653c4ee96313eea8ba379941476064121d014851ce15bc6e7cf59ee4dcf3b77d68cd43fef7863238b9bbb55a68
|
|
7
|
+
data.tar.gz: 4d92357d6f58a7a1588bf4e6fb7793c94bbd8e47ecd1106cf9bbab87ef4321a4dd7fd172cbafab873f10c27c2563224fb7634fc0ad652baeda59e2ec433597b7
|
data/fontist.gemspec
CHANGED
|
@@ -8,8 +8,10 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Ribose Inc."]
|
|
9
9
|
spec.email = ["open.source@ribose.com"]
|
|
10
10
|
|
|
11
|
+
# rubocop:disable Layout/LineLength
|
|
11
12
|
spec.summary = "Install openly-licensed fonts on Windows, Linux and Mac!"
|
|
12
13
|
spec.description = "Install openly-licensed fonts on Windows, Linux and Mac!"
|
|
14
|
+
# rubocop:enable Layout/LineLength
|
|
13
15
|
spec.homepage = "https://github.com/fontist/fontist"
|
|
14
16
|
spec.license = "BSD-2-Clause"
|
|
15
17
|
|
|
@@ -34,11 +36,10 @@ Gem::Specification.new do |spec|
|
|
|
34
36
|
spec.add_dependency "fontisan", "~> 0.4", ">= 0.4.47"
|
|
35
37
|
spec.add_dependency "fuzzy_match", "~> 2.1"
|
|
36
38
|
spec.add_dependency "git", "~> 6.0"
|
|
37
|
-
spec.add_dependency "json", "~> 2.0"
|
|
38
39
|
spec.add_dependency "lutaml-model", "~> 0.8.0"
|
|
39
40
|
spec.add_dependency "marcel", "~> 1.0"
|
|
40
41
|
spec.add_dependency "nokogiri", "~> 1.0"
|
|
41
|
-
spec.add_dependency "octokit", "~>
|
|
42
|
+
spec.add_dependency "octokit", "~> 10.0"
|
|
42
43
|
spec.add_dependency "paint", "~> 2.3"
|
|
43
44
|
spec.add_dependency "parallel", "~> 1.24"
|
|
44
45
|
spec.add_dependency "plist", "~> 3.0"
|
data/lib/fontist/cache/store.rb
CHANGED
|
@@ -40,7 +40,7 @@ module Fontist
|
|
|
40
40
|
ensure_cache_dir
|
|
41
41
|
entry = CacheEntry.new(value, ttl)
|
|
42
42
|
write_entry(key, entry)
|
|
43
|
-
rescue TypeError, JSON::
|
|
43
|
+
rescue TypeError, Fontist::JSON::DumpError
|
|
44
44
|
# Values outside the JSON type system (Time, Date, arbitrary
|
|
45
45
|
# objects) skip the write and read back as a miss
|
|
46
46
|
false
|
|
@@ -113,9 +113,9 @@ module Fontist
|
|
|
113
113
|
def read_entry(key)
|
|
114
114
|
return nil unless File.exist?(cache_path(key))
|
|
115
115
|
|
|
116
|
-
payload = JSON.parse(File.read(cache_path(key)))
|
|
116
|
+
payload = Fontist::JSON.parse(File.read(cache_path(key)))
|
|
117
117
|
CacheEntry.new(payload["value"], expires_at: payload["expires_at"])
|
|
118
|
-
rescue JSON::
|
|
118
|
+
rescue Fontist::JSON::ParseError, TypeError, EOFError
|
|
119
119
|
# Corrupted file - delete it and return nil. This can happen
|
|
120
120
|
# on Windows when a file is read while being written, or when
|
|
121
121
|
# cache files from previous runs are corrupted
|
|
@@ -137,9 +137,16 @@ module Fontist
|
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
def entry_payload(entry)
|
|
140
|
-
JSON.dump("version" => FORMAT_VERSION,
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
Fontist::JSON.dump("version" => FORMAT_VERSION,
|
|
141
|
+
"expires_at" => entry.expires_at,
|
|
142
|
+
"value" => entry.value)
|
|
143
|
+
rescue Fontist::JSON::DumpError
|
|
144
|
+
# Values outside the JSON type system (Time, Date, arbitrary
|
|
145
|
+
# objects) degrade to their string form, keeping the cache a
|
|
146
|
+
# best-effort store instead of a source of crashes
|
|
147
|
+
Fontist::JSON.dump("version" => FORMAT_VERSION,
|
|
148
|
+
"expires_at" => entry.expires_at,
|
|
149
|
+
"value" => entry.value.to_s)
|
|
143
150
|
end
|
|
144
151
|
end
|
|
145
152
|
end
|
data/lib/fontist/cli.rb
CHANGED
data/lib/fontist/font_file.rb
CHANGED
|
@@ -63,14 +63,30 @@ module Fontist
|
|
|
63
63
|
font = validate_and_load_single_font(path)
|
|
64
64
|
end
|
|
65
65
|
names = extract_names_from_font(font)
|
|
66
|
-
#
|
|
66
|
+
# `has_table?` consults the table directory, which survives the
|
|
67
|
+
# metadata-mode load, so the fvar check is cheap; only an actual
|
|
68
|
+
# variable font pays for the follow-up full read. WOFF and WOFF2
|
|
69
|
+
# fonts from Fontisan hold no open file to close.
|
|
70
|
+
variable = font.has_table?("fvar")
|
|
67
71
|
font.close if font.respond_to?(:close)
|
|
72
|
+
names[:variable_instances] = extract_variable_instances_from_path(path) if
|
|
73
|
+
variable && names[:family_name]
|
|
68
74
|
names
|
|
69
75
|
rescue StandardError => e
|
|
70
76
|
raise_font_file_error(e)
|
|
71
77
|
end
|
|
72
78
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
73
79
|
|
|
80
|
+
def extract_variable_instances_from_path(path)
|
|
81
|
+
# Full mode: the metadata-mode loader gates fvar data out, so the
|
|
82
|
+
# instance pass needs the real table read; this runs only for
|
|
83
|
+
# fonts already identified as variable by the cheap directory check.
|
|
84
|
+
font = Fontisan::FontLoader.load(path, mode: :full, lazy: true)
|
|
85
|
+
extract_variable_instances(font)
|
|
86
|
+
ensure
|
|
87
|
+
font.close if font.respond_to?(:close)
|
|
88
|
+
end
|
|
89
|
+
|
|
74
90
|
# rubocop:disable Metrics/MethodLength
|
|
75
91
|
def validate_and_load_single_font(path)
|
|
76
92
|
# Load font first (we need to validate it, not the file path)
|
|
@@ -183,6 +199,62 @@ module Fontist
|
|
|
183
199
|
end
|
|
184
200
|
# rubocop:enable Metrics/MethodLength
|
|
185
201
|
|
|
202
|
+
# Named instances of a variable font, as `{family:, style:, full_name:}`
|
|
203
|
+
# triples. A variable file carries every named instance in one fvar
|
|
204
|
+
# table, but its name table exposes only the default instance's
|
|
205
|
+
# family, so an index built from the name table alone registers the
|
|
206
|
+
# file under a single family ("Fira Code Light") and every lookup of
|
|
207
|
+
# the other families/styles misses it. The RIBBI mapping follows the
|
|
208
|
+
# convention static families use: Regular/Bold live on the base
|
|
209
|
+
# family, every other weight becomes its own "Base Weight" family.
|
|
210
|
+
#
|
|
211
|
+
# @return [Array<Hash>] empty for a static font
|
|
212
|
+
# rubocop:disable Metrics/MethodLength
|
|
213
|
+
def extract_variable_instances(font)
|
|
214
|
+
return [] unless font.has_table?("fvar")
|
|
215
|
+
|
|
216
|
+
fvar = font.table("fvar")
|
|
217
|
+
name_table = font.table(Fontisan::Constants::NAME_TAG)
|
|
218
|
+
return [] unless name_table
|
|
219
|
+
|
|
220
|
+
base = name_table.english_name(Fontisan::Tables::Name::PREFERRED_FAMILY) ||
|
|
221
|
+
extract_any_name(name_table, Fontisan::Tables::Name::PREFERRED_FAMILY) ||
|
|
222
|
+
name_table.english_name(Fontisan::Tables::Name::FAMILY)
|
|
223
|
+
return [] unless base
|
|
224
|
+
|
|
225
|
+
ribbi = ["regular", "bold", "italic", "bold italic"]
|
|
226
|
+
|
|
227
|
+
fvar.instances.filter_map do |instance|
|
|
228
|
+
# fvar yields plain hashes ({name_id:, flags:, coordinates:,
|
|
229
|
+
# postscript_name_id:}), not message objects.
|
|
230
|
+
style = name_table.english_name(instance[:name_id]) ||
|
|
231
|
+
extract_any_name(name_table, instance[:name_id])
|
|
232
|
+
next unless style
|
|
233
|
+
|
|
234
|
+
if ribbi.include?(style.downcase)
|
|
235
|
+
family = base
|
|
236
|
+
type = style
|
|
237
|
+
full_name = "#{family} #{style}"
|
|
238
|
+
else
|
|
239
|
+
family = "#{base} #{style}"
|
|
240
|
+
type = "Regular"
|
|
241
|
+
full_name = family
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
family: family,
|
|
246
|
+
style: type,
|
|
247
|
+
full_name: full_name,
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
rescue StandardError => e
|
|
251
|
+
Fontist.ui.debug(
|
|
252
|
+
"Could not read variable instances: #{e.message}",
|
|
253
|
+
)
|
|
254
|
+
[]
|
|
255
|
+
end
|
|
256
|
+
# rubocop:enable Metrics/MethodLength
|
|
257
|
+
|
|
186
258
|
# Extract any available name for a given name ID by manually decoding the name record
|
|
187
259
|
def extract_any_name(name_table, name_id)
|
|
188
260
|
# Find any name record with the matching name_id
|
|
@@ -256,5 +328,11 @@ module Fontist
|
|
|
256
328
|
def preferred_subfamily_name
|
|
257
329
|
@info[:preferred_subfamily]
|
|
258
330
|
end
|
|
331
|
+
|
|
332
|
+
# Named instances of a variable font (`{family:, style:, full_name:}`
|
|
333
|
+
# triples); empty for a static font.
|
|
334
|
+
def variable_instances
|
|
335
|
+
@info[:variable_instances] || []
|
|
336
|
+
end
|
|
259
337
|
end
|
|
260
338
|
end
|
data/lib/fontist/formula.rb
CHANGED
|
@@ -106,10 +106,16 @@ module Fontist
|
|
|
106
106
|
# Parsing thousands of formula files dominates index rebuilds;
|
|
107
107
|
# worker threads overlap the libyaml C parse, which releases
|
|
108
108
|
# the GVL. The pool is capped like every other fontist pool, so
|
|
109
|
-
# worker counts never scale with host core counts
|
|
109
|
+
# worker counts never scale with host core counts.
|
|
110
|
+
#
|
|
111
|
+
# The parses bypass the shared parse cache: a corpus sweep never
|
|
112
|
+
# hits it (the LRU holds 64 entries against thousands of files),
|
|
113
|
+
# so caching would only evict entries that lookups re-use and
|
|
114
|
+
# pin dozens of parsed formulas in long-lived host processes
|
|
115
|
+
# after the sweep ends
|
|
110
116
|
workers = [Parallel.processor_count, 8].min
|
|
111
117
|
formulas = Parallel.map(paths, in_threads: workers) do |path|
|
|
112
|
-
Formula.from_file(path)
|
|
118
|
+
Formula.from_file(path, cache: false)
|
|
113
119
|
end.compact
|
|
114
120
|
|
|
115
121
|
FormulaCollection.new(formulas)
|
|
@@ -181,7 +187,7 @@ module Fontist
|
|
|
181
187
|
find_by_key(key)
|
|
182
188
|
end
|
|
183
189
|
|
|
184
|
-
def from_file(path)
|
|
190
|
+
def from_file(path, cache: true)
|
|
185
191
|
unless File.exist?(path)
|
|
186
192
|
raise Fontist::Errors::FormulaNotFoundError,
|
|
187
193
|
"Formula file not found: #{path}"
|
|
@@ -192,8 +198,13 @@ module Fontist
|
|
|
192
198
|
# Parsing a formula costs ~10-20ms and the same formula is
|
|
193
199
|
# re-read on every lookup, so the parsed instance is memoized
|
|
194
200
|
# (mtime-validated) and shared between callers; treat as
|
|
195
|
-
# read-only
|
|
196
|
-
|
|
201
|
+
# read-only. Callers that sweep the whole corpus pass
|
|
202
|
+
# cache: false, since their parses would only thrash the cache
|
|
203
|
+
formula = if cache
|
|
204
|
+
Fontist::FromFileCache.memoize(path) { from_yaml(content) }
|
|
205
|
+
else
|
|
206
|
+
from_yaml(content)
|
|
207
|
+
end
|
|
197
208
|
|
|
198
209
|
formula.tap do |f|
|
|
199
210
|
f.path = path
|
data/lib/fontist/helpers.rb
CHANGED
|
@@ -58,8 +58,8 @@ module Fontist
|
|
|
58
58
|
raise "API request failed: #{response.code} #{response.message}"
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
JSON.parse(response.body)
|
|
62
|
-
rescue JSON::
|
|
61
|
+
Fontist::JSON.parse(response.body)
|
|
62
|
+
rescue Fontist::JSON::ParseError => e
|
|
63
63
|
raise "Failed to parse API response: #{e.message}"
|
|
64
64
|
rescue StandardError => e
|
|
65
65
|
raise "Failed to fetch from API: #{e.message}"
|
|
@@ -90,7 +90,7 @@ module Fontist
|
|
|
90
90
|
# @param item [Hash] the raw item data
|
|
91
91
|
# @return [FontFamily] the parsed font family
|
|
92
92
|
def parse_item(item)
|
|
93
|
-
Models::FontFamily.from_json(item
|
|
93
|
+
Models::FontFamily.from_json(Fontist::JSON.dump(item))
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -59,15 +59,15 @@ module Fontist
|
|
|
59
59
|
|
|
60
60
|
# Try to parse as JSON first
|
|
61
61
|
begin
|
|
62
|
-
JSON.parse(files_data)
|
|
63
|
-
rescue JSON::
|
|
62
|
+
Fontist::JSON.parse(files_data)
|
|
63
|
+
rescue Fontist::JSON::ParseError, TypeError
|
|
64
64
|
# If it fails, convert Ruby hash syntax to JSON and parse
|
|
65
65
|
if files_data.is_a?(String)
|
|
66
66
|
# Convert Ruby hash syntax (=>) to JSON syntax (:)
|
|
67
67
|
json_str = files_data.gsub("=>", ":")
|
|
68
68
|
begin
|
|
69
|
-
return JSON.parse(json_str)
|
|
70
|
-
rescue JSON::
|
|
69
|
+
return Fontist::JSON.parse(json_str)
|
|
70
|
+
rescue Fontist::JSON::ParseError
|
|
71
71
|
{}
|
|
72
72
|
end
|
|
73
73
|
end
|
|
@@ -95,7 +95,7 @@ module Fontist
|
|
|
95
95
|
def load_yaml_without_aliases(path)
|
|
96
96
|
data = YAML.safe_load(File.read(path), aliases: true,
|
|
97
97
|
permitted_classes: [Date])
|
|
98
|
-
JSON.parse(JSON.
|
|
98
|
+
Fontist::JSON.parse(Fontist::JSON.dump(data))
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def file_has_yaml_aliases?(path)
|
data/lib/fontist/index_cli.rb
CHANGED
|
@@ -103,13 +103,13 @@ module Fontist
|
|
|
103
103
|
return entries if entries
|
|
104
104
|
|
|
105
105
|
parse_sidecar_entries(sidecar_path)
|
|
106
|
-
rescue JSON::
|
|
106
|
+
rescue Fontist::JSON::ParseError, TypeError
|
|
107
107
|
nil
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def rebuild_sidecar_entries
|
|
111
111
|
entries_hash = from_file(path).entries_hash
|
|
112
|
-
File.write("#{path}.json", JSON.dump("entries" => entries_hash))
|
|
112
|
+
File.write("#{path}.json", Fontist::JSON.dump("entries" => entries_hash))
|
|
113
113
|
entries_hash
|
|
114
114
|
end
|
|
115
115
|
|
|
@@ -135,7 +135,7 @@ module Fontist
|
|
|
135
135
|
# as read-only
|
|
136
136
|
def parse_sidecar_entries(sidecar_path)
|
|
137
137
|
mtime = File.mtime(sidecar_path)
|
|
138
|
-
entries = JSON.parse(File.read(sidecar_path))["entries"]
|
|
138
|
+
entries = Fontist::JSON.parse(File.read(sidecar_path))["entries"]
|
|
139
139
|
|
|
140
140
|
sidecar_cache_mutex.synchronize do
|
|
141
141
|
sidecar_cache[sidecar_path] = { mtime: mtime, entries: entries }
|
|
@@ -244,7 +244,7 @@ module Fontist
|
|
|
244
244
|
|
|
245
245
|
def write_lookup_sidecar(file_path)
|
|
246
246
|
File.write("#{file_path}.json",
|
|
247
|
-
JSON.dump("entries" => entries_hash))
|
|
247
|
+
Fontist::JSON.dump("entries" => entries_hash))
|
|
248
248
|
end
|
|
249
249
|
|
|
250
250
|
# key -> formula paths, in the sidecar's JSON-friendly shape
|
data/lib/fontist/json.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "yeptris"
|
|
2
|
+
require "yeptris/json"
|
|
3
|
+
|
|
4
|
+
module Fontist
|
|
5
|
+
# JSON encoding and decoding through yeptris's native engine, replacing
|
|
6
|
+
# the json gem as fontist's JSON backend. The yeptris parser and
|
|
7
|
+
# generator produce and accept the same plain Ruby structures as the
|
|
8
|
+
# json gem (string keys, arrays, scalars), so this module is a
|
|
9
|
+
# drop-in surface; call sites rescue the module's error classes
|
|
10
|
+
# instead of JSON::ParserError and JSON::GeneratorError.
|
|
11
|
+
module JSON
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
class ParseError < Error; end
|
|
14
|
+
class DumpError < Error; end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def parse(source)
|
|
18
|
+
unless source.is_a?(String)
|
|
19
|
+
raise ParseError,
|
|
20
|
+
"cannot parse #{source.class}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Yeptris::JSON.load(source)
|
|
24
|
+
rescue Yeptris::Error, TypeError => e
|
|
25
|
+
raise ParseError, e.message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dump(object)
|
|
29
|
+
Yeptris::JSON.dump(object)
|
|
30
|
+
rescue Yeptris::Error, TypeError => e
|
|
31
|
+
raise DumpError, e.message
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Two-space-indented JSON for human-facing CLI output; yeptris's
|
|
35
|
+
# generator is compact-only, so the indentation is applied here
|
|
36
|
+
def pretty(object)
|
|
37
|
+
pretty_value(object, 0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def pretty_value(value, depth)
|
|
43
|
+
case value
|
|
44
|
+
when Hash then pretty_object(value, depth)
|
|
45
|
+
when Array then pretty_array(value, depth)
|
|
46
|
+
else dump(value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def pretty_object(hash, depth)
|
|
51
|
+
return "{}" if hash.empty?
|
|
52
|
+
|
|
53
|
+
inner = hash.map do |key, value|
|
|
54
|
+
"#{indent(depth + 1)}#{dump(key.to_s)}: #{pretty_value(value,
|
|
55
|
+
depth + 1)}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
"{\n#{inner.join(",\n")}\n#{indent(depth)}}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def pretty_array(array, depth)
|
|
62
|
+
return "[]" if array.empty?
|
|
63
|
+
|
|
64
|
+
inner = array.map do |value|
|
|
65
|
+
"#{indent(depth + 1)}#{pretty_value(value, depth + 1)}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
"[\n#{inner.join(",\n")}\n#{indent(depth)}]"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def indent(depth)
|
|
72
|
+
" " * depth
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/fontist/resource.rb
CHANGED
|
@@ -6,7 +6,12 @@ module Fontist
|
|
|
6
6
|
attribute :name, :string
|
|
7
7
|
attribute :source, :string
|
|
8
8
|
attribute :urls, :string, collection: true
|
|
9
|
-
|
|
9
|
+
# Declared scalar: the corpus writes one digest per resource almost
|
|
10
|
+
# everywhere, and consumers render it verbatim. A handful of
|
|
11
|
+
# resources pair one digest with each mirror URL and write a list;
|
|
12
|
+
# a scalar-typed attribute keeps both forms as written instead of
|
|
13
|
+
# normalizing every single digest into a one-element Array.
|
|
14
|
+
attribute :sha256, :string
|
|
10
15
|
attribute :file_size, :integer
|
|
11
16
|
attribute :family, :string
|
|
12
17
|
attribute :files, :string, collection: true
|
data/lib/fontist/system_index.rb
CHANGED
|
@@ -729,9 +729,48 @@ spinner_index = nil)
|
|
|
729
729
|
def detect_file_font(path)
|
|
730
730
|
font_file = FontFile.from_path(path)
|
|
731
731
|
|
|
732
|
+
entries = variable_font_entries(font_file, path)
|
|
733
|
+
return entries unless entries.empty?
|
|
734
|
+
|
|
732
735
|
parse_font(font_file, path)
|
|
733
736
|
end
|
|
734
737
|
|
|
738
|
+
# One index entry per named instance of a variable font: a variable
|
|
739
|
+
# file answers every lookup only if each family/style pair it carries
|
|
740
|
+
# is indexed separately — the name table alone registers just the
|
|
741
|
+
# default instance ("Fira Code Light"), leaving find_styles blind to
|
|
742
|
+
# the rest of the set.
|
|
743
|
+
def variable_font_entries(font_file, path)
|
|
744
|
+
instances = font_file.variable_instances
|
|
745
|
+
return [] if instances.empty?
|
|
746
|
+
|
|
747
|
+
file_size = begin
|
|
748
|
+
File.size(path)
|
|
749
|
+
rescue StandardError
|
|
750
|
+
0
|
|
751
|
+
end
|
|
752
|
+
file_mtime = begin
|
|
753
|
+
File.mtime(path).to_i
|
|
754
|
+
rescue StandardError
|
|
755
|
+
0
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
base = font_file.preferred_family_name || font_file.family
|
|
759
|
+
instances.map do |instance|
|
|
760
|
+
family = instance[:family]
|
|
761
|
+
SystemIndexFont.new(
|
|
762
|
+
path: path,
|
|
763
|
+
full_name: instance[:full_name],
|
|
764
|
+
family_name: family,
|
|
765
|
+
subfamily: instance[:style],
|
|
766
|
+
preferred_family_name: base,
|
|
767
|
+
preferred_subfamily_name: instance[:style],
|
|
768
|
+
file_size: file_size,
|
|
769
|
+
file_mtime: file_mtime,
|
|
770
|
+
)
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
|
|
735
774
|
def detect_collection_fonts(path)
|
|
736
775
|
CollectionFile.from_path(path) do |collection|
|
|
737
776
|
collection.map do |font_file|
|
data/lib/fontist/validate_cli.rb
CHANGED
|
@@ -189,7 +189,7 @@ module Fontist
|
|
|
189
189
|
|
|
190
190
|
def output_json_report(report, output_file)
|
|
191
191
|
require "json"
|
|
192
|
-
json_content = JSON.
|
|
192
|
+
json_content = Fontist::JSON.pretty(report.to_hash)
|
|
193
193
|
|
|
194
194
|
if output_file
|
|
195
195
|
File.write(output_file, json_content)
|
data/lib/fontist/version.rb
CHANGED
data/lib/fontist/yaml_backend.rb
CHANGED
|
@@ -14,13 +14,26 @@ module Fontist
|
|
|
14
14
|
ENABLED_VALUE = "yeptris"
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
#
|
|
18
|
-
#
|
|
17
|
+
# Pins the YAML adapter explicitly: lutaml-model auto-selects
|
|
18
|
+
# yeptris for a format whenever the yeptris gem is in the bundle,
|
|
19
|
+
# which would silently switch YAML parsing and dumping (and its
|
|
20
|
+
# line-wrap behavior) for every model in the process. The yeptris
|
|
21
|
+
# YAML engine activates only when explicitly enabled with
|
|
22
|
+
# FONTIST_YAML=yeptris; otherwise the standard Psych engine is
|
|
23
|
+
# pinned, because the yeptris x86_64-linux and Windows builds
|
|
24
|
+
# currently mis-parse some plain scalars as single-element Arrays
|
|
25
|
+
# (leptris/yeptris#863). Flip the default once that parity fix
|
|
26
|
+
# lands.
|
|
27
|
+
#
|
|
28
|
+
# Returns the activated adapter class.
|
|
19
29
|
def activate!
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
adapter =
|
|
31
|
+
if enabled? && yeptris_available?
|
|
32
|
+
Yaml::YeptrisAdapter
|
|
33
|
+
else
|
|
34
|
+
Lutaml::Yaml::Adapter::StandardAdapter
|
|
35
|
+
end
|
|
22
36
|
|
|
23
|
-
adapter = Yaml::YeptrisAdapter
|
|
24
37
|
Lutaml::Model::AdapterResolver.set_adapter_class(:yaml, adapter)
|
|
25
38
|
|
|
26
39
|
adapter
|
data/lib/fontist.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Fontist
|
|
|
18
18
|
# Core classes
|
|
19
19
|
autoload :Config, "#{__dir__}/fontist/config"
|
|
20
20
|
autoload :Css, "#{__dir__}/fontist/css"
|
|
21
|
+
autoload :JSON, "#{__dir__}/fontist/json"
|
|
21
22
|
autoload :Font, "#{__dir__}/fontist/font"
|
|
22
23
|
autoload :FontCollection, "#{__dir__}/fontist/font_collection"
|
|
23
24
|
autoload :FontFile, "#{__dir__}/fontist/font_file"
|
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.
|
|
4
|
+
version: 3.0.31
|
|
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-
|
|
11
|
+
date: 2026-11-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: down
|
|
@@ -92,20 +92,6 @@ dependencies:
|
|
|
92
92
|
- - "~>"
|
|
93
93
|
- !ruby/object:Gem::Version
|
|
94
94
|
version: '6.0'
|
|
95
|
-
- !ruby/object:Gem::Dependency
|
|
96
|
-
name: json
|
|
97
|
-
requirement: !ruby/object:Gem::Requirement
|
|
98
|
-
requirements:
|
|
99
|
-
- - "~>"
|
|
100
|
-
- !ruby/object:Gem::Version
|
|
101
|
-
version: '2.0'
|
|
102
|
-
type: :runtime
|
|
103
|
-
prerelease: false
|
|
104
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
-
requirements:
|
|
106
|
-
- - "~>"
|
|
107
|
-
- !ruby/object:Gem::Version
|
|
108
|
-
version: '2.0'
|
|
109
95
|
- !ruby/object:Gem::Dependency
|
|
110
96
|
name: lutaml-model
|
|
111
97
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -154,14 +140,14 @@ dependencies:
|
|
|
154
140
|
requirements:
|
|
155
141
|
- - "~>"
|
|
156
142
|
- !ruby/object:Gem::Version
|
|
157
|
-
version: '
|
|
143
|
+
version: '10.0'
|
|
158
144
|
type: :runtime
|
|
159
145
|
prerelease: false
|
|
160
146
|
version_requirements: !ruby/object:Gem::Requirement
|
|
161
147
|
requirements:
|
|
162
148
|
- - "~>"
|
|
163
149
|
- !ruby/object:Gem::Version
|
|
164
|
-
version: '
|
|
150
|
+
version: '10.0'
|
|
165
151
|
- !ruby/object:Gem::Dependency
|
|
166
152
|
name: paint
|
|
167
153
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -473,6 +459,7 @@ files:
|
|
|
473
459
|
- lib/fontist/install_locations/fontist_location.rb
|
|
474
460
|
- lib/fontist/install_locations/system_location.rb
|
|
475
461
|
- lib/fontist/install_locations/user_location.rb
|
|
462
|
+
- lib/fontist/json.rb
|
|
476
463
|
- lib/fontist/macos.rb
|
|
477
464
|
- lib/fontist/macos/catalog.rb
|
|
478
465
|
- lib/fontist/macos/catalog/asset.rb
|