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,187 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Comparison script for stack-aware vs normal subroutine optimization
5
+ #
6
+ # Usage:
7
+ # ruby scripts/compare_stack_aware.rb [font_path]
8
+ #
9
+ # If no font path provided, uses NotoSans-Regular.ttf from fixtures
10
+
11
+ require_relative "../lib/fontisan"
12
+
13
+ def format_bytes(bytes)
14
+ if bytes < 1024
15
+ "#{bytes} B"
16
+ elsif bytes < 1024 * 1024
17
+ "#{(bytes / 1024.0).round(2)} KB"
18
+ else
19
+ "#{(bytes / (1024.0 * 1024)).round(2)} MB"
20
+ end
21
+ end
22
+
23
+ def format_time(seconds)
24
+ if seconds < 1
25
+ "#{(seconds * 1000).round(1)} ms"
26
+ else
27
+ "#{seconds.round(2)} s"
28
+ end
29
+ end
30
+
31
+ def measure_optimization(font_path, mode)
32
+ puts "\n#{if mode == :none
33
+ 'Unoptimized'
34
+ else
35
+ mode == :normal ? 'Normal Optimization' : 'Stack-Aware Optimization'
36
+ end}:"
37
+ puts "=" * 60
38
+
39
+ font = Fontisan::FontLoader.load(font_path)
40
+ converter = Fontisan::Converters::OutlineConverter.new
41
+
42
+ options = {
43
+ target_format: :otf,
44
+ optimize_subroutines: mode != :none,
45
+ stack_aware: mode == :stack_aware,
46
+ verbose: false,
47
+ }
48
+
49
+ start_time = Time.now
50
+ result = converter.convert(font, options)
51
+ processing_time = Time.now - start_time
52
+
53
+ cff_size = result["CFF "].bytesize
54
+ optimization = result.instance_variable_get(:@subroutine_optimization)
55
+
56
+ puts " CFF table size: #{format_bytes(cff_size)}"
57
+ puts " Processing time: #{format_time(processing_time)}"
58
+
59
+ if optimization
60
+ puts " Patterns found: #{optimization[:pattern_count]}"
61
+ puts " Patterns selected: #{optimization[:selected_count]}"
62
+ puts " Local subroutines: #{optimization[:local_subrs].length}"
63
+ puts " Estimated savings: #{format_bytes(optimization[:savings])}"
64
+ puts " Bias: #{optimization[:bias]}"
65
+ else
66
+ puts " No optimization performed"
67
+ end
68
+
69
+ {
70
+ size: cff_size,
71
+ time: processing_time,
72
+ optimization: optimization,
73
+ }
74
+ end
75
+
76
+ def main
77
+ font_path = ARGV[0] || "spec/fixtures/fonts/NotoSans-Regular.ttf"
78
+
79
+ unless File.exist?(font_path)
80
+ puts "Error: Font file not found: #{font_path}"
81
+ puts "Usage: ruby scripts/compare_stack_aware.rb [font_path]"
82
+ exit 1
83
+ end
84
+
85
+ puts "╔═══════════════════════════════════════════════════════════════╗"
86
+ puts "║ Stack-Aware vs Normal Optimization Comparison ║"
87
+ puts "╚═══════════════════════════════════════════════════════════════╝"
88
+ puts
89
+ puts "Font: #{font_path}"
90
+
91
+ # Measure all three modes
92
+ unoptimized = measure_optimization(font_path, :none)
93
+ normal = measure_optimization(font_path, :normal)
94
+ stack_aware = measure_optimization(font_path, :stack_aware)
95
+
96
+ # Summary comparison
97
+ puts "\n#{'=' * 60}"
98
+ puts "SUMMARY COMPARISON"
99
+ puts "=" * 60
100
+
101
+ puts "\nFile Sizes:"
102
+ puts " Unoptimized: #{format_bytes(unoptimized[:size])} (baseline)"
103
+ puts " Normal: #{format_bytes(normal[:size])} (#{((normal[:size] - unoptimized[:size]) * 100.0 / unoptimized[:size]).round(2)}% change)"
104
+ puts " Stack-Aware: #{format_bytes(stack_aware[:size])} (#{((stack_aware[:size] - unoptimized[:size]) * 100.0 / unoptimized[:size]).round(2)}% change)"
105
+
106
+ puts "\nProcessing Times:"
107
+ puts " Unoptimized: #{format_time(unoptimized[:time])}"
108
+ puts " Normal: #{format_time(normal[:time])}"
109
+ puts " Stack-Aware: #{format_time(stack_aware[:time])}"
110
+
111
+ if normal[:optimization] && stack_aware[:optimization]
112
+ puts "\nOptimization Metrics:"
113
+ puts " Patterns Found:"
114
+ puts " Normal: #{normal[:optimization][:pattern_count]}"
115
+ puts " Stack-Aware: #{stack_aware[:optimization][:pattern_count]}"
116
+
117
+ puts " Patterns Selected:"
118
+ puts " Normal: #{normal[:optimization][:selected_count]}"
119
+ puts " Stack-Aware: #{stack_aware[:optimization][:selected_count]}"
120
+
121
+ puts " Subroutines Generated:"
122
+ puts " Normal: #{normal[:optimization][:local_subrs].length}"
123
+ puts " Stack-Aware: #{stack_aware[:optimization][:local_subrs].length}"
124
+
125
+ puts "\nBytes Saved (estimated):"
126
+ puts " Normal: #{format_bytes(normal[:optimization][:savings])}"
127
+ puts " Stack-Aware: #{format_bytes(stack_aware[:optimization][:savings])}"
128
+ end
129
+
130
+ puts "\n#{'=' * 60}"
131
+ puts "ANALYSIS"
132
+ puts "=" * 60
133
+
134
+ # Calculate relative improvements
135
+ normal_reduction = unoptimized[:size] - normal[:size]
136
+ stack_aware_reduction = unoptimized[:size] - stack_aware[:size]
137
+
138
+ puts "\nFile Size Reduction:"
139
+ if normal_reduction.positive?
140
+ puts " Normal: #{format_bytes(normal_reduction)} (#{(normal_reduction * 100.0 / unoptimized[:size]).round(2)}%)"
141
+ else
142
+ puts " Normal: No reduction achieved"
143
+ end
144
+
145
+ if stack_aware_reduction.positive?
146
+ puts " Stack-Aware: #{format_bytes(stack_aware_reduction)} (#{(stack_aware_reduction * 100.0 / unoptimized[:size]).round(2)}%)"
147
+ else
148
+ puts " Stack-Aware: No reduction achieved"
149
+ end
150
+
151
+ if normal_reduction.positive? && stack_aware_reduction.positive?
152
+ efficiency_ratio = (stack_aware_reduction.to_f / normal_reduction * 100).round(2)
153
+ puts "\nStack-Aware Efficiency: #{efficiency_ratio}% of normal optimization"
154
+ end
155
+
156
+ time_overhead = ((stack_aware[:time] / unoptimized[:time] - 1) * 100).round(2)
157
+ puts "\nProcessing Time Overhead:"
158
+ puts " Stack-Aware vs Unoptimized: +#{time_overhead}%"
159
+
160
+ puts "\n#{'=' * 60}"
161
+ puts "CONCLUSION"
162
+ puts "=" * 60
163
+
164
+ if stack_aware_reduction.positive?
165
+ puts "\n✓ Stack-aware optimization achieved #{(stack_aware_reduction * 100.0 / unoptimized[:size]).round(2)}% file size reduction"
166
+ puts "✓ Trade-off: More reliable (stack-neutral patterns only)"
167
+ if normal_reduction > stack_aware_reduction
168
+ diff = normal_reduction - stack_aware_reduction
169
+ puts "✓ Cost: #{format_bytes(diff)} less compression vs normal mode"
170
+ elsif stack_aware_reduction > normal_reduction
171
+ diff = stack_aware_reduction - normal_reduction
172
+ puts "✓ Bonus: #{format_bytes(diff)} more compression than normal mode!"
173
+ else
174
+ puts "✓ Same compression as normal mode"
175
+ end
176
+ else
177
+ puts "\n⚠ Stack-aware optimization did not achieve compression for this font"
178
+ puts " This may indicate:"
179
+ puts " - Few stack-neutral patterns available"
180
+ puts " - Font has simple glyph structures"
181
+ puts " - Pattern sizes too small after stack validation"
182
+ end
183
+
184
+ puts
185
+ end
186
+
187
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/fontisan"
5
+
6
+ def measure_font(font_path, options = {})
7
+ puts "\n#{'=' * 80}"
8
+ puts "Analyzing: #{File.basename(font_path)}"
9
+ puts "=" * 80
10
+
11
+ font = Fontisan::FontLoader.load(font_path)
12
+ converter = Fontisan::Converters::OutlineConverter.new
13
+
14
+ # Get glyph count
15
+ maxp = font.table("maxp")
16
+ num_glyphs = maxp.num_glyphs
17
+ puts "Glyphs: #{num_glyphs}"
18
+
19
+ # Measure without optimization
20
+ puts "\n[1/2] Converting without optimization..."
21
+ start_time = Time.now
22
+ tables_unopt = converter.convert(font,
23
+ target_format: :otf,
24
+ optimize_subroutines: false)
25
+ time_unopt = Time.now - start_time
26
+ size_unopt = tables_unopt["CFF "].bytesize
27
+
28
+ # Measure with optimization
29
+ puts "[2/2] Converting with optimization..."
30
+ start_time = Time.now
31
+ tables_opt = converter.convert(font,
32
+ target_format: :otf,
33
+ optimize_subroutines: true,
34
+ min_pattern_length: options[:min_pattern_length] || 10,
35
+ max_subroutines: options[:max_subroutines] || 1000,
36
+ verbose: false)
37
+ time_opt = Time.now - start_time
38
+ size_opt = tables_opt["CFF "].bytesize
39
+
40
+ # Get optimization details
41
+ opt_result = tables_opt.instance_variable_get(:@subroutine_optimization)
42
+
43
+ # Calculate metrics
44
+ savings = size_unopt - size_opt
45
+ reduction_percent = (savings * 100.0 / size_unopt).round(2)
46
+ size_per_glyph_unopt = (size_unopt.to_f / num_glyphs).round(1)
47
+ size_per_glyph_opt = (size_opt.to_f / num_glyphs).round(1)
48
+
49
+ # Display results
50
+ puts "\n#{'-' * 80}"
51
+ puts "RESULTS"
52
+ puts "-" * 80
53
+ puts "File Sizes:"
54
+ puts " Without optimization: #{format('%10d', size_unopt)} bytes (#{size_per_glyph_unopt} bytes/glyph)"
55
+ puts " With optimization: #{format('%10d', size_opt)} bytes (#{size_per_glyph_opt} bytes/glyph)"
56
+ puts " Bytes saved: #{format('%10d', savings)} bytes"
57
+ puts " Reduction: #{format('%9.2f', reduction_percent)}%"
58
+
59
+ puts "\nProcessing Time:"
60
+ puts " Without optimization: #{format('%.2f', time_unopt)} seconds"
61
+ puts " With optimization: #{format('%.2f', time_opt)} seconds"
62
+ puts " Overhead: #{format('%.2f', time_opt - time_unopt)} seconds"
63
+
64
+ if opt_result
65
+ puts "\nOptimization Details:"
66
+ puts " Patterns found: #{format('%10d', opt_result[:pattern_count])}"
67
+ puts " Patterns selected: #{format('%10d', opt_result[:selected_count])}"
68
+ puts " Subroutines: #{format('%10d', opt_result[:local_subrs].length)}"
69
+ puts " CFF bias: #{format('%10d', opt_result[:bias])}"
70
+ puts " Est. bytes saved: #{format('%10d', opt_result[:savings])}"
71
+ end
72
+
73
+ {
74
+ font: File.basename(font_path),
75
+ num_glyphs: num_glyphs,
76
+ size_unopt: size_unopt,
77
+ size_opt: size_opt,
78
+ savings: savings,
79
+ reduction_percent: reduction_percent,
80
+ time_unopt: time_unopt,
81
+ time_opt: time_opt,
82
+ patterns_found: opt_result[:pattern_count],
83
+ patterns_selected: opt_result[:selected_count],
84
+ subroutines: opt_result[:local_subrs].length,
85
+ }
86
+ end
87
+
88
+ def print_summary(results)
89
+ return if results.empty?
90
+
91
+ puts "\n\n#{'=' * 80}"
92
+ puts "SUMMARY TABLE"
93
+ puts "=" * 80
94
+ puts format("%-30s %8s %12s %12s %10s %8s",
95
+ "Font", "Glyphs", "Before", "After", "Saved", "Reduction")
96
+ puts "-" * 80
97
+
98
+ results.each do |r|
99
+ puts format("%-30s %8d %12d %12d %10d %7.2f%%",
100
+ r[:font], r[:num_glyphs],
101
+ r[:size_unopt], r[:size_opt],
102
+ r[:savings], r[:reduction_percent])
103
+ end
104
+
105
+ # Calculate averages
106
+ avg_reduction = (results.sum { |r| r[:reduction_percent] } / results.length).round(2)
107
+ total_saved = results.sum { |r| r[:savings] }
108
+
109
+ puts "-" * 80
110
+ puts "Average reduction: #{avg_reduction}%"
111
+ puts "Total bytes saved: #{total_saved} bytes"
112
+ puts "=" * 80
113
+ end
114
+
115
+ # Main execution
116
+ if __FILE__ == $PROGRAM_NAME
117
+ font_paths = ARGV
118
+
119
+ if font_paths.empty?
120
+ # Use fixtures if no arguments provided
121
+ fixtures_dir = File.join(File.dirname(__FILE__), "..", "spec", "fixtures", "fonts")
122
+ font_paths = Dir.glob(File.join(fixtures_dir, "*.ttf"))
123
+ end
124
+
125
+ if font_paths.empty?
126
+ puts "Usage: ruby #{__FILE__} <font1.ttf> [font2.ttf ...]"
127
+ puts " or: ruby #{__FILE__} (uses fixture fonts)"
128
+ exit 1
129
+ end
130
+
131
+ results = []
132
+ font_paths.each do |path|
133
+ result = measure_font(path)
134
+ results << result
135
+ rescue StandardError => e
136
+ puts "\nERROR: Failed to process #{File.basename(path)}"
137
+ puts " #{e.message}"
138
+ end
139
+
140
+ print_summary(results)
141
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-08 00:00:00.000000000 Z
11
+ date: 2025-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bindata
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
40
  version: '2.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: brotli
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.5'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: lutaml-model
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -38,20 +66,34 @@ dependencies:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
68
  version: '0.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.16'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.16'
