fontisan 0.2.17 → 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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +14 -90
  3. data/README.adoc +257 -1
  4. data/docs/.vitepress/config.ts +68 -8
  5. data/docs/.vitepress/theme/style.css +570 -272
  6. data/docs/CONVERSION_GUIDE.adoc +31 -8
  7. data/docs/EXTRACT_TTC_MIGRATION.md +1 -1
  8. data/docs/WOFF_WOFF2_FORMATS.adoc +53 -0
  9. data/docs/api/conversion-options.md +37 -14
  10. data/docs/cli/audit.md +337 -0
  11. data/docs/cli/convert.md +20 -1
  12. data/docs/cli/index.md +31 -0
  13. data/docs/guide/color.md +1 -1
  14. data/docs/guide/conversion/options.md +32 -3
  15. data/docs/guide/conversion/ttf-otf.md +1 -1
  16. data/docs/guide/conversion/type1.md +1 -1
  17. data/docs/guide/conversion/web.md +91 -32
  18. data/docs/guide/conversion.md +6 -5
  19. data/docs/guide/formats/woff.md +35 -11
  20. data/docs/guide/index.md +2 -2
  21. data/docs/guide/migrations/extract-ttc.md +1 -1
  22. data/docs/guide/quick-start.md +4 -4
  23. data/docs/guide/type1.md +4 -4
  24. data/docs/guide/woff.md +19 -17
  25. data/docs/index.md +2 -0
  26. data/docs/lychee.toml +5 -1
  27. data/docs/package.json +1 -1
  28. data/docs/public/robots.txt +4 -0
  29. data/docs/scripts/post-build.mjs +81 -0
  30. data/lib/fontisan/audit/codepoint_range_coalescer.rb +41 -0
  31. data/lib/fontisan/audit/context.rb +122 -0
  32. data/lib/fontisan/audit/differ.rb +124 -0
  33. data/lib/fontisan/audit/extractors/aggregations.rb +54 -0
  34. data/lib/fontisan/audit/extractors/base.rb +26 -0
  35. data/lib/fontisan/audit/extractors/color_capabilities.rb +141 -0
  36. data/lib/fontisan/audit/extractors/coverage.rb +48 -0
  37. data/lib/fontisan/audit/extractors/hinting.rb +197 -0
  38. data/lib/fontisan/audit/extractors/identity.rb +52 -0
  39. data/lib/fontisan/audit/extractors/language_coverage.rb +37 -0
  40. data/lib/fontisan/audit/extractors/licensing.rb +79 -0
  41. data/lib/fontisan/audit/extractors/metrics.rb +103 -0
  42. data/lib/fontisan/audit/extractors/opentype_layout.rb +69 -0
  43. data/lib/fontisan/audit/extractors/provenance.rb +29 -0
  44. data/lib/fontisan/audit/extractors/style.rb +32 -0
  45. data/lib/fontisan/audit/extractors/variation_detail.rb +99 -0
  46. data/lib/fontisan/audit/extractors.rb +27 -0
  47. data/lib/fontisan/audit/library_aggregator.rb +83 -0
  48. data/lib/fontisan/audit/library_auditor.rb +90 -0
  49. data/lib/fontisan/audit/registry.rb +60 -0
  50. data/lib/fontisan/audit/style_extractor.rb +80 -0
  51. data/lib/fontisan/audit.rb +20 -0
  52. data/lib/fontisan/base_collection.rb +23 -9
  53. data/lib/fontisan/binary/structures.rb +0 -2
  54. data/lib/fontisan/binary.rb +11 -0
  55. data/lib/fontisan/cldr/aggregator.rb +33 -0
  56. data/lib/fontisan/cldr/cache_manager.rb +110 -0
  57. data/lib/fontisan/cldr/config.rb +59 -0
  58. data/lib/fontisan/cldr/download_error.rb +9 -0
  59. data/lib/fontisan/cldr/downloader.rb +79 -0
  60. data/lib/fontisan/cldr/error.rb +8 -0
  61. data/lib/fontisan/cldr/index.rb +64 -0
  62. data/lib/fontisan/cldr/index_builder.rb +72 -0
  63. data/lib/fontisan/cldr/unicode_set_parser.rb +172 -0
  64. data/lib/fontisan/cldr/unknown_version_error.rb +9 -0
  65. data/lib/fontisan/cldr/version_resolver.rb +91 -0
  66. data/lib/fontisan/cldr.rb +23 -0
  67. data/lib/fontisan/cli/cldr_cli.rb +85 -0
  68. data/lib/fontisan/cli/ucd_cli.rb +97 -0
  69. data/lib/fontisan/cli.rb +201 -2
  70. data/lib/fontisan/collection/builder.rb +0 -4
  71. data/lib/fontisan/collection/dfont_builder.rb +0 -4
  72. data/lib/fontisan/collection/shared_logic.rb +0 -2
  73. data/lib/fontisan/collection/writer.rb +0 -3
  74. data/lib/fontisan/collection.rb +15 -0
  75. data/lib/fontisan/commands/audit_command.rb +123 -0
  76. data/lib/fontisan/commands/audit_compare_command.rb +66 -0
  77. data/lib/fontisan/commands/audit_library_command.rb +46 -0
  78. data/lib/fontisan/commands/base_command.rb +0 -3
  79. data/lib/fontisan/commands/convert_command.rb +25 -20
  80. data/lib/fontisan/commands/dump_table_command.rb +0 -3
  81. data/lib/fontisan/commands/export_command.rb +0 -4
  82. data/lib/fontisan/commands/features_command.rb +0 -3
  83. data/lib/fontisan/commands/instance_command.rb +0 -5
  84. data/lib/fontisan/commands/ls_command.rb +0 -6
  85. data/lib/fontisan/commands/optical_size_command.rb +0 -3
  86. data/lib/fontisan/commands/pack_command.rb +0 -5
  87. data/lib/fontisan/commands/scripts_command.rb +0 -2
  88. data/lib/fontisan/commands/subset_command.rb +0 -3
  89. data/lib/fontisan/commands/unicode_command.rb +0 -3
  90. data/lib/fontisan/commands/unpack_command.rb +0 -7
  91. data/lib/fontisan/commands/validate_command.rb +0 -8
  92. data/lib/fontisan/commands/variable_command.rb +0 -3
  93. data/lib/fontisan/commands.rb +29 -0
  94. data/lib/fontisan/config/cldr.yml +22 -0
  95. data/lib/fontisan/config/conversion_matrix.yml +38 -0
  96. data/lib/fontisan/config/ucd.yml +23 -0
  97. data/lib/fontisan/constants.rb +19 -0
  98. data/lib/fontisan/conversion_options.rb +30 -19
  99. data/lib/fontisan/converters/cff_table_builder.rb +0 -3
  100. data/lib/fontisan/converters/collection_converter.rb +0 -8
  101. data/lib/fontisan/converters/conversion_strategy.rb +161 -46
  102. data/lib/fontisan/converters/format_converter.rb +143 -32
  103. data/lib/fontisan/converters/glyf_table_builder.rb +0 -2
  104. data/lib/fontisan/converters/outline_converter.rb +0 -19
  105. data/lib/fontisan/converters/outline_extraction.rb +0 -5
  106. data/lib/fontisan/converters/outline_optimizer.rb +0 -5
  107. data/lib/fontisan/converters/svg_generator.rb +0 -4
  108. data/lib/fontisan/converters/table_copier.rb +0 -2
  109. data/lib/fontisan/converters/type1_converter.rb +0 -11
  110. data/lib/fontisan/converters/woff2_encoder.rb +49 -20
  111. data/lib/fontisan/converters/woff_writer.rb +211 -282
  112. data/lib/fontisan/converters.rb +21 -0
  113. data/lib/fontisan/dfont_collection.rb +29 -10
  114. data/lib/fontisan/export/exporter.rb +0 -6
  115. data/lib/fontisan/export/transformers/font_to_ttx.rb +0 -9
  116. data/lib/fontisan/export/transformers/head_transformer.rb +0 -2
  117. data/lib/fontisan/export/transformers/hhea_transformer.rb +0 -2
  118. data/lib/fontisan/export/transformers/maxp_transformer.rb +0 -2
  119. data/lib/fontisan/export/transformers/name_transformer.rb +0 -2
  120. data/lib/fontisan/export/transformers/os2_transformer.rb +0 -2
  121. data/lib/fontisan/export/transformers/post_transformer.rb +0 -2
  122. data/lib/fontisan/export/transformers.rb +17 -0
  123. data/lib/fontisan/export.rb +13 -0
  124. data/lib/fontisan/font_loader.rb +14 -19
  125. data/lib/fontisan/font_writer.rb +0 -1
  126. data/lib/fontisan/formatters/audit_diff_text_renderer.rb +122 -0
  127. data/lib/fontisan/formatters/audit_text_renderer.rb +324 -0
  128. data/lib/fontisan/formatters/library_summary_text_renderer.rb +99 -0
  129. data/lib/fontisan/formatters/text_formatter.rb +6 -0
  130. data/lib/fontisan/formatters.rb +12 -0
  131. data/lib/fontisan/hints/hint_converter.rb +0 -1
  132. data/lib/fontisan/hints/postscript_hint_applier.rb +0 -9
  133. data/lib/fontisan/hints/postscript_hint_extractor.rb +0 -2
  134. data/lib/fontisan/hints/truetype_hint_extractor.rb +0 -2
  135. data/lib/fontisan/hints.rb +16 -0
  136. data/lib/fontisan/metrics_calculator.rb +0 -2
  137. data/lib/fontisan/models/all_scripts_features_info.rb +0 -1
  138. data/lib/fontisan/models/audit/audit_axis.rb +30 -0
  139. data/lib/fontisan/models/audit/audit_block.rb +32 -0
  140. data/lib/fontisan/models/audit/audit_diff.rb +77 -0
  141. data/lib/fontisan/models/audit/audit_report.rb +153 -0
  142. data/lib/fontisan/models/audit/codepoint_range.rb +40 -0
  143. data/lib/fontisan/models/audit/codepoint_set_diff.rb +34 -0
  144. data/lib/fontisan/models/audit/color_capabilities.rb +93 -0
  145. data/lib/fontisan/models/audit/duplicate_group.rb +23 -0
  146. data/lib/fontisan/models/audit/embedding_type.rb +76 -0
  147. data/lib/fontisan/models/audit/field_change.rb +28 -0
  148. data/lib/fontisan/models/audit/fs_selection_flags.rb +61 -0
  149. data/lib/fontisan/models/audit/gasp_range.rb +63 -0
  150. data/lib/fontisan/models/audit/hinting.rb +93 -0
  151. data/lib/fontisan/models/audit/library_summary.rb +40 -0
  152. data/lib/fontisan/models/audit/licensing.rb +48 -0
  153. data/lib/fontisan/models/audit/metrics.rb +111 -0
  154. data/lib/fontisan/models/audit/named_instance.rb +41 -0
  155. data/lib/fontisan/models/audit/opentype_layout.rb +40 -0
  156. data/lib/fontisan/models/audit/script_coverage_row.rb +26 -0
  157. data/lib/fontisan/models/audit/script_features.rb +28 -0
  158. data/lib/fontisan/models/audit/variation_detail.rb +44 -0
  159. data/lib/fontisan/models/audit.rb +33 -0
  160. data/lib/fontisan/models/cldr/language_coverage.rb +31 -0
  161. data/lib/fontisan/models/cldr.rb +12 -0
  162. data/lib/fontisan/models/collection_brief_info.rb +0 -1
  163. data/lib/fontisan/models/collection_info.rb +0 -2
  164. data/lib/fontisan/models/collection_list_info.rb +0 -1
  165. data/lib/fontisan/models/collection_validation_report.rb +0 -2
  166. data/lib/fontisan/models/color_glyph.rb +0 -1
  167. data/lib/fontisan/models/font_report.rb +0 -1
  168. data/lib/fontisan/models/ttx/tables.rb +21 -0
  169. data/lib/fontisan/models/ttx/ttfont.rb +0 -8
  170. data/lib/fontisan/models/ttx.rb +14 -0
  171. data/lib/fontisan/models/ucd/ucd.rb +38 -0
  172. data/lib/fontisan/models/ucd/ucd_char.rb +67 -0
  173. data/lib/fontisan/models/ucd.rb +19 -0
  174. data/lib/fontisan/models.rb +47 -0
  175. data/lib/fontisan/open_type_collection.rb +6 -5
  176. data/lib/fontisan/open_type_font.rb +8 -2
  177. data/lib/fontisan/open_type_font_extensions.rb +9 -9
  178. data/lib/fontisan/optimizers/pattern_analyzer.rb +0 -1
  179. data/lib/fontisan/optimizers.rb +14 -0
  180. data/lib/fontisan/outline_extractor.rb +0 -2
  181. data/lib/fontisan/parsers/dfont_parser.rb +0 -1
  182. data/lib/fontisan/parsers.rb +10 -0
  183. data/lib/fontisan/pipeline/format_detector.rb +29 -102
  184. data/lib/fontisan/pipeline/output_writer.rb +11 -9
  185. data/lib/fontisan/pipeline/strategies/instance_strategy.rb +0 -4
  186. data/lib/fontisan/pipeline/strategies/named_strategy.rb +0 -4
  187. data/lib/fontisan/pipeline/strategies/preserve_strategy.rb +0 -2
  188. data/lib/fontisan/pipeline/strategies.rb +14 -0
  189. data/lib/fontisan/pipeline/transformation_pipeline.rb +0 -7
  190. data/lib/fontisan/pipeline/variation_resolver.rb +0 -7
  191. data/lib/fontisan/pipeline.rb +13 -0
  192. data/lib/fontisan/sfnt_font.rb +29 -14
  193. data/lib/fontisan/sfnt_table.rb +0 -4
  194. data/lib/fontisan/subset/builder.rb +0 -6
  195. data/lib/fontisan/subset.rb +13 -0
  196. data/lib/fontisan/svg/font_generator.rb +0 -4
  197. data/lib/fontisan/svg/glyph_generator.rb +0 -2
  198. data/lib/fontisan/svg.rb +12 -0
  199. data/lib/fontisan/tables/cbdt.rb +0 -1
  200. data/lib/fontisan/tables/cblc.rb +0 -1
  201. data/lib/fontisan/tables/cff/charset.rb +0 -1
  202. data/lib/fontisan/tables/cff/charstring.rb +0 -1
  203. data/lib/fontisan/tables/cff/charstring_rebuilder.rb +0 -4
  204. data/lib/fontisan/tables/cff/charstrings_index.rb +0 -3
  205. data/lib/fontisan/tables/cff/dict.rb +0 -1
  206. data/lib/fontisan/tables/cff/encoding.rb +0 -1
  207. data/lib/fontisan/tables/cff/header.rb +0 -2
  208. data/lib/fontisan/tables/cff/hint_operation_injector.rb +0 -2
  209. data/lib/fontisan/tables/cff/index.rb +0 -1
  210. data/lib/fontisan/tables/cff/private_dict.rb +0 -2
  211. data/lib/fontisan/tables/cff/private_dict_writer.rb +0 -2
  212. data/lib/fontisan/tables/cff/table_builder.rb +0 -6
  213. data/lib/fontisan/tables/cff/top_dict.rb +0 -2
  214. data/lib/fontisan/tables/cff.rb +22 -15
  215. data/lib/fontisan/tables/cff2/charstring_parser.rb +0 -2
  216. data/lib/fontisan/tables/cff2/table_builder.rb +0 -11
  217. data/lib/fontisan/tables/cff2/table_reader.rb +0 -2
  218. data/lib/fontisan/tables/cff2.rb +13 -14
  219. data/lib/fontisan/tables/cmap.rb +24 -2
  220. data/lib/fontisan/tables/cmap_table.rb +0 -3
  221. data/lib/fontisan/tables/colr.rb +0 -1
  222. data/lib/fontisan/tables/cpal.rb +0 -1
  223. data/lib/fontisan/tables/cvar.rb +0 -2
  224. data/lib/fontisan/tables/fvar.rb +0 -1
  225. data/lib/fontisan/tables/glyf/compound_glyph_resolver.rb +0 -2
  226. data/lib/fontisan/tables/glyf/glyph_builder.rb +0 -3
  227. data/lib/fontisan/tables/glyf.rb +0 -6
  228. data/lib/fontisan/tables/glyf_table.rb +0 -3
  229. data/lib/fontisan/tables/gpos.rb +0 -2
  230. data/lib/fontisan/tables/gsub.rb +0 -2
  231. data/lib/fontisan/tables/gvar.rb +0 -2
  232. data/lib/fontisan/tables/head.rb +0 -2
  233. data/lib/fontisan/tables/head_table.rb +0 -3
  234. data/lib/fontisan/tables/hhea.rb +0 -2
  235. data/lib/fontisan/tables/hhea_table.rb +0 -3
  236. data/lib/fontisan/tables/hmtx.rb +0 -2
  237. data/lib/fontisan/tables/hmtx_table.rb +0 -3
  238. data/lib/fontisan/tables/hvar.rb +0 -3
  239. data/lib/fontisan/tables/loca.rb +0 -2
  240. data/lib/fontisan/tables/loca_table.rb +0 -3
  241. data/lib/fontisan/tables/maxp.rb +0 -2
  242. data/lib/fontisan/tables/maxp_table.rb +0 -3
  243. data/lib/fontisan/tables/mvar.rb +0 -3
  244. data/lib/fontisan/tables/name.rb +0 -2
  245. data/lib/fontisan/tables/name_table.rb +0 -3
  246. data/lib/fontisan/tables/os2_table.rb +0 -3
  247. data/lib/fontisan/tables/post_table.rb +0 -3
  248. data/lib/fontisan/tables/sbix.rb +0 -1
  249. data/lib/fontisan/tables/svg.rb +0 -1
  250. data/lib/fontisan/tables/variation_common.rb +0 -1
  251. data/lib/fontisan/tables/vvar.rb +0 -3
  252. data/lib/fontisan/tables.rb +54 -0
  253. data/lib/fontisan/true_type_collection.rb +6 -14
  254. data/lib/fontisan/true_type_font.rb +8 -2
  255. data/lib/fontisan/true_type_font_extensions.rb +9 -9
  256. data/lib/fontisan/type1/afm_generator.rb +0 -4
  257. data/lib/fontisan/type1/conversion_options.rb +0 -2
  258. data/lib/fontisan/type1/encodings.rb +0 -2
  259. data/lib/fontisan/type1/generator.rb +0 -8
  260. data/lib/fontisan/type1/pfa_generator.rb +0 -3
  261. data/lib/fontisan/type1/pfb_generator.rb +0 -5
  262. data/lib/fontisan/type1/pfm_generator.rb +0 -4
  263. data/lib/fontisan/type1.rb +42 -69
  264. data/lib/fontisan/type1_font.rb +40 -11
  265. data/lib/fontisan/ucd/aggregator.rb +73 -0
  266. data/lib/fontisan/ucd/cache_manager.rb +111 -0
  267. data/lib/fontisan/ucd/config.rb +59 -0
  268. data/lib/fontisan/ucd/download_error.rb +9 -0
  269. data/lib/fontisan/ucd/downloader.rb +88 -0
  270. data/lib/fontisan/ucd/error.rb +8 -0
  271. data/lib/fontisan/ucd/index.rb +103 -0
  272. data/lib/fontisan/ucd/index_builder.rb +107 -0
  273. data/lib/fontisan/ucd/range_entry.rb +56 -0
  274. data/lib/fontisan/ucd/unknown_version_error.rb +9 -0
  275. data/lib/fontisan/ucd/version_resolver.rb +79 -0
  276. data/lib/fontisan/ucd.rb +23 -0
  277. data/lib/fontisan/utilities/checksum_calculator.rb +0 -1
  278. data/lib/fontisan/utilities.rb +10 -0
  279. data/lib/fontisan/utils.rb +10 -0
  280. data/lib/fontisan/validation/collection_validator.rb +0 -2
  281. data/lib/fontisan/validation.rb +9 -0
  282. data/lib/fontisan/validators/basic_validator.rb +0 -2
  283. data/lib/fontisan/validators/font_book_validator.rb +0 -2
  284. data/lib/fontisan/validators/opentype_validator.rb +0 -2
  285. data/lib/fontisan/validators/profile_loader.rb +0 -5
  286. data/lib/fontisan/validators/validator.rb +0 -2
  287. data/lib/fontisan/validators/web_font_validator.rb +0 -2
  288. data/lib/fontisan/validators.rb +14 -0
  289. data/lib/fontisan/variable/delta_applicator.rb +0 -4
  290. data/lib/fontisan/variable/instancer.rb +0 -3
  291. data/lib/fontisan/variable/static_font_builder.rb +0 -3
  292. data/lib/fontisan/variable.rb +16 -0
  293. data/lib/fontisan/variation/blend_applier.rb +0 -2
  294. data/lib/fontisan/variation/cache.rb +0 -2
  295. data/lib/fontisan/variation/converter.rb +0 -3
  296. data/lib/fontisan/variation/data_extractor.rb +0 -2
  297. data/lib/fontisan/variation/delta_applier.rb +0 -5
  298. data/lib/fontisan/variation/inspector.rb +0 -1
  299. data/lib/fontisan/variation/instance_generator.rb +0 -6
  300. data/lib/fontisan/variation/instance_writer.rb +0 -5
  301. data/lib/fontisan/variation/metrics_adjuster.rb +0 -4
  302. data/lib/fontisan/variation/optimizer.rb +0 -3
  303. data/lib/fontisan/variation/parallel_generator.rb +0 -3
  304. data/lib/fontisan/variation/subsetter.rb +0 -4
  305. data/lib/fontisan/variation/tuple_variation_header.rb +0 -2
  306. data/lib/fontisan/variation/variable_svg_generator.rb +0 -3
  307. data/lib/fontisan/variation/variation_context.rb +0 -3
  308. data/lib/fontisan/variation/variation_preserver.rb +0 -3
  309. data/lib/fontisan/variation.rb +31 -0
  310. data/lib/fontisan/version.rb +1 -1
  311. data/lib/fontisan/woff2.rb +13 -0
  312. data/lib/fontisan/woff2_font.rb +31 -9
  313. data/lib/fontisan/woff_font.rb +31 -2
  314. data/lib/fontisan.rb +124 -196
  315. metadata +114 -7
  316. data/fontisan.gemspec +0 -48
