fontisan 0.2.1 → 0.2.3

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