fontisan 0.2.7 → 0.2.9

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +103 -0
  3. data/.rubocop_todo.yml +65 -361
  4. data/CHANGELOG.md +116 -0
  5. data/Gemfile +1 -1
  6. data/README.adoc +106 -27
  7. data/Rakefile +12 -7
  8. data/benchmark/variation_quick_bench.rb +1 -1
  9. data/docs/APPLE_LEGACY_FONTS.adoc +173 -0
  10. data/docs/COLLECTION_VALIDATION.adoc +143 -0
  11. data/docs/COLOR_FONTS.adoc +127 -0
  12. data/docs/DOCUMENTATION_SUMMARY.md +141 -0
  13. data/docs/FONT_HINTING.adoc +9 -1
  14. data/docs/VALIDATION.adoc +254 -0
  15. data/docs/WOFF_WOFF2_FORMATS.adoc +94 -0
  16. data/lib/fontisan/cli.rb +45 -13
  17. data/lib/fontisan/collection/dfont_builder.rb +2 -1
  18. data/lib/fontisan/commands/convert_command.rb +2 -4
  19. data/lib/fontisan/commands/info_command.rb +3 -3
  20. data/lib/fontisan/commands/pack_command.rb +2 -1
  21. data/lib/fontisan/commands/validate_command.rb +157 -6
  22. data/lib/fontisan/converters/collection_converter.rb +22 -13
  23. data/lib/fontisan/converters/svg_generator.rb +2 -1
  24. data/lib/fontisan/converters/woff2_encoder.rb +6 -6
  25. data/lib/fontisan/converters/woff_writer.rb +3 -1
  26. data/lib/fontisan/font_loader.rb +7 -6
  27. data/lib/fontisan/formatters/text_formatter.rb +18 -14
  28. data/lib/fontisan/hints/hint_converter.rb +1 -1
  29. data/lib/fontisan/hints/hint_validator.rb +13 -10
  30. data/lib/fontisan/hints/truetype_instruction_analyzer.rb +15 -8
  31. data/lib/fontisan/hints/truetype_instruction_generator.rb +1 -1
  32. data/lib/fontisan/models/collection_validation_report.rb +104 -0
  33. data/lib/fontisan/models/font_report.rb +24 -0
  34. data/lib/fontisan/models/validation_report.rb +7 -2
  35. data/lib/fontisan/open_type_font.rb +18 -425
  36. data/lib/fontisan/optimizers/charstring_rewriter.rb +1 -1
  37. data/lib/fontisan/optimizers/subroutine_optimizer.rb +6 -2
  38. data/lib/fontisan/sfnt_font.rb +699 -0
  39. data/lib/fontisan/sfnt_table.rb +264 -0
  40. data/lib/fontisan/subset/glyph_mapping.rb +2 -0
  41. data/lib/fontisan/subset/table_subsetter.rb +2 -2
  42. data/lib/fontisan/tables/cblc.rb +8 -4
  43. data/lib/fontisan/tables/cff/index.rb +2 -0
  44. data/lib/fontisan/tables/cff.rb +6 -3
  45. data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +1 -1
  46. data/lib/fontisan/tables/cff2.rb +1 -1
  47. data/lib/fontisan/tables/cmap.rb +5 -5
  48. data/lib/fontisan/tables/cmap_table.rb +231 -0
  49. data/lib/fontisan/tables/glyf.rb +8 -10
  50. data/lib/fontisan/tables/glyf_table.rb +255 -0
  51. data/lib/fontisan/tables/head.rb +3 -3
  52. data/lib/fontisan/tables/head_table.rb +111 -0
  53. data/lib/fontisan/tables/hhea.rb +4 -4
  54. data/lib/fontisan/tables/hhea_table.rb +255 -0
  55. data/lib/fontisan/tables/hmtx_table.rb +191 -0
  56. data/lib/fontisan/tables/loca_table.rb +212 -0
  57. data/lib/fontisan/tables/maxp.rb +2 -2
  58. data/lib/fontisan/tables/maxp_table.rb +258 -0
  59. data/lib/fontisan/tables/name.rb +1 -1
  60. data/lib/fontisan/tables/name_table.rb +176 -0
  61. data/lib/fontisan/tables/os2.rb +8 -8
  62. data/lib/fontisan/tables/os2_table.rb +329 -0
  63. data/lib/fontisan/tables/post.rb +2 -2
  64. data/lib/fontisan/tables/post_table.rb +183 -0
  65. data/lib/fontisan/tables/sbix.rb +5 -4
  66. data/lib/fontisan/true_type_font.rb +12 -464
  67. data/lib/fontisan/utilities/checksum_calculator.rb +0 -44
  68. data/lib/fontisan/validation/collection_validator.rb +4 -2
  69. data/lib/fontisan/validators/basic_validator.rb +11 -21
  70. data/lib/fontisan/validators/font_book_validator.rb +29 -50
  71. data/lib/fontisan/validators/opentype_validator.rb +24 -28
  72. data/lib/fontisan/validators/validator.rb +87 -66
  73. data/lib/fontisan/validators/web_font_validator.rb +16 -21
  74. data/lib/fontisan/version.rb +1 -1
  75. data/lib/fontisan/woff2/glyf_transformer.rb +31 -8
  76. data/lib/fontisan/woff2/hmtx_transformer.rb +2 -1
  77. data/lib/fontisan/woff2/table_transformer.rb +4 -2
  78. data/lib/fontisan/woff2_font.rb +4 -2
  79. data/lib/fontisan/woff_font.rb +46 -30
  80. data/lib/fontisan.rb +2 -2
  81. data/scripts/compare_stack_aware.rb +1 -1
  82. data/scripts/measure_optimization.rb +1 -2
  83. metadata +23 -2