@@ -40,13 +40,22 @@ Generating options control how the output font is written.
40
40
  | `optimize_tables` | Boolean | Enable table optimization | Reduce file size |
41
41
  | `reencode_first_256` | Boolean | Reencode first 256 glyphs | Type 1 output |
42
42
  | `encoding_vector` | String | Custom encoding vector | Type 1 output |
43
- | `compression` | String | Compression: zlib, brotli, none | Web font output |
44
- | `transform_tables` | Boolean | Transform tables for output | Format-specific |
43
+ | `zlib_level` | Integer (0–9) | zlib compression level for WOFF | WOFF output (default 6) |
44
+ | `uncompressed` | Boolean | Store WOFF tables uncompressed (legal per WOFF 1.0 §5.1) | Tooling pipelines |
45
+ | `compression_threshold` | Integer | Skip compression for tables smaller than N bytes | Tiny-table edge cases |
46
+ | `brotli_quality` | Integer (0–11) | Brotli quality for WOFF2 | WOFF2 output (default 11) |
47
+ | `transform_tables` | Boolean | Apply WOFF2 glyf/loca + hmtx transformations | Smaller WOFF2 output |
48
+ | `metadata_xml` | String | Optional WOFF metadata XML block | WOFF metadata block |
49
+ | `private_data` | String | Optional WOFF private data block | WOFF private block |
45
50
  | `preserve_metadata` | Boolean | Preserve copyright/license metadata | Maintain metadata |