41
83
  - !ruby/object:Gem::Dependency
42
84
  name: thor
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
87
  - - "~>"
46
88
  - !ruby/object:Gem::Version
47
- version: '1.4'
89
+ version: '1.3'
48
90
  type: :runtime
49
91
  prerelease: false
50
92
  version_requirements: !ruby/object:Gem::Requirement
51
93
  requirements:
52
94
  - - "~>"
53
95
  - !ruby/object:Gem::Version
54
- version: '1.4'
96
+ version: '1.3'
55
97
  description: |
56
98
  Fontisan provides font analysis tools and utilities. It is
57
99
  designed as a pure Ruby implementation with full object-oriented architecture,
@@ -73,53 +115,212 @@ files:
73
115
  - LICENSE
74
116
  - README.adoc
75
117
  - Rakefile
118
+ - benchmark/variation_quick_bench.rb
119
+ - docs/EXTRACT_TTC_MIGRATION.md
76
120
  - exe/fontisan
77
121
  - fontisan.gemspec
78
122
  - lib/fontisan.rb
79
123
  - lib/fontisan/binary/base_record.rb
80
124
  - lib/fontisan/binary/structures.rb
81
125
  - lib/fontisan/cli.rb
126
+ - lib/fontisan/collection/builder.rb
127
+ - lib/fontisan/collection/offset_calculator.rb
128
+ - lib/fontisan/collection/table_analyzer.rb
129
+ - lib/fontisan/collection/table_deduplicator.rb
130
+ - lib/fontisan/collection/writer.rb
82
131
  - lib/fontisan/commands/base_command.rb
