fontisan 0.2.16 → 0.2.22

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 (318) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +14 -90
  3. data/Gemfile +6 -3
  4. data/README.adoc +257 -1
  5. data/docs/.vitepress/config.ts +68 -8
  6. data/docs/.vitepress/theme/style.css +570 -272
  7. data/docs/CONVERSION_GUIDE.adoc +31 -8
  8. data/docs/EXTRACT_TTC_MIGRATION.md +1 -1
  9. data/docs/WOFF_WOFF2_FORMATS.adoc +53 -0
  10. data/docs/api/conversion-options.md +37 -14
  11. data/docs/api/font-loader.md +21 -15
  12. data/docs/cli/audit.md +337 -0
  13. data/docs/cli/convert.md +20 -1
  14. data/docs/cli/index.md +31 -0
  15. data/docs/guide/color.md +1 -1
  16. data/docs/guide/conversion/options.md +32 -3
  17. data/docs/guide/conversion/ttf-otf.md +1 -1
  18. data/docs/guide/conversion/type1.md +1 -1
  19. data/docs/guide/conversion/web.md +91 -32
  20. data/docs/guide/conversion.md +6 -5
  21. data/docs/guide/formats/woff.md +35 -11
  22. data/docs/guide/index.md +2 -2
  23. data/docs/guide/migrations/extract-ttc.md +1 -1
  24. data/docs/guide/quick-start.md +4 -4
  25. data/docs/guide/type1.md +4 -4
  26. data/docs/guide/woff.md +19 -17
  27. data/docs/index.md +2 -0
  28. data/docs/lychee.toml +5 -1
  29. data/docs/package.json +1 -1
  30. data/docs/public/robots.txt +4 -0
  31. data/docs/scripts/post-build.mjs +81 -0
  32. data/lib/fontisan/audit/codepoint_range_coalescer.rb +41 -0
  33. data/lib/fontisan/audit/context.rb +122 -0
  34. data/lib/fontisan/audit/differ.rb +124 -0
  35. data/lib/fontisan/audit/extractors/aggregations.rb +54 -0
  36. data/lib/fontisan/audit/extractors/base.rb +26 -0
  37. data/lib/fontisan/audit/extractors/color_capabilities.rb +141 -0
  38. data/lib/fontisan/audit/extractors/coverage.rb +48 -0
  39. data/lib/fontisan/audit/extractors/hinting.rb +197 -0
  40. data/lib/fontisan/audit/extractors/identity.rb +52 -0
  41. data/lib/fontisan/audit/extractors/language_coverage.rb +37 -0
  42. data/lib/fontisan/audit/extractors/licensing.rb +79 -0
  43. data/lib/fontisan/audit/extractors/metrics.rb +103 -0
  44. data/lib/fontisan/audit/extractors/opentype_layout.rb +69 -0
  45. data/lib/fontisan/audit/extractors/provenance.rb +29 -0
  46. data/lib/fontisan/audit/extractors/style.rb +32 -0
  47. data/lib/fontisan/audit/extractors/variation_detail.rb +99 -0
  48. data/lib/fontisan/audit/extractors.rb +27 -0
  49. data/lib/fontisan/audit/library_aggregator.rb +83 -0
  50. data/lib/fontisan/audit/library_auditor.rb +90 -0
  51. data/lib/fontisan/audit/registry.rb +60 -0
  52. data/lib/fontisan/audit/style_extractor.rb +80 -0
  53. data/lib/fontisan/audit.rb +20 -0
  54. data/lib/fontisan/base_collection.rb +23 -9
  55. data/lib/fontisan/binary/structures.rb +0 -2
  56. data/lib/fontisan/binary.rb +11 -0
  57. data/lib/fontisan/cldr/aggregator.rb +33 -0
  58. data/lib/fontisan/cldr/cache_manager.rb +110 -0
  59. data/lib/fontisan/cldr/config.rb +59 -0
  60. data/lib/fontisan/cldr/download_error.rb +9 -0
  61. data/lib/fontisan/cldr/downloader.rb +79 -0
  62. data/lib/fontisan/cldr/error.rb +8 -0
  63. data/lib/fontisan/cldr/index.rb +64 -0
  64. data/lib/fontisan/cldr/index_builder.rb +72 -0
  65. data/lib/fontisan/cldr/unicode_set_parser.rb +172 -0
  66. data/lib/fontisan/cldr/unknown_version_error.rb +9 -0
  67. data/lib/fontisan/cldr/version_resolver.rb +91 -0
  68. data/lib/fontisan/cldr.rb +23 -0
  69. data/lib/fontisan/cli/cldr_cli.rb +85 -0
  70. data/lib/fontisan/cli/ucd_cli.rb +97 -0
  71. data/lib/fontisan/cli.rb +201 -2
  72. data/lib/fontisan/collection/builder.rb +0 -4
  73. data/lib/fontisan/collection/dfont_builder.rb +0 -4
  74. data/lib/fontisan/collection/shared_logic.rb +0 -2
  75. data/lib/fontisan/collection/writer.rb +0 -3
  76. data/lib/fontisan/collection.rb +15 -0
  77. data/lib/fontisan/commands/audit_command.rb +123 -0
  78. data/lib/fontisan/commands/audit_compare_command.rb +66 -0
  79. data/lib/fontisan/commands/audit_library_command.rb +46 -0
  80. data/lib/fontisan/commands/base_command.rb +0 -3
  81. data/lib/fontisan/commands/convert_command.rb +25 -20
  82. data/lib/fontisan/commands/dump_table_command.rb +0 -3
  83. data/lib/fontisan/commands/export_command.rb +0 -4
  84. data/lib/fontisan/commands/features_command.rb +0 -3
  85. data/lib/fontisan/commands/instance_command.rb +0 -5
  86. data/lib/fontisan/commands/ls_command.rb +0 -6
  87. data/lib/fontisan/commands/optical_size_command.rb +0 -3
  88. data/lib/fontisan/commands/pack_command.rb +0 -5
  89. data/lib/fontisan/commands/scripts_command.rb +0 -2
  90. data/lib/fontisan/commands/subset_command.rb +0 -3
  91. data/lib/fontisan/commands/unicode_command.rb +0 -3
  92. data/lib/fontisan/commands/unpack_command.rb +0 -7
  93. data/lib/fontisan/commands/validate_command.rb +0 -8
  94. data/lib/fontisan/commands/variable_command.rb +0 -3
  95. data/lib/fontisan/commands.rb +29 -0
  96. data/lib/fontisan/config/cldr.yml +22 -0
  97. data/lib/fontisan/config/conversion_matrix.yml +38 -0
  98. data/lib/fontisan/config/ucd.yml +23 -0
  99. data/lib/fontisan/constants.rb +48 -6
  100. data/lib/fontisan/conversion_options.rb +30 -19
  101. data/lib/fontisan/converters/cff_table_builder.rb +0 -3
  102. data/lib/fontisan/converters/collection_converter.rb +0 -8
  103. data/lib/fontisan/converters/conversion_strategy.rb +161 -46
  104. data/lib/fontisan/converters/format_converter.rb +143 -32
  105. data/lib/fontisan/converters/glyf_table_builder.rb +0 -2
  106. data/lib/fontisan/converters/outline_converter.rb +0 -19
  107. data/lib/fontisan/converters/outline_extraction.rb +0 -5
  108. data/lib/fontisan/converters/outline_optimizer.rb +0 -5
  109. data/lib/fontisan/converters/svg_generator.rb +0 -4
  110. data/lib/fontisan/converters/table_copier.rb +0 -2
  111. data/lib/fontisan/converters/type1_converter.rb +0 -11
  112. data/lib/fontisan/converters/woff2_encoder.rb +49 -20
  113. data/lib/fontisan/converters/woff_writer.rb +211 -282
  114. data/lib/fontisan/converters.rb +21 -0
  115. data/lib/fontisan/dfont_collection.rb +29 -10
  116. data/lib/fontisan/export/exporter.rb +0 -6
  117. data/lib/fontisan/export/transformers/font_to_ttx.rb +0 -9
  118. data/lib/fontisan/export/transformers/head_transformer.rb +0 -2
  119. data/lib/fontisan/export/transformers/hhea_transformer.rb +0 -2
  120. data/lib/fontisan/export/transformers/maxp_transformer.rb +0 -2
  121. data/lib/fontisan/export/transformers/name_transformer.rb +0 -2
  122. data/lib/fontisan/export/transformers/os2_transformer.rb +0 -2
  123. data/lib/fontisan/export/transformers/post_transformer.rb +0 -2
  124. data/lib/fontisan/export/transformers.rb +17 -0
  125. data/lib/fontisan/export.rb +13 -0
  126. data/lib/fontisan/font_loader.rb +189 -328
  127. data/lib/fontisan/font_writer.rb +0 -1
  128. data/lib/fontisan/formatters/audit_diff_text_renderer.rb +122 -0
  129. data/lib/fontisan/formatters/audit_text_renderer.rb +324 -0
  130. data/lib/fontisan/formatters/library_summary_text_renderer.rb +99 -0
  131. data/lib/fontisan/formatters/text_formatter.rb +6 -0
  132. data/lib/fontisan/formatters.rb +12 -0
  133. data/lib/fontisan/hints/hint_converter.rb +0 -1
  134. data/lib/fontisan/hints/postscript_hint_applier.rb +0 -9
  135. data/lib/fontisan/hints/postscript_hint_extractor.rb +0 -2
  136. data/lib/fontisan/hints/truetype_hint_extractor.rb +0 -2
  137. data/lib/fontisan/hints.rb +16 -0
  138. data/lib/fontisan/metrics_calculator.rb +0 -2
  139. data/lib/fontisan/models/all_scripts_features_info.rb +0 -1
  140. data/lib/fontisan/models/audit/audit_axis.rb +30 -0
  141. data/lib/fontisan/models/audit/audit_block.rb +32 -0
  142. data/lib/fontisan/models/audit/audit_diff.rb +77 -0
  143. data/lib/fontisan/models/audit/audit_report.rb +153 -0
  144. data/lib/fontisan/models/audit/codepoint_range.rb +40 -0
  145. data/lib/fontisan/models/audit/codepoint_set_diff.rb +34 -0
  146. data/lib/fontisan/models/audit/color_capabilities.rb +93 -0
  147. data/lib/fontisan/models/audit/duplicate_group.rb +23 -0
  148. data/lib/fontisan/models/audit/embedding_type.rb +76 -0
  149. data/lib/fontisan/models/audit/field_change.rb +28 -0
  150. data/lib/fontisan/models/audit/fs_selection_flags.rb +61 -0
  151. data/lib/fontisan/models/audit/gasp_range.rb +63 -0
  152. data/lib/fontisan/models/audit/hinting.rb +93 -0
  153. data/lib/fontisan/models/audit/library_summary.rb +40 -0
  154. data/lib/fontisan/models/audit/licensing.rb +48 -0
  155. data/lib/fontisan/models/audit/metrics.rb +111 -0
  156. data/lib/fontisan/models/audit/named_instance.rb +41 -0
  157. data/lib/fontisan/models/audit/opentype_layout.rb +40 -0
  158. data/lib/fontisan/models/audit/script_coverage_row.rb +26 -0
  159. data/lib/fontisan/models/audit/script_features.rb +28 -0
  160. data/lib/fontisan/models/audit/variation_detail.rb +44 -0
  161. data/lib/fontisan/models/audit.rb +33 -0
  162. data/lib/fontisan/models/cldr/language_coverage.rb +31 -0
  163. data/lib/fontisan/models/cldr.rb +12 -0
  164. data/lib/fontisan/models/collection_brief_info.rb +0 -1
  165. data/lib/fontisan/models/collection_info.rb +0 -2
  166. data/lib/fontisan/models/collection_list_info.rb +0 -1
  167. data/lib/fontisan/models/collection_validation_report.rb +0 -2
  168. data/lib/fontisan/models/color_glyph.rb +0 -1
  169. data/lib/fontisan/models/font_report.rb +0 -1
  170. data/lib/fontisan/models/ttx/tables.rb +21 -0
  171. data/lib/fontisan/models/ttx/ttfont.rb +0 -8
  172. data/lib/fontisan/models/ttx.rb +14 -0
  173. data/lib/fontisan/models/ucd/ucd.rb +38 -0
  174. data/lib/fontisan/models/ucd/ucd_char.rb +67 -0
  175. data/lib/fontisan/models/ucd.rb +19 -0
  176. data/lib/fontisan/models.rb +47 -0
  177. data/lib/fontisan/open_type_collection.rb +6 -5
  178. data/lib/fontisan/open_type_font.rb +8 -2
  179. data/lib/fontisan/open_type_font_extensions.rb +9 -9
  180. data/lib/fontisan/optimizers/pattern_analyzer.rb +0 -1
  181. data/lib/fontisan/optimizers.rb +14 -0
  182. data/lib/fontisan/outline_extractor.rb +0 -2
  183. data/lib/fontisan/parsers/dfont_parser.rb +0 -1
  184. data/lib/fontisan/parsers.rb +10 -0
  185. data/lib/fontisan/pipeline/format_detector.rb +29 -102
  186. data/lib/fontisan/pipeline/output_writer.rb +11 -9
  187. data/lib/fontisan/pipeline/strategies/instance_strategy.rb +0 -4
  188. data/lib/fontisan/pipeline/strategies/named_strategy.rb +0 -4
  189. data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +0 -2
  190. data/lib/fontisan/pipeline/strategies.rb +14 -0
  191. data/lib/fontisan/pipeline/transformation_pipeline.rb +0 -7
  192. data/lib/fontisan/pipeline/variation_resolver.rb +0 -7
  193. data/lib/fontisan/pipeline.rb +13 -0
  194. data/lib/fontisan/sfnt_font.rb +29 -14
  195. data/lib/fontisan/sfnt_table.rb +0 -4
  196. data/lib/fontisan/subset/builder.rb +0 -6
  197. data/lib/fontisan/subset.rb +13 -0
  198. data/lib/fontisan/svg/font_generator.rb +0 -4
  199. data/lib/fontisan/svg/glyph_generator.rb +0 -2
  200. data/lib/fontisan/svg.rb +12 -0
  201. data/lib/fontisan/tables/cbdt.rb +0 -1
  202. data/lib/fontisan/tables/cblc.rb +0 -1
  203. data/lib/fontisan/tables/cff/charset.rb +0 -1
  204. data/lib/fontisan/tables/cff/charstring.rb +0 -1
  205. data/lib/fontisan/tables/cff/charstring_rebuilder.rb +0 -4
  206. data/lib/fontisan/tables/cff/charstrings_index.rb +0 -3
  207. data/lib/fontisan/tables/cff/dict.rb +0 -1
  208. data/lib/fontisan/tables/cff/encoding.rb +0 -1
  209. data/lib/fontisan/tables/cff/header.rb +0 -2
  210. data/lib/fontisan/tables/cff/hint_operation_injector.rb +0 -2
  211. data/lib/fontisan/tables/cff/index.rb +0 -1
  212. data/lib/fontisan/tables/cff/private_dict.rb +0 -2
  213. data/lib/fontisan/tables/cff/private_dict_writer.rb +0 -2
  214. data/lib/fontisan/tables/cff/table_builder.rb +0 -6
  215. data/lib/fontisan/tables/cff/top_dict.rb +0 -2
  216. data/lib/fontisan/tables/cff.rb +22 -15
  217. data/lib/fontisan/tables/cff2/charstring_parser.rb +0 -2
  218. data/lib/fontisan/tables/cff2/table_builder.rb +0 -11
  219. data/lib/fontisan/tables/cff2/table_reader.rb +0 -2
  220. data/lib/fontisan/tables/cff2.rb +13 -14
  221. data/lib/fontisan/tables/cmap.rb +24 -2
  222. data/lib/fontisan/tables/cmap_table.rb +0 -3
  223. data/lib/fontisan/tables/colr.rb +0 -1
  224. data/lib/fontisan/tables/cpal.rb +0 -1
  225. data/lib/fontisan/tables/cvar.rb +0 -2
  226. data/lib/fontisan/tables/fvar.rb +0 -1
  227. data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +0 -2
  228. data/lib/fontisan/tables/glyf/glyph_builder.rb +0 -3
  229. data/lib/fontisan/tables/glyf.rb +0 -6
  230. data/lib/fontisan/tables/glyf_table.rb +0 -3
  231. data/lib/fontisan/tables/gpos.rb +0 -2
  232. data/lib/fontisan/tables/gsub.rb +0 -2
  233. data/lib/fontisan/tables/gvar.rb +0 -2
  234. data/lib/fontisan/tables/head.rb +0 -2
  235. data/lib/fontisan/tables/head_table.rb +0 -3
  236. data/lib/fontisan/tables/hhea.rb +0 -2
  237. data/lib/fontisan/tables/hhea_table.rb +0 -3
  238. data/lib/fontisan/tables/hmtx.rb +0 -2
  239. data/lib/fontisan/tables/hmtx_table.rb +0 -3
  240. data/lib/fontisan/tables/hvar.rb +0 -3
  241. data/lib/fontisan/tables/loca.rb +0 -2
  242. data/lib/fontisan/tables/loca_table.rb +0 -3
  243. data/lib/fontisan/tables/maxp.rb +0 -2
  244. data/lib/fontisan/tables/maxp_table.rb +0 -3
  245. data/lib/fontisan/tables/mvar.rb +0 -3
  246. data/lib/fontisan/tables/name.rb +0 -2
  247. data/lib/fontisan/tables/name_table.rb +0 -3
  248. data/lib/fontisan/tables/os2_table.rb +0 -3
  249. data/lib/fontisan/tables/post_table.rb +0 -3
  250. data/lib/fontisan/tables/sbix.rb +0 -1
  251. data/lib/fontisan/tables/svg.rb +0 -1
  252. data/lib/fontisan/tables/variation_common.rb +0 -1
  253. data/lib/fontisan/tables/vvar.rb +0 -3
  254. data/lib/fontisan/tables.rb +54 -0
  255. data/lib/fontisan/true_type_collection.rb +6 -14
  256. data/lib/fontisan/true_type_font.rb +8 -2
  257. data/lib/fontisan/true_type_font_extensions.rb +9 -9
  258. data/lib/fontisan/type1/afm_generator.rb +0 -4
  259. data/lib/fontisan/type1/conversion_options.rb +0 -2
  260. data/lib/fontisan/type1/encodings.rb +0 -2
  261. data/lib/fontisan/type1/generator.rb +0 -8
  262. data/lib/fontisan/type1/pfa_generator.rb +0 -3
  263. data/lib/fontisan/type1/pfb_generator.rb +0 -5
  264. data/lib/fontisan/type1/pfm_generator.rb +0 -4
  265. data/lib/fontisan/type1.rb +42 -69
  266. data/lib/fontisan/type1_font.rb +40 -11
  267. data/lib/fontisan/ucd/aggregator.rb +73 -0
  268. data/lib/fontisan/ucd/cache_manager.rb +111 -0
  269. data/lib/fontisan/ucd/config.rb +59 -0
  270. data/lib/fontisan/ucd/download_error.rb +9 -0
  271. data/lib/fontisan/ucd/downloader.rb +88 -0
  272. data/lib/fontisan/ucd/error.rb +8 -0
  273. data/lib/fontisan/ucd/index.rb +103 -0
  274. data/lib/fontisan/ucd/index_builder.rb +107 -0
  275. data/lib/fontisan/ucd/range_entry.rb +56 -0
  276. data/lib/fontisan/ucd/unknown_version_error.rb +9 -0
  277. data/lib/fontisan/ucd/version_resolver.rb +79 -0
  278. data/lib/fontisan/ucd.rb +23 -0
  279. data/lib/fontisan/utilities/checksum_calculator.rb +0 -1
  280. data/lib/fontisan/utilities.rb +10 -0
  281. data/lib/fontisan/utils.rb +10 -0
  282. data/lib/fontisan/validation/collection_validator.rb +0 -2
  283. data/lib/fontisan/validation.rb +9 -0
  284. data/lib/fontisan/validators/basic_validator.rb +0 -2
  285. data/lib/fontisan/validators/font_book_validator.rb +0 -2
  286. data/lib/fontisan/validators/opentype_validator.rb +0 -2
  287. data/lib/fontisan/validators/profile_loader.rb +0 -5
  288. data/lib/fontisan/validators/validator.rb +0 -2
  289. data/lib/fontisan/validators/web_font_validator.rb +0 -2
  290. data/lib/fontisan/validators.rb +14 -0
  291. data/lib/fontisan/variable/delta_applicator.rb +0 -4
  292. data/lib/fontisan/variable/instancer.rb +0 -3
  293. data/lib/fontisan/variable/static_font_builder.rb +0 -3
  294. data/lib/fontisan/variable.rb +16 -0
  295. data/lib/fontisan/variation/blend_applier.rb +0 -2
  296. data/lib/fontisan/variation/cache.rb +0 -2
  297. data/lib/fontisan/variation/converter.rb +0 -3
  298. data/lib/fontisan/variation/data_extractor.rb +0 -2
  299. data/lib/fontisan/variation/delta_applier.rb +0 -5
  300. data/lib/fontisan/variation/inspector.rb +0 -1
  301. data/lib/fontisan/variation/instance_generator.rb +0 -6
  302. data/lib/fontisan/variation/instance_writer.rb +0 -5
  303. data/lib/fontisan/variation/metrics_adjuster.rb +0 -4
  304. data/lib/fontisan/variation/optimizer.rb +0 -3
  305. data/lib/fontisan/variation/parallel_generator.rb +0 -3
  306. data/lib/fontisan/variation/subsetter.rb +0 -4
  307. data/lib/fontisan/variation/tuple_variation_header.rb +0 -2
  308. data/lib/fontisan/variation/variable_svg_generator.rb +0 -3
  309. data/lib/fontisan/variation/variation_context.rb +0 -3
  310. data/lib/fontisan/variation/variation_preserver.rb +0 -3
  311. data/lib/fontisan/variation.rb +31 -0
  312. data/lib/fontisan/version.rb +1 -1
  313. data/lib/fontisan/woff2.rb +13 -0
  314. data/lib/fontisan/woff2_font.rb +31 -9
  315. data/lib/fontisan/woff_font.rb +31 -2
  316. data/lib/fontisan.rb +124 -196
  317. metadata +128 -7
  318. data/fontisan.gemspec +0 -47
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # Hinting summary for one face.
9
+ #
10
+ # Answers the practical questions a designer or QA engineer asks:
11
+ # "Is this font hinted at all? What flavour? How much hinting, by
12
+ # byte / instruction count?" Unhinted fonts render poorly at small
13
+ # sizes; heavily hinted fonts can be 20%+ bytecode by file size.
14
+ #
15
+ # TrueType hinting surfaces as the fpgm/prep/cvt programs plus the
16
+ # gasp per-ppem policy. CFF/CFF2 hinting surfaces as stem hints
17
+ # encoded inside each CharString. This model carries both, plus a
18
+ # derived `is_unhinted` flag and `hinting_format` classification so
19
+ # downstream tooling does not need to re-derive either.
20
+ #
21
+ # All counts are nil-safe: a face with no hinting at all produces
22
+ # `Hinting.new` with every field falsy/nil rather than raising.
23
+ class Hinting < Lutaml::Model::Serializable
24
+ # TrueType bytecode programs.
25
+ attribute :has_fpgm, Lutaml::Model::Type::Boolean
26
+ attribute :fpgm_instruction_count, :integer
27
+ attribute :has_prep, Lutaml::Model::Type::Boolean
28
+ attribute :prep_instruction_count, :integer
29
+
30
+ # TrueType Control Value Table (hinting metrics).
31
+ attribute :has_cvt, Lutaml::Model::Type::Boolean
32
+ attribute :cvt_entry_count, :integer
33
+
34
+ # CVT variation table for variable TrueType fonts. Carried for
35
+ # context only — never included in cvt_entry_count.
36
+ attribute :has_cvar, Lutaml::Model::Type::Boolean
37
+
38
+ # gasp policy ranges, ordered by ascending max_ppem.
39
+ attribute :gasp_ranges, GaspRange, collection: true
40
+
41
+ # CFF/CFF2 hinting. cff_has_private_dict is true for every CFF
42
+ # face (Private DICT is mandatory); cff_hint_count sums stem
43
+ # declarations across all CharStrings, nil when unparsable.
44
+ attribute :cff_has_private_dict, Lutaml::Model::Type::Boolean
45
+ attribute :cff_hint_count, :integer
46
+
47
+ # Derived at extraction time so consumers read flat fields.
48
+ attribute :is_unhinted, Lutaml::Model::Type::Boolean
49
+ attribute :hinting_format, :string
50
+
51
+ key_value do
52
+ map "has_fpgm", to: :has_fpgm
53
+ map "fpgm_instruction_count", to: :fpgm_instruction_count
54
+ map "has_prep", to: :has_prep
55
+ map "prep_instruction_count", to: :prep_instruction_count
56
+ map "has_cvt", to: :has_cvt
57
+ map "cvt_entry_count", to: :cvt_entry_count
58
+ map "has_cvar", to: :has_cvar
59
+ map "gasp_ranges", to: :gasp_ranges
60
+ map "cff_has_private_dict", to: :cff_has_private_dict
61
+ map "cff_hint_count", to: :cff_hint_count
62
+ map "is_unhinted", to: :is_unhinted
63
+ map "hinting_format", to: :hinting_format
64
+ end
65
+
66
+ FORMAT_TRUETYPE = "truetype"
67
+ FORMAT_CFF = "cff"
68
+ FORMAT_MIXED = "mixed"
69
+ FORMAT_NONE = "none"
70
+
71
+ # Derive {is_unhinted} and {hinting_format} from individual flags.
72
+ # Called by the extractor before construction so the values land
73
+ # in serialized output without recomputation at read time.
74
+ #
75
+ # gasp is a TrueType-specific table, so it counts toward the
76
+ # TrueType hinting bucket even when no fpgm/prep/cvt is present.
77
+ #
78
+ # @return [Hash] keys :is_unhinted, :hinting_format
79
+ def self.derive_flags(has_tt:, has_cff:, has_gasp:)
80
+ tt_hints = has_tt || has_gasp
81
+ any = tt_hints || has_cff
82
+ format =
83
+ if tt_hints && has_cff then FORMAT_MIXED
84
+ elsif tt_hints then FORMAT_TRUETYPE
85
+ elsif has_cff then FORMAT_CFF
86
+ else FORMAT_NONE
87
+ end
88
+ { is_unhinted: !any, hinting_format: format }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # Aggregate view over a directory (tree) of audited fonts.
9
+ #
10
+ # Built by {Audit::LibraryAuditor}. Combines a flat list of
11
+ # per-face {AuditReport}s with derived cross-face rollups:
12
+ # script coverage matrix, duplicate detection (by source_sha256),
13
+ # and license distribution. Lets a librarian inventory a font
14
+ # collection in one pass.
15
+ class LibrarySummary < Lutaml::Model::Serializable
16
+ attribute :root_path, :string
17
+ attribute :total_files, :integer
18
+ attribute :total_faces, :integer
19
+ attribute :scanned_extensions, :string, collection: true
20
+ attribute :aggregate_metrics, :hash
21
+ attribute :script_coverage, ScriptCoverageRow, collection: true
22
+ attribute :duplicate_groups, DuplicateGroup, collection: true
23
+ attribute :license_distribution, :hash
24
+ attribute :per_face_reports, AuditReport, collection: true
25
+
26
+ key_value do
27
+ map "root_path", to: :root_path
28
+ map "total_files", to: :total_files
29
+ map "total_faces", to: :total_faces
30
+ map "scanned_extensions", to: :scanned_extensions
31
+ map "aggregate_metrics", to: :aggregate_metrics
32
+ map "script_coverage", to: :script_coverage
33
+ map "duplicate_groups", to: :duplicate_groups
34
+ map "license_distribution", to: :license_distribution
35
+ map "per_face_reports", to: :per_face_reports
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # Licensing + embedding + vendor provenance fields for a face.
9
+ #
10
+ # Combines the human-readable legal/identity fields from the name
11
+ # table with the machine-readable embedding permissions from OS/2.
12
+ # Type 1 fonts have no OS/2 — callers must tolerate a nil
13
+ # embedding_type / fs_selection_flags / vendor_id.
14
+ class Licensing < Lutaml::Model::Serializable
15
+ # Name-table fields (English name IDs)
16
+ attribute :copyright, :string
17
+ attribute :trademark, :string
18
+ attribute :manufacturer, :string
19
+ attribute :designer, :string
20
+ attribute :description, :string
21
+ attribute :vendor_url, :string
22
+ attribute :designer_url, :string
23
+ attribute :license_description, :string
24
+ attribute :license_url, :string
25
+
26
+ # OS/2 fields
27
+ attribute :vendor_id, :string
28
+ attribute :embedding_type, :string
29
+ attribute :fs_selection_flags, :string, collection: true
30
+
31
+ key_value do
32
+ map "copyright", to: :copyright
33
+ map "trademark", to: :trademark
34
+ map "manufacturer", to: :manufacturer
35
+ map "designer", to: :designer
36
+ map "description", to: :description
37
+ map "vendor_url", to: :vendor_url
38
+ map "designer_url", to: :designer_url
39
+ map "license_description", to: :license_description
40
+ map "license_url", to: :license_url
41
+ map "vendor_id", to: :vendor_id
42
+ map "embedding_type", to: :embedding_type
43
+ map "fs_selection_flags", to: :fs_selection_flags
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # Layout-critical metrics for a face, consolidated from head, hhea,
9
+ # OS/2, and post tables. Designers and engineers can read all
10
+ # spacing-relevant numbers in one place instead of cross-referencing
11
+ # raw table dumps.
12
+ #
13
+ # All fields are nil-safe — Type 1 fonts and stripped WOFF builds
14
+ # may not carry every table. Derived booleans (e.g. metrics_consistent?)
15
+ # tolerate nil inputs and return false rather than raising.
16
+ class Metrics < Lutaml::Model::Serializable
17
+ # head
18
+ attribute :units_per_em, :integer
19
+ attribute :bbox_x_min, :integer
20
+ attribute :bbox_y_min, :integer
21
+ attribute :bbox_x_max, :integer
22
+ attribute :bbox_y_max, :integer
23
+
24
+ # hhea (horizontal)
25
+ attribute :hhea_ascent, :integer
26
+ attribute :hhea_descent, :integer
27
+ attribute :hhea_line_gap, :integer
28
+
29
+ # OS/2 typo
30
+ attribute :typo_ascender, :integer
31
+ attribute :typo_descender, :integer
32
+ attribute :typo_line_gap, :integer
33
+
34
+ # OS/2 win
35
+ attribute :win_ascent, :integer
36
+ attribute :win_descent, :integer
37
+
38
+ # OS/2 v2+ (optional)
39
+ attribute :x_height, :integer
40
+ attribute :cap_height, :integer
41
+
42
+ # OS/2 subscript/superscript
43
+ attribute :subscript_x_size, :integer
44
+ attribute :subscript_y_size, :integer
45
+ attribute :subscript_x_offset, :integer
46
+ attribute :subscript_y_offset, :integer
47
+ attribute :superscript_x_size, :integer
48
+ attribute :superscript_y_size, :integer
49
+ attribute :superscript_x_offset, :integer
50
+ attribute :superscript_y_offset, :integer
51
+
52
+ # OS/2 strikeout
53
+ attribute :strikeout_size, :integer
54
+ attribute :strikeout_position, :integer
55
+
56
+ # post underline
57
+ attribute :underline_position, :float
58
+ attribute :underline_thickness, :float
59
+
60
+ key_value do
61
+ map "units_per_em", to: :units_per_em
62
+ map "bbox_x_min", to: :bbox_x_min
63
+ map "bbox_y_min", to: :bbox_y_min
64
+ map "bbox_x_max", to: :bbox_x_max
65
+ map "bbox_y_max", to: :bbox_y_max
66
+
67
+ map "hhea_ascent", to: :hhea_ascent
68
+ map "hhea_descent", to: :hhea_descent
69
+ map "hhea_line_gap", to: :hhea_line_gap
70
+
71
+ map "typo_ascender", to: :typo_ascender
72
+ map "typo_descender", to: :typo_descender
73
+ map "typo_line_gap", to: :typo_line_gap
74
+
75
+ map "win_ascent", to: :win_ascent
76
+ map "win_descent", to: :win_descent
77
+
78
+ map "x_height", to: :x_height
79
+ map "cap_height", to: :cap_height
80
+
81
+ map "subscript_x_size", to: :subscript_x_size
82
+ map "subscript_y_size", to: :subscript_y_size
83
+ map "subscript_x_offset", to: :subscript_x_offset
84
+ map "subscript_y_offset", to: :subscript_y_offset
85
+ map "superscript_x_size", to: :superscript_x_size
86
+ map "superscript_y_size", to: :superscript_y_size
87
+ map "superscript_x_offset", to: :superscript_x_offset
88
+ map "superscript_y_offset", to: :superscript_y_offset
89
+
90
+ map "strikeout_size", to: :strikeout_size
91
+ map "strikeout_position", to: :strikeout_position
92
+
93
+ map "underline_position", to: :underline_position
94
+ map "underline_thickness", to: :underline_thickness
95
+ end
96
+
97
+ # True when hhea ascent/descent match OS/2 typo ascent/descent.
98
+ # Mismatch is a common font bug that causes inconsistent line
99
+ # height across platforms.
100
+ #
101
+ # @return [Boolean]
102
+ def metrics_consistent?
103
+ return false if hhea_ascent.nil? || typo_ascender.nil?
104
+ return false if hhea_descent.nil? || typo_descender.nil?
105
+
106
+ hhea_ascent == typo_ascender && hhea_descent == typo_descender
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # One fvar named instance (e.g. "Bold", "SemiCondensed").
9
+ #
10
+ # `coordinates` is serialized as a compact "tag=value,tag=value" string
11
+ # (e.g. "wght=700,wdth=100") for human readability. The AuditReport is
12
+ # primarily a human-facing artifact; downstream tooling that needs
13
+ # structured coordinates can re-derive them from fvar.
14
+ class NamedInstance < Lutaml::Model::Serializable
15
+ attribute :subfamily_name, :string
16
+ attribute :postscript_name, :string
17
+ attribute :coordinates, :string
18
+
19
+ key_value do
20
+ map "subfamily_name", to: :subfamily_name
21
+ map "postscript_name", to: :postscript_name
22
+ map "coordinates", to: :coordinates
23
+ end
24
+
25
+ # Build the coordinates string from a parallel array of axis tags
26
+ # and fvar coordinate values. Returns nil if either side is empty.
27
+ #
28
+ # @param axis_tags [Array<String>] ordered axis tags (e.g. ["wght", "wdth"])
29
+ # @param values [Array<Numeric>] ordered coordinate values
30
+ # @return [String, nil]
31
+ def self.format_coordinates(axis_tags, values)
32
+ return nil if axis_tags.nil? || values.nil?
33
+ return nil if axis_tags.empty? || values.empty?
34
+
35
+ pairs = axis_tags.zip(values).map { |tag, val| "#{tag}=#{val}" }
36
+ pairs.join(",")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # Structured OpenType layout summary for one face.
9
+ #
10
+ # Replaces the previous flat `opentype_scripts` + `features` pair
11
+ # on AuditReport for MECE cleanliness. Carries:
12
+ #
13
+ # - `scripts`: union of GSUB + GPOS script tags (sorted, unique).
14
+ # - `features`: union of GSUB + GPOS feature tags across every
15
+ # script (sorted, unique).
16
+ # - `by_script`: per-script breakdown preserving the
17
+ # "feature X is for script Y" relationship that the flat arrays
18
+ # discarded.
19
+ # - `has_gsub` / `has_gpos`: presence flags so consumers can tell
20
+ # "font has no layout" from "font has GSUB but no GPOS".
21
+ #
22
+ # nil for Type 1 fonts (no SFNT table structure).
23
+ class OpenTypeLayout < Lutaml::Model::Serializable
24
+ attribute :scripts, :string, collection: true
25
+ attribute :features, :string, collection: true
26
+ attribute :by_script, ScriptFeatures, collection: true
27
+ attribute :has_gsub, Lutaml::Model::Type::Boolean
28
+ attribute :has_gpos, Lutaml::Model::Type::Boolean
29
+
30
+ key_value do
31
+ map "scripts", to: :scripts
32
+ map "features", to: :features
33
+ map "by_script", to: :by_script
34
+ map "has_gsub", to: :has_gsub
35
+ map "has_gpos", to: :has_gpos
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # One row in a LibrarySummary's script-coverage matrix.
9
+ #
10
+ # Lists every face (by postscript_name) whose cmap covers at least
11
+ # one codepoint assigned to a Unicode script. Lets a librarian
12
+ # answer "which fonts cover Cyrillic?" without re-auditing.
13
+ class ScriptCoverageRow < Lutaml::Model::Serializable
14
+ attribute :script, :string
15
+ attribute :face_count, :integer
16
+ attribute :faces, :string, collection: true
17
+
18
+ key_value do
19
+ map "script", to: :script
20
+ map "face_count", to: :face_count
21
+ map "faces", to: :faces
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # Per-script breakdown of OpenType features.
9
+ #
10
+ # Pairs a script tag (e.g. "latn", "kana ") with the GSUB features
11
+ # and GPOS features that apply to it. The two collections are
12
+ # kept separate because substitution and positioning have different
13
+ # semantics — consumers answering "does this font support kerning
14
+ # for Latin?" want to look at GPOS only.
15
+ class ScriptFeatures < Lutaml::Model::Serializable
16
+ attribute :script, :string
17
+ attribute :gsub_features, :string, collection: true
18
+ attribute :gpos_features, :string, collection: true
19
+
20
+ key_value do
21
+ map "script", to: :script
22
+ map "gsub_features", to: :gsub_features
23
+ map "gpos_features", to: :gpos_features
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Audit
8
+ # Variable-font detail for one face.
9
+ #
10
+ # Bundles everything fvar-derived (axes + named instances) with the
11
+ # presence flags for every variation side-table (avar/cvar/HVAR/VVAR/
12
+ # MVAR/gvar). Replaces the previous flat `axes` + `is_variable` pair
13
+ # on AuditReport for MECE cleanliness — a face is variable iff this
14
+ # object is non-nil.
15
+ #
16
+ # `axes` reuses the existing AuditAxis shape; `named_instances` is a
17
+ # parallel NamedInstance collection. The has_* booleans are presence
18
+ # checks only — they don't validate the table contents.
19
+ class VariationDetail < Lutaml::Model::Serializable
20
+ attribute :axes, AuditAxis, collection: true
21
+ attribute :named_instances, NamedInstance, collection: true
22
+
23
+ # Variation side-table presence flags.
24
+ attribute :has_avar, Lutaml::Model::Type::Boolean # axis variation
25
+ attribute :has_cvar, Lutaml::Model::Type::Boolean # CVT variation
26
+ attribute :has_hvar, Lutaml::Model::Type::Boolean # horizontal metrics
27
+ attribute :has_vvar, Lutaml::Model::Type::Boolean # vertical metrics
28
+ attribute :has_mvar, Lutaml::Model::Type::Boolean # metrics variation
29
+ attribute :has_gvar, Lutaml::Model::Type::Boolean # glyph variation (TT)
30
+
31
+ key_value do
32
+ map "axes", to: :axes
33
+ map "named_instances", to: :named_instances
34
+ map "has_avar", to: :has_avar
35
+ map "has_cvar", to: :has_cvar
36
+ map "has_hvar", to: :has_hvar
37
+ map "has_vvar", to: :has_vvar
38
+ map "has_mvar", to: :has_mvar
39
+ map "has_gvar", to: :has_gvar
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Namespace hub for audit-related models.
4
+ #
5
+ # All Models::Audit::* constants are autoloaded from here.
6
+
7
+ module Fontisan
8
+ module Models
9
+ module Audit
10
+ autoload :AuditBlock, "fontisan/models/audit/audit_block"
11
+ autoload :AuditAxis, "fontisan/models/audit/audit_axis"
12
+ autoload :AuditDiff, "fontisan/models/audit/audit_diff"
13
+ autoload :AuditReport, "fontisan/models/audit/audit_report"
14
+ autoload :CodepointRange, "fontisan/models/audit/codepoint_range"
15
+ autoload :CodepointSetDiff, "fontisan/models/audit/codepoint_set_diff"
16
+ autoload :ColorCapabilities, "fontisan/models/audit/color_capabilities"
17
+ autoload :DuplicateGroup, "fontisan/models/audit/duplicate_group"
18
+ autoload :EmbeddingType, "fontisan/models/audit/embedding_type"
19
+ autoload :FieldChange, "fontisan/models/audit/field_change"
20
+ autoload :FsSelectionFlags, "fontisan/models/audit/fs_selection_flags"
21
+ autoload :GaspRange, "fontisan/models/audit/gasp_range"
22
+ autoload :Hinting, "fontisan/models/audit/hinting"
23
+ autoload :LibrarySummary, "fontisan/models/audit/library_summary"
24
+ autoload :Licensing, "fontisan/models/audit/licensing"
25
+ autoload :Metrics, "fontisan/models/audit/metrics"
26
+ autoload :NamedInstance, "fontisan/models/audit/named_instance"
27
+ autoload :OpenTypeLayout, "fontisan/models/audit/opentype_layout"
28
+ autoload :ScriptCoverageRow, "fontisan/models/audit/script_coverage_row"
29
+ autoload :ScriptFeatures, "fontisan/models/audit/script_features"
30
+ autoload :VariationDetail, "fontisan/models/audit/variation_detail"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Cldr
8
+ # Per-language coverage for one face against a CLDR exemplar set.
9
+ #
10
+ # `coverage_ratio` is in [0.0, 1.0] rounded to 4 decimal places;
11
+ # `fully_supported` is true only when every required codepoint
12
+ # (total > 0) is covered. A language with total == 0 (empty exemplar
13
+ # set in the index) is reported as ratio 0.0, fully_supported false.
14
+ class LanguageCoverage < Lutaml::Model::Serializable
15
+ attribute :language, :string
16
+ attribute :covered, :integer
17
+ attribute :total, :integer
18
+ attribute :coverage_ratio, :float
19
+ attribute :fully_supported, Lutaml::Model::Type::Boolean
20
+
21
+ key_value do
22
+ map "language", to: :language
23
+ map "covered", to: :covered
24
+ map "total", to: :total
25
+ map "coverage_ratio", to: :coverage_ratio
26
+ map "fully_supported", to: :fully_supported
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ # Namespace for CLDR-derived audit models.
8
+ module Cldr
9
+ autoload :LanguageCoverage, "fontisan/models/cldr/language_coverage"
10
+ end
11
+ end
12
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "font_info"
5
4
 