46
51
  | `strip_metadata` | Boolean | Remove metadata | Reduce file size |
47
52
  | `target_format` | String | Collection target format | Collection conversions |
48
53
  | `curve_tolerance` | Float | Curve approximation tolerance | OTF → TTF |
49
54
 
55
+ Compression knobs come from each strategy's `ConversionStrategy` DSL
56
+ declarations. Knobs that don't apply to the requested target format are
57
+ rejected by `FormatConverter.validate_options_for_target!`.
58
+
50
59
  ## CLI Option Mapping
51
60
 
52
61
  ### Opening Options
@@ -103,6 +112,16 @@ Generating options control how the output font is written.
103
112
 
104
113
  # Curves
105
114
  --curve-tolerance N # Approximation tolerance (0.1-2.0)
115
+
116
+ # WOFF only (zlib)
117
+ --zlib-level=N # 0–9, default 6
118
+ --uncompressed # Store tables uncompressed (legal per WOFF 1.0 §5.1)
119
+ --compression-threshold=N # Skip compression for tables < N bytes (default 100)
120
+
121
+ # WOFF2 only (Brotli)
122
+ --brotli-quality=N # 0–11, default 11
123
+ --transform-tables # Apply glyf/loca + hmtx transformations
124
+ --no-transform-tables # Disable transformations (default)
106
125
  ```
107
126
 
108
127
  ### Preset Option
@@ -194,10 +213,20 @@ Fonts optimized for web delivery.
194
213
  Fontisan::ConversionOptions.from_preset(:web_optimized)
195
214
  # From: :otf, To: :woff2
196
215
  # opening: {}
197
- # generating: { compression: "brotli", transform_tables: true,
216
+ # generating: { brotli_quality: 11, transform_tables: true,
198
217
  # optimize_tables: true, preserve_metadata: true }
199
218
  ```