132
+ - lib/fontisan/commands/convert_command.rb
83
133
  - lib/fontisan/commands/dump_table_command.rb
134
+ - lib/fontisan/commands/export_command.rb
84
135
  - lib/fontisan/commands/features_command.rb
85
136
  - lib/fontisan/commands/glyphs_command.rb
86
137
  - lib/fontisan/commands/info_command.rb
138
+ - lib/fontisan/commands/instance_command.rb
139
+ - lib/fontisan/commands/ls_command.rb
87
140
  - lib/fontisan/commands/optical_size_command.rb
141
+ - lib/fontisan/commands/pack_command.rb
88
142
  - lib/fontisan/commands/scripts_command.rb
143
+ - lib/fontisan/commands/subset_command.rb
89
144
  - lib/fontisan/commands/tables_command.rb
90
145
  - lib/fontisan/commands/unicode_command.rb
146
+ - lib/fontisan/commands/unpack_command.rb
147
+ - lib/fontisan/commands/validate_command.rb
91
148
  - lib/fontisan/commands/variable_command.rb
149
+ - lib/fontisan/config/collection_settings.yml
150
+ - lib/fontisan/config/conversion_matrix.yml
151
+ - lib/fontisan/config/export_settings.yml
92
152
  - lib/fontisan/config/features.yml
