fontist 3.0.37 → 3.0.38

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: 85b1409d941968bcf69c2058ab3e3ffac4138279bf6c262d670206590dd8e557
4
- data.tar.gz: f025af9d9214504168b1612f7f42f3142afdadb9112e9283022fe92378665000
3
+ metadata.gz: 3ec4754ec2eb25b7e106ecd9254296d72900bf8df94b59d1707099f1585333c6
4
+ data.tar.gz: f41467d946e5513fb8f297d52984c792ffceb6ee20f4400b238ac786c844960f
5
5
  SHA512:
6
- metadata.gz: 6c13e6cca540bd6f5e2ed3bd203bad3b4dad7db56824d34e36b511331b6a9bd5b5178ecf7fc6717318d54981950c33536ae3dfc6eafc82e4ceaf771f3b08fbdb
7
- data.tar.gz: f1a3542fcda412f62e763cb38d9484028ddc65585eba3d6c9cb982eea8ef80656672769fc898a5b4266ef7419adadcf7e592200f4d7c8c9845505ddf13ec4f58
6
+ metadata.gz: 4a89a610ffd86cf8c4eaef413cb58e4837496b3eb637cb218ba9ba043f61c9f72e4b6e637c34d03c50acc539006fbefd02db5be870a35627758e789b3b583977
7
+ data.tar.gz: 1070a5850da3b68d883b9c578718d072ccad5c249df5f580e1510a9a2019313f76f645f063f6b39186a45deb9391d4c9a6cd501135ac54698da579ffbf5f68cb
@@ -55,8 +55,11 @@ jobs:
55
55
  - id: tag
56
56
  env:
57
57
  GH_TOKEN: ${{ github.token }}
58
- CALLED: ${{ github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' && inputs.publish }}
59
- INPUT_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || '' }}
58
+ # A called workflow sees the caller's event (release.yml runs on
59
+ # workflow_dispatch), so tell the triggers apart by their inputs:
60
+ # only workflow_call has `publish`, only workflow_dispatch has `tag`.
61
+ CALLED: ${{ inputs.publish == true }}
62
+ INPUT_TAG: ${{ inputs.tag || '' }}
60
63
  run: |
61
64
  if [ "$CALLED" = true ]; then
62
65
  tag="v$(sed -n 's/.*VERSION = "\(.*\)".*/\1/p' lib/fontist/version.rb)"
@@ -183,7 +186,10 @@ jobs:
183
186
  matrix:
184
187
  # Distros whose git meets the git gem's >= 2.43; older ones refuse
185
188
  # the package on its git dependency.
186
- image: [ 'ubuntu:24.04', 'debian:13', 'fedora:latest', 'rockylinux:9' ]
189
+ # ubuntu:22.04 ships an older git and first gets a newer one the
190
+ # documented way (git-prep.sh, see README.adoc). debian:12 has no leg:
191
+ # bookworm-backports carries no git package, so no >= 2.43 exists there.
192
+ image: [ 'ubuntu:24.04', 'debian:13', 'fedora:latest', 'rockylinux:9', 'ubuntu:22.04' ]
187
193
  arch: [ amd64 ]
188
194
  runner: [ ubuntu-24.04 ]
189
195
  include:
@@ -207,13 +213,46 @@ jobs:
207
213
  fi
208
214
  ls -l pkg
209
215
 
216
+ - name: Get git 2.43 or newer
217
+ if: matrix.image == 'ubuntu:22.04'
218
+ run: script/tebako/git-prep.sh
219
+
210
220
  - name: Install, use, remove
211
221
  run: script/tebako/package-smoke.sh pkg
212
222
 
