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
@@ -0,0 +1,599 @@
1
+ = Variable Font Operations Guide
2
+ :toc:
3
+ :toclevels: 3
4
+
5
+ == Purpose
6
+
7
+ This guide provides comprehensive documentation for working with variable fonts in Fontisan, covering all supported operations including instance generation, format conversion, SVG generation, collection management, and validation.
8
+
9
+ == Variable Font Concepts
10
+
11
+ === General
12
+
13
+ Variable fonts are OpenType fonts that contain multiple variations of a typeface in a single file. Instead of having separate font files for different weights, widths, or styles, a variable font uses variation axes to interpolate between different design extremes.
14
+
15
+ Key components:
16
+
17
+ fvar table:: Defines the variation axes and named instances
18
+ gvar table:: (TrueType) Contains glyph variation data as delta tuples
19
+ CFF2 table:: (OpenType) Contains variation data as blend operators
20
+ avar table:: (Optional) Defines axis value mappings for non-linear interpolation
21
+ STAT table:: (Optional) Provides style attributes and axis value names
22
+ HVAR/VVAR/MVAR:: (Optional) Metrics variation tables
23
+
24
+ === Variation Axes
25
+
26
+ Each axis represents a design dimension along which the font can vary:
27
+
28
+ Common registered axes::
29
+ * `wght` (Weight): 1-1000, typically 400 (Regular) to 700 (Bold)
30
+ * `wdth` (Width): 1-1000, typically 75 (Condensed) to 125 (Expanded)
31
+ * `slnt` (Slant): -90 to 90 degrees
32
+ * `ital` (Italic): 0 (Roman) or 1 (Italic)
33
+ * `opsz` (Optical Size): Point size for optical sizing
34
+
35
+ Custom axes:: Four-character tags starting with uppercase letter (e.g., `GRAD`)
36
+
37
+ === Named Instances
38
+
39
+ Variable fonts can define named instances - predefined points in the design space with specific names (e.g., "Bold", "Light", "Condensed Bold").
40
+
41
+ == Instance Generation
42
+
43
+ === General
44
+
45
+ Generate static font instances from variable fonts at specific variation coordinates.
46
+
47
+ === Using the CLI
48
+
49
+ ==== Generate instance at specific coordinates
50
+
51
+ [source,bash]
52
+ ----
53
+ # Generate bold instance
54
+ fontisan instance variable.ttf --wght 700 --output bold.ttf
55
+
56
+ # Generate condensed bold
57
+ fontisan instance variable.ttf --wght 700 --wdth 75 --output condensed-bold.ttf
58
+
59
+ # Generate with all axes
60
+ fontisan instance variable.ttf --wght 600 --wdth 90 --slnt -12 --output custom.ttf
61
+ ----
62
+
63
+ ==== Use named instances
64
+
65
+ [source,bash]
66
+ ----
67
+ # List available instances
68
+ fontisan instance variable.ttf --list-instances
69
+
70
+ # Generate using named instance
71
+ fontisan instance variable.ttf --named-instance 0 --output bold.ttf
72
+ fontisan instance variable.ttf --named-instance "Bold" --output bold.ttf
73
+ ----
74
+
75
+ ==== Output format options
76
+
77
+ [source,bash]
78
+ ----
79
+ # Generate as TrueType (default)
80
+ fontisan instance variable.ttf --wght 700 --output bold.ttf
81
+
82
+ # Generate as OpenType/CFF
83
+ fontisan instance variable.ttf --wght 700 --to otf --output bold.otf
84
+
85
+ # Generate as WOFF2
86
+ fontisan instance variable.ttf --wght 700 --to woff2 --output bold.woff2
87
+ ----
88
+
89
+ === Using the Ruby API
90
+
91
+ ==== Basic instance generation
92
+
93
+ [source,ruby]
94
+ ----
95
+ require 'fontisan'
96
+
97
+ # Load variable font
98
+ font = Fontisan::FontLoader.load('variable.ttf')
99
+
100
+ # Generate instance at specific coordinates
101
+ writer = Fontisan::Variation::InstanceWriter.new(font)
102
+ instance_font = writer.generate_instance(wght: 700)
103
+
104
+ # Write to file
105
+ Fontisan::FontWriter.write_to_file(
106
+ instance_font.table_data,
107
+ 'bold.ttf',
108
+ sfnt_version: 0x00010000
109
+ )
110
+ ----
111
+
112
+ ==== Generate with multiple axes
113
+
114
+ [source,ruby]
115
+ ----
116
+ # Generate instance with multiple axis values
117
+ instance_font = writer.generate_instance(
118
+ wght: 700,
119
+ wdth: 75,
120
+ slnt: -8
121
+ )
122
+ ----
123
+
124
+ ==== Use named instances
125
+
126
+ [source,ruby]
127
+ ----
128
+ # Get named instance information
129
+ fvar = font.table('fvar')
130
+ instance = fvar.instances[0]
131
+
132
+ # Extract coordinates from named instance
133
+ coordinates = instance[:coordinates]
134
+ axis_tags = fvar.axes.map(&:axis_tag)
135
+ axis_values = Hash[axis_tags.zip(coordinates)]
136
+
137
+ # Generate instance
138
+ instance_font = writer.generate_instance(axis_values)
139
+ ----
140
+
141
+ == Format Conversion
142
+
143
+ === General
144
+
145
+ Convert variable fonts between formats while preserving or converting variation data.
146
+
147
+ === Variation Preservation Strategy
148
+
149
+ Compatible formats (same outline)::
150
+ * Variable TTF ↔ Variable TTF/WOFF/WOFF2: All variation tables preserved
151
+ * Variable OTF ↔ Variable OTF/WOFF/WOFF2: All variation tables preserved
152
+
153
+ Convertible formats (different outline)::
154
+ * Variable TTF ↔ Variable OTF: Common tables preserved (fvar, avar, STAT, metrics)
155
+ * Outline-specific tables require conversion (gvar ↔ CFF2 blend)
156
+
157
+ Unsupported formats::
158
+ * Variable fonts to SVG: Creates instance at default coordinates
159
+
160
+ === Using the CLI
161
+
162
+ ==== Convert with variation preservation
163
+
164
+ [source,bash]
165
+ ----
166
+ # Variable TTF to WOFF2 (preserves all variation)
167
+ fontisan convert variable.ttf --to woff2 --output variable.woff2
168
+
169
+ # Variable OTF to WOFF2 (preserves all variation)
170
+ fontisan convert variable.otf --to woff2 --output variable.woff2
171
+ ----
172
+
173
+ ==== Convert between outline formats
174
+
175
+ [source,bash]
176
+ ----
177
+ # Variable TTF to OTF (preserves common variation tables)
178
+ fontisan convert variable.ttf --to otf --output variable.otf
179
+
180
+ # Shows warning about gvar → CFF2 conversion not yet implemented
181
+ # Preserves: fvar, avar, STAT, HVAR, VVAR, MVAR
182
+ # Does not preserve: gvar (requires conversion to CFF2 blend operators)
183
+ ----
184
+
185
+ ==== Create static font from variable
186
+
187
+ [source,bash]
188
+ ----
189
+ # Remove all variation data
190
+ fontisan convert variable.ttf --to ttf --output static.ttf --no-preserve-variation
191
+
192
+ # Creates static font at default variation coordinates
193
+ ----
194
+
195
+ === Using the Ruby API
196
+
197
+ ==== Preserve variation during conversion
198
+
199
+ [source,ruby]
200
+ ----
201
+ require 'fontisan'
202
+
203
+ # Load variable font
204
+ font = Fontisan::FontLoader.load('variable.ttf')
205
+
206
+ # Convert with variation preservation (default)
207
+ converter = Fontisan::Converters::FormatConverter.new
208
+ tables = converter.convert(font, :woff2, preserve_variation: true)
209
+
210
+ # Write output
211
+ Fontisan::FontWriter.write_to_file(tables, 'variable.woff2')
212
+ ----
213
+
214
+ ==== Create static font
215
+
216
+ [source,ruby]
217
+ ----
218
+ # Convert without variation preservation
219
+ tables = converter.convert(font, :ttf, preserve_variation: false)
220
+ Fontisan::FontWriter.write_to_file(tables, 'static.ttf')
221
+ ----
222
+
223
+ == SVG Generation from Variable Fonts
224
+
225
+ === General
226
+
227
+ Generate SVG fonts from variable fonts at any point in the design space.
228
+
229
+ === Using the CLI
230
+
231
+ ==== Generate SVG at specific coordinates
232
+
233
+ [source,bash]
234
+ ----
235
+ # At default coordinates
236
+ fontisan convert variable.ttf --to svg --output default.svg
237
+
238
+ # At specific weight
239
+ fontisan convert variable.ttf --to svg --output bold.svg --wght 700
240
+
241
+ # With multiple axes
242
+ fontisan convert variable.ttf --to svg --output custom.svg --wght 700 --wdth 75
243
+ ----
244
+
245
+ ==== Use named instance for SVG
246
+
247
+ [source,bash]
248
+ ----
249
+ # Generate from named instance
250
+ fontisan convert variable.ttf --to svg --output bold.svg --instance-index 0
251
+ ----
252
+
253
+ === Using the Ruby API
254
+
255
+ ==== Basic SVG generation
256
+
257
+ [source,ruby]
258
+ ----
259
+ require 'fontisan'
260
+
261
+ # Load variable font
262
+ font = Fontisan::FontLoader.load('variable.ttf')
263
+
264
+ # Generate instance at specific coordinates
265
+ writer = Fontisan::Variation::InstanceWriter.new(font)
266
+ instance_font = writer.generate_instance(wght: 700)
267
+
268
+ # Convert to SVG
269
+ svg_generator = Fontisan::Variation::VariableSvgGenerator.new
270
+ svg_content = svg_generator.generate(instance_font)
271
+
272
+ # Write to file
273
+ File.write('bold.svg', svg_content)
274
+ ----
275
+
276
+ == Collection Operations
277
+
278
+ === General
279
+
280
+ Create variable font collections (TTC/OTC) with optimized table sharing.
281
+
282
+ === Validation Requirements
283
+
284
+ All variable fonts in a collection must:
285
+
286
+ . Have the same variation type (all TrueType or all CFF2)
287
+ . Have the same variation axes (same tags, in same order)
288
+ . Have compatible axis ranges (min/max/default values)
289
+
290
+ === Using the CLI
291
+
292
+ ==== Create variable font collection
293
+
294
+ [source,bash]
295
+ ----
296
+ # Merge variable TrueType fonts
297
+ fontisan pack var-regular.ttf var-bold.ttf var-italic.ttf --output family-var.ttc
298
+
299
+ # Merge variable OpenType fonts
300
+ fontisan pack var-regular.otf var-bold.otf --output family-var.otc --format otc
301
+ ----
302
+
303
+ ==== Extract from variable collection
304
+
305
+ [source,bash]
306
+ ----
307
+ # Extract all fonts
308
+ fontisan unpack family-var.ttc --output-dir extracted/
309
+
310
+ # Extract specific font
311
+ fontisan unpack family-var.ttc --font-index 0 --output extracted/regular.ttf
312
+ ----
313
+
314
+ === Using the Ruby API
315
+
316
+ ==== Build variable font collection
317
+
318
+ [source,ruby]
319
+ ----
320
+ require 'fontisan'
321
+
322
+ # Load variable fonts
323
+ fonts = [
324
+ Fontisan::FontLoader.load('var-regular.ttf'),
325
+ Fontisan::FontLoader.load('var-bold.ttf'),
326
+ Fontisan::FontLoader.load('var-italic.ttf')
327
+ ]
328
+
329
+ # Create collection
330
+ builder = Fontisan::Collection::Builder.new(fonts, format: :ttc)
331
+ builder.validate! # Validates variation compatibility
332
+ result = builder.build_to_file('family-var.ttc')
333
+
334
+ puts "Collection created: #{result[:output_path]}"
335
+ puts "Fonts: #{result[:num_fonts]}"
336
+ puts "Space saved: #{result[:space_savings]} bytes"
337
+ ----
338
+
339
+ === Table Sharing Strategy
340
+
341
+ Common variation tables (shared if identical)::
342
+ * fvar (Font Variations)
343
+ * avar (Axis Variations)
344
+ * STAT (Style Attributes)
345
+ * HVAR (Horizontal Metrics Variations)
346
+ * VVAR (Vertical Metrics Variations)
347
+ * MVAR (Metrics Variations)
348
+
349
+ Font-specific tables (always separate)::
350
+ * gvar (Glyph Variations - TrueType)
351
+ * CFF2 (with blend operators - OpenType)
352
+
353
+ == Validation
354
+
355
+ === General
356
+
357
+ Validate variable font structure, axes, instances, and variation tables.
358
+
359
+ === Using the CLI
360
+
361
+ ==== Basic validation
362
+
363
+ [source,bash]
364
+ ----
365
+ # Validate variable font
366
+ fontisan validate variable.ttf
367
+
368
+ # Verbose output
369
+ fontisan validate variable.ttf --verbose
370
+ ----
371
+
372
+ === Validation Checks
373
+
374
+ The validator performs the following checks:
375
+
376
+ ==== fvar Table Structure
377
+
378
+ * Axes are defined
379
+ * Axis count matches actual number of axes
380
+ * Each axis has valid min/max/default values
381
+ * Axis tags are valid (4 ASCII letters)
382
+
383
+ ==== Axis Validation
384
+
385
+ * Minimum value ≤ maximum value
386
+ * Default value within range [min, max]
387
+ * Axis tags follow naming conventions
388
+
389
+ ==== Instance Validation
390
+
391
+ * Coordinate count matches axis count
392
+ * All coordinates within axis ranges
393
+ * Named instances have valid data
394
+
395
+ ==== Variation Table Consistency
396
+
397
+ * TrueType variable fonts have gvar table
398
+ * CFF variable fonts have CFF2 table
399
+ * Font doesn't have both gvar and CFF2 (incompatible)
400
+
401
+ === Using the Ruby API
402
+
403
+ ==== Validate variable font
404
+
405
+ [source,ruby]
406
+ ----
407
+ require 'fontisan'
408
+ require 'fontisan/validation/variable_font_validator'
409
+
410
+ # Load font
411
+ font = Fontisan::FontLoader.load('variable.ttf')
412
+
413
+ # Validate
414
+ validator = Fontisan::Validation::VariableFontValidator.new(font)
415
+ errors = validator.validate
416
+
417
+ if errors.empty?
418
+ puts "✓ Variable font is valid"
419
+ else
420
+ puts "✗ Found #{errors.length} errors:"
421
+ errors.each { |err| puts " - #{err}" }
422
+ end
423
+ ----
424
+
425
+ == Performance Considerations
426
+
427
+ === Instance Generation
428
+
429
+ * Instance generation creates a new static font with updated tables
430
+ * Performance: ~100-500ms for typical variable fonts
431
+ * Memory: Proportional to font size (usually 1-5 MB)
432
+
433
+ === Format Conversion
434
+
435
+ * Variation-preserving conversions are fast (table copying)
436
+ * Outline conversion (TTF ↔ OTF) is slower (glyph processing)
437
+ * Optimization: Use batch processing for multiple conversions
438
+
439
+ === Collection Building
440
+
441
+ * Table deduplication provides significant space savings
442
+ * Analysis overhead: ~50-200ms per font
443
+ * Recommended for font families with 3+ fonts
444
+
445
+ == Troubleshooting
446
+
447
+ === Common Issues
448
+
449
+ ==== Error: Cannot mix TrueType and CFF2 variable fonts
450
+
451
+ *Cause*: Attempting to create collection with mixed variation types
452
+
453
+ *Solution*: Separate fonts into different collections:
454
+
455
+ [source,bash]
456
+ ----
457
+ # Create separate collections
458
+ fontisan pack var-ttf1.ttf var-ttf2.ttf --output ttf-collection.ttc
459
+ fontisan pack var-otf1.otf var-otf2.otf --output otf-collection.otc
460
+ ----
461
+
462
+ ==== Error: Variable fonts have different axes
463
+
464
+ *Cause*: Fonts have incompatible axis definitions
465
+
466
+ *Solution*: Verify all fonts have same axes:
467
+
468
+ [source,bash]
469
+ ----
470
+ # Check axes for each font
471
+ fontisan variable font1.ttf
472
+ fontisan variable font2.ttf
473
+
474
+ # Ensure axes match: same tags, same order
475
+ ----
476
+
477
+ ==== Warning: Full variation conversion not yet implemented
478
+
479
+ *Cause*: Converting between TTF and OTF outline formats
480
+
481
+ *Impact*: Common variation tables preserved, but gvar/CFF2 not converted
482
+
483
+ *Workaround*: Generate instances instead of converting:
484
+
485
+ [source,bash]
486
+ ----
487
+ # Instead of: fontisan convert var.ttf --to otf
488
+ # Use: fontisan instance var.ttf --wght 400 --to otf --output regular.otf
489
+ ----
490
+
491
+ ==== Instance generation produces unexpected results
492
+
493
+ *Cause*: Invalid axis coordinates
494
+
495
+ *Solution*: Check valid axis ranges:
496
+
497
+ [source,bash]
498
+ ----
499
+ # List available axes and ranges
500
+ fontisan variable font.ttf
501
+
502
+ # Use coordinates within valid ranges
503
+ fontisan instance font.ttf --wght 600 # If wght range is 400-900
504
+ ----
505
+
506
+ == Best Practices
507
+
508
+ === Instance Generation
509
+
510
+ . List named instances before generating custom coordinates
511
+ . Validate coordinates are within axis ranges
512
+ . Use named instances when available for better compatibility
513
+ . Test generated instances before deployment
514
+
515
+ === Format Conversion
516
+
517
+ . Always validate fonts after conversion
518
+ . Use `--preserve-variation` explicitly for clarity
519
+ . Check warning messages for conversion limitations
520
+ . Test converted fonts in target environment
521
+
522
+ === Collection Building
523
+
524
+ . Validate individual fonts before packing
525
+ . Use `--analyze` flag to preview space savings
526
+ . Keep font variations consistent (same axes, ranges)
527
+ . Document shared vs. font-specific tables
528
+
529
+ === Validation
530
+
531
+ . Run validation before distribution
532
+ . Use `--verbose` mode during development
533
+ . Fix errors before warnings
534
+ . Validate after each operation (conversion, instance generation)
535
+
536
+ == API Reference
537
+
538
+ === InstanceWriter
539
+
540
+ [source,ruby]
541
+ ----
542
+ # Initialize with variable font
543
+ writer = Fontisan::Variation::InstanceWriter.new(font)
544
+
545
+ # Generate instance
546
+ instance = writer.generate_instance(axis_values)
547
+ # axis_values: Hash of axis_tag => value
548
+
549
+ # Returns: Font object with variation tables removed
550
+ ----
551
+
552
+ === VariableSvgGenerator
553
+
554
+ [source,ruby]
555
+ ----
556
+ # Initialize
557
+ generator = Fontisan::Variation::VariableSvgGenerator.new
558
+
559
+ # Generate SVG from font
560
+ svg_content = generator.generate(font)
561
+ # font: Can be variable or static font
562
+ # Returns: String containing SVG XML
563
+ ----
564
+
565
+ === VariableFontValidator
566
+
567
+ [source,ruby]
568
+ ----
569
+ # Initialize with font
570
+ validator = Fontisan::Validation::VariableFontValidator.new(font)
571
+
572
+ # Validate
573
+ errors = validator.validate
574
+ # Returns: Array of error message strings
575
+ ----
576
+
577
+ === Collection::Builder
578
+
579
+ [source,ruby]
580
+ ----
581
+ # Initialize with fonts
582
+ builder = Fontisan::Collection::Builder.new(fonts, options)
583
+ # options:
584
+ # format: :ttc or :otc
585
+ # optimize: true/false
586
+
587
+ # Validate before building
588
+ builder.validate!
589
+
590
+ # Build collection
591
+ result = builder.build_to_file(output_path)
592
+ # Returns: Hash with :binary, :space_savings, :statistics
593
+ ----
594
+
595
+ == See Also
596
+
597
+ * link:VARIABLE_FONTS_ARCHITECTURE.md[Variable Fonts Architecture]
598
+ * link:VARIABLE_FONT_GUIDE.md[Variable Font Development Guide]
599
+ * link:../README.adoc[Fontisan README]
data/lib/fontisan/cli.rb CHANGED
@@ -26,6 +26,9 @@ module Fontisan
26
26
  aliases: "-q"