93
153
  - lib/fontisan/config/scripts.yml
154
+ - lib/fontisan/config/subset_profiles.yml
155
+ - lib/fontisan/config/svg_settings.yml
156
+ - lib/fontisan/config/validation_rules.yml
157
+ - lib/fontisan/config/variable_settings.yml
158
+ - lib/fontisan/config/woff2_settings.yml
94
159
  - lib/fontisan/constants.rb
160
+ - lib/fontisan/converters/conversion_strategy.rb
161
+ - lib/fontisan/converters/format_converter.rb
162
+ - lib/fontisan/converters/outline_converter.rb
163
+ - lib/fontisan/converters/svg_generator.rb
164
+ - lib/fontisan/converters/table_copier.rb
165
+ - lib/fontisan/converters/woff2_encoder.rb
166
+ - lib/fontisan/converters/woff_writer.rb
95
167
  - lib/fontisan/error.rb
168
+ - lib/fontisan/export/exporter.rb
169
+ - lib/fontisan/export/table_serializer.rb
170
+ - lib/fontisan/export/transformers/font_to_ttx.rb
171
+ - lib/fontisan/export/transformers/head_transformer.rb
172
+ - lib/fontisan/export/transformers/hhea_transformer.rb
173
+ - lib/fontisan/export/transformers/maxp_transformer.rb
174
+ - lib/fontisan/export/transformers/name_transformer.rb
175
+ - lib/fontisan/export/transformers/os2_transformer.rb
176
+ - lib/fontisan/export/transformers/post_transformer.rb
177
+ - lib/fontisan/export/ttx_generator.rb
178
+ - lib/fontisan/export/ttx_parser.rb
96
179
  - lib/fontisan/font_loader.rb
