fontisan 0.2.4 → 0.2.6

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +168 -32
  3. data/README.adoc +673 -1091
  4. data/lib/fontisan/cli.rb +94 -13
  5. data/lib/fontisan/collection/dfont_builder.rb +315 -0
  6. data/lib/fontisan/commands/convert_command.rb +118 -7
  7. data/lib/fontisan/commands/pack_command.rb +129 -22
  8. data/lib/fontisan/commands/validate_command.rb +107 -151
  9. data/lib/fontisan/config/conversion_matrix.yml +175 -1
  10. data/lib/fontisan/constants.rb +8 -0
  11. data/lib/fontisan/converters/collection_converter.rb +438 -0
  12. data/lib/fontisan/converters/woff2_encoder.rb +7 -29
  13. data/lib/fontisan/dfont_collection.rb +185 -0
  14. data/lib/fontisan/font_loader.rb +91 -6
  15. data/lib/fontisan/models/validation_report.rb +227 -0
  16. data/lib/fontisan/parsers/dfont_parser.rb +192 -0
  17. data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
  18. data/lib/fontisan/tables/cmap.rb +82 -2
  19. data/lib/fontisan/tables/glyf.rb +118 -0
  20. data/lib/fontisan/tables/head.rb +60 -0
  21. data/lib/fontisan/tables/hhea.rb +74 -0
  22. data/lib/fontisan/tables/maxp.rb +60 -0
  23. data/lib/fontisan/tables/name.rb +76 -0
  24. data/lib/fontisan/tables/os2.rb +113 -0
  25. data/lib/fontisan/tables/post.rb +57 -0
  26. data/lib/fontisan/true_type_font.rb +8 -46
  27. data/lib/fontisan/validation/collection_validator.rb +265 -0
  28. data/lib/fontisan/validators/basic_validator.rb +85 -0
  29. data/lib/fontisan/validators/font_book_validator.rb +130 -0
  30. data/lib/fontisan/validators/opentype_validator.rb +112 -0
  31. data/lib/fontisan/validators/profile_loader.rb +139 -0
  32. data/lib/fontisan/validators/validator.rb +484 -0
  33. data/lib/fontisan/validators/web_font_validator.rb +102 -0
  34. data/lib/fontisan/version.rb +1 -1
  35. data/lib/fontisan.rb +78 -6
  36. metadata +13 -12
  37. data/lib/fontisan/config/validation_rules.yml +0 -149
  38. data/lib/fontisan/validation/checksum_validator.rb +0 -170
  39. data/lib/fontisan/validation/consistency_validator.rb +0 -197
  40. data/lib/fontisan/validation/structure_validator.rb +0 -198
  41. data/lib/fontisan/validation/table_validator.rb +0 -158
  42. data/lib/fontisan/validation/validator.rb +0 -152
  43. data/lib/fontisan/validation/variable_font_validator.rb +0 -218
  44. data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
  45. data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
  46. data/lib/fontisan/validation/woff2_validator.rb +0 -248
data/README.adoc CHANGED
@@ -10,7 +10,7 @@ Fontisan is a Ruby gem providing font analysis tools and utilities.
10
10
 
11
11
  It is designed as a pure Ruby implementation with full object-oriented
12
12
  architecture, supporting extraction of information from OpenType and TrueType
13
- fonts (OTF, TTF, TTC).
13
+ fonts (OTF, TTF, OTC, TTC, dfont).
14
14
 
15
15
  The gem provides both a Ruby library API and a command-line interface, with
16
16
  structured output formats (YAML, JSON, text) via lutaml-model.
@@ -71,6 +71,7 @@ gem install fontisan
71
71
  * Font validation with multiple severity levels
72
72
  * Collection management (pack/unpack TTC/OTC files with table deduplication)
73
73
  * Support for TTF, OTF, TTC, OTC font formats (production ready)
74
+ * Apple legacy font support: 'true' signature TrueType fonts and dfont (Data Fork Font) format
74
75
  * WOFF format support (reading complete, writing functional, pending full integration)
75
76
  * WOFF2 format support (reading complete with table transformations, writing planned)
76
77
  * SVG font generation (complete)
@@ -100,12 +101,13 @@ and font metrics.
100
101
 
101
102
  [source,shell]
102
103
  ----
104
+
103
105
  $ fontisan info FONT_FILE [--format FORMAT] [--brief]
104
106
  ----
105
107
 
106
108
  Where,
107
109
 
108
- `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTF)
110
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
109
111
  `FORMAT`:: Output format: `text` (default), `json`, or `yaml`
110
112
  `--brief`:: Show only basic font information
111
113
 
@@ -366,7 +368,7 @@ $ fontisan tables FONT_FILE [--format FORMAT]
366
368
 
367
369
  Where,
368
370
 
369
- `FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
371
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
370
372
  `FORMAT`:: Output format: `text` (default), `json`, or `yaml`
371
373
 
372
374
  .List of OpenType tables in Libertinus Serif Regular
@@ -424,6 +426,7 @@ tables:
424
426
  length: 17870
425
427
  offset: 542992
426
428
  checksum: 701383168
429
+ ...
427
430
  ----
428
431
  ====
429
432
 
@@ -446,7 +449,7 @@ $ fontisan glyphs FONT_FILE [--format FORMAT]
446
449
 
447
450
  Where,
448
451
 
449
- `FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
452
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
450
453
  `FORMAT`:: Output format: `text` (default), `json`, or `yaml`
451
454
 
452
455
 
@@ -461,7 +464,7 @@ $ fontisan glyphs spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
461
464
  [source,text]
462
465
  ----
463
466
  Glyph count: 2731
464
- Source: post_2.0
467
+ Source: post-2.0
465
468
 
466
469
  Glyph names:
467
470
  0 .notdef
@@ -505,9 +508,9 @@ Syntax:
505
508
  $ fontisan unicode FONT_FILE [--format FORMAT]
506
509
  ----
507
510
 
508
- Where,
511
+ Where:
509
512
 
510
- `FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
513
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
511
514
  `FORMAT`:: Output format: `text` (default), `json`, or `yaml`
512
515
 
513
516
 
@@ -734,10 +737,8 @@ Dry-run mode: Preview of instance generation
734
737
  Coordinates:
735
738
  wght: 700.0
736
739
 
737
- Output would be written to: variable-instance.ttf
738
- Output
739
-
740
- format: same as input
740
+ Output file: variable-instance.ttf
741
+ Format: same as input
741
742
 
742
743
  Use without --dry-run to actually generate the instance.
743
744
  ----
@@ -799,7 +800,7 @@ $ fontisan scripts FONT_FILE [--format FORMAT]
799
800
 
800
801
  Where,
801
802
 
802
- `FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
803
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
803
804
  `FORMAT`:: Output format: `text` (default), `json`, or `yaml`
804
805
 
805
806
 
@@ -842,7 +843,7 @@ $ fontisan features FONT_FILE [--script SCRIPT] [--format FORMAT]
842
843
 
843
844
  Where,
844
845
 
845
- `FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
846
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
846
847
  `SCRIPT`:: Optional 4-character script tag (e.g., `latn`, `cyrl`, `arab`). If not specified, shows features for all scripts
847
848
  `FORMAT`:: Output format: `text` (default), `json`, or `yaml`
848
849
 
@@ -939,7 +940,7 @@ $ fontisan dump-table FONT_FILE TABLE_TAG
939
940
 
940
941
  Where,
941
942
 
