fontisan 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +58 -392
  3. data/README.adoc +1509 -1430
  4. data/Rakefile +3 -2
  5. data/benchmark/variation_quick_bench.rb +4 -4
  6. data/docs/FONT_HINTING.adoc +562 -0
  7. data/docs/VARIABLE_FONT_OPERATIONS.adoc +599 -0
  8. data/lib/fontisan/base_collection.rb +296 -0
  9. data/lib/fontisan/cli.rb +10 -3
  10. data/lib/fontisan/collection/builder.rb +2 -1
  11. data/lib/fontisan/collection/offset_calculator.rb +2 -0
  12. data/lib/fontisan/commands/base_command.rb +5 -2
  13. data/lib/fontisan/commands/convert_command.rb +6 -2
  14. data/lib/fontisan/commands/info_command.rb +129 -5
  15. data/lib/fontisan/commands/instance_command.rb +8 -7
  16. data/lib/fontisan/commands/validate_command.rb +4 -1
  17. data/lib/fontisan/constants.rb +24 -24
  18. data/lib/fontisan/converters/format_converter.rb +8 -4
  19. data/lib/fontisan/converters/outline_converter.rb +21 -16
  20. data/lib/fontisan/converters/woff_writer.rb +8 -3
  21. data/lib/fontisan/font_loader.rb +120 -30
  22. data/lib/fontisan/font_writer.rb +2 -0
  23. data/lib/fontisan/formatters/text_formatter.rb +116 -19
  24. data/lib/fontisan/hints/hint_converter.rb +43 -47
  25. data/lib/fontisan/hints/hint_validator.rb +284 -0
  26. data/lib/fontisan/hints/postscript_hint_applier.rb +1 -3
  27. data/lib/fontisan/hints/postscript_hint_extractor.rb +78 -43
  28. data/lib/fontisan/hints/truetype_hint_extractor.rb +22 -26
  29. data/lib/fontisan/hints/truetype_instruction_analyzer.rb +261 -0
  30. data/lib/fontisan/hints/truetype_instruction_generator.rb +266 -0
  31. data/lib/fontisan/loading_modes.rb +4 -4
  32. data/lib/fontisan/models/collection_brief_info.rb +37 -0
  33. data/lib/fontisan/models/collection_info.rb +6 -1
  34. data/lib/fontisan/models/font_export.rb +2 -2
  35. data/lib/fontisan/models/font_info.rb +3 -30
  36. data/lib/fontisan/models/hint.rb +22 -23
  37. data/lib/fontisan/models/outline.rb +4 -1
  38. data/lib/fontisan/models/validation_report.rb +1 -1
  39. data/lib/fontisan/open_type_collection.rb +17 -220
  40. data/lib/fontisan/open_type_font.rb +3 -1
  41. data/lib/fontisan/optimizers/pattern_analyzer.rb +2 -1
  42. data/lib/fontisan/optimizers/subroutine_generator.rb +1 -1
  43. data/lib/fontisan/pipeline/output_writer.rb +8 -3
  44. data/lib/fontisan/pipeline/transformation_pipeline.rb +8 -3
  45. data/lib/fontisan/subset/table_subsetter.rb +5 -5
  46. data/lib/fontisan/tables/cff/charstring.rb +38 -12
  47. data/lib/fontisan/tables/cff/charstring_parser.rb +23 -11
  48. data/lib/fontisan/tables/cff/charstring_rebuilder.rb +14 -14
  49. data/lib/fontisan/tables/cff/dict_builder.rb +4 -1
  50. data/lib/fontisan/tables/cff/hint_operation_injector.rb +6 -4
  51. data/lib/fontisan/tables/cff/offset_recalculator.rb +1 -1
  52. data/lib/fontisan/tables/cff/private_dict_writer.rb +10 -4
  53. data/lib/fontisan/tables/cff/table_builder.rb +1 -1
  54. data/lib/fontisan/tables/cff2/charstring_parser.rb +14 -8
  55. data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +7 -6
  56. data/lib/fontisan/tables/cff2/region_matcher.rb +2 -2
  57. data/lib/fontisan/tables/cff2/table_builder.rb +26 -20
  58. data/lib/fontisan/tables/cff2/table_reader.rb +35 -33
  59. data/lib/fontisan/tables/cff2/variation_data_extractor.rb +2 -2
  60. data/lib/fontisan/tables/cff2.rb +1 -1
  61. data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +2 -1
  62. data/lib/fontisan/tables/glyf/curve_converter.rb +10 -4
  63. data/lib/fontisan/tables/glyf/glyph_builder.rb +27 -10
  64. data/lib/fontisan/tables/name.rb +4 -4
  65. data/lib/fontisan/true_type_collection.rb +29 -113
  66. data/lib/fontisan/true_type_font.rb +3 -1
  67. data/lib/fontisan/validation/checksum_validator.rb +2 -2
  68. data/lib/fontisan/variation/cache.rb +3 -1
  69. data/lib/fontisan/variation/converter.rb +2 -1
  70. data/lib/fontisan/variation/delta_applier.rb +2 -1
  71. data/lib/fontisan/variation/inspector.rb +2 -1
  72. data/lib/fontisan/variation/instance_generator.rb +2 -1
  73. data/lib/fontisan/variation/optimizer.rb +6 -3
  74. data/lib/fontisan/variation/subsetter.rb +32 -10
  75. data/lib/fontisan/variation/variation_preserver.rb +4 -1
  76. data/lib/fontisan/version.rb +1 -1
  77. data/lib/fontisan/woff2/glyf_transformer.rb +57 -30
  78. data/lib/fontisan/woff2_font.rb +31 -15
  79. data/lib/fontisan.rb +42 -2
  80. data/scripts/measure_optimization.rb +15 -7
  81. metadata +9 -2