180
+ - lib/fontisan/font_writer.rb
97
181
  - lib/fontisan/formatters/text_formatter.rb
182
+ - lib/fontisan/glyph_accessor.rb
183
+ - lib/fontisan/hints/hint_converter.rb
184
+ - lib/fontisan/hints/postscript_hint_applier.rb
185
+ - lib/fontisan/hints/postscript_hint_extractor.rb
186
+ - lib/fontisan/hints/truetype_hint_applier.rb
187
+ - lib/fontisan/hints/truetype_hint_extractor.rb
188
+ - lib/fontisan/loading_modes.rb
189
+ - lib/fontisan/metrics_calculator.rb
98
190
  - lib/fontisan/models/all_scripts_features_info.rb
191
+ - lib/fontisan/models/collection_font_summary.rb
192
+ - lib/fontisan/models/collection_info.rb
193
+ - lib/fontisan/models/collection_list_info.rb
99
194
  - lib/fontisan/models/features_info.rb
195
+ - lib/fontisan/models/font_export.rb
100
196
  - lib/fontisan/models/font_info.rb
197
+ - lib/fontisan/models/font_summary.rb
101
198
  - lib/fontisan/models/glyph_info.rb
199
+ - lib/fontisan/models/glyph_outline.rb
200
+ - lib/fontisan/models/hint.rb
102
201
  - lib/fontisan/models/optical_size_info.rb
