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