223
+ # Test-only: the packed executables again, on runners other than the ones
224
+ # that packed them. No macOS leg: macos-14 is not offered to this repo
225
+ # (run 42989415969: "The configuration 'macos-14-arm64-us-default' is not
226
+ # supported") and macos-13 is a label GitHub has retired; the macOS
227
+ # binaries are smoke-tested on their packing runners in `pack`.
228
+ binary-smoke:
229
+ name: run ${{ matrix.name }} on ${{ matrix.os }}
230
+ needs: pack
231
+ runs-on: ${{ matrix.os }}
232
+ strategy:
233
+ fail-fast: false
234
+ matrix:
235
+ include:
236
+ - { os: ubuntu-24.04, name: linux-x86_64 }
237
+ steps:
238
+ - uses: actions/checkout@v4
239
+
240
+ - uses: actions/download-artifact@v4
241
+ with:
242
+ name: fontist-${{ matrix.name }}
243
+ path: out
244
+
245
+ - name: Smoke test
246
+ run: |
247
+ chmod +x "out/fontist-${{ matrix.name }}"
248
+ git_dir="$(dirname "$(command -v git)")"
249
+ env -i HOME="$HOME" PATH="$git_dir:/usr/bin:/bin:/usr/sbin:/sbin" \
250
+ script/tebako/smoke.sh "out/fontist-${{ matrix.name }}"
251
+
213
252
  publish:
214
253
  name: attach to release
215
254
  if: needs.resolve.outputs.tag != ''
216
- needs: [ resolve, pack, package-smoke ]
255
+ needs: [ resolve, pack, package-smoke, binary-smoke ]
217
256
  runs-on: ubuntu-24.04
218
257
  permissions:
219
258
  contents: write
data/Gemfile CHANGED
@@ -18,11 +18,10 @@ gem "simplecov"
18
18
  gem "vcr"
19
19
  gem "webmock"
20
20
 
21
- # Optional YAML engine (consumer-side dependency; needs Ruby >= 3.0,
22
- # which is above fontist's gemspec floor, so it stays out of the
23
- # gemspec). Activated with FONTIST_YAML=yeptris; off by default
24
- # pending leptris/yeptris#863 (linux scalar-parity fix).
25
- gem "yeptris", require: false
21
+ # yeptris is a runtime dependency (see the gemspec): the JSON backend in
22
+ # lib/fontist/json.rb loads it unconditionally. The optional YAML engine
23
+ # activated with FONTIST_YAML=yeptris stays off by default pending
24
+ # leptris/yeptris#863 (linux scalar-parity fix).
26
25
 
27
26
  # Profiling tools (development only)
28
27
  # gem "ruby-prof", "~> 1.7"
data/README.adoc CHANGED
@@ -78,8 +78,16 @@ macOS is older, so install a current one first (for example
78
78
  `brew install git`).
79
79
 
80
80
  On Debian and Ubuntu, install the `.deb` package. On Fedora and RHEL, install
81
- the `.rpm` package. Both put `fontist` in `/usr/bin` and depend on git 2.43 or
82
- newer, which Ubuntu 24.04, Debian 13, Fedora and RHEL 9 provide.
81
+ the `.rpm` package. Both install the executable to `/usr/lib/fontist/fontist` and link it from
82
+ `/usr/local/bin/fontist` (the link is removed with the package). They depend on
83
+ git 2.43 or newer, which Ubuntu 24.04, Debian 13, Fedora and RHEL 9 provide.
84
+
85
+ Ubuntu 22.04 ships an older git, so get a newer one once before installing the
86
+ `.deb`: `sudo add-apt-repository ppa:git-core/ppa && sudo apt-get update && sudo apt-get install git`.
87
+ Debian 12 has no git 2.43 or newer in its archive or in bookworm-backports, so
88
+ the package cannot be installed there.
89
+
90
+ `script/tebako/git-prep.sh` does exactly this and is what CI runs.
83
91
 
84
92
  [source,sh]
85
93
  ----
data/fontist.gemspec CHANGED
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
 
34
34
  spec.add_dependency "down", "~> 5.0"
35
35
  spec.add_dependency "excavate", "~> 1.0", ">= 1.0.3"
36
- spec.add_dependency "fontisan", "~> 0.4", ">= 0.4.47"
36
+ spec.add_dependency "fontisan", "~> 0.4", ">= 0.4.48"
37
37
  spec.add_dependency "fuzzy_match", "~> 2.1"
