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,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Ucd
8
+ # Root <ucd> element of the UCDXML flat file.
9
+ #
10
+ # The flat UCDXML file has the structure:
11
+ #
12
+ # <ucd>
13
+ # <description>...</description>
14
+ # <last_revision date="2025-..." version="17.0.0" />
15
+ # <char cp="0000" .../>
16
+ # <char cp="0001" .../>
17
+ # ...
18
+ # <char first-cp="3400" last-cp="4DBF" .../>
19
+ # ...
20
+ # </ucd>
21
+ #
22
+ # The flat variant merges all per-category UCD files (Blocks.txt,
23
+ # Scripts.txt, UnicodeData.txt, etc.) into one stream of <char>
24
+ # elements. Roughly 340,000 entries for Unicode 17.0.0.
25
+ class Ucd < Lutaml::Model::Serializable
26
+ attribute :last_revision, :string
27
+ attribute :chars, UcdChar, collection: true
28
+
29
+ xml do
30
+ element "ucd"
31
+
32
+ map_element "last_revision", to: :last_revision
33
+ map_element "char", to: :chars
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ module Ucd
8
+ # Single <char> element from the UCDXML flat file.
9
+ #
10
+ # UCDXML uses two forms:
11
+ # <char cp="0041" name="..." script="Latin" block="Basic Latin" .../>
12
+ # <char first-cp="3400" last-cp="4DBF" name="..." script="Han" .../>
13
+ #
14
+ # The first form describes one codepoint. The second form describes a
15
+ # closed range of codepoints that share the same properties (used for
16
+ # CJK ideograph ranges where each codepoint would otherwise need its
17
+ # own <char> entry).
18
+ #
19
+ # Both forms can appear in the same document; `cp` is mutually
20
+ # exclusive with `first-cp`/`last-cp`.
21
+ class UcdChar < Lutaml::Model::Serializable
22
+ attribute :cp, :string
23
+ attribute :first_cp, :string
24
+ attribute :last_cp, :string
25
+ attribute :name, :string
26
+ attribute :general_category, :string
27
+ attribute :script, :string
28
+ attribute :block, :string
29
+ attribute :age, :string
30
+
31
+ xml do
32
+ element "char"
33
+
34
+ map_attribute "cp", to: :cp
35
+ map_attribute "first-cp", to: :first_cp
36
+ map_attribute "last-cp", to: :last_cp
37
+ map_attribute "name", to: :name
38
+ map_attribute "general-category", to: :general_category
39
+ map_attribute "script", to: :script
40
+ map_attribute "block", to: :block
41
+ map_attribute "age", to: :age
42
+ end
43
+
44
+ # True if this entry describes a codepoint range rather than a
45
+ # single codepoint.
46
+ def range?
47
+ !first_cp.nil? && !last_cp.nil?
48
+ end
49
+
50
+ # The codepoints covered by this entry, as Integers.
51
+ # For a single-codepoint entry, returns a one-element array.
52
+ # For a range entry, returns the inclusive range as an array
53
+ # (caller should treat this lazily if the range is huge — CJK
54
+ # ranges can have tens of thousands of codepoints).
55
+ def codepoints
56
+ if range?
57
+ (first_cp.to_i(16)..last_cp.to_i(16)).to_a
58
+ elsif cp
59
+ [cp.to_i(16)]
60
+ else
61
+ []
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Fontisan
6
+ module Models
7
+ # Namespace for UCDXML deserialization models.
8
+ #
9
+ # These classes deserialize the upstream UCDXML flat file
10
+ # (https://www.unicode.org/Public/<version>/ucdxml/ucd.all.flat.zip)
11
+ # into Ruby objects. They are used by Fontisan::Ucd::IndexBuilder to
12
+ # derive compact run-length-encoded indices for Unicode block and
13
+ # script lookup.
14
+ module Ucd
15
+ autoload :UcdChar, "fontisan/models/ucd/ucd_char"
16
+ autoload :Ucd, "fontisan/models/ucd/ucd"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Models namespace.
4
+
5
+ module Fontisan
6
+ module Models
7
+ autoload :AllScriptsFeaturesInfo, "fontisan/models/all_scripts_features_info"
8
+ autoload :Audit, "fontisan/models/audit"
9
+ autoload :AxisInfo, "fontisan/models/variable_font_info"
10
+ autoload :BitmapGlyph, "fontisan/models/bitmap_glyph"
11
+ autoload :BitmapStrike, "fontisan/models/bitmap_strike"
12
+ autoload :CollectionBriefInfo, "fontisan/models/collection_brief_info"
13
+ autoload :CollectionFontSummary, "fontisan/models/collection_font_summary"
14
+ autoload :CollectionInfo, "fontisan/models/collection_info"
15
+ autoload :CollectionListInfo, "fontisan/models/collection_list_info"
16
+ autoload :CollectionValidationReport, "fontisan/models/collection_validation_report"
17
+ autoload :Cldr, "fontisan/models/cldr"
18
+ autoload :ColorGlyph, "fontisan/models/color_glyph"
19
+ autoload :ColorLayer, "fontisan/models/color_layer"
20
+ autoload :ColorPalette, "fontisan/models/color_palette"
21
+ autoload :FeatureRecord, "fontisan/models/features_info"
22
+ autoload :FeaturesInfo, "fontisan/models/features_info"
23
+ autoload :FontExport, "fontisan/models/font_export"
24
+ autoload :FontInfo, "fontisan/models/font_info"
25
+ autoload :FontReport, "fontisan/models/font_report"
26
+ autoload :FontSummary, "fontisan/models/font_summary"
27
+ autoload :GlyphInfo, "fontisan/models/glyph_info"
28
+ autoload :GlyphOutline, "fontisan/models/glyph_outline"
29
+ autoload :Hint, "fontisan/models/hint"
30
+ autoload :HintSet, "fontisan/models/hint"
31
+ autoload :InstanceInfo, "fontisan/models/variable_font_info"
32
+ autoload :OpticalSizeInfo, "fontisan/models/optical_size_info"
33
+ autoload :Outline, "fontisan/models/outline"
34
+ autoload :ScriptRecord, "fontisan/models/scripts_info"
35
+ autoload :ScriptsInfo, "fontisan/models/scripts_info"
36
+ autoload :SvgGlyph, "fontisan/models/svg_glyph"
37
+ autoload :TableEntry, "fontisan/models/table_info"
38
+ autoload :TableInfo, "fontisan/models/table_info"
39
+ autoload :TableSharingInfo, "fontisan/models/table_sharing_info"
40
+ autoload :Ttx, "fontisan/models/ttx/ttfont"
41
+ autoload :Ucd, "fontisan/models/ucd"
42
+ autoload :UnicodeMapping, "fontisan/models/unicode_mappings"
43
+ autoload :UnicodeMappings, "fontisan/models/unicode_mappings"
44
+ autoload :ValidationReport, "fontisan/models/validation_report"
45
+ autoload :VariableFontInfo, "fontisan/models/variable_font_info"
46
+ end
47
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_collection"
4
-
5
3
  module Fontisan