@@ -138,7 +138,8 @@ module Fontisan
138
138
  num_glyphs = maxp_table.num_glyphs
139
139
 
140
140
  # Parse hmtx table
141
- advance_widths, lsbs = parse_hmtx_table(hmtx_data, num_h_metrics, num_glyphs)
141
+ advance_widths, lsbs = parse_hmtx_table(hmtx_data, num_h_metrics,
142
+ num_glyphs)
142
143
 
143
144
  # Build transformed hmtx table
144
145
  build_transformed_hmtx(advance_widths, lsbs, num_h_metrics, num_glyphs)
@@ -388,7 +389,8 @@ module Fontisan
388
389
  # @param num_h_metrics [Integer] Number of hMetric entries
389
390
  # @param num_glyphs [Integer] Total number of glyphs
390
391
  # @return [String] Transformed hmtx data
391
- def build_transformed_hmtx(advance_widths, lsbs, num_h_metrics, num_glyphs)
392
+ def build_transformed_hmtx(advance_widths, lsbs, num_h_metrics,
393
+ num_glyphs)
392
394
  data = String.new(encoding: Encoding::BINARY)
393
395
 
394
396
  # Flags: Use proportional encoding (not explicit) and explicit LSBs
@@ -666,7 +666,8 @@ module Fontisan
666
666
  offset += padding
667
667
  end
668
668
 
669
- # Write table directory
669
+ # Write table directory (all entries first)
670
+ # rubocop:disable Style/CombinableLoops - Must write directory entries first, then data
670
671
  table_records.each do |record|
671
672
  sfnt_data << record[:tag].ljust(4, "\x00")
672
673
  sfnt_data << [record[:checksum]].pack("N")
@@ -674,7 +675,7 @@ module Fontisan
674
675
  sfnt_data << [record[:length]].pack("N")
675
676
  end
676
677
 
677
- # Write table data with padding
678
+ # Then write all table data with padding
678
679
  table_records.each do |record|
679
680
  sfnt_data << record[:data]
680
681
 
@@ -683,6 +684,7 @@ module Fontisan
683
684
  Constants::TABLE_ALIGNMENT
684
685
  sfnt_data << ("\x00" * padding) if padding.positive?
685
686
  end
687
+ # rubocop:enable Style/CombinableLoops
686
688
 
687
689
  # Update checksumAdjustment in head table
688
690
  update_checksum_in_memory(sfnt_data, table_records)
@@ -438,12 +438,13 @@ module Fontisan
438
438
  io.write("\x00" * padding) if padding.positive?
439
439
  end
440
440
 
441
+ # Update checksum adjustment in head table BEFORE closing file
442
+ # This avoids Windows file locking issues when Tempfiles are used
443
+ update_checksum_adjustment_in_io(io)
444
+
441
445
  io.pos
442
446
  end
443
447
 
444
- # Update checksum adjustment in head table
445
- update_checksum_adjustment_in_file(output_path)
446
-
447
448
  File.size(output_path)
448
449
  end
449
450
 
@@ -458,43 +459,58 @@ module Fontisan
458
459
  [search_range, entry_selector, range_shift]
459
460
  end
460
461
 
461
- # Update checksumAdjustment field in head table
462
+ # Update checksumAdjustment field in head table using an open IO object
462
463
  #
463
- # @param path [String] Path to the font file
464
+ # @param io [IO] Open IO object positioned at start of file
464
465
  # @return [void]
465
- def update_checksum_adjustment_in_file(path)
466
- # Find head table position in output file
466
+ def update_checksum_adjustment_in_io(io)
467
+ # Save current position
468
+ current_pos = io.pos
469
+
470
+ # Find head table position in the file
467
471
  head_offset = nil
