fontisan 0.1.0 → 0.2.1

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 (214) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +672 -69
  3. data/Gemfile +1 -0
  4. data/LICENSE +5 -1
  5. data/README.adoc +1477 -297
  6. data/Rakefile +63 -41
  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 +364 -4
  12. data/lib/fontisan/collection/builder.rb +341 -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 +317 -0
  16. data/lib/fontisan/collection/writer.rb +306 -0
  17. data/lib/fontisan/commands/base_command.rb +24 -1
  18. data/lib/fontisan/commands/convert_command.rb +218 -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 +286 -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 +203 -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 +79 -0
  37. data/lib/fontisan/converters/conversion_strategy.rb +96 -0
  38. data/lib/fontisan/converters/format_converter.rb +408 -0
  39. data/lib/fontisan/converters/outline_converter.rb +998 -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 +122 -15
  57. data/lib/fontisan/font_writer.rb +302 -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 +310 -0
  61. data/lib/fontisan/hints/postscript_hint_applier.rb +266 -0
  62. data/lib/fontisan/hints/postscript_hint_extractor.rb +354 -0
  63. data/lib/fontisan/hints/truetype_hint_applier.rb +117 -0
  64. data/lib/fontisan/hints/truetype_hint_extractor.rb +289 -0
  65. data/lib/fontisan/loading_modes.rb +115 -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 +405 -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 +321 -19
  88. data/lib/fontisan/open_type_font_extensions.rb +54 -0
  89. data/lib/fontisan/optimizers/charstring_rewriter.rb +161 -0
  90. data/lib/fontisan/optimizers/pattern_analyzer.rb +308 -0
  91. data/lib/fontisan/optimizers/stack_tracker.rb +246 -0
  92. data/lib/fontisan/optimizers/subroutine_builder.rb +134 -0
  93. data/lib/fontisan/optimizers/subroutine_generator.rb +207 -0
  94. data/lib/fontisan/optimizers/subroutine_optimizer.rb +107 -0
  95. data/lib/fontisan/outline_extractor.rb +423 -0
  96. data/lib/fontisan/pipeline/format_detector.rb +249 -0
  97. data/lib/fontisan/pipeline/output_writer.rb +154 -0
  98. data/lib/fontisan/pipeline/strategies/base_strategy.rb +75 -0
  99. data/lib/fontisan/pipeline/strategies/instance_strategy.rb +93 -0
  100. data/lib/fontisan/pipeline/strategies/named_strategy.rb +118 -0
  101. data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +56 -0
  102. data/lib/fontisan/pipeline/transformation_pipeline.rb +411 -0
  103. data/lib/fontisan/pipeline/variation_resolver.rb +165 -0
  104. data/lib/fontisan/subset/builder.rb +268 -0
  105. data/lib/fontisan/subset/glyph_mapping.rb +215 -0
  106. data/lib/fontisan/subset/options.rb +142 -0
  107. data/lib/fontisan/subset/profile.rb +152 -0
  108. data/lib/fontisan/subset/table_subsetter.rb +461 -0
  109. data/lib/fontisan/svg/font_face_generator.rb +278 -0
  110. data/lib/fontisan/svg/font_generator.rb +264 -0
  111. data/lib/fontisan/svg/glyph_generator.rb +168 -0
  112. data/lib/fontisan/svg/view_box_calculator.rb +137 -0
  113. data/lib/fontisan/tables/cff/cff_glyph.rb +176 -0
  114. data/lib/fontisan/tables/cff/charset.rb +282 -0
  115. data/lib/fontisan/tables/cff/charstring.rb +934 -0
  116. data/lib/fontisan/tables/cff/charstring_builder.rb +356 -0
  117. data/lib/fontisan/tables/cff/charstring_parser.rb +237 -0
  118. data/lib/fontisan/tables/cff/charstring_rebuilder.rb +172 -0
  119. data/lib/fontisan/tables/cff/charstrings_index.rb +162 -0
  120. data/lib/fontisan/tables/cff/dict.rb +351 -0
  121. data/lib/fontisan/tables/cff/dict_builder.rb +257 -0
  122. data/lib/fontisan/tables/cff/encoding.rb +274 -0
  123. data/lib/fontisan/tables/cff/header.rb +102 -0
  124. data/lib/fontisan/tables/cff/hint_operation_injector.rb +207 -0
  125. data/lib/fontisan/tables/cff/index.rb +237 -0
  126. data/lib/fontisan/tables/cff/index_builder.rb +170 -0
  127. data/lib/fontisan/tables/cff/offset_recalculator.rb +70 -0
  128. data/lib/fontisan/tables/cff/private_dict.rb +284 -0
  129. data/lib/fontisan/tables/cff/private_dict_writer.rb +125 -0
  130. data/lib/fontisan/tables/cff/table_builder.rb +221 -0
  131. data/lib/fontisan/tables/cff/top_dict.rb +236 -0
  132. data/lib/fontisan/tables/cff.rb +489 -0
  133. data/lib/fontisan/tables/cff2/blend_operator.rb +240 -0
  134. data/lib/fontisan/tables/cff2/charstring_parser.rb +591 -0
  135. data/lib/fontisan/tables/cff2/operand_stack.rb +232 -0
  136. data/lib/fontisan/tables/cff2/private_dict_blend_handler.rb +246 -0
  137. data/lib/fontisan/tables/cff2/region_matcher.rb +200 -0
  138. data/lib/fontisan/tables/cff2/table_builder.rb +574 -0
  139. data/lib/fontisan/tables/cff2/table_reader.rb +419 -0
  140. data/lib/fontisan/tables/cff2/variation_data_extractor.rb +212 -0
  141. data/lib/fontisan/tables/cff2.rb +346 -0
  142. data/lib/fontisan/tables/cvar.rb +203 -0
  143. data/lib/fontisan/tables/fvar.rb +2 -2
  144. data/lib/fontisan/tables/glyf/compound_glyph.rb +483 -0
  145. data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +136 -0
  146. data/lib/fontisan/tables/glyf/curve_converter.rb +343 -0
  147. data/lib/fontisan/tables/glyf/glyph_builder.rb +450 -0
  148. data/lib/fontisan/tables/glyf/simple_glyph.rb +382 -0
  149. data/lib/fontisan/tables/glyf.rb +235 -0
  150. data/lib/fontisan/tables/gvar.rb +231 -0
  151. data/lib/fontisan/tables/hhea.rb +124 -0
  152. data/lib/fontisan/tables/hmtx.rb +287 -0
  153. data/lib/fontisan/tables/hvar.rb +191 -0
  154. data/lib/fontisan/tables/loca.rb +322 -0
  155. data/lib/fontisan/tables/maxp.rb +192 -0
  156. data/lib/fontisan/tables/mvar.rb +185 -0
  157. data/lib/fontisan/tables/name.rb +99 -30
  158. data/lib/fontisan/tables/variation_common.rb +346 -0
  159. data/lib/fontisan/tables/vvar.rb +234 -0
  160. data/lib/fontisan/true_type_collection.rb +156 -2
  161. data/lib/fontisan/true_type_font.rb +321 -20
  162. data/lib/fontisan/true_type_font_extensions.rb +54 -0
  163. data/lib/fontisan/utilities/brotli_wrapper.rb +159 -0
  164. data/lib/fontisan/utilities/checksum_calculator.rb +60 -0
  165. data/lib/fontisan/utils/thread_pool.rb +134 -0
  166. data/lib/fontisan/validation/checksum_validator.rb +170 -0
  167. data/lib/fontisan/validation/consistency_validator.rb +197 -0
  168. data/lib/fontisan/validation/structure_validator.rb +198 -0
  169. data/lib/fontisan/validation/table_validator.rb +158 -0
  170. data/lib/fontisan/validation/validator.rb +152 -0
  171. data/lib/fontisan/validation/variable_font_validator.rb +218 -0
  172. data/lib/fontisan/variable/axis_normalizer.rb +215 -0
  173. data/lib/fontisan/variable/delta_applicator.rb +313 -0
  174. data/lib/fontisan/variable/glyph_delta_processor.rb +218 -0
  175. data/lib/fontisan/variable/instancer.rb +344 -0
  176. data/lib/fontisan/variable/metric_delta_processor.rb +282 -0
  177. data/lib/fontisan/variable/region_matcher.rb +208 -0
  178. data/lib/fontisan/variable/static_font_builder.rb +213 -0
  179. data/lib/fontisan/variable/table_updater.rb +219 -0
  180. data/lib/fontisan/variation/blend_applier.rb +199 -0
  181. data/lib/fontisan/variation/cache.rb +298 -0
  182. data/lib/fontisan/variation/cache_key_builder.rb +162 -0
  183. data/lib/fontisan/variation/converter.rb +375 -0
  184. data/lib/fontisan/variation/data_extractor.rb +86 -0
  185. data/lib/fontisan/variation/delta_applier.rb +266 -0
  186. data/lib/fontisan/variation/delta_parser.rb +228 -0
  187. data/lib/fontisan/variation/inspector.rb +275 -0
  188. data/lib/fontisan/variation/instance_generator.rb +273 -0
  189. data/lib/fontisan/variation/instance_writer.rb +341 -0
  190. data/lib/fontisan/variation/interpolator.rb +231 -0
  191. data/lib/fontisan/variation/metrics_adjuster.rb +318 -0
  192. data/lib/fontisan/variation/optimizer.rb +418 -0
  193. data/lib/fontisan/variation/parallel_generator.rb +150 -0
  194. data/lib/fontisan/variation/region_matcher.rb +221 -0
  195. data/lib/fontisan/variation/subsetter.rb +463 -0
  196. data/lib/fontisan/variation/table_accessor.rb +105 -0
  197. data/lib/fontisan/variation/tuple_variation_header.rb +51 -0
  198. data/lib/fontisan/variation/validator.rb +345 -0
  199. data/lib/fontisan/variation/variable_svg_generator.rb +268 -0
  200. data/lib/fontisan/variation/variation_context.rb +211 -0
  201. data/lib/fontisan/variation/variation_preserver.rb +288 -0
  202. data/lib/fontisan/version.rb +1 -1
  203. data/lib/fontisan/version.rb.orig +9 -0
  204. data/lib/fontisan/woff2/directory.rb +257 -0
  205. data/lib/fontisan/woff2/glyf_transformer.rb +666 -0
  206. data/lib/fontisan/woff2/header.rb +101 -0
  207. data/lib/fontisan/woff2/hmtx_transformer.rb +164 -0
  208. data/lib/fontisan/woff2/table_transformer.rb +163 -0
  209. data/lib/fontisan/woff2_font.rb +717 -0
  210. data/lib/fontisan/woff_font.rb +488 -0
  211. data/lib/fontisan.rb +132 -0
  212. data/scripts/compare_stack_aware.rb +187 -0
  213. data/scripts/measure_optimization.rb +141 -0
  214. metadata +234 -4