data/Rakefile CHANGED
@@ -35,7 +35,8 @@ namespace :fixtures do
35
35
  FileUtils.mkdir_p(target_dir)
36
36
 
37
37
  # Create a manual temp file path - OS will clean up temp files automatically
38
- temp_path = File.join(Dir.tmpdir, "fontisan_#{name}_#{Process.pid}_#{rand(10000)}.zip")
38
+ temp_path = File.join(Dir.tmpdir,
39
+ "fontisan_#{name}_#{Process.pid}_#{rand(10000)}.zip")
39
40
 
40
41
  # Download using IO.copy_stream for better Windows compatibility
41
42
  URI.open(url, "rb") do |remote|
@@ -70,7 +71,7 @@ namespace :fixtures do
70
71
  end
71
72
  ensure
72
73
  # Explicitly close the zip file to release file handle on Windows
73
- zip_file.close if zip_file
74
+ zip_file&.close
74
75
  end
75
76
 
76
77
  # Temp file left in Dir.tmpdir - OS will clean it up automatically
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'benchmark'
4
- require 'fontisan'
3
+ require "benchmark"
4
+ require "fontisan"
5
5
 
6
6
  # Load variable font
7
- FONT_PATH = 'spec/fixtures/SourceSans3VF-Roman.otf'
7
+ FONT_PATH = "spec/fixtures/SourceSans3VF-Roman.otf"
8
8
 
9
9
  unless File.exist?(FONT_PATH)
10
10
  puts "Error: Test font not found at #{FONT_PATH}"
@@ -18,7 +18,7 @@ puts "Font: #{FONT_PATH}"
18
18
  puts
19
19
 
20
20
  # Test coordinates
21
- coords = 4.times.map { |i| { "wght" => 300 + i * 200 } }
21
+ coords = Array.new(4) { |i| { "wght" => 300 + i * 200 } }
22
22
  puts "Generating #{coords.size} instances"
23
23
  puts
24
24
 