6
4
  # OpenType Collection domain object
7
5
  #
@@ -15,11 +13,16 @@ module Fontisan
15
13
  # fonts = otc.extract_fonts(io) # => [OpenTypeFont, OpenTypeFont, ...]
16
14
  # end
17
15
  class OpenTypeCollection < BaseCollection
16
+ # High-level pipeline format identifier. Owned by the collection class
17
+ # so the conversion pipeline can dispatch without case statements (OCP).
18
+ #
19
+ # @return [Symbol] :otc
20
+ def format = :otc
21
+
18
22
  # Get the font class for OpenType collections
19
23
  #
20
24
  # @return [Class] OpenTypeFont class
21
25
  def self.font_class
22
- require_relative "open_type_font"
23
26
  OpenTypeFont
24
27
  end
25
28
 
@@ -38,8 +41,6 @@ module Fontisan
38
41
  # @param io [IO] Open file handle to read fonts from
39
42
  # @return [Array<OpenTypeFont>] Array of font objects
40
43
  def extract_fonts(io)
41
- require_relative "open_type_font"
42
-
43
44
  font_offsets.map do |offset|
44
45
  OpenTypeFont.from_collection(io, offset)
45
46
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "sfnt_font"
4
-
5
3
  module Fontisan
6
4
  # OpenType Font domain object