38
38
  spec.add_dependency "git", "~> 6.0"
39
39
  spec.add_dependency "lutaml-model", "~> 0.8.164"
@@ -46,4 +46,5 @@ Gem::Specification.new do |spec|
46
46
  spec.add_dependency "socksify", "~> 1.7"
47
47
  spec.add_dependency "thor", "~> 1.4"
48
48
  spec.add_dependency "unibuf", "~> 0.1"
49
+ spec.add_dependency "yeptris", "~> 0.6.55"
49
50
  end
@@ -39,9 +39,7 @@ module Fontist
39
39
 
40
40
  def run(source, target, output)
41
41
  FileUtils.mkdir_p(File.dirname(output))
42
- Fontisan::Commands::ConvertCommand.new(
43
- source, to: target, output: output, quiet: true
44
- ).run
42
+ Fontisan::Reference.convert(source, to: target, output: output)
45
43
  end
46
44
 
47
45
  def verify!(path, target)
@@ -18,9 +18,9 @@ module Fontist
18
18
  end
19
19
 
20
20
  def unpack(collection_path, index, output_dir)
21
- Fontisan::Commands::UnpackCommand.new(
22
- collection_path, output_dir: output_dir, font_index: index
23
- ).run[:extracted_files].first
21
+ Fontisan::Reference.extract(
22
+ collection_path, index: index, output_dir: output_dir
23
+ )
24
24
  end
25
25
 
26
26
  def check_index!(path, index)
@@ -59,13 +59,17 @@ module Fontist
59
59
  "#{File.basename(source)} is not a variable font"
60
60
  end
61
61
 
62
- instancer = Fontisan::Variable::Instancer.new(font)
63
- validate!(instancer, coords, File.basename(source))
62
+ validate!(Fontisan::Variable::Instancer.new(font), coords,
63
+ File.basename(source))
64
64
  FileUtils.mkdir_p(File.dirname(output))
65
- File.binwrite(output, instancer.instance(coords))
65
+ File.binwrite(output, instance_bytes(source, coords))
66
66
  output
67
67
  end
68
68
 
69
+ def instance_bytes(source, coords)
70
+ Fontisan::Reference.instance(source, coords: coords)
71
+ end
72
+
69
73
  def validate!(instancer, coords, name)
70
74
  axes = instancer.axes.to_h { |axis| [axis.tag, axis] }
71
75
  coords.each do |tag, value|
@@ -4,6 +4,7 @@ module Fontist
4
4
  module FontOperations
5
5
  # Keeps only the glyphs for the given characters.
6
6
  module Subset
7
+ NO_GLYPHS = "None of the requested characters exist in".freeze
7
8
  UNICODE_PART = /\A(u\+)?\h+(-(u\+)?\h+)?\z/i
8
9
 
9
10
  module_function
@@ -28,21 +29,16 @@ module Fontist
28
29
  end
29
30
 
30
31
  def write(source, output, codepoints)
31
- font = Fontisan::FontLoader.load(source)
32
- gids = glyph_ids(font, codepoints, File.basename(source))
33
- options = Fontisan::Subset::Options.new(profile: "web")
34
- binary = Fontisan::Subset::Builder.new(font, gids, options).build
32
+ binary = Fontisan::Reference.subset(
33
+ source, unicodes: codepoints, profile: "web"
34
+ )
35
35
  FileUtils.mkdir_p(File.dirname(output))
36
36
  File.binwrite(output, binary)
37
37
  output
38
- end
39
-
40
- def glyph_ids(font, codepoints, name)
41
- mappings = font.table("cmap").unicode_mappings
42
- gids = codepoints.uniq.filter_map { |cp| mappings[cp] }.uniq.sort
43
- return gids if gids.any?
38
+ rescue Fontisan::Error => e
39
+ raise unless e.message.start_with?(NO_GLYPHS)
44
40
 
45
- raise Error, "None of the requested characters exist in #{name}"
41
+ raise Error, e.message
46
42
  end
47
43
 
