fontisan 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +58 -392
  3. data/README.adoc +1509 -1430
  4. data/Rakefile +3 -2
  5. data/benchmark/variation_quick_bench.rb +4 -4
  6. data/docs/FONT_HINTING.adoc +562 -0
  7. data/docs/VARIABLE_FONT_OPERATIONS.adoc +599 -0
  8. data/lib/fontisan/base_collection.rb +296 -0
  9. data/lib/fontisan/cli.rb +10 -3
  10. data/lib/fontisan/collection/builder.rb +2 -1
  11. data/lib/fontisan/collection/offset_calculator.rb +2 -0
  12. data/lib/fontisan/commands/base_command.rb +5 -2
  13. data/lib/fontisan/commands/convert_command.rb +6 -2
  14. data/lib/fontisan/commands/info_command.rb +129 -5
  15. data/lib/fontisan/commands/instance_command.rb +8 -7
  16. data/lib/fontisan/commands/validate_command.rb +4 -1
  17. data/lib/fontisan/constants.rb +24 -24
  18. data/lib/fontisan/converters/format_converter.rb +8 -4
  19. data/lib/fontisan/converters/outline_converter.rb +21 -16
  20. data/lib/fontisan/converters/woff_writer.rb +8 -3
  21. data/lib/fontisan/font_loader.rb +120 -30
  22. data/lib/fontisan/font_writer.rb +2 -0
  23. data/lib/fontisan/formatters/text_formatter.rb +116 -19
  24. data/lib/fontisan/hints/hint_converter.rb +43 -47
  25. data/lib/fontisan/hints/hint_validator.rb +284 -0
  26. data/lib/fontisan/hints/postscript_hint_applier.rb +1 -3
  27. data/lib/fontisan/hints/postscript_hint_extractor.rb +78 -43
  28. data/lib/fontisan/hints/truetype_hint_extractor.rb +22 -26
  29. data/lib/fontisan/hints/truetype_instruction_analyzer.rb +261 -0
  30. data/lib/fontisan/hints/truetype_instruction_generator.rb +266 -0
  31. data/lib/fontisan/loading_modes.rb +4 -4
  32. data/lib/fontisan/models/collection_brief_info.rb +37 -0
  33. data/lib/fontisan/models/collection_info.rb +6 -1
  34. data/lib/fontisan/models/font_export.rb +2 -2
  35. data/lib/fontisan/models/font_info.rb +3 -30
  36. data/lib/fontisan/models/hint.rb +22 -23
  37. data/lib/fontisan/models/outline.rb +4 -1
  38. data/lib/fontisan/models/validation_report.rb +1 -1
  39. data/lib/fontisan/open_type_collection.rb +17 -220
  40. data/lib/fontisan/open_type_font.rb +3 -1
  41. data/lib/fontisan/optimizers/pattern_analyzer.rb +2 -1
  42. data/lib/fontisan/optimizers/subroutine_generator.rb +1 -1
  43. data/lib/fontisan/pipeline/output_writer.rb +8 -3
  44. data/lib/fontisan/pipeline/transformation_pipeline.rb +8 -3
  45. data/lib/fontisan/subset/table_subsetter.rb +5 -5
  46. data/lib/fontisan/tables/cff/charstring.rb +38 -12
  47. data/lib/fontisan/tables/cff/charstring_parser.rb +23 -11
  48. data/lib/fontisan/tables/cff/charstring_rebuilder.rb +14 -14
  49. data/lib/fontisan/tables/cff/dict_builder.rb +4 -1
  50. data/lib/fontisan/tables/cff/hint_operation_injector.rb +6 -4
  51. data/lib/fontisan/tables/cff/offset_recalculator.rb +1 -1
  52. data/lib/fontisan/tables/cff/private_dict_writer.rb +10 -4
  53. data/lib/fontisan/tables/cff/table_builder.rb +1 -1
  54. data/lib/fontisan/tables/cff2/charstring_parser.rb +14 -8
  55. data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +7 -6
  56. data/lib/fontisan/tables/cff2/region_matcher.rb +2 -2
  57. data/lib/fontisan/tables/cff2/table_builder.rb +26 -20
  58. data/lib/fontisan/tables/cff2/table_reader.rb +35 -33
  59. data/lib/fontisan/tables/cff2/variation_data_extractor.rb +2 -2
  60. data/lib/fontisan/tables/cff2.rb +1 -1
  61. data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +2 -1
  62. data/lib/fontisan/tables/glyf/curve_converter.rb +10 -4
  63. data/lib/fontisan/tables/glyf/glyph_builder.rb +27 -10
  64. data/lib/fontisan/tables/name.rb +4 -4
  65. data/lib/fontisan/true_type_collection.rb +29 -113
  66. data/lib/fontisan/true_type_font.rb +3 -1
  67. data/lib/fontisan/validation/checksum_validator.rb +2 -2
  68. data/lib/fontisan/variation/cache.rb +3 -1
  69. data/lib/fontisan/variation/converter.rb +2 -1
  70. data/lib/fontisan/variation/delta_applier.rb +2 -1
  71. data/lib/fontisan/variation/inspector.rb +2 -1
  72. data/lib/fontisan/variation/instance_generator.rb +2 -1
  73. data/lib/fontisan/variation/optimizer.rb +6 -3
  74. data/lib/fontisan/variation/subsetter.rb +32 -10
  75. data/lib/fontisan/variation/variation_preserver.rb +4 -1
  76. data/lib/fontisan/version.rb +1 -1
  77. data/lib/fontisan/woff2/glyf_transformer.rb +57 -30
  78. data/lib/fontisan/woff2_font.rb +31 -15
  79. data/lib/fontisan.rb +42 -2
  80. data/scripts/measure_optimization.rb +15 -7
  81. metadata +9 -2
@@ -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]