202
+ - lib/fontisan/models/outline.rb
103
203
  - lib/fontisan/models/scripts_info.rb
104
204
  - lib/fontisan/models/table_info.rb
205
+ - lib/fontisan/models/table_sharing_info.rb
206
+ - lib/fontisan/models/ttx/glyph_order.rb
207
+ - lib/fontisan/models/ttx/tables/binary_table.rb
208
+ - lib/fontisan/models/ttx/tables/head_table.rb
209
+ - lib/fontisan/models/ttx/tables/hhea_table.rb
210
+ - lib/fontisan/models/ttx/tables/maxp_table.rb
211
+ - lib/fontisan/models/ttx/tables/name_table.rb
212
+ - lib/fontisan/models/ttx/tables/os2_table.rb
213
+ - lib/fontisan/models/ttx/tables/post_table.rb
214
+ - lib/fontisan/models/ttx/ttfont.rb
105
215
  - lib/fontisan/models/unicode_mappings.rb
216
+ - lib/fontisan/models/validation_report.rb
106
217
  - lib/fontisan/models/variable_font_info.rb
107
218
  - lib/fontisan/open_type_collection.rb
108
219
  - lib/fontisan/open_type_font.rb
220
+ - lib/fontisan/optimizers/charstring_rewriter.rb
221
+ - lib/fontisan/optimizers/pattern_analyzer.rb
222
+ - lib/fontisan/optimizers/stack_tracker.rb
223
+ - lib/fontisan/optimizers/subroutine_builder.rb
224
+ - lib/fontisan/optimizers/subroutine_generator.rb
225
+ - lib/fontisan/optimizers/subroutine_optimizer.rb
226
+ - lib/fontisan/outline_extractor.rb
109
227
  - lib/fontisan/parsers/tag.rb
228
+ - lib/fontisan/subset/builder.rb
229
+ - lib/fontisan/subset/glyph_mapping.rb
230
+ - lib/fontisan/subset/options.rb
231
+ - lib/fontisan/subset/profile.rb
232
+ - lib/fontisan/subset/table_subsetter.rb
233
+ - lib/fontisan/svg/font_face_generator.rb
234
+ - lib/fontisan/svg/font_generator.rb
235
+ - lib/fontisan/svg/glyph_generator.rb
236
+ - lib/fontisan/svg/view_box_calculator.rb
237
+ - lib/fontisan/tables/cff.rb
238
+ - lib/fontisan/tables/cff/cff_glyph.rb
239
+ - lib/fontisan/tables/cff/charset.rb
240
+ - lib/fontisan/tables/cff/charstring.rb
241
+ - lib/fontisan/tables/cff/charstring_builder.rb
242
+ - lib/fontisan/tables/cff/charstrings_index.rb
243
+ - lib/fontisan/tables/cff/dict.rb
244
+ - lib/fontisan/tables/cff/dict_builder.rb
245
+ - lib/fontisan/tables/cff/encoding.rb
246
+ - lib/fontisan/tables/cff/header.rb
247
+ - lib/fontisan/tables/cff/index.rb
248
+ - lib/fontisan/tables/cff/index_builder.rb
249
+ - lib/fontisan/tables/cff/private_dict.rb
250
+ - lib/fontisan/tables/cff/top_dict.rb
251
+ - lib/fontisan/tables/cff2.rb
252
+ - lib/fontisan/tables/cff2/blend_operator.rb
253
+ - lib/fontisan/tables/cff2/charstring_parser.rb
254
+ - lib/fontisan/tables/cff2/operand_stack.rb
110
255
  - lib/fontisan/tables/cmap.rb
256
+ - lib/fontisan/tables/cvar.rb
111
257
  - lib/fontisan/tables/fvar.rb