data/Rakefile CHANGED
@@ -9,67 +9,89 @@ require "rubocop/rake_task"
9
9
  RuboCop::RakeTask.new
10
10
 
11
11
  namespace :fixtures do
12
- fixtures_dir = "spec/fixtures/fonts"
12
+ # Load centralized fixture configuration
13
+ require_relative "spec/support/fixture_fonts"
14
+
15
+ # Helper method to download a single file
16
+ def download_single_file(name, url, target_path)
17
+ require "open-uri"
18
+
19
+ puts "[fixtures:download] Downloading #{name}..."
20
+ FileUtils.mkdir_p(File.dirname(target_path))
21
+
22
+ URI.open(url) do |remote|
23
+ File.binwrite(target_path, remote.read)
24
+ end
25
+
26
+ puts "[fixtures:download] #{name} downloaded successfully"
27
+ end
13
28
 
14
29
  # Helper method to download and extract a font archive
15
30
  def download_font(name, url, target_dir)
16
31
  require "open-uri"
17
32
  require "zip"
18
33
 
19
- zip_file = "#{target_dir}/#{name}.zip"
20
-
21
34
  puts "[fixtures:download] Downloading #{name}..."
22
35
  FileUtils.mkdir_p(target_dir)
23
36
 
24
- URI.open(url) do |remote|
25
- File.binwrite(zip_file, remote.read)
37
+ # Create a manual temp file path - OS will clean up temp files automatically
38
+ temp_path = File.join(Dir.tmpdir, "fontisan_#{name}_#{Process.pid}_#{rand(10000)}.zip")
39
+
40
+ # Download using IO.copy_stream for better Windows compatibility
41
+ URI.open(url, "rb") do |remote|
42
+ File.open(temp_path, "wb") do |file|
43
+ IO.copy_stream(remote, file)
44
+ end
26
45
  end
