fontisan 0.2.1 → 0.2.2

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