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 +4 -4
- data/.github/workflows/tebako-pack.yml +43 -4
- data/Gemfile +4 -5
- data/README.adoc +10 -2
- data/fontist.gemspec +2 -1
- data/lib/fontist/font_operations/convert.rb +1 -3
- data/lib/fontist/font_operations/extract.rb +3 -3
- data/lib/fontist/font_operations/instance.rb +7 -3
- data/lib/fontist/font_operations/subset.rb +7 -11
- data/lib/fontist/scan_row_fonts.rb +82 -0
- data/lib/fontist/system_index.rb +47 -5
- data/lib/fontist/validator.rb +64 -33
- data/lib/fontist/version.rb +1 -1
- data/lib/fontist.rb +1 -0
- data/packaging/nfpm.yaml +6 -1
- data/packaging/scripts/postinstall.sh +5 -0
- data/packaging/scripts/preremove.sh +12 -0
- data/script/tebako/git-prep.sh +25 -0
- data/script/tebako/package-smoke.sh +7 -2
- data/script/tebako/package.sh +3 -0
- data/script/tebako/smoke.sh +12 -1
- metadata +22 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ec4754ec2eb25b7e106ecd9254296d72900bf8df94b59d1707099f1585333c6
|
|
4
|
+
data.tar.gz: f41467d946e5513fb8f297d52984c792ffceb6ee20f4400b238ac786c844960f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
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
|
|
82
|
-
|
|
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.
|
|
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::
|
|
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::
|
|
22
|
-
collection_path,
|
|
23
|
-
)
|
|
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
|
-
|
|
63
|
-
|
|
62
|
+
validate!(Fontisan::Variable::Instancer.new(font), coords,
|
|
63
|
+
File.basename(source))
|
|
64
64
|
FileUtils.mkdir_p(File.dirname(output))
|
|
65
|
-
File.binwrite(output,
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
data/lib/fontist/system_index.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
data/lib/fontist/validator.rb
CHANGED
|
@@ -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
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
#
|
|
157
|
-
#
|
|
158
|
-
def
|
|
159
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
#
|
|
173
|
-
def
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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.
|
data/lib/fontist/version.rb
CHANGED
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/
|
|
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,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
|
|
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"
|
data/script/tebako/package.sh
CHANGED
|
@@ -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
|
data/script/tebako/smoke.sh
CHANGED
|
@@ -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
|
|
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.
|
|
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-
|
|
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.
|
|
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.
|
|
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
|