@@ -0,0 +1,562 @@
1
+ = Font Hinting Support
2
+ :toc:
3
+ :toclevels: 3
4
+
5
+ == General
6
+
7
+ Fontisan provides comprehensive support for font hints, including extraction, conversion, validation, and preservation. Hints are rendering instructions embedded in fonts that improve appearance at small sizes and low resolutions by providing grid-fitting guidance to the rasterizer.
8
+
9
+ Fontisan supports two hinting systems:
10
+
11
+ **TrueType Instructions**:: Bytecode-based hints using instruction opcodes (SSW, SCVTCI, SSWCI, etc.) stored in prep, fpgm, and cvt tables
12
+
13
+ **PostScript Hints**:: Declarative hints using operators (hstem, vstem, hintmask) stored in CFF Private dictionaries
14
+
15
+ The hint conversion system enables bidirectional transformation between these formats, allowing fonts to maintain their rendering quality during TTF ↔ OTF conversion.
16
+
17
+ == Features
18
+
19
+ * **Bidirectional Conversion**: Convert hints between TrueType and PostScript formats
20
+ * **Hint Extraction**: Extract existing hints from TTF and OTF fonts
21
+ * **Hint Application**: Apply hints to fonts during format conversion
22
+ * **Validation**: Comprehensive validation of hint data for correctness
23
+ * **Round-Trip Preservation**: Maintain hint integrity during conversion cycles
24
+ * **Stack-Aware Analysis**: Intelligent parsing of TrueType instruction bytecode
25
+ * **CFF2 Support**: Variable font PostScript hint handling
26
+
27
+ == TrueType to PostScript Conversion
28
+
29
+ When converting from TrueType (TTF) to OpenType/CFF (OTF), Fontisan analyzes TrueType instructions and generates equivalent PostScript hint parameters.
30
+
31
+ === Conversion Process
32
+
33
+ [source]
34
+ ----
35
+ TrueType Font → Instruction Analysis → PostScript Parameters → CFF Table
36
+ (Input) (prep/fpgm/cvt) (Hint Dict) (Output)
37
+ ----
38
+
39
+ **Instruction Analysis**:
40
+
41
+ . Parse prep (Control Value Program) bytecode
42
+ . Analyze fpgm (Font Program) for complexity indicators
43
+ . Extract blue zones from CVT (Control Value Table) values
44
+ . Extract stem widths and alignment zones
45
+
46
+ **PostScript Parameters Generated**:
47
+
48
+ * `blue_scale`: Threshold for alignment zone application
49
+ * `std_hw`: Standard horizontal stem width
50
+ * `std_vw`: Standard vertical stem width
51
+ * `stem_snap_h`: Horizontal stem snap values
52
+ * `stem_snap_v`: Vertical stem snap values
53
+ * `blue_values`: Baseline and top alignment zones
54
+ * `other_blues`: Descender alignment zones
55
+
56
+ === TrueType Instructions Supported
57
+
58
+ [cols="1,2,3"]
59
+ |===
60
+ |Opcode |Name |Purpose
61
+
62
+ |`0x40` |NPUSHB |Push n bytes onto stack
63
+ |`0x41` |NPUSHW |Push n words onto stack
64
+ |`0xB0-0xB7` |PUSHB[0-7] |Push 1-8 bytes onto stack
65
+ |`0xB8-0xBF` |PUSHW[0-7] |Push 1-8 words onto stack
66
+ |`0x1D` |SCVTCI |Set CVT cut-in
67
+ |`0x1E` |SSWCI |Set single width cut-in
68
+ |`0x1F` |SSW |Set single width
69
+ |`0x44` |WCVTP |Write CVT in pixels
70
+ |`0x70` |WCVTF |Write CVT in FUnits
71
+ |===
72
+
73
+ == PostScript to TrueType Conversion
74
+
75
+ When converting from OpenType/CFF (OTF) to TrueType (TTF), Fontisan generates TrueType instruction bytecode from PostScript hint parameters.
76
+
77
+ === Conversion Process
78
+
79
+ [source]
80
+ ----
81
+ OTF Font → Parameter Extraction → Instruction Generation → TrueType Tables
82
+ (Input) (CFF Private Dict) (prep/fpgm/cvt) (Output)
83
+ ----
84
+
85
+ **Instruction Generation**:
86
+
87
+ . Generate prep program with CVT cut-in, single width settings
88
+ . Build CVT table with stem widths and blue zone values
89
+ . Create fpgm program (typically empty for converted fonts)
90
+ . Encode instructions using optimal PUSH opcodes
91
+
92
+ **Generated Tables**:
93
+
94
+ * `prep`: Control Value Program with hint setup instructions
95
+ * `cvt`: Control Value Table with stem widths and alignment zones
96
+ * `fpgm`: Font Program (empty for converted fonts)
97
+
98
+ === Instruction Encoding
99
+
100
+ The generator automatically selects the most efficient instruction encoding:
101
+
102
+ **Byte Values** (0-255):
103
+ [source]
104
+ ----
105
+ Value 17: PUSHB[0] 17 (2 bytes)
106
+ Values [10,20,30]: NPUSHB 3 10 20 30 (5 bytes)
107
+ ----
108
+
109
+ **Word Values** (256-65535):
110
+ [source]
111
+ ----
112
+ Value 300: PUSHW[0] 0x01 0x2C (3 bytes, big-endian)
113
+ Values [300,400]: NPUSHW 2 0x01 0x2C 0x01 0x90 (7 bytes)
114
+ ----
115
+
116
+ == Hint Validation
117
+
118
+ Fontisan includes comprehensive validation for both TrueType instructions and PostScript hint parameters.
119
+
120
+ === TrueType Instruction Validation
121
+
122
+ * **Bytecode Structure**: Validates instruction opcodes and parameters
123
+ * **Stack Operations**: Ensures proper stack depth management
124
+ * **Parameter Counts**: Verifies correct number of operands
125
+ * **Truncation Detection**: Identifies incomplete instructions
126
+ * **Stack Neutrality**: Checks if instruction sequences maintain stack balance
127
+
128
+ .Validate TrueType instructions
129
+ [example]
130
+ ====
131
+ [source,ruby]
132
+ ----
133
+ validator = Fontisan::Hints::HintValidator.new
134
+ instructions = [0xB0, 17, 0x1D].pack("C*") # PUSHB[0] 17, SCVTCI
135
+
136
+ result = validator.validate_truetype_instructions(instructions)
137
+ if result[:valid]
138
+ puts "Valid instructions"
139
+ else
140
+ puts "Errors: #{result[:errors]}"
141
+ end
142
+ ----
143
+ ====
144
+
145
+ === PostScript Hint Validation
146
+
147
+ * **Value Ranges**: Validates hint parameter bounds
148
+ * **Pair Validation**: Ensures blue zones are in pairs (even count)
149
+ * **Array Limits**: Enforces CFF specification limits:
150
+ - `blue_values`: Maximum 14 values (7 pairs)
151
+ - `other_blues`: Maximum 10 values (5 pairs)
152
+ - `stem_snap_h/v`: Maximum 12 values each
153
+ * **Positive Values**: Verifies stem widths are positive
154
+ * **Language Group**: Validates language_group is 0 (Latin) or 1 (CJK)
155
+
156
+ .Validate PostScript hints
157
+ [example]
158
+ ====
159
+ [source,ruby]
160
+ ----
161
+ validator = Fontisan::Hints::HintValidator.new
162
+ hints = {
163
+ blue_scale: 0.039625,
164
+ std_hw: 80,
165
+ blue_values: [-20, 0, 700, 720]
166
+ }
167
+
168
+ result = validator.validate_postscript_hints(hints)
169
+ if result[:valid]
170
+ puts "Valid PostScript hints"
171
+ else
172
+ result[:errors].each { |err| puts "Error: #{err}" }
173
+ end
174
+ ----
175
+ ====
176
+
177
+ == Round-Trip Conversion
178
+
179
+ Fontisan ensures hint integrity through round-trip conversion with validation:
180
+
181
+ [source]
182
+ ----
183
+ Original PS Hints → TrueType → PostScript → Validation
184
+ (Input) (Convert) (Convert) (Verify)
185
+ ----
186
+
187
+ **Round-Trip Guarantees**:
188
+
189
+ * CVT values preserved (sorted and deduplicated)
190
+ * Standard widths (std_hw, std_vw) maintained
191
+ * Blue zone values retained
192
+ * <10% loss tolerance for approximations
193
+
194
+ **Known Limitations**:
195
+
196
+ * CVT sorting may change positions (optimization trade-off)
197
+ * blue_scale not perfectly round-trippable (conversion approximation)
198
+ * Standard widths extracted from CVT[0] and CVT[1] positions
199
+
200
+ .Round-trip conversion example
201
+ [example]
202
+ ====
203
+ [source,ruby]
204
+ ----
205
+ require 'fontisan'
206
+
207
+ # Original PostScript parameters
208
+ original = {
209
+ blue_scale: 0.039625,
210
+ std_hw: 80,
211
+ std_vw: 90,
212
+ blue_values: [-20, 0, 700, 720]
213
+ }
214
+
215
+ # Convert PS → TT
216
+ generator = Fontisan::Hints::TrueTypeInstructionGenerator.new
217
+ tt_programs = generator.generate(original)
218
+
219
+ # Convert TT → PS
220
+ converter = Fontisan::Hints::HintConverter.new
221
+ recovered = converter.send(:convert_tt_programs_to_ps_dict,
222
+ tt_programs[:fpgm],
223
+ tt_programs[:prep],
224
+ tt_programs[:cvt])
225
+
226
+ # Verify preservation
227
+ puts "Original std_hw: #{original[:std_hw]}"
228
+ puts "Recovered std_hw: #{recovered[:std_hw]}"
229
+ puts "CVT contains: #{tt_programs[:cvt].include?(80)}"
230
+ ----
231
+ ====
232
+
233
+ == Using the CLI
234
+
235
+ === Convert with hint preservation
236
+
237
+ [source,bash]
238
+ ----
239
+ # TTF to OTF with hints
240
+ $ fontisan convert input.ttf --to otf --output output.otf
241
+
242
+ # OTF to TTF with hints
243
+ $ fontisan convert input.otf --to ttf --output output.ttf
244
+
245
+ # Validate hints after conversion
246
+ $ fontisan validate output.otf
247
+ ----
248
+
249
+ Hints are automatically extracted and converted during format conversion. No additional flags are required.
250
+
251
+ == Using the Ruby API
252
+
253
+ === Extract hints from a font
254
+
255
+ .Extract TrueType hints
256
+ [example]
257
+ ====
258
+ [source,ruby]
259
+ ----
260
+ require 'fontisan'
261
+
262
+ # Load TrueType font
263
+ font = Fontisan::FontLoader.load('input.ttf')
264
+
265
+ # Extract hints
266
+ extractor = Fontisan::Hints::TrueTypeHintExtractor.new
267
+ hint_set = extractor.extract_from_font(font)
268
+
269
+ # Access hint data
270
+ puts "Format: #{hint_set.format}"
271
+ puts "Has hints: #{hint_set.has_hints}"
272
+ puts "Font program size: #{hint_set.font_program&.bytesize}"
273
+ puts "Prep program size: #{hint_set.control_value_program&.bytesize}"
274
+ puts "CVT entries: #{hint_set.control_values&.length}"
275
+ ----
276
+ ====
277
+
278
+ .Extract PostScript hints
279
+ [example]
280
+ ====
281
+ [source,ruby]
282
+ ----
283
+ require 'fontisan'
284
+
285
+ # Load OpenType/CFF font
286
+ font = Fontisan::FontLoader.load('input.otf')
287
+
288
+ # Extract hints
289
+ extractor = Fontisan::Hints::PostScriptHintExtractor.new
290
+ hint_set = extractor.extract_from_font(font)
291
+
292
+ # Parse hint parameters
293
+ if hint_set.private_dict_hints
294
+ params = JSON.parse(hint_set.private_dict_hints)
295
+ puts "Standard H width: #{params['std_hw']}"
296
+ puts "Standard V width: #{params['std_vw']}"
297
+ puts "Blue values: #{params['blue_values']}"
298
+ end
299
+ ----
300
+ ====
301
+
302
+ === Convert hints between formats
303
+
304
+ .Convert TrueType to PostScript hints
305
+ [example]
306
+ ====
307
+ [source,ruby]
308
+ ----
309
+ require 'fontisan'
310
+
311
+ # Load TrueType font
312
+ font = Fontisan::FontLoader.load('input.ttf')
313
+
314
+ # Extract TrueType hints
315
+ extractor = Fontisan::Hints::TrueTypeHintExtractor.new
316
+ tt_hints = extractor.extract_from_font(font)
317
+
318
+ # Convert to PostScript
319
+ converter = Fontisan::Hints::HintConverter.new
320
+ ps_hints = converter.convert_hint_set(tt_hints, :postscript)
321
+
322
+ # Access PostScript parameters
323
+ if ps_hints.private_dict_hints
324
+ params = JSON.parse(ps_hints.private_dict_hints)
325
+ puts "Converted to PostScript:"
326
+ puts " std_hw: #{params['std_hw']}"
327
+ puts " std_vw: #{params['std_vw']}"
328
+ puts " blue_scale: #{params['blue_scale']}"
329
+ end
330
+ ----
331
+ ====
332
+
333
+ .Convert PostScript to TrueType hints
334
+ [example]
335
+ ====
336
+ [source,ruby]
337
+ ----
338
+ require 'fontisan'
339
+
340
+ # Load OpenType/CFF font
341
+ font = Fontisan::FontLoader.load('input.otf')
342
+
343
+ # Extract PostScript hints
344
+ extractor = Fontisan::Hints::PostScriptHintExtractor.new
345
+ ps_hints = extractor.extract_from_font(font)
346
+
347
+ # Convert to TrueType
348
+ converter = Fontisan::Hints::HintConverter.new
349
+ tt_hints = converter.convert_hint_set(ps_hints, :truetype)
350
+
351
+ # Access TrueType programs
352
+ puts "Converted to TrueType:"
353
+ puts " prep size: #{tt_hints.control_value_program&.bytesize} bytes"
354
+ puts " cvt entries: #{tt_hints.control_values&.length}"
355
+ puts " fpgm size: #{tt_hints.font_program&.bytesize} bytes"
356
+ ----
357
+ ====
358
+
359
+ == Technical Details
360
+
361
+ === Hint Conversion Pipeline
362
+
363
+ The conversion system uses a three-stage pipeline:
364
+
365
+ [source]
366
+ ----
367
+ Source Hints → Analysis/Generation → Target Hints → Validation
368
+ (Input) (Transformation) (Output) (Verify)
369
+ ----
370
+
371
+ **Stage 1: Analysis** (TrueType → PostScript):
372
+
373
+ . Parse TrueType instruction bytecode
374
+ . Track stack depth at each byte position
375
+ . Extract CVT values and operations
376
+ . Calculate PostScript equivalents
377
+
378
+ **Stage 2: Generation** (PostScript → TrueType):
379
+
380
+ . Select optimal instruction opcodes
381
+ . Build prep program with hint setup
382
+ . Generate CVT table with sorted values
383
+ . Ensure stack neutrality
384
+
385
+ **Stage 3: Validation**:
386
+
387
+ . Verify instruction bytecode structure
388
+ . Check PostScript parameter ranges
389
+ . Validate stack operations
390
+ . Confirm round-trip integrity
391
+
392
+ === TrueType Instruction Analyzer
393
+
394
+ The link:../lib/fontisan/hints/truetype_instruction_analyzer.rb[TrueTypeInstructionAnalyzer] analyzes TrueType programs to extract semantic hint information:
395
+
396
+ **Capabilities**:
397
+
398
+ * Parse 8 TrueType instruction opcodes
399
+ * Extract blue zones from CVT using heuristics
400
+ * Calculate complexity indicators for fpgm
401
+ * Support for scaled fonts (large UPM values)
402
+
403
+ **Heuristics**:
404
+
405
+ [source,ruby]
406
+ ----
407
+ # CVT value ranges for blue zone detection
408
+ Descender zone: -300 to -150 (scaled by UPM)
409
+ Baseline zone: -50 to +50
410
+ X-height zone: 450 to 600
411
+ Cap-height zone: 650 to 800 (wider for large UPM)
412
+ ----
413
+
414
+ === TrueType Instruction Generator
415
+
416
+ The link:../lib/fontisan/hints/truetype_instruction_generator.rb[TrueTypeInstructionGenerator] creates TrueType instruction bytecode from PostScript parameters:
417
+
418
+ **Features**:
419
+
420
+ * Optimal PUSH instruction selection
421
+ * Big-endian word encoding
422
+ * CVT table optimization (deduplicated and sorted)
423
+ * Stack-neutral instruction sequences
424
+
425
+ **CVT Generation**:
426
+
427
+ [source,ruby]
428
+ ----
429
+ CVT Table Structure:
430
+ 1. Standard widths (std_hw, std_vw)
431
+ 2. Stem snap values (horizontal, vertical)
432
+ 3. Blue zone values (baseline, cap-height)
433
+ 4. Other blues (descender zones)
434
+
435
+ Note: Sorted and deduplicated for optimization
436
+ ----
437
+
438
+ === Hint Validator
439
+
440
+ The link:../lib/fontisan/hints/hint_validator.rb[HintValidator] ensures hint data correctness:
441
+
442
+ **Validation Levels**:
443
+
444
+ * **Errors**: Critical issues that prevent font rendering
445
+ * **Warnings**: Non-critical issues that may affect quality
446
+
447
+ **Stack Neutrality Check**:
448
+
449
+ [source,ruby]
450
+ ----
451
+ Stack-neutral instruction sequence:
452
+ - Same stack depth at start and end
453
+ - No stack underflow during execution
454
+ - Consistent result regardless of initial stack
455
+ ----
456
+
457
+ == Architecture
458
+
459
+ === Class Hierarchy
460
+
461
+ [source]
462
+ ----
463
+ Fontisan::Hints
464
+ ├── TrueTypeInstructionAnalyzer # Parse TT bytecode → PS params
465
+ ├── TrueTypeInstructionGenerator # Generate TT bytecode from PS params
466
+ ├── TrueTypeHintExtractor # Extract TT hints from font
467
+ ├── TrueTypeHintApplier # Apply TT hints to font
468
+ ├── PostScriptHintExtractor # Extract PS hints from font
469
+ ├── PostScriptHintApplier # Apply PS hints to font
470
+ ├── HintConverter # Bidirectional conversion
471
+ └── HintValidator # Validate hint data
472
+ ----
473
+
474
+ === Data Flow
475
+
476
+ **TrueType → PostScript**:
477
+ [source]
478
+ ----
479
+ TTF Font
480
+
481
+ TrueTypeHintExtractor → HintSet (TrueType format)
482
+
483
+ HintConverter.convert_hint_set(:postscript)
484
+ ├── TrueTypeInstructionAnalyzer.analyze_prep(prep, cvt)
485
+ ├── TrueTypeInstructionAnalyzer.analyze_fpgm(fpgm)
486
+ └── TrueTypeInstructionAnalyzer.extract_blue_zones_from_cvt(cvt)
487
+
488
+ HintSet (PostScript format)
489
+
490
+ PostScriptHintApplier → OTF Font
491
+ ----
492
+
493
+ **PostScript → TrueType**:
494
+ [source]
495
+ ----
496
+ OTF Font
497
+
498
+ PostScriptHintExtractor → HintSet (PostScript format)
499
+
500
+ HintConverter.convert_hint_set(:truetype)
501
+ └── TrueTypeInstructionGenerator.generate(ps_params)
502
+ ├── generate_prep(ps_params) → Binary prep program
503
+ ├── generate_cvt(ps_params) → CVT array
504
+ └── generate_fpgm(ps_params) → Binary fpgm program
505
+
506
+ HintSet (TrueType format)
507
+
508
+ TrueTypeHintApplier → TTF Font
509
+ ----
510
+
511
+ == Current Limitations
512
+
513
+ === Not Yet Supported
514
+
515
+ * Per-glyph TrueType instructions (glyf table bytecode)
516
+ * PostScript hint masks for complex glyphs
517
+ * Hint substitution (CFF Type 2 hints)
518
+ * Complete CFF2 variable font hint preservation
519
+
520
+ === Known Issues
521
+
522
+ * **CVT Position Semantics**: CVT sorting changes position semantics (optimization trade-off)
523
+ * **blue_scale Approximation**: Conversion is approximate due to different scaling models
524
+ * **Instruction Coverage**: Some advanced TrueType instructions not yet supported
525
+ * **Heuristic Limitations**: Blue zone extraction uses heuristics that may not work for all fonts
526
+
527
+ == Future Enhancements
528
+
529
+ === Planned Features
530
+
531
+ * **Per-Glyph Hints**: Support for glyph-specific TrueType instructions
532
+ * **Hint Masks**: PostScript hintmask and cntrmask support
533
+ * **CFF2 Variable Fonts**: Complete blend operator preservation
534
+ * **Advanced Optimization**: Instruction size minimization
535
+ * **Hint Debugging**: Visualization and debugging tools
536
+
537
+ == Test Coverage
538
+
539
+ The hint system has comprehensive test coverage:
540
+
541
+ * **Total Tests**: 306 (100% passing)
542
+ * **Generator Tests**: 45
543
+ * **Validator Tests**: 48
544
+ * **Round-Trip Tests**: 16
545
+ * **Analyzer Tests**: 30
546
+ * **Integration Tests**: 167
547
+
548
+ Test files:
549
+
550
+ * link:../spec/fontisan/hints/truetype_instruction_generator_spec.rb[truetype_instruction_generator_spec.rb]
551
+ * link:../spec/fontisan/hints/truetype_instruction_analyzer_spec.rb[truetype_instruction_analyzer_spec.rb]
552
+ * link:../spec/fontisan/hints/hint_validator_spec.rb[hint_validator_spec.rb]
553
+ * link:../spec/fontisan/hints/hint_round_trip_spec.rb[hint_round_trip_spec.rb]
554
+ * link:../spec/fontisan/hints/hint_converter_spec.rb[hint_converter_spec.rb]
555
+ * link:../spec/fontisan/hints/hint_conversion_integration_spec.rb[hint_conversion_integration_spec.rb]
556
+
557
+ == References
558
+
559
+ * link:HINT_IMPLEMENTATION_STATUS.md[Hint Implementation Status]
560
+ * link:HINT_CONVERSION_LIMITATIONS.md[Hint Conversion Limitations]
561
+ * link:HINT_IMPLEMENTATION_CONTINUATION_PLAN.md[Implementation Continuation Plan]
562
+ * link:../lib/fontisan/hints/[Hints Source Code Directory]