fontisan 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +57 -385
  3. data/README.adoc +1483 -1435
  4. data/Rakefile +3 -2
  5. data/benchmark/variation_quick_bench.rb +4 -4
  6. data/docs/FONT_HINTING.adoc +562 -0
  7. data/docs/VARIABLE_FONT_OPERATIONS.adoc +599 -0
  8. data/lib/fontisan/cli.rb +10 -3
  9. data/lib/fontisan/collection/builder.rb +2 -1
  10. data/lib/fontisan/collection/offset_calculator.rb +2 -0
  11. data/lib/fontisan/commands/base_command.rb +5 -2
  12. data/lib/fontisan/commands/convert_command.rb +6 -2
  13. data/lib/fontisan/commands/info_command.rb +111 -5
  14. data/lib/fontisan/commands/instance_command.rb +8 -7
  15. data/lib/fontisan/commands/validate_command.rb +4 -1
  16. data/lib/fontisan/constants.rb +24 -24
  17. data/lib/fontisan/converters/format_converter.rb +8 -4
  18. data/lib/fontisan/converters/outline_converter.rb +21 -16
  19. data/lib/fontisan/converters/woff_writer.rb +8 -3
  20. data/lib/fontisan/font_loader.rb +11 -4
  21. data/lib/fontisan/font_writer.rb +2 -0
  22. data/lib/fontisan/formatters/text_formatter.rb +45 -1
  23. data/lib/fontisan/hints/hint_converter.rb +43 -47
  24. data/lib/fontisan/hints/hint_validator.rb +284 -0
  25. data/lib/fontisan/hints/postscript_hint_applier.rb +1 -3
  26. data/lib/fontisan/hints/postscript_hint_extractor.rb +78 -43
  27. data/lib/fontisan/hints/truetype_hint_extractor.rb +22 -26
  28. data/lib/fontisan/hints/truetype_instruction_analyzer.rb +261 -0
  29. data/lib/fontisan/hints/truetype_instruction_generator.rb +266 -0
  30. data/lib/fontisan/loading_modes.rb +4 -4
  31. data/lib/fontisan/models/collection_brief_info.rb +31 -0
  32. data/lib/fontisan/models/font_export.rb +2 -2
  33. data/lib/fontisan/models/font_info.rb +3 -30
  34. data/lib/fontisan/models/hint.rb +22 -23
  35. data/lib/fontisan/models/outline.rb +4 -1
  36. data/lib/fontisan/models/validation_report.rb +1 -1
  37. data/lib/fontisan/open_type_font.rb +3 -1
  38. data/lib/fontisan/optimizers/pattern_analyzer.rb +2 -1
  39. data/lib/fontisan/optimizers/subroutine_generator.rb +1 -1
  40. data/lib/fontisan/pipeline/output_writer.rb +8 -3
  41. data/lib/fontisan/pipeline/transformation_pipeline.rb +8 -3
  42. data/lib/fontisan/subset/table_subsetter.rb +5 -5
  43. data/lib/fontisan/tables/cff/charstring.rb +38 -12
  44. data/lib/fontisan/tables/cff/charstring_parser.rb +23 -11
  45. data/lib/fontisan/tables/cff/charstring_rebuilder.rb +14 -14
  46. data/lib/fontisan/tables/cff/dict_builder.rb +4 -1
  47. data/lib/fontisan/tables/cff/hint_operation_injector.rb +6 -4
  48. data/lib/fontisan/tables/cff/offset_recalculator.rb +1 -1
  49. data/lib/fontisan/tables/cff/private_dict_writer.rb +10 -4
  50. data/lib/fontisan/tables/cff/table_builder.rb +1 -1
  51. data/lib/fontisan/tables/cff2/charstring_parser.rb +14 -8
  52. data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +7 -6
  53. data/lib/fontisan/tables/cff2/region_matcher.rb +2 -2
  54. data/lib/fontisan/tables/cff2/table_builder.rb +26 -20
  55. data/lib/fontisan/tables/cff2/table_reader.rb +35 -33
  56. data/lib/fontisan/tables/cff2/variation_data_extractor.rb +2 -2
  57. data/lib/fontisan/tables/cff2.rb +1 -1
  58. data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +2 -1
  59. data/lib/fontisan/tables/glyf/curve_converter.rb +10 -4
  60. data/lib/fontisan/tables/glyf/glyph_builder.rb +27 -10
  61. data/lib/fontisan/tables/name.rb +4 -4
  62. data/lib/fontisan/true_type_font.rb +3 -1
  63. data/lib/fontisan/validation/checksum_validator.rb +2 -2
  64. data/lib/fontisan/variation/cache.rb +3 -1
  65. data/lib/fontisan/variation/converter.rb +2 -1
  66. data/lib/fontisan/variation/delta_applier.rb +2 -1
  67. data/lib/fontisan/variation/inspector.rb +2 -1
  68. data/lib/fontisan/variation/instance_generator.rb +2 -1
  69. data/lib/fontisan/variation/optimizer.rb +6 -3
  70. data/lib/fontisan/variation/subsetter.rb +32 -10
  71. data/lib/fontisan/variation/variation_preserver.rb +4 -1
  72. data/lib/fontisan/version.rb +1 -1
  73. data/lib/fontisan/woff2/glyf_transformer.rb +57 -30
  74. data/lib/fontisan/woff2_font.rb +31 -15
  75. data/lib/fontisan.rb +42 -2
  76. data/scripts/measure_optimization.rb +15 -7
  77. metadata +8 -2