27
46
 
28
47
  puts "[fixtures:download] Extracting #{name}..."
29
- Zip::File.open(zip_file) do |zip|
30
- zip.each do |entry|
31
- dest_path = File.join(target_dir, entry.name)
48
+
49
+ # Open zip file and ensure it's fully closed before we're done
50
+ zip_file = Zip::File.open(temp_path)
51
+ begin
52
+ zip_file.each do |entry|
53
+ # Skip macOS metadata files and directories
54
+ next if entry.name.start_with?("__MACOSX/") || entry.name.include?("/._")
55
+ next if entry.directory?
56
+
57
+ # Ensure entry.name is relative by stripping leading slashes
58
+ relative_name = entry.name.sub(%r{^/+}, "")
59
+
60
+ dest_path = File.join(target_dir, relative_name)
32
61
  FileUtils.mkdir_p(File.dirname(dest_path))
33
- entry.extract(dest_path) unless File.exist?(dest_path)
62
+
63
+ # Skip if file already exists
64
+ next if File.exist?(dest_path)
65
+
66
+ # Write the file content directly using binary mode
67
+ File.open(dest_path, "wb") do |file|
68
+ IO.copy_stream(entry.get_input_stream, file)
69
+ end
34
70
  end
71
+ ensure
72
+ # Explicitly close the zip file to release file handle on Windows
73
+ zip_file.close if zip_file
35
74
  end