200
219
 
220
+ ### legacy_web
221
+
222
+ WOFF with maximum zlib compression for legacy browser reach (IE 9+).
223
+
224
+ ```ruby
225
+ Fontisan::ConversionOptions.from_preset(:legacy_web)
226
+ # From: :otf, To: :woff
227
+ # generating: { zlib_level: 9, optimize_tables: true, preserve_metadata: true }
228
+ ```
229
+
201
230
  ### archive_to_modern
202
231
 
203
232
  Collection extraction and modernization.
@@ -157,7 +157,7 @@ Result: Some precision loss unavoidable.
157
157
  # Convert all TTF files in a directory
158
158
  Dir.glob('fonts/*.ttf').each do |input|
159
159
  output = input.sub('.ttf', '.otf')
160
- Fontisan.convert(input, output_format: :otf, output_path: output)
160
+ Fontisan.convert(input, to: :otf, output: output)
161
161
  end
162
162
  ```
163
163
 
@@ -203,6 +203,6 @@ fontisan convert font.pfb --to woff2 --output font.woff2 \
203
203
  ```ruby
204
204
  Dir.glob('legacy/*.pfb').each do |input|
205
205
  output = input.sub('.pfb', '.otf').sub('legacy/', 'modern/')
206
- Fontisan.convert(input, output_format: :otf, output_path: output)
206
+ Fontisan.convert(input, to: :otf, output: output)
207
207
  end
