fontisan 0.2.4 → 0.2.5
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_todo.yml +150 -30
- data/README.adoc +497 -242
- data/lib/fontisan/cli.rb +67 -6
- data/lib/fontisan/commands/validate_command.rb +107 -151
- data/lib/fontisan/converters/woff2_encoder.rb +7 -29
- data/lib/fontisan/models/validation_report.rb +227 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
- data/lib/fontisan/tables/cmap.rb +82 -2
- data/lib/fontisan/tables/glyf.rb +118 -0
- data/lib/fontisan/tables/head.rb +60 -0
- data/lib/fontisan/tables/hhea.rb +74 -0
- data/lib/fontisan/tables/maxp.rb +60 -0
- data/lib/fontisan/tables/name.rb +76 -0
- data/lib/fontisan/tables/os2.rb +113 -0
- data/lib/fontisan/tables/post.rb +57 -0
- data/lib/fontisan/validators/basic_validator.rb +85 -0
- data/lib/fontisan/validators/font_book_validator.rb +130 -0
- data/lib/fontisan/validators/opentype_validator.rb +112 -0
- data/lib/fontisan/validators/profile_loader.rb +139 -0
- data/lib/fontisan/validators/validator.rb +484 -0
- data/lib/fontisan/validators/web_font_validator.rb +102 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +78 -6
- metadata +7 -11
- data/lib/fontisan/config/validation_rules.yml +0 -149
- data/lib/fontisan/validation/checksum_validator.rb +0 -170
- data/lib/fontisan/validation/consistency_validator.rb +0 -197
- data/lib/fontisan/validation/structure_validator.rb +0 -198
- data/lib/fontisan/validation/table_validator.rb +0 -158
- data/lib/fontisan/validation/validator.rb +0 -152
- data/lib/fontisan/validation/variable_font_validator.rb +0 -218
- data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
- data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
- data/lib/fontisan/validation/woff2_validator.rb +0 -248
data/README.adoc
CHANGED
|
@@ -424,6 +424,11 @@ tables:
|
|
|
424
424
|
length: 17870
|
|
425
425
|
offset: 542992
|
|
426
426
|
checksum: 701383168
|
|
427
|
+
- tag: OS/2
|
|
428
|
+
length: 96
|
|
429
|
+
offset: 392
|
|
430
|
+
checksum: 1193824355
|
|
431
|
+
...
|
|
427
432
|
----
|
|
428
433
|
====
|
|
429
434
|
|
|
@@ -505,7 +510,7 @@ Syntax:
|
|
|
505
510
|
$ fontisan unicode FONT_FILE [--format FORMAT]
|
|
506
511
|
----
|
|
507
512
|
|
|
508
|
-
Where
|
|
513
|
+
Where:
|
|
509
514
|
|
|
510
515
|
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
511
516
|
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
@@ -734,10 +739,8 @@ Dry-run mode: Preview of instance generation
|
|
|
734
739
|
Coordinates:
|
|
735
740
|
wght: 700.0
|
|
736
741
|
|
|
737
|
-
Output
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
format: same as input
|
|
742
|
+
Output file: variable-instance.ttf
|
|
743
|
+
Format: same as input
|
|
741
744
|
|
|
742
745
|
Use without --dry-run to actually generate the instance.
|
|
743
746
|
----
|
|
@@ -1020,6 +1023,492 @@ Uses base64 encoding for binary data instead of hexadecimal, useful for
|
|
|
1020
1023
|
JSON-based workflows.
|
|
1021
1024
|
====
|
|
1022
1025
|
|
|
1026
|
+
== Font validation
|
|
1027
|
+
|
|
1028
|
+
=== General
|
|
1029
|
+
|
|
1030
|
+
Fontisan provides validation functionality to ensure font quality, structural
|
|
1031
|
+
integrity, and compliance with various standards.
|
|
1032
|
+
|
|
1033
|
+
The validation framework allows developers to create custom validators using a
|
|
1034
|
+
declarative DSL. Validators can perform checks on font tables, fields,
|
|
1035
|
+
structures, usability, instructions, and glyphs.
|
|
1036
|
+
|
|
1037
|
+
=== Predefined profiles
|
|
1038
|
+
|
|
1039
|
+
Fontisan includes several predefined validation profiles for common use cases:
|
|
1040
|
+
|
|
1041
|
+
`indexability`:: Fast validation for font discovery and indexing (< 50ms). Uses
|
|
1042
|
+
BasicValidator with 8 essential checks. Loading mode: metadata.
|
|
1043
|
+
|
|
1044
|
+
`usability`:: Basic usability for font installation. Uses FontBookValidator with
|
|
1045
|
+
26 checks including macOS Font Book compatibility. Loading mode: full.
|
|
1046
|
+
|
|
1047
|
+
`production`:: Comprehensive quality checks for production fonts (default
|
|
1048
|
+
profile). Uses OpenTypeValidator with 36 checks for OpenType spec compliance.
|
|
1049
|
+
Loading mode: full.
|
|
1050
|
+
|
|
1051
|
+
`web`:: Web font embedding and optimization validation. Uses WebFontValidator
|
|
1052
|
+
with 18 checks for web deployment. Loading mode: full.
|
|
1053
|
+
|
|
1054
|
+
`spec_compliance`:: Full OpenType specification compliance with detailed checks.
|
|
1055
|
+
Uses OpenTypeValidator with info-level severity for comprehensive analysis.
|
|
1056
|
+
Loading mode: full.
|
|
1057
|
+
|
|
1058
|
+
`default`:: Alias for the production profile.
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
=== Command-line usage
|
|
1062
|
+
|
|
1063
|
+
==== Validate a font file against a predefined profile
|
|
1064
|
+
|
|
1065
|
+
.Validate with default profile
|
|
1066
|
+
[example]
|
|
1067
|
+
====
|
|
1068
|
+
[source,shell]
|
|
1069
|
+
----
|
|
1070
|
+
$ fontisan validate font.ttf
|
|
1071
|
+
----
|
|
1072
|
+
====
|
|
1073
|
+
|
|
1074
|
+
.Validate for web use
|
|
1075
|
+
[example]
|
|
1076
|
+
====
|
|
1077
|
+
[source,shell]
|
|
1078
|
+
----
|
|
1079
|
+
$ fontisan validate font.ttf -t web
|
|
1080
|
+
----
|
|
1081
|
+
====
|
|
1082
|
+
|
|
1083
|
+
.List available profiles
|
|
1084
|
+
[example]
|
|
1085
|
+
====
|
|
1086
|
+
[source,shell]
|
|
1087
|
+
----
|
|
1088
|
+
$ fontisan validate --list
|
|
1089
|
+
Available validation profiles:
|
|
1090
|
+
indexability - Fast validation for font discovery and indexing
|
|
1091
|
+
usability - Basic usability for installation
|
|
1092
|
+
production - Comprehensive quality checks
|
|
1093
|
+
web - Web embedding and optimization
|
|
1094
|
+
spec_compliance - Full OpenType spec compliance
|
|
1095
|
+
default - Default validation profile (alias for production)
|
|
1096
|
+
----
|
|
1097
|
+
====
|
|
1098
|
+
|
|
1099
|
+
.Full report to file
|
|
1100
|
+
[example]
|
|
1101
|
+
====
|
|
1102
|
+
[source,shell]
|
|
1103
|
+
----
|
|
1104
|
+
$ fontisan validate font.ttf -t production -r -o report.txt
|
|
1105
|
+
----
|
|
1106
|
+
====
|
|
1107
|
+
|
|
1108
|
+
.Summary with return value
|
|
1109
|
+
[example]
|
|
1110
|
+
====
|
|
1111
|
+
[source,shell]
|
|
1112
|
+
----
|
|
1113
|
+
$ fontisan validate font.ttf -S -R
|
|
1114
|
+
0 errors, 2 warnings, 0 info
|
|
1115
|
+
$ echo $?
|
|
1116
|
+
4 # Exit code 4 indicates warnings found
|
|
1117
|
+
----
|
|
1118
|
+
====
|
|
1119
|
+
|
|
1120
|
+
.Table format output
|
|
1121
|
+
[example]
|
|
1122
|
+
====
|
|
1123
|
+
[source,shell]
|
|
1124
|
+
----
|
|
1125
|
+
$ fontisan validate font.ttf -T
|
|
1126
|
+
CHECK_ID | STATUS | SEVERITY | TABLE
|
|
1127
|
+
------------------------------------------------------------
|
|
1128
|
+
required_tables | PASS | error | N/A
|
|
1129
|
+
name_version | PASS | error | name
|
|
1130
|
+
family_name | PASS | error | name
|
|
1131
|
+
...
|
|
1132
|
+
----
|
|
1133
|
+
====
|
|
1134
|
+
|
|
1135
|
+
==== CLI options
|
|
1136
|
+
|
|
1137
|
+
-t, --test-list PROFILE:: Select validation profile (indexability, usability,
|
|
1138
|
+
production, web, spec_compliance, default)
|
|
1139
|
+
|
|
1140
|
+
-l, --list:: List available validation profiles
|
|
1141
|
+
|
|
1142
|
+
-o, --output FILE:: Write report to file instead of stdout
|
|
1143
|
+
|
|
1144
|
+
-r, --full-report:: Generate full detailed report
|
|
1145
|
+
|
|
1146
|
+
-R, --return-value-results:: Use return value to indicate results (0=none,
|
|
1147
|
+
1=error, 2=fatal, 3=major, 4=minor, 5=info)
|
|
1148
|
+
|
|
1149
|
+
-S, --summary-report:: Generate brief summary report
|
|
1150
|
+
|
|
1151
|
+
-T, --table-report:: Generate tabular format report
|
|
1152
|
+
|
|
1153
|
+
-v, --verbose:: Enable verbose output
|
|
1154
|
+
|
|
1155
|
+
-w, --suppress-warnings:: Suppress warning output
|
|
1156
|
+
|
|
1157
|
+
-e, --exclude CHECKS:: Exclude specific checks (comma-separated list)
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
=== Ruby API usage
|
|
1161
|
+
|
|
1162
|
+
==== Architecture
|
|
1163
|
+
|
|
1164
|
+
The validation framework in Ruby consists of:
|
|
1165
|
+
|
|
1166
|
+
DSL base class:: `Fontisan::Validators::Validator` provides a declarative syntax
|
|
1167
|
+
for defining validation checks
|
|
1168
|
+
|
|
1169
|
+
Table validation helpers::
|
|
1170
|
+
56 helper methods across 8 core OpenType tables (name, head, maxp, hhea, glyf,
|
|
1171
|
+
cmap, post, OS/2) that perform specific validation checks
|
|
1172
|
+
|
|
1173
|
+
ValidationReport::
|
|
1174
|
+
Structured reports with individual check results, severity levels, and
|
|
1175
|
+
comprehensive issue tracking
|
|
1176
|
+
|
|
1177
|
+
==== Using predefined profiles
|
|
1178
|
+
|
|
1179
|
+
.Validate with Ruby API
|
|
1180
|
+
[example]
|
|
1181
|
+
====
|
|
1182
|
+
[source,ruby]
|
|
1183
|
+
----
|
|
1184
|
+
require 'fontisan'
|
|
1185
|
+
|
|
1186
|
+
# Validate with default profile (production)
|
|
1187
|
+
report = Fontisan.validate('font.ttf')
|
|
1188
|
+
puts report.valid? # => true or false
|
|
1189
|
+
|
|
1190
|
+
# Validate with specific profile
|
|
1191
|
+
report = Fontisan.validate('font.ttf', profile: :web)
|
|
1192
|
+
puts "Errors: #{report.summary.errors}"
|
|
1193
|
+
puts "Warnings: #{report.summary.warnings}"
|
|
1194
|
+
|
|
1195
|
+
# Check validation status
|
|
1196
|
+
if report.valid?
|
|
1197
|
+
puts "Font is valid for web use!"
|
|
1198
|
+
else
|
|
1199
|
+
puts "Font has #{report.summary.errors} errors"
|
|
1200
|
+
end
|
|
1201
|
+
----
|
|
1202
|
+
====
|
|
1203
|
+
|
|
1204
|
+
.Query validation results
|
|
1205
|
+
[example]
|
|
1206
|
+
====
|
|
1207
|
+
[source,ruby]
|
|
1208
|
+
----
|
|
1209
|
+
report = Fontisan.validate('font.ttf', profile: :production)
|
|
1210
|
+
|
|
1211
|
+
# Get issues by severity
|
|
1212
|
+
fatal_issues = report.fatal_errors
|
|
1213
|
+
error_issues = report.errors_only
|
|
1214
|
+
warning_issues = report.warnings_only
|
|
1215
|
+
info_issues = report.info_only
|
|
1216
|
+
|
|
1217
|
+
# Get issues by category
|
|
1218
|
+
table_issues = report.issues_by_category('table_validation')
|
|
1219
|
+
|
|
1220
|
+
# Get check results
|
|
1221
|
+
failed_ids = report.failed_check_ids
|
|
1222
|
+
pass_rate = report.pass_rate
|
|
1223
|
+
|
|
1224
|
+
# Export to different formats
|
|
1225
|
+
yaml_output = report.to_yaml
|
|
1226
|
+
json_output = report.to_json
|
|
1227
|
+
summary = report.to_summary # "2 errors, 3 warnings, 0 info"
|
|
1228
|
+
----
|
|
1229
|
+
====
|
|
1230
|
+
|
|
1231
|
+
.Use validators directly
|
|
1232
|
+
[example]
|
|
1233
|
+
====
|
|
1234
|
+
[source,ruby]
|
|
1235
|
+
----
|
|
1236
|
+
require 'fontisan'
|
|
1237
|
+
|
|
1238
|
+
# Load font
|
|
1239
|
+
font = Fontisan::FontLoader.load('font.ttf')
|
|
1240
|
+
|
|
1241
|
+
# Use specific validator
|
|
1242
|
+
validator = Fontisan::Validators::OpenTypeValidator.new
|
|
1243
|
+
report = validator.validate(font)
|
|
1244
|
+
|
|
1245
|
+
# Check individual results
|
|
1246
|
+
name_check = report.result_of(:name_version)
|
|
1247
|
+
puts name_check.passed?
|
|
1248
|
+
puts name_check.severity
|
|
1249
|
+
----
|
|
1250
|
+
====
|
|
1251
|
+
|
|
1252
|
+
==== Using custom validators
|
|
1253
|
+
|
|
1254
|
+
===== General
|
|
1255
|
+
|
|
1256
|
+
Custom validators inherit from `Fontisan::Validators::Validator` and define
|
|
1257
|
+
validation logic using the DSL.
|
|
1258
|
+
|
|
1259
|
+
The DSL provides 6 check methods for different validation types:
|
|
1260
|
+
|
|
1261
|
+
* `check_table` - Validate table-level properties
|
|
1262
|
+
* `check_field` - Validate specific field values
|
|
1263
|
+
* `check_structure` - Validate font structure and relationships
|
|
1264
|
+
* `check_usability` - Validate usability and best practices
|
|
1265
|
+
* `check_instructions` - Validate TrueType instructions/hinting
|
|
1266
|
+
* `check_glyphs` - Validate individual glyphs
|
|
1267
|
+
|
|
1268
|
+
Each check receives a unique ID, severity level (:info, :warning, :error,
|
|
1269
|
+
:fatal), and a validation block.
|
|
1270
|
+
|
|
1271
|
+
.Creating a custom validator
|
|
1272
|
+
[example]
|
|
1273
|
+
====
|
|
1274
|
+
[source,ruby]
|
|
1275
|
+
----
|
|
1276
|
+
require 'fontisan/validators/validator'
|
|
1277
|
+
|
|
1278
|
+
# Define a custom validator
|
|
1279
|
+
class MyFontValidator < Fontisan::Validators::Validator
|
|
1280
|
+
private
|
|
1281
|
+
|
|
1282
|
+
def define_checks
|
|
1283
|
+
# Check name table
|
|
1284
|
+
check_table :name_version, 'name', severity: :error do |table|
|
|
1285
|
+
table.valid_version?
|
|
1286
|
+
end
|
|
1287
|
+
|
|
1288
|
+
check_table :family_name, 'name', severity: :error do |table|
|
|
1289
|
+
table.family_name_present?
|
|
1290
|
+
end
|
|
1291
|
+
|
|
1292
|
+
# Check head table
|
|
1293
|
+
check_table :head_magic, 'head', severity: :error do |table|
|
|
1294
|
+
table.valid_magic?
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
check_table :units_per_em, 'head', severity: :error do |table|
|
|
1298
|
+
table.valid_units_per_em?
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1301
|
+
# Check structure
|
|
1302
|
+
check_structure :required_tables, severity: :error do |font|
|
|
1303
|
+
%w[name head maxp hhea].all? { |tag| !font.table(tag).nil? }
|
|
1304
|
+
end
|
|
1305
|
+
end
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
# Use the validator
|
|
1309
|
+
font = Fontisan::FontLoader.load('font.ttf')
|
|
1310
|
+
validator = MyFontValidator.new
|
|
1311
|
+
report = validator.validate(font)
|
|
1312
|
+
|
|
1313
|
+
# Check results
|
|
1314
|
+
puts report.valid? # => true/false
|
|
1315
|
+
puts report.status # => "valid", "valid_with_warnings", "invalid"
|
|
1316
|
+
puts report.summary.errors # => number of errors
|
|
1317
|
+
puts report.summary.warnings # => number of warnings
|
|
1318
|
+
|
|
1319
|
+
# Query specific checks
|
|
1320
|
+
result = report.result_of(:name_version)
|
|
1321
|
+
puts result.passed? # => true/false
|
|
1322
|
+
puts result.severity # => "error"
|
|
1323
|
+
puts result.messages # => array of messages
|
|
1324
|
+
|
|
1325
|
+
# Get all failed checks
|
|
1326
|
+
report.failed_checks.each do |check|
|
|
1327
|
+
puts "#{check.check_id}: #{check.messages.join(', ')}"
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
# Serialize report
|
|
1331
|
+
puts report.to_yaml
|
|
1332
|
+
puts report.to_json
|
|
1333
|
+
----
|
|
1334
|
+
====
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
.Using table validation helpers
|
|
1338
|
+
[example]
|
|
1339
|
+
====
|
|
1340
|
+
The validation framework integrates with 56 built-in validation helpers across
|
|
1341
|
+
core OpenType tables. These helpers perform specific validation checks and
|
|
1342
|
+
return boolean values.
|
|
1343
|
+
|
|
1344
|
+
[source,ruby]
|
|
1345
|
+
----
|
|
1346
|
+
class ComprehensiveValidator < Fontisan::Validators::Validator
|
|
1347
|
+
private
|
|
1348
|
+
|
|
1349
|
+
def define_checks
|
|
1350
|
+
# Name table validation helpers
|
|
1351
|
+
check_table :name_validation, 'name' do |table|
|
|
1352
|
+
table.valid_version? &&
|
|
1353
|
+
table.valid_encoding_heuristics? &&
|
|
1354
|
+
table.family_name_present? &&
|
|
1355
|
+
table.postscript_name_valid?
|
|
1356
|
+
end
|
|
1357
|
+
|
|
1358
|
+
# Head table validation helpers
|
|
1359
|
+
check_table :head_validation, 'head' do |table|
|
|
1360
|
+
table.valid_magic? &&
|
|
1361
|
+
table.valid_version? &&
|
|
1362
|
+
table.valid_units_per_em? &&
|
|
1363
|
+
table.valid_bounding_box? &&
|
|
1364
|
+
table.valid_index_to_loc_format? &&
|
|
1365
|
+
table.valid_glyph_data_format?
|
|
1366
|
+
end
|
|
1367
|
+
|
|
1368
|
+
# Maxp table validation helpers
|
|
1369
|
+
check_table :maxp_validation, 'maxp' do |table|
|
|
1370
|
+
table.valid_version? &&
|
|
1371
|
+
table.valid_num_glyphs? &&
|
|
1372
|
+
table.valid_max_zones? &&
|
|
1373
|
+
table.has_truetype_metrics? &&
|
|
1374
|
+
table.reasonable_metrics?
|
|
1375
|
+
end
|
|
1376
|
+
end
|
|
1377
|
+
end
|
|
1378
|
+
----
|
|
1379
|
+
====
|
|
1380
|
+
|
|
1381
|
+
.Handling validation results
|
|
1382
|
+
[example]
|
|
1383
|
+
====
|
|
1384
|
+
[source,ruby]
|
|
1385
|
+
----
|
|
1386
|
+
validator = MyFontValidator.new
|
|
1387
|
+
report = validator.validate(font)
|
|
1388
|
+
|
|
1389
|
+
# Overall validation status
|
|
1390
|
+
if report.valid?
|
|
1391
|
+
puts "Font is valid!"
|
|
1392
|
+
else
|
|
1393
|
+
puts "Font has issues:"
|
|
1394
|
+
|
|
1395
|
+
# Show errors
|
|
1396
|
+
report.errors.each do |error|
|
|
1397
|
+
puts " [ERROR] #{error.category}: #{error.message}"
|
|
1398
|
+
puts " Location: #{error.location}" if error.location
|
|
1399
|
+
end
|
|
1400
|
+
|
|
1401
|
+
# Show warnings
|
|
1402
|
+
report.warnings.each do |warning|
|
|
1403
|
+
puts " [WARN] #{warning.category}: #{warning.message}"
|
|
1404
|
+
end
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
# Check specific validation results
|
|
1408
|
+
if report.result_of(:name_version)&.passed?
|
|
1409
|
+
puts "Name table version is valid"
|
|
1410
|
+
else
|
|
1411
|
+
puts "Name table version check failed"
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
# Get summary statistics
|
|
1415
|
+
puts "\nValidation Summary:"
|
|
1416
|
+
puts " Total checks: #{report.check_results.count}"
|
|
1417
|
+
puts " Passed: #{report.passed_checks.count}"
|
|
1418
|
+
puts " Failed: #{report.failed_checks.count}"
|
|
1419
|
+
puts " Errors: #{report.summary.errors}"
|
|
1420
|
+
puts " Warnings: #{report.summary.warnings}"
|
|
1421
|
+
puts " Info: #{report.summary.info}"
|
|
1422
|
+
----
|
|
1423
|
+
====
|
|
1424
|
+
|
|
1425
|
+
==== Validation helpers
|
|
1426
|
+
|
|
1427
|
+
===== General
|
|
1428
|
+
|
|
1429
|
+
The validation framework provides 56 helper methods across 8 core OpenType
|
|
1430
|
+
tables. Each helper returns a boolean indicating whether the validation passed.
|
|
1431
|
+
|
|
1432
|
+
Name table:
|
|
1433
|
+
|
|
1434
|
+
* `valid_version?` - Check if version is 0 or 1
|
|
1435
|
+
* `valid_encoding_heuristics?` - Check platform/encoding combinations
|
|
1436
|
+
* `has_valid_platform_combos?` - Check for required platform combinations
|
|
1437
|
+
* `family_name_present?` - Check if family name exists and is non-empty
|
|
1438
|
+
* `postscript_name_present?` - Check if PostScript name exists and is non-empty
|
|
1439
|
+
* `postscript_name_valid?` - Check if PostScript name matches required pattern
|
|
1440
|
+
|
|
1441
|
+
Head table:
|
|
1442
|
+
|
|
1443
|
+
* `valid_magic?` - Check magic number (0x5F0F3CF5)
|
|
1444
|
+
* `valid_version?` - Check version is 1.0
|
|
1445
|
+
* `valid_units_per_em?` - Check units per em is valid (16-16384)
|
|
1446
|
+
* `valid_bounding_box?` - Check bounding box coordinates
|
|
1447
|
+
* `valid_index_to_loc_format?` - Check format is 0 or 1
|
|
1448
|
+
* `valid_glyph_data_format?` - Check format is 0
|
|
1449
|
+
|
|
1450
|
+
Maxp Table:
|
|
1451
|
+
|
|
1452
|
+
* `valid_version?` - Check version is 0.5 or 1.0
|
|
1453
|
+
* `valid_num_glyphs?` - Check num glyphs >= 1
|
|
1454
|
+
* `valid_max_zones?` - Check maxZones is 1 or 2
|
|
1455
|
+
* `has_truetype_metrics?` - Check TrueType metrics are present
|
|
1456
|
+
* `reasonable_metrics?` - Check metrics are within reasonable bounds
|
|
1457
|
+
|
|
1458
|
+
Hhea Table:
|
|
1459
|
+
|
|
1460
|
+
* `valid_version?` - Check version is 1.0
|
|
1461
|
+
* `valid_metric_data_format?` - Check format is 0
|
|
1462
|
+
* `valid_number_of_h_metrics?` - Check count >= 1
|
|
1463
|
+
* `valid_ascent_descent?` - Check signs are correct
|
|
1464
|
+
* `valid_line_gap?` - Check line gap >= 0
|
|
1465
|
+
* `valid_advance_width_max?` - Check max width > 0
|
|
1466
|
+
* `valid_caret_slope?` - Check caret slope values
|
|
1467
|
+
* `valid_x_max_extent?` - Check extent > 0
|
|
1468
|
+
|
|
1469
|
+
Glyf Table:
|
|
1470
|
+
|
|
1471
|
+
* `has_empty_glyphs?` - Check for empty glyphs
|
|
1472
|
+
* `has_clipped_glyphs?` - Check for clipped glyphs
|
|
1473
|
+
* `has_instructions?` - Check for TrueType instructions
|
|
1474
|
+
* `contours_valid?` - Check contour counts
|
|
1475
|
+
* `glyphs_accessible?` - Check glyph accessibility
|
|
1476
|
+
|
|
1477
|
+
Cmap Table:
|
|
1478
|
+
|
|
1479
|
+
* `valid_version?` - Check version is 0
|
|
1480
|
+
* `has_valid_subtables?` - Check subtable validity
|
|
1481
|
+
* `has_unicode_mapping?` - Check for Unicode support
|
|
1482
|
+
* `has_valid_bmp_coverage?` - Check BMP coverage
|
|
1483
|
+
* `has_valid_format4?` - Check format 4 subtable
|
|
1484
|
+
* `glyph_indices_valid?` - Check glyph index validity
|
|
1485
|
+
* `supports_platform?` - Check platform support
|
|
1486
|
+
|
|
1487
|
+
Post Table:
|
|
1488
|
+
|
|
1489
|
+
* `valid_version?` - Check version (1.0, 2.0, 2.5, 3.0, 4.0)
|
|
1490
|
+
* `valid_italic_angle?` - Check italic angle range
|
|
1491
|
+
* `valid_underline_position?` - Check underline metrics
|
|
1492
|
+
* `valid_underline_thickness?` - Check underline thickness
|
|
1493
|
+
* `valid_is_fixed_pitch?` - Check fixed pitch flag
|
|
1494
|
+
* `has_glyph_names?` - Check for glyph names
|
|
1495
|
+
|
|
1496
|
+
OS/2 Table:
|
|
1497
|
+
|
|
1498
|
+
* `valid_version?` - Check version (0-5)
|
|
1499
|
+
* `valid_weight_class?` - Check weight class (100-900)
|
|
1500
|
+
* `valid_width_class?` - Check width class (1-9)
|
|
1501
|
+
* `valid_vendor_id?` - Check vendor ID format
|
|
1502
|
+
* `valid_typo_metrics?` - Check typographic metrics
|
|
1503
|
+
* `valid_win_metrics?` - Check Windows metrics
|
|
1504
|
+
* `valid_unicode_ranges?` - Check Unicode range bits
|
|
1505
|
+
* `has_valid_panose?` - Check PANOSE classification
|
|
1506
|
+
* `valid_selection_flags?` - Check style selection flags
|
|
1507
|
+
* `valid_first_char_index?` - Check first character index
|
|
1508
|
+
* `valid_last_char_index?` - Check last character index
|
|
1509
|
+
* `valid_typo_ascender?` - Check typographic ascender
|
|
1510
|
+
* `valid_typo_descender?` - Check typographic descender
|
|
1511
|
+
|
|
1023
1512
|
|
|
1024
1513
|
== Version information
|
|
1025
1514
|
|
|
@@ -1300,173 +1789,6 @@ $ fontisan validate FONT.{ttc,otc}
|
|
|
1300
1789
|
NOTE: In `extract_ttc`, this was done with `extract_ttc --validate FONT.ttc`.
|
|
1301
1790
|
|
|
1302
1791
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
== Color font support
|
|
1306
|
-
|
|
1307
|
-
=== General
|
|
1308
|
-
|
|
1309
|
-
Fontisan provides comprehensive analysis of all color font formats defined in the OpenType specification.
|
|
1310
|
-
|
|
1311
|
-
=== Supported color font formats
|
|
1312
|
-
|
|
1313
|
-
Fontisan supports all four color font table formats:
|
|
1314
|
-
|
|
1315
|
-
COLR/CPAL:: Layered color glyphs with custom color palettes (Microsoft/Google standard)
|
|
1316
|
-
|
|
1317
|
-
SVG:: Embedded SVG graphics with gzip compression support (W3C standard)
|
|
1318
|
-
|
|
1319
|
-
CBDT/CBLC:: Bitmap glyphs at multiple ppem sizes (Google format)
|
|
1320
|
-
|
|
1321
|
-
sbix:: Bitmap graphics with PNG/JPEG/TIFF support (Apple format)
|
|
1322
|
-
|
|
1323
|
-
=== Analyzing color fonts
|
|
1324
|
-
|
|
1325
|
-
[source,ruby]
|
|
1326
|
-
----
|
|
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
|
|
1343
|
-
|
|
1344
|
-
# Get SVG glyph information
|
|
1345
|
-
if info.has_svg_table
|
|
1346
|
-
puts "SVG glyphs: #{info.svg_glyph_count}"
|
|
1347
|
-
end
|
|
1348
|
-
|
|
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(', ')}"
|
|
1353
|
-
|
|
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
|
|
1360
|
-
----
|
|
1361
|
-
|
|
1362
|
-
=== Color font table access
|
|
1363
|
-
|
|
1364
|
-
Access color font tables directly:
|
|
1365
|
-
|
|
1366
|
-
[source,ruby]
|
|
1367
|
-
----
|
|
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}"
|
|
1374
|
-
|
|
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
|
|
1381
|
-
|
|
1382
|
-
# Access CPAL table (color palettes)
|
|
1383
|
-
if font.has_table?('CPAL')
|
|
1384
|
-
cpal = font.table('CPAL')
|
|
1385
|
-
|
|
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
|
|
1392
|
-
|
|
1393
|
-
# Access SVG table
|
|
1394
|
-
if font.has_table?('SVG ')
|
|
1395
|
-
svg = font.table('SVG ')
|
|
1396
|
-
|
|
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
1792
|
== Advanced features
|
|
1471
1793
|
|
|
1472
1794
|
Fontisan provides capabilities:
|
|
@@ -1556,9 +1878,6 @@ The loading mode can be queried at any time.
|
|
|
1556
1878
|
|
|
1557
1879
|
[source,ruby]
|
|
1558
1880
|
----
|
|
1559
|
-
# Check laziness
|
|
1560
|
-
font.lazy? # => true
|
|
1561
|
-
|
|
1562
1881
|
# Mode stored as font property
|
|
1563
1882
|
font.loading_mode # => :metadata or :full
|
|
1564
1883
|
|
|
@@ -1567,12 +1886,6 @@ font.table_available?(tag) # => boolean
|
|
|
1567
1886
|
|
|
1568
1887
|
# Access restricted based on mode
|
|
1569
1888
|
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
1889
|
----
|
|
1577
1890
|
|
|
1578
1891
|
|
|
@@ -1606,7 +1919,7 @@ fields:
|
|
|
1606
1919
|
`family_name`:: Font family name (nameID 1)
|
|
1607
1920
|
`subfamily_name`:: Font subfamily/style name (nameID 2)
|
|
1608
1921
|
`full_name`:: Full font name (nameID 4)
|
|
1609
|
-
`
|
|
1922
|
+
`post_script_name`:: PostScript name (nameID 6)
|
|
1610
1923
|
`preferred_family_name`:: Preferred family name (nameID 16, may be nil)
|
|
1611
1924
|
`preferred_subfamily_name`:: Preferred subfamily name (nameID 17, may be nil)
|
|
1612
1925
|
`units_per_em`:: Units per em from head table
|
|
@@ -1660,64 +1973,6 @@ font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: false)
|
|
|
1660
1973
|
----
|
|
1661
1974
|
|
|
1662
1975
|
|
|
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
1976
|
|
|
1722
1977
|
|
|
1723
1978
|
== Outline format conversion
|
|
@@ -2376,7 +2631,7 @@ end
|
|
|
2376
2631
|
====
|
|
2377
2632
|
|
|
2378
2633
|
|
|
2379
|
-
== Round-
|
|
2634
|
+
== Round-Trip validation
|
|
2380
2635
|
|
|
2381
2636
|
=== General
|
|
2382
2637
|
|
|
@@ -2449,7 +2704,7 @@ Loron.
|
|
|
2449
2704
|
|
|
2450
2705
|
Support for layered import CFF color glyphs rasterizing on demand, with
|
|
2451
2706
|
composite font support, a multi-layer color font represented by many
|
|
2452
|
-
CFF fonts stacked on top of each other.
|
|
2707
|
+
CFF fonts stacked on top of each other. ColorGlyph support contains
|
|
2453
2708
|
color glyphs, advanced color fonts glyphs and raster images (PNG or JPG)
|
|
2454
2709
|
combined with TrueType outlines.
|
|
2455
2710
|
|