48
44
  # "U+0020-007E,U+00E9" => [32..126, 233].flatten
@@ -0,0 +1,82 @@
1
+ require "fontisan"
2
+
3
+ module Fontist
4
+ # Turns `Fontisan.scan` rows into the font objects the indexes already
5
+ # consume (`FontFile` for single fonts, a `FontFile` over the english-only
6
+ # names for collection faces), so one batch scan can replace the per-path
7
+ # `FontFile.from_path` / `CollectionFile.from_path` loads.
8
+ #
9
+ # Only rows that carry no error are usable; anything else is left to the
10
+ # per-file path so its message stays byte-identical.
11
+ module ScanRowFonts
12
+ SINGLE_FORMATS = %w[ttf otf woff woff2].freeze
13
+ COLLECTION_FORMATS = %w[ttc otc].freeze
14
+
15
+ class << self
16
+ def usable?(rows)
17
+ return false if rows.nil? || rows.empty?
18
+
19
+ if rows.first["is_collection"]
20
+ collection_usable?(rows)
21
+ else
22
+ single_usable?(rows)
23
+ end
24
+ end
25
+
26
+ def file_font(row)
27
+ report_validation(row)
28
+ info = symbolize(row["names"] || {})
29
+ if row["has_fvar"] && info[:family_name]
30
+ info[:variable_instances] =
31
+ row["variable_instances"].map { |i| symbolize(i) }
32
+ end
33
+ FontFile.new(info)
34
+ end
35
+
36
+ def collection_face(row)
37
+ FontFile.new(symbolize(row["english_names"] || {}))
38
+ end
39
+
40
+ def warn_file_extension(path, row)
41
+ FontFile.check_extension_warning(path, row["is_collection"])
42
+ end
43
+
44
+ def warn_collection_extension(path)
45
+ CollectionFile.send(:check_extension_warning, path)
46
+ end
47
+
48
+ private
49
+
50
+ def collection_usable?(rows)
51
+ COLLECTION_FORMATS.include?(rows.first["format"]) &&
52
+ rows.all? { |row| collection_row_ok?(row) }
53
+ end
54
+
55
+ def single_usable?(rows)
56
+ row = rows.first
57
+ rows.size == 1 && SINGLE_FORMATS.include?(row["format"]) &&
58
+ row["errors"]["font_file"].nil? && !row["names"].nil?
59
+ end
60
+
61
+ def symbolize(hash)
62
+ hash.transform_keys(&:to_sym)
63
+ end
64
+
65
+ def collection_row_ok?(row)
66
+ row["is_collection"] && row["num_faces"].to_i.positive? &&
67
+ row["errors"]["collection_file"].nil? &&
68
+ row["errors"]["collection_member"].nil?
69
+ end
70
+
71
+ def report_validation(row)
72
+ validation = row["validation"]
73
+ return if validation.nil? || validation["valid"]
74
+
75
+ name = File.basename(row["path"])
76
+ Fontist.ui.debug(
77
+ "Font validation issues for #{name}: #{validation['error_summary']}",
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
@@ -667,7 +667,8 @@ verbose:, stats:)
667
667
  show_minimal_progress = !verbose && sorted_paths.size > 100
668
668
  progress_interval = sorted_paths.size > 1000 ? 100 : 50
669
669
 
670
- results = sorted_paths.flat_map.with_index do |path, index|
670
+ misses = []
671
+ slots = sorted_paths.each_with_index.map do |path, index|
671
672
  if verbose && stats
672
673
  display_progress(index + 1, sorted_paths.size, path, spinner_chars,
673
674
  spinner_index)
@@ -678,25 +679,66 @@ verbose:, stats:)
678
679
  spinner_index += 1
679
680
 
680
681
  cached = existing_fonts_by_path[path]
681
- detect_font_with_cache(path, cached, stats: stats)
682
+ if reusable_cache?(path, cached, stats: stats)
683
+ cached
684
+ else
685
+ misses << path unless excluded?(path)
686
+ path
687
+ end
688
+ end
689
+
690
+ rows = scan_rows_by_path(misses)
691
+ results = slots.flat_map do |slot|
692
+ slot.is_a?(String) ? entries_for_miss(slot, rows[slot], stats) : slot
682
693
  end.compact