27
27
 
28
28
  desc "info PATH", "Display font information"
29
+ option :brief, type: :boolean, default: false,
30
+ desc: "Brief mode - only essential info (5x faster, uses metadata loading)",
31
+ aliases: "-b"
29
32
  # Extract and display comprehensive font metadata.
30
33
  #
31
34
  # @param path [String] Path to the font file or collection
@@ -33,7 +36,7 @@ module Fontisan
33
36
  command = Commands::InfoCommand.new(path, options)
34
37
  info = command.run
35
38
  output_result(info) unless options[:quiet]
36
- rescue Errno::ENOENT => e
39
+ rescue Errno::ENOENT
37
40
  if options[:verbose]
38
41
  raise
39
42
  else
@@ -270,7 +273,10 @@ module Fontisan
270
273
 
271
274
  # Merge coordinates into options
272
275
  convert_options = options.to_h.dup
273
- convert_options[:instance_coordinates] = instance_coords if instance_coords.any?
276
+ if instance_coords.any?
277
+ convert_options[:instance_coordinates] =
278
+ instance_coords
279
+ end
274
280
 
275
281
  command = Commands::ConvertCommand.new(font_file, convert_options)
276
282
  command.run
@@ -348,7 +354,8 @@ module Fontisan
348
354
  option :verbose, type: :boolean, default: false,
