fontisan 0.2.0 → 0.2.2
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 +119 -308
- data/README.adoc +1525 -1323
- data/Rakefile +45 -47
- data/benchmark/variation_quick_bench.rb +4 -4
- data/docs/FONT_HINTING.adoc +562 -0
- data/docs/VARIABLE_FONT_OPERATIONS.adoc +599 -0
- data/lib/fontisan/cli.rb +92 -34
- data/lib/fontisan/collection/builder.rb +82 -0
- data/lib/fontisan/collection/offset_calculator.rb +2 -0
- data/lib/fontisan/collection/table_deduplicator.rb +76 -0
- data/lib/fontisan/commands/base_command.rb +21 -2
- data/lib/fontisan/commands/convert_command.rb +96 -165
- data/lib/fontisan/commands/info_command.rb +111 -5
- data/lib/fontisan/commands/instance_command.rb +77 -85
- data/lib/fontisan/commands/validate_command.rb +28 -0
- data/lib/fontisan/config/validation_rules.yml +1 -1
- data/lib/fontisan/constants.rb +34 -24
- data/lib/fontisan/converters/format_converter.rb +154 -1
- data/lib/fontisan/converters/outline_converter.rb +101 -34
- data/lib/fontisan/converters/woff_writer.rb +9 -4
- data/lib/fontisan/font_loader.rb +14 -9
- data/lib/fontisan/font_writer.rb +9 -6
- data/lib/fontisan/formatters/text_formatter.rb +45 -1
- data/lib/fontisan/hints/hint_converter.rb +131 -2
- data/lib/fontisan/hints/hint_validator.rb +284 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +219 -140
- data/lib/fontisan/hints/postscript_hint_extractor.rb +151 -16
- data/lib/fontisan/hints/truetype_hint_applier.rb +90 -44
- data/lib/fontisan/hints/truetype_hint_extractor.rb +134 -11
- data/lib/fontisan/hints/truetype_instruction_analyzer.rb +261 -0
- data/lib/fontisan/hints/truetype_instruction_generator.rb +266 -0
- data/lib/fontisan/loading_modes.rb +6 -4
- data/lib/fontisan/models/collection_brief_info.rb +31 -0
- data/lib/fontisan/models/font_info.rb +3 -30
- data/lib/fontisan/models/hint.rb +183 -12
- data/lib/fontisan/models/outline.rb +4 -1
- data/lib/fontisan/open_type_font.rb +28 -10
- data/lib/fontisan/open_type_font_extensions.rb +54 -0
- data/lib/fontisan/optimizers/pattern_analyzer.rb +2 -1
- data/lib/fontisan/optimizers/subroutine_generator.rb +1 -1
- data/lib/fontisan/pipeline/format_detector.rb +249 -0
- data/lib/fontisan/pipeline/output_writer.rb +159 -0
- data/lib/fontisan/pipeline/strategies/base_strategy.rb +75 -0
- data/lib/fontisan/pipeline/strategies/instance_strategy.rb +93 -0
- data/lib/fontisan/pipeline/strategies/named_strategy.rb +118 -0
- data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +56 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +416 -0
- data/lib/fontisan/pipeline/variation_resolver.rb +165 -0
- data/lib/fontisan/subset/table_subsetter.rb +5 -5
- data/lib/fontisan/tables/cff/charstring.rb +58 -3
- data/lib/fontisan/tables/cff/charstring_builder.rb +34 -0
- data/lib/fontisan/tables/cff/charstring_parser.rb +249 -0
- data/lib/fontisan/tables/cff/charstring_rebuilder.rb +172 -0
- data/lib/fontisan/tables/cff/dict_builder.rb +19 -1
- data/lib/fontisan/tables/cff/hint_operation_injector.rb +209 -0
- data/lib/fontisan/tables/cff/offset_recalculator.rb +70 -0
- data/lib/fontisan/tables/cff/private_dict_writer.rb +131 -0
- data/lib/fontisan/tables/cff/table_builder.rb +221 -0
- data/lib/fontisan/tables/cff.rb +2 -0
- data/lib/fontisan/tables/cff2/charstring_parser.rb +14 -8
- data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +247 -0
- data/lib/fontisan/tables/cff2/region_matcher.rb +200 -0
- data/lib/fontisan/tables/cff2/table_builder.rb +580 -0
- data/lib/fontisan/tables/cff2/table_reader.rb +421 -0
- data/lib/fontisan/tables/cff2/variation_data_extractor.rb +212 -0
- data/lib/fontisan/tables/cff2.rb +10 -5
- data/lib/fontisan/tables/cvar.rb +2 -41
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +2 -1
- data/lib/fontisan/tables/glyf/curve_converter.rb +10 -4
- data/lib/fontisan/tables/glyf/glyph_builder.rb +27 -10
- data/lib/fontisan/tables/gvar.rb +2 -41
- data/lib/fontisan/tables/name.rb +4 -4
- data/lib/fontisan/true_type_font.rb +27 -10
- data/lib/fontisan/true_type_font_extensions.rb +54 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +42 -0
- data/lib/fontisan/validation/checksum_validator.rb +2 -2
- data/lib/fontisan/validation/table_validator.rb +1 -1
- data/lib/fontisan/validation/variable_font_validator.rb +218 -0
- data/lib/fontisan/variation/cache.rb +3 -1
- data/lib/fontisan/variation/converter.rb +121 -13
- data/lib/fontisan/variation/delta_applier.rb +2 -1
- data/lib/fontisan/variation/inspector.rb +2 -1
- data/lib/fontisan/variation/instance_generator.rb +2 -1
- data/lib/fontisan/variation/instance_writer.rb +341 -0
- data/lib/fontisan/variation/optimizer.rb +6 -3
- data/lib/fontisan/variation/subsetter.rb +32 -10
- data/lib/fontisan/variation/tuple_variation_header.rb +51 -0
- data/lib/fontisan/variation/variable_svg_generator.rb +268 -0
- data/lib/fontisan/variation/variation_preserver.rb +291 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/version.rb.orig +9 -0
- data/lib/fontisan/woff2/glyf_transformer.rb +693 -0
- data/lib/fontisan/woff2/hmtx_transformer.rb +164 -0
- data/lib/fontisan/woff2_font.rb +489 -468
- data/lib/fontisan/woff_font.rb +16 -11
- data/lib/fontisan.rb +54 -2
- data/scripts/measure_optimization.rb +15 -7
- metadata +37 -2
data/README.adoc
CHANGED
|
@@ -56,6 +56,7 @@ gem install fontisan
|
|
|
56
56
|
|
|
57
57
|
== Features
|
|
58
58
|
|
|
59
|
+
* Bidirectional font hint conversion (see link:docs/FONT_HINTING.adoc[Font Hinting Guide])
|
|
59
60
|
* Extract comprehensive font metadata (name, version, designer, license, etc.)
|
|
60
61
|
* List OpenType tables with checksums and offsets
|
|
61
62
|
* Extract glyph names from post table
|
|
@@ -71,7 +72,7 @@ gem install fontisan
|
|
|
71
72
|
* Collection management (pack/unpack TTC/OTC files with table deduplication)
|
|
72
73
|
* Support for TTF, OTF, TTC, OTC font formats (production ready)
|
|
73
74
|
* WOFF format support (reading complete, writing functional, pending full integration)
|
|
74
|
-
* WOFF2 format support (reading
|
|
75
|
+
* WOFF2 format support (reading complete with table transformations, writing planned)
|
|
75
76
|
* SVG font generation (complete)
|
|
76
77
|
* TTX/YAML/JSON export (complete)
|
|
77
78
|
* Command-line interface with 18 commands
|
|
@@ -85,1873 +86,2074 @@ gem install fontisan
|
|
|
85
86
|
* Compound glyph decomposition with transformation support (complete)
|
|
86
87
|
* CFF subroutine optimization for space-efficient OTF generation (preview mode)
|
|
87
88
|
* Various loading modes for high-performance font indexing (5x faster)
|
|
89
|
+
* Bidirectional hint conversion (TrueType ↔ PostScript) with validation (complete)
|
|
90
|
+
* CFF2 variable font support for PostScript hint conversion (complete)
|
|
88
91
|
|
|
89
|
-
NOTE: TTF ↔ OTF outline format conversion is in active development (Phase 1, ~80% complete). The universal outline model, CFF builders, curve converter, and compound glyph support are fully functional. Simple and compound glyphs convert successfully. See link:docs/IMPLEMENTATION_STATUS_V4.md[Implementation Status] for detailed progress tracking.
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
== Loading Modes
|
|
93
|
+
== Font information
|
|
93
94
|
|
|
94
95
|
=== General
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
Extract comprehensive metadata from font files. This includes font names,
|
|
98
|
+
version information, designer credits, vendor details, licensing information,
|
|
99
|
+
and font metrics.
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
[source,shell]
|
|
102
|
+
----
|
|
103
|
+
$ fontisan info FONT_FILE [--format FORMAT] [--brief]
|
|
104
|
+
----
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
manipulation
|
|
106
|
+
Where,
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, TTC, OTF)
|
|
109
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
110
|
+
`--brief`:: Show only basic font information
|
|
107
111
|
|
|
108
|
-
This architecture is particularly useful for software that only
|
|
109
|
-
needs basic font information without full parsing overhead, such as
|
|
110
|
-
font indexing systems or font discovery tools.
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
https://github.com/fontist/fontist[Fontist] library, where system fonts
|
|
114
|
-
need to be scanned quickly without loading unnecessary data.
|
|
113
|
+
=== Brief mode
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
loaded, and attempts to access non-loaded tables will return `nil`.
|
|
115
|
+
==== General
|
|
118
116
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
For font indexing systems that need to scan thousands of fonts quickly, use the
|
|
118
|
+
`--brief` flag to get essential metadata only. This mode uses metadata loading
|
|
119
|
+
and is **5x faster** than full mode.
|
|
122
120
|
|
|
123
|
-
|
|
124
|
-
font.table_available?("name") # => true
|
|
125
|
-
font.table_available?("GSUB") # => false
|
|
121
|
+
Brief mode provides significant performance improvements for font indexing:
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
* **5x faster** than full mode by using the `metadata` load mode
|
|
124
|
+
* **Loads only 6 tables** instead of 15-20 (name, head, hhea, maxp, OS/2, post)
|
|
125
|
+
* **Lower memory usage** through reduced table loading
|
|
126
|
+
* **Optimized for batch processing** of many fonts
|
|
130
127
|
|
|
131
|
-
|
|
132
|
-
font.table("GSUB") # => nil (not loaded in metadata mode)
|
|
133
|
-
----
|
|
128
|
+
Brief mode populates only the following 13 essential attributes:
|
|
134
129
|
|
|
135
|
-
|
|
130
|
+
Font identification::
|
|
131
|
+
* `font_format` - Font format (truetype, cff)
|
|
132
|
+
* `is_variable` - Whether font is variable
|
|
136
133
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
134
|
+
Essential names::
|
|
135
|
+
* `family_name` - Font family name
|
|
136
|
+
* `subfamily_name` - Font subfamily/style
|
|
137
|
+
* `full_name` - Full font name
|
|
138
|
+
* `postscript_name` - PostScript name
|
|
142
139
|
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
Version info::
|
|
141
|
+
* `version` - Version string
|
|
145
142
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
Metrics::
|
|
144
|
+
* `font_revision` - Font revision number
|
|
145
|
+
* `units_per_em` - Units per em
|
|
149
146
|
|
|
150
|
-
|
|
147
|
+
Vendor::
|
|
148
|
+
* `vendor_id` - Vendor/foundry ID
|
|
151
149
|
|
|
152
|
-
[source,ruby]
|
|
153
|
-
----
|
|
154
|
-
# Mode stored as font property
|
|
155
|
-
font.loading_mode # => :metadata or :full
|
|
156
150
|
|
|
157
|
-
|
|
158
|
-
font.table_available?(tag) # => boolean
|
|
151
|
+
==== Command-line usage
|
|
159
152
|
|
|
160
|
-
|
|
161
|
-
|
|
153
|
+
Syntax:
|
|
154
|
+
|
|
155
|
+
[source,shell]
|
|
156
|
+
----
|
|
157
|
+
$ fontisan info FONT_FILE --brief [--format FORMAT]
|
|
158
|
+
----
|
|
159
|
+
|
|
160
|
+
[source,shell]
|
|
162
161
|
----
|
|
162
|
+
# Individual font
|
|
163
|
+
$ fontisan info font.ttf --brief
|
|
164
|
+
Font type: TrueType (Not Variable)
|
|
165
|
+
Family: Noto Sans
|
|
166
|
+
...
|
|
163
167
|
|
|
168
|
+
# Collection
|
|
169
|
+
$ fontisan info fonts.ttc --brief
|
|
170
|
+
Collection: fonts.ttc
|
|
171
|
+
Fonts: 35
|
|
164
172
|
|
|
173
|
+
Font 0 (offset: 152):
|
|
174
|
+
Font type: OpenType (CFF) (Not Variable)
|
|
175
|
+
Family: Noto Serif CJK JP ExtraLight
|
|
176
|
+
...
|
|
177
|
+
----
|
|
165
178
|
|
|
166
|
-
=== Metadata mode
|
|
167
179
|
|
|
168
|
-
|
|
180
|
+
.Brief mode with default text output
|
|
181
|
+
[example]
|
|
182
|
+
====
|
|
183
|
+
[source,shell]
|
|
184
|
+
----
|
|
185
|
+
$ fontisan info spec/fixtures/fonts/MonaSans/mona-sans-2.0.8/googlefonts/variable/MonaSans[wdth,wght].ttf --brief
|
|
169
186
|
|
|
170
|
-
|
|
171
|
-
|
|
187
|
+
Font type: TrueType (Variable)
|
|
188
|
+
Family: Mona Sans ExtraLight
|
|
189
|
+
Subfamily: Regular
|
|
190
|
+
Full name: Mona Sans ExtraLight
|
|
191
|
+
PostScript name: MonaSans-ExtraLight
|
|
192
|
+
Version: Version 2.001
|
|
193
|
+
Vendor ID: GTHB
|
|
194
|
+
Font revision: 2.00101
|
|
195
|
+
Units per em: 1000
|
|
172
196
|
----
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
197
|
+
====
|
|
198
|
+
|
|
199
|
+
.Brief mode with JSON output
|
|
200
|
+
[example]
|
|
201
|
+
====
|
|
202
|
+
[source,shell]
|
|
203
|
+
----
|
|
204
|
+
$ fontisan info font.ttf --brief --format json
|
|
177
205
|
----
|
|
178
206
|
|
|
179
|
-
|
|
207
|
+
[source,json]
|
|
208
|
+
----
|
|
209
|
+
{
|
|
210
|
+
"font_format": "truetype",
|
|
211
|
+
"is_variable": false,
|
|
212
|
+
"family_name": "Open Sans",
|
|
213
|
+
"subfamily_name": "Regular",
|
|
214
|
+
"full_name": "Open Sans Regular",
|
|
215
|
+
"postscript_name": "OpenSans-Regular",
|
|
216
|
+
"version": "Version 3.000",
|
|
217
|
+
"font_revision": 3.0,
|
|
218
|
+
"vendor_id": "2001",
|
|
219
|
+
"units_per_em": 2048
|
|
220
|
+
}
|
|
221
|
+
----
|
|
222
|
+
====
|
|
180
223
|
|
|
181
|
-
name:: Font names and metadata
|
|
182
|
-
head:: Font header with global metrics
|
|
183
|
-
hhea:: Horizontal header with line spacing
|
|
184
|
-
maxp:: Maximum profile with glyph count
|
|
185
|
-
OS/2:: OS/2 and Windows metrics
|
|
186
|
-
post:: PostScript information
|
|
187
224
|
|
|
225
|
+
==== Ruby API usage
|
|
188
226
|
|
|
189
|
-
|
|
190
|
-
|
|
227
|
+
.Basic brief info access
|
|
228
|
+
[example]
|
|
229
|
+
====
|
|
230
|
+
[source,ruby]
|
|
231
|
+
----
|
|
232
|
+
require 'fontisan'
|
|
191
233
|
|
|
192
|
-
|
|
193
|
-
`subfamily_name`:: Font subfamily/style name (nameID 2)
|
|
194
|
-
`full_name`:: Full font name (nameID 4)
|
|
195
|
-
`post_script_name`:: PostScript name (nameID 6)
|
|
196
|
-
`preferred_family_name`:: Preferred family name (nameID 16, may be nil)
|
|
197
|
-
`preferred_subfamily_name`:: Preferred subfamily name (nameID 17, may be nil)
|
|
198
|
-
`units_per_em`:: Units per em from head table
|
|
234
|
+
info = Fontisan.info("font.ttf", brief: true)
|
|
199
235
|
|
|
236
|
+
# Access populated fields
|
|
237
|
+
puts info.family_name # "Open Sans"
|
|
238
|
+
puts info.postscript_name # "OpenSans-Regular"
|
|
239
|
+
puts info.is_variable # false
|
|
200
240
|
|
|
201
|
-
|
|
241
|
+
# Non-essential fields are nil
|
|
242
|
+
puts info.copyright # nil (not populated)
|
|
243
|
+
puts info.designer # nil (not populated)
|
|
202
244
|
|
|
203
|
-
|
|
245
|
+
# Serialize to YAML/JSON
|
|
246
|
+
puts info.to_yaml
|
|
247
|
+
puts info.to_json
|
|
248
|
+
----
|
|
249
|
+
====
|
|
204
250
|
|
|
205
|
-
.
|
|
251
|
+
.Brief info for font collections
|
|
252
|
+
[example]
|
|
253
|
+
====
|
|
206
254
|
[source,ruby]
|
|
207
255
|
----
|
|
208
|
-
|
|
209
|
-
font.table("GSUB") # => Available
|
|
210
|
-
font.table("GPOS") # => Available
|
|
256
|
+
require 'fontisan'
|
|
211
257
|
|
|
212
|
-
#
|
|
213
|
-
|
|
258
|
+
# Specify font index for TTC/OTC files
|
|
259
|
+
info = Fontisan.info("/path/to/fonts.ttc", brief: true, font_index: 0)
|
|
260
|
+
puts info.family_name
|
|
214
261
|
----
|
|
262
|
+
====
|
|
215
263
|
|
|
216
|
-
|
|
264
|
+
=== Full mode
|
|
217
265
|
|
|
218
|
-
|
|
219
|
-
* Including GSUB, GPOS, cmap, glyf/CFF, etc.
|
|
266
|
+
==== General
|
|
220
267
|
|
|
221
|
-
|
|
268
|
+
In full mode, these additional attributes are populated (remain `nil` in brief
|
|
269
|
+
mode):
|
|
222
270
|
|
|
223
|
-
|
|
224
|
-
|
|
271
|
+
* `postscript_cid_name`, `preferred_family`, `preferred_subfamily`, `mac_font_menu_name`
|
|
272
|
+
* `unique_id`, `description`, `designer`, `designer_url`
|
|
273
|
+
* `manufacturer`, `vendor_url`, `trademark`, `copyright`
|
|
274
|
+
* `license_description`, `license_url`, `sample_text`, `permissions`
|
|
225
275
|
|
|
226
|
-
|
|
276
|
+
==== Command-line usage
|
|
227
277
|
|
|
228
|
-
|
|
229
|
-
upfront.
|
|
278
|
+
Syntax:
|
|
230
279
|
|
|
231
|
-
|
|
280
|
+
[source,shell]
|
|
281
|
+
----
|
|
282
|
+
$ fontisan info FONT_FILE [--format FORMAT]
|
|
283
|
+
----
|
|
232
284
|
|
|
233
|
-
|
|
285
|
+
.Font information for Libertinus Serif Regular
|
|
286
|
+
[example]
|
|
287
|
+
====
|
|
288
|
+
[source,shell]
|
|
234
289
|
----
|
|
235
|
-
|
|
236
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: true)
|
|
290
|
+
$ fontisan info spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
237
291
|
|
|
238
|
-
|
|
239
|
-
|
|
292
|
+
Font type: TrueType
|
|
293
|
+
Family: Libertinus Serif
|
|
294
|
+
Subfamily: Regular
|
|
295
|
+
Full name: Libertinus Serif Regular
|
|
296
|
+
PostScript name: LibertinusSerif-Regular
|
|
297
|
+
Version: Version 7.051;RELEASE
|
|
298
|
+
Unique ID: 5.000;QUE ;LibertinusSerif-Regular
|
|
299
|
+
Designer: Philipp H. Poll, Khaled Hosny
|
|
300
|
+
Manufacturer: Caleb Maclennan
|
|
301
|
+
Vendor URL: https://github.com/alerque/libertinus
|
|
302
|
+
Vendor ID: QUE
|
|
303
|
+
License Description: This Font Software is licensed under the SIL Open Font
|
|
304
|
+
License, Version 1.1. This license is available with a
|
|
305
|
+
FAQ at: https://openfontlicense.org
|
|
306
|
+
License URL: https://openfontlicense.org
|
|
307
|
+
Font revision: 7.05099
|
|
308
|
+
Permissions: Installable
|
|
309
|
+
Units per em: 1000
|
|
310
|
+
----
|
|
311
|
+
====
|
|
240
312
|
|
|
241
|
-
# Full mode with lazy loading (tables loaded on-demand)
|
|
242
|
-
font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: true)
|
|
243
313
|
|
|
244
|
-
|
|
245
|
-
|
|
314
|
+
.Output in structured YAML format
|
|
315
|
+
[example]
|
|
316
|
+
====
|
|
317
|
+
[source,shell]
|
|
318
|
+
----
|
|
319
|
+
$ fontisan info spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --format yaml
|
|
246
320
|
----
|
|
247
321
|
|
|
322
|
+
[source,yaml]
|
|
323
|
+
----
|
|
324
|
+
font_format: truetype
|
|
325
|
+
is_variable: false
|
|
326
|
+
family_name: Libertinus Serif
|
|
327
|
+
subfamily_name: Regular
|
|
328
|
+
full_name: Libertinus Serif Regular
|
|
329
|
+
postscript_name: LibertinusSerif-Regular
|
|
330
|
+
version: Version 7.051;RELEASE
|
|
331
|
+
unique_id: 5.000;QUE ;LibertinusSerif-Regular
|
|
332
|
+
designer: Philipp H. Poll, Khaled Hosny
|
|
333
|
+
manufacturer: Caleb Maclennan
|
|
334
|
+
vendor_url: https://github.com/alerque/libertinus
|
|
335
|
+
vendor_id: QUE
|
|
336
|
+
license_description: 'This Font Software is licensed under the SIL Open Font License,
|
|
337
|
+
Version 1.1. This license is available with a FAQ at: https://openfontlicense.org'
|
|
338
|
+
license_url: https://openfontlicense.org
|
|
339
|
+
font_revision: 7.050994873046875
|
|
340
|
+
permissions: Installable
|
|
341
|
+
units_per_em: 1000
|
|
342
|
+
----
|
|
343
|
+
====
|
|
248
344
|
|
|
249
345
|
|
|
250
346
|
|
|
251
|
-
== Outline Format Conversion
|
|
252
347
|
|
|
253
|
-
|
|
348
|
+
== List OpenType tables
|
|
254
349
|
|
|
255
350
|
=== General
|
|
256
351
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
* **TrueType (TTF)**: Uses quadratic Bézier curves stored in glyf/loc tables
|
|
260
|
-
* **OpenType/CFF (OTF)**: Uses cubic Bézier curves stored in CFF table
|
|
352
|
+
To understanding font structure and verifying table integrity, Fontisan provides
|
|
353
|
+
detailed table listings.
|
|
261
354
|
|
|
262
|
-
|
|
355
|
+
Display the font's table directory, showing all OpenType tables with their
|
|
356
|
+
sizes, offsets, and checksums.
|
|
263
357
|
|
|
264
|
-
===
|
|
358
|
+
=== Command-line usage
|
|
265
359
|
|
|
266
|
-
|
|
360
|
+
Syntax:
|
|
267
361
|
|
|
268
|
-
[source,
|
|
362
|
+
[source,shell]
|
|
269
363
|
----
|
|
270
|
-
|
|
271
|
-
fontisan convert input.ttf --to otf --output output.otf
|
|
364
|
+
$ fontisan tables FONT_FILE [--format FORMAT]
|
|
272
365
|
----
|
|
273
366
|
|
|
274
|
-
|
|
367
|
+
Where,
|
|
275
368
|
|
|
276
|
-
|
|
369
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
370
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
371
|
+
|
|
372
|
+
.List of OpenType tables in Libertinus Serif Regular
|
|
373
|
+
[example]
|
|
374
|
+
====
|
|
375
|
+
[source,shell]
|
|
277
376
|
----
|
|
278
|
-
|
|
279
|
-
fontisan convert input.otf --to ttf --output output.ttf
|
|
377
|
+
$ fontisan tables spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
280
378
|
----
|
|
281
379
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
380
|
+
[source,text]
|
|
381
|
+
----
|
|
382
|
+
SFNT Version: TrueType (0x00010000)
|
|
383
|
+
Number of tables: 16
|
|
285
384
|
|
|
286
|
-
|
|
385
|
+
Tables:
|
|
386
|
+
GDEF 834 bytes (offset: 542156, checksum: 0x429C5C0C)
|
|
387
|
+
GPOS 17870 bytes (offset: 542992, checksum: 0x29CE4200)
|
|
388
|
+
OS/2 96 bytes (offset: 392, checksum: 0x4830F1C3)
|
|
389
|
+
cmap 3620 bytes (offset: 11412, checksum: 0x03AD3899)
|
|
390
|
+
cvt 248 bytes (offset: 18868, checksum: 0x3098127E)
|
|
391
|
+
fpgm 3596 bytes (offset: 15032, checksum: 0x622F0781)
|
|
392
|
+
gasp 8 bytes (offset: 542148, checksum: 0x00000010)
|
|
393
|
+
glyf 484900 bytes (offset: 30044, checksum: 0x0FF34594)
|
|
394
|
+
head 54 bytes (offset: 268, checksum: 0x18F5BDD0)
|
|
395
|
+
hhea 36 bytes (offset: 324, checksum: 0x191E2264)
|
|
396
|
+
hmtx 10924 bytes (offset: 488, checksum: 0x1F9D892B)
|
|
397
|
+
loca 10928 bytes (offset: 19116, checksum: 0x230B1A58)
|
|
398
|
+
maxp 32 bytes (offset: 360, checksum: 0x0EF919E7)
|
|
399
|
+
name 894 bytes (offset: 514944, checksum: 0x4E9173E6)
|
|
400
|
+
post 26308 bytes (offset: 515840, checksum: 0xE3D70231)
|
|
401
|
+
prep 239 bytes (offset: 18628, checksum: 0x8B4AB356)
|
|
287
402
|
----
|
|
288
|
-
|
|
289
|
-
fontisan convert font.otf --to ttf --output font.ttf
|
|
290
|
-
fontisan convert font.otf --to truetype --output font.ttf
|
|
403
|
+
====
|
|
291
404
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
405
|
+
.Output in structured YAML format
|
|
406
|
+
[example]
|
|
407
|
+
====
|
|
408
|
+
[source,shell]
|
|
409
|
+
----
|
|
410
|
+
$ fontisan tables spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --format yaml
|
|
296
411
|
----
|
|
297
412
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
After conversion, validate the output font:
|
|
301
|
-
|
|
302
|
-
[source,bash]
|
|
413
|
+
[source,yaml]
|
|
303
414
|
----
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
415
|
+
---
|
|
416
|
+
sfnt_version: TrueType (0x00010000)
|
|
417
|
+
num_tables: 16
|
|
418
|
+
tables:
|
|
419
|
+
- tag: GDEF
|
|
420
|
+
length: 834
|
|
421
|
+
offset: 542156
|
|
422
|
+
checksum: 1117543436
|
|
423
|
+
- tag: GPOS
|
|
424
|
+
length: 17870
|
|
425
|
+
offset: 542992
|
|
426
|
+
checksum: 701383168
|
|
307
427
|
----
|
|
428
|
+
====
|
|
308
429
|
|
|
309
|
-
=== Using the Ruby API
|
|
310
|
-
|
|
311
|
-
==== Basic conversion
|
|
312
430
|
|
|
313
|
-
|
|
314
|
-
----
|
|
315
|
-
require 'fontisan'
|
|
431
|
+
== List glyph names
|
|
316
432
|
|
|
317
|
-
|
|
318
|
-
font = Fontisan::FontLoader.load('input.ttf')
|
|
433
|
+
=== General
|
|
319
434
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
tables = converter.convert(font, target_format: :otf)
|
|
435
|
+
Show all glyph names defined in the font's post table. Each glyph is listed with
|
|
436
|
+
its index and name, useful for understanding the font's character coverage.
|
|
323
437
|
|
|
324
|
-
|
|
325
|
-
Fontisan::FontWriter.write_to_file(
|
|
326
|
-
tables,
|
|
327
|
-
'output.otf',
|
|
328
|
-
sfnt_version: 0x4F54544F # 'OTTO' for OpenType/CFF
|
|
329
|
-
)
|
|
330
|
-
----
|
|
438
|
+
=== Command-line usage
|
|
331
439
|
|
|
332
|
-
|
|
440
|
+
Syntax:
|
|
333
441
|
|
|
334
|
-
[source,
|
|
442
|
+
[source,shell]
|
|
443
|
+
----
|
|
444
|
+
$ fontisan glyphs FONT_FILE [--format FORMAT]
|
|
335
445
|
----
|
|
336
|
-
require 'fontisan'
|
|
337
446
|
|
|
338
|
-
|
|
339
|
-
font = Fontisan::FontLoader.load('input.ttf')
|
|
447
|
+
Where,
|
|
340
448
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if converter.supported?(:ttf, :otf)
|
|
344
|
-
tables = converter.convert(font, :otf)
|
|
449
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
450
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
345
451
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
452
|
+
|
|
453
|
+
.List of glyph names in Libertinus Serif Regular
|
|
454
|
+
[example]
|
|
455
|
+
====
|
|
456
|
+
[source,shell]
|
|
457
|
+
----
|
|
458
|
+
$ fontisan glyphs spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
353
459
|
----
|
|
354
460
|
|
|
355
|
-
|
|
461
|
+
[source,text]
|
|
462
|
+
----
|
|
463
|
+
Glyph count: 2731
|
|
464
|
+
Source: post_2.0
|
|
356
465
|
|
|
357
|
-
|
|
466
|
+
Glyph names:
|
|
467
|
+
0 .notdef
|
|
468
|
+
1 space
|
|
469
|
+
2 exclam
|
|
470
|
+
3 quotedbl
|
|
471
|
+
4 numbersign
|
|
472
|
+
5 dollar
|
|
473
|
+
6 percent
|
|
474
|
+
7 ampersand
|
|
475
|
+
8 quotesingle
|
|
476
|
+
9 parenleft
|
|
477
|
+
10 parenright
|
|
478
|
+
11 asterisk
|
|
479
|
+
12 plus
|
|
480
|
+
13 comma
|
|
481
|
+
14 hyphen
|
|
482
|
+
15 period
|
|
483
|
+
16 slash
|
|
484
|
+
17 zero
|
|
485
|
+
18 one
|
|
486
|
+
19 two
|
|
487
|
+
20 three
|
|
488
|
+
...
|
|
358
489
|
----
|
|
359
|
-
|
|
490
|
+
====
|
|
360
491
|
|
|
361
|
-
# Check if conversion is supported
|
|
362
|
-
converter.supported?(:ttf, :otf) # => true
|
|
363
|
-
converter.supported?(:otf, :ttf) # => true
|
|
364
492
|
|
|
365
|
-
|
|
366
|
-
converter.all_conversions
|
|
367
|
-
# => [{from: :ttf, to: :otf}, {from: :otf, to: :ttf}, ...]
|
|
493
|
+
== Show Unicode mappings
|
|
368
494
|
|
|
369
|
-
|
|
370
|
-
converter.supported_targets(:ttf)
|
|
371
|
-
# => [:ttf, :otf, :woff2, :svg]
|
|
372
|
-
----
|
|
495
|
+
=== General
|
|
373
496
|
|
|
374
|
-
|
|
497
|
+
Display Unicode codepoint to glyph index mappings from the cmap table. Shows
|
|
498
|
+
which glyphs are assigned to which Unicode characters.
|
|
375
499
|
|
|
376
|
-
|
|
500
|
+
=== Command-line usage
|
|
501
|
+
Syntax:
|
|
377
502
|
|
|
378
|
-
[source]
|
|
503
|
+
[source,shell]
|
|
379
504
|
----
|
|
380
|
-
|
|
381
|
-
------------- ------------------ -------------
|
|
382
|
-
TrueType (glyf) →→→ Command-based model →→→ OpenType/CFF
|
|
383
|
-
Quadratic curves Path representation Cubic curves
|
|
384
|
-
On/off-curve pts (format-agnostic) CharStrings
|
|
385
|
-
Delta encoding Bounding boxes Type 2 operators
|
|
386
|
-
Metrics Compact encoding
|
|
505
|
+
$ fontisan unicode FONT_FILE [--format FORMAT]
|
|
387
506
|
----
|
|
388
507
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
. Extract glyphs from glyf/loca tables
|
|
392
|
-
. Convert quadratic Bézier curves to universal outline format
|
|
393
|
-
. Build CFF table with CharStrings INDEX
|
|
394
|
-
. Update maxp table to version 0.5 (CFF format)
|
|
395
|
-
. Update head table (clear indexToLocFormat)
|
|
396
|
-
. Remove glyf/loca tables
|
|
397
|
-
. Preserve all other tables
|
|
398
|
-
|
|
399
|
-
==== OTF → TTF conversion
|
|
400
|
-
|
|
401
|
-
. Extract CharStrings from CFF table
|
|
402
|
-
. Convert cubic Bézier curves to universal outline format
|
|
403
|
-
. Convert cubic curves to quadratic using adaptive subdivision
|
|
404
|
-
. Build glyf and loca tables with optimal format selection
|
|
405
|
-
. Update maxp table to version 1.0 (TrueType format)
|
|
406
|
-
. Update head table (set indexToLocFormat)
|
|
407
|
-
. Remove CFF table
|
|
408
|
-
. Preserve all other tables
|
|
508
|
+
Where,
|
|
409
509
|
|
|
410
|
-
|
|
510
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
511
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
411
512
|
|
|
412
|
-
**Quadratic to cubic** (lossless):
|
|
413
513
|
|
|
414
|
-
|
|
514
|
+
.Unicode to glyph mappings in Libertinus Serif Regular
|
|
515
|
+
[example]
|
|
516
|
+
====
|
|
517
|
+
[source,shell]
|
|
415
518
|
----
|
|
416
|
-
|
|
417
|
-
P0 (start), Q (control), P2 (end)
|
|
418
|
-
|
|
419
|
-
Calculate cubic control points:
|
|
420
|
-
CP1 = P0 + (2/3) × (Q - P0)
|
|
421
|
-
CP2 = P2 + (2/3) × (Q - P2)
|
|
422
|
-
|
|
423
|
-
Result: Exact mathematical equivalent
|
|
519
|
+
$ fontisan unicode spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
424
520
|
----
|
|
425
521
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
[source]
|
|
522
|
+
[source,text]
|
|
429
523
|
----
|
|
430
|
-
|
|
431
|
-
P0 (start), CP1, CP2, P3 (end)
|
|
432
|
-
|
|
433
|
-
Use adaptive subdivision algorithm:
|
|
434
|
-
1. Estimate error of quadratic approximation
|
|
435
|
-
2. If error > threshold (0.5 units):
|
|
436
|
-
- Subdivide cubic curve at midpoint
|
|
437
|
-
- Recursively convert each half
|
|
438
|
-
3. Otherwise: Output quadratic approximation
|
|
524
|
+
Unicode mappings: 2382
|
|
439
525
|
|
|
440
|
-
|
|
526
|
+
U+0020 glyph 1 space
|
|
527
|
+
U+0021 glyph 2 exclam
|
|
528
|
+
U+0022 glyph 3 quotedbl
|
|
529
|
+
U+0023 glyph 4 numbersign
|
|
530
|
+
U+0024 glyph 5 dollar
|
|
531
|
+
U+0025 glyph 6 percent
|
|
532
|
+
U+0026 glyph 7 ampersand
|
|
533
|
+
U+0027 glyph 8 quotesingle
|
|
534
|
+
U+0028 glyph 9 parenleft
|
|
535
|
+
U+0029 glyph 10 parenright
|
|
536
|
+
U+002A glyph 11 asterisk
|
|
537
|
+
U+002B glyph 12 plus
|
|
538
|
+
U+002C glyph 13 comma
|
|
539
|
+
U+002D glyph 14 hyphen
|
|
540
|
+
U+002E glyph 15 period
|
|
541
|
+
U+002F glyph 16 slash
|
|
542
|
+
U+0030 glyph 17 zero
|
|
543
|
+
U+0031 glyph 18 one
|
|
544
|
+
...
|
|
441
545
|
----
|
|
546
|
+
====
|
|
442
547
|
|
|
443
|
-
=== Compound Glyph Support
|
|
444
548
|
|
|
445
|
-
|
|
549
|
+
== Variable font information
|
|
446
550
|
|
|
447
|
-
|
|
448
|
-
* **OTF → TTF**: CFF glyphs are converted to simple TrueType glyphs
|
|
551
|
+
=== General
|
|
449
552
|
|
|
450
|
-
|
|
553
|
+
Display variation axes and named instances for variable fonts. Shows the design
|
|
554
|
+
space and predefined styles available in the font.
|
|
451
555
|
|
|
452
|
-
|
|
556
|
+
=== Command-line usage
|
|
453
557
|
|
|
454
|
-
|
|
455
|
-
. Components recursively resolved (handling nested compound glyphs)
|
|
456
|
-
. Transformation matrices applied to each component (translation, scale, rotation)
|
|
457
|
-
. All components merged into a single simple outline
|
|
458
|
-
. Converted to CFF CharString format
|
|
558
|
+
Syntax:
|
|
459
559
|
|
|
460
|
-
|
|
560
|
+
[source,shell]
|
|
561
|
+
----
|
|
562
|
+
$ fontisan variable FONT_FILE [--format FORMAT]
|
|
563
|
+
----
|
|
461
564
|
|
|
462
|
-
|
|
565
|
+
Where,
|
|
463
566
|
|
|
464
|
-
|
|
567
|
+
`FONT_FILE`:: Path to the variable font file
|
|
568
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
465
569
|
|
|
466
|
-
|
|
570
|
+
|
|
571
|
+
.Variable font axes and instances in Mona Sans
|
|
572
|
+
[example]
|
|
573
|
+
====
|
|
574
|
+
[source,shell]
|
|
575
|
+
----
|
|
576
|
+
$ fontisan variable spec/fixtures/fonts/MonaSans/variable/MonaSans[wdth,wght].ttf
|
|
467
577
|
----
|
|
468
|
-
x' = a*x + c*y + e
|
|
469
|
-
y' = b*x + d*y + f
|
|
470
578
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
579
|
+
[source,text]
|
|
580
|
+
----
|
|
581
|
+
Axis 0: wdth
|
|
582
|
+
Axis 0 name: Width
|
|
583
|
+
Axis 0 range: 75 125
|
|
584
|
+
Axis 0 default: 100
|
|
585
|
+
Axis 1: wght
|
|
586
|
+
Axis 1 name: Weight
|
|
587
|
+
Axis 1 range: 200 900
|
|
588
|
+
Axis 1 default: 400
|
|
589
|
+
Instance 0 name: Mona Sans Narrow Thin
|
|
590
|
+
Instance 0 position: 75 200
|
|
591
|
+
Instance 1 name: Mona Sans Narrow ExtraLight
|
|
592
|
+
Instance 1 position: 75 250
|
|
593
|
+
Instance 2 name: Mona Sans Narrow Light
|
|
594
|
+
Instance 2 position: 75 300
|
|
595
|
+
...
|
|
475
596
|
----
|
|
597
|
+
====
|
|
476
598
|
|
|
477
|
-
The resolver handles:
|
|
478
599
|
|
|
479
|
-
|
|
480
|
-
* Nested compound glyphs (compounds referencing other compounds)
|
|
481
|
-
* Circular reference detection with maximum recursion depth (32 levels)
|
|
482
|
-
* Complex transformation matrices (uniform scale, x/y scale, full 2×2 matrix)
|
|
600
|
+
== Generate static instances from variable fonts
|
|
483
601
|
|
|
484
|
-
===
|
|
602
|
+
=== General
|
|
485
603
|
|
|
486
|
-
|
|
604
|
+
Generate static font instances from variable fonts at specific variation
|
|
605
|
+
coordinates and output in any supported format (TTF, OTF, WOFF).
|
|
487
606
|
|
|
488
|
-
|
|
607
|
+
=== Command-line usage
|
|
489
608
|
|
|
490
|
-
|
|
609
|
+
Syntax:
|
|
491
610
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
Typical space savings: 30-50% reduction in CFF table size for fonts with similar glyph shapes.
|
|
498
|
-
|
|
499
|
-
NOTE: Current implementation calculates accurate optimization metrics but does not modify the output CFF table. Full CFF serialization with subroutines will be available in the next development phase.
|
|
500
|
-
|
|
501
|
-
==== Subroutine Optimization Improvements (v2.0.0-rc1)
|
|
502
|
-
|
|
503
|
-
===== Bug Fixes
|
|
504
|
-
|
|
505
|
-
Three critical bugs were fixed in v2.0.0-rc1 to improve CharString parsing and round-trip validation:
|
|
506
|
-
|
|
507
|
-
. **CFF Bias Calculation**: Fixed incorrect bias values that caused wrong subroutine calls
|
|
508
|
-
* Changed from `bias=0` to `bias=107` for <1240 subroutines (per CFF specification)
|
|
509
|
-
* Changed from `bias=107` to `bias=1131` for 1240-33899 subroutines
|
|
510
|
-
* Encoder and decoder now use matching bias values
|
|
511
|
-
* Eliminates "nil can't be coerced into Float" errors
|
|
512
|
-
|
|
513
|
-
. **Operator Boundaries**: Patterns now respect CharString structure to prevent malformed sequences
|
|
514
|
-
* Added [`find_operator_boundaries`](lib/fontisan/optimizers/pattern_analyzer.rb) method
|
|
515
|
-
* Added [`skip_number`](lib/fontisan/optimizers/pattern_analyzer.rb) helper for multi-byte parsing
|
|
516
|
-
* Prevents splitting multi-byte number encodings (1-5 bytes)
|
|
517
|
-
* Prevents separating operators from their operands
|
|
518
|
-
|
|
519
|
-
. **Overlap Prevention**: Multiple patterns at same positions no longer cause byte corruption
|
|
520
|
-
* Added [`remove_overlaps`](lib/fontisan/optimizers/charstring_rewriter.rb) method
|
|
521
|
-
* Keeps patterns with higher savings when overlaps detected
|
|
522
|
-
* Ensures data integrity during CharString rewriting
|
|
523
|
-
|
|
524
|
-
These fixes significantly reduce parsing errors after optimization (from 91 failures to ~140 warnings in integration tests).
|
|
525
|
-
|
|
526
|
-
===== Edge Cases
|
|
527
|
-
|
|
528
|
-
The optimizer now correctly handles:
|
|
529
|
-
|
|
530
|
-
* **Multi-byte numbers**: Number encodings from 1-5 bytes (CFF Type 2 format)
|
|
531
|
-
* **Two-byte operators**: Operators with 0x0c prefix (e.g., [`div`](lib/fontisan/tables/cff/charstring.rb), [`flex`](lib/fontisan/tables/cff/charstring.rb))
|
|
532
|
-
* **Overlapping patterns**: Multiple patterns at same byte positions
|
|
533
|
-
* **Stack-neutral validation**: Patterns verified to maintain consistent stack state
|
|
534
|
-
|
|
535
|
-
===== Troubleshooting
|
|
611
|
+
[source,shell]
|
|
612
|
+
----
|
|
613
|
+
$ fontisan instance VARIABLE_FONT [OPTIONS]
|
|
614
|
+
----
|
|
536
615
|
|
|
537
|
-
|
|
616
|
+
Where,
|
|
538
617
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
. **Ensure no overlaps**: Multiple patterns should not occupy same byte positions
|
|
542
|
-
. **Enable verbose mode**: Use `--verbose` flag for detailed diagnostics
|
|
618
|
+
`VARIABLE_FONT`:: Path to the variable font file
|
|
619
|
+
`OPTIONS`:: Instance generation options
|
|
543
620
|
|
|
544
|
-
|
|
621
|
+
Options:
|
|
545
622
|
|
|
546
|
-
|
|
623
|
+
`--wght VALUE`:: Weight axis value
|
|
624
|
+
`--wdth VALUE`:: Width axis value
|
|
625
|
+
`--slnt VALUE`:: Slant axis value
|
|
626
|
+
`--ital VALUE`:: Italic axis value
|
|
627
|
+
`--opsz VALUE`:: Optical size axis value
|
|
628
|
+
`--to FORMAT`:: Output format: `ttf` (default), `otf`, `woff`, or `woff2`
|
|
629
|
+
`--output FILE`:: Output file path
|
|
630
|
+
`--optimize`:: Enable CFF optimization for OTF output
|
|
631
|
+
`--named-instance INDEX`:: Use named instance by index
|
|
632
|
+
`--list-instances`:: List available named instances
|
|
633
|
+
`--validate`:: Validate font before generation
|
|
634
|
+
`--dry-run`:: Preview instance without generating
|
|
635
|
+
`--progress`:: Show progress during generation
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
.Generate bold instance at wght=700
|
|
639
|
+
[example]
|
|
640
|
+
====
|
|
641
|
+
[source,shell]
|
|
547
642
|
----
|
|
548
|
-
|
|
549
|
-
$ fontisan convert input.ttf --to otf --output output.otf --optimize --verbose
|
|
550
|
-
|
|
551
|
-
# Validate the output
|
|
552
|
-
$ fontisan validate output.otf
|
|
643
|
+
$ fontisan instance variable.ttf --wght 700 --output bold.ttf
|
|
553
644
|
|
|
554
|
-
|
|
555
|
-
|
|
645
|
+
Generating instance... done
|
|
646
|
+
Writing output... done
|
|
647
|
+
Static font instance written to: bold.ttf
|
|
556
648
|
----
|
|
649
|
+
====
|
|
557
650
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
651
|
+
.Generate instance and convert to OTF
|
|
652
|
+
[example]
|
|
653
|
+
====
|
|
654
|
+
[source,shell]
|
|
561
655
|
----
|
|
562
|
-
|
|
563
|
-
$ fontisan convert input.ttf --to otf --output output.otf
|
|
656
|
+
$ fontisan instance variable.ttf --wght 300 --to otf --output light.otf
|
|
564
657
|
|
|
565
|
-
|
|
566
|
-
|
|
658
|
+
Generating instance... done
|
|
659
|
+
Writing output... done
|
|
660
|
+
Static font instance written to: light.otf
|
|
567
661
|
----
|
|
662
|
+
====
|
|
568
663
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
.Convert with subroutine optimization
|
|
664
|
+
.Generate instance and convert to WOFF
|
|
572
665
|
[example]
|
|
573
666
|
====
|
|
574
|
-
[source,
|
|
667
|
+
[source,shell]
|
|
575
668
|
----
|
|
576
|
-
|
|
577
|
-
$ fontisan convert input.ttf --to otf --output output.otf --optimize --verbose
|
|
578
|
-
|
|
579
|
-
Converting input.ttf to otf...
|
|
669
|
+
$ fontisan instance variable.ttf --wght 600 --to woff --output semibold.woff
|
|
580
670
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
Subroutines generated: 89
|
|
585
|
-
Estimated bytes saved: 45,234
|
|
586
|
-
CFF bias: 107
|
|
587
|
-
|
|
588
|
-
Conversion complete!
|
|
589
|
-
Input: input.ttf (806.3 KB)
|
|
590
|
-
Output: output.otf (979.5 KB)
|
|
671
|
+
Generating instance... done
|
|
672
|
+
Writing output... done
|
|
673
|
+
Static font instance written to: semibold.woff
|
|
591
674
|
----
|
|
592
675
|
====
|
|
593
676
|
|
|
594
|
-
.
|
|
677
|
+
.Generate instance with multiple axes
|
|
595
678
|
[example]
|
|
596
679
|
====
|
|
597
|
-
[source,
|
|
680
|
+
[source,shell]
|
|
598
681
|
----
|
|
599
|
-
|
|
600
|
-
$ fontisan convert input.ttf --to otf --output output.otf \
|
|
601
|
-
--optimize \
|
|
602
|
-
--min-pattern-length 15 \
|
|
603
|
-
--max-subroutines 10000 \
|
|
604
|
-
--verbose
|
|
682
|
+
$ fontisan instance variable.ttf --wght 600 --wdth 75 --output condensed.ttf
|
|
605
683
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
--no-optimize-ordering
|
|
684
|
+
Generating instance... done
|
|
685
|
+
Writing output... done
|
|
686
|
+
Static font instance written to: condensed.ttf
|
|
610
687
|
----
|
|
611
688
|
====
|
|
612
689
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
`--verbose`:: Show detailed optimization statistics
|
|
620
|
-
|
|
621
|
-
==== Stack-Aware Optimization
|
|
622
|
-
|
|
623
|
-
===== General
|
|
624
|
-
|
|
625
|
-
Stack-aware optimization is an advanced mode that ensures all extracted patterns are stack-neutral, guaranteeing 100% safety and reliability. Unlike normal byte-level pattern matching, stack-aware mode simulates CharString execution to track operand stack depth, only extracting patterns that maintain consistent stack state.
|
|
690
|
+
.List available named instances
|
|
691
|
+
[example]
|
|
692
|
+
====
|
|
693
|
+
[source,shell]
|
|
694
|
+
----
|
|
695
|
+
$ fontisan instance variable.ttf --list-instances
|
|
626
696
|
|
|
627
|
-
|
|
697
|
+
Available named instances:
|
|
628
698
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
699
|
+
[0] Instance 4
|
|
700
|
+
Coordinates:
|
|
701
|
+
wdth: 75.0
|
|
702
|
+
wght: 200.0
|
|
633
703
|
|
|
634
|
-
|
|
704
|
+
[1] Instance 5
|
|
705
|
+
Coordinates:
|
|
706
|
+
wdth: 75.0
|
|
707
|
+
wght: 250.0
|
|
635
708
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
709
|
+
[2] Instance 6
|
|
710
|
+
Coordinates:
|
|
711
|
+
wdth: 75.0
|
|
712
|
+
wght: 300.0
|
|
713
|
+
----
|
|
714
|
+
====
|
|
639
715
|
|
|
640
|
-
|
|
716
|
+
.Use named instance
|
|
717
|
+
[example]
|
|
718
|
+
====
|
|
719
|
+
[source,shell]
|
|
720
|
+
----
|
|
721
|
+
$ fontisan instance variable.ttf --named-instance 0 --output thin.ttf
|
|
722
|
+
----
|
|
723
|
+
====
|
|
641
724
|
|
|
642
|
-
.
|
|
725
|
+
.Preview instance generation (dry-run)
|
|
643
726
|
[example]
|
|
644
727
|
====
|
|
645
|
-
[source,
|
|
728
|
+
[source,shell]
|
|
646
729
|
----
|
|
647
|
-
|
|
648
|
-
$ fontisan convert input.ttf --to otf --output output.otf \
|
|
649
|
-
--optimize \
|
|
650
|
-
--stack-aware \
|
|
651
|
-
--verbose
|
|
730
|
+
$ fontisan instance variable.ttf --wght 700 --dry-run
|
|
652
731
|
|
|
653
|
-
|
|
732
|
+
Dry-run mode: Preview of instance generation
|
|
654
733
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
Selecting optimal patterns...
|
|
658
|
-
Selected 832 patterns for subroutinization
|
|
659
|
-
Building subroutines...
|
|
660
|
-
Generated 832 subroutines
|
|
661
|
-
Rewriting CharStrings with subroutine calls...
|
|
662
|
-
Rewrote 4515 CharStrings
|
|
734
|
+
Coordinates:
|
|
735
|
+
wght: 700.0
|
|
663
736
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
Patterns selected: 832
|
|
667
|
-
Subroutines generated: 832
|
|
668
|
-
Estimated bytes saved: 46,280
|
|
669
|
-
CFF bias: 0
|
|
737
|
+
Output would be written to: variable-instance.ttf
|
|
738
|
+
Output
|
|
670
739
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
740
|
+
format: same as input
|
|
741
|
+
|
|
742
|
+
Use without --dry-run to actually generate the instance.
|
|
674
743
|
----
|
|
675
744
|
====
|
|
676
745
|
|
|
677
|
-
.SQLite stack-aware vs normal mode
|
|
678
|
-
[example]
|
|
679
|
-
====
|
|
680
|
-
[source,bash]
|
|
681
|
-
----
|
|
682
|
-
# Use the comparison script
|
|
683
|
-
$ ruby scripts/compare_stack_aware.rb input.ttf
|
|
684
746
|
|
|
685
|
-
|
|
686
|
-
Normal: 81.49 KB (11.27%)
|
|
687
|
-
Stack-Aware: 43.17 KB (6.13%)
|
|
747
|
+
== Optical size information
|
|
688
748
|
|
|
689
|
-
|
|
690
|
-
Normal: 18.38 s
|
|
691
|
-
Stack-Aware: 1.54 s (12x faster)
|
|
749
|
+
=== General
|
|
692
750
|
|
|
693
|
-
|
|
751
|
+
Display optical size range from the OS/2 table for fonts designed for specific
|
|
752
|
+
point sizes.
|
|
753
|
+
|
|
754
|
+
=== Command-line usage
|
|
755
|
+
|
|
756
|
+
Syntax:
|
|
757
|
+
|
|
758
|
+
[source,shell]
|
|
759
|
+
----
|
|
760
|
+
$ fontisan optical-size FONT_FILE [--format FORMAT]
|
|
694
761
|
----
|
|
695
|
-
====
|
|
696
762
|
|
|
697
763
|
Where,
|
|
698
764
|
|
|
699
|
-
|
|
765
|
+
`FONT_FILE`:: Path to the font file with optical sizing
|
|
766
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
700
767
|
|
|
701
|
-
===== Using the Ruby API
|
|
702
768
|
|
|
703
|
-
.
|
|
769
|
+
.Optical size information in Libertinus Serif Display
|
|
704
770
|
[example]
|
|
705
771
|
====
|
|
706
|
-
[source,
|
|
772
|
+
[source,shell]
|
|
773
|
+
----
|
|
774
|
+
$ fontisan optical-size spec/fixtures/fonts/libertinus/ttf/LibertinusSerifDisplay-Regular.ttf
|
|
707
775
|
----
|
|
708
|
-
require 'fontisan'
|
|
709
776
|
|
|
710
|
-
|
|
711
|
-
|
|
777
|
+
[source,text]
|
|
778
|
+
----
|
|
779
|
+
Size range: [18, 72) pt (source: OS/2_usLowerOpticalPointSize)
|
|
780
|
+
----
|
|
781
|
+
====
|
|
712
782
|
|
|
713
|
-
# Convert with stack-aware optimization
|
|
714
|
-
converter = Fontisan::Converters::OutlineConverter.new
|
|
715
|
-
tables = converter.convert(font, {
|
|
716
|
-
target_format: :otf,
|
|
717
|
-
optimize_subroutines: true,
|
|
718
|
-
stack_aware: true # Enable safe mode
|
|
719
|
-
})
|
|
720
783
|
|
|
721
|
-
|
|
722
|
-
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
723
|
-
puts "Patterns found: #{optimization[:pattern_count]}"
|
|
724
|
-
puts "Stack-neutral patterns: #{optimization[:selected_count]}"
|
|
725
|
-
puts "Processing time: #{optimization[:processing_time]}s"
|
|
784
|
+
== List supported scripts
|
|
726
785
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
sfnt_version: 0x4F54544F
|
|
732
|
-
)
|
|
733
|
-
----
|
|
734
|
-
====
|
|
786
|
+
=== General
|
|
787
|
+
|
|
788
|
+
Show all scripts (writing systems) supported by the font, extracted from GSUB
|
|
789
|
+
and GPOS tables. Useful for understanding language coverage.
|
|
735
790
|
|
|
736
|
-
|
|
791
|
+
=== Command-line usage
|
|
737
792
|
|
|
738
|
-
|
|
793
|
+
Syntax:
|
|
739
794
|
|
|
740
|
-
[source]
|
|
795
|
+
[source,shell]
|
|
741
796
|
----
|
|
742
|
-
|
|
743
|
-
(Input) (Simulate) (Filter) (Output)
|
|
797
|
+
$ fontisan scripts FONT_FILE [--format FORMAT]
|
|
744
798
|
----
|
|
745
799
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
. Simulates CharString execution without full interpretation
|
|
749
|
-
. Records stack depth at each byte position
|
|
750
|
-
. Handles 40+ Type 2 CharString operators with correct stack effects
|
|
751
|
-
|
|
752
|
-
**Pattern Validation**:
|
|
800
|
+
Where,
|
|
753
801
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
. Verifies consistent results regardless of initial stack state
|
|
802
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
803
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
757
804
|
|
|
758
|
-
**Stack-Neutral Pattern** criteria:
|
|
759
805
|
|
|
760
|
-
|
|
806
|
+
.Supported scripts in Libertinus Serif Regular
|
|
807
|
+
[example]
|
|
808
|
+
====
|
|
809
|
+
[source,shell]
|
|
761
810
|
----
|
|
762
|
-
|
|
763
|
-
1. depth_at(pattern_start) == depth_at(pattern_end)
|
|
764
|
-
2. No negative depth during pattern execution
|
|
765
|
-
3. Pattern produces same result for any valid initial stack
|
|
811
|
+
$ fontisan scripts spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
766
812
|
----
|
|
767
813
|
|
|
768
|
-
|
|
769
|
-
[source]
|
|
770
|
-
----
|
|
771
|
-
10 20 rmoveto # Pushes 2 operands, consumes 2 → neutral
|
|
814
|
+
[source,text]
|
|
772
815
|
----
|
|
816
|
+
Script count: 5
|
|
773
817
|
|
|
774
|
-
|
|
775
|
-
|
|
818
|
+
DFLT Default
|
|
819
|
+
cyrl Cyrillic
|
|
820
|
+
grek Greek
|
|
821
|
+
hebr Hebrew
|
|
822
|
+
latn Latin
|
|
776
823
|
----
|
|
777
|
-
|
|
824
|
+
====
|
|
778
825
|
|
|
779
|
-
, produces 1 → NOT neutral
|
|
780
|
-
----
|
|
781
826
|
|
|
782
|
-
|
|
827
|
+
== List OpenType features
|
|
783
828
|
|
|
784
|
-
|
|
829
|
+
=== General
|
|
785
830
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
* Web fonts where correctness matters more than minimal size
|
|
789
|
-
* Situations where testing/validation is limited
|
|
831
|
+
Show OpenType layout features (typography features like ligatures, kerning,
|
|
832
|
+
small capitals) available for specific scripts or all scripts.
|
|
790
833
|
|
|
791
|
-
|
|
834
|
+
=== Command-line usage
|
|
792
835
|
|
|
793
|
-
|
|
794
|
-
* When full validation will be performed post-conversion
|
|
795
|
-
* Maximum compression is priority over guaranteed safety
|
|
836
|
+
Syntax:
|
|
796
837
|
|
|
797
|
-
|
|
838
|
+
[source,shell]
|
|
839
|
+
----
|
|
840
|
+
$ fontisan features FONT_FILE [--script SCRIPT] [--format FORMAT]
|
|
841
|
+
----
|
|
798
842
|
|
|
799
|
-
|
|
843
|
+
Where,
|
|
844
|
+
|
|
845
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
846
|
+
`SCRIPT`:: Optional 4-character script tag (e.g., `latn`, `cyrl`, `arab`). If not specified, shows features for all scripts
|
|
847
|
+
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
.OpenType features for Latin script
|
|
800
851
|
[example]
|
|
801
852
|
====
|
|
802
|
-
[source,
|
|
853
|
+
[source,shell]
|
|
854
|
+
----
|
|
855
|
+
$ fontisan features spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --script latn
|
|
803
856
|
----
|
|
804
|
-
require 'fontisan'
|
|
805
857
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
converter = Fontisan::Converters::OutlineConverter.new
|
|
811
|
-
tables = converter.convert(font, {
|
|
812
|
-
target_format: :otf,
|
|
813
|
-
optimize_subroutines: true
|
|
814
|
-
})
|
|
815
|
-
|
|
816
|
-
# Access optimization results
|
|
817
|
-
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
818
|
-
puts "Patterns found: #{optimization[:pattern_count]}"
|
|
819
|
-
puts "Selected: #{optimization[:selected_count]}"
|
|
820
|
-
puts "Savings: #{optimization[:savings]} bytes"
|
|
858
|
+
[source,text]
|
|
859
|
+
----
|
|
860
|
+
Script: latn
|
|
861
|
+
Feature count: 4
|
|
821
862
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
sfnt_version: 0x4F54544F
|
|
827
|
-
)
|
|
863
|
+
cpsp Capital Spacing
|
|
864
|
+
kern Kerning
|
|
865
|
+
mark Mark Positioning
|
|
866
|
+
mkmk Mark to Mark Positioning
|
|
828
867
|
----
|
|
829
868
|
====
|
|
830
869
|
|
|
831
|
-
|
|
870
|
+
|
|
871
|
+
.OpenType features for all scripts
|
|
832
872
|
[example]
|
|
833
873
|
====
|
|
834
|
-
[source,
|
|
874
|
+
[source,shell]
|
|
835
875
|
----
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
font = Fontisan::FontLoader.load('input.ttf')
|
|
839
|
-
converter = Fontisan::Converters::OutlineConverter.new
|
|
840
|
-
|
|
841
|
-
# Fine-tune optimization
|
|
842
|
-
tables = converter.convert(font, {
|
|
843
|
-
target_format: :otf,
|
|
844
|
-
optimize_subroutines: true,
|
|
845
|
-
min_pattern_length: 15,
|
|
846
|
-
max_subroutines: 5000,
|
|
847
|
-
optimize_ordering: true,
|
|
848
|
-
verbose: true
|
|
849
|
-
})
|
|
850
|
-
|
|
851
|
-
# Analyze results
|
|
852
|
-
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
853
|
-
if optimization[:selected_count] > 0
|
|
854
|
-
efficiency = optimization[:savings].to_f / optimization[:selected_count]
|
|
855
|
-
puts "Average savings per subroutine: #{efficiency.round(2)} bytes"
|
|
856
|
-
end
|
|
876
|
+
$ fontisan features spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
857
877
|
----
|
|
858
|
-
====
|
|
859
878
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
The subroutine optimizer uses a four-stage pipeline:
|
|
863
|
-
|
|
864
|
-
[source]
|
|
865
|
-
----
|
|
866
|
-
CharStrings → Pattern Analysis → Selection → Ordering → Metadata
|
|
867
|
-
(Input) (Find repeats) (Optimize) (Frequency) (Output)
|
|
879
|
+
[source,text]
|
|
868
880
|
----
|
|
881
|
+
Script: DFLT
|
|
882
|
+
Feature count: 4
|
|
869
883
|
|
|
870
|
-
|
|
884
|
+
cpsp Capital Spacing
|
|
885
|
+
kern Kerning
|
|
886
|
+
mark Mark Positioning
|
|
887
|
+
mkmk Mark to Mark Positioning
|
|
871
888
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
889
|
+
Script: cyrl
|
|
890
|
+
Feature count: 4
|
|
891
|
+
|
|
892
|
+
cpsp Capital Spacing
|
|
893
|
+
kern Kerning
|
|
894
|
+
mark Mark Positioning
|
|
895
|
+
mkmk Mark to Mark Positioning
|
|
876
896
|
|
|
877
|
-
|
|
897
|
+
Script: grek
|
|
898
|
+
Feature count: 4
|
|
878
899
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
900
|
+
cpsp Capital Spacing
|
|
901
|
+
kern Kerning
|
|
902
|
+
mark Mark Positioning
|
|
903
|
+
mkmk Mark to Mark Positioning
|
|
883
904
|
|
|
884
|
-
|
|
905
|
+
Script: hebr
|
|
906
|
+
Feature count: 2
|
|
885
907
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
. Ensures subroutine indices fit within CFF constraints
|
|
908
|
+
mark Mark Positioning
|
|
909
|
+
mkmk Mark to Mark Positioning
|
|
889
910
|
|
|
890
|
-
|
|
911
|
+
Script: latn
|
|
912
|
+
Feature count: 4
|
|
891
913
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
0-1239 107
|
|
897
|
-
1240-33899 1131
|
|
898
|
-
33900-65535 32768
|
|
914
|
+
cpsp Capital Spacing
|
|
915
|
+
kern Kerning
|
|
916
|
+
mark Mark Positioning
|
|
917
|
+
mkmk Mark to Mark Positioning
|
|
899
918
|
----
|
|
919
|
+
====
|
|
900
920
|
|
|
901
|
-
The bias value determines how subroutine indices are encoded in CharStrings, affecting the final size.
|
|
902
|
-
|
|
903
|
-
=== Round-Trip Validation
|
|
904
921
|
|
|
905
|
-
|
|
922
|
+
== Dump raw table data
|
|
906
923
|
|
|
907
|
-
|
|
924
|
+
=== General
|
|
908
925
|
|
|
909
|
-
|
|
926
|
+
Extract raw binary data from a specific OpenType table. Useful for detailed
|
|
927
|
+
analysis or debugging font issues.
|
|
910
928
|
|
|
911
|
-
|
|
912
|
-
* **Coordinate Tolerance**: Accepts ±2 pixels tolerance for rounding during conversion
|
|
913
|
-
* **Format-Aware Comparison**: Handles differences between TrueType quadratic and CFF cubic curves
|
|
914
|
-
* **Closepath Handling**: Smart detection of geometrically closed vs open contours
|
|
915
|
-
* **100% Coverage**: All 4,515 glyphs validated in test fonts
|
|
929
|
+
=== Command-line usage
|
|
916
930
|
|
|
917
|
-
|
|
931
|
+
TODO: should support output to file directly with `--output FILE`.
|
|
918
932
|
|
|
919
|
-
|
|
933
|
+
Syntax:
|
|
920
934
|
|
|
921
|
-
[source]
|
|
935
|
+
[source,shell]
|
|
922
936
|
----
|
|
923
|
-
|
|
924
|
-
(Input) (Encode) (Decode) (Validate)
|
|
937
|
+
$ fontisan dump-table FONT_FILE TABLE_TAG
|
|
925
938
|
----
|
|
926
939
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
. Extract glyph outlines from original TTF
|
|
930
|
-
. Convert to CFF format with CharString encoding
|
|
931
|
-
. Parse CFF CharStrings back to universal outlines
|
|
932
|
-
. Compare geometry with coordinate tolerance (±2 pixels)
|
|
933
|
-
|
|
934
|
-
**Format Differences Handled**:
|
|
940
|
+
Where,
|
|
935
941
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
* **Coordinate Rounding**: Different number encoding causes minor differences
|
|
942
|
+
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
943
|
+
`TABLE_TAG`:: Four-character table tag (e.g., `name`, `head`, `GSUB`, `GPOS`)
|
|
939
944
|
|
|
940
|
-
**Validation Criteria**:
|
|
941
945
|
|
|
942
|
-
|
|
946
|
+
.Dump raw table data to files
|
|
947
|
+
[example]
|
|
948
|
+
====
|
|
949
|
+
[source,shell]
|
|
943
950
|
----
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
3. Same endpoint coordinates for curves (±2 pixels)
|
|
948
|
-
4. Quadratic→cubic conversion accepted
|
|
951
|
+
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf name > name_table.bin
|
|
952
|
+
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf GPOS > gpos_table.bin
|
|
953
|
+
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf head > head_table.bin
|
|
949
954
|
----
|
|
950
955
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
* **Without Optimization**: All glyphs convert correctly ✅
|
|
956
|
-
* **With Optimization**: Pending fix for subroutine bug (91 glyphs affected)
|
|
957
|
-
|
|
958
|
-
**Status**:
|
|
959
|
-
```
|
|
960
|
-
Round-trip validation: 100% passing (without optimization)
|
|
961
|
-
Test suite: 2870/2870 passing, 15 pending (future features)
|
|
962
|
-
```
|
|
963
|
-
|
|
964
|
-
==== Known Issues
|
|
965
|
-
|
|
966
|
-
**Integration Tests** (low priority):
|
|
967
|
-
One bbox mismatch failure remains in integration tests. This is not a CharString parsing error but may be a rounding issue during conversion. The issue is documented and does not affect the core functionality. Round-trip validation for CharString parsing is now fully functional after v2.0.0-rc1 bug fixes.
|
|
968
|
-
|
|
969
|
-
=== Current Limitations
|
|
970
|
-
|
|
971
|
-
==== Features not yet implemented
|
|
972
|
-
|
|
973
|
-
* **CFF Table Serialization**: While subroutine optimization calculates accurate space savings, the serialization of optimized CFF tables with subroutines is pending. Current output CFF tables function correctly but do not include generated subroutines.
|
|
974
|
-
* **Hints**: TrueType instructions and CFF hints are not preserved during conversion
|
|
975
|
-
* **CFF2**: Variable fonts using CFF2 tables are not supported
|
|
976
|
-
|
|
977
|
-
==== Optimization Preview Mode
|
|
978
|
-
|
|
979
|
-
The subroutine optimizer is currently in preview mode:
|
|
980
|
-
|
|
981
|
-
* Pattern analysis and subroutine generation work correctly
|
|
982
|
-
* Accurate space savings calculations are provided
|
|
983
|
-
* Optimization results are stored in metadata
|
|
984
|
-
* CFF table serialization with subroutines will be added in the next phase
|
|
985
|
-
|
|
986
|
-
=== Planned Features
|
|
987
|
-
|
|
988
|
-
==== Phase 2 (Current development phase)
|
|
989
|
-
|
|
990
|
-
* CFF table serialization with subroutine support (in progress)
|
|
991
|
-
* Hint preservation and conversion
|
|
992
|
-
* CFF2 support for variable fonts
|
|
993
|
-
* Round-trip conversion validation
|
|
994
|
-
* Batch conversion support
|
|
956
|
+
The output is binary data written directly to stdout, which can be redirected to
|
|
957
|
+
a file for further analysis.
|
|
958
|
+
====
|
|
995
959
|
|
|
996
960
|
|
|
997
|
-
==
|
|
961
|
+
== Export font structure
|
|
998
962
|
|
|
999
|
-
===
|
|
963
|
+
=== General
|
|
1000
964
|
|
|
1001
|
-
|
|
965
|
+
Export font structure to TTX (FontTools XML), YAML, or JSON formats for
|
|
966
|
+
analysis, interchange, or version control. Supports selective table export and
|
|
967
|
+
configurable binary data encoding.
|
|
1002
968
|
|
|
1003
|
-
|
|
1004
|
-
version information, designer credits, vendor details, licensing information,
|
|
1005
|
-
and font metrics.
|
|
969
|
+
=== Command-line usage
|
|
1006
970
|
|
|
1007
971
|
Syntax:
|
|
1008
972
|
|
|
1009
973
|
[source,shell]
|
|
1010
974
|
----
|
|
1011
|
-
$ fontisan
|
|
975
|
+
$ fontisan export FONT_FILE [--output FILE] [--format FORMAT] [--tables TABLES] [--binary-format FORMAT]
|
|
1012
976
|
----
|
|
1013
977
|
|
|
1014
978
|
Where,
|
|
1015
979
|
|
|
1016
980
|
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
1017
|
-
|
|
981
|
+
`--output FILE`:: Output file path (default: stdout)
|
|
982
|
+
`--format FORMAT`:: Export format: `yaml` (default), `json`, or `ttx`
|
|
983
|
+
`--tables TABLES`:: Specific tables to export (space-separated list)
|
|
984
|
+
`--binary-format FORMAT`:: Binary encoding: `hex` (default) or `base64`
|
|
1018
985
|
|
|
1019
986
|
|
|
1020
|
-
.
|
|
987
|
+
.Export font to YAML format
|
|
1021
988
|
[example]
|
|
1022
989
|
====
|
|
1023
990
|
[source,shell]
|
|
1024
991
|
----
|
|
1025
|
-
$ fontisan
|
|
992
|
+
$ fontisan export spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --output font.yaml
|
|
1026
993
|
|
|
1027
|
-
|
|
1028
|
-
Family: Libertinus Serif
|
|
1029
|
-
Subfamily: Regular
|
|
1030
|
-
Full name: Libertinus Serif Regular
|
|
1031
|
-
PostScript name: LibertinusSerif-Regular
|
|
1032
|
-
Version: Version 7.051;RELEASE
|
|
1033
|
-
Unique ID: 5.000;QUE ;LibertinusSerif-Regular
|
|
1034
|
-
Designer: Philipp H. Poll, Khaled Hosny
|
|
1035
|
-
Manufacturer: Caleb Maclennan
|
|
1036
|
-
Vendor URL: https://github.com/alerque/libertinus
|
|
1037
|
-
Vendor ID: QUE
|
|
1038
|
-
License Description: This Font Software is licensed under the SIL Open Font
|
|
1039
|
-
License, Version 1.1. This license is available with a
|
|
1040
|
-
FAQ at: https://openfontlicense.org
|
|
1041
|
-
License URL: https://openfontlicense.org
|
|
1042
|
-
Font revision: 7.05099
|
|
1043
|
-
Permissions: Installable
|
|
1044
|
-
Units per em: 1000
|
|
994
|
+
# Output: font.yaml with complete font structure in YAML
|
|
1045
995
|
----
|
|
1046
996
|
====
|
|
1047
997
|
|
|
1048
|
-
|
|
1049
|
-
.Output in structured YAML format
|
|
998
|
+
.Export specific tables to TTX format
|
|
1050
999
|
[example]
|
|
1051
1000
|
====
|
|
1052
1001
|
[source,shell]
|
|
1053
1002
|
----
|
|
1054
|
-
$ fontisan
|
|
1003
|
+
$ fontisan export spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf \
|
|
1004
|
+
--format ttx --tables head hhea maxp name --output font.ttx
|
|
1055
1005
|
----
|
|
1056
1006
|
|
|
1057
|
-
|
|
1007
|
+
Exports only the specified tables in FontTools TTX XML format for compatibility
|
|
1008
|
+
with fonttools.
|
|
1009
|
+
====
|
|
1010
|
+
|
|
1011
|
+
.Export to JSON with base64 binary encoding
|
|
1012
|
+
[example]
|
|
1013
|
+
====
|
|
1014
|
+
[source,shell]
|
|
1058
1015
|
----
|
|
1059
|
-
|
|
1060
|
-
is_variable: false
|
|
1061
|
-
family_name: Libertinus Serif
|
|
1062
|
-
subfamily_name: Regular
|
|
1063
|
-
full_name: Libertinus Serif Regular
|
|
1064
|
-
postscript_name: LibertinusSerif-Regular
|
|
1065
|
-
version: Version 7.051;RELEASE
|
|
1066
|
-
unique_id: 5.000;QUE ;LibertinusSerif-Regular
|
|
1067
|
-
designer: Philipp H. Poll, Khaled Hosny
|
|
1068
|
-
manufacturer: Caleb Maclennan
|
|
1069
|
-
vendor_url: https://github.com/alerque/libertinus
|
|
1070
|
-
vendor_id: QUE
|
|
1071
|
-
license_description: 'This Font Software is licensed under the SIL Open Font License,
|
|
1072
|
-
Version 1.1. This license is available with a FAQ at: https://openfontlicense.org'
|
|
1073
|
-
license_url: https://openfontlicense.org
|
|
1074
|
-
font_revision: 7.050994873046875
|
|
1075
|
-
permissions: Installable
|
|
1076
|
-
units_per_em: 1000
|
|
1016
|
+
$ fontisan export font.ttf --format json --binary-format base64 --output font.json
|
|
1077
1017
|
----
|
|
1018
|
+
|
|
1019
|
+
Uses base64 encoding for binary data instead of hexadecimal, useful for
|
|
1020
|
+
JSON-based workflows.
|
|
1078
1021
|
====
|
|
1079
1022
|
|
|
1080
1023
|
|
|
1081
|
-
|
|
1024
|
+
== Version information
|
|
1082
1025
|
|
|
1083
|
-
|
|
1084
|
-
sizes, offsets, and checksums. Useful for understanding font structure and
|
|
1085
|
-
verifying table integrity.
|
|
1026
|
+
=== General
|
|
1086
1027
|
|
|
1087
|
-
|
|
1028
|
+
Display the Fontisan version.
|
|
1029
|
+
|
|
1030
|
+
=== Command-line usage
|
|
1088
1031
|
|
|
1089
1032
|
[source,shell]
|
|
1090
1033
|
----
|
|
1091
|
-
|
|
1034
|
+
fontisan version
|
|
1092
1035
|
----
|
|
1093
1036
|
|
|
1094
|
-
Where,
|
|
1095
1037
|
|
|
1096
|
-
|
|
1097
|
-
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
1038
|
+
== Font collections
|
|
1098
1039
|
|
|
1099
|
-
|
|
1100
|
-
[example]
|
|
1101
|
-
====
|
|
1102
|
-
[source,shell]
|
|
1103
|
-
----
|
|
1104
|
-
$ fontisan tables spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf
|
|
1105
|
-
----
|
|
1040
|
+
=== General
|
|
1106
1041
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
Number of tables: 16
|
|
1042
|
+
Fontisan provides comprehensive tools for managing TrueType Collections (TTC)
|
|
1043
|
+
and OpenType Collections (OTC). You can list fonts in a collection, extract
|
|
1044
|
+
individual fonts, unpack entire collections, and validate collection integrity.
|
|
1111
1045
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
maxp 32 bytes (offset: 360, checksum: 0x0EF919E7)
|
|
1126
|
-
name 894 bytes (offset: 514944, checksum: 0x4E9173E6)
|
|
1127
|
-
post 26308 bytes (offset: 515840, checksum: 0xE3D70231)
|
|
1128
|
-
prep 239 bytes (offset: 18628, checksum: 0x8B4AB356)
|
|
1046
|
+
|
|
1047
|
+
=== List fonts
|
|
1048
|
+
|
|
1049
|
+
==== General
|
|
1050
|
+
|
|
1051
|
+
List all fonts in a TrueType Collection (TTC) or OpenType Collection (OTC), with
|
|
1052
|
+
their index, family name, and style.
|
|
1053
|
+
|
|
1054
|
+
==== Command-line usage
|
|
1055
|
+
|
|
1056
|
+
[source,shell]
|
|
1057
|
+
----
|
|
1058
|
+
$ fontisan ls FONT.{ttc,otc}
|
|
1129
1059
|
----
|
|
1130
|
-
====
|
|
1131
1060
|
|
|
1132
|
-
|
|
1061
|
+
NOTE: In `extract_ttc`, this was done with `extract_ttc --list FONT.ttc`.
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
.List collection contents
|
|
1133
1065
|
[example]
|
|
1134
1066
|
====
|
|
1135
1067
|
[source,shell]
|
|
1136
1068
|
----
|
|
1137
|
-
|
|
1069
|
+
# List all fonts in a TTC with detailed info
|
|
1070
|
+
$ fontisan ls spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc
|
|
1071
|
+
|
|
1072
|
+
Font 0: Noto Serif CJK JP
|
|
1073
|
+
Family: Noto Serif CJK JP
|
|
1074
|
+
Subfamily: Regular
|
|
1075
|
+
PostScript: NotoSerifCJKJP-Regular
|
|
1076
|
+
|
|
1077
|
+
Font 1: Noto Serif CJK KR
|
|
1078
|
+
Family: Noto Serif CJK KR
|
|
1079
|
+
Subfamily: Regular
|
|
1080
|
+
PostScript: NotoSerifCJKKR-Regular
|
|
1081
|
+
|
|
1082
|
+
Font 2: Noto Serif CJK SC
|
|
1083
|
+
Family: Noto Serif CJK SC
|
|
1084
|
+
Subfamily: Regular
|
|
1085
|
+
PostScript: NotoSerifCJKSC-Regular
|
|
1086
|
+
|
|
1087
|
+
Font 3: Noto Serif CJK TC
|
|
1088
|
+
Family: Noto Serif CJK TC
|
|
1089
|
+
Subfamily: Regular
|
|
1090
|
+
PostScript: NotoSerifCJKTC-Regular
|
|
1138
1091
|
----
|
|
1092
|
+
====
|
|
1139
1093
|
|
|
1140
|
-
|
|
1094
|
+
|
|
1095
|
+
=== Show collection info
|
|
1096
|
+
|
|
1097
|
+
==== General
|
|
1098
|
+
|
|
1099
|
+
Show detailed information about a TrueType Collection (TTC) or OpenType Collection
|
|
1100
|
+
(OTC), including the number of fonts and metadata for each font.
|
|
1101
|
+
|
|
1102
|
+
==== Command-line usage
|
|
1103
|
+
|
|
1104
|
+
[source,shell]
|
|
1105
|
+
----
|
|
1106
|
+
$ fontisan info FONT.{ttc,otc}
|
|
1141
1107
|
----
|
|
1108
|
+
|
|
1109
|
+
NOTE: In `extract_ttc`, this was done with `extract_ttc --info FONT.ttc`.
|
|
1110
|
+
|
|
1111
|
+
.Get collection information
|
|
1112
|
+
[example]
|
|
1113
|
+
====
|
|
1114
|
+
[source,shell]
|
|
1115
|
+
----
|
|
1116
|
+
# Detailed collection analysis
|
|
1117
|
+
$ fontisan info spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc --format yaml
|
|
1118
|
+
|
|
1142
1119
|
---
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
offset: 15032
|
|
1169
|
-
checksum: 1647249281
|
|
1170
|
-
----
|
|
1171
|
-
====
|
|
1172
|
-
|
|
1173
|
-
==== List glyph names
|
|
1120
|
+
collection_type: ttc
|
|
1121
|
+
font_count: 4
|
|
1122
|
+
fonts:
|
|
1123
|
+
- index: 0
|
|
1124
|
+
family_name: Noto Serif CJK JP
|
|
1125
|
+
subfamily_name: Regular
|
|
1126
|
+
postscript_name: NotoSerifCJKJP-Regular
|
|
1127
|
+
font_format: opentype
|
|
1128
|
+
- index: 1
|
|
1129
|
+
family_name: Noto Serif CJK KR
|
|
1130
|
+
subfamily_name: Regular
|
|
1131
|
+
postscript_name: NotoSerifCJKKR-Regular
|
|
1132
|
+
font_format: opentype
|
|
1133
|
+
- index: 2
|
|
1134
|
+
family_name: Noto Serif CJK SC
|
|
1135
|
+
subfamily_name: Regular
|
|
1136
|
+
postscript_name: NotoSerifCJKSC-Regular
|
|
1137
|
+
font_format: opentype
|
|
1138
|
+
- index: 3
|
|
1139
|
+
family_name: Noto Serif CJK TC
|
|
1140
|
+
subfamily_name: Regular
|
|
1141
|
+
postscript_name: NotoSerifCJKTC-Regular
|
|
1142
|
+
font_format: opentype
|
|
1143
|
+
----
|
|
1144
|
+
====
|
|
1174
1145
|
|
|
1175
|
-
Show all glyph names defined in the font's post table. Each glyph is listed with
|
|
1176
|
-
its index and name, useful for understanding the font's character coverage.
|
|
1177
1146
|
|
|
1178
|
-
|
|
1147
|
+
=== Unpack fonts
|
|
1148
|
+
|
|
1149
|
+
==== General
|
|
1150
|
+
|
|
1151
|
+
Extract all fonts from a TrueType Collection (TTC) or OpenType Collection (OTC)
|
|
1152
|
+
to a specified output directory.
|
|
1153
|
+
|
|
1154
|
+
==== Command-line usage
|
|
1179
1155
|
|
|
1180
1156
|
[source,shell]
|
|
1181
1157
|
----
|
|
1182
|
-
$ fontisan
|
|
1158
|
+
$ fontisan unpack FONT.{ttc,otc} OUTPUT_DIR
|
|
1183
1159
|
----
|
|
1184
1160
|
|
|
1185
|
-
|
|
1161
|
+
NOTE: In `extract_ttc`, this was done with `extract_ttc --unpack FONT.ttc OUTPUT_DIR`.
|
|
1186
1162
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1163
|
+
.Extract fonts from collection
|
|
1164
|
+
[example]
|
|
1165
|
+
====
|
|
1166
|
+
[source,shell]
|
|
1167
|
+
----
|
|
1168
|
+
# Extract all fonts from collection
|
|
1169
|
+
$ fontisan unpack family.ttc --output-dir extracted/
|
|
1189
1170
|
|
|
1171
|
+
Collection unpacked successfully:
|
|
1172
|
+
Input: family.ttc
|
|
1173
|
+
Output directory: extracted/
|
|
1174
|
+
Fonts extracted: 3/3
|
|
1175
|
+
- font1.ttf (89.2 KB)
|
|
1176
|
+
- font2.ttf (89.2 KB)
|
|
1177
|
+
- font3.ttf (67.4 KB)
|
|
1190
1178
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1179
|
+
# Extract specific font with format conversion
|
|
1180
|
+
$ fontisan unpack family.ttc --output-dir extracted/ --font-index 0 --format woff2
|
|
1181
|
+
----
|
|
1193
1182
|
====
|
|
1183
|
+
|
|
1184
|
+
=== Extract specific font
|
|
1185
|
+
|
|
1186
|
+
==== General
|
|
1187
|
+
|
|
1188
|
+
Extract a specific font from a TrueType Collection (TTC) or OpenType Collection
|
|
1189
|
+
(OTC) by its index.
|
|
1190
|
+
|
|
1191
|
+
==== Command-line usage
|
|
1192
|
+
|
|
1194
1193
|
[source,shell]
|
|
1195
1194
|
----
|
|
1196
|
-
$ fontisan
|
|
1195
|
+
$ fontisan unpack FONT.{ttc,otc} --font-index INDEX OUTPUT.{ttf,otf}
|
|
1197
1196
|
----
|
|
1198
1197
|
|
|
1199
|
-
|
|
1198
|
+
NOTE: In `extract_ttc`, this was done with `extract_ttc --font-index INDEX FONT.ttc OUTPUT.ttf`.
|
|
1199
|
+
|
|
1200
|
+
.Extract with validation
|
|
1201
|
+
[example]
|
|
1202
|
+
====
|
|
1203
|
+
[source,shell]
|
|
1200
1204
|
----
|
|
1201
|
-
|
|
1202
|
-
|
|
1205
|
+
# Extract and validate simultaneously
|
|
1206
|
+
$ fontisan unpack spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc extracted_fonts/ --validate
|
|
1203
1207
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
5 dollar
|
|
1211
|
-
6 percent
|
|
1212
|
-
7 ampersand
|
|
1213
|
-
8 quotesingle
|
|
1214
|
-
9 parenleft
|
|
1215
|
-
10 parenright
|
|
1216
|
-
11 asterisk
|
|
1217
|
-
12 plus
|
|
1218
|
-
13 comma
|
|
1219
|
-
14 hyphen
|
|
1220
|
-
15 period
|
|
1221
|
-
16 slash
|
|
1222
|
-
17 zero
|
|
1223
|
-
18 one
|
|
1224
|
-
19 two
|
|
1225
|
-
20 three
|
|
1226
|
-
...
|
|
1208
|
+
Extracting font 0: Noto Serif CJK JP → extracted_fonts/NotoSerifCJKJP-Regular.ttf
|
|
1209
|
+
Extracting font 1: Noto Serif CJK KR → extracted_fonts/NotoSerifCJKKR-Regular.ttf
|
|
1210
|
+
Extracting font 2: Noto Serif CJK SC → extracted_fonts/NotoSerifCJKSC-Regular.ttf
|
|
1211
|
+
Extracting font 3: Noto Serif CJK TC → extracted_fonts/NotoSerifCJKTC-Regular.ttf
|
|
1212
|
+
|
|
1213
|
+
Validation: All fonts extracted successfully
|
|
1227
1214
|
----
|
|
1228
1215
|
====
|
|
1229
1216
|
|
|
1230
|
-
|
|
1217
|
+
=== Pack fonts into collection
|
|
1231
1218
|
|
|
1232
|
-
|
|
1233
|
-
which glyphs are assigned to which Unicode characters.
|
|
1219
|
+
==== General
|
|
1234
1220
|
|
|
1235
|
-
|
|
1221
|
+
Create a new TrueType Collection (TTC) or OpenType Collection (OTC) from multiple
|
|
1222
|
+
font files. Fontisan optimizes the collection by deduplicating shared tables
|
|
1223
|
+
to reduce file size.
|
|
1224
|
+
|
|
1225
|
+
==== Command-line usage
|
|
1236
1226
|
|
|
1227
|
+
.Create TTC collection from multiple fonts
|
|
1237
1228
|
[source,shell]
|
|
1238
1229
|
----
|
|
1239
|
-
|
|
1230
|
+
# Pack fonts into TTC with table sharing optimization
|
|
1231
|
+
$ fontisan pack font1.ttf font2.ttf font3.ttf --output family.ttc --analyze
|
|
1232
|
+
|
|
1233
|
+
Collection Analysis:
|
|
1234
|
+
Total fonts: 3
|
|
1235
|
+
Shared tables: 12
|
|
1236
|
+
Potential space savings: 45.2 KB
|
|
1237
|
+
Table sharing: 68.5%
|
|
1238
|
+
|
|
1239
|
+
Collection created successfully:
|
|
1240
|
+
Output: family.ttc
|
|
1241
|
+
Format: TTC
|
|
1242
|
+
Fonts: 3
|
|
1243
|
+
Size: 245.8 KB
|
|
1244
|
+
Space saved: 45.2 KB
|
|
1245
|
+
Sharing: 68.5%
|
|
1240
1246
|
----
|
|
1241
1247
|
|
|
1242
|
-
|
|
1248
|
+
.Create OTC collection from OpenType fonts
|
|
1249
|
+
[source,shell]
|
|
1250
|
+
----
|
|
1251
|
+
$ fontisan pack Regular.otf Bold.otf Italic.otf --output family.otc --format otc
|
|
1252
|
+
----
|
|
1243
1253
|
|
|
1244
|
-
`FONT_FILE`:: Path to the font file (OTF, TTF, or TTC)
|
|
1245
|
-
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
1246
1254
|
|
|
1255
|
+
=== Validate collection
|
|
1256
|
+
|
|
1257
|
+
==== General
|
|
1258
|
+
|
|
1259
|
+
Validate the structure and checksums of a TrueType Collection (TTC) or OpenType
|
|
1260
|
+
Collection (OTC).
|
|
1261
|
+
|
|
1262
|
+
==== Command-line usage
|
|
1247
1263
|
|
|
1248
|
-
.Unicode to glyph mappings in Libertinus Serif Regular
|
|
1249
|
-
[example]
|
|
1250
|
-
====
|
|
1251
1264
|
[source,shell]
|
|
1252
1265
|
----
|
|
1253
|
-
$ fontisan
|
|
1266
|
+
$ fontisan validate FONT.{ttc,otc}
|
|
1267
|
+
----
|
|
1268
|
+
|
|
1269
|
+
NOTE: In `extract_ttc`, this was done with `extract_ttc --validate FONT.ttc`.
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
== Advanced features
|
|
1273
|
+
|
|
1274
|
+
Fontisan provides capabilities:
|
|
1275
|
+
|
|
1276
|
+
.Font analysis and inspection
|
|
1277
|
+
* Extract OpenType tables with checksums and offsets
|
|
1278
|
+
* Display Unicode mappings and glyph names
|
|
1279
|
+
* Analyze variable font axes and instances
|
|
1280
|
+
* Show supported scripts and OpenType features
|
|
1281
|
+
* Dump raw binary table data
|
|
1282
|
+
|
|
1283
|
+
.Format conversion and subsetting
|
|
1284
|
+
* Convert between TTF, OTF, WOFF, and WOFF2 formats
|
|
1285
|
+
* Create font subsets with specific glyph ranges
|
|
1286
|
+
* Validate font structure and integrity
|
|
1287
|
+
* Generate SVG representations of glyphs
|
|
1288
|
+
|
|
1289
|
+
.Collection creation
|
|
1290
|
+
* Build new TTC files from individual fonts
|
|
1291
|
+
* Optimize collection with table deduplication
|
|
1292
|
+
* Pack fonts with shared tables for smaller file sizes
|
|
1293
|
+
|
|
1294
|
+
For complete migration guide, see link:docs/EXTRACT_TTC_MIGRATION.md[extract_ttc Migration Guide].
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
== Loading modes
|
|
1300
|
+
|
|
1301
|
+
=== General
|
|
1302
|
+
|
|
1303
|
+
Fontisan provides a flexible loading modes architecture that enables efficient
|
|
1304
|
+
font parsing for different use cases.
|
|
1305
|
+
|
|
1306
|
+
The system supports two distinct modes:
|
|
1307
|
+
|
|
1308
|
+
`:full` mode:: (default) Loads all tables in the font for complete analysis and
|
|
1309
|
+
manipulation
|
|
1310
|
+
|
|
1311
|
+
`:metadata` mode:: Loads only metadata tables needed for font identification and
|
|
1312
|
+
metrics (similar to `otfinfo` functionality). This mode is around 5x faster
|
|
1313
|
+
than full parsing and uses significantly less memory.
|
|
1314
|
+
|
|
1315
|
+
This architecture is particularly useful for software that only
|
|
1316
|
+
needs basic font information without full parsing overhead, such as
|
|
1317
|
+
font indexing systems or font discovery tools.
|
|
1318
|
+
|
|
1319
|
+
This mode was developed to improve performance in font indexing in the
|
|
1320
|
+
https://github.com/fontist/fontist[Fontist] library, where system fonts
|
|
1321
|
+
need to be scanned quickly without loading unnecessary data.
|
|
1322
|
+
|
|
1323
|
+
A font file opened in `:metadata` mode will only have a subset of tables
|
|
1324
|
+
loaded, and attempts to access non-loaded tables will return `nil`.
|
|
1325
|
+
|
|
1326
|
+
[source,ruby]
|
|
1327
|
+
----
|
|
1328
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
|
|
1329
|
+
|
|
1330
|
+
# Check table availability before accessing
|
|
1331
|
+
font.table_available?("name") # => true
|
|
1332
|
+
font.table_available?("GSUB") # => false
|
|
1333
|
+
|
|
1334
|
+
# Access allowed tables
|
|
1335
|
+
font.table("name") # => Works
|
|
1336
|
+
font.table("head") # => Works
|
|
1337
|
+
|
|
1338
|
+
# Restricted tables return nil
|
|
1339
|
+
font.table("GSUB") # => nil (not loaded in metadata mode)
|
|
1340
|
+
----
|
|
1341
|
+
|
|
1342
|
+
You can also set loading modes via the environment:
|
|
1343
|
+
|
|
1344
|
+
[source,ruby]
|
|
1254
1345
|
----
|
|
1346
|
+
# Set defaults via environment
|
|
1347
|
+
ENV['FONTISAN_MODE'] = 'metadata'
|
|
1348
|
+
ENV['FONTISAN_LAZY'] = 'false'
|
|
1349
|
+
|
|
1350
|
+
# Uses environment settings
|
|
1351
|
+
font = Fontisan::FontLoader.load('font.ttf')
|
|
1352
|
+
|
|
1353
|
+
# Explicit parameters override environment
|
|
1354
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :full)
|
|
1355
|
+
----
|
|
1356
|
+
|
|
1357
|
+
The loading mode can be queried at any time.
|
|
1358
|
+
|
|
1359
|
+
[source,ruby]
|
|
1360
|
+
----
|
|
1361
|
+
# Mode stored as font property
|
|
1362
|
+
font.loading_mode # => :metadata or :full
|
|
1363
|
+
|
|
1364
|
+
# Table availability checked before access
|
|
1365
|
+
font.table_available?(tag) # => boolean
|
|
1366
|
+
|
|
1367
|
+
# Access restricted based on mode
|
|
1368
|
+
font.table(tag) # => Returns table or raises error
|
|
1369
|
+
----
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
|
|
1373
|
+
=== Metadata mode
|
|
1374
|
+
|
|
1375
|
+
Loads only 6 tables (name, head, hhea, maxp, OS/2, post) instead of 15-20 tables.
|
|
1376
|
+
|
|
1377
|
+
.Metadata mode: Fast loading for font identification
|
|
1378
|
+
[source,ruby]
|
|
1379
|
+
----
|
|
1380
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata)
|
|
1381
|
+
puts font.family_name # => "Arial"
|
|
1382
|
+
puts font.subfamily_name # => "Regular"
|
|
1383
|
+
puts font.post_script_name # => "ArialMT"
|
|
1384
|
+
----
|
|
1385
|
+
|
|
1386
|
+
Tables loaded:
|
|
1387
|
+
|
|
1388
|
+
name:: Font names and metadata
|
|
1389
|
+
head:: Font header with global metrics
|
|
1390
|
+
hhea:: Horizontal header with line spacing
|
|
1391
|
+
maxp:: Maximum profile with glyph count
|
|
1392
|
+
OS/2:: OS/2 and Windows metrics
|
|
1393
|
+
post:: PostScript information
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
In metadata mode, these convenience methods provide direct access to name table
|
|
1397
|
+
fields:
|
|
1398
|
+
|
|
1399
|
+
`family_name`:: Font family name (nameID 1)
|
|
1400
|
+
`subfamily_name`:: Font subfamily/style name (nameID 2)
|
|
1401
|
+
`full_name`:: Full font name (nameID 4)
|
|
1402
|
+
`post_script_name`:: PostScript name (nameID 6)
|
|
1403
|
+
`preferred_family_name`:: Preferred family name (nameID 16, may be nil)
|
|
1404
|
+
`preferred_subfamily_name`:: Preferred subfamily name (nameID 17, may be nil)
|
|
1405
|
+
`units_per_em`:: Units per em from head table
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
=== Full mode
|
|
1409
|
+
|
|
1410
|
+
Loads all tables in the font for complete analysis and manipulation.
|
|
1411
|
+
|
|
1412
|
+
.Full mode: Complete font analysis
|
|
1413
|
+
[source,ruby]
|
|
1414
|
+
----
|
|
1415
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :full)
|
|
1416
|
+
font.table("GSUB") # => Available
|
|
1417
|
+
font.table("GPOS") # => Available
|
|
1418
|
+
|
|
1419
|
+
# Check which mode is active
|
|
1420
|
+
puts font.loading_mode # => :metadata or :full
|
|
1421
|
+
----
|
|
1422
|
+
|
|
1423
|
+
Tables loaded:
|
|
1424
|
+
|
|
1425
|
+
* All tables in the font
|
|
1426
|
+
* Including GSUB, GPOS, cmap, glyf/CFF, etc.
|
|
1427
|
+
|
|
1428
|
+
=== Lazy loading option
|
|
1429
|
+
|
|
1430
|
+
Fontisan supports lazy loading of tables in both `:metadata` and `:full` modes.
|
|
1431
|
+
When lazy loading is enabled (optional), tables are only parsed when accessed.
|
|
1432
|
+
|
|
1433
|
+
Options:
|
|
1434
|
+
|
|
1435
|
+
`false`:: (default) Eager loading. All tables for the selected mode are parsed
|
|
1436
|
+
upfront.
|
|
1437
|
+
|
|
1438
|
+
`true`:: Lazy loading enabled. Tables are parsed on-demand.
|
|
1439
|
+
|
|
1440
|
+
[source,ruby]
|
|
1441
|
+
----
|
|
1442
|
+
# Metadata mode with lazy loading (default, fastest)
|
|
1443
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: true)
|
|
1444
|
+
|
|
1445
|
+
# Metadata mode with eager loading (loads all metadata tables upfront)
|
|
1446
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :metadata, lazy: false)
|
|
1447
|
+
|
|
1448
|
+
# Full mode with lazy loading (tables loaded on-demand)
|
|
1449
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: true)
|
|
1255
1450
|
|
|
1256
|
-
|
|
1451
|
+
# Full mode with eager loading (all tables loaded upfront)
|
|
1452
|
+
font = Fontisan::FontLoader.load('font.ttf', mode: :full, lazy: false)
|
|
1257
1453
|
----
|
|
1258
|
-
Unicode mappings: 2382
|
|
1259
1454
|
|
|
1260
|
-
U+0020 glyph 1 space
|
|
1261
|
-
U+0021 glyph 2 exclam
|
|
1262
|
-
U+0022 glyph 3 quotedbl
|
|
1263
|
-
U+0023 glyph 4 numbersign
|
|
1264
|
-
U+0024 glyph 5 dollar
|
|
1265
|
-
U+0025 glyph 6 percent
|
|
1266
|
-
U+0026 glyph 7 ampersand
|
|
1267
|
-
U+0027 glyph 8 quotesingle
|
|
1268
|
-
U+0028 glyph 9 parenleft
|
|
1269
|
-
U+0029 glyph 10 parenright
|
|
1270
|
-
U+002A glyph 11 asterisk
|
|
1271
|
-
U+002B glyph 12 plus
|
|
1272
|
-
U+002C glyph 13 comma
|
|
1273
|
-
U+002D glyph 14 hyphen
|
|
1274
|
-
U+002E glyph 15 period
|
|
1275
|
-
U+002F glyph 16 slash
|
|
1276
|
-
U+0030 glyph 17 zero
|
|
1277
|
-
U+0031 glyph 18 one
|
|
1278
|
-
...
|
|
1279
|
-
----
|
|
1280
|
-
====
|
|
1281
1455
|
|
|
1282
|
-
==== Variable font information
|
|
1283
1456
|
|
|
1284
|
-
Display variation axes and named instances for variable fonts. Shows the design
|
|
1285
|
-
space and predefined styles available in the font.
|
|
1286
1457
|
|
|
1287
|
-
|
|
1458
|
+
== Outline format conversion
|
|
1288
1459
|
|
|
1289
|
-
|
|
1290
|
-
----
|
|
1291
|
-
$ fontisan variable FONT_FILE [--format FORMAT]
|
|
1292
|
-
----
|
|
1460
|
+
=== General
|
|
1293
1461
|
|
|
1294
|
-
|
|
1462
|
+
Fontisan supports bidirectional conversion between TrueType (TTF) and
|
|
1463
|
+
OpenType/CFF (OTF) outline formats through the Fontist universal outline model
|
|
1464
|
+
(UOM).
|
|
1295
1465
|
|
|
1296
|
-
|
|
1297
|
-
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
1466
|
+
The outline converter enables transformation between glyph outline formats:
|
|
1298
1467
|
|
|
1468
|
+
TrueType (TTF):: Uses quadratic Bézier curves stored in glyf/loca tables
|
|
1469
|
+
OpenType/CFF (OTF):: Uses cubic Bézier curves stored in CFF table
|
|
1299
1470
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
[source,shell]
|
|
1304
|
-
----
|
|
1305
|
-
$ fontisan variable spec/fixtures/fonts/MonaSans/variable/MonaSans[wdth,wght].ttf
|
|
1306
|
-
----
|
|
1471
|
+
Conversion uses a format-agnostic universal outline model as an intermediate
|
|
1472
|
+
representation, ensuring high-quality results while preserving glyph metrics and
|
|
1473
|
+
bounding boxes.
|
|
1307
1474
|
|
|
1308
|
-
[source,text]
|
|
1309
|
-
----
|
|
1310
|
-
Axis 0: wdth
|
|
1311
|
-
Axis 0 name: Width
|
|
1312
|
-
Axis 0 range: 75 125
|
|
1313
|
-
Axis 0 default: 100
|
|
1314
|
-
Axis 1: wght
|
|
1315
|
-
Axis 1 name: Weight
|
|
1316
|
-
Axis 1 range: 200 900
|
|
1317
|
-
Axis 1 default: 400
|
|
1318
|
-
Instance 0 name: Mona Sans Narrow Thin
|
|
1319
|
-
Instance 0 position: 75 200
|
|
1320
|
-
Instance 1 name: Mona Sans Narrow ExtraLight
|
|
1321
|
-
Instance 1 position: 75 250
|
|
1322
|
-
Instance 2 name: Mona Sans Narrow Light
|
|
1323
|
-
Instance 2 position: 75 300
|
|
1324
|
-
...
|
|
1325
|
-
----
|
|
1326
|
-
====
|
|
1327
1475
|
|
|
1328
|
-
|
|
1476
|
+
=== Convert between TTF and OTF
|
|
1329
1477
|
|
|
1330
|
-
|
|
1331
|
-
point sizes.
|
|
1478
|
+
==== Command-line usage
|
|
1332
1479
|
|
|
1333
1480
|
Syntax:
|
|
1334
1481
|
|
|
1335
|
-
[source,
|
|
1482
|
+
[source,bash]
|
|
1336
1483
|
----
|
|
1337
|
-
$ fontisan
|
|
1484
|
+
$ fontisan convert INPUT_FONT --to FORMAT --output OUTPUT_FONT
|
|
1338
1485
|
----
|
|
1339
1486
|
|
|
1340
1487
|
Where,
|
|
1341
1488
|
|
|
1342
|
-
`
|
|
1343
|
-
`FORMAT`::
|
|
1489
|
+
`INPUT_FONT`:: Path to the input font file (TTF or OTF)
|
|
1490
|
+
`FORMAT`:: Target format:
|
|
1491
|
+
`ttf`, `truetype`::: TrueType format
|
|
1492
|
+
`otf`, `opentype`, `cff`::: OpenType/CFF format
|
|
1493
|
+
`OUTPUT_FONT`:: Path to the output font file
|
|
1344
1494
|
|
|
1345
1495
|
|
|
1346
|
-
|
|
1347
|
-
[example]
|
|
1348
|
-
====
|
|
1349
|
-
[source,shell]
|
|
1350
|
-
----
|
|
1351
|
-
$ fontisan optical-size spec/fixtures/fonts/libertinus/ttf/LibertinusSerifDisplay-Regular.ttf
|
|
1496
|
+
[source,bash]
|
|
1352
1497
|
----
|
|
1498
|
+
# Convert TrueType font to OpenType/CFF
|
|
1499
|
+
fontisan convert input.ttf --to otf --output output.otf
|
|
1353
1500
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
Size range: [18, 72) pt (source: OS/2_usLowerOpticalPointSize)
|
|
1501
|
+
# Convert OpenType/CFF font to TrueType
|
|
1502
|
+
fontisan convert input.otf --to ttf --output output.ttf
|
|
1357
1503
|
----
|
|
1358
|
-
====
|
|
1359
1504
|
|
|
1360
|
-
==== List supported scripts
|
|
1361
1505
|
|
|
1362
|
-
|
|
1363
|
-
and GPOS tables. Useful for understanding language coverage.
|
|
1506
|
+
==== Ruby API usage
|
|
1364
1507
|
|
|
1365
|
-
|
|
1508
|
+
Basic conversion with OutlineConverter:
|
|
1366
1509
|
|
|
1367
|
-
[source,
|
|
1368
|
-
----
|
|
1369
|
-
$ fontisan scripts FONT_FILE [--format FORMAT]
|
|
1510
|
+
[source,ruby]
|
|
1370
1511
|
----
|
|
1512
|
+
require 'fontisan'
|
|
1371
1513
|
|
|
1372
|
-
|
|
1514
|
+
# Load a TrueType font
|
|
1515
|
+
font = Fontisan::FontLoader.load('input.ttf')
|
|
1373
1516
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1517
|
+
# Convert to OpenType/CFF
|
|
1518
|
+
converter = Fontisan::Converters::OutlineConverter.new
|
|
1519
|
+
tables = converter.convert(font, target_format: :otf)
|
|
1376
1520
|
|
|
1521
|
+
# Write output
|
|
1522
|
+
Fontisan::FontWriter.write_to_file(
|
|
1523
|
+
tables,
|
|
1524
|
+
'output.otf',
|
|
1525
|
+
sfnt_version: 0x4F54544F # 'OTTO' for OpenType/CFF
|
|
1526
|
+
)
|
|
1527
|
+
----
|
|
1377
1528
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
[source,shell]
|
|
1529
|
+
Using FormatConverter:
|
|
1530
|
+
|
|
1531
|
+
[source,ruby]
|
|
1382
1532
|
----
|
|
1383
|
-
|
|
1533
|
+
require 'fontisan'
|
|
1534
|
+
|
|
1535
|
+
# Load font
|
|
1536
|
+
font = Fontisan::FontLoader.load('input.ttf')
|
|
1537
|
+
|
|
1538
|
+
# Convert using high-level API
|
|
1539
|
+
converter = Fontisan::Converters::FormatConverter.new
|
|
1540
|
+
if converter.supported?(:ttf, :otf)
|
|
1541
|
+
tables = converter.convert(font, :otf)
|
|
1542
|
+
|
|
1543
|
+
# Write output
|
|
1544
|
+
Fontisan::FontWriter.write_to_file(
|
|
1545
|
+
tables,
|
|
1546
|
+
'output.otf',
|
|
1547
|
+
sfnt_version: 0x4F54544F
|
|
1548
|
+
)
|
|
1549
|
+
end
|
|
1384
1550
|
----
|
|
1385
1551
|
|
|
1386
|
-
|
|
1552
|
+
To check supported conversions:
|
|
1553
|
+
|
|
1554
|
+
[source,ruby]
|
|
1387
1555
|
----
|
|
1388
|
-
|
|
1556
|
+
converter = Fontisan::Converters::FormatConverter.new
|
|
1389
1557
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1558
|
+
# Check if conversion is supported
|
|
1559
|
+
converter.supported?(:ttf, :otf) # => true
|
|
1560
|
+
converter.supported?(:otf, :ttf) # => true
|
|
1561
|
+
|
|
1562
|
+
# Get all supported conversions
|
|
1563
|
+
converter.all_conversions
|
|
1564
|
+
# => [{from: :ttf, to: :otf}, {from: :otf, to: :ttf}, ...]
|
|
1565
|
+
|
|
1566
|
+
# Get supported targets for a source format
|
|
1567
|
+
converter.supported_targets(:ttf)
|
|
1568
|
+
# => [:ttf, :otf, :woff2, :svg]
|
|
1395
1569
|
----
|
|
1396
|
-
====
|
|
1397
1570
|
|
|
1398
|
-
==== List OpenType features
|
|
1399
1571
|
|
|
1400
|
-
|
|
1401
|
-
small capitals) available for specific scripts or all scripts.
|
|
1572
|
+
=== Validation
|
|
1402
1573
|
|
|
1403
|
-
|
|
1574
|
+
Font integrity validation is enabled by default for all conversions.
|
|
1404
1575
|
|
|
1405
|
-
|
|
1576
|
+
The validator ensures proper OpenType checksum calculation including correct
|
|
1577
|
+
handling of the head table's checksumAdjustment field per the OpenType
|
|
1578
|
+
specification.
|
|
1579
|
+
|
|
1580
|
+
After conversion, validate the output font:
|
|
1581
|
+
|
|
1582
|
+
[source,bash]
|
|
1406
1583
|
----
|
|
1407
|
-
|
|
1584
|
+
fontisan validate output.otf
|
|
1585
|
+
fontisan info output.otf
|
|
1586
|
+
fontisan tables output.otf
|
|
1408
1587
|
----
|
|
1409
1588
|
|
|
1410
|
-
Where,
|
|
1411
1589
|
|
|
1412
|
-
|
|
1413
|
-
`SCRIPT`:: Optional 4-character script tag (e.g., `latn`, `cyrl`, `arab`). If not specified, shows features for all scripts
|
|
1414
|
-
`FORMAT`:: Output format: `text` (default), `json`, or `yaml`
|
|
1590
|
+
== Technical details of outline conversion
|
|
1415
1591
|
|
|
1592
|
+
=== General
|
|
1416
1593
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
[source,shell]
|
|
1594
|
+
The converter uses a three-stage pipeline:
|
|
1595
|
+
|
|
1596
|
+
[source]
|
|
1421
1597
|
----
|
|
1422
|
-
|
|
1598
|
+
Source Format Universal Outline Target Format
|
|
1599
|
+
------------- ------------------ -------------
|
|
1600
|
+
TrueType (glyf) →→→ Command-based model →→→ OpenType/CFF
|
|
1601
|
+
Quadratic curves Path representation Cubic curves
|
|
1602
|
+
On/off-curve pts (format-agnostic) CharStrings
|
|
1603
|
+
Delta encoding Bounding boxes Type 2 operators
|
|
1604
|
+
Metrics Compact encoding
|
|
1423
1605
|
----
|
|
1424
1606
|
|
|
1425
|
-
|
|
1426
|
-
----
|
|
1427
|
-
Script: latn
|
|
1428
|
-
Feature count: 4
|
|
1607
|
+
=== Conversion steps
|
|
1429
1608
|
|
|
1430
|
-
|
|
1431
|
-
kern Kerning
|
|
1432
|
-
mark Mark Positioning
|
|
1433
|
-
mkmk Mark to Mark Positioning
|
|
1434
|
-
----
|
|
1435
|
-
====
|
|
1609
|
+
TTF → OTF conversion:
|
|
1436
1610
|
|
|
1611
|
+
. Extract glyphs from glyf/loca tables
|
|
1612
|
+
. Convert quadratic Bézier curves to universal outline format
|
|
1613
|
+
. Build CFF table with CharStrings INDEX
|
|
1614
|
+
. Update maxp table to version 0.5 (CFF format)
|
|
1615
|
+
. Update head table (clear indexToLocFormat)
|
|
1616
|
+
. Remove glyf/loca tables
|
|
1617
|
+
. Preserve all other tables
|
|
1437
1618
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1619
|
+
OTF → TTF conversion:
|
|
1620
|
+
|
|
1621
|
+
. Extract CharStrings from CFF table
|
|
1622
|
+
. Convert cubic Bézier curves to universal outline format
|
|
1623
|
+
. Convert cubic curves to quadratic using adaptive subdivision
|
|
1624
|
+
. Build glyf and loca tables with optimal format selection
|
|
1625
|
+
. Update maxp table to version 1.0 (TrueType format)
|
|
1626
|
+
. Update head table (set indexToLocFormat)
|
|
1627
|
+
. Remove CFF table
|
|
1628
|
+
. Preserve all other tables
|
|
1629
|
+
|
|
1630
|
+
=== Curve conversion
|
|
1631
|
+
|
|
1632
|
+
**Quadratic to cubic** (lossless):
|
|
1633
|
+
|
|
1634
|
+
[source]
|
|
1444
1635
|
----
|
|
1636
|
+
Given quadratic curve with control point Q:
|
|
1637
|
+
P0 (start), Q (control), P2 (end)
|
|
1445
1638
|
|
|
1446
|
-
|
|
1639
|
+
Calculate cubic control points:
|
|
1640
|
+
CP1 = P0 + (2/3) × (Q - P0)
|
|
1641
|
+
CP2 = P2 + (2/3) × (Q - P2)
|
|
1642
|
+
|
|
1643
|
+
Result: Exact mathematical equivalent
|
|
1447
1644
|
----
|
|
1448
|
-
Script: DFLT
|
|
1449
|
-
Feature count: 4
|
|
1450
1645
|
|
|
1451
|
-
|
|
1452
|
-
kern Kerning
|
|
1453
|
-
mark Mark Positioning
|
|
1454
|
-
mkmk Mark to Mark Positioning
|
|
1646
|
+
**Cubic to quadratic** (adaptive):
|
|
1455
1647
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1648
|
+
[source]
|
|
1649
|
+
----
|
|
1650
|
+
Given cubic curve with control points:
|
|
1651
|
+
P0 (start), CP1, CP2, P3 (end)
|
|
1458
1652
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1653
|
+
Use adaptive subdivision algorithm:
|
|
1654
|
+
1. Estimate error of quadratic approximation
|
|
1655
|
+
2. If error > threshold (0.5 units):
|
|
1656
|
+
- Subdivide cubic curve at midpoint
|
|
1657
|
+
- Recursively convert each half
|
|
1658
|
+
3. Otherwise: Output quadratic approximation
|
|
1659
|
+
|
|
1660
|
+
Result: High-quality approximation with < 0.5 unit deviation
|
|
1661
|
+
----
|
|
1463
1662
|
|
|
1464
|
-
|
|
1465
|
-
Feature count: 4
|
|
1663
|
+
=== Compound glyph support
|
|
1466
1664
|
|
|
1467
|
-
|
|
1468
|
-
kern Kerning
|
|
1469
|
-
mark Mark Positioning
|
|
1470
|
-
mkmk Mark to Mark Positioning
|
|
1665
|
+
==== General
|
|
1471
1666
|
|
|
1472
|
-
|
|
1473
|
-
Feature count: 2
|
|
1667
|
+
Fontisan fully supports compound (composite) glyphs in both conversion directions:
|
|
1474
1668
|
|
|
1475
|
-
|
|
1476
|
-
|
|
1669
|
+
* **TTF → OTF**: Compound glyphs are decomposed into simple outlines with transformations applied
|
|
1670
|
+
* **OTF → TTF**: CFF glyphs are converted to simple TrueType glyphs
|
|
1477
1671
|
|
|
1478
|
-
|
|
1479
|
-
Feature count: 4
|
|
1672
|
+
==== Decomposition process
|
|
1480
1673
|
|
|
1481
|
-
|
|
1482
|
-
kern Kerning
|
|
1483
|
-
mark Mark Positioning
|
|
1484
|
-
mkmk Mark to Mark Positioning
|
|
1485
|
-
----
|
|
1486
|
-
====
|
|
1674
|
+
When converting TTF to OTF, compound glyphs undergo the following process:
|
|
1487
1675
|
|
|
1488
|
-
|
|
1676
|
+
. Detected from glyf table flags (numberOfContours = -1)
|
|
1677
|
+
. Components recursively resolved (handling nested compound glyphs)
|
|
1678
|
+
. Transformation matrices applied to each component (translation, scale, rotation)
|
|
1679
|
+
. All components merged into a single simple outline
|
|
1680
|
+
. Converted to CFF CharString format
|
|
1489
1681
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1682
|
+
This ensures that all glyphs render identically while maintaining proper metrics
|
|
1683
|
+
and bounding boxes.
|
|
1492
1684
|
|
|
1493
|
-
|
|
1685
|
+
==== Technical implementation
|
|
1494
1686
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1687
|
+
Compound glyphs reference other glyphs by index and apply 2×3 affine
|
|
1688
|
+
transformation matrices:
|
|
1689
|
+
|
|
1690
|
+
[source]
|
|
1498
1691
|
----
|
|
1692
|
+
x' = a*x + c*y + e
|
|
1693
|
+
y' = b*x + d*y + f
|
|
1499
1694
|
|
|
1500
|
-
Where
|
|
1695
|
+
Where:
|
|
1696
|
+
- a, d: Scale factors for x and y axes
|
|
1697
|
+
- b, c: Rotation/skew components
|
|
1698
|
+
- e, f: Translation offsets (x, y position)
|
|
1699
|
+
----
|
|
1501
1700
|
|
|
1502
|
-
|
|
1503
|
-
`TABLE_TAG`:: Four-character table tag (e.g., `name`, `head`, `GSUB`, `GPOS`)
|
|
1701
|
+
The resolver handles:
|
|
1504
1702
|
|
|
1703
|
+
* Simple glyphs referenced by compounds
|
|
1704
|
+
* Nested compound glyphs (compounds referencing other compounds)
|
|
1705
|
+
* Circular reference detection with maximum recursion depth (32 levels)
|
|
1706
|
+
* Complex transformation matrices (uniform scale, x/y scale, full 2×2 matrix)
|
|
1505
1707
|
|
|
1506
|
-
|
|
1507
|
-
[example]
|
|
1508
|
-
====
|
|
1509
|
-
[source,shell]
|
|
1510
|
-
----
|
|
1511
|
-
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf name > name_table.bin
|
|
1512
|
-
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf GPOS > gpos_table.bin
|
|
1513
|
-
$ fontisan dump-table spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf head > head_table.bin
|
|
1514
|
-
----
|
|
1708
|
+
=== Subroutine optimization
|
|
1515
1709
|
|
|
1516
|
-
|
|
1517
|
-
====
|
|
1710
|
+
==== General
|
|
1518
1711
|
|
|
1519
|
-
|
|
1712
|
+
When converting TrueType (TTF) to OpenType/CFF (OTF), Fontisan can automatically
|
|
1713
|
+
generate CFF subroutines to reduce file size.
|
|
1520
1714
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1715
|
+
Subroutines extract repeated CharString patterns across glyphs and store them
|
|
1716
|
+
once, significantly reducing CFF table size while maintaining identical glyph
|
|
1717
|
+
rendering.
|
|
1524
1718
|
|
|
1525
|
-
|
|
1719
|
+
Key features:
|
|
1526
1720
|
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1721
|
+
* Pattern analysis: Analyzes byte sequences across all CharStrings to identify repeating patterns
|
|
1722
|
+
* Frequency-based selection: Prioritizes patterns that provide maximum space savings
|
|
1723
|
+
* Configurable thresholds: Customizable minimum pattern length and maximum subroutine count
|
|
1724
|
+
* Ordering optimization: Automatically orders subroutines by frequency for better compression
|
|
1531
1725
|
|
|
1532
|
-
|
|
1726
|
+
Typical space savings: 30-50% reduction in CFF table size for fonts with similar
|
|
1727
|
+
glyph shapes.
|
|
1533
1728
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
`--tables TABLES`:: Specific tables to export (space-separated list)
|
|
1538
|
-
`--binary-format FORMAT`:: Binary encoding: `hex` (default) or `base64`
|
|
1729
|
+
NOTE: Current implementation calculates accurate optimization metrics but does
|
|
1730
|
+
not modify the output CFF table. Full CFF serialization with subroutines will be
|
|
1731
|
+
available in the next development phase.
|
|
1539
1732
|
|
|
1733
|
+
==== Edge cases
|
|
1540
1734
|
|
|
1541
|
-
|
|
1542
|
-
[example]
|
|
1543
|
-
====
|
|
1544
|
-
[source,shell]
|
|
1545
|
-
----
|
|
1546
|
-
$ fontisan export spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf --output font.yaml
|
|
1735
|
+
The optimizer correctly handles:
|
|
1547
1736
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1737
|
+
* Multi-byte numbers: Number encodings from 1-5 bytes (CFF Type 2 format)
|
|
1738
|
+
* 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`)
|
|
1739
|
+
* Overlapping patterns: Multiple patterns at same byte positions
|
|
1740
|
+
* Stack-neutral validation: Patterns verified to maintain consistent stack state
|
|
1551
1741
|
|
|
1552
|
-
|
|
1553
|
-
[example]
|
|
1554
|
-
====
|
|
1555
|
-
[source,shell]
|
|
1556
|
-
----
|
|
1557
|
-
$ fontisan export spec/fixtures/fonts/libertinus/ttf/LibertinusSerif-Regular.ttf \
|
|
1558
|
-
--format ttx --tables head hhea maxp name --output font.ttx
|
|
1559
|
-
----
|
|
1742
|
+
==== Technical details
|
|
1560
1743
|
|
|
1561
|
-
|
|
1562
|
-
with fonttools.
|
|
1563
|
-
====
|
|
1744
|
+
The subroutine optimizer uses a four-stage pipeline:
|
|
1564
1745
|
|
|
1565
|
-
|
|
1566
|
-
[example]
|
|
1567
|
-
====
|
|
1568
|
-
[source,shell]
|
|
1746
|
+
[source]
|
|
1569
1747
|
----
|
|
1570
|
-
|
|
1748
|
+
CharStrings → Pattern Analysis → Selection → Ordering → Metadata
|
|
1749
|
+
(Input) (Find repeats) (Optimize) (Frequency) (Output)
|
|
1571
1750
|
----
|
|
1572
1751
|
|
|
1573
|
-
|
|
1574
|
-
JSON-based workflows.
|
|
1575
|
-
====
|
|
1576
|
-
|
|
1577
|
-
==== General options
|
|
1752
|
+
**Pattern analysis**:
|
|
1578
1753
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1754
|
+
. Extracts byte sequences from all CharStrings
|
|
1755
|
+
. Identifies repeating patterns across glyphs
|
|
1756
|
+
. Filters by minimum pattern length (default: 10 bytes)
|
|
1757
|
+
. Builds pattern frequency map
|
|
1582
1758
|
|
|
1583
|
-
|
|
1759
|
+
**Selection algorithm**:
|
|
1584
1760
|
|
|
1585
|
-
|
|
1761
|
+
. Calculates savings for each pattern: `frequency × (length - overhead)`
|
|
1762
|
+
. Ranks patterns by total savings (descending)
|
|
1763
|
+
. Selects top patterns up to `max_subroutines` limit
|
|
1764
|
+
. Ensures selected patterns don't exceed CFF limits
|
|
1586
1765
|
|
|
1587
|
-
|
|
1766
|
+
**Ordering optimization**:
|
|
1588
1767
|
|
|
1589
|
-
|
|
1768
|
+
. Sorts subroutines by usage frequency (most used first)
|
|
1769
|
+
. Optimizes CFF bias calculation for better compression
|
|
1770
|
+
. Ensures subroutine indices fit within CFF constraints
|
|
1590
1771
|
|
|
1591
|
-
|
|
1772
|
+
**CFF bias calculation**:
|
|
1592
1773
|
|
|
1593
|
-
[source
|
|
1774
|
+
[source]
|
|
1594
1775
|
----
|
|
1595
|
-
|
|
1776
|
+
Subroutine count CFF Bias
|
|
1777
|
+
----------------- ---------
|
|
1778
|
+
0-1239 107
|
|
1779
|
+
1240-33899 1131
|
|
1780
|
+
33900-65535 32768
|
|
1596
1781
|
----
|
|
1597
1782
|
|
|
1783
|
+
The bias value determines how subroutine indices are encoded in CharStrings,
|
|
1784
|
+
affecting the final size.
|
|
1598
1785
|
|
|
1599
|
-
==== Font collections
|
|
1600
1786
|
|
|
1601
|
-
|
|
1787
|
+
==== Troubleshooting
|
|
1602
1788
|
|
|
1603
|
-
|
|
1604
|
-
their index, family name, and style.
|
|
1789
|
+
If you encounter CharString parsing errors after optimization:
|
|
1605
1790
|
|
|
1606
|
-
|
|
1791
|
+
. Verify bias calculation: Ensure bias matches CFF specification (107, 1131, or 32768)
|
|
1792
|
+
. Check operator boundaries: Patterns should only be extracted at valid boundaries
|
|
1793
|
+
. Ensure no overlaps: Multiple patterns should not occupy same byte positions
|
|
1794
|
+
. Enable verbose mode: Use `--verbose` flag for detailed diagnostics
|
|
1795
|
+
|
|
1796
|
+
.Subroutine debugging workflow example
|
|
1797
|
+
====
|
|
1798
|
+
[source,bash]
|
|
1607
1799
|
----
|
|
1608
|
-
|
|
1800
|
+
# Convert with verbose output
|
|
1801
|
+
$ fontisan convert input.ttf --to otf --output output.otf --optimize --verbose
|
|
1802
|
+
|
|
1803
|
+
# Validate the output
|
|
1804
|
+
$ fontisan validate output.otf
|
|
1805
|
+
|
|
1806
|
+
# Check CharString structure
|
|
1807
|
+
$ fontisan info output.otf
|
|
1609
1808
|
----
|
|
1610
1809
|
|
|
1611
|
-
|
|
1810
|
+
If validation fails, try:
|
|
1612
1811
|
|
|
1812
|
+
[source,bash]
|
|
1813
|
+
----
|
|
1814
|
+
# Disable optimization
|
|
1815
|
+
$ fontisan convert input.ttf --to otf --output output.otf
|
|
1613
1816
|
|
|
1614
|
-
|
|
1817
|
+
# Use stack-aware mode for safer optimization
|
|
1818
|
+
$ fontisan convert input.ttf --to otf --output output.otf --optimize --stack-aware
|
|
1819
|
+
----
|
|
1820
|
+
====
|
|
1821
|
+
|
|
1822
|
+
.Optimization parameters
|
|
1615
1823
|
[example]
|
|
1616
1824
|
====
|
|
1617
|
-
[source,
|
|
1825
|
+
[source,bash]
|
|
1618
1826
|
----
|
|
1619
|
-
#
|
|
1620
|
-
$ fontisan
|
|
1827
|
+
# Adjust pattern matching sensitivity
|
|
1828
|
+
$ fontisan convert input.ttf --to otf --output output.otf \
|
|
1829
|
+
--optimize \
|
|
1830
|
+
--min-pattern-length 15 \
|
|
1831
|
+
--max-subroutines 10000 \
|
|
1832
|
+
--verbose
|
|
1621
1833
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1834
|
+
# Disable ordering optimization
|
|
1835
|
+
$ fontisan convert input.ttf --to otf --output output.otf \
|
|
1836
|
+
--optimize \
|
|
1837
|
+
--no-optimize-ordering
|
|
1838
|
+
----
|
|
1839
|
+
====
|
|
1626
1840
|
|
|
1627
|
-
|
|
1628
|
-
Family: Noto Serif CJK KR
|
|
1629
|
-
Subfamily: Regular
|
|
1630
|
-
PostScript: NotoSerifCJKKR-Regular
|
|
1841
|
+
Where,
|
|
1631
1842
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1843
|
+
`--optimize`:: Enable subroutine optimization (default: false)
|
|
1844
|
+
`--min-pattern-length N`:: Minimum pattern length in bytes (default: 10)
|
|
1845
|
+
`--max-subroutines N`:: Maximum number of subroutines to generate (default: 65,535)
|
|
1846
|
+
`--optimize-ordering`:: Optimize subroutine ordering by frequency (default: true)
|
|
1847
|
+
`--verbose`:: Show detailed optimization statistics
|
|
1636
1848
|
|
|
1637
|
-
Font 3: Noto Serif CJK TC
|
|
1638
|
-
Family: Noto Serif CJK TC
|
|
1639
|
-
Subfamily: Regular
|
|
1640
|
-
PostScript: NotoSerifCJKTC-Regular
|
|
1641
|
-
----
|
|
1642
|
-
====
|
|
1643
1849
|
|
|
1850
|
+
=== Stack-aware optimization
|
|
1644
1851
|
|
|
1645
|
-
|
|
1852
|
+
==== General
|
|
1646
1853
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1854
|
+
Stack-aware optimization is an advanced mode that ensures all extracted patterns
|
|
1855
|
+
are stack-neutral, guaranteeing 100% safety and reliability.
|
|
1649
1856
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
----
|
|
1857
|
+
Unlike normal byte-level pattern matching, stack-aware mode simulates CharString
|
|
1858
|
+
execution to track operand stack depth, only extracting patterns that maintain
|
|
1859
|
+
consistent stack state.
|
|
1654
1860
|
|
|
1655
|
-
|
|
1861
|
+
Key benefits:
|
|
1656
1862
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
----
|
|
1662
|
-
# Detailed collection analysis
|
|
1663
|
-
$ fontisan info spec/fixtures/fonts/NotoSerifCJK/NotoSerifCJK.ttc --format yaml
|
|
1863
|
+
* **100% Reliability**: All patterns are validated to be stack-neutral
|
|
1864
|
+
* **No Stack Errors**: Eliminates stack underflow/overflow issues
|
|
1865
|
+
* **Faster Processing**: 6-12x faster than normal optimization due to early filtering
|
|
1866
|
+
* **Smaller Pattern Set**: Significantly fewer candidates reduce memory usage
|
|
1664
1867
|
|
|
1665
|
-
|
|
1666
|
-
collection_type: ttc
|
|
1667
|
-
font_count: 4
|
|
1668
|
-
fonts:
|
|
1669
|
-
- index: 0
|
|
1670
|
-
family_name: Noto Serif CJK JP
|
|
1671
|
-
subfamily_name: Regular
|
|
1672
|
-
postscript_name: NotoSerifCJKJP-Regular
|
|
1673
|
-
font_format: opentype
|
|
1674
|
-
- index: 1
|
|
1675
|
-
family_name: Noto Serif CJK KR
|
|
1676
|
-
subfamily_name: Regular
|
|
1677
|
-
postscript_name: NotoSerifCJKKR-Regular
|
|
1678
|
-
font_format: opentype
|
|
1679
|
-
- index: 2
|
|
1680
|
-
family_name: Noto Serif CJK SC
|
|
1681
|
-
subfamily_name: Regular
|
|
1682
|
-
postscript_name: NotoSerifCJKSC-Regular
|
|
1683
|
-
font_format: opentype
|
|
1684
|
-
- index: 3
|
|
1685
|
-
family_name: Noto Serif CJK TC
|
|
1686
|
-
subfamily_name: Regular
|
|
1687
|
-
postscript_name: NotoSerifCJKTC-Regular
|
|
1688
|
-
font_format: opentype
|
|
1689
|
-
----
|
|
1690
|
-
====
|
|
1868
|
+
Trade-offs:
|
|
1691
1869
|
|
|
1692
|
-
|
|
1870
|
+
* **Lower Compression**: ~6% reduction vs ~11% with normal mode
|
|
1871
|
+
* **Fewer Patterns**: Filters out 90%+ of raw patterns for safety
|
|
1872
|
+
* **Stack Validation Overhead**: Adds stack tracking during analysis
|
|
1693
1873
|
|
|
1694
|
-
|
|
1695
|
-
to a specified output directory.
|
|
1874
|
+
==== Command-line usage
|
|
1696
1875
|
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1876
|
+
.Enable stack-aware optimization
|
|
1877
|
+
[example]
|
|
1878
|
+
====
|
|
1879
|
+
[source,bash]
|
|
1700
1880
|
----
|
|
1881
|
+
# Convert with stack-aware optimization
|
|
1882
|
+
$ fontisan convert input.ttf --to otf --output output.otf \
|
|
1883
|
+
--optimize \
|
|
1884
|
+
--stack-aware \
|
|
1885
|
+
--verbose
|
|
1701
1886
|
|
|
1702
|
-
|
|
1887
|
+
Converting input.ttf to otf...
|
|
1703
1888
|
|
|
1704
|
-
|
|
1889
|
+
Analyzing CharString patterns (4515 glyphs)...
|
|
1890
|
+
Found 8566 potential patterns
|
|
1891
|
+
Selecting optimal patterns...
|
|
1892
|
+
Selected 832 patterns for subroutinization
|
|
1893
|
+
Building subroutines...
|
|
1894
|
+
Generated 832 subroutines
|
|
1895
|
+
Rewriting CharStrings with subroutine calls...
|
|
1896
|
+
Rewrote 4515 CharStrings
|
|
1705
1897
|
|
|
1706
|
-
|
|
1898
|
+
Subroutine Optimization Results:
|
|
1899
|
+
Patterns found: 8566
|
|
1900
|
+
Patterns selected: 832
|
|
1901
|
+
Subroutines generated: 832
|
|
1902
|
+
Estimated bytes saved: 46,280
|
|
1903
|
+
CFF bias: 0
|
|
1707
1904
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1905
|
+
Conversion complete!
|
|
1906
|
+
Input: input.ttf (806.3 KB)
|
|
1907
|
+
Output: output.otf (660.7 KB)
|
|
1711
1908
|
----
|
|
1909
|
+
====
|
|
1712
1910
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
.Extract with validation
|
|
1911
|
+
.Stack-aware vs normal mode
|
|
1716
1912
|
[example]
|
|
1717
1913
|
====
|
|
1718
|
-
[source,
|
|
1914
|
+
[source,bash]
|
|
1719
1915
|
----
|
|
1720
|
-
#
|
|
1721
|
-
$
|
|
1916
|
+
# Use the comparison script
|
|
1917
|
+
$ ruby scripts/compare_stack_aware.rb input.ttf
|
|
1722
1918
|
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
Extracting font 3: Noto Serif CJK TC → extracted_fonts/NotoSerifCJKTC-Regular.ttf
|
|
1919
|
+
File Size Reduction:
|
|
1920
|
+
Normal: 81.49 KB (11.27%)
|
|
1921
|
+
Stack-Aware: 43.17 KB (6.13%)
|
|
1727
1922
|
|
|
1728
|
-
|
|
1923
|
+
Processing Times:
|
|
1924
|
+
Normal: 18.38 s
|
|
1925
|
+
Stack-Aware: 1.54 s (12x faster)
|
|
1926
|
+
|
|
1927
|
+
Stack-Aware Efficiency: 52.97% of normal optimization
|
|
1729
1928
|
----
|
|
1730
1929
|
====
|
|
1731
1930
|
|
|
1732
|
-
|
|
1931
|
+
Where,
|
|
1733
1932
|
|
|
1734
|
-
|
|
1735
|
-
Collection (OTC).
|
|
1933
|
+
`--stack-aware`:: Enable stack-aware pattern detection (default: false)
|
|
1736
1934
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1935
|
+
==== Using the Ruby API
|
|
1936
|
+
|
|
1937
|
+
.Basic stack-aware optimization
|
|
1938
|
+
[example]
|
|
1939
|
+
====
|
|
1940
|
+
[source,ruby]
|
|
1740
1941
|
----
|
|
1942
|
+
require 'fontisan'
|
|
1741
1943
|
|
|
1742
|
-
|
|
1944
|
+
# Load TrueType font
|
|
1945
|
+
font = Fontisan::FontLoader.load('input.ttf')
|
|
1743
1946
|
|
|
1947
|
+
# Convert with stack-aware optimization
|
|
1948
|
+
converter = Fontisan::Converters::OutlineConverter.new
|
|
1949
|
+
tables = converter.convert(font, {
|
|
1950
|
+
target_format: :otf,
|
|
1951
|
+
optimize_subroutines: true,
|
|
1952
|
+
stack_aware: true # Enable safe mode
|
|
1953
|
+
})
|
|
1744
1954
|
|
|
1745
|
-
|
|
1955
|
+
# Access optimization results
|
|
1956
|
+
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
1957
|
+
puts "Patterns found: #{optimization[:pattern_count]}"
|
|
1958
|
+
puts "Stack-neutral patterns: #{optimization[:selected_count]}"
|
|
1959
|
+
puts "Processing time: #{optimization[:processing_time]}s"
|
|
1746
1960
|
|
|
1747
|
-
|
|
1961
|
+
# Write output
|
|
1962
|
+
Fontisan::FontWriter.write_to_file(
|
|
1963
|
+
tables,
|
|
1964
|
+
'output.otf',
|
|
1965
|
+
sfnt_version: 0x4F54544F
|
|
1966
|
+
)
|
|
1967
|
+
----
|
|
1968
|
+
====
|
|
1748
1969
|
|
|
1749
|
-
|
|
1750
|
-
* Extract OpenType tables with checksums and offsets
|
|
1751
|
-
* Display Unicode mappings and glyph names
|
|
1752
|
-
* Analyze variable font axes and instances
|
|
1753
|
-
* Show supported scripts and OpenType features
|
|
1754
|
-
* Dump raw binary table data
|
|
1970
|
+
==== Technical details
|
|
1755
1971
|
|
|
1756
|
-
|
|
1757
|
-
* Convert between TTF, OTF, WOFF, and WOFF2 formats
|
|
1758
|
-
* Create font subsets with specific glyph ranges
|
|
1759
|
-
* Validate font structure and integrity
|
|
1760
|
-
* Generate SVG representations of glyphs
|
|
1972
|
+
Stack-aware mode uses a three-stage validation process:
|
|
1761
1973
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1974
|
+
[source]
|
|
1975
|
+
----
|
|
1976
|
+
CharString Bytes → Stack Tracking → Pattern Validation → Safe Patterns
|
|
1977
|
+
(Input) (Simulate) (Filter) (Output)
|
|
1978
|
+
----
|
|
1766
1979
|
|
|
1767
|
-
|
|
1980
|
+
**Stack tracking**:
|
|
1768
1981
|
|
|
1769
|
-
|
|
1982
|
+
. Simulates CharString execution without full interpretation
|
|
1983
|
+
. Records stack depth at each byte position
|
|
1984
|
+
. Handles 40+ Type 2 CharString operators with correct stack effects
|
|
1770
1985
|
|
|
1771
|
-
|
|
1986
|
+
**Pattern validation**:
|
|
1772
1987
|
|
|
1773
|
-
.
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
# Pack fonts into TTC with table sharing optimization
|
|
1777
|
-
$ fontisan pack font1.ttf font2.ttf font3.ttf --output family.ttc --analyze
|
|
1988
|
+
. Checks if pattern start and end have same stack depth
|
|
1989
|
+
. Ensures no stack underflow during pattern execution
|
|
1990
|
+
. Verifies consistent results regardless of initial stack state
|
|
1778
1991
|
|
|
1779
|
-
|
|
1780
|
-
Total fonts: 3
|
|
1781
|
-
Shared tables: 12
|
|
1782
|
-
Potential space savings: 45.2 KB
|
|
1783
|
-
Table sharing: 68.5%
|
|
1992
|
+
**Stack-neutral pattern** criteria. Pattern is stack-neutral if:
|
|
1784
1993
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
Sharing: 68.5%
|
|
1994
|
+
. depth_at(pattern_start) == depth_at(pattern_end)
|
|
1995
|
+
. No negative depth during pattern execution
|
|
1996
|
+
. Pattern produces same result for any valid initial stack
|
|
1997
|
+
+
|
|
1998
|
+
.Example Stack-Neutral Pattern
|
|
1999
|
+
[source]
|
|
1792
2000
|
----
|
|
1793
|
-
|
|
1794
|
-
.Create OTC collection from OpenType fonts
|
|
1795
|
-
[source,shell]
|
|
2001
|
+
10 20 rmoveto # Pushes 2 operands, consumes 2 → neutral
|
|
1796
2002
|
----
|
|
1797
|
-
|
|
2003
|
+
+
|
|
2004
|
+
.Example Non-Neutral Pattern
|
|
2005
|
+
[source]
|
|
1798
2006
|
----
|
|
1799
|
-
|
|
1800
|
-
.Extract fonts from collection
|
|
1801
|
-
[source,shell]
|
|
2007
|
+
10 20 add # Pushes 2, consumes 2, produces 1 → NOT neutral
|
|
1802
2008
|
----
|
|
1803
|
-
# Extract all fonts from collection
|
|
1804
|
-
$ fontisan unpack family.ttc --output-dir extracted/
|
|
1805
2009
|
|
|
1806
|
-
|
|
1807
|
-
Input: family.ttc
|
|
1808
|
-
Output directory: extracted/
|
|
1809
|
-
Fonts extracted: 3/3
|
|
1810
|
-
- font1.ttf (89.2 KB)
|
|
1811
|
-
- font2.ttf (89.2 KB)
|
|
1812
|
-
- font3.ttf (67.4 KB)
|
|
2010
|
+
==== When to use stack-aware mode
|
|
1813
2011
|
|
|
1814
|
-
|
|
1815
|
-
$ fontisan unpack family.ttc --output-dir extracted/ --font-index 0 --format woff2
|
|
1816
|
-
----
|
|
2012
|
+
**Recommended for**:
|
|
1817
2013
|
|
|
1818
|
-
|
|
2014
|
+
* Production font conversion where reliability is critical
|
|
2015
|
+
* Fonts that will undergo further processing
|
|
2016
|
+
* Web fonts where correctness matters more than minimal size
|
|
2017
|
+
* Situations where testing/validation is limited
|
|
1819
2018
|
|
|
1820
|
-
|
|
1821
|
-
[source,shell]
|
|
1822
|
-
----
|
|
1823
|
-
$ fontisan convert font.ttf --to woff2 --output font.woff2
|
|
2019
|
+
**Normal mode acceptable for**:
|
|
1824
2020
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
Output: font.woff2 (89.2 KB)
|
|
1829
|
-
----
|
|
2021
|
+
* Development/testing environments
|
|
2022
|
+
* When full validation will be performed post-conversion
|
|
2023
|
+
* Maximum compression is priority over guaranteed safety
|
|
1830
2024
|
|
|
1831
|
-
|
|
1832
|
-
[source,shell]
|
|
1833
|
-
----
|
|
1834
|
-
$ fontisan convert font.ttf --to svg --output font.svg
|
|
2025
|
+
==== Using the Ruby API
|
|
1835
2026
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2027
|
+
.Basic optimization
|
|
2028
|
+
[example]
|
|
2029
|
+
====
|
|
2030
|
+
[source,ruby]
|
|
1840
2031
|
----
|
|
2032
|
+
require 'fontisan'
|
|
1841
2033
|
|
|
1842
|
-
|
|
2034
|
+
# Load TrueType font
|
|
2035
|
+
font = Fontisan::FontLoader.load('input.ttf')
|
|
1843
2036
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2037
|
+
# Convert with optimization
|
|
2038
|
+
converter = Fontisan::Converters::OutlineConverter.new
|
|
2039
|
+
tables = converter.convert(font, {
|
|
2040
|
+
target_format: :otf,
|
|
2041
|
+
optimize_subroutines: true
|
|
2042
|
+
})
|
|
1848
2043
|
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
Profile: pdf
|
|
1855
|
-
Size: 12.4 KB
|
|
1856
|
-
----
|
|
2044
|
+
# Access optimization results
|
|
2045
|
+
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
2046
|
+
puts "Patterns found: #{optimization[:pattern_count]}"
|
|
2047
|
+
puts "Selected: #{optimization[:selected_count]}"
|
|
2048
|
+
puts "Savings: #{optimization[:savings]} bytes"
|
|
1857
2049
|
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2050
|
+
# Write output
|
|
2051
|
+
Fontisan::FontWriter.write_to_file(
|
|
2052
|
+
tables,
|
|
2053
|
+
'output.otf',
|
|
2054
|
+
sfnt_version: 0x4F54544F
|
|
2055
|
+
)
|
|
1862
2056
|
----
|
|
2057
|
+
====
|
|
1863
2058
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
[source,
|
|
2059
|
+
.Custom optimization parameters
|
|
2060
|
+
[example]
|
|
2061
|
+
====
|
|
2062
|
+
[source,ruby]
|
|
1868
2063
|
----
|
|
1869
|
-
|
|
1870
|
-
$ fontisan validate font.ttf
|
|
2064
|
+
require 'fontisan'
|
|
1871
2065
|
|
|
1872
|
-
|
|
1873
|
-
|
|
2066
|
+
font = Fontisan::FontLoader.load('input.ttf')
|
|
2067
|
+
converter = Fontisan::Converters::OutlineConverter.new
|
|
2068
|
+
|
|
2069
|
+
# Fine-tune optimization
|
|
2070
|
+
tables = converter.convert(font, {
|
|
2071
|
+
target_format: :otf,
|
|
2072
|
+
optimize_subroutines: true,
|
|
2073
|
+
min_pattern_length: 15,
|
|
2074
|
+
max_subroutines: 5000,
|
|
2075
|
+
optimize_ordering: true,
|
|
2076
|
+
verbose: true
|
|
2077
|
+
})
|
|
1874
2078
|
|
|
1875
|
-
#
|
|
1876
|
-
|
|
2079
|
+
# Analyze results
|
|
2080
|
+
optimization = tables.instance_variable_get(:@subroutine_optimization)
|
|
2081
|
+
if optimization[:selected_count] > 0
|
|
2082
|
+
efficiency = optimization[:savings].to_f / optimization[:selected_count]
|
|
2083
|
+
puts "Average savings per subroutine: #{efficiency.round(2)} bytes"
|
|
2084
|
+
end
|
|
1877
2085
|
----
|
|
2086
|
+
====
|
|
1878
2087
|
|
|
1879
|
-
==== Variable Font Instances
|
|
1880
2088
|
|
|
1881
|
-
|
|
1882
|
-
[source,shell]
|
|
1883
|
-
----
|
|
1884
|
-
# Create bold instance
|
|
1885
|
-
$ fontisan instance variable.ttf --wght=700 --output bold.ttf
|
|
2089
|
+
== Round-Trip validation
|
|
1886
2090
|
|
|
1887
|
-
|
|
1888
|
-
$ fontisan instance variable.ttf --named-instance="Bold" --output bold.ttf
|
|
2091
|
+
=== General
|
|
1889
2092
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
----
|
|
2093
|
+
Fontisan ensures high-fidelity font conversion through comprehensive round-trip
|
|
2094
|
+
validation.
|
|
1893
2095
|
|
|
1894
|
-
|
|
2096
|
+
When converting between TrueType (TTF) and OpenType/CFF (OTF) formats, the
|
|
2097
|
+
validation system verifies that glyph geometry is preserved accurately.
|
|
1895
2098
|
|
|
1896
|
-
|
|
1897
|
-
[source,shell]
|
|
1898
|
-
----
|
|
1899
|
-
$ fontisan dump-table font.ttf name > name_table.bin
|
|
1900
|
-
$ fontisan dump-table font.ttf GPOS > gpos_table.bin
|
|
1901
|
-
----
|
|
2099
|
+
Key validation features:
|
|
1902
2100
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
$ fontisan tables font.ttf --format yaml
|
|
2101
|
+
* **Command-Level Precision**: Validates individual drawing commands (move, line, curve)
|
|
2102
|
+
* **Coordinate Tolerance**: Accepts ±2 pixels tolerance for rounding during conversion
|
|
2103
|
+
* **Format-Aware Comparison**: Handles differences between TrueType quadratic and CFF cubic curves
|
|
2104
|
+
* **Closepath Handling**: Smart detection of geometrically closed vs open contours
|
|
1908
2105
|
|
|
1909
|
-
|
|
1910
|
-
$ fontisan variable font.ttf
|
|
2106
|
+
=== Technical details
|
|
1911
2107
|
|
|
1912
|
-
|
|
1913
|
-
$ fontisan optical-size font.ttf
|
|
1914
|
-
----
|
|
2108
|
+
Round-trip validation works by:
|
|
1915
2109
|
|
|
1916
|
-
|
|
1917
|
-
|
|
2110
|
+
[source]
|
|
2111
|
+
----
|
|
2112
|
+
Original TTF → Convert to CFF → Extract CFF → Compare Geometry
|
|
2113
|
+
(Input) (Encode) (Decode) (Validate)
|
|
1918
2114
|
----
|
|
1919
|
-
# Basic font info
|
|
1920
|
-
$ fontisan info font.ttf
|
|
1921
2115
|
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2116
|
+
**Validation process**:
|
|
2117
|
+
|
|
2118
|
+
. Extract glyph outlines from original TTF
|
|
2119
|
+
. Convert to CFF format with CharString encoding
|
|
2120
|
+
. Parse CFF CharStrings back to universal outlines
|
|
2121
|
+
. Compare geometry with coordinate tolerance (±2 pixels)
|
|
1925
2122
|
|
|
1926
|
-
|
|
1927
|
-
$ fontisan unicode font.ttf
|
|
2123
|
+
**Format differences handled**:
|
|
1928
2124
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2125
|
+
* **Closepath**: CFF has implicit closepath, TTF has explicit
|
|
2126
|
+
* **Curve types**: TrueType quadratic (`:quad_to`) vs CFF cubic (`:curve_to`)
|
|
2127
|
+
* **Coordinate rounding**: Different number encoding causes minor differences
|
|
1932
2128
|
|
|
2129
|
+
**Validation criteria**: Geometry Match:
|
|
2130
|
+
. Same bounding box (±2 pixel tolerance)
|
|
2131
|
+
. Same number of path commands (excluding closepath)
|
|
2132
|
+
. Same endpoint coordinates for curves (±2 pixels)
|
|
2133
|
+
. Quadratic→cubic conversion accepted
|
|
1933
2134
|
|
|
1934
2135
|
|
|
1935
|
-
==
|
|
2136
|
+
== Universal outline model
|
|
1936
2137
|
|
|
1937
2138
|
=== General
|
|
1938
2139
|
|
|
1939
|
-
Universal Outline Model (UOM) is based on a self-stable algorithm
|
|
1940
|
-
soft glyph contours to outline format used in all tools of
|
|
1941
|
-
ability allows easy modeling of import glyphs from one font
|
|
1942
|
-
TrueType (TTF, OTF binaries), converting glyph elements into any font
|
|
2140
|
+
The Fontisan Universal Outline Model (UOM) is based on a self-stable algorithm
|
|
2141
|
+
for converting soft glyph contours to outline format used in all tools of
|
|
2142
|
+
Fontisan. This ability allows easy modeling of import glyphs from one font
|
|
2143
|
+
format TrueType (TTF, OTF binaries), converting glyph elements into any font
|
|
1943
2144
|
format, TrueType for example.
|
|
1944
2145
|
|
|
1945
2146
|
=== Locker
|
|
1946
2147
|
|
|
1947
|
-
Locker is
|
|
1948
|
-
|
|
1949
|
-
|
|
2148
|
+
Locker is an object-oriented model for storing imported outlines and glyphs.
|
|
2149
|
+
Storage is based on monotonic spirals computed based on 2D points and curves.
|
|
2150
|
+
Invisible converting from TrueType, CFF Opentype and ColorGlyph formats.
|
|
1950
2151
|
|
|
1951
2152
|
=== Translator
|
|
1952
2153
|
|
|
1953
|
-
Translation
|
|
1954
|
-
includes PostScript
|
|
2154
|
+
Translation is an object-oriented model for converting from and to PostScript
|
|
2155
|
+
custom CFF charset. New encoding/decoding includes PostScript Type 2/3/composite
|
|
2156
|
+
Loron.
|
|
1955
2157
|
|
|
1956
2158
|
=== ColorGlyph
|
|
1957
2159
|
|
|
@@ -1963,10 +2165,10 @@ combined with TrueType outlines.
|
|
|
1963
2165
|
|
|
1964
2166
|
=== Universal fonts
|
|
1965
2167
|
|
|
1966
|
-
Fontisan can
|
|
2168
|
+
Fontisan can:
|
|
1967
2169
|
|
|
1968
2170
|
* Import TrueType contours into Universal Outline Model (UOM)
|
|
1969
|
-
* Operate UOM outlines including transformations, serialization (save)
|
|
2171
|
+
* Operate UOM outlines including transformations, serialization (save)
|
|
1970
2172
|
* Select and convert all UOM contours to TTF/OTF
|
|
1971
2173
|
* Cleaning
|
|
1972
2174
|
* Improve
|
|
@@ -1978,26 +2180,26 @@ Fontisan can now:
|
|
|
1978
2180
|
|
|
1979
2181
|
=== Universal glyphs
|
|
1980
2182
|
|
|
1981
|
-
Fontisan can
|
|
2183
|
+
Fontisan can:
|
|
1982
2184
|
|
|
1983
|
-
* Use Universal Outline Model (UOM) for TrueType contours and CFF color glyphs
|
|
1984
|
-
* Repository for investor-defined fonts
|
|
1985
|
-
* Custom Unicode assignments, rewriting Unicode configurations
|
|
2185
|
+
* Use Universal Outline Model (UOM) for TrueType contours and CFF color glyphs
|
|
2186
|
+
* Repository for investor-defined fonts
|
|
2187
|
+
* Custom Unicode assignments, rewriting Unicode configurations
|
|
1986
2188
|
* Saving and import outlines, including TrueType and OTF/CFF
|
|
1987
2189
|
* Rendering for advanced font types
|
|
1988
2190
|
* Universal layer stacking for advanced color glyph combinations
|
|
1989
2191
|
|
|
1990
2192
|
=== Universal color layers
|
|
1991
2193
|
|
|
1992
|
-
(Converted
|
|
2194
|
+
(Converted TTF, OTF files)
|
|
1993
2195
|
|
|
1994
|
-
Fontisan can
|
|
2196
|
+
Fontisan can:
|
|
1995
2197
|
|
|
1996
|
-
* Import embedded TTF/OTF color layers
|
|
1997
|
-
* Assembler from individual TTF/OTF slices
|
|
1998
|
-
* Advanced managing layer maps in TTF color (CFF) fonts
|
|
1999
|
-
*
|
|
2000
|
-
* Managing Gray/Overprint/Color-Full image comps and layer
|
|
2198
|
+
* Import embedded TTF/OTF color layers
|
|
2199
|
+
* Assembler from individual TTF/OTF slices
|
|
2200
|
+
* Advanced managing layer maps in TTF color (CFF) fonts
|
|
2201
|
+
* Advanced color layer blending style management
|
|
2202
|
+
* Managing Gray/Overprint/Color-Full image comps and layer conversion
|
|
2001
2203
|
* Strategy management for smart vector combos from raster
|
|
2002
2204
|
* Importing and generation PNG block ruler layers
|
|
2003
2205
|
|