fontisan 0.2.0 → 0.2.2

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