683
694
 
684
695
  clear_progress_line if verbose
685
696
  results
686
697
  end
687
698
 
688
- def detect_font_with_cache(path, cached, stats:)
699
+ def reusable_cache?(path, cached, stats:)
689
700
  # Fast path: reuse cached metadata if file unchanged
690
701
  if cached&.any? && file_unchanged?(path, cached.first)
691
702
  stats&.record_cache_hit
692
- cached
703
+ true
693
704
  else
694
- # Slow path: parse font file
695
705
  stats&.record_cache_miss
706
+ false
707
+ end
708
+ end
709
+
710
+ # One batch scan for every cache miss. The digest and name decoding run
711
+ # inside Fontisan.scan; no forks, no threads here.
712
+ def scan_rows_by_path(paths)
713
+ return {} if paths.empty?
714
+
715
+ Fontisan.scan(paths).group_by { |row| row["path"] }
716
+ end
717
+
718
+ def entries_for_miss(path, rows, stats)
719
+ return if excluded?(path)
720
+
721
+ if ScanRowFonts.usable?(rows)
722
+ build_entries_from_rows(path, rows)
723
+ else
724
+ # A failing or unusual file keeps the per-file path, so its error
725
+ # text and stats are exactly what they were before batching.
696
726
  detect_fonts(path, stats: stats)
697
727
  end
698
728
  end
699
729
 
730
+ def build_entries_from_rows(path, rows)
731
+ if rows.first["is_collection"]
732
+ ScanRowFonts.warn_collection_extension(path)
733
+ rows.map { |row| parse_font(ScanRowFonts.collection_face(row), path) }
734
+ else
735
+ ScanRowFonts.warn_file_extension(path, rows.first)
736
+ font_file = ScanRowFonts.file_font(rows.first)
737
+ entries = variable_font_entries(font_file, path)
738
+ entries.empty? ? parse_font(font_file, path) : entries
739
+ end
740
+ end
741
+
700
742
  def display_progress(current, total, path, spinner_chars,
701
743
  spinner_index = nil)
702
744
  spinner_index ||= (current / 10) % spinner_chars.length
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fontisan"
3
4
  require "parallel"
4
5
 
5
6
  module Fontist
@@ -43,14 +44,11 @@ module Fontist
43
44
  # Build read-only lookup from cache (non-shared state)
44
45
  cache_lookup = cache&.to_lookup || {}
45
46
 
46
- # Validate in parallel or sequential
47
- results = if use_parallel && font_paths.size > 10
48
- validate_parallel(font_paths, cache_lookup: cache_lookup,
49
- verbose: verbose)
50
- else
51
- validate_sequential(font_paths, cache_lookup: cache_lookup,
52
- verbose: verbose)
53
- end
47
+ # Cache hits are reused; every miss goes through one batch scan.
48
+ parallel = use_parallel && font_paths.size > 10
49
+ results = validate_batched(font_paths, cache_lookup: cache_lookup,
50
+ parallel: parallel,
51
+ verbose: verbose)
54
52
 
55
53
  @report.results = results
56
54
  @report.calculate_summary!
@@ -153,41 +151,74 @@ module Fontist
153
151
 
154
152
  private
155
153
 
156
- # Validate fonts in parallel using non-shared state.
157
- # Each thread gets read-only cache_lookup (no shared writes)
158
- def validate_parallel(font_paths, cache_lookup:, verbose:)
159
- num_cores = [Parallel.processor_count, 8].min
154
+ # Reuse unchanged cache entries, then validate the misses with a single
155
+ # Fontisan.scan. Results keep the order of +font_paths+.
156
+ def validate_batched(font_paths, cache_lookup:, parallel:, verbose:)
157
+ hits, misses = font_paths.partition do |path|
158
+ cached = cache_lookup[path]
159
+ cached && file_unchanged?(path, cached)
160
+ end
160
161
 