@@ -99,7 +99,11 @@ module Fontisan
99
99
  # Check array limits
100
100
  if HINT_LIMITS[key]
101
101
  raise ArgumentError, "#{key} invalid" unless value.is_a?(Array)
102
- raise ArgumentError, "#{key} too long" if value.length > HINT_LIMITS[key][:max]
102
+
103
+ if value.length > HINT_LIMITS[key][:max]
104
+ raise ArgumentError,
105
+ "#{key} too long"
106
+ end
103
107
  if HINT_LIMITS[key][:pairs] && value.length.odd?
104
108
  raise ArgumentError, "#{key} must be pairs"
105
109
  end
@@ -114,12 +118,14 @@ module Fontisan
114
118
  when :blue_shift, :blue_fuzz
115
119
  raise ArgumentError, "#{key} invalid" unless value.is_a?(Numeric)
116
120
  when :force_bold
117
- raise ArgumentError, "#{key} must be 0 or 1" unless [0, 1].include?(value)
121
+ raise ArgumentError, "#{key} must be 0 or 1" unless [0,
122
+ 1].include?(value)
118
123
  when :language_group
119
- raise ArgumentError, "#{key} must be 0 or 1" unless [0, 1].include?(value)
124
+ raise ArgumentError, "#{key} must be 0 or 1" unless [0,
125
+ 1].include?(value)
120
126
  end
121
127
  end
122
128
  end
123
129
  end
124
130
  end
125
- end
131
+ end
@@ -218,4 +218,4 @@ module Fontisan
218
218
  end
219
219
  end
220
220
  end
221
- end
221
+ end
@@ -68,7 +68,8 @@ module Fontisan
68
68
  # @param global_subrs [Cff::Index, nil] Global subroutines INDEX
69
69
  # @param local_subrs [Cff::Index, nil] Local subroutines INDEX
70
70
  # @param vsindex [Integer] Variation store index (default 0)
71
- def initialize(data, num_axes = 0, global_subrs = nil, local_subrs = nil, vsindex = 0)
71
+ def initialize(data, num_axes = 0, global_subrs = nil,
72
+ local_subrs = nil, vsindex = 0)
72
73
  @data = data
73
74
  @num_axes = num_axes
74
75
  @global_subrs = global_subrs
@@ -123,7 +124,8 @@ module Fontisan
123
124
  when :line_to
124
125
  [:line_to, cmd[:x], cmd[:y]]
125
126
  when :curve_to
126
- [:curve_to, cmd[:x1], cmd[:y1], cmd[:x2], cmd[:y2], cmd[:x], cmd[:y]]
127
+ [:curve_to, cmd[:x1], cmd[:y1], cmd[:x2], cmd[:y2], cmd[:x],
128
+ cmd[:y]]
127
129
  end
128
130
  end
