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
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ # Model for individual font summary within a collection
8
+ #
9
+ # Represents basic metadata for a single font in a TTC/OTC collection.
10
+ # Used by CollectionListInfo to provide per-font summaries.
11
+ #
12
+ # @example Creating a font summary
13
+ # summary = CollectionFontSummary.new(
14
+ # index: 0,
15
+ # family_name: "Helvetica",
16
+ # subfamily_name: "Regular",
17
+ # postscript_name: "Helvetica-Regular",
18
+ # font_format: "TrueType",
19
+ # num_glyphs: 268,
20
+ # num_tables: 14
21
+ # )
22
+ class CollectionFontSummary < Lutaml::Model::Serializable
23
+ attribute :index, :integer
24
+ attribute :family_name, :string
25
+ attribute :subfamily_name, :string
26
+ attribute :postscript_name, :string
27
+ attribute :font_format, :string
28
+ attribute :num_glyphs, :integer
29
+ attribute :num_tables, :integer
30
+
31
+ yaml do
32
+ map "index", to: :index
33
+ map "family_name", to: :family_name
34
+ map "subfamily_name", to: :subfamily_name
35
+ map "postscript_name", to: :postscript_name
36
+ map "font_format", to: :font_format
37
+ map "num_glyphs", to: :num_glyphs
38
+ map "num_tables", to: :num_tables
39
+ end
40
+
41
+ json do
42
+ map "index", to: :index
43
+ map "family_name", to: :family_name
44
+ map "subfamily_name", to: :subfamily_name
45
+ map "postscript_name", to: :postscript_name
46
+ map "font_format", to: :font_format
47
+ map "num_glyphs", to: :num_glyphs
48
+ map "num_tables", to: :num_tables
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "table_sharing_info"
5
+
6
+ module Fontisan
7
+ module Models
8
+ # Model for collection metadata
9
+ #
10
+ # Represents comprehensive information about a TTC/OTC collection.
11
+ # Used by InfoCommand when operating on collection files.
12
+ #
13
+ # @example Creating collection info
14
+ # info = CollectionInfo.new(
15
+ # collection_path: "fonts.ttc",
16
+ # collection_format: "TTC",
17
+ # ttc_tag: "ttcf",
18
+ # major_version: 2,
19
+ # minor_version: 0,
20
+ # num_fonts: 6,
21
+ # font_offsets: [48, 380, 712, 1044, 1376, 1676],
22
+ # file_size_bytes: 2240000,
23
+ # table_sharing: table_sharing_obj
24
+ # )
25
+ class CollectionInfo < Lutaml::Model::Serializable
26
+ attribute :collection_path, :string
27
+ attribute :collection_format, :string
28
+ attribute :ttc_tag, :string
29
+ attribute :major_version, :integer
30
+ attribute :minor_version, :integer
31
+ attribute :num_fonts, :integer
32
+ attribute :font_offsets, :integer, collection: true
33
+ attribute :file_size_bytes, :integer
34
+ attribute :table_sharing, TableSharingInfo
35
+
36
+ yaml do
37
+ map "collection_path", to: :collection_path
38
+ map "collection_format", to: :collection_format
39
+ map "ttc_tag", to: :ttc_tag
40
+ map "major_version", to: :major_version
41
+ map "minor_version", to: :minor_version
42
+ map "num_fonts", to: :num_fonts
43
+ map "font_offsets", to: :font_offsets
44
+ map "file_size_bytes", to: :file_size_bytes
45
+ map "table_sharing", to: :table_sharing
46
+ end
47
+
48
+ json do
49
+ map "collection_path", to: :collection_path
50
+ map "collection_format", to: :collection_format
51
+ map "ttc_tag", to: :ttc_tag
52
+ map "major_version", to: :major_version
53
+ map "minor_version", to: :minor_version
54
+ map "num_fonts", to: :num_fonts
55
+ map "font_offsets", to: :font_offsets
56
+ map "file_size_bytes", to: :file_size_bytes
57
+ map "table_sharing", to: :table_sharing
58
+ end
59
+
60
+ # Get version as a formatted string
61
+ #
62
+ # @return [String] Version string (e.g., "2.0")
63
+ def version_string
64
+ "#{major_version}.#{minor_version}"
65
+ end
66
+
67
+ # Get version as a hexadecimal string
68
+ #
69
+ # @return [String] Hex version (e.g., "0x00020000")
70
+ def version_hex
71
+ version_int = (major_version << 16) | minor_version
72
+ format("0x%08X", version_int)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "collection_font_summary"
5
+
6
+ module Fontisan
7
+ module Models
8
+ # Model for collection font listing
9
+ #
10
+ # Represents a list of fonts within a TTC/OTC collection.
11
+ # Used by LsCommand when operating on collection files.
12
+ #
13
+ # @example Creating a collection list
14
+ # list = CollectionListInfo.new(
15
+ # collection_path: "fonts.ttc",
16
+ # num_fonts: 6,
17
+ # fonts: [summary1, summary2, ...]
18
+ # )
19
+ class CollectionListInfo < Lutaml::Model::Serializable
20
+ attribute :collection_path, :string
21
+ attribute :num_fonts, :integer
22
+ attribute :fonts, CollectionFontSummary, collection: true
23
+
24
+ yaml do
25
+ map "collection_path", to: :collection_path
26
+ map "num_fonts", to: :num_fonts
27
+ map "fonts", to: :fonts
28
+ end
29
+
30
+ json do
31
+ map "collection_path", to: :collection_path
32
+ map "num_fonts", to: :num_fonts
33
+ map "fonts", to: :fonts
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ # FontExport represents complete font structure for export to YAML/JSON
8
+ #
9
+ # This model encapsulates the entire font structure including header
10
+ # information, all tables (parsed and binary), and metadata. It supports
11
+ # round-trip conversion: font → export → import → font.
12
+ #
13
+ # @example Exporting a font
14
+ # export = FontExport.new(source_file: "font.ttf")
15
+ # export.extract_from_font(font)
16
+ # yaml_output = export.to_yaml
17
+ #
18
+ # @example Importing from YAML
19
+ # export = FontExport.from_yaml(yaml_string)
20
+ # font = export.rebuild_font
21
+ class FontExport < Lutaml::Model::Serializable
22
+ # Metadata about the export
23
+ class Metadata < Lutaml::Model::Serializable
24
+ attribute :source_file, :string
25
+ attribute :export_date, :string
26
+ attribute :exporter_version, :string
27
+ attribute :font_format, :string
28
+
29
+ yaml do
30
+ map "source_file", to: :source_file
31
+ map "export_date", to: :export_date
32
+ map "exporter_version", to: :exporter_version
33
+ map "font_format", to: :font_format
34
+ end
35
+
36
+ json do
37
+ map "source_file", to: :source_file
38
+ map "export_date", to: :export_date
39
+ map "exporter_version", to: :exporter_version
40
+ map "font_format", to: :font_format
41
+ end
42
+ end
43
+
44
+ # Font header information
45
+ class Header < Lutaml::Model::Serializable
46
+ attribute :sfnt_version, :string
47
+ attribute :num_tables, :integer
48
+ attribute :search_range, :integer
49
+ attribute :entry_selector, :integer
50
+ attribute :range_shift, :integer
51
+
52
+ yaml do
53
+ map "sfnt_version", to: :sfnt_version
54
+ map "num_tables", to: :num_tables
55
+ map "search_range", to: :search_range
56
+ map "entry_selector", to: :entry_selector
57
+ map "range_shift", to: :range_shift
58
+ end
59
+
60
+ json do
61
+ map "sfnt_version", to: :sfnt_version
62
+ map "num_tables", to: :num_tables
63
+ map "search_range", to: :search_range
64
+ map "entry_selector", to: :entry_selector
65
+ map "range_shift", to: :range_shift
66
+ end
67
+ end
68
+
69
+ # Individual table export
70
+ class TableExport < Lutaml::Model::Serializable
71
+ attribute :tag, :string
72
+ attribute :checksum, :string
73
+ attribute :parsed, :boolean, default: -> { false }
74
+ attribute :data, :string, default: -> { nil }
75
+ attribute :fields, :string, default: -> { nil }
76
+
77
+ yaml do
78
+ map "tag", to: :tag
79
+ map "checksum", to: :checksum
80
+ map "parsed", to: :parsed
81
+ map "data", to: :data
82
+ map "fields", to: :fields
83
+ end
84
+
85
+ json do
86
+ map "tag", to: :tag
87
+ map "checksum", to: :checksum
88
+ map "parsed", to: :parsed
89
+ map "data", to: :data
90
+ map "fields", to: :fields
91
+ end
92
+ end
93
+
94
+ attribute :metadata, Metadata
95
+ attribute :header, Header
96
+ attribute :tables, TableExport, collection: true, default: -> { [] }
97
+
98
+ yaml do
99
+ map "metadata", to: :metadata
100
+ map "header", to: :header
101
+ map "tables", to: :tables
102
+ end
103
+
104
+ json do
105
+ map "metadata", to: :metadata
106
+ map "header", to: :header
107
+ map "tables", to: :tables
108
+ end
109
+
110
+ # Find a table by tag
111
+ #
112
+ # @param tag [String] The table tag (e.g., "head", "name")
113
+ # @return [TableExport, nil] The table or nil if not found
114
+ def find_table(tag)
115
+ tables.find { |t| t.tag == tag }
116
+ end
117
+
118
+ # Get all parsed tables
119
+ #
120
+ # @return [Array<TableExport>] Array of parsed tables
121
+ def parsed_tables
122
+ tables.select(&:parsed)
123
+ end
124
+
125
+ # Get all binary-only tables
126
+ #
127
+ # @return [Array<TableExport>] Array of binary tables
128
+ def binary_tables
129
+ tables.reject(&:parsed)
130
+ end
131
+
132
+ # Add a table to the export
133
+ #
134
+ # @param tag [String] Table tag
135
+ # @param checksum [String] Table checksum
136
+ # @param parsed [Boolean] Whether table is parsed
137
+ # @param data [String, nil] Binary data (hex/base64)
138
+ # @param fields [Hash, nil] Parsed fields as Hash/JSON
139
+ # @return [void]
140
+ def add_table(tag:, checksum:, parsed: false, data: nil, fields: nil)
141
+ tables << TableExport.new(
142
+ tag: tag,
143
+ checksum: checksum,
144
+ parsed: parsed,
145
+ data: data,
146
+ fields: fields,
147
+ )
148
+ end
149
+
150
+ # Validate export structure
151
+ #
152
+ # @return [Boolean] True if export is valid
153
+ def valid?
154
+ !metadata.nil? && !header.nil? && !tables.empty?
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ # Model for quick font summary
8
+ #
9
+ # Represents a brief overview of an individual font file.
10
+ # Used by LsCommand when operating on TTF/OTF files.
11
+ #
12
+ # @example Creating a font summary
13
+ # summary = FontSummary.new(
14
+ # font_path: "font.ttf",
15
+ # family_name: "Helvetica",
16
+ # subfamily_name: "Regular",
17
+ # font_format: "TrueType",
18
+ # num_glyphs: 268,
19
+ # num_tables: 14
20
+ # )
21
+ class FontSummary < Lutaml::Model::Serializable
22
+ attribute :font_path, :string
23
+ attribute :family_name, :string
24
+ attribute :subfamily_name, :string
25
+ attribute :font_format, :string
26
+ attribute :num_glyphs, :integer
27
+ attribute :num_tables, :integer
28
+
29
+ yaml do
30
+ map "font_path", to: :font_path
31
+ map "family_name", to: :family_name
32
+ map "subfamily_name", to: :subfamily_name
33
+ map "font_format", to: :font_format
34
+ map "num_glyphs", to: :num_glyphs
35
+ map "num_tables", to: :num_tables
36
+ end
37
+
38
+ json do
39
+ map "font_path", to: :font_path
40
+ map "family_name", to: :family_name
41
+ map "subfamily_name", to: :subfamily_name
42
+ map "font_format", to: :font_format
43
+ map "num_glyphs", to: :num_glyphs
44
+ map "num_tables", to: :num_tables
45
+ end
46
+ end
47
+ end
48
+ end