258
+ - lib/fontisan/tables/glyf.rb
259
+ - lib/fontisan/tables/glyf/compound_glyph.rb
260
+ - lib/fontisan/tables/glyf/compound_glyph_resolver.rb
261
+ - lib/fontisan/tables/glyf/curve_converter.rb
262
+ - lib/fontisan/tables/glyf/glyph_builder.rb
263
+ - lib/fontisan/tables/glyf/simple_glyph.rb
112
264
  - lib/fontisan/tables/gpos.rb
113
265
  - lib/fontisan/tables/gsub.rb
266
+ - lib/fontisan/tables/gvar.rb
114
267
  - lib/fontisan/tables/head.rb
268
+ - lib/fontisan/tables/hhea.rb
269
+ - lib/fontisan/tables/hmtx.rb
270
+ - lib/fontisan/tables/hvar.rb
115
271
  - lib/fontisan/tables/layout_common.rb
272
+ - lib/fontisan/tables/loca.rb
273
+ - lib/fontisan/tables/maxp.rb
274
+ - lib/fontisan/tables/mvar.rb
116
275
  - lib/fontisan/tables/name.rb
117
276
  - lib/fontisan/tables/os2.rb
118
277
  - lib/fontisan/tables/post.rb
278
+ - lib/fontisan/tables/variation_common.rb
279
+ - lib/fontisan/tables/vvar.rb
119
280
  - lib/fontisan/true_type_collection.rb
120
281
  - lib/fontisan/true_type_font.rb
282
+ - lib/fontisan/utilities/brotli_wrapper.rb
121
283
  - lib/fontisan/utilities/checksum_calculator.rb
284
+ - lib/fontisan/utils/thread_pool.rb
285
+ - lib/fontisan/validation/checksum_validator.rb
286
+ - lib/fontisan/validation/consistency_validator.rb
287
+ - lib/fontisan/validation/structure_validator.rb
288
+ - lib/fontisan/validation/table_validator.rb
289
+ - lib/fontisan/validation/validator.rb
290
+ - lib/fontisan/variable/axis_normalizer.rb
291
+ - lib/fontisan/variable/delta_applicator.rb
292
+ - lib/fontisan/variable/glyph_delta_processor.rb
293
+ - lib/fontisan/variable/instancer.rb
294
+ - lib/fontisan/variable/metric_delta_processor.rb
295
+ - lib/fontisan/variable/region_matcher.rb
296
+ - lib/fontisan/variable/static_font_builder.rb
297
+ - lib/fontisan/variable/table_updater.rb
298
+ - lib/fontisan/variation/blend_applier.rb
299
+ - lib/fontisan/variation/cache.rb
300
+ - lib/fontisan/variation/cache_key_builder.rb
301
+ - lib/fontisan/variation/converter.rb
302
+ - lib/fontisan/variation/data_extractor.rb
303
+ - lib/fontisan/variation/delta_applier.rb
304
+ - lib/fontisan/variation/delta_parser.rb
305
+ - lib/fontisan/variation/inspector.rb
306
+ - lib/fontisan/variation/instance_generator.rb
307
+ - lib/fontisan/variation/interpolator.rb
308
+ - lib/fontisan/variation/metrics_adjuster.rb
309
+ - lib/fontisan/variation/optimizer.rb
310
+ - lib/fontisan/variation/parallel_generator.rb
311
+ - lib/fontisan/variation/region_matcher.rb
312
+ - lib/fontisan/variation/subsetter.rb
313
+ - lib/fontisan/variation/table_accessor.rb
314
+ - lib/fontisan/variation/validator.rb
315
+ - lib/fontisan/variation/variation_context.rb
122
316
  - lib/fontisan/version.rb
317
+ - lib/fontisan/woff2/directory.rb
318
+ - lib/fontisan/woff2/header.rb
319
+ - lib/fontisan/woff2/table_transformer.rb
320
+ - lib/fontisan/woff2_font.rb
321
+ - lib/fontisan/woff_font.rb
322
+ - scripts/compare_stack_aware.rb
323
+ - scripts/measure_optimization.rb
123
324
  homepage: https://github.com/fontist/fontisan
124
325
  licenses:
125
326
  - BSD-2-Clause