fontisan 0.1.0 → 0.2.0

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +529 -65
  3. data/Gemfile +1 -0
  4. data/LICENSE +5 -1
  5. data/README.adoc +1301 -275
  6. data/Rakefile +27 -2
  7. data/benchmark/variation_quick_bench.rb +47 -0
  8. data/docs/EXTRACT_TTC_MIGRATION.md +549 -0
  9. data/fontisan.gemspec +4 -1
  10. data/lib/fontisan/binary/base_record.rb +22 -1
  11. data/lib/fontisan/cli.rb +309 -0
  12. data/lib/fontisan/collection/builder.rb +260 -0
  13. data/lib/fontisan/collection/offset_calculator.rb +227 -0
  14. data/lib/fontisan/collection/table_analyzer.rb +204 -0
  15. data/lib/fontisan/collection/table_deduplicator.rb +241 -0
  16. data/lib/fontisan/collection/writer.rb +306 -0
  17. data/lib/fontisan/commands/base_command.rb +8 -1
  18. data/lib/fontisan/commands/convert_command.rb +291 -0
  19. data/lib/fontisan/commands/export_command.rb +161 -0
  20. data/lib/fontisan/commands/info_command.rb +40 -6
  21. data/lib/fontisan/commands/instance_command.rb +295 -0
  22. data/lib/fontisan/commands/ls_command.rb +113 -0
  23. data/lib/fontisan/commands/pack_command.rb +241 -0
  24. data/lib/fontisan/commands/subset_command.rb +245 -0
  25. data/lib/fontisan/commands/unpack_command.rb +338 -0
  26. data/lib/fontisan/commands/validate_command.rb +178 -0
  27. data/lib/fontisan/commands/variable_command.rb +30 -1
  28. data/lib/fontisan/config/collection_settings.yml +56 -0
  29. data/lib/fontisan/config/conversion_matrix.yml +212 -0
  30. data/lib/fontisan/config/export_settings.yml +66 -0
  31. data/lib/fontisan/config/subset_profiles.yml +100 -0
  32. data/lib/fontisan/config/svg_settings.yml +60 -0
  33. data/lib/fontisan/config/validation_rules.yml +149 -0
  34. data/lib/fontisan/config/variable_settings.yml +99 -0
  35. data/lib/fontisan/config/woff2_settings.yml +77 -0
  36. data/lib/fontisan/constants.rb +69 -0
  37. data/lib/fontisan/converters/conversion_strategy.rb +96 -0
  38. data/lib/fontisan/converters/format_converter.rb +259 -0
  39. data/lib/fontisan/converters/outline_converter.rb +936 -0
  40. data/lib/fontisan/converters/svg_generator.rb +244 -0
  41. data/lib/fontisan/converters/table_copier.rb +117 -0
  42. data/lib/fontisan/converters/woff2_encoder.rb +416 -0
  43. data/lib/fontisan/converters/woff_writer.rb +391 -0
  44. data/lib/fontisan/error.rb +203 -0
  45. data/lib/fontisan/export/exporter.rb +262 -0
  46. data/lib/fontisan/export/table_serializer.rb +255 -0
  47. data/lib/fontisan/export/transformers/font_to_ttx.rb +172 -0
  48. data/lib/fontisan/export/transformers/head_transformer.rb +96 -0
  49. data/lib/fontisan/export/transformers/hhea_transformer.rb +59 -0
  50. data/lib/fontisan/export/transformers/maxp_transformer.rb +63 -0
  51. data/lib/fontisan/export/transformers/name_transformer.rb +63 -0
  52. data/lib/fontisan/export/transformers/os2_transformer.rb +121 -0
  53. data/lib/fontisan/export/transformers/post_transformer.rb +51 -0
  54. data/lib/fontisan/export/ttx_generator.rb +527 -0
  55. data/lib/fontisan/export/ttx_parser.rb +300 -0
  56. data/lib/fontisan/font_loader.rb +121 -12
  57. data/lib/fontisan/font_writer.rb +301 -0
  58. data/lib/fontisan/formatters/text_formatter.rb +102 -0
  59. data/lib/fontisan/glyph_accessor.rb +503 -0
  60. data/lib/fontisan/hints/hint_converter.rb +177 -0
  61. data/lib/fontisan/hints/postscript_hint_applier.rb +185 -0
  62. data/lib/fontisan/hints/postscript_hint_extractor.rb +254 -0
  63. data/lib/fontisan/hints/truetype_hint_applier.rb +71 -0
  64. data/lib/fontisan/hints/truetype_hint_extractor.rb +162 -0
  65. data/lib/fontisan/loading_modes.rb +113 -0
  66. data/lib/fontisan/metrics_calculator.rb +277 -0
  67. data/lib/fontisan/models/collection_font_summary.rb +52 -0
  68. data/lib/fontisan/models/collection_info.rb +76 -0
  69. data/lib/fontisan/models/collection_list_info.rb +37 -0
  70. data/lib/fontisan/models/font_export.rb +158 -0
  71. data/lib/fontisan/models/font_summary.rb +48 -0
  72. data/lib/fontisan/models/glyph_outline.rb +343 -0
  73. data/lib/fontisan/models/hint.rb +233 -0
  74. data/lib/fontisan/models/outline.rb +664 -0
  75. data/lib/fontisan/models/table_sharing_info.rb +40 -0
  76. data/lib/fontisan/models/ttx/glyph_order.rb +31 -0
  77. data/lib/fontisan/models/ttx/tables/binary_table.rb +67 -0
  78. data/lib/fontisan/models/ttx/tables/head_table.rb +74 -0
  79. data/lib/fontisan/models/ttx/tables/hhea_table.rb +74 -0
  80. data/lib/fontisan/models/ttx/tables/maxp_table.rb +55 -0
  81. data/lib/fontisan/models/ttx/tables/name_table.rb +45 -0
  82. data/lib/fontisan/models/ttx/tables/os2_table.rb +157 -0
  83. data/lib/fontisan/models/ttx/tables/post_table.rb +50 -0
  84. data/lib/fontisan/models/ttx/ttfont.rb +49 -0
  85. data/lib/fontisan/models/validation_report.rb +203 -0
  86. data/lib/fontisan/open_type_collection.rb +156 -2
  87. data/lib/fontisan/open_type_font.rb +296 -10
  88. data/lib/fontisan/optimizers/charstring_rewriter.rb +161 -0
  89. data/lib/fontisan/optimizers/pattern_analyzer.rb +308 -0
  90. data/lib/fontisan/optimizers/stack_tracker.rb +246 -0
  91. data/lib/fontisan/optimizers/subroutine_builder.rb +134 -0
  92. data/lib/fontisan/optimizers/subroutine_generator.rb +207 -0
  93. data/lib/fontisan/optimizers/subroutine_optimizer.rb +107 -0
  94. data/lib/fontisan/outline_extractor.rb +423 -0
  95. data/lib/fontisan/subset/builder.rb +268 -0
  96. data/lib/fontisan/subset/glyph_mapping.rb +215 -0
  97. data/lib/fontisan/subset/options.rb +142 -0
  98. data/lib/fontisan/subset/profile.rb +152 -0
  99. data/lib/fontisan/subset/table_subsetter.rb +461 -0
  100. data/lib/fontisan/svg/font_face_generator.rb +278 -0
  101. data/lib/fontisan/svg/font_generator.rb +264 -0
  102. data/lib/fontisan/svg/glyph_generator.rb +168 -0
  103. data/lib/fontisan/svg/view_box_calculator.rb +137 -0
  104. data/lib/fontisan/tables/cff/cff_glyph.rb +176 -0
  105. data/lib/fontisan/tables/cff/charset.rb +282 -0
  106. data/lib/fontisan/tables/cff/charstring.rb +905 -0
  107. data/lib/fontisan/tables/cff/charstring_builder.rb +322 -0
  108. data/lib/fontisan/tables/cff/charstrings_index.rb +162 -0
  109. data/lib/fontisan/tables/cff/dict.rb +351 -0
  110. data/lib/fontisan/tables/cff/dict_builder.rb +242 -0
  111. data/lib/fontisan/tables/cff/encoding.rb +274 -0
  112. data/lib/fontisan/tables/cff/header.rb +102 -0
  113. data/lib/fontisan/tables/cff/index.rb +237 -0
  114. data/lib/fontisan/tables/cff/index_builder.rb +170 -0
  115. data/lib/fontisan/tables/cff/private_dict.rb +284 -0
  116. data/lib/fontisan/tables/cff/top_dict.rb +236 -0
  117. data/lib/fontisan/tables/cff.rb +487 -0
  118. data/lib/fontisan/tables/cff2/blend_operator.rb +240 -0
  119. data/lib/fontisan/tables/cff2/charstring_parser.rb +591 -0
  120. data/lib/fontisan/tables/cff2/operand_stack.rb +232 -0
  121. data/lib/fontisan/tables/cff2.rb +341 -0
  122. data/lib/fontisan/tables/cvar.rb +242 -0
  123. data/lib/fontisan/tables/fvar.rb +2 -2
  124. data/lib/fontisan/tables/glyf/compound_glyph.rb +483 -0
  125. data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +136 -0
  126. data/lib/fontisan/tables/glyf/curve_converter.rb +343 -0
  127. data/lib/fontisan/tables/glyf/glyph_builder.rb +450 -0
  128. data/lib/fontisan/tables/glyf/simple_glyph.rb +382 -0
  129. data/lib/fontisan/tables/glyf.rb +235 -0
  130. data/lib/fontisan/tables/gvar.rb +270 -0
  131. data/lib/fontisan/tables/hhea.rb +124 -0
  132. data/lib/fontisan/tables/hmtx.rb +287 -0
  133. data/lib/fontisan/tables/hvar.rb +191 -0
  134. data/lib/fontisan/tables/loca.rb +322 -0
  135. data/lib/fontisan/tables/maxp.rb +192 -0
  136. data/lib/fontisan/tables/mvar.rb +185 -0
  137. data/lib/fontisan/tables/name.rb +99 -30
  138. data/lib/fontisan/tables/variation_common.rb +346 -0
  139. data/lib/fontisan/tables/vvar.rb +234 -0
  140. data/lib/fontisan/true_type_collection.rb +156 -2
  141. data/lib/fontisan/true_type_font.rb +297 -11
  142. data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
  143. data/lib/fontisan/utilities/checksum_calculator.rb +18 -0
  144. data/lib/fontisan/utils/thread_pool.rb +134 -0
  145. data/lib/fontisan/validation/checksum_validator.rb +170 -0
  146. data/lib/fontisan/validation/consistency_validator.rb +197 -0
  147. data/lib/fontisan/validation/structure_validator.rb +198 -0
  148. data/lib/fontisan/validation/table_validator.rb +158 -0
  149. data/lib/fontisan/validation/validator.rb +152 -0
  150. data/lib/fontisan/variable/axis_normalizer.rb +215 -0
  151. data/lib/fontisan/variable/delta_applicator.rb +313 -0
  152. data/lib/fontisan/variable/glyph_delta_processor.rb +218 -0
  153. data/lib/fontisan/variable/instancer.rb +344 -0
  154. data/lib/fontisan/variable/metric_delta_processor.rb +282 -0
  155. data/lib/fontisan/variable/region_matcher.rb +208 -0
  156. data/lib/fontisan/variable/static_font_builder.rb +213 -0
  157. data/lib/fontisan/variable/table_updater.rb +219 -0
  158. data/lib/fontisan/variation/blend_applier.rb +199 -0
  159. data/lib/fontisan/variation/cache.rb +298 -0
  160. data/lib/fontisan/variation/cache_key_builder.rb +162 -0
  161. data/lib/fontisan/variation/converter.rb +268 -0
  162. data/lib/fontisan/variation/data_extractor.rb +86 -0
  163. data/lib/fontisan/variation/delta_applier.rb +266 -0
  164. data/lib/fontisan/variation/delta_parser.rb +228 -0
  165. data/lib/fontisan/variation/inspector.rb +275 -0
  166. data/lib/fontisan/variation/instance_generator.rb +273 -0
  167. data/lib/fontisan/variation/interpolator.rb +231 -0
  168. data/lib/fontisan/variation/metrics_adjuster.rb +318 -0
  169. data/lib/fontisan/variation/optimizer.rb +418 -0
  170. data/lib/fontisan/variation/parallel_generator.rb +150 -0
  171. data/lib/fontisan/variation/region_matcher.rb +221 -0
  172. data/lib/fontisan/variation/subsetter.rb +463 -0
  173. data/lib/fontisan/variation/table_accessor.rb +105 -0
  174. data/lib/fontisan/variation/validator.rb +345 -0
  175. data/lib/fontisan/variation/variation_context.rb +211 -0
  176. data/lib/fontisan/version.rb +1 -1
  177. data/lib/fontisan/woff2/directory.rb +257 -0
  178. data/lib/fontisan/woff2/header.rb +101 -0
  179. data/lib/fontisan/woff2/table_transformer.rb +163 -0
  180. data/lib/fontisan/woff2_font.rb +712 -0
  181. data/lib/fontisan/woff_font.rb +483 -0
  182. data/lib/fontisan.rb +120 -0
  183. data/scripts/compare_stack_aware.rb +187 -0
  184. data/scripts/measure_optimization.rb +141 -0
  185. metadata +205 -4
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require_relative "../binary/base_record"
5
+
6
+ module Fontisan
7
+ module Tables
8
+ # Parser for the 'cvar' (CVT Variations) table
9
+ #
10
+ # The cvar table provides variation data for the Control Value Table (CVT)
11
+ # in TrueType variable fonts with hinting. The CVT contains scalar values
12
+ # that are referenced by TrueType instructions for grid-fitting.
13
+ #
14
+ # Like gvar, this table uses a TupleVariationStore structure with packed
15
+ # delta values rather than ItemVariationStore.
16
+ #
17
+ # Reference: OpenType specification, cvar table
18
+ #
19
+ # @example Reading a cvar table
20
+ # data = font.table_data("cvar")
21
+ # cvar = Fontisan::Tables::Cvar.read(data)
22
+ # cvt_deltas = cvar.cvt_variations
23
+ class Cvar < Binary::BaseRecord
24
+ uint16 :major_version
25
+ uint16 :minor_version
26
+ uint16 :tuple_variation_count
27
+ uint16 :data_offset
28
+
29
+ # Tuple variation header
30
+ class TupleVariationHeader < Binary::BaseRecord
31
+ uint16 :variation_data_size
32
+ uint16 :tuple_index
33
+
34
+ # Tuple index flags
35
+ EMBEDDED_PEAK_TUPLE = 0x8000
36
+ INTERMEDIATE_REGION = 0x4000
37
+ PRIVATE_POINT_NUMBERS = 0x2000
38
+ TUPLE_INDEX_MASK = 0x0FFF
39
+
40
+ # Check if tuple has embedded peak coordinates
41
+ #
42
+ # @return [Boolean] True if embedded
43
+ def embedded_peak_tuple?
44
+ (tuple_index & EMBEDDED_PEAK_TUPLE) != 0
45
+ end
46
+
47
+ # Check if tuple has intermediate region
48
+ #
49
+ # @return [Boolean] True if intermediate region
50
+ def intermediate_region?
51
+ (tuple_index & INTERMEDIATE_REGION) != 0
52
+ end
53
+
54
+ # Check if tuple has private point numbers
55
+ #
56
+ # @return [Boolean] True if private points
57
+ def private_point_numbers?
58
+ (tuple_index & PRIVATE_POINT_NUMBERS) != 0
59
+ end
60
+
61
+ # Get shared tuple index
62
+ #
63
+ # @return [Integer] Tuple index
64
+ def shared_tuple_index
65
+ tuple_index & TUPLE_INDEX_MASK
66
+ end
67
+ end
68
+
69
+ # Get version as a float
70
+ #
71
+ # @return [Float] Version number (e.g., 1.0)
72
+ def version
73
+ major_version + (minor_version / 10.0)
74
+ end
75
+
76
+ # Get tuple count
77
+ #
78
+ # @return [Integer] Number of tuple variations
79
+ def tuple_count
80
+ tuple_variation_count & 0x0FFF
81
+ end
82
+
83
+ # Check if using shared point numbers
84
+ #
85
+ # @return [Boolean] True if shared points
86
+ def shared_point_numbers?
87
+ (tuple_variation_count & 0x8000) != 0
88
+ end
89
+
90
+ # Get axis count from fvar table (needs to be provided externally)
91
+ # This is a placeholder that should be set by the caller
92
+ attr_accessor :axis_count
93
+
94
+ # Parse tuple variation headers
95
+ #
96
+ # @return [Array<Hash>] Array of tuple information
97
+ def tuple_variations
98
+ return @tuple_variations if @tuple_variations
99
+ return @tuple_variations = [] if tuple_count.zero?
100
+
101
+ data = raw_data
102
+ # Tuple records start after header (8 bytes)
103
+ offset = 8
104
+
105
+ count = tuple_count
106
+ tuples = []
107
+
108
+ count.times do |_i|
109
+ break if offset + 4 > data.bytesize
110
+
111
+ header_data = data.byteslice(offset, 4)
112
+ header = TupleVariationHeader.read(header_data)
113
+ offset += 4
114
+
115
+ tuple_info = {
116
+ data_size: header.variation_data_size,
117
+ embedded_peak: header.embedded_peak_tuple?,
118
+ intermediate: header.intermediate_region?,
119
+ private_points: header.private_point_numbers?,
120
+ shared_index: header.shared_tuple_index,
121
+ }
122
+
123
+ # Read peak tuple if embedded
124
+ if header.embedded_peak_tuple? && axis_count
125
+ peak = Array.new(axis_count) do
126
+ next nil if offset + 2 > data.bytesize
127
+
128
+ coord_data = data.byteslice(offset, 2)
129
+ offset += 2
130
+
131
+ value = coord_data.unpack1("n")
132
+ signed = value > 0x7FFF ? value - 0x10000 : value
133
+ signed / 16384.0
134
+ end.compact
135
+ tuple_info[:peak] = peak
136
+ end
137
+
138
+ # Read intermediate region if present
139
+ if header.intermediate_region? && axis_count
140
+ start_tuple = Array.new(axis_count) do
141
+ next nil if offset + 2 > data.bytesize
142
+
143
+ coord_data = data.byteslice(offset, 2)
144
+ offset += 2
145
+
146
+ value = coord_data.unpack1("n")
147
+ signed = value > 0x7FFF ? value - 0x10000 : value
148
+ signed / 16384.0
149
+ end.compact
150
+
151
+ end_tuple = Array.new(axis_count) do
152
+ next nil if offset + 2 > data.bytesize
153
+
154
+ coord_data = data.byteslice(offset, 2)
155
+ offset += 2
156
+
157
+ value = coord_data.unpack1("n")
158
+ signed = value > 0x7FFF ? value - 0x10000 : value
159
+ signed / 16384.0
160
+ end.compact
161
+
162
+ tuple_info[:start] = start_tuple
163
+ tuple_info[:end] = end_tuple
164
+ end
165
+
166
+ tuples << tuple_info
167
+ end
168
+
169
+ @tuple_variations = tuples
170
+ rescue StandardError => e
171
+ warn "Failed to parse cvar tuple variations: #{e.message}"
172
+ @tuple_variations = []
173
+ end
174
+
175
+ # Get variation data section
176
+ #
177
+ # @return [String, nil] Raw variation data
178
+ def variation_data
179
+ return @variation_data if defined?(@variation_data)
180
+
181
+ data = raw_data
182
+ offset = data_offset
183
+
184
+ return @variation_data = nil if offset >= data.bytesize
185
+
186
+ @variation_data = data.byteslice(offset..-1)
187
+ end
188
+
189
+ # Parse CVT deltas for a specific tuple
190
+ #
191
+ # This is a simplified parser that returns the raw delta data.
192
+ # Full delta unpacking would require knowing point counts and
193
+ # delta formats.
194
+ #
195
+ # @param tuple_index [Integer] Tuple index
196
+ # @return [Hash, nil] Tuple info with data offset
197
+ def tuple_variation_data(tuple_index)
198
+ return nil if tuple_index >= tuple_count
199
+
200
+ tuples = tuple_variations
201
+ return nil if tuple_index >= tuples.length
202
+
203
+ tuple = tuples[tuple_index]
204
+
205
+ # Calculate data offset for this tuple
206
+ # This is complex and requires walking through all previous tuples
207
+ # For now, return tuple metadata
208
+ {
209
+ tuple: tuple,
210
+ data_size: tuple[:data_size],
211
+ }
212
+ end
213
+
214
+ # Get summary of CVT variations
215
+ #
216
+ # @return [Hash] Summary information
217
+ def summary
218
+ {
219
+ version: version,
220
+ tuple_count: tuple_count,
221
+ shared_points: shared_point_numbers?,
222
+ data_offset: data_offset,
223
+ tuples: tuple_variations.map do |t|
224
+ {
225
+ embedded_peak: t[:embedded_peak],
226
+ intermediate: t[:intermediate],
227
+ private_points: t[:private_points],
228
+ peak: t[:peak],
229
+ }
230
+ end,
231
+ }
232
+ end
233
+
234
+ # Check if table is valid
235
+ #
236
+ # @return [Boolean] True if valid
237
+ def valid?
238
+ major_version == 1 && minor_version.zero?
239
+ end
240
+ end
241
+ end
242
+ end
@@ -89,7 +89,7 @@ module Fontisan
89
89
  return @axes = [] if axis_count.zero?
90
90
 
91
91
  # Get the full data buffer as binary string
92
- data = to_binary_s
92
+ data = raw_data
93
93
 
94
94
  @axes = Array.new(axis_count) do |i|
95
95
  offset = axes_array_offset + (i * axis_size)
@@ -106,7 +106,7 @@ module Fontisan
106
106
  return @instances = [] if instance_count.zero?
107
107
 
108
108
  # Get the full data buffer as binary string
109
- data = to_binary_s
109
+ data = raw_data
110
110
 
111
111
  # Calculate instance data offset (after all axes)
112
112
  instance_offset = axes_array_offset + (axis_count * axis_size)