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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +58 -392
- data/README.adoc +1509 -1430
- data/Rakefile +3 -2
- data/benchmark/variation_quick_bench.rb +4 -4
- data/docs/FONT_HINTING.adoc +562 -0
- data/docs/VARIABLE_FONT_OPERATIONS.adoc +599 -0
- data/lib/fontisan/base_collection.rb +296 -0
- data/lib/fontisan/cli.rb +10 -3
- data/lib/fontisan/collection/builder.rb +2 -1
- data/lib/fontisan/collection/offset_calculator.rb +2 -0
- data/lib/fontisan/commands/base_command.rb +5 -2
- data/lib/fontisan/commands/convert_command.rb +6 -2
- data/lib/fontisan/commands/info_command.rb +129 -5
- data/lib/fontisan/commands/instance_command.rb +8 -7
- data/lib/fontisan/commands/validate_command.rb +4 -1
- data/lib/fontisan/constants.rb +24 -24
- data/lib/fontisan/converters/format_converter.rb +8 -4
- data/lib/fontisan/converters/outline_converter.rb +21 -16
- data/lib/fontisan/converters/woff_writer.rb +8 -3
- data/lib/fontisan/font_loader.rb +120 -30
- data/lib/fontisan/font_writer.rb +2 -0
- data/lib/fontisan/formatters/text_formatter.rb +116 -19
- data/lib/fontisan/hints/hint_converter.rb +43 -47
- data/lib/fontisan/hints/hint_validator.rb +284 -0
- data/lib/fontisan/hints/postscript_hint_applier.rb +1 -3
- data/lib/fontisan/hints/postscript_hint_extractor.rb +78 -43
- data/lib/fontisan/hints/truetype_hint_extractor.rb +22 -26
- data/lib/fontisan/hints/truetype_instruction_analyzer.rb +261 -0
- data/lib/fontisan/hints/truetype_instruction_generator.rb +266 -0
- data/lib/fontisan/loading_modes.rb +4 -4
- data/lib/fontisan/models/collection_brief_info.rb +37 -0
- data/lib/fontisan/models/collection_info.rb +6 -1
- data/lib/fontisan/models/font_export.rb +2 -2
- data/lib/fontisan/models/font_info.rb +3 -30
- data/lib/fontisan/models/hint.rb +22 -23
- data/lib/fontisan/models/outline.rb +4 -1
- data/lib/fontisan/models/validation_report.rb +1 -1
- data/lib/fontisan/open_type_collection.rb +17 -220
- data/lib/fontisan/open_type_font.rb +3 -1
- data/lib/fontisan/optimizers/pattern_analyzer.rb +2 -1
- data/lib/fontisan/optimizers/subroutine_generator.rb +1 -1
- data/lib/fontisan/pipeline/output_writer.rb +8 -3
- data/lib/fontisan/pipeline/transformation_pipeline.rb +8 -3
- data/lib/fontisan/subset/table_subsetter.rb +5 -5
- data/lib/fontisan/tables/cff/charstring.rb +38 -12
- data/lib/fontisan/tables/cff/charstring_parser.rb +23 -11
- data/lib/fontisan/tables/cff/charstring_rebuilder.rb +14 -14
- data/lib/fontisan/tables/cff/dict_builder.rb +4 -1
- data/lib/fontisan/tables/cff/hint_operation_injector.rb +6 -4
- data/lib/fontisan/tables/cff/offset_recalculator.rb +1 -1
- data/lib/fontisan/tables/cff/private_dict_writer.rb +10 -4
- data/lib/fontisan/tables/cff/table_builder.rb +1 -1
- data/lib/fontisan/tables/cff2/charstring_parser.rb +14 -8
- data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +7 -6
- data/lib/fontisan/tables/cff2/region_matcher.rb +2 -2
- data/lib/fontisan/tables/cff2/table_builder.rb +26 -20
- data/lib/fontisan/tables/cff2/table_reader.rb +35 -33
- data/lib/fontisan/tables/cff2/variation_data_extractor.rb +2 -2
- data/lib/fontisan/tables/cff2.rb +1 -1
- data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +2 -1
- data/lib/fontisan/tables/glyf/curve_converter.rb +10 -4
- data/lib/fontisan/tables/glyf/glyph_builder.rb +27 -10
- data/lib/fontisan/tables/name.rb +4 -4
- data/lib/fontisan/true_type_collection.rb +29 -113
- data/lib/fontisan/true_type_font.rb +3 -1
- data/lib/fontisan/validation/checksum_validator.rb +2 -2
- data/lib/fontisan/variation/cache.rb +3 -1
- data/lib/fontisan/variation/converter.rb +2 -1
- data/lib/fontisan/variation/delta_applier.rb +2 -1
- data/lib/fontisan/variation/inspector.rb +2 -1
- data/lib/fontisan/variation/instance_generator.rb +2 -1
- data/lib/fontisan/variation/optimizer.rb +6 -3
- data/lib/fontisan/variation/subsetter.rb +32 -10
- data/lib/fontisan/variation/variation_preserver.rb +4 -1
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/glyf_transformer.rb +57 -30
- data/lib/fontisan/woff2_font.rb +31 -15
- data/lib/fontisan.rb +42 -2
- data/scripts/measure_optimization.rb +15 -7
- 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,
|
|
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
|
|
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
|
|
4
|
-
require
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require "fontisan"
|
|
5
5
|
|
|
6
6
|
# Load variable font
|
|
7
|
-
FONT_PATH =
|
|
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
|
|
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]
|