129
131
  end
@@ -146,7 +148,8 @@ module Fontisan
146
148
  end
147
149
  end
148
150
  rescue StandardError => e
149
- raise CorruptedTableError, "Failed to parse CFF2 CharString: #{e.message}"
151
+ raise CorruptedTableError,
152
+ "Failed to parse CFF2 CharString: #{e.message}"
150
153
  end
151
154
 
152
155
  # Check if byte is an operator
@@ -165,7 +168,10 @@ module Fontisan
165
168
  if first_byte == 12
166
169
  # Two-byte operator
167
170
  second_byte = @io.getbyte
168
- raise CorruptedTableError, "Unexpected end of CharString" if second_byte.nil?
171
+ if second_byte.nil?
172
+ raise CorruptedTableError,
173
+ "Unexpected end of CharString"
174
+ end
169
175
 
170
176
  [12, second_byte]
171
177
  else
@@ -305,7 +311,7 @@ module Fontisan
305
311
  # @param blend_op [Hash] Blend operation data
306
312
  # @param coordinates [Hash<String, Float>] Axis coordinates
307
313
  # @return [Array<Float>] Blended values
308
- def apply_blend(blend_op, coordinates)
314
+ def apply_blend(blend_op, _coordinates)
309
315
  blend_op[:blends].map do |blend|
310
316
  base = blend[:base]
311
317
  deltas = blend[:deltas]
@@ -313,7 +319,7 @@ module Fontisan
313
319
  # Apply deltas based on coordinates
314
320
  # This will be enhanced when we have proper coordinate interpolation
315
321
  blended_value = base
316
- deltas.each_with_index do |delta, axis_index|
322
+ deltas.each_with_index do |delta, _axis_index|
317
323
  # Placeholder: use normalized coordinate (will be replaced with proper interpolation)
318
324
  scalar = 0.0 # Will be calculated by interpolator
319
325
  blended_value += delta * scalar
@@ -573,7 +579,7 @@ module Fontisan
573
579
  def callsubr
574
580
  return if @local_subrs.nil? || @stack.empty?
575
581
 
576
- subr_index = @stack.pop
582
+ @stack.pop
577
583
  # Implement subroutine call (placeholder)
578
584
  @stack.clear
579
585
  end
@@ -581,7 +587,7 @@ module Fontisan
581
587
  def callgsubr
582
588
  return if @global_subrs.nil? || @stack.empty?
583
589
 
584
- subr_index = @stack.pop
590
+ @stack.pop
585
591
  # Implement global subroutine call (placeholder)
586
592
  @stack.clear
587
593
  end
@@ -63,14 +63,14 @@ module Fontisan
63
63
 
64
64
  blends << {
65
65
  base: base,
66
- deltas: deltas
66
+ deltas: deltas,
67
67
  }
68
68
  end
69
69
 
70
70
  {
71
71
  num_values: num_values,
72
72
  num_axes: num_axes,
73
- blends: blends
73
+ blends: blends,
74
74
  }
75
75
  end
76
76
 
@@ -90,7 +90,7 @@ module Fontisan
90
90
  {
91
91
  base: value[0],
92
92
  deltas: value[1..num_axes],
93
- num_axes: num_axes
93
+ num_axes: num_axes,
94
94
  }
95
95
  end
96
96
 
@@ -149,7 +149,8 @@ module Fontisan
149
149
  else
150
150
  # Try as single blend value
151
151
  blend_data = parse_blend_value(key, num_axes: num_axes)
152
- result[key] = blend_data ? apply_blend(blend_data, scalars) : value
152
+ result[key] =
153
+ blend_data ? apply_blend(blend_data, scalars) : value
153
154
  end
154
155
  else
155
156
  # Non-blend value, copy as-is
@@ -186,7 +187,7 @@ module Fontisan
186
187
  # Hint with blend data - normalize and flatten for DICT storage
187
188
  normalized_value = {
188
189
  base: value[:base] || value["base"],
189
- deltas: value[:deltas] || value["deltas"]
190
+ deltas: value[:deltas] || value["deltas"],
190
191
  }
191
192
  result[key] = flatten_blend(normalized_value, num_axes: num_axes)
