fontisan 0.4.47 → 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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/BENCHMARKS.md +56 -0
  3. data/CHANGELOG.md +12 -0
  4. data/Cargo.lock +550 -0
  5. data/Cargo.toml +12 -0
  6. data/Gemfile +10 -0
  7. data/README.adoc +52 -0
  8. data/Rakefile +5 -1
  9. data/benchmark/baseline.json +5 -0
  10. data/benchmark/ci_gate.rb +217 -0
  11. data/benchmark/corpus/harness.rb +175 -0
  12. data/benchmark/corpus/ops.rb +150 -0
  13. data/benchmark/corpus/run.rb +182 -0
  14. data/benchmark/corpus/scan_child.rb +8 -0
  15. data/benchmark/corpus/tiers.rb +176 -0
  16. data/benchmark/tier_baseline.json +12 -0
  17. data/docs/.vitepress/config.ts +2 -1
  18. data/docs/guide/engines.md +117 -0
  19. data/docs/reference-facade.md +130 -0
  20. data/ext/fontisan_cdylib/Cargo.lock +313 -0
  21. data/ext/fontisan_cdylib/Cargo.toml +18 -0
  22. data/ext/fontisan_cdylib/build.rs +22 -0
  23. data/ext/fontisan_cdylib/extconf.rb +36 -0
  24. data/ext/fontisan_cdylib/src/lib.rs +13 -0
  25. data/ext/fontisan_native/Cargo.toml +21 -0
  26. data/ext/fontisan_native/build.rs +22 -0
  27. data/ext/fontisan_native/clippy.toml +10 -0
  28. data/ext/fontisan_native/extconf.rb +59 -0
  29. data/ext/fontisan_native/src/lib.rs +463 -0
  30. data/lib/fontisan/audit/checks/cmap_check.rb +7 -0
  31. data/lib/fontisan/audit/checks/hinting_check.rb +1 -1
  32. data/lib/fontisan/audit/checks/maxp_check.rb +1 -1
  33. data/lib/fontisan/audit/checks/opentype_conformance_check.rb +5 -2
  34. data/lib/fontisan/base_collection.rb +30 -0
  35. data/lib/fontisan/byte_ops.rb +219 -0
  36. data/lib/fontisan/cli.rb +5 -5
  37. data/lib/fontisan/commands/audit_command.rb +38 -43
  38. data/lib/fontisan/commands/convert_command.rb +53 -0
  39. data/lib/fontisan/commands/instance_command.rb +75 -0
  40. data/lib/fontisan/commands/subset_command.rb +43 -0
  41. data/lib/fontisan/commands/unpack_command.rb +56 -0
  42. data/lib/fontisan/converters/cff_table_builder.rb +1 -1
  43. data/lib/fontisan/converters/collection_converter.rb +13 -5
  44. data/lib/fontisan/converters/format_converter.rb +1 -1
  45. data/lib/fontisan/converters/woff2_encoder.rb +8 -6
  46. data/lib/fontisan/converters/woff_writer.rb +6 -5
  47. data/lib/fontisan/engine.rb +350 -0
  48. data/lib/fontisan/ffi/client.rb +121 -0
  49. data/lib/fontisan/ffi.rb +343 -0
  50. data/lib/fontisan/ffi_platform.rb +24 -0
  51. data/lib/fontisan/font_loader.rb +5 -1
  52. data/lib/fontisan/font_writer.rb +13 -1
  53. data/lib/fontisan/native.rb +231 -0
  54. data/lib/fontisan/reference/table_digests.rb +251 -0
  55. data/lib/fontisan/reference.rb +619 -0
  56. data/lib/fontisan/scan.rb +372 -0
  57. data/lib/fontisan/sfnt_directory.rb +41 -0
  58. data/lib/fontisan/sfnt_font.rb +16 -6
  59. data/lib/fontisan/subset/builder.rb +2 -1
  60. data/lib/fontisan/tables/cff/charstring.rb +16 -0
  61. data/lib/fontisan/tables/cff.rb +15 -5
  62. data/lib/fontisan/tables/cmap.rb +11 -1
  63. data/lib/fontisan/tables/gpos.rb +7 -0
  64. data/lib/fontisan/tables/gsub.rb +7 -0
  65. data/lib/fontisan/tables/layout_common.rb +172 -0
  66. data/lib/fontisan/tables/name.rb +10 -4
  67. data/lib/fontisan/utilities/brotli_wrapper.rb +39 -4
  68. data/lib/fontisan/validators/opentype_validator.rb +2 -0
  69. data/lib/fontisan/variable/instancer.rb +1 -0
  70. data/lib/fontisan/variable/static_font_builder.rb +4 -1
  71. data/lib/fontisan/variable/table_updater.rb +4 -2
  72. data/lib/fontisan/variation/instancing/true_type_instancer.rb +14 -6
  73. data/lib/fontisan/version.rb +1 -1
  74. data/lib/fontisan/woff2/collection_decoder.rb +15 -1
  75. data/lib/fontisan/woff2/collection_encoder.rb +29 -6
  76. data/lib/fontisan/woff2/encoder_rules.rb +12 -4
  77. data/lib/fontisan/woff2/glyf_loca_reconstruct.rb +1 -1
  78. data/lib/fontisan/woff2_collection.rb +7 -5
  79. data/lib/fontisan/woff2_font.rb +12 -3
  80. data/lib/fontisan/woff_container.rb +83 -0
  81. data/lib/fontisan/woff_font.rb +24 -20
  82. data/lib/fontisan.rb +24 -1
  83. data/rakelib/bench.rake +9 -0
  84. data/rakelib/cdylib.rake +39 -0
  85. data/rakelib/corpus.rake +272 -0
  86. data/rakelib/corpus_convert.rake +43 -0
  87. data/rakelib/corpus_goldens.rake +22 -0
  88. data/rakelib/corpus_malformed.rake +20 -0
  89. data/rakelib/corpus_scan.rake +24 -0
  90. data/rakelib/java.rake +61 -0
  91. data/rakelib/native.rake +43 -0
  92. data/scripts/build-woff2.sh +57 -0
  93. data/scripts/corpus/generate_convert_goldens.rb +478 -0
  94. data/scripts/corpus/generate_goldens.rb +282 -0
  95. data/scripts/corpus/generate_malformed_goldens.rb +254 -0
  96. data/scripts/corpus/generate_scan_goldens.rb +98 -0
  97. data/scripts/tier_fallback.rb +219 -0
  98. data/scripts/tier_smoke.rb +64 -0
  99. metadata +68 -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.47
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-03 00:00:00.000000000 Z
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
@@ -864,9 +912,26 @@ files:
864
912
  - lib/fontisan/woff2/uint255.rb
865
913
  - lib/fontisan/woff2_collection.rb
866
914
  - lib/fontisan/woff2_font.rb
915
+ - lib/fontisan/woff_container.rb
867
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
868
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
869
932
  - scripts/measure_optimization.rb
933
+ - scripts/tier_fallback.rb
934
+ - scripts/tier_smoke.rb
870
935
  homepage: https://github.com/fontist/fontisan
871
936
  licenses:
872
937
  - BSD-2-Clause