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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +168 -32
- data/README.adoc +673 -1091
- data/lib/fontisan/cli.rb +94 -13
- data/lib/fontisan/collection/dfont_builder.rb +315 -0
- data/lib/fontisan/commands/convert_command.rb +118 -7
- data/lib/fontisan/commands/pack_command.rb +129 -22
- data/lib/fontisan/commands/validate_command.rb +107 -151
- data/lib/fontisan/config/conversion_matrix.yml +175 -1
- data/lib/fontisan/constants.rb +8 -0
- data/lib/fontisan/converters/collection_converter.rb +438 -0
- data/lib/fontisan/converters/woff2_encoder.rb +7 -29
- data/lib/fontisan/dfont_collection.rb +185 -0
- data/lib/fontisan/font_loader.rb +91 -6
- data/lib/fontisan/models/validation_report.rb +227 -0
- data/lib/fontisan/parsers/dfont_parser.rb +192 -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/true_type_font.rb +8 -46
- data/lib/fontisan/validation/collection_validator.rb +265 -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 +13 -12
- 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
|
@@ -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,
|
|
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,
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
738
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
1043
|
-
|
|
1044
|
-
|
|
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
|
-
|
|
1047
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1871
|
+
=== Convert between font container formats
|
|
1310
1872
|
|
|
1311
|
-
|
|
1873
|
+
==== General
|
|
1312
1874
|
|
|
1313
|
-
Fontisan supports
|
|
1875
|
+
Fontisan supports converting font collections between TrueType Collection (TTC),
|
|
1876
|
+
OpenType Collection (OTC), and Apple dfont formats.
|
|
1314
1877
|
|
|
1315
|
-
|
|
1878
|
+
All collection formats (TTC, OTC, dfont) support mixed TrueType and OpenType fonts.
|
|
1316
1879
|
|
|
1317
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1888
|
+
NOTE: dfont supports mixed TrueType and OpenType fonts in the same suitcase.
|
|
1324
1889
|
|
|
1325
|
-
|
|
1890
|
+
.TTC to OTC conversion (preserves formats)
|
|
1891
|
+
[example]
|
|
1892
|
+
====
|
|
1893
|
+
[source,shell]
|
|
1326
1894
|
----
|
|
1327
|
-
|
|
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
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1921
|
+
All TrueType fonts are converted to OpenType/CFF format.
|
|
1922
|
+
====
|
|
1365
1923
|
|
|
1366
|
-
|
|
1924
|
+
.dfont to OTC conversion (preserves formats)
|
|
1925
|
+
[example]
|
|
1926
|
+
====
|
|
1927
|
+
[source,shell]
|
|
1367
1928
|
----
|
|
1368
|
-
|
|
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
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
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
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1938
|
+
Fonts are extracted from dfont and repacked as OTC.
|
|
1939
|
+
Original font formats are preserved (mixed TTF+OTF supported).
|
|
1940
|
+
====
|
|
1385
1941
|
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
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
|
-
|
|
1398
|
-
|
|
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-
|
|
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.
|
|
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
|
-
(
|
|
2066
|
+
(Fontisan, converted TTF, OTF files)
|
|
2485
2067
|
|
|
2486
2068
|
Fontisan can:
|
|
2487
2069
|
|