942
- `FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
943
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
943
944
  `TABLE_TAG`:: Four-character table tag (e.g., `name`, `head`, `GSUB`, `GPOS`)
944
945
 
945
946
 
@@ -977,7 +978,7 @@ $ fontisan export FONT_FILE [--output FILE] [--format FORMAT] [--tables TABLES]
977
978
 
978
979
  Where,
979
980
 
980
- `FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
981
+ `FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTC, dfont)
981
982
  `--output FILE`:: Output file path (default: stdout)
982
983
  `--format FORMAT`:: Export format: `yaml` (default), `json`, or `ttx`
983
984
  `--tables TABLES`:: Specific tables to export (space-separated list)
@@ -1020,6 +1021,488 @@ Uses base64 encoding for binary data instead of hexadecimal, useful for
1020
1021
  JSON-based workflows.
1021
1022
  ====
1022
1023
 
1024
+ == Font validation
1025
+
1026
+ === General
1027
+
1028
+ Fontisan provides validation functionality to ensure font quality, structural
1029
+ integrity, and compliance with various standards.
1030
+
1031
+ The validation framework allows developers to create custom validators using a
1032
+ declarative DSL. Validators can perform checks on font tables, fields,
1033
+ structures, usability, instructions, and glyphs.
1034
+
1035
+ === Predefined profiles
1036
+
1037
+ Fontisan includes several predefined validation profiles for common use cases:
1038
+
1039
+ `indexability`:: Fast validation for font discovery and indexing (< 50ms). Uses
1040
+ BasicValidator with 8 essential checks. Loading mode: metadata.
1041
+
1042
+ `usability`:: Basic usability for font installation. Uses FontBookValidator with
1043
+ 26 checks including macOS Font Book compatibility. Loading mode: full.
1044
+
1045
+ `production`:: Comprehensive quality checks for production fonts (default
1046
+ profile). Uses OpenTypeValidator with 36 checks for OpenType spec compliance.
1047
+ Loading mode: full.
1048
+
1049
+ `web`:: Web font embedding and optimization validation. Uses WebFontValidator
1050
+ with 18 checks for web deployment. Loading mode: full.
1051
+
1052
+ `spec_compliance`:: Full OpenType specification compliance with detailed checks.
1053
+ Uses OpenTypeValidator with info-level severity for comprehensive analysis.
1054
+ Loading mode: full.
1055
+
1056
+ `default`:: Alias for the production profile.
1057
+
1058
+
1059
+ === Command-line usage
1060
+
1061
+ ==== Validate a font file against a predefined profile
1062
+
1063
+ .Validate with default profile
1064
+ [example]
1065
+ ====
1066
+ [source,shell]
1067
+ ----
1068
+ $ fontisan validate font.ttf
1069
+ ----
1070
+ ====
1071
+
1072
+ .Validate for web use
1073
+ [example]
1074
+ ====
1075
+ [source,shell]
1076
+ ----
1077
+ $ fontisan validate font.ttf -t web
1078
+ ----
1079
+ ====
1080
+
1081
+ .List available profiles
1082
+ [example]
1083
+ ====
1084
+ [source,shell]
1085
+ ----
1086
+ $ fontisan validate --list
1087
+ Available validation profiles:
1088
+ indexability - Fast validation for font discovery and indexing
1089
+ usability - Basic usability for installation
1090
+ production - Comprehensive quality checks
1091
+ web - Web embedding and optimization
1092
+ spec_compliance - Full OpenType spec compliance
1093
+ default - Default validation profile (alias for production)
1094
+ ----
1095
+ ====
1096
+
1097
+ .Full report to file
1098
+ [example]
1099
+ ====
1100
+ [source,shell]
1101
+ ----
1102
+ $ fontisan validate font.ttf -t production -r -o report.txt
1103
+ ----
1104
+ ====
1105
+
1106
+ .Summary with return value
1107
+ [example]
1108
+ ====
1109
+ [source,shell]
1110
+ ----
1111
+ $ fontisan validate font.ttf -S -R
1112
+ 0 errors, 2 warnings, 0 info
1113
+ $ echo $?
1114
+ 4 # Exit code 4 indicates warnings found
1115
+ ----
1116
+ ====
1117
+
1118
+ .Table format output
1119
+ [example]
1120
+ ====
1121
+ [source,shell]
1122
+ ----
1123
+ $ fontisan validate font.ttf -T
1124
+ CHECK_ID | STATUS | SEVERITY | TABLE
1125
+ ------------------------------------------------------------
1126
+ required_tables | PASS | error | N/A
1127
+ name_version | PASS | error | name
1128
+ family_name | PASS | error | name
1129
+ ...
1130
+ ----
1131
+ ====
1132
+
1133
+ ==== CLI options
1134
+
1135
+ -t, --test-list PROFILE:: Select validation profile (indexability, usability,
1136
+ production, web, spec_compliance, default)
1137
+
1138
+ -l, --list:: List available validation profiles
1139
+
1140
+ -o, --output FILE:: Write report to file instead of stdout
1141
+
1142
+ -r, --full-report:: Generate full detailed report
1143
+
1144
+ -R, --return-value-results:: Use return value to indicate results (0=none,
1145
+ 1=error, 2=fatal, 3=major, 4=minor, 5=info)
1146
+
1147
+ -S, --summary-report:: Generate brief summary report
1148
+
1149
+ -T, --table-report:: Generate tabular format report
1150
+
1151
+ -v, --verbose:: Enable verbose output
1152
+
1153
+ -w, --suppress-warnings:: Suppress warning output
1154
+
1155
+ -e, --exclude CHECKS:: Exclude specific checks (comma-separated list)
1156
+
1157
+
1158
+ === Ruby API usage
1159
+
1160
+ ==== Architecture
1161
+
1162
+ The validation framework in Ruby consists of:
1163
+
1164
+ DSL base class:: `Fontisan::Validators::Validator` provides a declarative syntax
1165
+ for defining validation checks
1166
+
1167
+ Table validation helpers::
1168
+ 56 helper methods across 8 core OpenType tables (name, head, maxp, hhea, glyf,
1169
+ cmap, post, OS/2) that perform specific validation checks
1170
+
1171
+ ValidationReport::
1172
+ Structured reports with individual check results, severity levels, and
1173
+ comprehensive issue tracking
1174
+
1175
+ ==== Using predefined profiles
1176
+
1177
+ .Validate with Ruby API
1178
+ [example]
1179
+ ====
1180
+ [source,ruby]
1181
+ ----
1182
+ require 'fontisan'
1183
+
1184
+ # Validate with default profile (production)
1185
+ report = Fontisan.validate('font.ttf')
1186
+ puts report.valid? # => true or false
1187
+
1188
+ # Validate with specific profile
1189
+ report = Fontisan.validate('font.ttf', profile: :web)
1190
+ puts "Errors: #{report.summary.errors}"
1191
+ puts "Warnings: #{report.summary.warnings}"
1192
+
1193
+ # Check validation status
1194
+ if report.valid?
1195
+ puts "Font is valid for web use!"
1196
+ else
1197
+ puts "Font has #{report.summary.errors} errors"
1198
+ end
1199
+ ----
1200
+ ====
1201
+
1202
+ .Query validation results
1203
+ [example]
1204
+ ====
1205
+ [source,ruby]
1206
+ ----
1207
+ report = Fontisan.validate('font.ttf', profile: :production)
1208
+
1209
+ # Get issues by severity
1210
+ fatal_issues = report.fatal_errors
1211
+ error_issues = report.errors_only
1212
+ warning_issues = report.warnings_only
1213
+ info_issues = report.info_only
1214
+
1215
+ # Get issues by category
1216
+ table_issues = report.issues_by_category('table_validation')
1217
+
1218
+ # Get check results
1219
+ failed_ids = report.failed_check_ids
1220
+ pass_rate = report.pass_rate
1221
+
1222
+ # Export to different formats
1223
+ yaml_output = report.to_yaml
1224
+ json_output = report.to_json
1225
+ summary = report.to_summary # "2 errors, 3 warnings, 0 info"
1226
+ ----
1227
+ ====
1228
+
1229
+ .Use validators directly
1230
+ [example]
1231
+ ====
1232
+ [source,ruby]
1233
+ ----
1234
+ require 'fontisan'
1235
+
1236
+ # Load font
1237
+ font = Fontisan::FontLoader.load('font.ttf')
1238
+
1239
+ # Use specific validator
1240
+ validator = Fontisan::Validators::OpenTypeValidator.new
1241
+ report = validator.validate(font)
1242
+
1243
+ # Check individual results
1244
+ name_check = report.result_of(:name_version)
1245
+ puts name_check.passed?
1246
+ puts name_check.severity
1247
+ ----
1248
+ ====
1249
+
1250
+ ==== Using custom validators
1251
+
1252
+ ===== General
1253
+
1254
+ Custom validators inherit from `Fontisan::Validators::Validator` and define
1255
+ validation logic using the DSL.
1256
+
1257
+ The DSL provides 6 check methods for different validation types:
1258
+
1259
+ * `check_table` - Validate table-level properties
1260
+ * `check_field` - Validate specific field values
1261
+ * `check_structure` - Validate font structure and relationships
1262
+ * `check_usability` - Validate usability and best practices
1263
+ * `check_instructions` - Validate TrueType instructions/hinting
1264
+ * `check_glyphs` - Validate individual glyphs
1265
+
1266
+ Each check receives a unique ID, severity level (:info, :warning, :error,
1267
+ :fatal), and a validation block.
1268
+
1269
+ .Creating a custom validator
1270
+ [example]
1271
+ ====
1272
+ [source,ruby]
1273
+ ----
1274
+ require 'fontisan/validators/validator'
1275
+
1276
+ # Define a custom validator
1277
+ class MyFontValidator < Fontisan::Validators::Validator
1278
+ private
1279
+
1280
+ def define_checks
1281
+ # Check name table
1282
+ check_table :name_validation, 'name', severity: :error do |table|
1283
+ table.valid_version? &&
1284
+ table.valid_encoding_heuristics? &&
1285
+ table.family_name_present? &&
1286
+ table.postscript_name_valid?
1287
+ end
1288
+
1289
+ # Check head table
1290
+ check_table :head_validation, 'head', severity: :error do |table|
1291
+ table.valid_magic? &&
1292
+ table.valid_version? &&
1293
+ table.valid_units_per_em? &&
1294
+ table.valid_bounding_box? &&
1295
+ table.valid_index_to_loc_format? &&
1296
+ table.valid_glyph_data_format?
1297
+ end
1298
+
1299
+ # Check structure
1300
+ check_structure :required_tables, severity: :error do |font|
1301
+ %w[name head maxp hhea].all? { |tag| !font.table(tag).nil? }
1302
+ end
1303
+ end
1304
+ end
1305
+
1306
+ # Use the validator
1307
+ font = Fontisan::FontLoader.load('font.ttf')
1308
+ validator = MyFontValidator.new
1309
+ report = validator.validate(font)
1310
+
1311
+ # Check results
1312
+ puts report.valid? # => true/false
1313
+ puts report.status # => "valid", "valid_with_warnings", "invalid"
1314
+ puts report.summary.errors # => number of errors
1315
+ puts report.summary.warnings # => number of warnings
1316
+
1317
+ # Query specific checks
1318
+ result = report.result_of(:name_validation)
1319
+ puts result.passed? # => true/false
1320
+ puts result.severity # => "error"
1321
+ puts result.messages # => array of messages
1322
+
1323
+ # Get all failed checks
1324
+ report.failed_checks.each do |check|
1325
+ puts "#{check.check_id}: #{check.messages.join(', ')}"
1326
+ end
1327
+
1328
+ # Serialize report
1329
+ puts report.to_yaml
1330
+ puts report.to_json
1331
+ ----
1332
+ ====
1333
+
1334
+
1335
+ .Using table validation helpers
1336
+ [example]
1337
+ ====
1338
+ [source,ruby]
1339
+ ----
1340
+ class ComprehensiveValidator < Fontisan::Validators::Validator
1341
+ private
1342
+
1343
+ def define_checks
1344
+ # Name table validation helpers
1345
+ check_table :name_validation, 'name', severity: :error do |table|
1346
+ table.valid_version? &&
1347
+ table.valid_encoding_heuristics? &&
1348
+ table.family_name_present? &&
1349
+ table.postscript_name_valid?
1350
+ end
1351
+
1352
+ # Head table validation helpers
1353
+ check_table :head_validation, 'head', severity: :error do |table|
1354
+ table.valid_magic? &&
1355
+ table.valid_version? &&
1356
+ table.valid_units_per_em? &&
1357
+ table.valid_bounding_box? &&
1358
+ table.valid_index_to_loc_format? &&
1359
+ table.valid_glyph_data_format?
1360
+ end
1361
+
1362
+ # Maxp table validation helpers
1363
+ check_table :maxp_validation, 'maxp', severity: :error do |table|
1364
+ table.valid_version? &&
1365
+ table.valid_num_glyphs? &&
1366
+ table.valid_max_zones? &&
1367
+ table.has_truetype_metrics? &&
1368
+ table.reasonable_metrics?
1369
+ end
1370
+ end
1371
+ end
1372
+ ----
1373
+ ====
1374
+
1375
+ .Handling validation results
1376
+ [example]
1377
+ ====
1378
+ [source,ruby]
1379
+ ----
1380
+ validator = MyFontValidator.new
1381
+ report = validator.validate(font)
1382
+
1383
+ # Overall validation status
1384
+ if report.valid?
1385
+ puts "Font is valid!"
1386
+ else
1387
+ puts "Font has issues:"
1388
+
1389
+ # Show errors
1390
+ report.errors.each do |error|
1391
+ puts " [ERROR] #{error.category}: #{error.message}"
1392
+ puts " Location: #{error.location}" if error.location
1393
+ end
1394
+
1395
+ # Show warnings
1396
+ report.warnings.each do |warning|
1397
+ puts " [WARN] #{warning.category}: #{warning.message}"
1398
+ end
1399
+ end
1400
+
1401
+ # Check specific validation results
1402
+ if report.result_of(:name_validation)&.passed?
1403
+ puts "Name table version is valid"
1404
+ else
1405
+ puts "Name table version check failed"
1406
+ end
1407
+
1408
+ # Get summary statistics
1409
+ puts "\nValidation Summary:"
1410
+ puts " Total checks: #{report.check_results.count}"
1411
+ puts " Passed: #{report.passed_checks.count}"
1412
+ puts " Failed: #{report.failed_checks.count}"
1413
+ puts " Errors: #{report.summary.errors}"
1414
+ puts " Warnings: #{report.summary.warnings}"
1415
+ puts " Info: #{report.summary.info}"
1416
+ ----
1417
+ ====
1418
+
1419
+ ==== Validation helpers
1420
+
1421
+ ===== General
1422
+
1423
+ The validation framework provides 56 helper methods across 8 core OpenType
1424
+ tables. Each helper returns a boolean indicating whether the validation passed.
1425
+
1426
+ Name table:
1427
+
1428
+ * `valid_version?` - Check if version is 0 or 1
1429
+ * `valid_encoding_heuristics?` - Check platform/encoding combinations
1430
+ * `has_valid_platform_combos?` - Check for required platform combinations
1431
+ * `family_name_present?` - Check if family name exists and is non-empty
1432
+ * `postscript_name_present?` - Check if PostScript name exists and is non-empty
1433
+ * `postscript_name_valid?` - Check if PostScript name matches required pattern
1434
+
1435
+ Head table:
1436
+
1437
+ * `valid_magic?` - Check magic number (0x5F0F3CF5)
1438
+ * `valid_version?` - Check version is 1.0
1439
+ * `valid_units_per_em?` - Check units per em is valid (16-16384)
1440
+ * `valid_bounding_box?` - Check bounding box coordinates
1441
+ * `valid_index_to_loc_format?` - Check format is 0 or 1
1442
+ * `valid_glyph_data_format?` - Check format is 0
1443
+
1444
+ Maxp Table:
1445
+
1446
+ * `valid_version?` - Check version is 0.5 or 1.0
1447
+ * `valid_num_glyphs?` - Check num glyphs >= 1
1448
+ * `valid_max_zones?` - Check maxZones is 1 or 2
1449
+ * `has_truetype_metrics?` - Check TrueType metrics are present
1450
+ * `reasonable_metrics?` - Check metrics are within reasonable bounds
1451
+
1452
+ Hhea Table:
1453
+
1454
+ * `valid_version?` - Check version is 1.0
1455
+ * `valid_metric_data_format?` - Check format is 0
1456
+ * `valid_number_of_h_metrics?` - Check count >= 1
1457
+ * `valid_ascent_descent?` - Check signs are correct
1458
+ * `valid_line_gap?` - Check line gap >= 0
1459
+ * `valid_advance_width_max?` - Check max width > 0
1460
+ * `valid_caret_slope?` - Check caret slope values
1461
+ * `valid_x_max_extent?` - Check extent > 0
1462
+
1463
+ Glyf Table:
1464
+
1465
+ * `has_empty_glyphs?` - Check for empty glyphs
1466
+ * `has_clipped_glyphs?` - Check for clipped glyphs
1467
+ * `has_instructions?` - Check for TrueType instructions
1468
+ * `contours_valid?` - Check contour counts
1469
+ * `glyphs_accessible?` - Check glyph accessibility
1470
+
1471
+ Cmap Table:
1472
+
1473
+ * `valid_version?` - Check version is 0
1474
+ * `has_valid_subtables?` - Check subtable validity
1475
+ * `has_unicode_mapping?` - Check for Unicode support
1476
+ * `has_valid_bmp_coverage?` - Check BMP coverage
1477
+ * `has_valid_format4?` - Check format 4 subtable
1478
+ * `glyph_indices_valid?` - Check glyph index validity
1479
+ * `supports_platform?` - Check platform support
1480
+
1481
+ Post Table:
1482
+
1483
+ * `valid_version?` - Check version (1.0, 2.0, 2.5, 3.0, 4.0)
1484
+ * `valid_italic_angle?` - Check italic angle range
1485
+ * `valid_underline_position?` - Check underline metrics
1486
+ * `valid_underline_thickness?` - Check underline thickness
1487
+ * `valid_is_fixed_pitch?` - Check fixed pitch flag
1488
+ * `has_glyph_names?` - Check for glyph names
1489
+
1490
+ OS/2 Table:
1491
+
1492
+ * `valid_version?` - Check version (0-5)
1493
+ * `valid_weight_class?` - Check weight class (100-900)
1494
+ * `valid_width_class?` - Check width class (1-9)
1495
+ * `valid_vendor_id?` - Check vendor ID format
1496
+ * `valid_typo_metrics?` - Check typographic metrics
1497
+ * `valid_win_metrics?` - Check Windows metrics
1498
+ * `valid_unicode_ranges?` - Check Unicode range bits
1499
+ * `has_valid_panose?` - Check PANOSE classification
1500
+ * `valid_selection_flags?` - Check style selection flags
1501
+ * `valid_first_char_index?` - Check first character index
1502
+ * `valid_last_char_index?` - Check last character index
1503
+ * `valid_typo_ascender?` - Check typographic ascender
1504
+ * `valid_typo_descender?` - Check typographic descender
1505
+
1023
1506
 
1024
1507
  == Version information
1025
1508
 
@@ -1035,16 +1518,51 @@ fontisan version
1035
1518
  ----
1036
1519
 
1037
1520
 
1038
- == Font collections
1521
+ == Font containers and collections
1039
1522
 
1040
1523
  === General
1041
1524
 
1042
- Fontisan provides comprehensive tools for managing TrueType Collections (TTC)
1043
- and OpenType Collections (OTC). You can list fonts in a collection, extract
1044
- individual fonts, unpack entire collections, and validate collection integrity.
1525
+ Fontisan provides comprehensive tools for managing font containers and
1526
+ collections. A font container is defined as a packaging format that can hold one
1527
+ or more font assets.
1528
+
1529
+ In general, Fontisan supports the following levels of font packaging:
1530
+
1531
+ Level 1:: Outline formats. This level defines the actual glyph outline data
1532
+ format for fonts.
1533
+
1534
+ TrueType::: Quadratic Bézier curves (glyf/loca tables)
1535
+ CFF::: Cubic Bézier curves (CFF table, PostScript outlines)
1536
+ CFF2::: Variable CFF with cubic curves (CFF2 table)
1537
+
1538
+ Level 2:: Curve and metadata container. This level defines the overall font
1539
+ file format that encapsulates outline data along with various metadata tables.
1540
+
1541
+ SFNT::: "Spine/Scalable font" defines the overall structure and directory system
1542
+ for a font file, which houses different "tables" containing specific data like
1543
+ glyph outlines, character maps, and kerning information.
1045
1544
 
1046
- Both TTC and OTC files use the same `ttcf` tag in their binary format, but
1047
- differ in the type of font data they contain:
1545
+ TrueType (TTF)::: A specific implementation of the SFNT container that uses
1546
+ TrueType outlines.
1547
+
1548
+ OpenType (OTF)::: A more versatile SFNT container that can house either TrueType
1549
+ or CFF outlines, along with additional typographic features.
1550
+
1551
+ Web Open Font Format (WOFF and WOFF2)::: Compressed SFNT-based formats optimized
1552
+ for web delivery.
1553
+
1554
+ Level 3:: Individual font file, representing a single font asset packaged in a
1555
+ specific container format.
1556
+
1557
+ TTF `.ttf`::: A single font file in TrueType format
1558
+ OTF `.otf`::: A single font file in OpenType format
1559
+ WOFF `.woff`::: A single font file in Web Open Font Format 1.0 that is SFNT-based
1560
+ and uses zlib compression
1561
+ WOFF2 `.woff2`::: A single font file in Web Open Font Format 2.0 that is
1562
+ SFNT-based and uses Brotli compression
1563
+
1564
+ Level 4:: Font collections, which are container formats that can hold multiple
1565
+ font assets within a single file.
1048
1566
 
1049
1567
  TTC (TrueType Collection):: Supported since OpenType 1.4. Contains fonts with
1050
1568
  TrueType outlines (glyf table). Multiple fonts can share identical tables for
@@ -1054,7 +1572,38 @@ OTC (OpenType Collection):: Supported since OpenType 1.8. Contains fonts with
1054
1572
  CFF-format outlines (CFF table). Provides the same storage benefits and
1055
1573
  glyph-count advantages as TTC but for CFF fonts. File extension: `.otc`
1056
1574
 
1057
- The collection format allows:
1575
+ dfont (Apple suitcase)::: Apple proprietary format storing complete Mac font
1576
+ suitcase resources in the data fork. Supports any SFNT fonts (TTF, OTF, or
1577
+ mixed). Mac OS X specific. File extension: `.dfont`
1578
+
1579
+
1580
+ === Collection compatibility
1581
+
1582
+ .Compatibility matrix
1583
+ |===
1584
+ | Container | TrueType | CFF | Mixed | WOFF/WOFF2 | Platform
1585
+
1586
+ | TTC | ✅ ONLY | ❌ | ❌ | ❌ | Cross-platform
1587
+ | OTC | ✅ | ✅ | ✅ | ❌ | Cross-platform
1588
+ | dfont | ✅ | ✅ | ✅ | ❌ | Apple only
1589
+
1590
+ |===
1591
+
1592
+ NOTE: WOFF/WOFF2 cannot be packaged into TTC, OTC or dfont collections. Web
1593
+ fonts are designed for single-font delivery only.
1594
+
1595
+
1596
+ === Working with collections
1597
+
1598
+ Fontist supports working with TrueType Collections (TTC), OpenType Collections
1599
+ (OTC) and the legacy Apple dfont format. You can list fonts in a collection,
1600
+ extract individual fonts, unpack entire collections, and validate collection
1601
+ integrity.
1602
+
1603
+ NOTE: Both TTC and OTC files use the same `ttcf` tag in their binary format, but
1604
+ differ in the type of font data they contain.
1605
+
1606
+ In particular, the TTC and OTC formats allows:
1058
1607
 
1059
1608
  Table sharing::
1060
1609
  Identical tables are stored once and referenced by multiple fonts
@@ -1079,8 +1628,7 @@ Fontist returns the appropriate collection type based on the font data:
1079
1628
 
1080
1629
  ==== General
1081
1630
 
1082
- List all fonts in a TrueType Collection (TTC) or OpenType Collection (OTC), with
1083
- their index, family name, and style.
1631
+ List all fonts in a font collection, with their index, family name, and style.
1084
1632
 
1085
1633
  ==== Command-line usage
1086
1634
 
@@ -1091,7 +1639,6 @@ $ fontisan ls FONT.{ttc,otc}
1091
1639
 
1092
1640
  NOTE: In `extract_ttc`, this was done with `extract_ttc --list FONT.ttc`.
1093
1641
 
1094
-
1095
1642
  .List collection contents
1096
1643
  [example]
1097
1644
  ====
@@ -1122,6 +1669,27 @@ Font 3: Noto Serif CJK TC
1122
1669
  ----
1123
1670
  ====
1124
1671
 
1672
+ .List fonts in dfont suitcase
1673
+ [example]
1674
+ ====
1675
+ [source,shell]
1676
+ ----
1677
+ $ fontisan ls family.dfont
1678
+
1679
+ Font 0: Arial
1680
+ Family: Arial
1681
+ Subfamily: Regular
1682
+
1683
+ Font 1: Arial
1684
+ Family: Arial
1685
+ Subfamily: Bold
1686
+
1687
+ Font 2: Arial
1688
+ Family: Arial
1689
+ Subfamily: Italic
1690
+ ----
1691
+ ====
1692
+
1125
1693
 
1126
1694
  === Show collection info
1127
1695
 
@@ -1282,1101 +1850,115 @@ Collection created successfully:
1282
1850
  $ fontisan pack Regular.otf Bold.otf Italic.otf --output family.otc --format otc
1283
1851
  ----
1284
1852
 
1285
-
1286
- === Validate collection
1287
-
1288
- ==== General
1289
-
1290
- Validate the structure and checksums of a TrueType Collection (TTC) or OpenType
1291
- Collection (OTC).
1292
-
1293
- ==== Command-line usage
1294
-
1853
+ .Pack into OTC
1854
+ [example]
1855
+ ====
1295
1856
  [source,shell]
1296
1857
  ----
1297
- $ fontisan validate FONT.{ttc,otc}
1298
- ----
1299
-
1300
- NOTE: In `extract_ttc`, this was done with `extract_ttc --validate FONT.ttc`.
1301
-
1302
-
1858
+ $ fontisan pack Regular.otf Bold.otf Italic.otf --output family.otc --format otc
1303
1859
 
1860
+ Collection created successfully:
1861
+ Output: family.otc
1862
+ Format: OTC
1863
+ Fonts: 3
1864
+ ----
1304
1865
 
1305
- == Color font support
1866
+ NOTE: dfont supports mixed TrueType and OpenType fonts in the same suitcase.
1867
+ dfont does not perform table deduplication like TTC/OTC.
1868
+ ====
1306
1869
 
1307
- === General
1308
1870
 
1309
- Fontisan provides comprehensive analysis of all color font formats defined in the OpenType specification.
1871
+ === Convert between font container formats
1310
1872
 
1311
- === Supported color font formats
1873
+ ==== General
1312
1874
 
1313
- Fontisan supports all four color font table formats:
1875
+ Fontisan supports converting font collections between TrueType Collection (TTC),
1876
+ OpenType Collection (OTC), and Apple dfont formats.
1314
1877
 
1315
- COLR/CPAL:: Layered color glyphs with custom color palettes (Microsoft/Google standard)
1878
+ All collection formats (TTC, OTC, dfont) support mixed TrueType and OpenType fonts.
1316
1879
 
1317
- SVG:: Embedded SVG graphics with gzip compression support (W3C standard)
1880
+ By default, original font formats are preserved during conversion. Use the
1881
+ `--target-format` option to standardize all fonts to a specific outline format:
1318
1882
 
1319
- CBDT/CBLC:: Bitmap glyphs at multiple ppem sizes (Google format)
1883
+ * `--target-format preserve` (default) - Keep original mixed TTF+OTF formats
1884
+ * `--target-format ttf` - Convert all fonts to TrueType outlines
1885
+ * `--target-format otf` - Convert all fonts to OpenType/CFF outlines
1320
1886
 
1321
- sbix:: Bitmap graphics with PNG/JPEG/TIFF support (Apple format)
1322
1887
 
1323
- === Analyzing color fonts
1888
+ NOTE: dfont supports mixed TrueType and OpenType fonts in the same suitcase.
1324
1889
 
1325
- [source,ruby]
1890
+ .TTC to OTC conversion (preserves formats)
1891
+ [example]
1892
+ ====
1893
+ [source,shell]
1326
1894
  ----
1327
- require 'fontisan'
1328
-
1329
- # Load and analyze a color font
1330
- info = Fontisan.info('emoji-font.ttf')
1331
-
1332
- # Color glyph detection
1333
- puts info.is_color_font # true if COLR/CPAL present
1334
- puts info.has_svg_table # true if SVG table present
1335
- puts info.has_bitmap_glyphs # true if CBDT/CBLC or sbix present
1336
-
1337
- # Get color palette information (COLR/CPAL)
1338
- if info.is_color_font
1339
- puts "Color glyphs: #{info.color_glyphs}"
1340
- puts "Color palettes: #{info.color_palettes}"
1341
- puts "Colors per palette: #{info.colors_per_palette}"
1342
- end
1895
+ $ fontisan convert family.ttc --to otc --output family.otc
1343
1896
 
1344
- # Get SVG glyph information
1345
- if info.has_svg_table
1346
- puts "SVG glyphs: #{info.svg_glyph_count}"
1347
- end
1897
+ Conversion complete!
1898
+ Input: family.ttc (245.8 KB)
1899
+ Output: family.otc (312.4 KB)
1900
+ Format: TTC → OTC
1901
+ Fonts: 3
1902
+ ----
1348
1903
 
1349
- # Get bitmap information
1350
- if info.has_bitmap_glyphs
1351
- puts "Bitmap formats: #{info.bitmap_formats.join(', ')}"
1352
- puts "Available sizes (ppem): #{info.bitmap_ppem_sizes.join(', ')}"
1904
+ Original font formats are preserved (mixed TTF+OTF supported).
1905
+ ====
1353
1906
 
1354
- info.bitmap_strikes.each do |strike|
1355
- puts "Strike at #{strike.ppem}ppem:"
1356
- puts " - Glyphs: #{strike.num_glyphs}"
1357
- puts " - Color depth: #{strike.color_depth}"
1358
- end
1359
- end
1907
+ .TTC to OTC with forced conversion
1908
+ [example]
1909
+ ====
1910
+ [source,shell]
1360
1911
  ----
1912
+ $ fontisan convert family.ttc --to otc --output family.otc --target-format otf
1361
1913
 
1362
- === Color font table access
1914
+ Conversion complete!
1915
+ Input: family.ttc (245.8 KB)
1916
+ Output: family.otc (312.4 KB)
1917
+ Format: TTC → OTC
1918
+ Fonts: 3
1919
+ ----
1363
1920
 
1364
- Access color font tables directly:
1921
+ All TrueType fonts are converted to OpenType/CFF format.
1922
+ ====
1365
1923
 
1366
- [source,ruby]
1924
+ .dfont to OTC conversion (preserves formats)
1925
+ [example]
1926
+ ====
1927
+ [source,shell]
1367
1928
  ----
1368
- font = Fontisan::FontLoader.load('emoji-font.ttf')
1369
-
1370
- # Access COLR table (layered color)
1371
- if font.has_table?('COLR')
1372
- colr = font.table('COLR')
1373
- puts "Color glyphs: #{colr.num_color_glyphs}"
1929
+ $ fontisan convert family.dfont --to otc --output family.otc
1374
1930
 
1375
- # Get color layers for a glyph
1376
- layers = colr.layers_for_glyph(42)
1377
- layers.each do |layer|
1378
- puts "Layer glyph: #{layer.glyph_id}, Palette index: #{layer.palette_index}"
1379
- end
1380
- end
1931
+ Conversion complete!
1932
+ Input: family.dfont (387.6 KB)
1933
+ Output: family.otc (312.4 KB)
1934
+ Format: DFONT OTC
1935
+ Fonts: 3
1936
+ ----
1381
1937
 
1382
- # Access CPAL table (color palettes)
1383
- if font.has_table?('CPAL')
1384
- cpal = font.table('CPAL')
1938
+ Fonts are extracted from dfont and repacked as OTC.
1939
+ Original font formats are preserved (mixed TTF+OTF supported).
1940
+ ====
1385
1941
 
1386
- # Get colors from first palette
1387
- palette = cpal.palette(0)
1388
- palette.each_with_index do |color, i|
1389
- puts "Color #{i}: R=#{color.red} G=#{color.green} B=#{color.blue} A=#{color.alpha}"
1390
- end
1391
- end
1942
+ .dfont to OTC with forced conversion
1943
+ [example]
1944
+ ====
1945
+ [source,shell]
1946
+ ----
1947
+ $ fontisan convert family.dfont --to otc --output family.otc --target-format otf
1392
1948
 
1393
- # Access SVG table
1394
- if font.has_table?('SVG ')
1395
- svg = font.table('SVG ')
1949
+ Conversion complete!
1950
+ Input: family.dfont (387.6 KB)
1951
+ Output: family.otc (312.4 KB)
1952
+ Format: DFONT → OTC
1953
+ Fonts: 3
1954
+ ----
1396
1955
 
1397
- # Get SVG document for glyph 100
1398
- svg_doc = svg.svg_document_for_glyph(100)
1399
- puts "Compressed: #{svg_doc.compressed?}"
1400
- puts "SVG data: #{svg_doc.svg_data}"
1401
- end
1402
-
1403
- # Access bitmap tables
1404
- if font.has_table?('CBLC')
1405
- cblc = font.table('CBLC')
1406
- puts "Available ppem sizes: #{cblc.ppem_sizes.join(', ')}"
1407
-
1408
- # Check if glyph 50 has bitmap at 64ppem
1409
- if cblc.has_bitmap_for_glyph?(50, 64)
1410
- puts "Glyph 50 has bitmap at 64ppem"
1411
- end
1412
- end
1413
-
1414
- if font.has_table?('sbix')
1415
- sbix = font.table('sbix')
1416
-
1417
- # Get bitmap data for glyph 42 at 128ppem
1418
- glyph_data = sbix.glyph_data(42, 128)
1419
- if glyph_data
1420
- puts "Format: #{glyph_data[:graphic_type_name]}"
1421
- puts "Origin: (#{glyph_data[:origin_x]}, #{glyph_data[:origin_y]})"
1422
- puts "Data size: #{glyph_data[:data].length} bytes"
1423
- end
1424
- end
1425
- ----
1426
-
1427
- === Output formats
1428
-
1429
- Color font information can be serialized to multiple formats:
1430
-
1431
- [source,ruby]
1432
- ----
1433
- info = Fontisan.info('emoji-font.ttf')
1434
-
1435
- # YAML output
1436
- puts info.to_yaml
1437
-
1438
- # JSON output
1439
- puts info.to_json
1440
-
1441
- # XML output
1442
- puts info.to_xml
1443
- ----
1444
-
1445
- Example YAML output:
1446
-
1447
- [source,yaml]
1448
- ----
1449
- font_format: truetype
1450
- family_name: "Emoji Font"
1451
- is_color_font: true
1452
- color_glyphs: 872
1453
- color_palettes: 1
1454
- colors_per_palette: 256
1455
- has_svg_table: true
1456
- svg_glyph_count: 872
1457
- has_bitmap_glyphs: true
1458
- bitmap_ppem_sizes: [16, 32, 64, 128, 256]
1459
- bitmap_formats: ["PNG"]
1460
- bitmap_strikes:
1461
- - ppem: 128
1462
- start_glyph_id: 1
1463
- end_glyph_id: 872
1464
- bit_depth: 32
1465
- num_glyphs: 872
1466
- color_depth: "32-bit (full color with alpha)"
1467
- ----
1468
-
1469
-
1470
- == Advanced features
1471
-
1472
- Fontisan provides capabilities:
1473
-
1474
- .Font analysis and inspection
1475
- * Extract OpenType tables with checksums and offsets
1476
- * Display Unicode mappings and glyph names
1477
- * Analyze variable font axes and instances
1478
- * Show supported scripts and OpenType features
1479
- * Dump raw binary table data
1480
-
1481
- .Format conversion and subsetting
1482
- * Convert between TTF, OTF, WOFF, and WOFF2 formats
1483
- * Create font subsets with specific glyph ranges
1484
- * Validate font structure and integrity
1485
- * Generate SVG representations of glyphs
1486
-
1487
- .Collection creation
1488
- * Build new TTC files from individual fonts
1489
- * Optimize collection with table deduplication
1490
- * Pack fonts with shared tables for smaller file sizes
1491
-
1492
- For complete migration guide, see link:docs/EXTRACT_TTC_MIGRATION.md[extract_ttc Migration Guide].
1493
-
1494
-
1495
-
1496
-
1497
- == Loading modes
1498
-
1499
- === General
1500
-
1501
- Fontisan provides a flexible loading modes architecture that enables efficient
1502
- font parsing for different use cases.
1503
-
1504
- The system supports two distinct modes:
1505
-
1506
- `:full` mode:: (default) Loads all tables in the font for complete analysis and
1507
- manipulation
1508
-
1509
- `:metadata` mode:: Loads only metadata tables needed for font identification and
1510
- metrics (similar to `otfinfo` functionality). This mode is around 5x faster
1511
- than full parsing and uses significantly less memory.
1512
-
1513
- This architecture is particularly useful for software that only
1514
- needs basic font information without full parsing overhead, such as
1515
- font indexing systems or font discovery tools.
1516
-
1517
- This mode was developed to improve performance in font indexing in the
1518
- https://github.com/fontist/fontist[Fontist] library, where system fonts
1519
- need to be scanned quickly without loading unnecessary data.
1520
-
1521
- A font file opened in `:metadata` mode will only have a subset of tables
1522
- loaded, and attempts to access non-loaded tables will return `nil`.
1523
-
1524
- [source,ruby]
1525
- ----
1526
- font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
1527
-
1528
- # Check table availability before accessing
1529
- font.table_available?("name") # => true
1530
- font.table_available?("GSUB") # => false
1531
-
1532
- # Access allowed tables
1533
- font.table("name") # => Works
1534
- font.table("head") # => Works
1535
-
1536
- # Restricted tables return nil
1537
- font.table("GSUB") # => nil (not loaded in metadata mode)
1538
- ----
1539
-
1540
- You can also set loading modes via the environment:
1541
-
1542
- [source,ruby]
1543
- ----
1544
- # Set defaults via environment
1545
- ENV['FONTISAN_MODE'] = 'metadata'
1546
- ENV['FONTISAN_LAZY'] = 'false'
1547
-
1548
- # Uses environment settings
1549
- font = Fontisan::FontLoader.load('font.ttf')
1550
-
1551
- # Explicit parameters override environment
1552
- font = Fontisan::FontLoader.load('font.ttf', mode: :full)
1553
- ----
1554
-
1555
- The loading mode can be queried at any time.
1556
-
1557
- [source,ruby]
1558
- ----
1559
- # Check laziness
1560
- font.lazy? # => true
1561
-
1562
- # Mode stored as font property
1563
- font.loading_mode # => :metadata or :full
1564
-
1565
- # Table availability checked before access
1566
- font.table_available?(tag) # => boolean
1567
-
1568
- # Access restricted based on mode
1569
- font.table(tag) # => Returns table or raises error
1570
-
1571
- # Test lazy loading with an expensive operation
1572
- glyphs = [] if font.subset_glyphs(lazy: true) { glyphs = font.subset_glyphs }
1573
- glyphs.count # => Tests whether glyphs were already loaded
1574
- font.table_available?("head") # => true (lazy loading enabled)
1575
- font.table_available?("GSUB") # => false (lazy loading enabled)
1576
- ----
1577
-
1578
-
1579
-
1580
- === Metadata mode
1581
-
1582
- Loads only 6 tables (name, head, hhea, maxp, OS/2, post) instead of 15-20 tables.
1583
-
1584
- .Metadata mode: Fast loading for font identification
1585
- [source,ruby]
1586
- ----
1587
- font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
1588
- puts font.family_name # => "Arial"
1589
- puts font.subfamily_name # => "Regular"
1590
- puts font.post_script_name # => "ArialMT"
1591
- ----
1592
-
1593
- Tables loaded:
1594
-
1595
- name:: Font names and metadata
1596
- head:: Font header with global metrics
1597
- hhea:: Horizontal header with line spacing
1598
- maxp:: Maximum profile with glyph count
1599
- OS/2:: OS/2 and Windows metrics
1600
- post:: PostScript information
1601
-
1602
-
1603
- In metadata mode, these convenience methods provide direct access to name table
1604
- fields:
1605
-
1606
- `family_name`:: Font family name (nameID 1)
1607
- `subfamily_name`:: Font subfamily/style name (nameID 2)
1608
- `full_name`:: Full font name (nameID 4)
1609
- `postscript_name`:: PostScript name (nameID 6)
1610
- `preferred_family_name`:: Preferred family name (nameID 16, may be nil)
1611
- `preferred_subfamily_name`:: Preferred subfamily name (nameID 17, may be nil)
1612
- `units_per_em`:: Units per em from head table
1613
-
1614
-
1615
- === Full mode
1616
-
1617
- Loads all tables in the font for complete analysis and manipulation.
1618
-
1619
- .Full mode: Complete font analysis
1620
- [source,ruby]
1621
- ----
1622
- font = Fontisan::FontLoader.load('font.ttf', mode: :full)
1623
- font.table("GSUB") # => Available
1624
- font.table("GPOS") # => Available
1625
-
1626
- # Check which mode is active
1627
- puts font.loading_mode # => :metadata or :full
1628
- ----
1629
-
1630
- Tables loaded:
1631
-
1632
- * All tables in the font
1633
- * Including GSUB, GPOS, cmap, glyf/CFF, etc.
1634
-
1635
- === Lazy loading option
1636
-
1637
- Fontisan supports lazy loading of tables in both `:metadata` and `:full` modes.
1638
- When lazy loading is enabled (optional), tables are only parsed when accessed.
1639
-
1640
- Options:
1641
-
1642
- `false`:: (default) Eager loading. All tables for the selected mode are parsed
1643
- upfront.
1644
-
1645
- `true`:: Lazy loading enabled. Tables are parsed on-demand.
1646
-
1647
- [source,ruby]
1648
- ----
1649
- # Metadata mode with lazy loading (default, fastest)
1650
- font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: true)
1651
-
1652
- # Metadata mode with eager loading (loads all metadata tables upfront)
1653
- font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: false)
1654
-
1655
- # Full mode with lazy loading (tables loaded on-demand)
1656
- font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: true)
1657
-
1658
- # Full mode with eager loading (all tables loaded upfront)
1659
- font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: false)
1660
- ----
1661
-
1662
-
1663
- === Table preparedness
1664
-
1665
- Every table uses preparedness to avoid lazy initialization overhead. Function
1666
- `table_available?` was added to check if table prepared for access.
1667
-
1668
- [source,ruby]
1669
- ----
1670
- font = Fontisan::FontLoader.load('font.ttf')
1671
-
1672
- # Check preparedness
1673
- font.table_available?("head") # => true
1674
- font.table_available?("GSUB") # => true
1675
- font.table_available?("GPOS") # => true
1676
-
1677
- # Force loading of a table
1678
- head = font.table("head")
1679
- puts head.metrics.units_per_em # => Asks explicitly for metric value
1680
- puts head.metrics.weight_class # => Asks explicitly for metric value
1681
- print head.ty # => Prints force loaded table
1682
-
1683
- # Check preparedness after loading
1684
- font.table_available?("head") # => true in cache
1685
- font.table_available?("GSUB") # => true in cache
1686
- font.table_available?("GPOS") # => true in cache
1687
- ----
1688
-
1689
- A table not present in a font file is loaded lazily and `table_available?`
1690
- returns `false` after loading this table or after calling directly `table` for
1691
- the given tag:
1692
-
1693
- [source,ruby]
1694
- ----
1695
- font = Fontisan::FontLoader.load('font.ttf')
1696
-
1697
- # Check preparedness
1698
- font.standard_glyphs # => instantly loading glyphs (CFF and GDEF)
1699
- font.candlestick_and_widget_chart # => instantly loading glyphs (Colr)
1700
- font.txt_encoder # => instantly loading glyphs (OutlinedFont)
1701
- font.table_available?("post") # => true (cached)
1702
-
1703
- font.table_available?("OS/2") # => false (not loaded)
1704
- font.table("OS/2") # => instant loading tables (lazy loading)
1705
- font.table_available?("OS/2") # => true (cached)
1706
-
1707
- font.table_available?("VORG") # => false (not loaded)
1708
- font.table("VORG") # => instant loading tables (lazy loading)
1709
- # Raises Fontisan::Tables::VorgTableNotInFont: Not a valid sfnt font or sfnt table VORG not included in input font
1710
- font.table_available?("VORG") # => true (cached)
1711
-
1712
- font.table_available?("GLYC") # => nil
1713
- font.table("GLYC") # => instant loading tables (lazy loading)
1714
- # Raises Fontisan::TableNotFound: Table GLYC not found in font
1715
- font.table_available?("GLYC") # => nil (not cached)
1716
- ----
1717
-
1718
- NOTE: If you do not need to check whether the table is present before access
1719
- it is faster to access directly `table` method and catch error with validation
1720
- than first call table_available? for the table not present in the font file.
1721
-
1722
-
1723
- == Outline format conversion
1724
-
1725
- === General
1726
-
1727
- Fontisan supports bidirectional conversion between TrueType (TTF) and
1728
- OpenType/CFF (OTF) outline formats through the Fontist universal outline model
1729
- (UOM).
1730
-
1731
- The outline converter enables transformation between glyph outline formats:
1732
-
1733
- TrueType (TTF):: Uses quadratic Bézier curves stored in glyf/loca tables
1734
- OpenType/CFF (OTF):: Uses cubic Bézier curves stored in CFF table
1735
-
1736
- Conversion uses a format-agnostic universal outline model as an intermediate
1737
- representation, ensuring high-quality results while preserving glyph metrics and
1738
- bounding boxes.
1739
-
1740
-
1741
- === Convert between TTF and OTF
1742
-
1743
- ==== Command-line usage
1744
-
1745
- Syntax:
1746
-
1747
- [source,bash]
1748
- ----
1749
- $ fontisan convert INPUT_FONT --to FORMAT --output OUTPUT_FONT
1750
- ----
1751
-
1752
- Where,
1753
-
1754
- `INPUT_FONT`:: Path to the input font file (TTF or OTF)
1755
- `FORMAT`:: Target format:
1756
- `ttf`, `truetype`::: TrueType format
1757
- `otf`, `opentype`, `cff`::: OpenType/CFF format
1758
- `OUTPUT_FONT`:: Path to the output font file
1759
-
1760
-
1761
- [source,bash]
1762
- ----
1763
- # Convert TrueType font to OpenType/CFF
1764
- fontisan convert input.ttf --to otf --output output.otf
1765
-
1766
- # Convert OpenType/CFF font to TrueType
1767
- fontisan convert input.otf --to ttf --output output.ttf
1768
- ----
1769
-
1770
-
1771
- ==== Ruby API usage
1772
-
1773
- Basic conversion with OutlineConverter:
1774
-
1775
- [source,ruby]
1776
- ----
1777
- require 'fontisan'
1778
-
1779
- # Load a TrueType font
1780
- font = Fontisan::FontLoader.load('input.ttf')
1781
-
1782
- # Convert to OpenType/CFF
1783
- converter = Fontisan::Converters::OutlineConverter.new
1784
- tables = converter.convert(font, target_format: :otf)
1785
-
1786
- # Write output
1787
- Fontisan::FontWriter.write_to_file(
1788
- tables,
1789
- 'output.otf',
1790
- sfnt_version: 0x4F54544F # 'OTTO' for OpenType/CFF
1791
- )
1792
- ----
1793
-
1794
- Using FormatConverter:
1795
-
1796
- [source,ruby]
1797
- ----
1798
- require 'fontisan'
1799
-
1800
- # Load font
1801
- font = Fontisan::FontLoader.load('input.ttf')
1802
-
1803
- # Convert using high-level API
1804
- converter = Fontisan::Converters::FormatConverter.new
1805
- if converter.supported?(:ttf, :otf)
1806
- tables = converter.convert(font, :otf)
1807
-
1808
- # Write output
1809
- Fontisan::FontWriter.write_to_file(
1810
- tables,
1811
- 'output.otf',
1812
- sfnt_version: 0x4F54544F
1813
- )
1814
- end
1815
- ----
1816
-
1817
- To check supported conversions:
1818
-
1819
- [source,ruby]
1820
- ----
1821
- converter = Fontisan::Converters::FormatConverter.new
1822
-
1823
- # Check if conversion is supported
1824
- converter.supported?(:ttf, :otf) # => true
1825
- converter.supported?(:otf, :ttf) # => true
1826
-
1827
- # Get all supported conversions
1828
- converter.all_conversions
1829
- # => [{from: :ttf, to: :otf}, {from: :otf, to: :ttf}, ...]
1830
-
1831
- # Get supported targets for a source format
1832
- converter.supported_targets(:ttf)
1833
- # => [:ttf, :otf, :woff2, :svg]
1834
- ----
1835
-
1836
- === Convert into WOFF2
1837
-
1838
- ==== Validation
1839
-
1840
- WOFF2 encoding can be validated automatically to ensure spec compliance:
1841
-
1842
- .Validation example
1843
- [example]
1844
- ====
1845
- [source,ruby]
1846
- ----
1847
- encoder = Fontisan::Converters::Woff2Encoder.new
1848
- result = encoder.convert(font, {
1849
- transform_tables: true,
1850
- validate: true,
1851
- validation_level: :strict # :strict, :standard, or :lenient
1852
- })
1853
-
1854
- report = result[:validation_report]
1855
- puts "Valid: #{report.valid}"
1856
- puts "Compression: #{report.info_issues.first.message}"
1857
- ----
1858
- ====
1859
-
1860
-
1861
-
1862
- === Validation
1863
-
1864
- Font integrity validation is enabled by default for all conversions.
1865
-
1866
- The validator ensures proper OpenType checksum calculation including correct
1867
- handling of the head table's checksumAdjustment field per the OpenType
1868
- specification.
1869
-
1870
- After conversion, validate the output font:
1871
-
1872
- [source,bash]
1873
- ----
1874
- fontisan validate output.otf
1875
- fontisan info output.otf
1876
- fontisan tables output.otf
1877
- ----
1878
-
1879
-
1880
- == Technical details of outline conversion
1881
-
1882
- === General
1883
-
1884
- The converter uses a three-stage pipeline:
1885
-
1886
- [source]
1887
- ----
1888
- Source Format Universal Outline Target Format
1889
- ------------- ------------------ -------------
1890
- TrueType (glyf) →→→ Command-based model →→→ OpenType/CFF
1891
- Quadratic curves Path representation Cubic curves
1892
- On/off-curve pts (format-agnostic) CharStrings
1893
- Delta encoding Bounding boxes Type 2 operators
1894
- Metrics Compact encoding
1895
- ----
1896
-
1897
- === Conversion steps
1898
-
1899
- TTF → OTF conversion:
1900
-
1901
- . Extract glyphs from glyf/loca tables
1902
- . Convert quadratic Bézier curves to universal outline format
1903
- . Build CFF table with CharStrings INDEX
1904
- . Update maxp table to version 0.5 (CFF format)
1905
- . Update head table (clear indexToLocFormat)
1906
- . Remove glyf/loca tables
1907
- . Preserve all other tables
1908
-
1909
- OTF → TTF conversion:
1910
-
1911
- . Extract CharStrings from CFF table
1912
- . Convert cubic Bézier curves to universal outline format
1913
- . Convert cubic curves to quadratic using adaptive subdivision
1914
- . Build glyf and loca tables with optimal format selection
1915
- . Update maxp table to version 1.0 (TrueType format)
1916
- . Update head table (set indexToLocFormat)
1917
- . Remove CFF table
1918
- . Preserve all other tables
1919
-
1920
- === Curve conversion
1921
-
1922
- **Quadratic to cubic** (lossless):
1923
-
1924
- [source]
1925
- ----
1926
- Given quadratic curve with control point Q:
1927
- P0 (start), Q (control), P2 (end)
1928
-
1929
- Calculate cubic control points:
1930
- CP1 = P0 + (2/3) × (Q - P0)
1931
- CP2 = P2 + (2/3) × (Q - P2)
1932
-
1933
- Result: Exact mathematical equivalent
1934
- ----
1935
-
1936
- **Cubic to quadratic** (adaptive):
1937
-
1938
- [source]
1939
- ----
1940
- Given cubic curve with control points:
1941
- P0 (start), CP1, CP2, P3 (end)
1942
-
1943
- Use adaptive subdivision algorithm:
1944
- 1. Estimate error of quadratic approximation
1945
- 2. If error > threshold (0.5 units):
1946
- - Subdivide cubic curve at midpoint
1947
- - Recursively convert each half
1948
- 3. Otherwise: Output quadratic approximation
1949
-
1950
- Result: High-quality approximation with < 0.5 unit deviation
1951
- ----
1952
-
1953
- === Compound glyph support
1954
-
1955
- ==== General
1956
-
1957
- Fontisan fully supports compound (composite) glyphs in both conversion directions:
1958
-
1959
- * **TTF → OTF**: Compound glyphs are decomposed into simple outlines with transformations applied
1960
- * **OTF → TTF**: CFF glyphs are converted to simple TrueType glyphs
1961
-
1962
- ==== Decomposition process
1963
-
1964
- When converting TTF to OTF, compound glyphs undergo the following process:
1965
-
1966
- . Detected from glyf table flags (numberOfContours = -1)
1967
- . Components recursively resolved (handling nested compound glyphs)
1968
- . Transformation matrices applied to each component (translation, scale, rotation)
1969
- . All components merged into a single simple outline
1970
- . Converted to CFF CharString format
1971
-
1972
- This ensures that all glyphs render identically while maintaining proper metrics
1973
- and bounding boxes.
1974
-
1975
- ==== Technical implementation
1976
-
1977
- Compound glyphs reference other glyphs by index and apply 2×3 affine
1978
- transformation matrices:
1979
-
1980
- [source]
1981
- ----
1982
- x' = a*x + c*y + e
1983
- y' = b*x + d*y + f
1984
-
1985
- Where:
1986
- - a, d: Scale factors for x and y axes
1987
- - b, c: Rotation/skew components
1988
- - e, f: Translation offsets (x, y position)
1989
- ----
1990
-
1991
- The resolver handles:
1992
-
1993
- * Simple glyphs referenced by compounds
1994
- * Nested compound glyphs (compounds referencing other compounds)
1995
- * Circular reference detection with maximum recursion depth (32 levels)
1996
- * Complex transformation matrices (uniform scale, x/y scale, full 2×2 matrix)
1997
-
1998
- === Subroutine optimization
1999
-
2000
- ==== General
2001
-
2002
- When converting TrueType (TTF) to OpenType/CFF (OTF), Fontisan can automatically
2003
- generate CFF subroutines to reduce file size.
2004
-
2005
- Subroutines extract repeated CharString patterns across glyphs and store them
2006
- once, significantly reducing CFF table size while maintaining identical glyph
2007
- rendering.
2008
-
2009
- Key features:
2010
-
2011
- * Pattern analysis: Analyzes byte sequences across all CharStrings to identify repeating patterns
2012
- * Frequency-based selection: Prioritizes patterns that provide maximum space savings
2013
- * Configurable thresholds: Customizable minimum pattern length and maximum subroutine count
2014
- * Ordering optimization: Automatically orders subroutines by frequency for better compression
2015
-
2016
- Typical space savings: 30-50% reduction in CFF table size for fonts with similar
2017
- glyph shapes.
2018
-
2019
- NOTE: Current implementation calculates accurate optimization metrics but does
2020
- not modify the output CFF table. Full CFF serialization with subroutines will be
2021
- available in the next development phase.
2022
-
2023
- ==== Edge cases
2024
-
2025
- The optimizer correctly handles:
2026
-
2027
- * Multi-byte numbers: Number encodings from 1-5 bytes (CFF Type 2 format)
2028
- * Two-byte operators: Operators with 0x0c prefix (e.g., `div` in `lib/fontisan/tables/cff/charstring.rb`, `flex` in `lib/fontisan/tables/cff/charstring.rb`)
2029
- * Overlapping patterns: Multiple patterns at same byte positions
2030
- * Stack-neutral validation: Patterns verified to maintain consistent stack state
2031
-
2032
- ==== Technical details
2033
-
2034
- The subroutine optimizer uses a four-stage pipeline:
2035
-
2036
- [source]
2037
- ----
2038
- CharStrings → Pattern Analysis → Selection → Ordering → Metadata
2039
- (Input) (Find repeats) (Optimize) (Frequency) (Output)
2040
- ----
2041
-
2042
- **Pattern analysis**:
2043
-
2044
- . Extracts byte sequences from all CharStrings
2045
- . Identifies repeating patterns across glyphs
2046
- . Filters by minimum pattern length (default: 10 bytes)
2047
- . Builds pattern frequency map
2048
-
2049
- **Selection algorithm**:
2050
-
2051
- . Calculates savings for each pattern: `frequency × (length - overhead)`
2052
- . Ranks patterns by total savings (descending)
2053
- . Selects top patterns up to `max_subroutines` limit
2054
- . Ensures selected patterns don't exceed CFF limits
2055
-
2056
- **Ordering optimization**:
2057
-
2058
- . Sorts subroutines by usage frequency (most used first)
2059
- . Optimizes CFF bias calculation for better compression
2060
- . Ensures subroutine indices fit within CFF constraints
2061
-
2062
- **CFF bias calculation**:
2063
-
2064
- [source]
2065
- ----
2066
- Subroutine count CFF Bias
2067
- ----------------- ---------
2068
- 0-1239 107
2069
- 1240-33899 1131
2070
- 33900-65535 32768
2071
- ----
2072
-
2073
- The bias value determines how subroutine indices are encoded in CharStrings,
2074
- affecting the final size.
2075
-
2076
-
2077
- ==== Troubleshooting
2078
-
2079
- If you encounter CharString parsing errors after optimization:
2080
-
2081
- . Verify bias calculation: Ensure bias matches CFF specification (107, 1131, or 32768)
2082
- . Check operator boundaries: Patterns should only be extracted at valid boundaries
2083
- . Ensure no overlaps: Multiple patterns should not occupy same byte positions
2084
- . Enable verbose mode: Use `--verbose` flag for detailed diagnostics
2085
-
2086
- .Subroutine debugging workflow example
2087
- ====
2088
- [source,bash]
2089
- ----
2090
- # Convert with verbose output
2091
- $ fontisan convert input.ttf --to otf --output output.otf --optimize --verbose
2092
-
2093
- # Validate the output
2094
- $ fontisan validate output.otf
2095
-
2096
- # Check CharString structure
2097
- $ fontisan info output.otf
2098
- ----
2099
-
2100
- If validation fails, try:
2101
-
2102
- [source,bash]
2103
- ----
2104
- # Disable optimization
2105
- $ fontisan convert input.ttf --to otf --output output.otf
2106
-
2107
- # Use stack-aware mode for safer optimization
2108
- $ fontisan convert input.ttf --to otf --output output.otf --optimize --stack-aware
2109
- ----
2110
- ====
2111
-
2112
- .Optimization parameters
2113
- [example]
2114
- ====
2115
- [source,bash]
2116
- ----
2117
- # Adjust pattern matching sensitivity
2118
- $ fontisan convert input.ttf --to otf --output output.otf \
2119
- --optimize \
2120
- --min-pattern-length 15 \
2121
- --max-subroutines 10000 \
2122
- --verbose
2123
-
2124
- # Disable ordering optimization
2125
- $ fontisan convert input.ttf --to otf --output output.otf \
2126
- --optimize \
2127
- --no-optimize-ordering
2128
- ----
2129
- ====
2130
-
2131
- Where,
2132
-
2133
- `--optimize`:: Enable subroutine optimization (default: false)
2134
- `--min-pattern-length N`:: Minimum pattern length in bytes (default: 10)
2135
- `--max-subroutines N`:: Maximum number of subroutines to generate (default: 65,535)
2136
- `--optimize-ordering`:: Optimize subroutine ordering by frequency (default: true)
2137
- `--verbose`:: Show detailed optimization statistics
2138
-
2139
-
2140
- === Stack-aware optimization
2141
-
2142
- ==== General
2143
-
2144
- Stack-aware optimization is an advanced mode that ensures all extracted patterns
2145
- are stack-neutral, guaranteeing 100% safety and reliability.
2146
-
2147
- Unlike normal byte-level pattern matching, stack-aware mode simulates CharString
2148
- execution to track operand stack depth, only extracting patterns that maintain
2149
- consistent stack state.
2150
-
2151
- Key benefits:
2152
-
2153
- * **100% Reliability**: All patterns are validated to be stack-neutral
2154
- * **No Stack Errors**: Eliminates stack underflow/overflow issues
2155
- * **Faster Processing**: 6-12x faster than normal optimization due to early filtering
2156
- * **Smaller Pattern Set**: Significantly fewer candidates reduce memory usage
2157
-
2158
- Trade-offs:
2159
-
2160
- * **Lower Compression**: ~6% reduction vs ~11% with normal mode
2161
- * **Fewer Patterns**: Filters out 90%+ of raw patterns for safety
2162
- * **Stack Validation Overhead**: Adds stack tracking during analysis
2163
-
2164
- ==== Command-line usage
2165
-
2166
- .Enable stack-aware optimization
2167
- [example]
2168
- ====
2169
- [source,bash]
2170
- ----
2171
- # Convert with stack-aware optimization
2172
- $ fontisan convert input.ttf --to otf --output output.otf \
2173
- --optimize \
2174
- --stack-aware \
2175
- --verbose
2176
-
2177
- Converting input.ttf to otf...
2178
-
2179
- Analyzing CharString patterns (4515 glyphs)...
2180
- Found 8566 potential patterns
2181
- Selecting optimal patterns...
2182
- Selected 832 patterns for subroutinization
2183
- Building subroutines...
2184
- Generated 832 subroutines
2185
- Rewriting CharStrings with subroutine calls...
2186
- Rewrote 4515 CharStrings
2187
-
2188
- Subroutine Optimization Results:
2189
- Patterns found: 8566
2190
- Patterns selected: 832
2191
- Subroutines generated: 832
2192
- Estimated bytes saved: 46,280
2193
- CFF bias: 0
2194
-
2195
- Conversion complete!
2196
- Input: input.ttf (806.3 KB)
2197
- Output: output.otf (660.7 KB)
2198
- ----
2199
- ====
2200
-
2201
- .Stack-aware vs normal mode
2202
- [example]
2203
- ====
2204
- [source,bash]
2205
- ----
2206
- # Use the comparison script
2207
- $ ruby scripts/compare_stack_aware.rb input.ttf
2208
-
2209
- File Size Reduction:
2210
- Normal: 81.49 KB (11.27%)
2211
- Stack-Aware: 43.17 KB (6.13%)
2212
-
2213
- Processing Times:
2214
- Normal: 18.38 s
2215
- Stack-Aware: 1.54 s (12x faster)
2216
-
2217
- Stack-Aware Efficiency: 52.97% of normal optimization
2218
- ----
2219
- ====
2220
-
2221
- Where,
2222
-
2223
- `--stack-aware`:: Enable stack-aware pattern detection (default: false)
2224
-
2225
- ==== Using the Ruby API
2226
-
2227
- .Basic stack-aware optimization
2228
- [example]
2229
- ====
2230
- [source,ruby]
2231
- ----
2232
- require 'fontisan'
2233
-
2234
- # Load TrueType font
2235
- font = Fontisan::FontLoader.load('input.ttf')
2236
-
2237
- # Convert with stack-aware optimization
2238
- converter = Fontisan::Converters::OutlineConverter.new
2239
- tables = converter.convert(font, {
2240
- target_format: :otf,
2241
- optimize_subroutines: true,
2242
- stack_aware: true # Enable safe mode
2243
- })
2244
-
2245
- # Access optimization results
2246
- optimization = tables.instance_variable_get(:@subroutine_optimization)
2247
- puts "Patterns found: #{optimization[:pattern_count]}"
2248
- puts "Stack-neutral patterns: #{optimization[:selected_count]}"
2249
- puts "Processing time: #{optimization[:processing_time]}s"
2250
-
2251
- # Write output
2252
- Fontisan::FontWriter.write_to_file(
2253
- tables,
2254
- 'output.otf',
2255
- sfnt_version: 0x4F54544F
2256
- )
2257
- ----
2258
- ====
2259
-
2260
- ==== Technical details
2261
-
2262
- Stack-aware mode uses a three-stage validation process:
2263
-
2264
- [source]
2265
- ----
2266
- CharString Bytes → Stack Tracking → Pattern Validation → Safe Patterns
2267
- (Input) (Simulate) (Filter) (Output)
2268
- ----
2269
-
2270
- **Stack tracking**:
2271
-
2272
- . Simulates CharString execution without full interpretation
2273
- . Records stack depth at each byte position
2274
- . Handles 40+ Type 2 CharString operators with correct stack effects
2275
-
2276
- **Pattern validation**:
2277
-
2278
- . Checks if pattern start and end have same stack depth
2279
- . Ensures no stack underflow during pattern execution
2280
- . Verifies consistent results regardless of initial stack state
2281
-
2282
- **Stack-neutral pattern** criteria. Pattern is stack-neutral if:
2283
-
2284
- . depth_at(pattern_start) == depth_at(pattern_end)
2285
- . No negative depth during pattern execution
2286
- . Pattern produces same result for any valid initial stack
2287
- +
2288
- .Example Stack-Neutral Pattern
2289
- [source]
2290
- ----
2291
- 10 20 rmoveto # Pushes 2 operands, consumes 2 → neutral
2292
- ----
2293
- +
2294
- .Example Non-Neutral Pattern
2295
- [source]
2296
- ----
2297
- 10 20 add # Pushes 2, consumes 2, produces 1 → NOT neutral
2298
- ----
2299
-
2300
- ==== When to use stack-aware mode
2301
-
2302
- **Recommended for**:
2303
-
2304
- * Production font conversion where reliability is critical
2305
- * Fonts that will undergo further processing
2306
- * Web fonts where correctness matters more than minimal size
2307
- * Situations where testing/validation is limited
2308
-
2309
- **Normal mode acceptable for**:
2310
-
2311
- * Development/testing environments
2312
- * When full validation will be performed post-conversion
2313
- * Maximum compression is priority over guaranteed safety
2314
-
2315
- ==== Using the Ruby API
2316
-
2317
- .Basic optimization
2318
- [example]
2319
- ====
2320
- [source,ruby]
2321
- ----
2322
- require 'fontisan'
2323
-
2324
- # Load TrueType font
2325
- font = Fontisan::FontLoader.load('input.ttf')
2326
-
2327
- # Convert with optimization
2328
- converter = Fontisan::Converters::OutlineConverter.new
2329
- tables = converter.convert(font, {
2330
- target_format: :otf,
2331
- optimize_subroutines: true
2332
- })
2333
-
2334
- # Access optimization results
2335
- optimization = tables.instance_variable_get(:@subroutine_optimization)
2336
- puts "Patterns found: #{optimization[:pattern_count]}"
2337
- puts "Selected: #{optimization[:selected_count]}"
2338
- puts "Savings: #{optimization[:savings]} bytes"
2339
-
2340
- # Write output
2341
- Fontisan::FontWriter.write_to_file(
2342
- tables,
2343
- 'output.otf',
2344
- sfnt_version: 0x4F54544F
2345
- )
2346
- ----
2347
- ====
2348
-
2349
- .Custom optimization parameters
2350
- [example]
2351
- ====
2352
- [source,ruby]
2353
- ----
2354
- require 'fontisan'
2355
-
2356
- font = Fontisan::FontLoader.load('input.ttf')
2357
- converter = Fontisan::Converters::OutlineConverter.new
2358
-
2359
- # Fine-tune optimization
2360
- tables = converter.convert(font, {
2361
- target_format: :otf,
2362
- optimize_subroutines: true,
2363
- min_pattern_length: 15,
2364
- max_subroutines: 5000,
2365
- optimize_ordering: true,
2366
- verbose: true
2367
- })
2368
-
2369
- # Analyze results
2370
- optimization = tables.instance_variable_get(:@subroutine_optimization)
2371
- if optimization[:selected_count] > 0
2372
- efficiency = optimization[:savings].to_f / optimization[:selected_count]
2373
- puts "Average savings per subroutine: #{efficiency.round(2)} bytes"
2374
- end
2375
- ----
1956
+ Fonts are extracted from dfont and repacked as OTC.
1957
+ All TrueType fonts are converted to OpenType/CFF format.
2376
1958
  ====
2377
1959
 
2378
1960
 
2379
- == Round-trip validation
1961
+ == Round-Trip validation
2380
1962
 
2381
1963
  === General
2382
1964
 
@@ -2449,7 +2031,7 @@ Loron.
2449
2031
 
2450
2032
  Support for layered import CFF color glyphs rasterizing on demand, with
2451
2033
  composite font support, a multi-layer color font represented by many
2452
- CFF fonts stacked on top of each other. ColorGlyph support contains
2034
+ CFF fonts stacked on top of each other. ColorGlyph support contains
2453
2035
  color glyphs, advanced color fonts glyphs and raster images (PNG or JPG)
2454
2036
  combined with TrueType outlines.
2455
2037
 
@@ -2481,7 +2063,7 @@ Fontisan can:
2481
2063
 
2482
2064
  === Universal color layers
2483
2065
 
2484
- (Converted TTF, OTF files)
2066
+ (Fontisan, converted TTF, OTF files)
2485
2067
 
2486
2068
  Fontisan can:
2487
2069