349
355
  desc: "Show detailed validation information"
350
356
  def validate(font_file)
351
- command = Commands::ValidateCommand.new(font_file, verbose: options[:verbose])
357
+ command = Commands::ValidateCommand.new(font_file,
358
+ verbose: options[:verbose])
352
359
  exit command.run
353
360
  end
354
361
 
@@ -291,7 +291,8 @@ module Fontisan
291
291
  otf_count = variable_fonts.count { |f| f.has_table?("CFF2") }
292
292
 
293
293
  if ttf_count.positive? && otf_count.positive?
294
- raise Error, "Cannot mix TrueType and CFF2 variable fonts in collection"
294
+ raise Error,
295
+ "Cannot mix TrueType and CFF2 variable fonts in collection"
295
296
  end
296
297
  end
297
298
 
@@ -165,6 +165,7 @@ module Fontisan
165
165
  end
166
166
  end
167
167
 
168
+ # rubocop:disable Style/CombinableLoops
168
169
  # First, assign offsets to shared tables
169
170
  # Shared tables are stored once and referenced by multiple fonts
170
171
  canonical_tables.each do |canonical_id, info|
@@ -182,6 +183,7 @@ module Fontisan
182
183
  @offsets[:table_offsets][canonical_id] = current_offset
183
184
  current_offset = align_offset(current_offset + info[:size])