161
- if verbose
162
- Fontist.ui.say("Using parallel processing with #{num_cores} cores")
162
+ fresh = validate_misses(misses, parallel: parallel, verbose: verbose)
163
+ hit_set = hits.to_set
164
+ font_paths.filter_map do |path|
165
+ hit_set.include?(path) ? cache_lookup[path] : fresh[path]
163
166
  end
167
+ end
164
168
 
165
- # Map: Each thread validates independently (non-shared state)
166
- # cache_lookup is read-only, no mutex needed
167
- Parallel.map(font_paths, in_threads: num_cores) do |path|
168
- validate_with_cache_lookup(path, cache_lookup)
169
- end.compact
169
+ # @return [Hash{String => FontValidationResult}]
170
+ def validate_misses(paths, parallel:, verbose:)
171
+ return {} if paths.empty?
172
+
173
+ fresh = scan_results(paths)
174
+ # Failing files keep the per-file path so their messages are unchanged.
175
+ leftovers = fresh.select { |_, result| result.nil? }.keys
176
+ validate_leftovers(leftovers, parallel: parallel, verbose: verbose)
177
+ .each { |result| fresh[result.path] = result }
178
+ fresh
170
179
  end
171
180
 
172
- # Validate fonts sequentially with cache lookup.
173
- def validate_sequential(font_paths, cache_lookup:, verbose:)
174
- font_paths.filter_map do |path|
175
- validate_with_cache_lookup(path, cache_lookup)
181
+ # One scan for all paths; nil marks a file the scan could not read.
182
+ def scan_results(paths)
183
+ started = Time.now
184
+ rows = Fontisan.scan(paths).group_by { |row| row["path"] }
185
+ share = ((Time.now - started) / paths.size).round(4)
186
+
187
+ paths.to_h do |path|
188
+ [path, result_from_rows(path, rows[path], share)]
176
189
  end
177
190
  end
178
191
 
179
- # Validate single font, using cache if available and file unchanged.
180
- def validate_with_cache_lookup(path, cache_lookup)
181
- cached = cache_lookup[path]
182
-
183
- # Check if cached result is still valid (file unchanged)
184
- if cached && file_unchanged?(path, cached)
185
- # Return cached result without re-validating
186
- return cached
192
+ def validate_leftovers(paths, parallel:, verbose:)
193
+ if parallel && paths.size > 1
194
+ num_cores = [Parallel.processor_count, 8].min
195
+ if verbose
196
+ Fontist.ui.say("Using parallel processing with #{num_cores} cores")
197
+ end
198
+ Parallel.map(paths, in_threads: num_cores) { |p| validate_single(p) }
199
+ else
200
+ paths.map { |path| validate_single(path) }
187
201
  end
202
+ end
203
+
204
+ # A passing result when the scan row for +path+ read cleanly, else nil.
205
+ def result_from_rows(path, rows, time_taken)
206
+ row = rows&.first
207
+ return unless row && row["names"] && row["errors"]["font_file"].nil?
208
+
209
+ names = row["names"]
210
+ FontValidationResult.new(
211
+ path: path, valid: true, time_taken: time_taken,
212
+ family_name: names["family_name"], full_name: names["full_name"],
213
+ file_size: safe_stat { File.size(path) },
214
+ file_mtime: safe_stat { File.mtime(path).to_i }
215
+ )
216
+ end
188
217
 
189
- # No cache or file changed - validate
190
- validate_single(path)
218
+ def safe_stat
219
+ yield
220
+ rescue StandardError
221
+ 0
191
222
  end
192
223
 
193
224
  # Check if file has unchanged metadata compared to cached result.
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "3.0.37".freeze
2
+ VERSION = "3.0.38".freeze
3
3
  end
data/lib/fontist.rb CHANGED
@@ -50,6 +50,7 @@ module Fontist
50
50
 
51
51
  # Data models
52
52
  autoload :CollectionFile, "#{__dir__}/fontist/collection_file"
