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,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../models/ttx/tables/head_table"
4
+
5
+ module Fontisan
6
+ module Export
7
+ module Transformers
8
+ # HeadTransformer transforms head table to TTX format
9
+ #
10
+ # Converts Fontisan::Tables::Head to Models::Ttx::Tables::HeadTable
11
+ # following proper model-to-model transformation principles.
12
+ class HeadTransformer
13
+ # Transform head table to TTX model
14
+ #
15
+ # @param head_table [Tables::Head] Source head table
16
+ # @return [Models::Ttx::Tables::HeadTable] TTX head table model
17
+ def self.transform(head_table)
18
+ return nil unless head_table
19
+
20
+ Models::Ttx::Tables::HeadTable.new.tap do |ttx|
21
+ ttx.table_version = format_fixed(to_int(head_table.version))
22
+ ttx.font_revision = format_fixed(to_int(head_table.font_revision))
23
+ ttx.checksum_adjustment = format_hex(to_int(head_table.checksum_adjustment))
24
+ ttx.magic_number = format_hex(to_int(head_table.magic_number))
25
+ ttx.flags = to_int(head_table.flags).to_s
26
+ ttx.units_per_em = to_int(head_table.units_per_em).to_s
27
+ ttx.created = format_timestamp(to_int(head_table.created))
28
+ ttx.modified = format_timestamp(to_int(head_table.modified))
29
+ ttx.x_min = to_int(head_table.x_min).to_s
30
+ ttx.y_min = to_int(head_table.y_min).to_s
31
+ ttx.x_max = to_int(head_table.x_max).to_s
32
+ ttx.y_max = to_int(head_table.y_max).to_s
33
+ ttx.mac_style = format_binary_flags(to_int(head_table.mac_style),
34
+ 16)
35
+ ttx.lowest_rec_ppem = to_int(head_table.lowest_rec_ppem).to_s
36
+ ttx.font_direction_hint = to_int(head_table.font_direction_hint).to_s
37
+ ttx.index_to_loc_format = to_int(head_table.index_to_loc_format).to_s
38
+ ttx.glyph_data_format = to_int(head_table.glyph_data_format).to_s
39
+ end
40
+ end
41
+
42
+ # Convert BinData value to native Ruby integer
43
+ #
44
+ # @param value [Object] BinData value or integer
45
+ # @return [Integer] Native integer
46
+ def self.to_int(value)
47
+ value.respond_to?(:to_i) ? value.to_i : value
48
+ end
49
+
50
+ # Format fixed-point number (16.16)
51
+ #
52
+ # @param value [Integer] Fixed-point value
53
+ # @return [String] Decimal string
54
+ def self.format_fixed(value)
55
+ result = value.to_f / 65536.0
56
+ if result == result.to_i
57
+ "#{result.to_i}.0"
58
+ else
59
+ result.to_s
60
+ end
61
+ end
62
+
63
+ # Format hex value
64
+ #
65
+ # @param value [Integer] Integer value
66
+ # @param width [Integer] Minimum hex width
67
+ # @return [String] Hex string (e.g., "0x1234")
68
+ def self.format_hex(value, width: 8)
69
+ "0x#{value.to_s(16).rjust(width, '0')}"
70
+ end
71
+
72
+ # Format binary flags
73
+ #
74
+ # @param value [Integer] Integer value
75
+ # @param bits [Integer] Number of bits
76
+ # @return [String] Binary string with spaces every 8 bits
77
+ def self.format_binary_flags(value, bits)
78
+ binary = value.to_s(2).rjust(bits, "0")
79
+ binary.scan(/.{1,8}/).join(" ")
80
+ end
81
+
82
+ # Format timestamp
83
+ #
84
+ # @param timestamp [Integer] Mac timestamp (seconds since 1904-01-01)
85
+ # @return [String] Human-readable date string
86
+ def self.format_timestamp(timestamp)
87
+ mac_epoch = Time.utc(1904, 1, 1)
88
+ time = mac_epoch + timestamp
89
+ time.strftime("%a %b %e %H:%M:%S %Y")
90
+ rescue StandardError
91
+ "Invalid Date"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../models/ttx/tables/hhea_table"
4
+
5
+ module Fontisan
6
+ module Export
7
+ module Transformers
8
+ # HheaTransformer transforms hhea table to TTX format
9
+ #
10
+ # Converts Fontisan horizontal header table to Models::Ttx::Tables::HheaTable
11
+ # following proper model-to-model transformation principles.
12
+ class HheaTransformer
13
+ # Transform hhea table to TTX model
14
+ #
15
+ # @param hhea_table [Object] Source hhea table
16
+ # @return [Models::Ttx::Tables::HheaTable] TTX hhea table model
17
+ def self.transform(hhea_table)
18
+ return nil unless hhea_table
19
+
20
+ Models::Ttx::Tables::HheaTable.new.tap do |ttx|
21
+ ttx.table_version = format_hex(to_int(hhea_table.version_raw))
22
+ ttx.ascent = to_int(hhea_table.ascent)
23
+ ttx.descent = to_int(hhea_table.descent)
24
+ ttx.line_gap = to_int(hhea_table.line_gap)
25
+ ttx.advance_width_max = to_int(hhea_table.advance_width_max)
26
+ ttx.min_left_side_bearing = to_int(hhea_table.min_left_side_bearing)
27
+ ttx.min_right_side_bearing = to_int(hhea_table.min_right_side_bearing)
28
+ ttx.x_max_extent = to_int(hhea_table.x_max_extent)
29
+ ttx.caret_slope_rise = to_int(hhea_table.caret_slope_rise)
30
+ ttx.caret_slope_run = to_int(hhea_table.caret_slope_run)
31
+ ttx.caret_offset = to_int(hhea_table.caret_offset)
32
+ ttx.reserved0 = 0
33
+ ttx.reserved1 = 0
34
+ ttx.reserved2 = 0
35
+ ttx.reserved3 = 0
36
+ ttx.metric_data_format = to_int(hhea_table.metric_data_format)
37
+ ttx.number_of_h_metrics = to_int(hhea_table.number_of_h_metrics)
38
+ end
39
+ end
40
+
41
+ # Convert BinData value to native Ruby integer
42
+ #
43
+ # @param value [Object] BinData value or integer
44
+ # @return [Integer] Native integer
45
+ def self.to_int(value)
46
+ value.respond_to?(:to_i) ? value.to_i : value
47
+ end
48
+
49
+ # Format hex value
50
+ #
51
+ # @param value [Integer] Integer value
52
+ # @return [String] Hex string (e.g., "0x00010000")
53
+ def self.format_hex(value)
54
+ "0x#{value.to_s(16).rjust(8, '0')}"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../models/ttx/tables/maxp_table"
4
+
5
+ module Fontisan
6
+ module Export
7
+ module Transformers
8
+ # MaxpTransformer transforms maxp table to TTX format
9
+ #
10
+ # Converts Fontisan maximum profile table to Models::Ttx::Tables::MaxpTable
11
+ # following proper model-to-model transformation principles.
12
+ class MaxpTransformer
13
+ # Transform maxp table to TTX model
14
+ #
15
+ # @param maxp_table [Object] Source maxp table
16
+ # @return [Models::Ttx::Tables::MaxpTable] TTX maxp table model
17
+ def self.transform(maxp_table)
18
+ return nil unless maxp_table
19
+
20
+ version = to_int(maxp_table.version)
21
+
22
+ Models::Ttx::Tables::MaxpTable.new.tap do |ttx|
23
+ ttx.table_version = format_hex(version)
24
+ ttx.num_glyphs = to_int(maxp_table.num_glyphs)
25
+
26
+ # Version 1.0 fields (TrueType)
27
+ if version >= 0x00010000
28
+ ttx.max_points = to_int(maxp_table.max_points)
29
+ ttx.max_contours = to_int(maxp_table.max_contours)
30
+ ttx.max_composite_points = to_int(maxp_table.max_component_points)
31
+ ttx.max_composite_contours = to_int(maxp_table.max_component_contours)
32
+ ttx.max_zones = to_int(maxp_table.max_zones)
33
+ ttx.max_twilight_points = to_int(maxp_table.max_twilight_points)
34
+ ttx.max_storage = to_int(maxp_table.max_storage)
35
+ ttx.max_function_defs = to_int(maxp_table.max_function_defs)
36
+ ttx.max_instruction_defs = to_int(maxp_table.max_instruction_defs)
37
+ ttx.max_stack_elements = to_int(maxp_table.max_stack_elements)
38
+ ttx.max_size_of_instructions = to_int(maxp_table.max_size_of_instructions)
39
+ ttx.max_component_elements = to_int(maxp_table.max_component_elements)
40
+ ttx.max_component_depth = to_int(maxp_table.max_component_depth)
41
+ end
42
+ end
43
+ end
44
+
45
+ # Convert BinData value to native Ruby integer
46
+ #
47
+ # @param value [Object] BinData value or integer
48
+ # @return [Integer] Native integer
49
+ def self.to_int(value)
50
+ value.respond_to?(:to_i) ? value.to_i : value
51
+ end
52
+
53
+ # Format hex value
54
+ #
55
+ # @param value [Integer] Integer value
56
+ # @return [String] Hex string (e.g., "0x00010000")
57
+ def self.format_hex(value)
58
+ "0x#{value.to_s(16).rjust(8, '0')}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../models/ttx/tables/name_table"
4
+
5
+ module Fontisan
6
+ module Export
7
+ module Transformers
8
+ # NameTransformer transforms name table to TTX format
9
+ #
10
+ # Converts Fontisan::Tables::Name to Models::Ttx::Tables::NameTable
11
+ # following proper model-to-model transformation principles.
12
+ class NameTransformer
13
+ # Transform name table to TTX model
14
+ #
15
+ # @param name_table [Tables::Name] Source name table
16
+ # @return [Models::Ttx::Tables::NameTable] TTX name table model
17
+ def self.transform(name_table)
18
+ return nil unless name_table
19
+
20
+ Models::Ttx::Tables::NameTable.new.tap do |ttx|
21
+ ttx.name_records = transform_name_records(name_table.name_records)
22
+ end
23
+ end
24
+
25
+ # Transform name records
26
+ #
27
+ # @param records [Array] Name records from source table
28
+ # @return [Array<Models::Ttx::Tables::NameRecord>] TTX name records
29
+ def self.transform_name_records(records)
30
+ return [] unless records
31
+
32
+ records.map do |record|
33
+ Models::Ttx::Tables::NameRecord.new.tap do |ttx_record|
34
+ ttx_record.name_id = to_int(record.name_id)
35
+ ttx_record.platform_id = to_int(record.platform_id)
36
+ ttx_record.plat_enc_id = to_int(record.encoding_id)
37
+ ttx_record.lang_id = format_hex(to_int(record.language_id),
38
+ width: 3)
39
+ ttx_record.string = record.string
40
+ end
41
+ end
42
+ end
43
+
44
+ # Convert BinData value to native Ruby integer
45
+ #
46
+ # @param value [Object] BinData value or integer
47
+ # @return [Integer] Native integer
48
+ def self.to_int(value)
49
+ value.respond_to?(:to_i) ? value.to_i : value
50
+ end
51
+
52
+ # Format hex value
53
+ #
54
+ # @param value [Integer] Integer value
55
+ # @param width [Integer] Minimum hex width
56
+ # @return [String] Hex string (e.g., "0x1234")
57
+ def self.format_hex(value, width: 8)
58
+ "0x#{value.to_s(16).rjust(width, '0')}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../models/ttx/tables/os2_table"
4
+
5
+ module Fontisan
6
+ module Export
7
+ module Transformers
8
+ # Os2Transformer transforms OS/2 table to TTX format
9
+ #
10
+ # Converts Fontisan::Tables::Os2 to Models::Ttx::Tables::Os2Table
11
+ # following proper model-to-model transformation principles.
12
+ class Os2Transformer
13
+ # Transform OS/2 table to TTX model
14
+ #
15
+ # @param os2_table [Tables::Os2] Source OS/2 table
16
+ # @return [Models::Ttx::Tables::Os2Table] TTX OS/2 table model
17
+ def self.transform(os2_table)
18
+ return nil unless os2_table
19
+
20
+ version = to_int(os2_table.version)
21
+
22
+ Models::Ttx::Tables::Os2Table.new.tap do |ttx|
23
+ ttx.version = version
24
+ ttx.x_avg_char_width = to_int(os2_table.x_avg_char_width)
25
+ ttx.us_weight_class = to_int(os2_table.us_weight_class)
26
+ ttx.us_width_class = to_int(os2_table.us_width_class)
27
+ ttx.fs_type = format_binary_flags(to_int(os2_table.fs_type), 16)
28
+ ttx.y_subscript_x_size = to_int(os2_table.y_subscript_x_size)
29
+ ttx.y_subscript_y_size = to_int(os2_table.y_subscript_y_size)
30
+ ttx.y_subscript_x_offset = to_int(os2_table.y_subscript_x_offset)
31
+ ttx.y_subscript_y_offset = to_int(os2_table.y_subscript_y_offset)
32
+ ttx.y_superscript_x_size = to_int(os2_table.y_superscript_x_size)
33
+ ttx.y_superscript_y_size = to_int(os2_table.y_superscript_y_size)
34
+ ttx.y_superscript_x_offset = to_int(os2_table.y_superscript_x_offset)
35
+ ttx.y_superscript_y_offset = to_int(os2_table.y_superscript_y_offset)
36
+ ttx.y_strikeout_size = to_int(os2_table.y_strikeout_size)
37
+ ttx.y_strikeout_position = to_int(os2_table.y_strikeout_position)
38
+ ttx.s_family_class = to_int(os2_table.s_family_class)
39
+ ttx.panose = transform_panose(os2_table.panose)
40
+ ttx.ul_unicode_range_1 = format_binary_flags(
41
+ to_int(os2_table.ul_unicode_range1), 32
42
+ )
43
+ ttx.ul_unicode_range_2 = format_binary_flags(
44
+ to_int(os2_table.ul_unicode_range2), 32
45
+ )
46
+ ttx.ul_unicode_range_3 = format_binary_flags(
47
+ to_int(os2_table.ul_unicode_range3), 32
48
+ )
49
+ ttx.ul_unicode_range_4 = format_binary_flags(
50
+ to_int(os2_table.ul_unicode_range4), 32
51
+ )
52
+ ttx.ach_vend_id = os2_table.vendor_id.to_s.strip
53
+ ttx.fs_selection = format_binary_flags(
54
+ to_int(os2_table.fs_selection), 16
55
+ )
56
+ ttx.us_first_char_index = to_int(os2_table.us_first_char_index)
57
+ ttx.us_last_char_index = to_int(os2_table.us_last_char_index)
58
+
59
+ if version >= 1
60
+ ttx.s_typo_ascender = to_int(os2_table.s_typo_ascender)
61
+ ttx.s_typo_descender = to_int(os2_table.s_typo_descender)
62
+ ttx.s_typo_line_gap = to_int(os2_table.s_typo_line_gap)
63
+ ttx.us_win_ascent = to_int(os2_table.us_win_ascent)
64
+ ttx.us_win_descent = to_int(os2_table.us_win_descent)
65
+ end
66
+
67
+ if version >= 2
68
+ ttx.ul_code_page_range_1 = format_binary_flags(
69
+ to_int(os2_table.ul_code_page_range1), 32
70
+ )
71
+ ttx.ul_code_page_range_2 = format_binary_flags(
72
+ to_int(os2_table.ul_code_page_range2), 32
73
+ )
74
+ end
75
+ end
76
+ end
77
+
78
+ # Transform Panose data
79
+ #
80
+ # @param panose [Object] Panose data (String or Array)
81
+ # @return [Models::Ttx::Tables::Panose] TTX Panose model
82
+ def self.transform_panose(panose)
83
+ return nil unless panose
84
+
85
+ bytes = panose.is_a?(String) ? panose.bytes : panose.to_a
86
+
87
+ Models::Ttx::Tables::Panose.new.tap do |ttx_panose|
88
+ ttx_panose.b_family_type = bytes[0]
89
+ ttx_panose.b_serif_style = bytes[1]
90
+ ttx_panose.b_weight = bytes[2]
91
+ ttx_panose.b_proportion = bytes[3]
92
+ ttx_panose.b_contrast = bytes[4]
93
+ ttx_panose.b_stroke_variation = bytes[5]
94
+ ttx_panose.b_arm_style = bytes[6]
95
+ ttx_panose.b_letter_form = bytes[7]
96
+ ttx_panose.b_midline = bytes[8]
97
+ ttx_panose.b_x_height = bytes[9]
98
+ end
99
+ end
100
+
101
+ # Convert BinData value to native Ruby integer
102
+ #
103
+ # @param value [Object] BinData value or integer
104
+ # @return [Integer] Native integer
105
+ def self.to_int(value)
106
+ value.respond_to?(:to_i) ? value.to_i : value
107
+ end
108
+
109
+ # Format binary flags
110
+ #
111
+ # @param value [Integer] Integer value
112
+ # @param bits [Integer] Number of bits
113
+ # @return [String] Binary string with spaces every 8 bits
114
+ def self.format_binary_flags(value, bits)
115
+ binary = value.to_s(2).rjust(bits, "0")
116
+ binary.scan(/.{1,8}/).join(" ")
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../models/ttx/tables/post_table"
4
+
5
+ module Fontisan
6
+ module Export
7
+ module Transformers
8
+ # PostTransformer transforms post table to TTX format
9
+ #
10
+ # Converts Fontisan::Tables::Post to Models::Ttx::Tables::PostTable
11
+ # following proper model-to-model transformation principles.
12
+ class PostTransformer
13
+ # Transform post table to TTX model
14
+ #
15
+ # @param post_table [Tables::Post] Source post table
16
+ # @return [Models::Ttx::Tables::PostTable] TTX post table model
17
+ def self.transform(post_table)
18
+ return nil unless post_table
19
+
20
+ Models::Ttx::Tables::PostTable.new.tap do |ttx|
21
+ ttx.format_type = format_fixed(to_int(post_table.version_raw))
22
+ ttx.italic_angle = format_fixed(to_int(post_table.italic_angle_raw))
23
+ ttx.underline_position = to_int(post_table.underline_position)
24
+ ttx.underline_thickness = to_int(post_table.underline_thickness)
25
+ ttx.is_fixed_pitch = to_int(post_table.is_fixed_pitch)
26
+ ttx.min_mem_type42 = to_int(post_table.min_mem_type42)
27
+ ttx.max_mem_type42 = to_int(post_table.max_mem_type42)
28
+ ttx.min_mem_type1 = to_int(post_table.min_mem_type1)
29
+ ttx.max_mem_type1 = to_int(post_table.max_mem_type1)
30
+ end
31
+ end
32
+
33
+ # Convert BinData value to native Ruby integer
34
+ #
35
+ # @param value [Object] BinData value or integer
36
+ # @return [Integer] Native integer
37
+ def self.to_int(value)
38
+ value.respond_to?(:to_i) ? value.to_i : value
39
+ end
40
+
41
+ # Format fixed-point number (16.16)
42
+ #
43
+ # @param value [Integer] Fixed-point value
44
+ # @return [Float] Decimal value
45
+ def self.format_fixed(value)
46
+ value.to_f / 65536.0
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end