fontist 3.0.6 → 3.0.8

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: 901ef91ebd6bdc2f41e97ecad10c4fe2e752f9b5dff6faa2d75a8d9065bea478
4
- data.tar.gz: b793fbea7abd2f98fb8334682238b7e8f34168b69509a3641196799bf1b171b0
3
+ metadata.gz: 83651351d9e3e1b81441f082b60ca4d9583a643e69dc0ce33e313ab2be4063be
4
+ data.tar.gz: c45afceafa8580fec964069c14aa4f0e7326ed9c9b4cee922425146763785cbc
5
5
  SHA512:
6
- metadata.gz: a3e4427e15dd34aecfb531bc1a25e980351c24d32515f58723b731ff0fa11467d450c3ef24feaf1661b23d5035147758d9cead9828c2a468e23ecc2230afa0bc
7
- data.tar.gz: 8e0e6912a1bf8c3cd07d0c4f7c2699450c0e5b324c65f82392a74e5c5d60147f764996eb18caf079fea5707bac10cc66398f32e510405ee2d531127d17be14f3
6
+ metadata.gz: bf4d03572d48ac3893f0f46d456fefd77887ceaa314a0cc167964642c54b72f31494fa2cbf8ae7fcbe504dac424b864d02e85522fc8694007768050baba86205
7
+ data.tar.gz: d5fd1c0d4631af5c6c7f94b22237bf2a11c777ebd7f064ac9af0282cf4ff0158ec822f3625338017900af8a8c8bfeb1276ca00c8c94ffee15dc150862cdd12dc
data/fontist.gemspec CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.add_dependency "down", "~> 5.0"
33
33
  spec.add_dependency "excavate", "~> 1.0", ">= 1.0.3"
34
- spec.add_dependency "fontisan", "~> 0.2", ">= 0.2.16"
34
+ spec.add_dependency "fontisan", "~> 0.2", ">= 0.2.17"
35
35
  spec.add_dependency "fuzzy_match", "~> 2.1"
36
36
  spec.add_dependency "git", "> 1.0"
37
37
  spec.add_dependency "json", "~> 2.0"
@@ -43,15 +43,17 @@ module Fontist
43
43
  "#{e.category}: #{e.message}"
44
44
  end.join("; ")
45
45
  # rubocop:disable Layout/LineLength
46
- raise Errors::FontFileError,
46
+ raise Errors::FontIndexabilityValidationError,
47
47
  "Font collection failed indexability validation (first font): #{error_messages}"
48
48
  # rubocop:enable Layout/LineLength
49
49
  end
50
50
 
51
51
  collection
52
+ rescue Errors::FontIndexabilityValidationError
53
+ raise
52
54
  rescue StandardError => e
53
55
  raise Errors::FontFileError,
54
- "Font collection could not be loaded: #{e.inspect}."
56
+ "Font collection could not be loaded: #{e.message}"
55
57
  end
56
58
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
57
59
 
@@ -20,6 +20,12 @@ module Fontist
20
20
 
21
21
  class FontFileError < GeneralError; end
22
22
 
23
+ # Raised when a font is structurally readable but fails the indexability
24
+ # profile (missing required tables, incomplete name records, etc.).
25
+ # Distinct from FontFileError so callers can tell "we don't recognise this
26
+ # file" apart from "this file is a font but unusable as an index entry".
27
+ class FontIndexabilityValidationError < FontFileError; end
28
+
23
29
  class FontExtractError < GeneralError; end
24
30
 
25
31
  class FontistVersionError < GeneralError; end
@@ -150,26 +150,9 @@ module Fontist
150
150
  end
151
151
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
152
152
 
153
- # rubocop:disable Metrics/MethodLength
154
153
  def detect_font_format(path)
155
- # Open file and check SFNT version to determine actual format
156
- File.open(path, "rb") do |io|
157
- signature = io.read(4)
158
- io.rewind
159
-
160
- case signature
161
- when "\x00\x01\x00\x00", "true"
162
- "ttf"
163
- when "OTTO"
164
- "otf"
165
- when "wOFF"
166
- "woff"
167
- when "wOF2"
168
- "woff2"
169
- else
170
- "unknown"
171
- end
172
- end
154
+ format = Fontisan::FontLoader.detect_format(path)
155
+ format ? format.to_s : "unknown"
173
156
  rescue StandardError
174
157
  "unknown"
175
158
  end
@@ -41,14 +41,21 @@ module Fontist
41
41
  # Get the collection, initializing it lazily if nil
42
42
  #
43
43
  # Uses lazy initialization so that the collection is created with fresh