36
75
 
37
- FileUtils.rm(zip_file)
76
+ # Temp file left in Dir.tmpdir - OS will clean it up automatically
77
+
38
78
  puts "[fixtures:download] #{name} downloaded successfully"
39
79
  rescue LoadError => e
40
80
  warn "[fixtures:download] Error: Required gem not installed. Please run: gem install rubyzip"
41
81
  raise e
42
82
  end
43
83
 
44
- # Font configurations with target directories and marker files
45
- # All fonts are downloaded via Rake
46
- fonts = {
47
- "Libertinus" => {
48
- url: "https://github.com/alerque/libertinus/releases/download/v7.051/Libertinus-7.051.zip",
49
- target_dir: "#{fixtures_dir}/libertinus",
50
- marker: "#{fixtures_dir}/libertinus/Libertinus-7.051/static/OTF/LibertinusSerif-Regular.otf",
51
- },
52
- "MonaSans" => {
53
- url: "https://github.com/github/mona-sans/releases/download/v2.0/MonaSans.zip",
54
- target_dir: "#{fixtures_dir}/MonaSans",
55
- marker: "#{fixtures_dir}/MonaSans/MonaSans/variable/MonaSans[wdth,wght].ttf",
56
- },
57
- "NotoSerifCJK" => {
58
- url: "https://github.com/notofonts/noto-cjk/releases/download/Serif2.003/01_NotoSerifCJK.ttc.zip",
59
- target_dir: "#{fixtures_dir}/NotoSerifCJK",
60
- marker: "#{fixtures_dir}/NotoSerifCJK/NotoSerifCJK.ttc",
61
- },
62
- "NotoSerifCJK-VF" => {
63
- url: "https://github.com/notofonts/noto-cjk/releases/download/Serif2.003/02_NotoSerifCJK-OTF-VF.zip",
64
- target_dir: "#{fixtures_dir}/NotoSerifCJK-VF",
65
- marker: "#{fixtures_dir}/NotoSerifCJK-VF/Variable/OTC/NotoSerifCJK-VF.otf.ttc",
66
- },
67
- }
84
+ # Get font configurations from centralized source
85
+ fonts = FixtureFonts.rakefile_config
68
86
 