192
193
  else
@@ -243,4 +244,4 @@ module Fontisan
243
244
  end
244
245
  end
245
246
  end
246
- end
247
+ end
@@ -121,7 +121,7 @@ module Fontisan
121
121
  def active_regions(coordinates)
122
122
  scalars = calculate_scalars(coordinates)
123
123
  scalars.each_with_index.select { |scalar, _| scalar.positive? }
124
- .map(&:last)
124
+ .map(&:last)
125
125
  end
126
126
 
127
127
  # Get scalar for specific region index
@@ -197,4 +197,4 @@ module Fontisan
197
197
  end
198
198
  end
199
199
  end
200
- end
200
+ end
@@ -31,6 +31,9 @@ module Fontisan
31
31
  # builder = CFF2TableBuilder.new(reader, hint_set)
32
32
  # new_cff2 = builder.build
33
33
  class TableBuilder < Tables::Cff::TableBuilder
34
+ # CFF2-specific operators not supported by CFF DictBuilder
35
+ INVALID_CFF_KEYS = [24].freeze # operator 24 = vstore (CFF2 only)
36
+
34
37
  # @return [CFF2TableReader] CFF2 table reader
35
38
  attr_reader :reader
36
39
 
@@ -79,7 +82,7 @@ module Fontisan
79
82
  top_dict: top_dict_hash,
80
83
  charstrings: charstrings_data,
81
84
  private_dict: private_dict_data,
82
- vstore: vstore_data
85
+ vstore: vstore_data,
83
86
  )
84
87
  end
85
88
 
@@ -134,7 +137,8 @@ module Fontisan
134
137
 
135
138
  # Create rebuilder with stem count
136
139
  stem_count = calculate_stem_count
137
- rebuilder = Cff::CharStringRebuilder.new(charstrings_index, stem_count: stem_count)
140
+ rebuilder = Cff::CharStringRebuilder.new(charstrings_index,
141
+ stem_count: stem_count)
138
142
 
139
143
  # Modify each glyph with hints
140
144
  hinted_glyph_ids.each do |glyph_id|
@@ -179,14 +183,14 @@ module Fontisan
179
183
  # Count stems from blue zones (hstem)
180
184
  hstem_count = 0
181
185
  blue_values = font_hints["blue_values"] || font_hints[:blue_values]
182
- if blue_values && blue_values.is_a?(Array)
186
+ if blue_values.is_a?(Array)
183
187
  hstem_count = blue_values.size / 2
184
188
  end
185
189
 
186
190
  # Count stems from stem snap (vstem)
187
191
  vstem_count = 0
188
192
  stem_snap_h = font_hints["stem_snap_h"] || font_hints[:stem_snap_h]
189
- if stem_snap_h && stem_snap_h.is_a?(Array)
193
+ if stem_snap_h.is_a?(Array)
190
194
  vstem_count = stem_snap_h.size
191
195
  end
192
196
 
@@ -365,7 +369,7 @@ module Fontisan
365
369
  # Extract Variable Store bytes unchanged
366
370
  # For simplicity, extract from vstore_offset to end of table
367
371
  # In production, we'd parse structure to get exact size
368
- @reader.data[vstore_offset..-1]
372
+ @reader.data[vstore_offset..]
369
373
  end
370
374
 
371
375
  # Rebuild complete CFF2 table
@@ -376,7 +380,8 @@ module Fontisan
376
380
  # @param private_dict [String, nil] Private DICT
377
381
  # @param vstore [String, nil] Variable Store
378
382
  # @return [String] Complete CFF2 table binary
379
- def rebuild_cff2_table(header:, top_dict:, charstrings:, private_dict:, vstore:)
383
+ def rebuild_cff2_table(header:, top_dict:, charstrings:, private_dict:,
384
+ vstore:)
380
385
  output = StringIO.new("".b)
381
386
 
382
387
  # 1. Write Header
@@ -387,7 +392,7 @@ module Fontisan
387
392
  header_size: header.size,
388
393
  charstrings: charstrings,
389
394
  private_dict: private_dict,
390
- vstore: vstore
395
+ vstore: vstore,
391
396
  )
392
397
 