7
5
  #
@@ -26,6 +24,14 @@ module Fontisan
26
24
  # @example Reading from TTC collection
27
25
  # otf = OpenTypeFont.from_collection(io, offset)
28
26
  class OpenTypeFont < SfntFont
27
+ extend OpenTypeFontExtensions
28
+
29
+ # High-level pipeline format identifier. Owned by the font class so the
30
+ # conversion pipeline can dispatch without case statements (OCP).
31
+ #
32
+ # @return [Symbol] :otf
33
+ def format = :otf
34
+
29
35
  # Page cache for lazy loading (maps page_start_offset => page_data)
30
36
  attr_accessor :page_cache
31
37
 
@@ -1,24 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- # Extensions to OpenTypeFont for table-based construction
5
- class OpenTypeFont
4
+ # Extension module for {OpenTypeFont} providing table-based construction.
5
+ #
6
+ # Extended into OpenTypeFont from +open_type_font.rb+ so that
7
+ # +OpenTypeFont.from_tables(...)+ is available whenever the class
8
+ # itself is loaded.
9
+ module OpenTypeFontExtensions
6
10
  # Create font from hash of tables
7
11
  #
8
12
  # This is used during font conversion when we have tables but not a file.
9
13
  #
10
14
  # @param tables [Hash<String, String>] Map of table tag to binary data
11
15
  # @return [OpenTypeFont] New font instance
12
- def self.from_tables(tables)
13
- # Create minimal header structure
16
+ def from_tables(tables)
14
17
  font = new
15
18
  font.initialize_storage
16
19
  font.loading_mode = LoadingModes::FULL
17
20
 
18
- # Store table data
19
21
  font.table_data = tables
20
22
 
21
- # Build header from tables
22
23
  num_tables = tables.size
23
24
  max_power = 0
24
25
  n = num_tables
@@ -37,13 +38,12 @@ module Fontisan
37
38
  font.header.entry_selector = entry_selector
38
39
  font.header.range_shift = range_shift
39
40
 
40
- # Build table directory
41
41
  font.tables.clear
42
42
  tables.each_key do |tag|
43
43
  entry = TableDirectory.new
44
44
  entry.tag = tag
45
- entry.checksum = 0 # Will be calculated on write
46
- entry.offset = 0 # Will be calculated on write
45
+ entry.checksum = 0
46
+ entry.offset = 0
47
47
  entry.table_length = tables[tag].bytesize
48
48
  font.tables << entry
49
49
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "stringio"
4
- require_relative "stack_tracker"
5
4
 
6
5
  module Fontisan
7
6
  module Optimizers
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Optimizers namespace.
4
+
5
+ module Fontisan
6
+ module Optimizers
7
+ autoload :CharstringRewriter, "fontisan/optimizers/charstring_rewriter"
8
+ autoload :PatternAnalyzer, "fontisan/optimizers/pattern_analyzer"
9
+ autoload :StackTracker, "fontisan/optimizers/stack_tracker"
10
+ autoload :SubroutineBuilder, "fontisan/optimizers/subroutine_builder"
11
+ autoload :SubroutineGenerator, "fontisan/optimizers/subroutine_generator"
12
+ autoload :SubroutineOptimizer, "fontisan/optimizers/subroutine_optimizer"
13
+ end
14
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "models/glyph_outline"
4
-
5
3
  module Fontisan
6
4
  # Extracts glyph outlines from font tables
7
5
  #
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bindata"
4
- require_relative "../error"
5
4
 
