fontisan 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +103 -0
- data/.rubocop_todo.yml +107 -318
- data/Gemfile +1 -1
- data/README.adoc +127 -17
- data/Rakefile +12 -7
- data/benchmark/variation_quick_bench.rb +1 -1
- data/lib/fontisan/base_collection.rb +5 -33
- data/lib/fontisan/cli.rb +45 -13
- data/lib/fontisan/collection/dfont_builder.rb +2 -1
- data/lib/fontisan/collection/shared_logic.rb +54 -0
- data/lib/fontisan/commands/convert_command.rb +2 -4
- data/lib/fontisan/commands/info_command.rb +3 -3
- data/lib/fontisan/commands/pack_command.rb +2 -1
- data/lib/fontisan/commands/validate_command.rb +157 -6
- data/lib/fontisan/converters/collection_converter.rb +22 -13
- data/lib/fontisan/converters/svg_generator.rb +2 -1
- data/lib/fontisan/converters/woff2_encoder.rb +6 -6
- data/lib/fontisan/converters/woff_writer.rb +3 -1
- data/lib/fontisan/dfont_collection.rb +84 -0
- data/lib/fontisan/font_loader.rb +9 -9
- data/lib/fontisan/formatters/text_formatter.rb +18 -14
- data/lib/fontisan/hints/hint_converter.rb +1 -1
- data/lib/fontisan/hints/hint_validator.rb +13 -10
- data/lib/fontisan/hints/truetype_instruction_analyzer.rb +15 -8
- data/lib/fontisan/hints/truetype_instruction_generator.rb +1 -1
- data/lib/fontisan/models/collection_validation_report.rb +104 -0
- data/lib/fontisan/models/font_report.rb +24 -0
- data/lib/fontisan/models/validation_report.rb +7 -2
- data/lib/fontisan/open_type_font.rb +2 -3
- data/lib/fontisan/optimizers/charstring_rewriter.rb +1 -1
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +6 -2
- data/lib/fontisan/subset/glyph_mapping.rb +2 -0
- data/lib/fontisan/subset/table_subsetter.rb +2 -2
- data/lib/fontisan/tables/cblc.rb +8 -4
- data/lib/fontisan/tables/cff/index.rb +2 -0
- data/lib/fontisan/tables/cff.rb +6 -3
- data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +1 -1
- data/lib/fontisan/tables/cff2.rb +1 -1
- data/lib/fontisan/tables/cmap.rb +5 -5
- data/lib/fontisan/tables/glyf.rb +8 -10
- data/lib/fontisan/tables/head.rb +3 -3
- data/lib/fontisan/tables/hhea.rb +4 -4
- data/lib/fontisan/tables/maxp.rb +2 -2
- data/lib/fontisan/tables/name.rb +1 -1
- data/lib/fontisan/tables/os2.rb +8 -8
- data/lib/fontisan/tables/post.rb +2 -2
- data/lib/fontisan/tables/sbix.rb +5 -4
- data/lib/fontisan/true_type_font.rb +2 -3
- data/lib/fontisan/utilities/checksum_calculator.rb +0 -44
- data/lib/fontisan/validation/collection_validator.rb +4 -2
- data/lib/fontisan/validators/basic_validator.rb +11 -21
- data/lib/fontisan/validators/font_book_validator.rb +29 -50
- data/lib/fontisan/validators/opentype_validator.rb +24 -28
- data/lib/fontisan/validators/validator.rb +87 -66
- data/lib/fontisan/validators/web_font_validator.rb +16 -21
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/glyf_transformer.rb +31 -8
- data/lib/fontisan/woff2/hmtx_transformer.rb +2 -1
- data/lib/fontisan/woff2/table_transformer.rb +4 -2
- data/lib/fontisan/woff2_font.rb +4 -2
- data/lib/fontisan/woff_font.rb +2 -2
- data/lib/fontisan.rb +2 -2
- data/scripts/compare_stack_aware.rb +1 -1
- data/scripts/measure_optimization.rb +1 -2
- metadata +5 -2
data/README.adoc
CHANGED
|
@@ -184,7 +184,7 @@ Family: Noto Serif CJK JP ExtraLight
|
|
|
184
184
|
====
|
|
185
185
|
[source,shell]
|
|
186
186
|
----
|
|
187
|
-
$ fontisan info spec/fixtures/fonts/MonaSans/
|
|
187
|
+
$ fontisan info spec/fixtures/fonts/MonaSans/variable/MonaSans[wdth,wght].ttf --brief
|
|
188
188
|
|
|
189
189
|
Font type: TrueType (Variable)
|
|
190
190
|
Family: Mona Sans ExtraLight
|
|
@@ -1034,26 +1034,19 @@ structures, usability, instructions, and glyphs.
|
|
|
1034
1034
|
|
|
1035
1035
|
=== Predefined profiles
|
|
1036
1036
|
|
|
1037
|
-
Fontisan includes
|
|
1037
|
+
Fontisan includes validation profiles for different use cases:
|
|
1038
1038
|
|
|
1039
|
-
`indexability`:: Fast
|
|
1040
|
-
BasicValidator with 8 essential checks. Loading mode: metadata.
|
|
1039
|
+
`indexability`:: Fast font discovery and indexing (~5x faster, 8 checks, metadata-only)
|
|
1041
1040
|
|
|
1042
|
-
`usability`::
|
|
1043
|
-
26 checks including macOS Font Book compatibility. Loading mode: full.
|
|
1041
|
+
`usability`:: Font installation compatibility (26 checks, macOS Font Book focused)
|
|
1044
1042
|
|
|
1045
|
-
`production`:: Comprehensive quality checks
|
|
1046
|
-
profile). Uses OpenTypeValidator with 36 checks for OpenType spec compliance.
|
|
1047
|
-
Loading mode: full.
|
|
1043
|
+
`production`:: Comprehensive production quality (36 checks, OpenType spec compliance - default)
|
|
1048
1044
|
|
|
1049
|
-
`web`:: Web font embedding
|
|
1050
|
-
with 18 checks for web deployment. Loading mode: full.
|
|
1045
|
+
`web`:: Web font embedding readiness (18 checks for web deployment)
|
|
1051
1046
|
|
|
1052
|
-
`spec_compliance`:: Full OpenType specification compliance
|
|
1053
|
-
Uses OpenTypeValidator with info-level severity for comprehensive analysis.
|
|
1054
|
-
Loading mode: full.
|
|
1047
|
+
`spec_compliance`:: Full OpenType specification compliance (detailed analysis mode)
|
|
1055
1048
|
|
|
1056
|
-
`default`:: Alias for the production profile
|
|
1049
|
+
`default`:: Alias for the production profile
|
|
1057
1050
|
|
|
1058
1051
|
|
|
1059
1052
|
=== Command-line usage
|
|
@@ -1154,6 +1147,80 @@ production, web, spec_compliance, default)
|
|
|
1154
1147
|
|
|
1155
1148
|
-e, --exclude CHECKS:: Exclude specific checks (comma-separated list)
|
|
1156
1149
|
|
|
1150
|
+
=== Collection validation
|
|
1151
|
+
|
|
1152
|
+
The `validate` command automatically detects and validates font collections (TTC, OTC, dfont).
|
|
1153
|
+
When validating a collection, fontisan validates all fonts in the collection and displays
|
|
1154
|
+
per-font results with an overall summary.
|
|
1155
|
+
|
|
1156
|
+
.Validation profiles with collections
|
|
1157
|
+
[example]
|
|
1158
|
+
====
|
|
1159
|
+
All validation profiles work with font collections. The selected profile determines:
|
|
1160
|
+
|
|
1161
|
+
* **Which tables are loaded** - metadata vs full mode
|
|
1162
|
+
* **Which checks are performed** - number and type of validations
|
|
1163
|
+
* **Performance characteristics** - indexability is ~5x faster than production
|
|
1164
|
+
|
|
1165
|
+
For large collections (CJK fonts with 30+ fonts), use the `indexability` profile for fast
|
|
1166
|
+
validation when scanning for font discovery, or `production` for comprehensive quality
|
|
1167
|
+
checks.
|
|
1168
|
+
|
|
1169
|
+
[source,shell]
|
|
1170
|
+
----
|
|
1171
|
+
# Quick validation for indexing (metadata mode, 8 checks, ~5x faster)
|
|
1172
|
+
$ fontisan validate /path/to/font.ttc -t indexability
|
|
1173
|
+
|
|
1174
|
+
# Comprehensive validation (full mode, 37 checks)
|
|
1175
|
+
$ fontisan validate /path/to/font.ttc -t production
|
|
1176
|
+
|
|
1177
|
+
# Web font readiness validation
|
|
1178
|
+
$ fontisan validate /path/to/font.ttc -t web
|
|
1179
|
+
----
|
|
1180
|
+
|
|
1181
|
+
The collection validation output includes:
|
|
1182
|
+
|
|
1183
|
+
* *Collection header* - Path, type, and number of fonts
|
|
1184
|
+
* *Summary* - Total errors, warnings, and info across all fonts
|
|
1185
|
+
* *Per-font sections* - Individual validation results for each font with:
|
|
1186
|
+
** Font index and name
|
|
1187
|
+
** Font path in `collection.ttc:index` format
|
|
1188
|
+
** Individual font status (VALID/INVALID/VALID_WITH_WARNINGS)
|
|
1189
|
+
** Font-specific errors and warnings
|
|
1190
|
+
** Exit codes* - For collections, uses the "worst" status across all fonts:
|
|
1191
|
+
** 0 = All fonts valid
|
|
1192
|
+
** 2 = Any font has fatal errors
|
|
1193
|
+
** 3 = Any font has errors (and no fatal)
|
|
1194
|
+
** 4 = Any font has warnings (and no errors)
|
|
1195
|
+
** 5 = Any font has info issues (and no errors or warnings)
|
|
1196
|
+
====
|
|
1197
|
+
|
|
1198
|
+
.Validate a font collection
|
|
1199
|
+
[example]
|
|
1200
|
+
====
|
|
1201
|
+
[source,shell]
|
|
1202
|
+
----
|
|
1203
|
+
$ fontisan validate /path/to/font.ttc
|
|
1204
|
+
Collection: /path/to/font.ttc
|
|
1205
|
+
Type: TTC
|
|
1206
|
+
Fonts: 4
|
|
1207
|
+
|
|
1208
|
+
Summary:
|
|
1209
|
+
Total Errors: 14
|
|
1210
|
+
Total Warnings: 8
|
|
1211
|
+
Total Info: 0
|
|
1212
|
+
|
|
1213
|
+
=== Font 0: Lucida Grande ===
|
|
1214
|
+
Font: /path/to/font.ttc:0
|
|
1215
|
+
Status: INVALID
|
|
1216
|
+
...
|
|
1217
|
+
|
|
1218
|
+
=== Font 1: Lucida Grande Bold ===
|
|
1219
|
+
Font: /path/to/font.ttc:1
|
|
1220
|
+
Status: INVALID
|
|
1221
|
+
...
|
|
1222
|
+
----
|
|
1223
|
+
====
|
|
1157
1224
|
|
|
1158
1225
|
=== Ruby API usage
|
|
1159
1226
|
|
|
@@ -1241,7 +1308,7 @@ validator = Fontisan::Validators::OpenTypeValidator.new
|
|
|
1241
1308
|
report = validator.validate(font)
|
|
1242
1309
|
|
|
1243
1310
|
# Check individual results
|
|
1244
|
-
name_check = report.result_of(:
|
|
1311
|
+
name_check = report.result_of(:name_validation)
|
|
1245
1312
|
puts name_check.passed?
|
|
1246
1313
|
puts name_check.severity
|
|
1247
1314
|
----
|
|
@@ -1308,7 +1375,7 @@ font = Fontisan::FontLoader.load('font.ttf')
|
|
|
1308
1375
|
validator = MyFontValidator.new
|
|
1309
1376
|
report = validator.validate(font)
|
|
1310
1377
|
|
|
1311
|
-
# Check results
|
|
1378
|
+
# Check validation results
|
|
1312
1379
|
puts report.valid? # => true/false
|
|
1313
1380
|
puts report.status # => "valid", "valid_with_warnings", "invalid"
|
|
1314
1381
|
puts report.summary.errors # => number of errors
|
|
@@ -1576,6 +1643,14 @@ dfont (Apple suitcase)::: Apple proprietary format storing complete Mac font
|
|
|
1576
1643
|
suitcase resources in the data fork. Supports any SFNT fonts (TTF, OTF, or
|
|
1577
1644
|
mixed). Mac OS X specific. File extension: `.dfont`
|
|
1578
1645
|
|
|
1646
|
+
Fontist returns the appropriate collection type based on the font data:
|
|
1647
|
+
|
|
1648
|
+
* Examines font data within collection to determine type (TTC vs OTC)
|
|
1649
|
+
* TTC contains fonts with TrueType outlines (glyf table)
|
|
1650
|
+
* OTC contains fonts with CFF outlines (CFF table)
|
|
1651
|
+
* If ANY font in the collection has CFF outlines, use OpenTypeCollection
|
|
1652
|
+
* Only use TrueTypeCollection if ALL fonts have TrueType outlines
|
|
1653
|
+
|
|
1579
1654
|
|
|
1580
1655
|
=== Collection compatibility
|
|
1581
1656
|
|
|
@@ -2076,6 +2151,41 @@ Fontisan can:
|
|
|
2076
2151
|
* Importing and generation PNG block ruler layers
|
|
2077
2152
|
|
|
2078
2153
|
|
|
2154
|
+
== Testing
|
|
2155
|
+
|
|
2156
|
+
=== General
|
|
2157
|
+
|
|
2158
|
+
Fontisan has a comprehensive test suite covering all font operations, formats,
|
|
2159
|
+
and features.
|
|
2160
|
+
|
|
2161
|
+
=== Running tests
|
|
2162
|
+
|
|
2163
|
+
[source,shell]
|
|
2164
|
+
----
|
|
2165
|
+
# Run full test suite
|
|
2166
|
+
bundle exec rspec
|
|
2167
|
+
|
|
2168
|
+
# Run with documentation format
|
|
2169
|
+
bundle exec rspec --format documentation
|
|
2170
|
+
|
|
2171
|
+
# Run specific file
|
|
2172
|
+
bundle exec rspec spec/fontisan/tables/maxp_spec.rb
|
|
2173
|
+
----
|
|
2174
|
+
|
|
2175
|
+
=== Test fixtures
|
|
2176
|
+
|
|
2177
|
+
All required font fixtures are automatically downloaded before tests run. The
|
|
2178
|
+
test suite uses a centralized fixture configuration system that ensures all
|
|
2179
|
+
necessary fonts are available.
|
|
2180
|
+
|
|
2181
|
+
[source,shell]
|
|
2182
|
+
----
|
|
2183
|
+
# Manual fixture management (if needed)
|
|
2184
|
+
bundle exec rake fixtures:download # Download all test fonts
|
|
2185
|
+
bundle exec rake fixtures:clean # Remove downloaded fonts
|
|
2186
|
+
----
|
|
2187
|
+
|
|
2188
|
+
|
|
2079
2189
|
== Copyright and license
|
|
2080
2190
|
|
|
2081
2191
|
Copyright https://www.ribose.com[Ribose].
|
data/Rakefile
CHANGED
|
@@ -8,6 +8,7 @@ require "rubocop/rake_task"
|
|
|
8
8
|
|
|
9
9
|
RuboCop::RakeTask.new
|
|
10
10
|
|
|
11
|
+
# rubocop:disable Metrics/BlockLength
|
|
11
12
|
namespace :fixtures do
|
|
12
13
|
# Load centralized fixture configuration
|
|
13
14
|
require_relative "spec/support/fixture_fonts"
|
|
@@ -99,8 +100,13 @@ namespace :fixtures do
|
|
|
99
100
|
end
|
|
100
101
|
end
|
|
101
102
|
|
|
103
|
+
# Compute download task prerequisites (marker files for non-skipped fonts)
|
|
104
|
+
download_prerequisites = fonts.values.reject do |config|
|
|
105
|
+
config[:skip_download]
|
|
106
|
+
end.map { |config| config[:marker] }
|
|
107
|
+
|
|
102
108
|
desc "Download all test fixture fonts"
|
|
103
|
-
task download:
|
|
109
|
+
task download: download_prerequisites
|
|
104
110
|
|
|
105
111
|
desc "Clean downloaded fixtures"
|
|
106
112
|
task :clean do
|
|
@@ -112,17 +118,16 @@ namespace :fixtures do
|
|
|
112
118
|
FileUtils.rm_f(config[:marker])
|
|
113
119
|
puts "[fixtures:clean] Removed #{config[:marker]}"
|
|
114
120
|
end
|
|
115
|
-
|
|
121
|
+
elsif File.exist?(config[:target_dir])
|
|
116
122
|
# For archives, delete the entire target directory
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
puts "[fixtures:clean] Removed #{config[:target_dir]}"
|
|
121
|
-
end
|
|
123
|
+
puts "[fixtures:clean] Removing #{config[:target_dir]}..."
|
|
124
|
+
FileUtils.rm_rf(config[:target_dir])
|
|
125
|
+
puts "[fixtures:clean] Removed #{config[:target_dir]}"
|
|
122
126
|
end
|
|
123
127
|
end
|
|
124
128
|
end
|
|
125
129
|
end
|
|
130
|
+
# rubocop:enable Metrics/BlockLength
|
|
126
131
|
|
|
127
132
|
# RSpec task depends on fixtures
|
|
128
133
|
RSpec::Core::RakeTask.new(spec: "fixtures:download")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "bindata"
|
|
4
4
|
require_relative "constants"
|
|
5
|
+
require_relative "collection/shared_logic"
|
|
5
6
|
|
|
6
7
|
module Fontisan
|
|
7
8
|
# Abstract base class for font collections (TTC/OTC)
|
|
@@ -29,6 +30,8 @@ module Fontisan
|
|
|
29
30
|
# end
|
|
30
31
|
# end
|
|
31
32
|
class BaseCollection < BinData::Record
|
|
33
|
+
include Collection::SharedLogic
|
|
34
|
+
|
|
32
35
|
endian :big
|
|
33
36
|
|
|
34
37
|
string :tag, length: 4, assert: "ttcf"
|
|
@@ -251,8 +254,6 @@ module Fontisan
|
|
|
251
254
|
# @param io [IO] Open file handle
|
|
252
255
|
# @return [TableSharingInfo] Sharing statistics
|
|
253
256
|
def calculate_table_sharing(io)
|
|
254
|
-
require_relative "models/table_sharing_info"
|
|
255
|
-
|
|
256
257
|
font_class = self.class.font_class
|
|
257
258
|
|
|
258
259
|
# Extract all fonts
|
|
@@ -260,37 +261,8 @@ module Fontisan
|
|
|
260
261
|
font_class.from_collection(io, offset)
|
|
261
262
|
end
|
|
262
263
|
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
total_table_size = 0
|
|
266
|
-
|
|
267
|
-
fonts.each do |font|
|
|
268
|
-
font.tables.each do |entry|
|
|
269
|
-
key = entry.checksum
|
|
270
|
-
size = entry.table_length
|
|
271
|
-
table_map[key] ||= size
|
|
272
|
-
total_table_size += size
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Count unique vs shared
|
|
277
|
-
unique_tables = table_map.size
|
|
278
|
-
total_tables = fonts.sum { |f| f.tables.length }
|
|
279
|
-
shared_tables = total_tables - unique_tables
|
|
280
|
-
|
|
281
|
-
# Calculate space saved
|
|
282
|
-
unique_size = table_map.values.sum
|
|
283
|
-
space_saved = total_table_size - unique_size
|
|
284
|
-
|
|
285
|
-
# Calculate sharing percentage
|
|
286
|
-
sharing_pct = total_tables.positive? ? (shared_tables.to_f / total_tables * 100).round(2) : 0.0
|
|
287
|
-
|
|
288
|
-
Models::TableSharingInfo.new(
|
|
289
|
-
shared_tables: shared_tables,
|
|
290
|
-
unique_tables: unique_tables,
|
|
291
|
-
sharing_percentage: sharing_pct,
|
|
292
|
-
space_saved_bytes: space_saved,
|
|
293
|
-
)
|
|
264
|
+
# Use shared logic for calculation
|
|
265
|
+
calculate_table_sharing_for_fonts(fonts)
|
|
294
266
|
end
|
|
295
267
|
end
|
|
296
268
|
end
|
data/lib/fontisan/cli.rb
CHANGED
|
@@ -367,18 +367,27 @@ module Fontisan
|
|
|
367
367
|
handle_error(e)
|
|
368
368
|
end
|
|
369
369
|
|
|
370
|
-
desc "validate FONT_FILE", "Validate font file"
|
|
370
|
+
desc "validate [FONT_FILE]", "Validate font file"
|
|
371
371
|
long_desc <<-DESC
|
|
372
372
|
Validate font file against quality checks and standards.
|
|
373
373
|
|
|
374
|
-
|
|
375
|
-
|
|
374
|
+
Supports individual fonts (TTF/OTF) and font collections (TTC/OTC/dfont).
|
|
375
|
+
For collections, validates each font and provides a summary report.
|
|
376
|
+
|
|
377
|
+
Profiles (-p/--profile):
|
|
378
|
+
indexability - Fast indexing validation (metadata only, 8 checks)
|
|
376
379
|
usability - Installation compatibility
|
|
377
380
|
production - Comprehensive quality (default)
|
|
378
381
|
web - Web font readiness
|
|
379
382
|
spec_compliance - OpenType spec compliance
|
|
380
383
|
default - Production profile (alias)
|
|
381
384
|
|
|
385
|
+
Collection validation with profiles:
|
|
386
|
+
When validating TTC/OTC/dfont collections, the selected profile
|
|
387
|
+
determines which tables are loaded and which checks are performed
|
|
388
|
+
for each font in the collection. Use 'indexability' for quick
|
|
389
|
+
validation or 'production' for comprehensive quality checks.
|
|
390
|
+
|
|
382
391
|
Return values (with -R/--return-value-results):
|
|
383
392
|
0 No results
|
|
384
393
|
1 Execution errors
|
|
@@ -389,25 +398,41 @@ module Fontisan
|
|
|
389
398
|
DESC
|
|
390
399
|
|
|
391
400
|
option :exclude, aliases: "-e", type: :array, desc: "Tests to exclude"
|
|
392
|
-
option :list, aliases: "-l", type: :boolean, desc: "List available
|
|
401
|
+
option :list, aliases: "-l", type: :boolean, desc: "List available profiles"
|
|
393
402
|
option :output, aliases: "-o", type: :string, desc: "Output file"
|
|
394
403
|
option :full_report, aliases: "-r", type: :boolean, desc: "Full report"
|
|
395
|
-
option :return_value_results, aliases: "-R", type: :boolean,
|
|
396
|
-
|
|
397
|
-
option :
|
|
404
|
+
option :return_value_results, aliases: "-R", type: :boolean,
|
|
405
|
+
desc: "Use return value for results"
|
|
406
|
+
option :summary_report, aliases: "-S", type: :boolean,
|
|
407
|
+
desc: "Summary report"
|
|
408
|
+
option :profile, aliases: "-p", type: :string, default: "default",
|
|
409
|
+
desc: "Validation profile"
|
|
398
410
|
option :table_report, aliases: "-T", type: :boolean, desc: "Tabular report"
|
|
399
411
|
option :verbose, aliases: "-v", type: :boolean, desc: "Verbose output"
|
|
400
|
-
option :suppress_warnings, aliases: "-W", type: :boolean,
|
|
412
|
+
option :suppress_warnings, aliases: "-W", type: :boolean,
|
|
413
|
+
desc: "Suppress warnings"
|
|
401
414
|
|
|
402
|
-
def validate(
|
|
415
|
+
def validate(*font_files)
|
|
403
416
|
if options[:list]
|
|
404
417
|
list_available_tests
|
|
405
418
|
return
|
|
406
419
|
end
|
|
407
420
|
|
|
421
|
+
# Validate argument count
|
|
422
|
+
if font_files.empty?
|
|
423
|
+
raise(Thor::Error, "FONT_FILE is required unless using --list")
|
|
424
|
+
elsif font_files.size > 1
|
|
425
|
+
raise(Thor::Error,
|
|
426
|
+
"Too many arguments. validate accepts only one font file.\n" \
|
|
427
|
+
"To validate multiple files, run validate separately for each.\n" \
|
|
428
|
+
"To validate all fonts in a collection, use: fontisan validate collection.ttc")
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
font_file = font_files.first
|
|
432
|
+
|
|
408
433
|
cmd = Commands::ValidateCommand.new(
|
|
409
434
|
input: font_file,
|
|
410
|
-
profile: options[:
|
|
435
|
+
profile: options[:profile],
|
|
411
436
|
exclude: options[:exclude] || [],
|
|
412
437
|
output: options[:output],
|
|
413
438
|
format: options[:format].to_sym,
|
|
@@ -416,11 +441,18 @@ module Fontisan
|
|
|
416
441
|
table_report: options[:table_report],
|
|
417
442
|
verbose: options[:verbose],
|
|
418
443
|
suppress_warnings: options[:suppress_warnings],
|
|
419
|
-
return_value_results: options[:return_value_results]
|
|
444
|
+
return_value_results: options[:return_value_results],
|
|
420
445
|
)
|
|
421
446
|
|
|
422
447
|
exit cmd.run
|
|
423
|
-
rescue => e
|
|
448
|
+
rescue Thor::Error => e
|
|
449
|
+
unless options[:quiet]
|
|
450
|
+
warn "ERROR: #{e.message}"
|
|
451
|
+
warn
|
|
452
|
+
help("validate")
|
|
453
|
+
end
|
|
454
|
+
exit 1
|
|
455
|
+
rescue StandardError => e
|
|
424
456
|
error "Validation failed: #{e.message}"
|
|
425
457
|
exit 1
|
|
426
458
|
end
|
|
@@ -546,7 +578,7 @@ module Fontisan
|
|
|
546
578
|
puts " Format: #{result[:format].upcase}"
|
|
547
579
|
puts " Fonts: #{result[:num_fonts]}"
|
|
548
580
|
puts " Size: #{format_size(result[:output_size] || result[:total_size])}"
|
|
549
|
-
if result[:space_savings]
|
|
581
|
+
if result[:space_savings]&.positive?
|
|
550
582
|
puts " Space saved: #{format_size(result[:space_savings])}"
|
|
551
583
|
puts " Sharing: #{result[:statistics][:sharing_percentage]}%"
|
|
552
584
|
end
|
|
@@ -86,7 +86,8 @@ module Fontisan
|
|
|
86
86
|
map_size = 28 + 2 + 8 + (sfnt_binaries.size * 12)
|
|
87
87
|
|
|
88
88
|
# Step 4: Build resource map
|
|
89
|
-
resource_map = build_resource_map(sfnt_binaries,
|
|
89
|
+
resource_map = build_resource_map(sfnt_binaries,
|
|
90
|
+
resource_data.bytesize, map_size)
|
|
90
91
|
|
|
91
92
|
# Step 5: Build header
|
|
92
93
|
header = build_header(resource_data.bytesize, resource_map.bytesize)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fontisan
|
|
4
|
+
module Collection
|
|
5
|
+
# Shared logic for font collection classes
|
|
6
|
+
#
|
|
7
|
+
# This module provides common functionality for all collection types
|
|
8
|
+
# (TTC, OTC, dfont) to maintain DRY principles.
|
|
9
|
+
module SharedLogic
|
|
10
|
+
# Calculate table sharing statistics
|
|
11
|
+
#
|
|
12
|
+
# Analyzes which tables are shared between fonts and calculates
|
|
13
|
+
# space savings from deduplication.
|
|
14
|
+
#
|
|
15
|
+
# @param fonts [Array<TrueTypeFont, OpenTypeFont>] Array of fonts
|
|
16
|
+
# @return [Models::TableSharingInfo] Sharing statistics
|
|
17
|
+
def calculate_table_sharing_for_fonts(fonts)
|
|
18
|
+
require_relative "../models/table_sharing_info"
|
|
19
|
+
|
|
20
|
+
# Build table hash map (checksum -> size)
|
|
21
|
+
table_map = {}
|
|
22
|
+
total_table_size = 0
|
|
23
|
+
|
|
24
|
+
fonts.each do |font|
|
|
25
|
+
font.tables.each do |entry|
|
|
26
|
+
key = entry.checksum
|
|
27
|
+
size = entry.table_length
|
|
28
|
+
table_map[key] ||= size
|
|
29
|
+
total_table_size += size
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Count unique vs shared
|
|
34
|
+
unique_tables = table_map.size
|
|
35
|
+
total_tables = fonts.sum { |f| f.tables.length }
|
|
36
|
+
shared_tables = total_tables - unique_tables
|
|
37
|
+
|
|
38
|
+
# Calculate space saved
|
|
39
|
+
unique_size = table_map.values.sum
|
|
40
|
+
space_saved = total_table_size - unique_size
|
|
41
|
+
|
|
42
|
+
# Calculate sharing percentage
|
|
43
|
+
sharing_pct = total_tables.positive? ? (shared_tables.to_f / total_tables * 100).round(2) : 0.0
|
|
44
|
+
|
|
45
|
+
Models::TableSharingInfo.new(
|
|
46
|
+
shared_tables: shared_tables,
|
|
47
|
+
unique_tables: unique_tables,
|
|
48
|
+
sharing_percentage: sharing_pct,
|
|
49
|
+
space_saved_bytes: space_saved,
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -73,7 +73,7 @@ module Fontisan
|
|
|
73
73
|
@instance_index = opts[:instance_index]
|
|
74
74
|
@preserve_variation = opts[:preserve_variation]
|
|
75
75
|
@preserve_hints = opts.fetch(:preserve_hints, false)
|
|
76
|
-
@collection_target_format = opts.fetch(:target_format,
|
|
76
|
+
@collection_target_format = opts.fetch(:target_format, "preserve").to_s
|
|
77
77
|
@validate = !opts[:no_validate]
|
|
78
78
|
end
|
|
79
79
|
|
|
@@ -196,7 +196,7 @@ module Fontisan
|
|
|
196
196
|
output: @output_path,
|
|
197
197
|
target_format: @collection_target_format,
|
|
198
198
|
verbose: @options[:verbose],
|
|
199
|
-
}
|
|
199
|
+
},
|
|
200
200
|
)
|
|
201
201
|
|
|
202
202
|
# Display results
|
|
@@ -245,8 +245,6 @@ module Fontisan
|
|
|
245
245
|
:otc
|
|
246
246
|
when ".dfont"
|
|
247
247
|
:dfont
|
|
248
|
-
else
|
|
249
|
-
nil
|
|
250
248
|
end
|
|
251
249
|
end
|
|
252
250
|
end
|
|
@@ -311,7 +311,7 @@ module Fontisan
|
|
|
311
311
|
start_glyph_id: strike_rec.start_glyph_index,
|
|
312
312
|
end_glyph_id: strike_rec.end_glyph_index,
|
|
313
313
|
bit_depth: strike_rec.bit_depth,
|
|
314
|
-
num_glyphs: strike_rec.glyph_range.size
|
|
314
|
+
num_glyphs: strike_rec.glyph_range.size,
|
|
315
315
|
)
|
|
316
316
|
end
|
|
317
317
|
formats << "PNG" # CBDT typically contains PNG data
|
|
@@ -329,8 +329,8 @@ module Fontisan
|
|
|
329
329
|
ppem: strike[:ppem],
|
|
330
330
|
start_glyph_id: 0,
|
|
331
331
|
end_glyph_id: strike[:num_glyphs] - 1,
|
|
332
|
-
bit_depth: 32,
|
|
333
|
-
num_glyphs: strike[:num_glyphs]
|
|
332
|
+
bit_depth: 32, # sbix is typically 32-bit
|
|
333
|
+
num_glyphs: strike[:num_glyphs],
|
|
334
334
|
)
|
|
335
335
|
end
|
|
336
336
|
end
|
|
@@ -260,7 +260,8 @@ module Fontisan
|
|
|
260
260
|
# @return [Symbol] Parsed format (:ttc, :otc, or :dfont)
|
|
261
261
|
# @raise [ArgumentError] if format is invalid
|
|
262
262
|
def parse_format(format)
|
|
263
|
-
return format if format.is_a?(Symbol) && %i[ttc otc
|
|
263
|
+
return format if format.is_a?(Symbol) && %i[ttc otc
|
|
264
|
+
dfont].include?(format)
|
|
264
265
|
|
|
265
266
|
case format.to_s.downcase
|
|
266
267
|
when "ttc"
|