53
+ autoload :ScanRowFonts, "#{__dir__}/fontist/scan_row_fonts"
53
54
  autoload :Extract, "#{__dir__}/fontist/extract"
54
55
  autoload :ExtractOptions, "#{__dir__}/fontist/extract"
55
56
  autoload :Resource, "#{__dir__}/fontist/resource"
data/packaging/nfpm.yaml CHANGED
@@ -27,7 +27,12 @@ overrides:
27
27
 
28
28
  contents:
29
29
  - src: ${BINARY}
30
- dst: /usr/bin/fontist
30
+ dst: /usr/lib/fontist/fontist
31
31
  expand: true # nfpm leaves ${BINARY} in src alone without this
32
32
  file_info:
33
33
  mode: 0755
34
+
35
+ # The postinstall script links the binary into /usr/local/bin.
36
+ scripts:
37
+ postinstall: packaging/scripts/postinstall.sh
38
+ preremove: packaging/scripts/preremove.sh
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ # The binary lives in /usr/lib/fontist; make it reachable from /usr/local/bin.
3
+ set -e
4
+ mkdir -p /usr/local/bin
5
+ ln -sf /usr/lib/fontist/fontist /usr/local/bin/fontist
@@ -0,0 +1,12 @@
1
+ #!/bin/sh
2
+ # Remove the /usr/local/bin symlink on removal, not on upgrade. dpkg passes
3
+ # "remove" or "purge"; rpm passes the number of copies left (0 on removal).
4
+ set -e
5
+ case "$1" in
6
+ remove|purge|0)
7
+ if [ "$(readlink /usr/local/bin/fontist 2>/dev/null)" = /usr/lib/fontist/fontist ]; then
8
+ rm -f /usr/local/bin/fontist
9
+ fi
10
+ ;;
11
+ esac
12
+ exit 0
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # Get git >= 2.43 on Ubuntu 22.04, the documented one-time step
3
+ # before installing the fontist .deb (see README.adoc). Run as root.
4
+ #
5
+ # script/tebako/git-prep.sh
6
+ set -euo pipefail
7
+
8
+ export DEBIAN_FRONTEND=noninteractive
9
+ # shellcheck disable=SC1091
10
+ . /etc/os-release
11
+
12
+ apt-get update -qq
13
+ case "$ID:$VERSION_ID" in
14
+ ubuntu:22.04)
15
+ apt-get install -y -qq ca-certificates software-properties-common
16
+ add-apt-repository -y ppa:git-core/ppa
17
+ apt-get update -qq
18
+ apt-get install -y -qq git
19
+ ;;
20
+ *)
21
+ echo "git-prep: nothing to do on $ID $VERSION_ID" >&2
22
+ exit 1
23
+ ;;
24
+ esac
25
+ git --version
@@ -24,10 +24,15 @@ if command -v ruby >/dev/null; then
24
24
  echo "package-smoke: this image has no business carrying ruby" >&2
25
25
  exit 1
26
26
  fi
27
+ link="$(readlink /usr/local/bin/fontist || true)"
28
+ if [ "$link" != /usr/lib/fontist/fontist ]; then
29
+ echo "package-smoke: /usr/local/bin/fontist links to '$link'" >&2
30
+ exit 1
31
+ fi
27
32
  "$here/smoke.sh" "$(command -v fontist)"
28
33
  remove
29
- if command -v fontist >/dev/null; then
30
- echo "package-smoke: fontist still on PATH after removal" >&2
34
+ if [ -e /usr/local/bin/fontist ] || [ -L /usr/local/bin/fontist ] || command -v fontist >/dev/null; then
35
+ echo "package-smoke: fontist still present after removal" >&2
31
36
  exit 1
32
37
  fi
33
38
  echo "package-smoke: ok"
@@ -14,6 +14,9 @@ version="$(sed -n 's/.*VERSION = "\(.*\)".*/\1/p' "$root/lib/fontist/version.rb"
14
14
 
15
15
  mkdir -p "$out"
16
16
  BINARY="$(cd "$(dirname "$binary")" && pwd)/$(basename "$binary")"