393
398
  # 3. Build Top DICT with updated offsets
@@ -416,7 +421,8 @@ module Fontisan
416
421
  # @param private_dict [String, nil] Private DICT data
417
422
  # @param vstore [String, nil] Variable Store data
418
423
  # @return [Hash] Section offsets
419
- def calculate_cff2_offsets(header_size:, charstrings:, private_dict:, vstore:)
424
+ def calculate_cff2_offsets(header_size:, charstrings:, private_dict:,
425
+ vstore:)
420
426
  # Start after header
421
427
  offset = header_size
422
428
 
@@ -446,7 +452,7 @@ module Fontisan
446
452
  charstrings: charstrings_offset,
447
453
  private_dict: private_dict_offset,
448
454
  private_dict_size: private_dict_size,
449
- vstore: vstore_offset
455
+ vstore: vstore_offset,
450
456
  }
451
457
  end
452
458
 
@@ -531,23 +537,23 @@ module Fontisan
531
537
  18 => :private,
532
538
  19 => :subrs,
533
539
  20 => :default_width_x,
534
- 21 => :nominal_width_x
535
- # Note: operator 24 (vstore) is CFF2-specific and handled separately
540
+ 21 => :nominal_width_x,
541
+ # Note: operator 24 (vstore) is CFF2-specific and handled separately
536
542
  }
537
543
 
538
544
  result = {}
539
545
  dict.each do |key, value|
540
546
  # Skip vstore (operator 24) - CFF2 specific, not in CFF DictBuilder
541
- next if key == 24 || key == :vstore
547
+ next if INVALID_CFF_KEYS.include?(key)
542
548
 
543
549
  # Convert string keys to symbols for DictBuilder
544
- if key.is_a?(String)
545
- symbol_key = key.to_sym
546
- elsif key.is_a?(Integer)
547
- symbol_key = operator_map[key] || key
548
- else
549
- symbol_key = key
550
- end
550
+ symbol_key = if key.is_a?(String)
551
+ key.to_sym
552
+ elsif key.is_a?(Integer)
553
+ operator_map[key] || key
554
+ else
555
+ key
556
+ end
551
557
 
552
558
  result[symbol_key] = value
553
559
  end
@@ -571,4 +577,4 @@ module Fontisan
571
577
  end
572
578
  end
573
579
  end
574
- end
580
+ end
@@ -61,7 +61,7 @@ module Fontisan
61
61
  major_version: read_uint8,
62
62
  minor_version: read_uint8,
63
63
  header_size: read_uint8,
64
- top_dict_length: read_uint16
64
+ top_dict_length: read_uint16,
65
65
  }
66
66
 
67
67
  # Validate CFF2 version
@@ -105,7 +105,7 @@ module Fontisan
105
105
  # Parse Variable Store structure
106
106
  @variable_store = {
107
107
  regions: read_region_list,
108
- item_variation_data: read_item_variation_data
108
+ item_variation_data: read_item_variation_data,
109
109
  }
110
110
 
111
111
  @variable_store
@@ -140,7 +140,7 @@ module Fontisan
140
140
  axes << {
141
141
  start_coord: read_f2dot14,
142
142
  peak_coord: read_f2dot14,
143
- end_coord: read_f2dot14
143
+ end_coord: read_f2dot14,
144
144
  }
145
145
  end
146
146
 
@@ -158,13 +158,11 @@ module Fontisan
158
158
 
159
159
  item_variation_data = []
160
160
 
161
- data_count.times do |idx|
162
- begin
163
- item_data = read_single_item_variation_data
164
- item_variation_data << item_data
165
- rescue EOFError => e
166
- # break
167
- end
161
+ data_count.times do |_idx|
162
+ item_data = read_single_item_variation_data
163
+ item_variation_data << item_data
164
+ rescue EOFError
165
+ # break
168
166
  end
169
167
 
170
168
  item_variation_data
@@ -186,32 +184,32 @@ module Fontisan
186
184
 
187
185
  # Read delta sets
188
186
  delta_sets = []