6
5
  module Fontisan
7
6
  module Parsers
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Parsers namespace.
4
+
5
+ module Fontisan
6
+ module Parsers
7
+ autoload :DfontParser, "fontisan/parsers/dfont_parser"
8
+ autoload :Tag, "fontisan/parsers/tag"
9
+ end
10
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../font_loader"
4
-
5
3
  module Fontisan
6
4
  module Pipeline
7
5
  # Detects font format and capabilities
@@ -50,107 +48,62 @@ module Fontisan
50
48
 
51
49
  # Detect font format
52
50
  #
53
- # @return [Symbol] One of :ttf, :otf, :ttc, :otc, :woff, :woff2, :svg
51
+ # Delegates to the loaded font object's own `#format` method.
52
+ # Each font class (TrueTypeFont, OpenTypeFont, WoffFont, Woff2Font,
53
+ # Type1Font, *Collection) is the single source of truth for its
54
+ # format identifier. This keeps FormatDetector closed for modification
55
+ # when new font classes are added (OCP): no case statement to edit.
56
+ #
57
+ # @return [Symbol] One of :ttf, :otf, :ttc, :otc, :woff, :woff2,
58
+ # :type1, :dfont, or :unknown
54
59
  def detect_format
55
- # Check for SVG first (from file extension even if font failed to load)
60
+ # SVG fonts are an export-only target; no font object exists yet.
56
61
  return :svg if @file_path.end_with?(".svg")
57
62
 
58
63
  return :unknown unless @font
59
64
 
60
- # Use is_a? for proper class checking
61
- case @font
62
- when Fontisan::TrueTypeCollection
63
- :ttc
64
- when Fontisan::OpenTypeCollection
65
- :otc
66
- when Fontisan::TrueTypeFont
67
- if @file_path.end_with?(".woff")
68
- :woff
69
- elsif @file_path.end_with?(".woff2")
70
- :woff2
71
- else
72
- :ttf
73
- end
74
- when Fontisan::OpenTypeFont
75
- if @file_path.end_with?(".woff")
76
- :woff
77
- elsif @file_path.end_with?(".woff2")
78
- :woff2
79
- else
80
- :otf
81
- end
82
- else
83
- :unknown
84
- end
65
+ @font.format
85
66
  end
86
67
 
87
68
  # Detect variation type
88
69
  #
89
- # @return [Symbol] One of :static, :gvar, :cff2
70
+ # Delegates to the loaded font object's own `#variation_type` method.
71
+ # Each font class is the single source of truth for its variation
72
+ # profile, so this method needs no class-specific branching.
73
+ #
74
+ # @return [Symbol] :static, :gvar (TrueType variable), or :cff2
90
75
  def detect_variation
91
76
  return :static unless @font
92
77
 
93
- # Collections don't have has_table? method
94
- # Return :static for collections (variation detection would need to load first font)
95
- return :static if collection?
96
-
97
- # Check for variable font tables
98
- if @font.has_table?("fvar")
99
- # Variable font detected - check variation type
100
- if @font.has_table?("gvar")
101
- :gvar # TrueType variable font
102
- elsif @font.has_table?("CFF2")
103
- :cff2 # OpenType variable font (CFF2)
104
- else
105
- :static # Has fvar but no variation data (shouldn't happen)
106
- end
107
- else
108
- :static
109
- end
78
+ @font.variation_type
110
79
  end
111
80
 
112
81
  # Detect font capabilities
113
82
  #
83
+ # Aggregates four pieces of self-knowledge owned by the font class:
84
+ # outline representation, variation support, collection status, and
85
+ # the table directory. Each is exposed as a method on the font object
86
+ # so this method stays free of class-specific branching.
87
+ #
114
88
  # @return [Hash] Capabilities hash
115
89
  def detect_capabilities
116
90
  return default_capabilities unless @font
117
91
 