184
185
  end
186
+ # rubocop:enable Style/CombinableLoops
185
187
  end
186
188
 
187
189
  # Align offset to TABLE_ALIGNMENT boundary
@@ -82,13 +82,16 @@ module Fontisan
82
82
  end
83
83
  end
84
84
 
85
+ # Brief mode uses metadata loading for 5x faster parsing
86
+ mode = @options[:brief] ? LoadingModes::METADATA : (@options[:mode] || LoadingModes::FULL)
87
+
85
88
  # ConvertCommand and similar commands need all tables loaded upfront
86
89
  # Use mode and lazy from options, or sensible defaults
87
90
  FontLoader.load(
88
91
  @font_path,
89
92
  font_index: @options[:font_index] || 0,
90
- mode: @options[:mode] || LoadingModes::FULL,
91
- lazy: @options.key?(:lazy) ? @options[:lazy] : false
93
+ mode: mode,
94
+ lazy: @options.key?(:lazy) ? @options[:lazy] : false,
92
95
  )
93
96
  rescue Errno::ENOENT
94
97
  # Re-raise file not found as-is
@@ -89,7 +89,10 @@ module Fontisan
89
89
  # Add variation options if specified
90
90
  pipeline_options[:coordinates] = @coordinates if @coordinates
91
91
  pipeline_options[:instance_index] = @instance_index if @instance_index
92
- pipeline_options[:preserve_variation] = @preserve_variation unless @preserve_variation.nil?
92
+ unless @preserve_variation.nil?
93
+ pipeline_options[:preserve_variation] =
94
+ @preserve_variation
95
+ end
93
96
 
94
97
  # Add hint preservation option
95
98
  pipeline_options[:preserve_hints] = @preserve_hints if @preserve_hints
@@ -155,7 +158,8 @@ module Fontisan
155
158
  end
156
159
  coords
157
160
  rescue StandardError => e
158
- raise ArgumentError, "Invalid coordinates format '#{coord_string}': #{e.message}"
161
+ raise ArgumentError,
162
+ "Invalid coordinates format '#{coord_string}': #{e.message}"
159
163
  end
160
164
 
161
165
  # Validate command options