189
- item_count.times do |item_idx|
190
- begin
191
- deltas = []
192
-
193
- # Short deltas (16-bit)
194
- short_delta_count.times do
195
- break if @io.eof?
196
- deltas << read_int16
197
- end
198
-
199
- # Long deltas (8-bit) for remaining regions
200
- (region_index_count - short_delta_count).times do
201
- break if @io.eof?
202
- deltas << read_int8
203
- end
204
-
205
- delta_sets << deltas
206
- rescue EOFError => e
207
- # break
187
+ item_count.times do |_item_idx|
188
+ deltas = []
189
+
190
+ # Short deltas (16-bit)
191
+ short_delta_count.times do
192
+ break if @io.eof?
193
+
194
+ deltas << read_int16
195
+ end
196
+
197
+ # Long deltas (8-bit) for remaining regions
198
+ (region_index_count - short_delta_count).times do
199
+ break if @io.eof?
200
+
201
+ deltas << read_int8
208
202
  end
203
+
204
+ delta_sets << deltas
205
+ rescue EOFError
206
+ # break
209
207
  end
210
208
 
211
209
  {
212
210
  item_count: item_count,
213
211
  region_indices: region_indices,
214
- delta_sets: delta_sets
212
+ delta_sets: delta_sets,
215
213
  }
216
214
  end
217
215
 
@@ -249,7 +247,10 @@ module Fontisan
249
247
  # @raise [EOFError] If not enough bytes available
250
248
  def read_safely(bytes, description)
251
249
  data = @io.read(bytes)
252
- raise EOFError, "Unexpected EOF while reading #{description}" if data.nil? || data.bytesize < bytes
250
+ if data.nil? || data.bytesize < bytes
251
+ raise EOFError,
252
+ "Unexpected EOF while reading #{description}"
253
+ end
253
254
 
254
255
  data
255
256
  end
@@ -269,7 +270,8 @@ module Fontisan
269
270
 
270
271
  if operator_byte?(byte)
271
272
  operator = read_dict_operator(io, byte)
272
- dict[operator] = operands.size == 1 ? operands.first : operands.dup
273
+ dict[operator] =
274
+ operands.size == 1 ? operands.first : operands.dup
273
275
  operands.clear
274
276
  else
275
277
  # Operand (number)
@@ -416,4 +418,4 @@ module Fontisan
416
418
  end
417
419
  end
418
420
  end
419
- end
421
+ end
@@ -148,7 +148,7 @@ module Fontisan
148
148
  {
149
149
  start: axis[:start_coord],
150
150
  peak: axis[:peak_coord],
151
- end: axis[:end_coord]
151
+ end: axis[:end_coord],
152
152
  }
153
153
  end
154
154
  end
@@ -209,4 +209,4 @@ module Fontisan
209
209
  end
210
210
  end
211
211
  end
212
- end
212
+ end
@@ -111,7 +111,7 @@ module Fontisan
111
111
  @num_axes,
112
112
  @global_subr_index,
113
113
  nil, # local subrs (CFF2 may not have them)
114
- 0 # vsindex
114
+ 0, # vsindex
115
115
  ).parse
116
116
  end
117
117
 
@@ -118,7 +118,8 @@ module Fontisan
118
118
  resolve(component_glyph, visited, depth + 1)
119
119
  else
120
120
  # Convert simple glyph to outline
121
- Models::Outline.from_truetype(component_glyph, component.glyph_index)
121
+ Models::Outline.from_truetype(component_glyph,
122
+ component.glyph_index)
122
123
  end
123
124
 
124
125
  # Apply transformation matrix
@@ -119,7 +119,10 @@ module Fontisan
119
119
  # @raise [ArgumentError] If parameters are invalid
120
120
  def self.calculate_error(cubic, quadratics)
121
121
  validate_cubic_curve!(cubic)
122
- raise ArgumentError, "quadratics must be Array" unless quadratics.is_a?(Array)
122
+ unless quadratics.is_a?(Array)
123
+ raise ArgumentError,
124
+ "quadratics must be Array"
125
+ end
123
126
  raise ArgumentError, "quadratics cannot be empty" if quadratics.empty?
124
127
 
125
128
  max_error = 0.0
@@ -305,7 +308,8 @@ module Fontisan
305
308
  required.each do |key|
306
309
  value = quad[key]