118
- # Check if this is a collection
119
- is_collection = collection?
120
-
121
- font_to_check = if is_collection
122
- # Collections don't have fonts method, need to load first font
123
- nil # Will handle in API usage
124
- else
125
- @font
126
- end
127
-
128
- # For collections, return basic capabilities
129
- if is_collection
130
- return {
131
- outline: :unknown, # Would need to load first font to know
132
- variation: false, # Would need to load first font to know
133
- collection: true,
134
- tables: [],
135
- }
136
- end
137
-
138
- return default_capabilities unless font_to_check
139
-
140
92
  {
141
- outline: detect_outline_type(font_to_check),
142
- variation: detect_variation != :static,
143
- collection: false,
144
- tables: available_tables(font_to_check),
93
+ outline: @font.outline_type,
94
+ variation: @font.variation_type != :static,
95
+ collection: @font.collection?,
96
+ tables: @font.table_names,
145
97
  }
146
98
  end
147
99
 
148
100
  # Check if font is a collection
149
101
  #
150
- # @return [Boolean] True if collection (TTC/OTC)
102
+ # @return [Boolean] True if the loaded object is a font collection
151
103
  def collection?
152
- @font.is_a?(Fontisan::TrueTypeCollection) ||
153
- @font.is_a?(Fontisan::OpenTypeCollection)
104
+ return false unless @font
105
+
106
+ @font.collection?
154
107
  end
155
108
 
156
109
  # Check if font is variable
@@ -207,32 +160,6 @@ module Fontisan
207
160
  @font = nil
208
161
  end
209
162
 
210
- # Detect outline type
211
- #
212
- # @param font [Font] Font object
213
- # @return [Symbol] :truetype or :cff
214
- def detect_outline_type(font)
215
- if font.has_table?("glyf") || font.has_table?("gvar")
216
- :truetype
217
- elsif font.has_table?("CFF ") || font.has_table?("CFF2")
218
- :cff
219
- else
220
- :unknown
221
- end
222
- end
223
-
224
- # Get available tables
225
- #
226
- # @param font [Font] Font object
227
- # @return [Array<String>] List of table tags
228
- def available_tables(font)
229
- return [] unless font.respond_to?(:table_names)
230
-
231
- font.table_names
232
- rescue StandardError
233
- []
234
- end
235
-
236
163
  # Default capabilities when font cannot be loaded
237
164
  #
238
165
  # @return [Hash] Default capabilities
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../font_writer"
4
-
5
3
  module Fontisan
6
4
  module Pipeline
7
5
  # Handles writing font tables to various output formats
@@ -33,8 +31,9 @@ module Fontisan
33
31
  # Initialize output writer
34
32
  #
35
33
  # @param output_path [String] Path to write output
36
- # @param format [Symbol] Target format (:ttf, :otf, :woff, :woff2)
37
- # @param options [Hash] Writing options
34
+ # @param format [Symbol] Target format (:ttf, :otf, :woff, :woff2, :svg)
35
+ # @param options [Hash] Writing options. Format-specific compression
36
+ # knobs (zlib_level, brotli_quality, etc.) pass through to the strategy.
38
37
  def initialize(output_path, format, options = {})
39
38
  @output_path = output_path
40
39
  @format = format
@@ -45,8 +44,15 @@ module Fontisan
45
44
  #
46
45
  # @param tables [Hash<String, String>, Hash] Font tables (tag => binary data) or special format result
47
46
  # @return [Integer] Number of bytes written
48
- # @raise [ArgumentError] If format is unsupported
47
+ # @raise [ArgumentError] If format is unsupported or options don't apply
49
48
  def write(tables)
49
+ # Catch cross-format misuse before delegating, so the error message
50
+ # is consistent regardless of whether the format goes through
51
+ # FormatConverter (outline conversions) or this writer (packaging
52
+ # changes for WOFF/WOFF2).
53
+ Converters::FormatConverter.validate_options_for_target!(@format,
54
+ @options)
55
+
50
56
  case @format
51
57
  when :ttf, :otf
52
58
  write_sfnt(tables)