17
+ out="$(cd "$out" && pwd)"
18
+ # nfpm resolves the script paths in nfpm.yaml against the working directory.
19
+ cd "$root"
17
20
  export ARCH="$arch" VERSION="$version" BINARY
18
21
 
19
22
  for packager in deb rpm; do
@@ -3,7 +3,8 @@
3
3
  #
4
4
  # script/tebako/smoke.sh <fontist-executable>
5
5
  #
6
- # Uses a throwaway FONTIST_PATH so it never touches a real ~/.fontist.
6
+ # Uses a throwaway FONTIST_PATH, then a throwaway HOME, so it never touches a
7
+ # real ~/.fontist.
7
8
  set -euo pipefail
8
9
 
9
10
  fontist="${1:?usage: smoke.sh <fontist-executable>}"
@@ -13,6 +14,7 @@ home="$(mktemp -d)"
13
14
  trap 'rm -rf "$home"' EXIT
14
15
  export FONTIST_PATH="$home"
15
16
 
17
+ "$fontist" --version
16
18
  "$fontist" version
17
19
  "$fontist" install --accept-all-licenses "$font"
18
20
 
@@ -22,4 +24,13 @@ if ! find "$home/fonts" -type f | grep -q .; then
22
24
  fi
23
25
  "$fontist" list "$font" | tee "$home/list.txt"
24
26
  grep -qi "installed" "$home/list.txt"
27
+
28
+ # Without FONTIST_PATH, fonts land in ~/.fontist.
29
+ user_home="$(mktemp -d)"
30
+ trap 'rm -rf "$home" "$user_home"' EXIT
31
+ env -u FONTIST_PATH HOME="$user_home" "$fontist" install --accept-all-licenses "$font"
32
+ if ! find "$user_home/.fontist/fonts" -type f | grep -q .; then
33
+ echo "smoke: no font file landed under $user_home/.fontist/fonts" >&2
34
+ exit 1
35
+ fi
25
36
  echo "smoke: ok"
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.37
4
+ version: 3.0.38
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-07 00:00:00.000000000 Z
11
+ date: 2026-11-13 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.47
56
+ version: 0.4.48
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.47
66
+ version: 0.4.48
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: fuzzy_match
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -232,6 +232,20 @@ dependencies:
232
232
  - - "~>"
233
233
  - !ruby/object:Gem::Version
234
234
  version: '0.1'
235
+ - !ruby/object:Gem::Dependency
236
+ name: yeptris
237
+ requirement: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - "~>"
240
+ - !ruby/object:Gem::Version
241
+ version: 0.6.55
242
+ type: :runtime
243
+ prerelease: false
244
+ version_requirements: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - "~>"
247
+ - !ruby/object:Gem::Version
248
+ version: 0.6.55
235
249
  description: Install openly-licensed fonts on Windows, Linux and Mac!
236
250
  email:
237
251
  - open.source@ribose.com
@@ -490,6 +504,7 @@ files:
490
504
  - lib/fontist/resources/archive_resource.rb
491
505
  - lib/fontist/resources/google_resource.rb
492
506
  - lib/fontist/resources/windows_fod_resource.rb
507
+ - lib/fontist/scan_row_fonts.rb
493
508
  - lib/fontist/sil_import_source.rb
494
509
  - lib/fontist/style_version.rb
495
510
  - lib/fontist/system.yml
@@ -518,10 +533,13 @@ files:
518
533
  - lib/fontist/yaml_backend.rb
519
534
  - lib/tasks/system_fonts.rake
520
535
  - packaging/nfpm.yaml
536
+ - packaging/scripts/postinstall.sh
537
+ - packaging/scripts/preremove.sh
521
538
  - script/capture_system_fonts
522
539
  - script/download_system_indexes
523
540
  - script/generate_windows_formulas.rb
524
541
  - script/tebako/entry.rb
542
+ - script/tebako/git-prep.sh
525
543
  - script/tebako/package-smoke.sh
526
544
  - script/tebako/package.sh
527
545
  - script/tebako/press.sh