208
208
  ```
@@ -10,8 +10,13 @@ Fontisan supports conversion to web-optimized formats (WOFF and WOFF2) for optim
10
10
 
11
11
  | Format | Compression | Browser Support | Use Case |
12
12
  |--------|-------------|-----------------|----------|
13
- | WOFF | zlib | All modern browsers | Wide compatibility |
14
- | WOFF2 | brotli | Modern browsers | Smallest size |
13
+ | WOFF | zlib | All modern browsers (IE 9+) | Wide compatibility |
14
+ | WOFF2 | Brotli | Modern browsers | Smallest size |
15
+
16
+ The format you pick **is** the algorithm choice — WOFF 1.0 mandates zlib,
17
+ WOFF2 mandates Brotli. There is no separate `--compression` flag. Each
18
+ format exposes its algorithm's tunable parameters instead (level, quality,
19
+ threshold, transform).
15
20
 
16
21
  ## WOFF2
17
22
 
@@ -34,7 +39,7 @@ fontisan convert font.pfb --to woff2 --output font.woff2
34
39
  Fontisan::ConversionOptions.from_preset(:web_optimized)
35
40
  # From: :otf, To: :woff2
36
41
  # opening: {}
37
- # generating: { compression: "brotli", transform_tables: true,
42
+ # generating: { brotli_quality: 11, transform_tables: true,
38
43
  # optimize_tables: true, preserve_metadata: true }
39
44
  ```
40
45
 
@@ -61,13 +66,20 @@ fontisan convert font.ttf --to woff --output font.woff
61
66
  options = Fontisan::ConversionOptions.new(
62
67
  to: :woff,
63
68
  generating: {
64
- compression: "zlib",
65
- preserve_metadata: true,
66
- add_private_data: false
69
+ zlib_level: 9, # max zlib compression
70
+ preserve_metadata: true
67
71
  }
68
72
  )
69
73
  ```
70
74
 
75
+ For maximum legacy reach use the `:legacy_web` preset:
76
+
77
+ ```ruby
78
+ Fontisan::ConversionOptions.from_preset(:legacy_web)
79
+ # From: :otf, To: :woff
80
+ # generating: { zlib_level: 9, optimize_tables: true, preserve_metadata: true }
81
+ ```
82
+
71
83
  ## web_optimized Preset
72
84
 
73
85
  Optimize fonts for web delivery:
@@ -76,7 +88,7 @@ Optimize fonts for web delivery:
76
88
  Fontisan::ConversionOptions.from_preset(:web_optimized)
77
89
  # From: :otf, To: :woff2
78
90
  # opening: {}
79
- # generating: { compression: "brotli", transform_tables: true,
91
+ # generating: { brotli_quality: 11, transform_tables: true,
80
92
  # optimize_tables: true, preserve_metadata: true }
81
93
  ```
82
94
 
@@ -113,43 +125,72 @@ fontisan convert font.otf --to woff2 --output font.woff2
113
125
  # Via OTF intermediate
114
126
  options = Fontisan::ConversionOptions.new(
115
127
  opening: { decompose_composites: false, generate_unicode: true },
116
- generating: { compression: "brotli" } # WOFF2
117
- # OR: generating: { compression: "zlib" } # WOFF
128
+ generating: {
129
+ brotli_quality: 11 # WOFF2
130
+ # For WOFF instead, use: zlib_level: 9
131
+ }
118
132
  )
119
133
  ```
120
134
 
121
- ## Compression Options
135
+ ## Compression Knobs
136
+
137
+ Each web format exposes its algorithm's parameters. Knobs that don't apply
138
+ to the requested target are rejected up-front with a clear error.
122
139
 
123
- ### brotli (WOFF2)
140
+ ### WOFF (zlib)
141
+
142
+ | Option | CLI | Range | Default | Notes |
143
+ |--------|-----|-------|---------|-------|
144
+ | `zlib_level` | `--zlib-level=N` | 0–9 | 6 | 0 = no compression, 9 = smallest |
145
+ | `uncompressed` | `--uncompressed` | bool | false | Store tables uncompressed (legal per WOFF 1.0 §5.1; `compLength == origLength`) |
146
+ | `compression_threshold` | `--compression-threshold=N` | bytes | 100 | Skip compression for tables smaller than N |
124
147
 
125
148
  ```ruby
126
- generating: { compression: "brotli" }
149
+ # Max zlib compression
150
+ options = Fontisan::ConversionOptions.new(
151
+ to: :woff,
152
+ generating: { zlib_level: 9 }
153
+ )
154
+
155
+ # No compression (legal per spec; useful for tooling pipelines)
156
+ options = Fontisan::ConversionOptions.new(
157
+ to: :woff,
158
+ generating: { uncompressed: true }
159
+ )
127
160
  ```
128
161
 
129
- - Best compression ratio
130
- - Requires modern browsers
131
- - Transforms tables for better compression
162
+ ### WOFF2 (Brotli)
132
163
 
133
- ### zlib (WOFF)
164
+ | Option | CLI | Range | Default | Notes |
165
+ |--------|-----|-------|---------|-------|
166
+ | `brotli_quality` | `--brotli-quality=N` | 0–11 | 11 | 0 = fastest, 11 = smallest |
167
+ | `transform_tables` | `--[no-]transform-tables` | bool | false | Apply glyf/loca and hmtx transformations |
134
168
 
135
169
  ```ruby
136
- generating: { compression: "zlib" }
170
+ # Smallest possible WOFF2
171
+ options = Fontisan::ConversionOptions.new(
172
+ to: :woff2,
173
+ generating: { brotli_quality: 11, transform_tables: true }
174
+ )
137
175
  ```
138
176
 
139
- - Good compression
140
- - Wide browser support
141
- - No table transforms
177
+ ### Cross-format validation
142
178
 
143
- ### none
179
+ Passing a WOFF knob to a WOFF2 target (or vice versa) is rejected at
180
+ conversion time by `FormatConverter.validate_options_for_target!`:
144
181
 
145
182
  ```ruby
146
- generating: { compression: "none" }
183
+ # Rejected at convert time: brotli_quality does not apply to woff
184
+ Fontisan.convert('font.ttf', to: :woff, output: 'font.woff',
185
+ brotli_quality: 11)
186
+ # => Fontisan::Error: ... Option(s) :brotli_quality do not apply to --to woff.
187
+ # Accepted for woff: zlib_level, uncompressed, compression_threshold,
188
+ # metadata_xml, private_data
147
189
  ```
148
190
 
149
- - No compression
150
- - For debugging
191
+ The CLI equivalent exits 1 with the same message.
151
192
 
152
- ## Table Transforms
193
+ ## Table Transforms (WOFF2)
153
194
 
154
195
  WOFF2 supports table transformations for better compression:
155
196
 
@@ -166,6 +207,12 @@ options = Fontisan::ConversionOptions.new(
166
207
  )
167
208
  ```