468
- File.open(path, "rb") do |io|
469
- io.seek(4) # Skip sfnt_version
470
- num_tables = io.read(2).unpack1("n")
471
- io.seek(12) # Start of table directory
472
-
473
- num_tables.times do
474
- tag = io.read(4)
475
- io.read(4) # checksum
476
- offset = io.read(4).unpack1("N")
477
- io.read(4) # length
478
-
479
- if tag == Constants::HEAD_TAG
480
- head_offset = offset
481
- break
482
- end
472
+ io.seek(4) # Skip sfnt_version
473
+ num_tables = io.read(2).unpack1("n")
474
+ io.seek(12) # Start of table directory
475
+
476
+ num_tables.times do
477
+ tag = io.read(4)
478
+ io.read(4) # checksum
479
+ offset = io.read(4).unpack1("N")
480
+ io.read(4) # length
481
+
482
+ if tag == Constants::HEAD_TAG
483
+ head_offset = offset
484
+ break
483
485
  end
484
486
  end
485
487
 
486
488
  return unless head_offset
487
489
 
488
- # Use tempfile-based checksum calculation for Windows compatibility
489
- File.open(path, "r+b") do |io|
490
- checksum, _tmpfile = Utilities::ChecksumCalculator.calculate_checksum_from_io_with_tempfile(io)
490
+ # Rewind to calculate checksum from the beginning
491
+ io.rewind
492
+
493
+ # Calculate checksum directly from IO to avoid Windows Tempfile issues
494
+ checksum = Utilities::ChecksumCalculator.calculate_checksum_from_io(io)
491
495
 
492
- # Calculate adjustment
493
- adjustment = Utilities::ChecksumCalculator.calculate_adjustment(checksum)
496
+ # Calculate adjustment
497
+ adjustment = Utilities::ChecksumCalculator.calculate_adjustment(checksum)
494
498
 
495
- # Write adjustment to head table (offset 8 within head table)
496
- io.seek(head_offset + 8)
497
- io.write([adjustment].pack("N"))
499
+ # Write adjustment to head table (offset 8 within head table)
500
+ io.seek(head_offset + 8)
501
+ io.write([adjustment].pack("N"))
502
+
503
+ # Restore original position
504
+ io.seek(current_pos)
505
+ end
506
+
507
+ # Update checksumAdjustment field in head table
508
+ #
509
+ # @param path [String] Path to the font file
510
+ # @return [void]
511
+ def update_checksum_adjustment_in_file(path)
512
+ File.open(path, "r+b") do |io|
513
+ update_checksum_adjustment_in_io(io)
498
514
  end
499
515
  end
500
516
  end
data/lib/fontisan.rb CHANGED
@@ -112,6 +112,8 @@ require_relative "fontisan/models/scripts_info"
112
112
  require_relative "fontisan/models/features_info"
113
113
  require_relative "fontisan/models/all_scripts_features_info"
114
114
  require_relative "fontisan/models/validation_report"
115
+ require_relative "fontisan/models/font_report"
116
+ require_relative "fontisan/models/collection_validation_report"
115
117
  require_relative "fontisan/models/font_export"
116
118
  require_relative "fontisan/models/collection_font_summary"
117
119
  require_relative "fontisan/models/collection_info"
@@ -319,8 +321,6 @@ module Fontisan
319
321
  end
320
322
 
321
323
  class << self
322
- private
323
-
324
324
  # Get loading mode for validation profile
325
325
  #
326
326
  # Temporarily disabled - will be reimplemented with new DSL framework
@@ -153,7 +153,7 @@ def main
153
153
  puts "\nStack-Aware Efficiency: #{efficiency_ratio}% of normal optimization"
154
154
  end
155
155
 
156
- time_overhead = ((stack_aware[:time] / unoptimized[:time] - 1) * 100).round(2)
156
+ time_overhead = (((stack_aware[:time] / unoptimized[:time]) - 1) * 100).round(2)
157
157
  puts "\nProcessing Time Overhead:"
158
158
  puts " Stack-Aware vs Unoptimized: +#{time_overhead}%"
159
159
 
@@ -96,8 +96,7 @@ def print_summary(results)
96
96
  puts "\n\n#{'=' * 80}"
97
97
  puts "SUMMARY TABLE"
98
98
  puts "=" * 80
99
- puts format("%-30s %8s %12s %12s %10s %8s",
100
- "Font", "Glyphs", "Before", "After", "Saved", "Reduction")
99
+ puts "Font Glyphs Before After Saved Reduction"
101
100
  puts "-" * 80
102
101
 
103
102
  results.each do |r|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-06 00:00:00.000000000 Z
11
+ date: 2026-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -111,14 +111,21 @@ files:
111
111
  - ".rspec"
112
112
  - ".rubocop.yml"
113
113
  - ".rubocop_todo.yml"