69
87
  # Create file tasks for each font
70
88
  fonts.each do |name, config|
71
89
  file config[:marker] do
72
- download_font(name, config[:url], config[:target_dir])
90
+ if config[:single_file]
91
+ download_single_file(name, config[:url], config[:marker])
92
+ else
93
+ download_font(name, config[:url], config[:target_dir])
94
+ end
73
95
  end
74
96
  end
75
97
 
@@ -78,12 +100,12 @@ namespace :fixtures do
78
100
 
79
101
  desc "Clean downloaded fixtures"
80
102
  task :clean do
81
- %w[libertinus MonaSans NotoSerifCJK NotoSerifCJK-VF].each do |dir|
82
- path = File.join(fixtures_dir, dir)
83
- if File.exist?(path)
84
- FileUtils.rm_rf(path)
85
- puts "[fixtures:clean] Removed #{path}"
86
- end
103
+ fonts.values.map { |config| config[:target_dir] }.each do |path|
104
+ next unless File.exist?(path)
105
+
106
+ puts "[fixtures:clean] Removing #{path}..."
107
+ FileUtils.rm_rf(path)
108
+ puts "[fixtures:clean] Removed #{path}"
87
109
  end
88
110
  end
89
111
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+ require 'fontisan'
5
+
6
+ # Load variable font
7
+ FONT_PATH = 'spec/fixtures/SourceSans3VF-Roman.otf'
8
+
9
+ unless File.exist?(FONT_PATH)
10
+ puts "Error: Test font not found at #{FONT_PATH}"
11
+ exit 1
12
+ end
13
+
14
+ font = Fontisan::FontLoader.load(FONT_PATH)
15
+
16
+ puts "=== Variable Font Parallel Performance ==="
17
+ puts "Font: #{FONT_PATH}"
18
+ puts
19
+
20
+ # Test coordinates
21
+ coords = 4.times.map { |i| { "wght" => 300 + i * 200 } }
22
+ puts "Generating #{coords.size} instances"
23
+ puts
24
+
25
+ # Sequential (1 thread)
26
+ seq_gen = Fontisan::Variation::ParallelGenerator.new(font, threads: 1)
27
+ seq_time = Benchmark.realtime do
28
+ seq_gen.generate_batch(coords)
29
+ end
30
+
31
+ # Parallel (4 threads)
32
+ par_gen = Fontisan::Variation::ParallelGenerator.new(font, threads: 4)
33
+ par_time = Benchmark.realtime do
34
+ par_gen.generate_batch(coords)
35
+ end
36
+
37
+ puts "Sequential (1 thread): #{format('%.3f', seq_time)}s"
38
+ puts "Parallel (4 threads): #{format('%.3f', par_time)}s"
39
+ puts "Speedup: #{format('%.2f', seq_time / par_time)}x"
40
+ puts
41
+
42
+ # Cache stats
43
+ cache = Fontisan::Variation::ThreadSafeCache.new
44
+ 5.times { cache.fetch("test_#{rand(100)}") { rand } }
45
+
46
+ puts "Cache Statistics:"
47
+ puts cache.statistics.inspect