168
209
 
210
+ CLI equivalent:
211
+
212
+ ```bash
213
+ fontisan convert font.ttf --to woff2 --output font.woff2 --transform-tables
214
+ ```
215
+
169
216
  ## Metadata Handling
170
217
 
171
218
  ### Preserve Metadata
@@ -198,20 +245,32 @@ font = Fontisan::FontLoader.load('source.ttf')
198
245
  woff2_options = Fontisan::ConversionOptions.from_preset(:web_optimized)
199
246
  Fontisan::FontWriter.write(font, 'font.woff2', options: woff2_options)
200
247
 
201
- # Create WOFF for older browsers
202
- woff_options = Fontisan::ConversionOptions.new(
203
- to: :woff,
204
- generating: { compression: "zlib", preserve_metadata: true }
205
- )
248
+ # Create WOFF for older browsers (max zlib)
249
+ woff_options = Fontisan::ConversionOptions.from_preset(:legacy_web)
206
250
  Fontisan::FontWriter.write(font, 'font.woff', options: woff_options)
207
251
  ```
208
252
 
253
+ ### Top-level Fontisan.convert
254
+
255
+ ```ruby
256
+ # WOFF2 with max Brotli
257
+ Fontisan.convert('font.ttf', to: :woff2, output: 'font.woff2',
258
+ brotli_quality: 11, transform_tables: true)
259
+
260
+ # WOFF with max zlib (for IE / older browsers)
261
+ Fontisan.convert('font.ttf', to: :woff, output: 'font.woff', zlib_level: 9)
262
+
263
+ # WOFF stored uncompressed (legal per WOFF 1.0 §5.1)
264
+ Fontisan.convert('font.ttf', to: :woff, output: 'font.woff', uncompressed: true)
265
+ ```
266
+
209
267
  ### Batch Web Conversion
210
268
 
211
269
  ```bash