114
+ - CHANGELOG.md
114
115
  - Gemfile
115
116
  - LICENSE
116
117
  - README.adoc
117
118
  - Rakefile
118
119
  - benchmark/variation_quick_bench.rb
120
+ - docs/APPLE_LEGACY_FONTS.adoc
121
+ - docs/COLLECTION_VALIDATION.adoc
122
+ - docs/COLOR_FONTS.adoc
123
+ - docs/DOCUMENTATION_SUMMARY.md
119
124
  - docs/EXTRACT_TTC_MIGRATION.md
120
125
  - docs/FONT_HINTING.adoc
126
+ - docs/VALIDATION.adoc
121
127
  - docs/VARIABLE_FONT_OPERATIONS.adoc
128
+ - docs/WOFF_WOFF2_FORMATS.adoc
122
129
  - exe/fontisan
123
130
  - fontisan.gemspec
124
131
  - lib/fontisan.rb
@@ -203,12 +210,14 @@ files:
203
210
  - lib/fontisan/models/collection_font_summary.rb
204
211
  - lib/fontisan/models/collection_info.rb
205
212
  - lib/fontisan/models/collection_list_info.rb
213
+ - lib/fontisan/models/collection_validation_report.rb
206
214
  - lib/fontisan/models/color_glyph.rb
207
215
  - lib/fontisan/models/color_layer.rb
208
216
  - lib/fontisan/models/color_palette.rb
209
217
  - lib/fontisan/models/features_info.rb
210
218
  - lib/fontisan/models/font_export.rb
211
219
  - lib/fontisan/models/font_info.rb
220
+ - lib/fontisan/models/font_report.rb
212
221
  - lib/fontisan/models/font_summary.rb
213
222
  - lib/fontisan/models/glyph_info.rb
214
223
  - lib/fontisan/models/glyph_outline.rb
@@ -251,6 +260,8 @@ files:
251
260
  - lib/fontisan/pipeline/strategies/preserve_strategy.rb
252
261
  - lib/fontisan/pipeline/transformation_pipeline.rb
253
262
  - lib/fontisan/pipeline/variation_resolver.rb
263
+ - lib/fontisan/sfnt_font.rb
264
+ - lib/fontisan/sfnt_table.rb
254
265
  - lib/fontisan/subset/builder.rb
255
266
  - lib/fontisan/subset/glyph_mapping.rb
256
267
  - lib/fontisan/subset/options.rb
@@ -292,6 +303,7 @@ files:
292
303
  - lib/fontisan/tables/cff2/table_reader.rb
293
304
  - lib/fontisan/tables/cff2/variation_data_extractor.rb
294
305
  - lib/fontisan/tables/cmap.rb
306
+ - lib/fontisan/tables/cmap_table.rb
295
307
  - lib/fontisan/tables/colr.rb
296
308
  - lib/fontisan/tables/cpal.rb
297
309
  - lib/fontisan/tables/cvar.rb
@@ -302,20 +314,29 @@ files:
302
314
  - lib/fontisan/tables/glyf/curve_converter.rb
303
315
  - lib/fontisan/tables/glyf/glyph_builder.rb
304
316
  - lib/fontisan/tables/glyf/simple_glyph.rb
317
+ - lib/fontisan/tables/glyf_table.rb
305
318
  - lib/fontisan/tables/gpos.rb
306
319
  - lib/fontisan/tables/gsub.rb
307
320
  - lib/fontisan/tables/gvar.rb
308
321
  - lib/fontisan/tables/head.rb
322
+ - lib/fontisan/tables/head_table.rb
309
323
  - lib/fontisan/tables/hhea.rb
324
+ - lib/fontisan/tables/hhea_table.rb
310
325
  - lib/fontisan/tables/hmtx.rb
326
+ - lib/fontisan/tables/hmtx_table.rb
311
327
  - lib/fontisan/tables/hvar.rb
312
328
  - lib/fontisan/tables/layout_common.rb
313
329
  - lib/fontisan/tables/loca.rb
330
+ - lib/fontisan/tables/loca_table.rb
314
331
  - lib/fontisan/tables/maxp.rb
332
+ - lib/fontisan/tables/maxp_table.rb
315
333
  - lib/fontisan/tables/mvar.rb
316
334
  - lib/fontisan/tables/name.rb
335
+ - lib/fontisan/tables/name_table.rb
317
336
  - lib/fontisan/tables/os2.rb
337
+ - lib/fontisan/tables/os2_table.rb
318
338
  - lib/fontisan/tables/post.rb
339
+ - lib/fontisan/tables/post_table.rb
319
340
  - lib/fontisan/tables/sbix.rb
320
341
  - lib/fontisan/tables/svg.rb
321
342
  - lib/fontisan/tables/variation_common.rb