307
310
  unless value.is_a?(Numeric)
308
- raise ArgumentError, "quad[:#{key}] must be Numeric, got: #{value.class}"
311
+ raise ArgumentError,
312
+ "quad[:#{key}] must be Numeric, got: #{value.class}"
309
313
  end
310
314
  end
311
315
  end
@@ -324,14 +328,16 @@ module Fontisan
324
328
  required.each do |key|
325
329
  value = cubic[key]
326
330
  unless value.is_a?(Numeric)
327
- raise ArgumentError, "cubic[:#{key}] must be Numeric, got: #{value.class}"
331
+ raise ArgumentError,
332
+ "cubic[:#{key}] must be Numeric, got: #{value.class}"
328
333
  end
329
334
  end
330
335
  end
331
336
 
332
337
  private_class_method def self.validate_max_error!(max_error)
333
338
  unless max_error.is_a?(Numeric)
334
- raise ArgumentError, "max_error must be Numeric, got: #{max_error.class}"
339
+ raise ArgumentError,
340
+ "max_error must be Numeric, got: #{max_error.class}"
335
341
  end
336
342
 
337
343
  if max_error <= 0
@@ -106,7 +106,11 @@ module Fontisan
106
106
  # @raise [ArgumentError] If parameters are invalid
107
107
  def self.build_compound_glyph(components, bbox, instructions: "".b)
108
108
  raise ArgumentError, "components cannot be nil" if components.nil?
109
- raise ArgumentError, "components must be Array" unless components.is_a?(Array)
109
+
110
+ unless components.is_a?(Array)
111
+ raise ArgumentError,
112
+ "components must be Array"
113
+ end
110
114
  raise ArgumentError, "components cannot be empty" if components.empty?
111
115
 
112
116
  validate_bbox!(bbox)
@@ -114,7 +118,8 @@ module Fontisan
114
118
  build_compound_glyph_data(components, bbox, instructions)
115
119
  end
116
120
 
117
- private_class_method def self.build_simple_glyph_data(contours, bbox, instructions)
121
+ private_class_method def self.build_simple_glyph_data(contours, bbox,
122
+ instructions)
118
123
  num_contours = contours.length
119
124
 
120
125
  # Build endPtsOfContours array
@@ -136,7 +141,8 @@ module Fontisan
136
141
 
137
142
  # Header (10 bytes)
138
143
  data << [num_contours].pack("n") # numberOfContours
139
- data << [bbox[:x_min], bbox[:y_min], bbox[:x_max], bbox[:y_max]].pack("n4")
144
+ data << [bbox[:x_min], bbox[:y_min], bbox[:x_max],
145
+ bbox[:y_max]].pack("n4")
140
146
 
141
147
  # endPtsOfContours
142
148
  data << end_pts_of_contours.pack("n*")
@@ -155,18 +161,21 @@ module Fontisan
155
161
  data
156
162
  end
157
163
 
158
- private_class_method def self.build_compound_glyph_data(components, bbox, instructions)
164
+ private_class_method def self.build_compound_glyph_data(components, bbox,
165
+ instructions)
159
166
  data = (+"").force_encoding(Encoding::BINARY)
160
167
 
161
168
  # Header (10 bytes) - numberOfContours = -1 for compound
162
169
  data << [-1].pack("n") # Use signed pack, will convert to 0xFFFF
163
- data << [bbox[:x_min], bbox[:y_min], bbox[:x_max], bbox[:y_max]].pack("n4")
170
+ data << [bbox[:x_min], bbox[:y_min], bbox[:x_max],
171
+ bbox[:y_max]].pack("n4")
164
172
 
165
173
  # Encode components
166
174
  has_instructions = instructions.bytesize.positive?
167
175
  components.each_with_index do |component, index|
168
176
  is_last = (index == components.length - 1)
169
- component_data = encode_component(component, is_last, has_instructions)
177
+ component_data = encode_component(component, is_last,
178
+ has_instructions)
170
179
  data << component_data
171
180
  end
172
181
 
@@ -179,7 +188,8 @@ module Fontisan
179
188
  data
180
189
  end
181
190
 