212
- # Convert all TTF files to WOFF2
270
+ # Convert all TTF files to WOFF2 with max Brotli
213
271
  for f in fonts/*.ttf; do
214
- fontisan convert "$f" --to woff2 --output "web/$(basename "${f%.ttf}.woff2")"
272
+ fontisan convert "$f" --to woff2 --output "web/$(basename "${f%.ttf}.woff2")" \
273
+ --brotli-quality 11 --transform-tables
215
274
  done
216
275
  ```
217
276
 
@@ -18,10 +18,10 @@ Fontisan supports conversion between various font formats.
18
18
  require 'fontisan'
19
19
 
20
20
  # Convert TTF to WOFF2
21
- Fontisan.convert('font.ttf', output_format: :woff2)
21
+ Fontisan.convert('font.ttf', to: :woff2, output: 'font.woff2')
22
22
 
23
- # Convert with custom output path
24
- Fontisan.convert('font.ttf', output_path: 'output/font.woff2')
23
+ # Convert TTF to OTF
24
+ Fontisan.convert('font.ttf', to: :otf, output: 'font.otf')
25
25
  ```
26
26
 
27
27
  ## Batch Conversion
@@ -30,10 +30,11 @@ Fontisan.convert('font.ttf', output_path: 'output/font.woff2')
30
30
  # Convert multiple files
31
31
  fonts = Dir.glob('fonts/*.ttf')
32
32
  fonts.each do |font|
33
- Fontisan.convert(font, output_format: :otf)
33
+ out = font.sub(/\.ttf$/, '.otf')
34
+ Fontisan.convert(font, to: :otf, output: out)
34
35
  end
35
36
  ```
36
37
 
37
38
  ## Related
38
39
 
39
- - [WOFF/WOFF2 Formats](/guide/woff) - Details on web font formats
40
+ - [WOFF/WOFF2 Formats](/guide/conversion/web) - Details on web font formats
@@ -10,8 +10,13 @@ Web Open Font Format (WOFF and WOFF2) are optimized for web delivery.
10
10
 
11
11
  | Format | Compression | Browser Support |
12
12
  |--------|-------------|-----------------|
13
- | WOFF | zlib | All modern browsers |
14
- | WOFF2 | brotli | Modern browsers |
13
+ | WOFF | zlib | All modern browsers (IE 9+) |
14
+ | WOFF2 | Brotli | Modern browsers |
15
+
16
+ The format you pick **is** the algorithm choice — WOFF 1.0 mandates zlib,
17
+ WOFF2 mandates Brotli. Each format exposes its algorithm's parameters
18
+ (level, quality, transform) rather than offering a separate algorithm
19
+ selector.
15
20
 
16
21
  ## WOFF2 Benefits
17
22
 
@@ -27,6 +32,9 @@ fontisan convert font.ttf --to woff --output font.woff
27
32
 
28
33
  # From OTF
29
34
  fontisan convert font.otf --to woff --output font.woff
35
+
36
+ # Max zlib compression
37
+ fontisan convert font.ttf --to woff --output font.woff --zlib-level 9
30
38
  ```
31
39
 
32
40
  ## Converting to WOFF2
@@ -40,6 +48,10 @@ fontisan convert font.otf --to woff2 --output font.woff2
40
48
 
41
49
  # From Type 1
42
50
  fontisan convert font.pfb --to woff2 --output font.woff2
51
+
52
+ # Smallest possible output (max Brotli + table transforms)
53
+ fontisan convert font.ttf --to woff2 --output font.woff2 \
54
+ --brotli-quality 11 --transform-tables
43
55
  ```
44
56
 
45
57
  ## API Usage
@@ -47,28 +59,40 @@ fontisan convert font.pfb --to woff2 --output font.woff2
47
59
  ```ruby
48
60
  options = Fontisan::ConversionOptions.from_preset(:web_optimized)
49
61
  # From: :otf, To: :woff2
50
- # generating: { compression: "brotli", transform_tables: true }
62
+ # generating: { brotli_quality: 11, transform_tables: true,
63
+ # optimize_tables: true, preserve_metadata: true }
51
64
 
52
65
  Fontisan::FontWriter.write(font, 'font.woff2', options: options)
53
66
  ```
54
67
 
55
- ## Compression Options
68
+ ## Compression Knobs
69
+
70
+ ### WOFF (zlib)
56
71
 
57
- ### brotli (WOFF2)
72
+ | Option | CLI | Range | Default |
73
+ |--------|-----|-------|---------|
74
+ | `zlib_level` | `--zlib-level=N` | 0–9 | 6 |
75
+ | `uncompressed` | `--uncompressed` | bool | false |
76
+ | `compression_threshold` | `--compression-threshold=N` | bytes | 100 |
58
77
 
59
78
  ```ruby
60
- generating: { compression: "brotli" }
79
+ generating: { zlib_level: 9 } # max zlib
80
+ generating: { uncompressed: true } # legal per WOFF 1.0 §5.1
61
81
  ```
62
82
 
63
- Best compression ratio.
83
+ ### WOFF2 (Brotli)
64
84
 
65
- ### zlib (WOFF)
85
+ | Option | CLI | Range | Default |
86
+ |--------|-----|-------|---------|
87
+ | `brotli_quality` | `--brotli-quality=N` | 0–11 | 11 |
88
+ | `transform_tables` | `--[no-]transform-tables` | bool | false |
66
89
 
67
90
  ```ruby
68
- generating: { compression: "zlib" }
91
+ generating: { brotli_quality: 11, transform_tables: true }
69
92
  ```
70
93
 
71
- Wide compatibility.
94
+ Cross-format misuse (e.g. `brotli_quality` on `:woff`) raises
95
+ `ArgumentError` from `FormatConverter.validate_options_for_target!`.
72
96
 
73
97
  ## Table Transforms
74
98
 
@@ -110,6 +134,6 @@ fontisan convert font.woff2 --to otf --output font.otf
110
134
  ## Best Practices
111
135
 
112
136
  1. **Use WOFF2 primarily** — Best compression
113
- 2. **Provide WOFF fallback** — For older browsers
137
+ 2. **Provide WOFF fallback** — For older browsers (IE 9+)
114
138
  3. **Keep original** — For editing
115
139
  4. **Preserve metadata** — Unless stripping for size
data/docs/guide/index.md CHANGED
@@ -86,7 +86,7 @@ puts tables.keys
86
86
 
87
87
  ```ruby
88
88
  # Simple conversion
89
- Fontisan.convert('input.ttf', output_format: :woff2)
89
+ Fontisan.convert('input.ttf', to: :woff2, output: 'output.woff2')
90
90
 
91
91
  # With custom options
92
92
  options = Fontisan::ConversionOptions.new(
@@ -94,7 +94,7 @@ options = Fontisan::ConversionOptions.new(
94
94
  to: :otf,
95
95
  opening: { autohint: true, convert_curves: true }
96
96
  )
97
- Fontisan.convert('input.ttf', output_format: :otf, options: options)
97
+ Fontisan.convert('input.ttf', to: :otf, output: 'output.otf', options: options)
98
98
  ```
99
99
 
100
100
  ### Validate Fonts
@@ -246,7 +246,7 @@ fontisan maintains 100% backward compatibility:
246
246
 
247
247
  = ExtractTTC to Fontisan Migration Guide
248
248
 
249
- This guide helps users migrate from https://github.com/fontist/extract_ttc[ExtractTTC] to Fontisan.
249
+ This guide helps users migrate from [ExtractTTC](https://github.com/fontist/extract_ttc) to Fontisan.
250
250
 
251
251
  Fontisan provides complete compatibility with all ExtractTTC functionality while adding comprehensive font analysis, subsetting, validation, and format conversion capabilities.
252
252
 
@@ -70,13 +70,13 @@ fontisan info font.ttf --format json
70
70
 
71
71
  ```ruby
72
72
  # TTF to OTF
73
- Fontisan.convert('input.ttf', output_format: :otf)
73
+ Fontisan.convert('input.ttf', to: :otf, output: 'output.otf')
74
74
 
75
75
  # OTF to WOFF2
76
- Fontisan.convert('input.otf', output_format: :woff2)
76
+ Fontisan.convert('input.otf', to: :woff2, output: 'output.woff2')
77
77
 
78
78
  # Type 1 to OTF
79
- Fontisan.convert('input.pfb', output_format: :otf)
79
+ Fontisan.convert('input.pfb', to: :otf, output: 'output.otf')
80
80
  ```
81
81
 
82
82
  ### With Options
@@ -89,7 +89,7 @@ options = Fontisan::ConversionOptions.new(
89
89
  generating: { hinting_mode: 'auto' }
90
90
  )
91
91
 
92
- Fontisan.convert('input.ttf', output_format: :otf, options: options)
92
+ Fontisan.convert('input.ttf', to: :otf, output: 'output.otf', options: options)
93
93
  ```
94
94
 
95
95
  ### Using CLI
data/docs/guide/type1.md CHANGED
@@ -28,13 +28,13 @@ puts font.num_glyphs
28
28
 
29
29
  ```ruby
30
30
  # Convert to TrueType
31
- Fontisan.convert('font.pfb', output_format: :ttf)
31
+ Fontisan.convert('font.pfb', to: :ttf, output: 'font.ttf')
32
32
 
33
33
  # Convert to OpenType
34
- Fontisan.convert('font.pfb', output_format: :otf)
34
+ Fontisan.convert('font.pfb', to: :otf, output: 'font.otf')
35
35
 
36
36
  # Convert to WOFF2
37
- Fontisan.convert('font.pfb', output_format: :woff2)
37
+ Fontisan.convert('font.pfb', to: :woff2, output: 'font.woff2')
38
38
  ```
39
39
 
40
40
  ## Handling Encodings
@@ -55,4 +55,4 @@ end
55
55
 
56
56
  ## Related
57
57
 
58
- - [Font Conversion](/guide/conversion) - General conversion guide
58
+ - [Font Conversion](/guide/conversion/) - General conversion guide
data/docs/guide/woff.md CHANGED
@@ -9,41 +9,42 @@ WOFF and WOFF2 are web font formats optimized for use on websites:
9
9
  - **WOFF** - Compressed version of TrueType/OpenType with metadata support
10
10
  - **WOFF2** - Improved compression using Brotli algorithm (typically 30% smaller than WOFF)
11
11
 
12
+ The format you pick (`--to woff` vs `--to woff2`) **is** the algorithm
13
+ choice: WOFF mandates zlib, WOFF2 mandates Brotli. Each format exposes
14
+ its algorithm's parameters (level, quality, transform) rather than
15
+ offering a separate algorithm selector.
16
+
12
17
  ## Converting to WOFF
13
18
 
14
19
  ```ruby
15
20
  require 'fontisan'
16
21
 
17
- # Convert to WOFF
18
- Fontisan.convert('font.ttf', output_format: :woff)
22
+ # Convert to WOFF (max zlib for legacy browser reach)
23
+ Fontisan.convert('font.ttf', to: :woff, output: 'font.woff', zlib_level: 9)
19
24
 
20
- # Convert to WOFF2 (recommended for web)
21
- Fontisan.convert('font.ttf', output_format: :woff2)
25
+ # Convert to WOFF2 (recommended for modern browsers)
26
+ Fontisan.convert('font.ttf', to: :woff2, output: 'font.woff2',
27
+ brotli_quality: 11, transform_tables: true)
22
28
  ```
23
29
 
24
30
  ## Metadata
25
31
 
26
- WOFF files can contain extended metadata:
32
+ WOFF files can carry an extended metadata block:
27
33
 
28
34
  ```ruby
29
- # Add metadata when converting
35
+ # Add WOFF metadata when converting
30
36
  Fontisan.convert('font.ttf',
31
- output_format: :woff2,
32
- metadata: {
33
- unique_id: 'my-font-1.0',
34
- license: 'OFL',
35
- license_url: 'https://scripts.sil.org/OFL',
36
- description: 'My custom font'
37
- }
38
- )
37
+ to: :woff,
38
+ output: 'font.woff',
39
+ metadata_xml: '<metadata>...</metadata>')
39
40
  ```
40
41
 
41
42
  ## Extracting from WOFF
42
43
 
43
44
  ```ruby
44
45
  # Extract original font from WOFF
45
- Fontisan.convert('font.woff', output_format: :ttf)
46
- Fontisan.convert('font.woff2', output_format: :otf)
46
+ Fontisan.convert('font.woff', to: :ttf, output: 'font.ttf')
47
+ Fontisan.convert('font.woff2', to: :otf, output: 'font.otf')
47
48
  ```
48
49
 
49
50
  ## Compression Comparison
@@ -56,4 +57,5 @@ Fontisan.convert('font.woff2', output_format: :otf)
56
57
 
57
58
  ## Related
58
59
 
59
- - [Font Conversion](/guide/conversion) - General conversion guide
60
+ - [Web Font Formats](/guide/conversion/web) - Detailed WOFF/WOFF2 guide with all compression knobs
61
+ - [Font Conversion](/guide/conversion/) - General conversion guide
data/docs/index.md CHANGED
@@ -18,6 +18,8 @@ hero:
18
18
  link: /guide/migrations/fonttools
19
19
 
20
20
  features:
21
+ - title: "🔍 Font Audit"
22
+ details: Comprehensive per-face audit (replaces otfinfo) — identity, style, metrics, coverage, licensing, hinting, color, variation, OpenType layout, UCD/CLDR aggregation, compare, and library-summary modes.
21
23
  - title: "🔄 Font Conversion"
22
24
  details: Convert between TTF, OTF, WOFF, WOFF2, Type 1 (PFB/PFA), and SVG formats with curve conversion and optimization.
23
25
  - title: "✅ Font Validation"
data/docs/lychee.toml CHANGED
@@ -23,7 +23,11 @@ include_verbatim = true
23
23
  # Exclude URLs (regex patterns)
24
24
  exclude = [
25
25
  'github\.com/fontist/fontisan/issues',
26
- 'github\.com/fontist/extract_ttc',
26
+ 'github\.com/extract_ttc',
27
+ # Exclude internal /fontisan/ links — these are deployment base paths that
28
+ # resolve correctly when deployed but fail lychee's local file:// checks
29
+ # (the built HTML has /fontisan/<path>; the file lives at dist/<path>).
30
+ '^file://.*/fontisan/',
27
31
  ]
