fontist 3.0.30 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d858c57f16d09454000112d1047f120b1a752a76331e3788f65cb441fe7a668c
4
- data.tar.gz: 60c5f0e33198e6b7b8ad24db123280a5fff7c4eaf8b56581d3ab8c9108114b44
3
+ metadata.gz: 2e9016d35c6100b87e2a7585f7193a8fbac76a1a32ab01e178a2d95bef017fa5
4
+ data.tar.gz: 6f02798721c34a5402bf413c9e1c003c3e968d1e01de44ae3562b726cf65bdb4
5
5
  SHA512:
6
- metadata.gz: 581ebfd99f20a966cea6574294b4a3de879cdcea6051f916645e84ac9bd5e9ee16d4d0bfcbbd82f0291b6d6ae3e72dcf1c5524d02f6e1d67d3fbcfd880b03d73
7
- data.tar.gz: 7188a89be063579e76ffd351c5ddd0c7296836a4c6162c1974ca54f79ca5cddd6134a71b3d48d04455fbc1ac8debbe1e6f2922d414a16aea2e85c83a62742fc2
6
+ metadata.gz: 7b2a062de85a9d66e0ec4e6e6d726c1d6716f8653c4ee96313eea8ba379941476064121d014851ce15bc6e7cf59ee4dcf3b77d68cd43fef7863238b9bbb55a68
7
+ data.tar.gz: 4d92357d6f58a7a1588bf4e6fb7793c94bbd8e47ecd1106cf9bbab87ef4321a4dd7fd172cbafab873f10c27c2563224fb7634fc0ad652baeda59e2ec433597b7
@@ -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
- # WOFF and WOFF2 fonts from Fontisan hold no open file to close.
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
@@ -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|
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.30".freeze
2
+ VERSION = "3.0.31".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.30
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-03 00:00:00.000000000 Z
11
+ date: 2026-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down