44
- # font_paths each time it's accessed after reset_cache
44
+ # font_paths each time it's accessed after reset_cache.
45
+ #
46
+ # On cold start (no on-disk index file), eagerly build the index once
47
+ # so that `fonts` reflects an authoritative scan. This lets callers in
48
+ # read-only mode trust the cached `fonts` without re-running `build` on
49
+ # every lookup.
45
50
  #
46
51
  # @return [SystemIndexFontCollection] The collection object
47
52
  def collection
48
53
  @collection ||= SystemIndexFontCollection.from_file(
49
54
  path: index_path,
50
55
  paths_loader: -> { font_paths },
51
- )
56
+ ).tap do |coll|
57
+ coll.build unless File.exist?(index_path)
58
+ end
52
59
  end
53
60
 
54
61
  # Enable read-only mode for operations that don't need index rebuilding
@@ -215,10 +215,9 @@ location: nil)
215
215
  installed_any = true
216
216
  end
217
217
  end
218
- # Only reset fontist index (not system index) if we actually installed fonts
219
218
  if installed_any
220
- # Reset only the fontist font cache, not system fonts
221
219
  Fontist::SystemFont.reset_fontist_font_paths_cache
220
+ Fontist::SystemFont.reset_find_styles_cache
222
221
  end
223
222
  to_response
224
223
  end
@@ -1,4 +1,5 @@
1
1
  require "paint"
2
+ require "fontisan"
2
3
 
3
4
  module Fontist
4
5
  # Statistics tracking for index building (thread-safe)
@@ -229,11 +230,7 @@ module Fontist
229
230
  end
230
231
 
231
232
  def index
232
- # Fast path: if read_only mode is set, skip index_changed? check entirely.
233
- # But we still need to build on first access — treat an empty fonts list
234
- # the same as nil, since Lutaml initializes `instances :fonts` to `[]`
235
- # rather than nil when the on-disk index file is absent.
236
- if @read_only_mode && !fonts.nil? && !fonts.empty?
233
+ if @read_only_mode && !fonts.nil?
237
234
  return fonts
238
235
  end
239
236
 
@@ -634,15 +631,12 @@ spinner_index = nil)
634
631
  return if excluded?(path)
635
632
 
636
633
  gather_fonts(path)
634
+ rescue Errors::FontIndexabilityValidationError => e
635
+ stats&.record_validation_failure
636
+ print_validation_error(e, path)
637
637
  rescue Errors::FontFileError => e
638
- # Check if this is a validation failure
639
- if e.message.include?("indexability validation")
640
- stats&.record_validation_failure
641
- print_validation_error(e, path)
642
- else
643
- stats&.record_error
644
- print_recognition_error(e, path)
645
- end
638
+ stats&.record_error
639
+ print_recognition_error(e, path)
646
640
  end
647
641
 
648
642
  def excluded?(path)
@@ -653,15 +647,24 @@ spinner_index = nil)
653
647
  @excluded_fonts ||= YAML.load_file(Fontist.excluded_fonts_path)
654
648
  end
655
649
 
650
+ # Dispatch a file to the file-font or collection parser based on its actual
651
+ # magic bytes, not its extension. Some vendors ship files with misleading
652
+ # extensions (notably macOS ships a single OpenType-CFF font as
653
+ # `SauberScript.ttc` inside its private FontServices framework). Trusting
654
+ # the extension causes such files to be routed through the collection
655
+ # parser, which correctly rejects them, producing spurious "not recognized"
656
+ # warnings.
656
657
  def gather_fonts(path)
657
- case File.extname(path).gsub(/^\./, "").downcase
658
- when "ttf", "otf", "woff", "woff2"
659
- detect_file_font(path)
660
- when "ttc", "otc"
658
+ case Fontisan::FontLoader.detect_format(path)
659
+ when :ttc, :otc
661
660
  detect_collection_fonts(path)
661
+ when :ttf, :otf, :woff, :woff2
662
+ detect_file_font(path)
662
663
  else
663
664
  print_recognition_error(Errors::UnknownFontTypeError.new(path), path)
664
665
  end
666
+ rescue Errno::ENOENT, Errno::EACCES, IOError => e
667
+ print_recognition_error(Errors::FontFileError.new(e.message), path)
665
668
  end
666
669
 
667
670
  def print_validation_error(exception, path)
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.6".freeze
2
+ VERSION = "3.0.8".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.6
4
+ version: 3.0.8
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-05-18 00:00:00.000000000 Z
11
+ date: 2026-06-11 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.2'
54
54
  - - ">="
55
55
  - !ruby/object:Gem::Version
56
- version: 0.2.16
56
+ version: 0.2.17
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.2'
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
- version: 0.2.16
66
+ version: 0.2.17
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: fuzzy_match
69
69
  requirement: !ruby/object:Gem::Requirement