fontisan 0.4.46 → 0.4.48
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/BENCHMARKS.md +56 -0
- data/CHANGELOG.md +12 -0
- data/Cargo.lock +550 -0
- data/Cargo.toml +12 -0
- data/Gemfile +10 -0
- data/README.adoc +52 -0
- data/Rakefile +5 -1
- data/benchmark/baseline.json +5 -0
- data/benchmark/ci_gate.rb +217 -0
- data/benchmark/corpus/harness.rb +175 -0
- data/benchmark/corpus/ops.rb +150 -0
- data/benchmark/corpus/run.rb +182 -0
- data/benchmark/corpus/scan_child.rb +8 -0
- data/benchmark/corpus/tiers.rb +176 -0
- data/benchmark/tier_baseline.json +12 -0
- data/docs/.vitepress/config.ts +2 -1
- data/docs/guide/engines.md +117 -0
- data/docs/reference-facade.md +130 -0
- data/ext/fontisan_cdylib/Cargo.lock +313 -0
- data/ext/fontisan_cdylib/Cargo.toml +18 -0
- data/ext/fontisan_cdylib/build.rs +22 -0
- data/ext/fontisan_cdylib/extconf.rb +36 -0
- data/ext/fontisan_cdylib/src/lib.rs +13 -0
- data/ext/fontisan_native/Cargo.toml +21 -0
- data/ext/fontisan_native/build.rs +22 -0
- data/ext/fontisan_native/clippy.toml +10 -0
- data/ext/fontisan_native/extconf.rb +59 -0
- data/ext/fontisan_native/src/lib.rs +463 -0
- data/lib/fontisan/audit/checks/cmap_check.rb +7 -0
- data/lib/fontisan/audit/checks/hinting_check.rb +1 -1
- data/lib/fontisan/audit/checks/maxp_check.rb +1 -1
- data/lib/fontisan/audit/checks/opentype_conformance_check.rb +5 -2
- data/lib/fontisan/base_collection.rb +30 -0
- data/lib/fontisan/byte_ops.rb +219 -0
- data/lib/fontisan/cli.rb +5 -5
- data/lib/fontisan/commands/audit_command.rb +38 -43
- data/lib/fontisan/commands/convert_command.rb +53 -0
- data/lib/fontisan/commands/instance_command.rb +75 -0
- data/lib/fontisan/commands/subset_command.rb +43 -0
- data/lib/fontisan/commands/unpack_command.rb +56 -0
- data/lib/fontisan/converters/cff_table_builder.rb +1 -1
- data/lib/fontisan/converters/collection_converter.rb +13 -5
- data/lib/fontisan/converters/format_converter.rb +1 -1
- data/lib/fontisan/converters/woff2_encoder.rb +8 -6
- data/lib/fontisan/converters/woff_writer.rb +6 -5
- data/lib/fontisan/engine.rb +350 -0
- data/lib/fontisan/ffi/client.rb +121 -0
- data/lib/fontisan/ffi.rb +343 -0
- data/lib/fontisan/ffi_platform.rb +24 -0
- data/lib/fontisan/font_loader.rb +5 -1
- data/lib/fontisan/font_writer.rb +13 -1
- data/lib/fontisan/native.rb +231 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +10 -8
- data/lib/fontisan/pipeline.rb +1 -1
- data/lib/fontisan/reference/table_digests.rb +251 -0
- data/lib/fontisan/reference.rb +619 -0
- data/lib/fontisan/scan.rb +372 -0
- data/lib/fontisan/sfnt_directory.rb +41 -0
- data/lib/fontisan/sfnt_font.rb +16 -6
- data/lib/fontisan/subset/builder.rb +2 -1
- data/lib/fontisan/tables/cff/charstring.rb +16 -0
- data/lib/fontisan/tables/cff.rb +15 -5
- data/lib/fontisan/tables/cmap.rb +11 -1
- data/lib/fontisan/tables/glyf.rb +4 -6
- data/lib/fontisan/tables/gpos.rb +7 -0
- data/lib/fontisan/tables/gsub.rb +7 -0
- data/lib/fontisan/tables/layout_common.rb +172 -0
- data/lib/fontisan/tables/name.rb +10 -4
- data/lib/fontisan/utilities/brotli_wrapper.rb +39 -4
- data/lib/fontisan/validators/opentype_validator.rb +15 -3
- data/lib/fontisan/variable/instancer.rb +23 -192
- data/lib/fontisan/variable/static_font_builder.rb +4 -1
- data/lib/fontisan/variable/table_updater.rb +4 -2
- data/lib/fontisan/variation/instance_generator.rb +3 -149
- data/lib/fontisan/variation/instancing/axis_space.rb +136 -0
- data/lib/fontisan/variation/instancing/glyph_outline.rb +204 -0
- data/lib/fontisan/variation/instancing/gvar_reader.rb +146 -0
- data/lib/fontisan/variation/instancing/item_variation_store.rb +92 -0
- data/lib/fontisan/variation/instancing/name_rewriter.rb +90 -0
- data/lib/fontisan/variation/instancing/true_type_instancer.rb +326 -0
- data/lib/fontisan/variation/instancing.rb +20 -0
- data/lib/fontisan/variation/variable_svg_generator.rb +19 -1
- data/lib/fontisan/variation.rb +1 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/collection_decoder.rb +15 -1
- data/lib/fontisan/woff2/collection_encoder.rb +29 -6
- data/lib/fontisan/woff2/encoder_rules.rb +12 -4
- data/lib/fontisan/woff2/glyf_loca_reconstruct.rb +1 -1
- data/lib/fontisan/woff2_collection.rb +7 -5
- data/lib/fontisan/woff2_font.rb +12 -3
- data/lib/fontisan/woff_container.rb +83 -0
- data/lib/fontisan/woff_font.rb +24 -20
- data/lib/fontisan.rb +24 -1
- data/rakelib/bench.rake +9 -0
- data/rakelib/cdylib.rake +39 -0
- data/rakelib/corpus.rake +272 -0
- data/rakelib/corpus_convert.rake +43 -0
- data/rakelib/corpus_goldens.rake +22 -0
- data/rakelib/corpus_malformed.rake +20 -0
- data/rakelib/corpus_scan.rake +24 -0
- data/rakelib/java.rake +61 -0
- data/rakelib/native.rake +43 -0
- data/scripts/build-woff2.sh +57 -0
- data/scripts/corpus/generate_convert_goldens.rb +478 -0
- data/scripts/corpus/generate_goldens.rb +282 -0
- data/scripts/corpus/generate_malformed_goldens.rb +254 -0
- data/scripts/corpus/generate_scan_goldens.rb +98 -0
- data/scripts/tier_fallback.rb +219 -0
- data/scripts/tier_smoke.rb +64 -0
- metadata +75 -3
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Tier fallback checks with strict mode OFF (fontist/fontisan#156 card 3a).
|
|
4
|
+
#
|
|
5
|
+
# ruby scripts/tier_fallback.rb TIERS [CORPUS_ROOT [SCENARIO]]
|
|
6
|
+
#
|
|
7
|
+
# TIERS is the comma list of non-Ruby tiers this platform is expected to have
|
|
8
|
+
# (native,ffi | ffi | none). Without SCENARIO it runs every scenario below, each
|
|
9
|
+
# in a fresh child process (a tier cannot be un-loaded), against the ci-tier
|
|
10
|
+
# corpus with FONTISAN_ENGINE_STRICT unset:
|
|
11
|
+
#
|
|
12
|
+
# no_native, no_ffi the forced tier removed -> every row served by
|
|
13
|
+
# ruby, rows equal the scan goldens
|
|
14
|
+
# native_missing_symbol, the extension / library lacks a symbol, or speaks
|
|
15
|
+
# native_abi_mismatch, another ABI: the tier probes unavailable, nothing
|
|
16
|
+
# ffi_missing_symbol, raises (no NameError) and ruby serves golden rows
|
|
17
|
+
# ffi_abi_mismatch
|
|
18
|
+
# mixed dfont and Type 1 rows are served by ruby, the
|
|
19
|
+
# rest by the forced tier
|
|
20
|
+
# auto_all, auto_no_native, `auto` resolves native, then ffi with native
|
|
21
|
+
# auto_none removed, then ruby with both removed
|
|
22
|
+
#
|
|
23
|
+
# A scenario about a tier the platform lacks is skipped. Run it against an
|
|
24
|
+
# INSTALLED gem on the CI matrix (outside the checkout), or locally with
|
|
25
|
+
# `bundle exec ruby scripts/tier_fallback.rb ...` from a checkout.
|
|
26
|
+
# Exit status 0 only when every scenario holds.
|
|
27
|
+
require "json"
|
|
28
|
+
require "rbconfig"
|
|
29
|
+
require "tmpdir"
|
|
30
|
+
require "fileutils"
|
|
31
|
+
|
|
32
|
+
SCENARIOS = %w[no_native no_ffi native_missing_symbol native_abi_mismatch
|
|
33
|
+
ffi_missing_symbol ffi_abi_mismatch mixed
|
|
34
|
+
auto_all auto_no_native auto_none].freeze
|
|
35
|
+
|
|
36
|
+
usage = "usage: tier_fallback.rb native,ffi|ffi|none [corpus root [scenario]]"
|
|
37
|
+
tiers = ARGV.fetch(0) { abort usage }.split(",") - ["none"]
|
|
38
|
+
root = File.expand_path(ARGV.fetch(1, File.join(__dir__, "..", "corpus")))
|
|
39
|
+
scenario = ARGV[2]
|
|
40
|
+
abort "unknown tiers #{tiers.inspect}" unless (tiers - %w[native ffi]).empty?
|
|
41
|
+
|
|
42
|
+
unless scenario
|
|
43
|
+
results = SCENARIOS.to_h do |name|
|
|
44
|
+
[name, system(RbConfig.ruby, __FILE__, tiers.join(",").then { |t| t.empty? ? "none" : t }, root, name)]
|
|
45
|
+
end
|
|
46
|
+
failed = results.reject { |_, ok| ok }.keys
|
|
47
|
+
puts failed.empty? ? "all #{SCENARIOS.size} fallback scenarios hold" : "FAILED: #{failed.join(', ')}"
|
|
48
|
+
exit(failed.empty? ? 0 : 1)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
abort "unknown scenario #{scenario.inspect}" unless SCENARIOS.include?(scenario)
|
|
52
|
+
|
|
53
|
+
# --- sabotage that must happen before the gem loads -----------------------
|
|
54
|
+
|
|
55
|
+
module Sabotage
|
|
56
|
+
# Takes the native extension away through the probe's own require.
|
|
57
|
+
module BlockNative
|
|
58
|
+
def require(name)
|
|
59
|
+
raise LoadError, "native extension removed for the test" if name.to_s.include?("fontisan_native")
|
|
60
|
+
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# A library lacking the fontisan_call symbol.
|
|
66
|
+
module MissingFfiSymbol
|
|
67
|
+
def attach_function(name, *rest)
|
|
68
|
+
raise FFI::NotFoundError.new(name.to_s, "library") if name == :fontisan_call
|
|
69
|
+
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
ENV["FONTISAN_ENGINE"] = "auto"
|
|
76
|
+
ENV.delete("FONTISAN_ENGINE_STRICT")
|
|
77
|
+
case scenario
|
|
78
|
+
when "no_native", "auto_no_native", "auto_none" then Object.prepend(Sabotage::BlockNative)
|
|
79
|
+
end
|
|
80
|
+
case scenario
|
|
81
|
+
when "no_ffi", "auto_none" then ENV["FONTISAN_CDYLIB"] = File.join(Dir.tmpdir, "fontisan-no-such-dir", "libfontisan_cdylib.none")
|
|
82
|
+
end
|
|
83
|
+
require "ffi" if scenario == "ffi_missing_symbol" && tiers.include?("ffi")
|
|
84
|
+
FFI::Library.prepend(Sabotage::MissingFfiSymbol) if defined?(FFI::Library) && scenario == "ffi_missing_symbol"
|
|
85
|
+
require "fontisan"
|
|
86
|
+
Fontisan::Reference # autoloading it installs the engine gates
|
|
87
|
+
|
|
88
|
+
needs = { "no_native" => "native", "native_missing_symbol" => "native", "native_abi_mismatch" => "native",
|
|
89
|
+
"no_ffi" => "ffi", "ffi_missing_symbol" => "ffi", "ffi_abi_mismatch" => "ffi",
|
|
90
|
+
"auto_no_native" => "native" }[scenario]
|
|
91
|
+
if needs && !tiers.include?(needs)
|
|
92
|
+
puts "skip #{scenario}: no #{needs} tier on this platform"
|
|
93
|
+
exit 0
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
case scenario
|
|
97
|
+
when "native_missing_symbol"
|
|
98
|
+
Fontisan::Native.send(:load_extension)
|
|
99
|
+
Fontisan::Native.singleton_class.send(:undef_method, :extension_abi_version)
|
|
100
|
+
when "native_abi_mismatch"
|
|
101
|
+
Fontisan::Native.send(:remove_const, :ABI_VERSION)
|
|
102
|
+
Fontisan::Native.const_set(:ABI_VERSION, 999)
|
|
103
|
+
when "ffi_abi_mismatch"
|
|
104
|
+
require "fontisan/ffi/client"
|
|
105
|
+
Fontisan::Ffi::Client.send(:remove_const, :ABI_VERSION)
|
|
106
|
+
Fontisan::Ffi::Client.const_set(:ABI_VERSION, 999)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# --- checks ---------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
HANDED_TO_RUBY = %w[.dfont .pfa .pfb].freeze
|
|
112
|
+
manifest = JSON.parse(File.read(File.join(root, "manifest.json"))).fetch("fonts")
|
|
113
|
+
entries = manifest.select { |f| f["tier"] == "ci" }.sort_by { |f| f["id"] }
|
|
114
|
+
problems = []
|
|
115
|
+
|
|
116
|
+
font_path = lambda do |entry|
|
|
117
|
+
File.join(root, entry["type"] == "derived" ? "derived" : File.join("fonts", entry["id"]), entry["file"])
|
|
118
|
+
end
|
|
119
|
+
golden = lambda do |entry|
|
|
120
|
+
JSON.parse(File.read(File.join(root, "goldens", "scan", "#{entry['id']}.json"))).fetch("rows")
|
|
121
|
+
end
|
|
122
|
+
plain = lambda do |rows|
|
|
123
|
+
JSON.parse(JSON.generate(rows.map { |r| r.except("served_by").merge("path" => File.basename(r["path"])) }))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Scan every font; rows equal the goldens, served_by is in allowed.(path).
|
|
127
|
+
scan_corpus = lambda do |allowed|
|
|
128
|
+
served = Hash.new(0)
|
|
129
|
+
entries.each do |entry|
|
|
130
|
+
path = font_path.call(entry)
|
|
131
|
+
# The java gem has no brotli: with the ffi tier sabotaged nothing can read a WOFF2,
|
|
132
|
+
# and the engine says so instead of falling back to a Ruby that cannot serve it.
|
|
133
|
+
if File.extname(path).casecmp?(".woff2") && allowed.call(path) == ["ruby"] && !Fontisan::Utilities::BrotliWrapper.available?
|
|
134
|
+
begin
|
|
135
|
+
Fontisan::Engine.scan([path])
|
|
136
|
+
problems << "#{entry['id']}: WOFF2 scan with no ffi tier and no brotli did not raise Woff2Unavailable"
|
|
137
|
+
rescue Fontisan::Engine::Woff2Unavailable
|
|
138
|
+
served["unavailable"] += 1
|
|
139
|
+
end
|
|
140
|
+
next
|
|
141
|
+
end
|
|
142
|
+
rows = Fontisan::Engine.scan([path])
|
|
143
|
+
problems << "#{entry['id']}: rows differ from the golden" unless plain.call(rows) == golden.call(entry)
|
|
144
|
+
rows.each do |row|
|
|
145
|
+
served[row["served_by"]] += 1
|
|
146
|
+
want = allowed.call(path)
|
|
147
|
+
problems << "#{entry['id']}: served_by #{row['served_by']}, expected #{want.join('|')}" unless want.include?(row["served_by"])
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
served
|
|
151
|
+
end
|
|
152
|
+
resolved = lambda do
|
|
153
|
+
Fontisan::Engine.resolve(:scan, font_path.call(entries.find { |e| e["id"] == "fonttools-test-ttf" })).to_s
|
|
154
|
+
end
|
|
155
|
+
all_ruby = ->(_path) { ["ruby"] }
|
|
156
|
+
|
|
157
|
+
# The scenarios that sabotage nothing first check the platform has the tiers it claims.
|
|
158
|
+
if %w[mixed auto_all].include?(scenario)
|
|
159
|
+
ENV["FONTISAN_ENGINE"] = "auto"
|
|
160
|
+
Fontisan::Engine.reset!
|
|
161
|
+
have = %w[native ffi].select { |t| Fontisan::Engine.send(:available?, t.to_sym) }
|
|
162
|
+
problems << "available tiers are #{have.inspect}, expected #{tiers.inspect}" unless have == tiers
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
begin
|
|
166
|
+
case scenario
|
|
167
|
+
when "no_native", "no_ffi", "native_missing_symbol", "native_abi_mismatch", "ffi_missing_symbol", "ffi_abi_mismatch"
|
|
168
|
+
tier = scenario[/native|ffi/]
|
|
169
|
+
ENV["FONTISAN_ENGINE"] = tier
|
|
170
|
+
Fontisan::Engine.reset!
|
|
171
|
+
problems << "#{tier} probes available" if Fontisan::Engine.send(:available?, tier.to_sym)
|
|
172
|
+
served = scan_corpus.call(all_ruby)
|
|
173
|
+
problems << "no ruby row served" if served["ruby"].zero?
|
|
174
|
+
when "mixed"
|
|
175
|
+
tiers.each do |tier|
|
|
176
|
+
ENV["FONTISAN_ENGINE"] = tier
|
|
177
|
+
Fontisan::Engine.reset!
|
|
178
|
+
# dfont and PFB must be ruby's. Ruby's detect_format does not recognise a
|
|
179
|
+
# PFA (nil), so the tier may take it; its error row equals the golden either way.
|
|
180
|
+
scan_corpus.call(lambda { |path|
|
|
181
|
+
case File.extname(path).downcase
|
|
182
|
+
when ".dfont", ".pfb" then ["ruby"]
|
|
183
|
+
when ".pfa" then ["ruby", tier]
|
|
184
|
+
else [tier]
|
|
185
|
+
end
|
|
186
|
+
})
|
|
187
|
+
end
|
|
188
|
+
# dfont and Type 1 in one audit_dir: ruby serves those entries, the tier the rest.
|
|
189
|
+
Dir.mktmpdir("fontisan-mixed") do |dir|
|
|
190
|
+
%w[fonttools-test-ttf fonttools-test-dfont fonttools-test-t1-pfb].each do |id|
|
|
191
|
+
entry = entries.find { |e| e["id"] == id }
|
|
192
|
+
FileUtils.cp(font_path.call(entry), dir)
|
|
193
|
+
end
|
|
194
|
+
tiers.each do |tier|
|
|
195
|
+
ENV["FONTISAN_ENGINE"] = tier
|
|
196
|
+
Fontisan::Engine.reset!
|
|
197
|
+
by_file = Fontisan::Engine.audit_dir(dir).to_h { |e| [e["path"], e["served_by"]] }
|
|
198
|
+
want = { "TestTTF.ttf" => tier, "TestDFONT.dfont" => "ruby", "TestT1-Regular.pfb" => "ruby" }
|
|
199
|
+
problems << "#{tier} audit_dir served #{by_file.inspect}, expected #{want.inspect}" unless by_file == want
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
problems << "mixed scenario needs a native or ffi tier" if tiers.empty?
|
|
203
|
+
when "auto_all", "auto_no_native", "auto_none"
|
|
204
|
+
ENV["FONTISAN_ENGINE"] = "auto"
|
|
205
|
+
Fontisan::Engine.reset!
|
|
206
|
+
want = { "auto_all" => tiers.first || "ruby",
|
|
207
|
+
"auto_no_native" => tiers.include?("ffi") ? "ffi" : "ruby",
|
|
208
|
+
"auto_none" => "ruby" }.fetch(scenario)
|
|
209
|
+
problems << "auto resolved #{resolved.call}, expected #{want}" unless resolved.call == want
|
|
210
|
+
served = scan_corpus.call(->(path) { [want, *("ruby" if HANDED_TO_RUBY.include?(File.extname(path).downcase))] })
|
|
211
|
+
problems << "no #{want} row served" if served[want].zero?
|
|
212
|
+
end
|
|
213
|
+
rescue StandardError => e # NameError included: a fallback must never raise it
|
|
214
|
+
problems << "raised #{e.class}: #{e.message[0, 200]}"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
puts "#{problems.empty? ? 'ok ' : 'FAIL'} #{scenario} (tiers: #{tiers.join(',')})"
|
|
218
|
+
problems.each { |p| puts " #{p}" }
|
|
219
|
+
exit(problems.empty? ? 0 : 1)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Installed-gem smoke test (fontist/fontisan#156 card 2e).
|
|
4
|
+
#
|
|
5
|
+
# ruby scripts/tier_smoke.rb EXPECTED_TIER [CORPUS_ROOT]
|
|
6
|
+
#
|
|
7
|
+
# Run it against an INSTALLED fontisan gem (never the checkout's lib/). It scans
|
|
8
|
+
# every `ci`-tier corpus font through Fontisan::Engine under the default engine (FONTISAN_ENGINE unset = auto),
|
|
9
|
+
# then checks that
|
|
10
|
+
# * the tier auto resolves to is EXPECTED_TIER (native, ffi or ruby);
|
|
11
|
+
# * every row equals the committed scan golden (minus the served_by key);
|
|
12
|
+
# * served_by is EXPECTED_TIER, except for files the engine hands to Ruby
|
|
13
|
+
# (dfont, Type 1), which may say ruby; with EXPECTED_TIER=ruby nothing else.
|
|
14
|
+
# Exit status 0 only when all hold.
|
|
15
|
+
require "json"
|
|
16
|
+
|
|
17
|
+
expected = ARGV.fetch(0) { abort "usage: tier_smoke.rb native|ffi|ruby [corpus root]" }
|
|
18
|
+
root = File.expand_path(ARGV.fetch(1, File.join(__dir__, "..", "corpus")))
|
|
19
|
+
abort "unknown tier #{expected.inspect}" unless %w[native ffi ruby].include?(expected)
|
|
20
|
+
|
|
21
|
+
ENV.delete("FONTISAN_ENGINE") # the default (auto) is what is under test
|
|
22
|
+
ENV.delete("FONTISAN_ENGINE_STRICT")
|
|
23
|
+
require "fontisan"
|
|
24
|
+
Fontisan::Reference # autoloading it installs the engine gates
|
|
25
|
+
|
|
26
|
+
lib_dir = Gem.loaded_specs.fetch("fontisan").full_gem_path
|
|
27
|
+
abort "smoke must run an installed gem, loaded #{lib_dir}" unless File.basename(File.dirname(lib_dir)) == "gems"
|
|
28
|
+
|
|
29
|
+
HANDED_TO_RUBY = %w[.dfont .pfa .pfb].freeze
|
|
30
|
+
|
|
31
|
+
manifest = JSON.parse(File.read(File.join(root, "manifest.json"))).fetch("fonts")
|
|
32
|
+
entries = manifest.select { |f| f["tier"] == "ci" }.sort_by { |f| f["id"] }
|
|
33
|
+
problems = []
|
|
34
|
+
served = Hash.new(0)
|
|
35
|
+
|
|
36
|
+
entries.each do |entry|
|
|
37
|
+
dir = entry["type"] == "derived" ? "derived" : File.join("fonts", entry["id"])
|
|
38
|
+
path = File.join(root, dir, entry["file"])
|
|
39
|
+
golden = JSON.parse(File.read(File.join(root, "goldens", "scan", "#{entry['id']}.json"))).fetch("rows")
|
|
40
|
+
rows = Fontisan::Engine.scan([path])
|
|
41
|
+
rows.each { |row| served[row["served_by"]] += 1 }
|
|
42
|
+
|
|
43
|
+
actual = rows.map { |r| r.except("served_by").merge("path" => File.basename(r["path"])) }
|
|
44
|
+
problems << "#{entry['id']}: rows differ from golden" unless JSON.parse(JSON.generate(actual)) == golden
|
|
45
|
+
|
|
46
|
+
allowed = [expected]
|
|
47
|
+
allowed << "ruby" if expected != "ruby" && HANDED_TO_RUBY.include?(File.extname(path).downcase)
|
|
48
|
+
rows.each do |row|
|
|
49
|
+
problems << "#{entry['id']}: served_by #{row['served_by']}, expected #{allowed.join('|')}" unless allowed.include?(row["served_by"])
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
resolved = Fontisan::Engine.resolve(:scan, File.join(root, "fonts", "fonttools-test-ttf", "TestTTF.ttf")).to_s
|
|
54
|
+
problems << "auto resolves scan to #{resolved}, expected #{expected}" unless resolved == expected
|
|
55
|
+
problems << "no #{expected} row served" if served[expected].zero?
|
|
56
|
+
|
|
57
|
+
puts "fontisan #{Fontisan::VERSION} from #{lib_dir}"
|
|
58
|
+
puts "ruby #{RUBY_VERSION} #{RUBY_PLATFORM}; fonts scanned: #{entries.size}; served_by: #{served.inspect}"
|
|
59
|
+
if problems.empty?
|
|
60
|
+
puts "OK: auto resolved to #{resolved}; rows equal the goldens"
|
|
61
|
+
else
|
|
62
|
+
warn problems
|
|
63
|
+
exit 1
|
|
64
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontisan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.48
|
|
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
|
+
date: 2026-11-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -94,6 +94,20 @@ dependencies:
|
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '1.16'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rb_sys
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0.9'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0.9'
|
|
97
111
|
- !ruby/object:Gem::Dependency
|
|
98
112
|
name: thor
|
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -119,16 +133,20 @@ email:
|
|
|
119
133
|
- open.source@ribose.com
|
|
120
134
|
executables:
|
|
121
135
|
- fontisan
|
|
122
|
-
extensions:
|
|
136
|
+
extensions:
|
|
137
|
+
- ext/fontisan_native/extconf.rb
|
|
123
138
|
extra_rdoc_files: []
|
|
124
139
|
files:
|
|
125
140
|
- ".rspec"
|
|
126
141
|
- ".rubocop.yml"
|
|
127
142
|
- ".rubocop_todo.yml"
|
|
143
|
+
- BENCHMARKS.md
|
|
128
144
|
- BUG-stitcher-drops-isolated-cps.md
|
|
129
145
|
- BUG-stitcher-drops-plane1-codepoints.md
|
|
130
146
|
- BUG-stitcher-gid-cap-65535.md
|
|
131
147
|
- CHANGELOG.md
|
|
148
|
+
- Cargo.lock
|
|
149
|
+
- Cargo.toml
|
|
132
150
|
- Gemfile
|
|
133
151
|
- LICENSE
|
|
134
152
|
- README.adoc
|
|
@@ -180,7 +198,15 @@ files:
|
|
|
180
198
|
- TODO.improvements/14-rubocop-baseline-chip.md
|
|
181
199
|
- TODO.improvements/15-cff-cff2-subsetter-strategy.md
|
|
182
200
|
- TODO.improvements/README.md
|
|
201
|
+
- benchmark/baseline.json
|
|
202
|
+
- benchmark/ci_gate.rb
|
|
183
203
|
- benchmark/compile_benchmark.rb
|
|
204
|
+
- benchmark/corpus/harness.rb
|
|
205
|
+
- benchmark/corpus/ops.rb
|
|
206
|
+
- benchmark/corpus/run.rb
|
|
207
|
+
- benchmark/corpus/scan_child.rb
|
|
208
|
+
- benchmark/corpus/tiers.rb
|
|
209
|
+
- benchmark/tier_baseline.json
|
|
184
210
|
- benchmark/variation_quick_bench.rb
|
|
185
211
|
- docs/.gitignore
|
|
186
212
|
- docs/.vitepress/config.ts
|
|
@@ -268,6 +294,7 @@ files:
|
|
|
268
294
|
- docs/guide/conversion/ttf-otf.md
|
|
269
295
|
- docs/guide/conversion/type1.md
|
|
270
296
|
- docs/guide/conversion/web.md
|
|
297
|
+
- docs/guide/engines.md
|
|
271
298
|
- docs/guide/formats/collections.md
|
|
272
299
|
- docs/guide/formats/dfont.md
|
|
273
300
|
- docs/guide/formats/index.md
|
|
@@ -320,8 +347,19 @@ files:
|
|
|
320
347
|
- docs/public/site.webmanifest
|
|
321
348
|
- docs/public/web-app-manifest-192x192.png
|
|
322
349
|
- docs/public/web-app-manifest-512x512.png
|
|
350
|
+
- docs/reference-facade.md
|
|
323
351
|
- docs/scripts/post-build.mjs
|
|
324
352
|
- exe/fontisan
|
|
353
|
+
- ext/fontisan_cdylib/Cargo.lock
|
|
354
|
+
- ext/fontisan_cdylib/Cargo.toml
|
|
355
|
+
- ext/fontisan_cdylib/build.rs
|
|
356
|
+
- ext/fontisan_cdylib/extconf.rb
|
|
357
|
+
- ext/fontisan_cdylib/src/lib.rs
|
|
358
|
+
- ext/fontisan_native/Cargo.toml
|
|
359
|
+
- ext/fontisan_native/build.rs
|
|
360
|
+
- ext/fontisan_native/clippy.toml
|
|
361
|
+
- ext/fontisan_native/extconf.rb
|
|
362
|
+
- ext/fontisan_native/src/lib.rs
|
|
325
363
|
- lib/fontisan.rb
|
|
326
364
|
- lib/fontisan/audit.rb
|
|
327
365
|
- lib/fontisan/audit/check.rb
|
|
@@ -352,6 +390,7 @@ files:
|
|
|
352
390
|
- lib/fontisan/binary.rb
|
|
353
391
|
- lib/fontisan/binary/base_record.rb
|
|
354
392
|
- lib/fontisan/binary/structures.rb
|
|
393
|
+
- lib/fontisan/byte_ops.rb
|
|
355
394
|
- lib/fontisan/cli.rb
|
|
356
395
|
- lib/fontisan/collection.rb
|
|
357
396
|
- lib/fontisan/collection/builder.rb
|
|
@@ -411,6 +450,7 @@ files:
|
|
|
411
450
|
- lib/fontisan/converters/woff2_encoder.rb
|
|
412
451
|
- lib/fontisan/converters/woff_writer.rb
|
|
413
452
|
- lib/fontisan/dfont_collection.rb
|
|
453
|
+
- lib/fontisan/engine.rb
|
|
414
454
|
- lib/fontisan/error.rb
|
|
415
455
|
- lib/fontisan/export.rb
|
|
416
456
|
- lib/fontisan/export/exporter.rb
|
|
@@ -425,6 +465,9 @@ files:
|
|
|
425
465
|
- lib/fontisan/export/transformers/post_transformer.rb
|
|
426
466
|
- lib/fontisan/export/ttx_generator.rb
|
|
427
467
|
- lib/fontisan/export/ttx_parser.rb
|
|
468
|
+
- lib/fontisan/ffi.rb
|
|
469
|
+
- lib/fontisan/ffi/client.rb
|
|
470
|
+
- lib/fontisan/ffi_platform.rb
|
|
428
471
|
- lib/fontisan/font_loader.rb
|
|
429
472
|
- lib/fontisan/font_writer.rb
|
|
430
473
|
- lib/fontisan/formatters.rb
|
|
@@ -482,6 +525,7 @@ files:
|
|
|
482
525
|
- lib/fontisan/models/unicode_mappings.rb
|
|
483
526
|
- lib/fontisan/models/validation_report.rb
|
|
484
527
|
- lib/fontisan/models/variable_font_info.rb
|
|
528
|
+
- lib/fontisan/native.rb
|
|
485
529
|
- lib/fontisan/open_type_collection.rb
|
|
486
530
|
- lib/fontisan/open_type_font.rb
|
|
487
531
|
- lib/fontisan/open_type_font_extensions.rb
|
|
@@ -506,7 +550,11 @@ files:
|
|
|
506
550
|
- lib/fontisan/pipeline/strategies/preserve_strategy.rb
|
|
507
551
|
- lib/fontisan/pipeline/transformation_pipeline.rb
|
|
508
552
|
- lib/fontisan/pipeline/variation_resolver.rb
|
|
553
|
+
- lib/fontisan/reference.rb
|
|
554
|
+
- lib/fontisan/reference/table_digests.rb
|
|
555
|
+
- lib/fontisan/scan.rb
|
|
509
556
|
- lib/fontisan/sfnt_builder.rb
|
|
557
|
+
- lib/fontisan/sfnt_directory.rb
|
|
510
558
|
- lib/fontisan/sfnt_font.rb
|
|
511
559
|
- lib/fontisan/sfnt_source.rb
|
|
512
560
|
- lib/fontisan/sfnt_table.rb
|
|
@@ -827,6 +875,13 @@ files:
|
|
|
827
875
|
- lib/fontisan/variation/instance_font_wrapper.rb
|
|
828
876
|
- lib/fontisan/variation/instance_generator.rb
|
|
829
877
|
- lib/fontisan/variation/instance_writer.rb
|
|
878
|
+
- lib/fontisan/variation/instancing.rb
|
|
879
|
+
- lib/fontisan/variation/instancing/axis_space.rb
|
|
880
|
+
- lib/fontisan/variation/instancing/glyph_outline.rb
|
|
881
|
+
- lib/fontisan/variation/instancing/gvar_reader.rb
|
|
882
|
+
- lib/fontisan/variation/instancing/item_variation_store.rb
|
|
883
|
+
- lib/fontisan/variation/instancing/name_rewriter.rb
|
|
884
|
+
- lib/fontisan/variation/instancing/true_type_instancer.rb
|
|
830
885
|
- lib/fontisan/variation/interpolator.rb
|
|
831
886
|
- lib/fontisan/variation/metrics_adjuster.rb
|
|
832
887
|
- lib/fontisan/variation/optimizer.rb
|
|
@@ -857,9 +912,26 @@ files:
|
|
|
857
912
|
- lib/fontisan/woff2/uint255.rb
|
|
858
913
|
- lib/fontisan/woff2_collection.rb
|
|
859
914
|
- lib/fontisan/woff2_font.rb
|
|
915
|
+
- lib/fontisan/woff_container.rb
|
|
860
916
|
- lib/fontisan/woff_font.rb
|
|
917
|
+
- rakelib/bench.rake
|
|
918
|
+
- rakelib/cdylib.rake
|
|
919
|
+
- rakelib/corpus.rake
|
|
920
|
+
- rakelib/corpus_convert.rake
|
|
921
|
+
- rakelib/corpus_goldens.rake
|
|
922
|
+
- rakelib/corpus_malformed.rake
|
|
923
|
+
- rakelib/corpus_scan.rake
|
|
924
|
+
- rakelib/java.rake
|
|
925
|
+
- rakelib/native.rake
|
|
926
|
+
- scripts/build-woff2.sh
|
|
861
927
|
- scripts/compare_stack_aware.rb
|
|
928
|
+
- scripts/corpus/generate_convert_goldens.rb
|
|
929
|
+
- scripts/corpus/generate_goldens.rb
|
|
930
|
+
- scripts/corpus/generate_malformed_goldens.rb
|
|
931
|
+
- scripts/corpus/generate_scan_goldens.rb
|
|
862
932
|
- scripts/measure_optimization.rb
|
|
933
|
+
- scripts/tier_fallback.rb
|
|
934
|
+
- scripts/tier_smoke.rb
|
|
863
935
|
homepage: https://github.com/fontist/fontisan
|
|
864
936
|
licenses:
|
|
865
937
|
- BSD-2-Clause
|