182
- private_class_method def self.encode_component(component, is_last, has_instructions)
191
+ private_class_method def self.encode_component(component, is_last,
192
+ has_instructions)
183
193
  validate_component!(component)
184
194
 
185
195
  glyph_index = component[:glyph_index]
@@ -344,7 +354,8 @@ module Fontisan
344
354
  data
345
355
  end
346
356
 
347
- private_class_method def self.encode_coordinate_values(flags, deltas, axis)
357
+ private_class_method def self.encode_coordinate_values(flags, deltas,
358
+ axis)
348
359
  data = (+"").force_encoding(Encoding::BINARY)
349
360
  short_flag = axis == :x ? X_SHORT_VECTOR : Y_SHORT_VECTOR
350
361
  same_flag = axis == :x ? X_IS_SAME_OR_POSITIVE_X_SHORT : Y_IS_SAME_OR_POSITIVE_Y_SHORT
@@ -400,7 +411,10 @@ module Fontisan
400
411
  # Convert float to F2DOT14 fixed-point format
401
412
  # F2DOT14: 2 bits integer, 14 bits fractional
402
413
  # Range: -2.0 to ~1.99993896484375
403
- raise ArgumentError, "value out of F2DOT14 range" if value < -2.0 || value > 2.0
414
+ if value < -2.0 || value > 2.0
415
+ raise ArgumentError,
416
+ "value out of F2DOT14 range"
417
+ end
404
418
 
405
419
  fixed = (value * 16_384.0).round
406
420
  # Convert to unsigned 16-bit
@@ -434,7 +448,10 @@ module Fontisan
434
448
  end
435
449
 
436
450
  private_class_method def self.validate_component!(component)
437
- raise ArgumentError, "component must be Hash" unless component.is_a?(Hash)
451
+ unless component.is_a?(Hash)
452
+ raise ArgumentError,
453
+ "component must be Hash"
454
+ end
438
455
  unless component[:glyph_index]
439
456
  raise ArgumentError, "component must have :glyph_index"
440
457
  end
@@ -173,13 +173,13 @@ module Fontisan
173
173
  record = find_name_record(
174
174
  name_id,
175
175
  platform: PLATFORM_WINDOWS,
176
- language: WINDOWS_LANGUAGE_EN_US
176
+ language: WINDOWS_LANGUAGE_EN_US,
177
177
  )
178
178
 
179
179
  record ||= find_name_record(
180
180
  name_id,
181
181
  platform: PLATFORM_MACINTOSH,
182
- language: MAC_LANGUAGE_ENGLISH
182
+ language: MAC_LANGUAGE_ENGLISH,
183
183
  )
184
184
 
185
185
  return nil unless record
@@ -236,10 +236,10 @@ module Fontisan
236
236
  decoded = case record.platform_id
237
237
  when PLATFORM_WINDOWS, PLATFORM_UNICODE
238
238
  string_data.dup.force_encoding("UTF-16BE")
239
- .encode("UTF-8", invalid: :replace, undef: :replace)
239
+ .encode("UTF-8", invalid: :replace, undef: :replace)
240
240
  when PLATFORM_MACINTOSH
241
241
  string_data.dup.force_encoding("ASCII-8BIT")
242
- .encode("UTF-8", invalid: :replace, undef: :replace)
242
+ .encode("UTF-8", invalid: :replace, undef: :replace)
243
243
  else
244
244
  string_data.dup.force_encoding("UTF-8")
245
245
  end
@@ -233,7 +233,8 @@ module Fontisan
233
233
  batch_entries.each do |entry|
234
234
  relative_offset = entry.offset - batch_offset
235
235
  tag_key = entry.tag.dup.force_encoding("UTF-8")
236
- @table_data[tag_key] = batch_data[relative_offset, entry.table_length]
236
+ @table_data[tag_key] =
237
+ batch_data[relative_offset, entry.table_length]
237
238
  end
238
239
  end
239
240
 
@@ -307,6 +308,7 @@ module Fontisan
307
308
  # @return [Boolean] true if table is available in current mode
308
309
  def table_available?(tag)
309
310
  return false unless has_table?(tag)
311
+
310
312
  LoadingModes.table_allowed?(@loading_mode, tag)
311
313
  end
312
314