6
5
  module Fontisan
7
6
  module Models
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "table_sharing_info"
5
- require_relative "font_info"
6
4
 
7
5
  module Fontisan
8
6
  module Models
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "collection_font_summary"
5
4
 
6
5
  module Fontisan
7
6
  module Models
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "font_report"
4
- require_relative "validation_report"
5
3
  require "lutaml/model"
6
4
 
7
5
  module Fontisan
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "color_layer"
5
4
 
6
5
  module Fontisan
7
6
  module Models
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "validation_report"
4
3
  require "lutaml/model"
5
4
 
6
5
  module Fontisan
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Models::Ttx::Tables namespace.
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Ttx
8
+ module Tables
9
+ autoload :BinaryTable, "fontisan/models/ttx/tables/binary_table"
10
+ autoload :HeadTable, "fontisan/models/ttx/tables/head_table"
11
+ autoload :HheaTable, "fontisan/models/ttx/tables/hhea_table"
12
+ autoload :MaxpTable, "fontisan/models/ttx/tables/maxp_table"
13
+ autoload :NameRecord, "fontisan/models/ttx/tables/name_table"
14
+ autoload :NameTable, "fontisan/models/ttx/tables/name_table"
15
+ autoload :Os2Table, "fontisan/models/ttx/tables/os2_table"
16
+ autoload :Panose, "fontisan/models/ttx/tables/os2_table"
17
+ autoload :PostTable, "fontisan/models/ttx/tables/post_table"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,14 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "glyph_order"
5
- require_relative "tables/head_table"
6
- require_relative "tables/name_table"
7
- require_relative "tables/maxp_table"
8
- require_relative "tables/hhea_table"
9
- require_relative "tables/os2_table"
10
- require_relative "tables/post_table"
11
- require_relative "tables/binary_table"
12
4
 
13
5
  module Fontisan
14
6
  module Models
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Models::Ttx namespace.
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Ttx
8
+ autoload :GlyphId, "fontisan/models/ttx/glyph_order"
9
+ autoload :GlyphOrder, "fontisan/models/ttx/glyph_order"
10
+ autoload :Tables, "fontisan/models/ttx/tables/os2_table"
11
+ autoload :TtFont, "fontisan/models/ttx/ttfont"
12
+ end
13
+ end
14
+ end