@@ -92,8 +98,6 @@ module Fontisan
92
98
  # @param tables [Hash<String, String>] Font tables
93
99
  # @return [Integer] Number of bytes written
94
100
  def write_woff(tables)
95
- require_relative "../converters/woff_writer"
96
-
97
101
  writer = Converters::WoffWriter.new
98
102
  font = build_font_from_tables(tables)
99
103
  woff_data = writer.convert(font, @options)
@@ -106,8 +110,6 @@ module Fontisan
106
110
  # @param tables [Hash<String, String>] Font tables
107
111
  # @return [Integer] Number of bytes written
108
112
  def write_woff2(tables)
109
- require_relative "../converters/woff2_encoder"
110
-
111
113
  encoder = Converters::Woff2Encoder.new
112
114
  font = build_font_from_tables(tables)
113
115
  result = encoder.convert(font, @options)
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_strategy"
4
- require_relative "../../variation/instance_generator"
5
- require_relative "../../variation/variation_context"
6
-
7
3
  module Fontisan
8
4
  module Pipeline
9
5
  module Strategies
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_strategy"
4
- require_relative "instance_strategy"
5
- require_relative "../../variation/variation_context"
6
-
7
3
  module Fontisan
8
4
  module Pipeline
9
5
  module Strategies
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base_strategy"
4
-
5
3
  module Fontisan
6
4
  module Pipeline
7
5
  module Strategies
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Pipeline::Strategies namespace.
4
+
5
+ module Fontisan
6
+ module Pipeline
7
+ module Strategies
8
+ autoload :BaseStrategy, "fontisan/pipeline/strategies/base_strategy"
9
+ autoload :InstanceStrategy, "fontisan/pipeline/strategies/instance_strategy"
10
+ autoload :NamedStrategy, "fontisan/pipeline/strategies/named_strategy"
11
+ autoload :PreserveStrategy, "fontisan/pipeline/strategies/preserve_strategy"
12
+ end
13
+ end
14
+ end
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "format_detector"
4
- require_relative "variation_resolver"
5
- require_relative "../converters/format_converter"
6
- require_relative "../font_loader"
7
- require_relative "../font_writer"
8
- require_relative "output_writer"
9
-
10
3
  module Fontisan
11
4
  module Pipeline
12
5
  # Orchestrates universal font transformation pipeline
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "strategies/base_strategy"
4
- require_relative "strategies/preserve_strategy"
5
- require_relative "strategies/instance_strategy"
6
- require_relative "strategies/named_strategy"
7
-
8
3
  module Fontisan
9
4
  module Pipeline
10
5
  # Resolves variation data using strategy pattern
@@ -136,7 +131,6 @@ module Fontisan
136
131
  def validate_instance_coordinates(coordinates)
137
132
  return if coordinates.empty?
138
133
 
139
- require_relative "../variation/variation_context"
140
134
  context = Variation::VariationContext.new(@font)
141
135
  context.validate_coordinates(coordinates)
142
136
  end
@@ -146,7 +140,6 @@ module Fontisan
146
140
  # @param instance_index [Integer] Instance index to validate
147
141
  # @raise [ArgumentError] If index invalid
148
142
  def validate_named_instance_index(instance_index)
149
- require_relative "../variation/variation_context"
150
143
  context = Variation::VariationContext.new(@font)
151
144
 
152
145
  unless context.fvar
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Autoload hub for the Fontisan::Pipeline namespace.
4
+
5
+ module Fontisan
6
+ module Pipeline
7
+ autoload :FormatDetector, "fontisan/pipeline/format_detector"
8
+ autoload :OutputWriter, "fontisan/pipeline/output_writer"
9
+ autoload :Strategies, "fontisan/pipeline/strategies/base_strategy"
10
+ autoload :TransformationPipeline, "fontisan/pipeline/transformation_pipeline"
11
+ autoload :VariationResolver, "fontisan/pipeline/variation_resolver"
12
+ end
13
+ end