28
32
 
29
33
  # Exclude paths from checking (regex patterns)
data/docs/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "scripts": {
3
3
  "dev": "vitepress dev",
4
- "build": "vitepress build",
4
+ "build": "vitepress build && node scripts/post-build.mjs",
5
5
  "preview": "vitepress preview",
6
6
  "format": "prettier -w ."
7
7
  },
@@ -0,0 +1,4 @@
1
+ User-agent: *
2
+ Allow: /
3
+
4
+ Sitemap: https://www.fontist.org/fontisan/sitemap.xml
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ // Post-build fixes for GitHub Pages subpath deployment.
3
+ //
4
+ // 1. Dirify: convert VitePress file-style output (foo.html) to directory-style
5
+ // (foo/index.html) so GitHub Pages resolves BOTH /foo and /foo/. Without
6
+ // this, /foo/ 404s because foo.html is a file, not a directory.
7
+ //
8
+ // 2. Sitemap base: insert the deployment base path into sitemap.xml URLs.
9
+ // VitePress omits `base` from sitemap route paths (and ignores the path
10
+ // portion of `sitemap.hostname`), so the generated URLs point at the origin
11
+ // root instead of the subsite. We rewrite each <loc> to include the base.
12
+ //
13
+ // Idempotent. Kept at the dist root: index.html (site root), 404.html (GHP 404 page).
14
+ import {
15
+ existsSync,
16
+ mkdirSync,
17
+ readdirSync,
18
+ readFileSync,
19
+ renameSync,
20
+ statSync,
21
+ writeFileSync,
22
+ } from "node:fs";
23
+ import { join } from "node:path";
24
+
25
+ const DIST = ".vitepress/dist";
26
+ const HTML = ".html";
27
+ const KEEP_AT_ROOT = new Set(["index.html", "404.html"]);
28
+ // Subsite identity — must match config.ts base and the real deployment URL.
29
+ const ORIGIN = "https://www.fontist.org";
30
+ const BASE = "fontisan"; // path segment under ORIGIN (no slashes)
31
+
32
+ let moved = 0;
33
+ let skipped = 0;
34
+
35
+ function dirify(dir) {
36
+ for (const entry of readdirSync(dir)) {
37
+ const full = join(dir, entry);
38
+ if (statSync(full).isDirectory()) {
39
+ dirify(full);
40
+ continue;
41
+ }
42
+ if (!entry.endsWith(HTML)) continue;
43
+ if (entry === "index.html") continue; // already directory-style
44
+ if (dir === DIST && KEEP_AT_ROOT.has(entry)) {
45
+ skipped++; // root index.html / 404.html stay put
46
+ continue;
47
+ }
48
+ const name = entry.slice(0, -HTML.length);
49
+ const targetDir = join(dir, name);
50
+ const targetIndex = join(targetDir, "index.html");
51
+ if (existsSync(targetIndex)) {
52
+ console.warn(`[post-build] skip ${full.replace(DIST + "/", "")}: target exists`);
53
+ skipped++;
54
+ continue;
55
+ }
56
+ mkdirSync(targetDir, { recursive: true });
57
+ renameSync(full, targetIndex);
58
+ moved++;
59
+ }
60
+ }
61
+
62
+ function fixSitemapBase() {
63
+ const sitemap = `${DIST}/sitemap.xml`;
64
+ if (!existsSync(sitemap)) return false;
65
+ let xml = readFileSync(sitemap, "utf8");
66
+ // Insert BASE after the origin unless the path already starts with BASE
67
+ // (VitePress emits the homepage route as "/<base>"; other routes omit it).
68
+ const originPattern = new RegExp(
69
+ `(<loc>${ORIGIN.replaceAll("/", "\\/")}\\/)(?!${BASE}(\\/|$))`,
70
+ "g",
71
+ );
72
+ xml = xml.replace(originPattern, `$1${BASE}/`);
73
+ writeFileSync(sitemap, xml);
74
+ return true;
75
+ }
76
+
77
+ dirify(DIST);
78
+ const sitemapFixed = fixSitemapBase();
79
+ console.log(
80
+ `[post-build] dirified ${moved} route(s), kept ${skipped} at root; sitemap ${sitemapFixed ? "rewritten with /" + BASE + "/" : "absent (skipped)"}`,
81
+ );