ucode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. checksums.yaml +7 -0
  2. data/CLAUDE.md +211 -0
  3. data/Gemfile +22 -0
  4. data/Gemfile.lock +406 -0
  5. data/README.md +469 -0
  6. data/Rakefile +18 -0
  7. data/TODO.new/00-README.md +66 -0
  8. data/TODO.new/01-pillar-terminology-alignment.md +69 -0
  9. data/TODO.new/02-audit-schema-design.md +255 -0
  10. data/TODO.new/03-directory-output-spec.md +203 -0
  11. data/TODO.new/04-fontist-org-contract.md +173 -0
  12. data/TODO.new/05-baseline-unicode17-coverage-audit.md +144 -0
  13. data/TODO.new/06-audit-namespace-skeleton.md +105 -0
  14. data/TODO.new/07-audit-models-port.md +132 -0
  15. data/TODO.new/08-extractors-cheap-port.md +113 -0
  16. data/TODO.new/09-extractors-expensive-port.md +99 -0
  17. data/TODO.new/10-aggregations-ucd-rewrite.md +168 -0
  18. data/TODO.new/11-differ-and-library-auditor-port.md +102 -0
  19. data/TODO.new/12-formatters-port.md +115 -0
  20. data/TODO.new/13-directory-emitter.md +147 -0
  21. data/TODO.new/14-html-face-browser.md +144 -0
  22. data/TODO.new/15-html-library-browser.md +102 -0
  23. data/TODO.new/16-cli-audit-subcommands.md +142 -0
  24. data/TODO.new/17-fontisan-cleanup-audit.md +147 -0
  25. data/TODO.new/18-fontisan-cleanup-ucd.md +156 -0
  26. data/TODO.new/19-fontisan-docs-update.md +155 -0
  27. data/TODO.new/20-canonical-resolver-4-tier.md +182 -0
  28. data/TODO.new/21-canonical-unicode17-build.md +148 -0
  29. data/TODO.new/22-implementation-order.md +176 -0
  30. data/UCODE_CHANGELOG.md +97 -0
  31. data/exe/ucode +8 -0
  32. data/lib/ucode/aggregator.rb +77 -0
  33. data/lib/ucode/audit/block_aggregator.rb +90 -0
  34. data/lib/ucode/audit/codepoint_range_coalescer.rb +42 -0
  35. data/lib/ucode/audit/context.rb +137 -0
  36. data/lib/ucode/audit/discrepancy_detector.rb +213 -0
  37. data/lib/ucode/audit/extractors/aggregations.rb +70 -0
  38. data/lib/ucode/audit/extractors/base.rb +21 -0
  39. data/lib/ucode/audit/extractors/color_capabilities.rb +143 -0
  40. data/lib/ucode/audit/extractors/coverage.rb +55 -0
  41. data/lib/ucode/audit/extractors/hinting.rb +199 -0
  42. data/lib/ucode/audit/extractors/identity.rb +65 -0
  43. data/lib/ucode/audit/extractors/licensing.rb +75 -0
  44. data/lib/ucode/audit/extractors/metrics.rb +108 -0
  45. data/lib/ucode/audit/extractors/opentype_layout.rb +71 -0
  46. data/lib/ucode/audit/extractors/provenance.rb +34 -0
  47. data/lib/ucode/audit/extractors/style.rb +88 -0
  48. data/lib/ucode/audit/extractors/variation_detail.rb +101 -0
  49. data/lib/ucode/audit/extractors.rb +31 -0
  50. data/lib/ucode/audit/plane_aggregator.rb +37 -0
  51. data/lib/ucode/audit/registry.rb +63 -0
  52. data/lib/ucode/audit/script_aggregator.rb +92 -0
  53. data/lib/ucode/audit.rb +27 -0
  54. data/lib/ucode/cache.rb +113 -0
  55. data/lib/ucode/cli.rb +272 -0
  56. data/lib/ucode/commands/build.rb +68 -0
  57. data/lib/ucode/commands/cache.rb +46 -0
  58. data/lib/ucode/commands/fetch.rb +62 -0
  59. data/lib/ucode/commands/font_coverage.rb +57 -0
  60. data/lib/ucode/commands/glyphs.rb +136 -0
  61. data/lib/ucode/commands/lookup.rb +65 -0
  62. data/lib/ucode/commands/parse.rb +62 -0
  63. data/lib/ucode/commands/site.rb +33 -0
  64. data/lib/ucode/commands.rb +19 -0
  65. data/lib/ucode/config.rb +110 -0
  66. data/lib/ucode/coordinator/indices.rb +34 -0
  67. data/lib/ucode/coordinator.rb +397 -0
  68. data/lib/ucode/database.rb +214 -0
  69. data/lib/ucode/db_builder.rb +107 -0
  70. data/lib/ucode/error.rb +96 -0
  71. data/lib/ucode/fetch/code_charts.rb +57 -0
  72. data/lib/ucode/fetch/http.rb +83 -0
  73. data/lib/ucode/fetch/ucd_zip.rb +57 -0
  74. data/lib/ucode/fetch/unihan_zip.rb +57 -0
  75. data/lib/ucode/fetch.rb +14 -0
  76. data/lib/ucode/glyphs/cell_extractor.rb +130 -0
  77. data/lib/ucode/glyphs/dvisvgm_renderer.rb +29 -0
  78. data/lib/ucode/glyphs/embedded_fonts/catalog.rb +372 -0
  79. data/lib/ucode/glyphs/embedded_fonts/content_stream_correlator.rb +228 -0
  80. data/lib/ucode/glyphs/embedded_fonts/font_entry.rb +126 -0
  81. data/lib/ucode/glyphs/embedded_fonts/renderer.rb +47 -0
  82. data/lib/ucode/glyphs/embedded_fonts/source.rb +94 -0
  83. data/lib/ucode/glyphs/embedded_fonts/svg.rb +123 -0
  84. data/lib/ucode/glyphs/embedded_fonts/tounicode.rb +103 -0
  85. data/lib/ucode/glyphs/embedded_fonts/writer.rb +76 -0
  86. data/lib/ucode/glyphs/embedded_fonts.rb +50 -0
  87. data/lib/ucode/glyphs/grid.rb +30 -0
  88. data/lib/ucode/glyphs/grid_detector.rb +165 -0
  89. data/lib/ucode/glyphs/last_resort/cmap_index.rb +96 -0
  90. data/lib/ucode/glyphs/last_resort/contents.rb +74 -0
  91. data/lib/ucode/glyphs/last_resort/glif.rb +124 -0
  92. data/lib/ucode/glyphs/last_resort/renderer.rb +67 -0
  93. data/lib/ucode/glyphs/last_resort/source.rb +125 -0
  94. data/lib/ucode/glyphs/last_resort/svg.rb +247 -0
  95. data/lib/ucode/glyphs/last_resort/writer.rb +83 -0
  96. data/lib/ucode/glyphs/last_resort.rb +36 -0
  97. data/lib/ucode/glyphs/monolith_page_map.rb +181 -0
  98. data/lib/ucode/glyphs/mutool_renderer.rb +28 -0
  99. data/lib/ucode/glyphs/page_renderer.rb +221 -0
  100. data/lib/ucode/glyphs/path_bbox.rb +62 -0
  101. data/lib/ucode/glyphs/pdf2svg_renderer.rb +26 -0
  102. data/lib/ucode/glyphs/pdf_fetcher.rb +102 -0
  103. data/lib/ucode/glyphs/pdftocairo_renderer.rb +32 -0
  104. data/lib/ucode/glyphs/real_fonts/block_coverage.rb +45 -0
  105. data/lib/ucode/glyphs/real_fonts/coverage_auditor.rb +117 -0
  106. data/lib/ucode/glyphs/real_fonts/font_coverage_report.rb +45 -0
  107. data/lib/ucode/glyphs/real_fonts/font_locator.rb +95 -0
  108. data/lib/ucode/glyphs/real_fonts/unicode_17_blocks.rb +104 -0
  109. data/lib/ucode/glyphs/real_fonts/writer.rb +50 -0
  110. data/lib/ucode/glyphs/real_fonts.rb +32 -0
  111. data/lib/ucode/glyphs/writer.rb +250 -0
  112. data/lib/ucode/glyphs.rb +27 -0
  113. data/lib/ucode/index.rb +106 -0
  114. data/lib/ucode/index_builder.rb +94 -0
  115. data/lib/ucode/models/audit/audit_axis.rb +30 -0
  116. data/lib/ucode/models/audit/audit_diff.rb +77 -0
  117. data/lib/ucode/models/audit/audit_report.rb +137 -0
  118. data/lib/ucode/models/audit/baseline.rb +32 -0
  119. data/lib/ucode/models/audit/block_summary.rb +72 -0
  120. data/lib/ucode/models/audit/codepoint_detail.rb +45 -0
  121. data/lib/ucode/models/audit/codepoint_range.rb +39 -0
  122. data/lib/ucode/models/audit/codepoint_set_diff.rb +34 -0
  123. data/lib/ucode/models/audit/color_capabilities.rb +91 -0
  124. data/lib/ucode/models/audit/discrepancy.rb +38 -0
  125. data/lib/ucode/models/audit/duplicate_group.rb +23 -0
  126. data/lib/ucode/models/audit/embedding_type.rb +81 -0
  127. data/lib/ucode/models/audit/field_change.rb +28 -0
  128. data/lib/ucode/models/audit/fs_selection_flags.rb +65 -0
  129. data/lib/ucode/models/audit/gasp_range.rb +63 -0
  130. data/lib/ucode/models/audit/hinting.rb +99 -0
  131. data/lib/ucode/models/audit/library_summary.rb +40 -0
  132. data/lib/ucode/models/audit/licensing.rb +48 -0
  133. data/lib/ucode/models/audit/metrics.rb +111 -0
  134. data/lib/ucode/models/audit/named_instance.rb +41 -0
  135. data/lib/ucode/models/audit/opentype_layout.rb +38 -0
  136. data/lib/ucode/models/audit/plane_summary.rb +31 -0
  137. data/lib/ucode/models/audit/script_coverage_row.rb +26 -0
  138. data/lib/ucode/models/audit/script_features.rb +28 -0
  139. data/lib/ucode/models/audit/script_summary.rb +54 -0
  140. data/lib/ucode/models/audit/variation_detail.rb +42 -0
  141. data/lib/ucode/models/audit.rb +50 -0
  142. data/lib/ucode/models/bidi_bracket_pair.rb +20 -0
  143. data/lib/ucode/models/bidi_mirroring.rb +19 -0
  144. data/lib/ucode/models/binary_property_assignment.rb +26 -0
  145. data/lib/ucode/models/block.rb +36 -0
  146. data/lib/ucode/models/case_folding_rule.rb +23 -0
  147. data/lib/ucode/models/cjk_radical.rb +23 -0
  148. data/lib/ucode/models/codepoint/bidi.rb +28 -0
  149. data/lib/ucode/models/codepoint/break_segmentation.rb +22 -0
  150. data/lib/ucode/models/codepoint/case_folding.rb +25 -0
  151. data/lib/ucode/models/codepoint/casing.rb +32 -0
  152. data/lib/ucode/models/codepoint/decomposition.rb +27 -0
  153. data/lib/ucode/models/codepoint/display.rb +24 -0
  154. data/lib/ucode/models/codepoint/emoji.rb +29 -0
  155. data/lib/ucode/models/codepoint/hangul.rb +20 -0
  156. data/lib/ucode/models/codepoint/identifier.rb +30 -0
  157. data/lib/ucode/models/codepoint/indic.rb +20 -0
  158. data/lib/ucode/models/codepoint/joining.rb +20 -0
  159. data/lib/ucode/models/codepoint/normalization.rb +35 -0
  160. data/lib/ucode/models/codepoint/numeric_value.rb +35 -0
  161. data/lib/ucode/models/codepoint.rb +122 -0
  162. data/lib/ucode/models/name_alias.rb +21 -0
  163. data/lib/ucode/models/named_sequence.rb +19 -0
  164. data/lib/ucode/models/names_list_entry.rb +38 -0
  165. data/lib/ucode/models/plane.rb +36 -0
  166. data/lib/ucode/models/property_alias.rb +24 -0
  167. data/lib/ucode/models/property_value_alias.rb +26 -0
  168. data/lib/ucode/models/relationship/compat_equiv.rb +18 -0
  169. data/lib/ucode/models/relationship/cross_reference.rb +17 -0
  170. data/lib/ucode/models/relationship/footnote.rb +24 -0
  171. data/lib/ucode/models/relationship/informal_alias.rb +18 -0
  172. data/lib/ucode/models/relationship/sample_sequence.rb +24 -0
  173. data/lib/ucode/models/relationship/variation_sequence.rb +19 -0
  174. data/lib/ucode/models/relationship.rb +57 -0
  175. data/lib/ucode/models/script.rb +41 -0
  176. data/lib/ucode/models/special_casing_rule.rb +28 -0
  177. data/lib/ucode/models/standardized_variant.rb +24 -0
  178. data/lib/ucode/models/unihan_entry.rb +23 -0
  179. data/lib/ucode/models.rb +47 -0
  180. data/lib/ucode/parsers/auxiliary.rb +26 -0
  181. data/lib/ucode/parsers/base.rb +137 -0
  182. data/lib/ucode/parsers/bidi_brackets.rb +41 -0
  183. data/lib/ucode/parsers/bidi_mirroring.rb +37 -0
  184. data/lib/ucode/parsers/blocks.rb +63 -0
  185. data/lib/ucode/parsers/case_folding.rb +53 -0
  186. data/lib/ucode/parsers/cjk_radicals.rb +102 -0
  187. data/lib/ucode/parsers/derived_age.rb +59 -0
  188. data/lib/ucode/parsers/derived_core_properties.rb +60 -0
  189. data/lib/ucode/parsers/extracted_properties.rb +74 -0
  190. data/lib/ucode/parsers/name_aliases.rb +44 -0
  191. data/lib/ucode/parsers/named_sequences.rb +51 -0
  192. data/lib/ucode/parsers/names_list.rb +250 -0
  193. data/lib/ucode/parsers/property_aliases.rb +41 -0
  194. data/lib/ucode/parsers/property_value_aliases.rb +46 -0
  195. data/lib/ucode/parsers/script_extensions.rb +64 -0
  196. data/lib/ucode/parsers/scripts.rb +60 -0
  197. data/lib/ucode/parsers/special_casing.rb +62 -0
  198. data/lib/ucode/parsers/standardized_variants.rb +56 -0
  199. data/lib/ucode/parsers/unicode_data/hangul_name.rb +73 -0
  200. data/lib/ucode/parsers/unicode_data.rb +268 -0
  201. data/lib/ucode/parsers/unihan.rb +125 -0
  202. data/lib/ucode/parsers.rb +35 -0
  203. data/lib/ucode/range_entry.rb +58 -0
  204. data/lib/ucode/repo/aggregate_writer.rb +364 -0
  205. data/lib/ucode/repo/atomic_writes.rb +48 -0
  206. data/lib/ucode/repo/codepoint_writer.rb +96 -0
  207. data/lib/ucode/repo/paths.rb +122 -0
  208. data/lib/ucode/repo.rb +22 -0
  209. data/lib/ucode/site/config_emitter.rb +124 -0
  210. data/lib/ucode/site/generator.rb +178 -0
  211. data/lib/ucode/site/search_index.rb +68 -0
  212. data/lib/ucode/site/template/.gitignore +4 -0
  213. data/lib/ucode/site/template/.vitepress/config.ts +8 -0
  214. data/lib/ucode/site/template/.vitepress/theme/index.js +20 -0
  215. data/lib/ucode/site/template/char/[codepoint].md +13 -0
  216. data/lib/ucode/site/template/components/BlockView.vue +57 -0
  217. data/lib/ucode/site/template/components/CharView.vue +85 -0
  218. data/lib/ucode/site/template/components/PlaneView.vue +56 -0
  219. data/lib/ucode/site/template/components/SearchView.vue +66 -0
  220. data/lib/ucode/site/template/index.md +25 -0
  221. data/lib/ucode/site/template/package.json +18 -0
  222. data/lib/ucode/site/template/search.md +9 -0
  223. data/lib/ucode/site.rb +13 -0
  224. data/lib/ucode/version.rb +5 -0
  225. data/lib/ucode/version_resolver.rb +76 -0
  226. data/lib/ucode.rb +74 -0
  227. data/ucode.gemspec +56 -0
  228. metadata +404 -0
@@ -0,0 +1,102 @@
1
+ # 15 — HTML library browser
2
+
3
+ ## Goal
4
+
5
+ Generate a library-level `index.html` that lists all audited fonts as
6
+ cards with summary stats, linking into each face's per-face
7
+ `index.html` (from TODO 14).
8
+
9
+ This is the "simple HTML browser" for browsing multiple audits
10
+ locally. Open one file, see all audited fonts, click into any one.
11
+
12
+ ## Files to create
13
+
14
+ - `lib/ucode/audit/browser/library_page.rb` — renders the library index.
15
+ - `lib/ucode/audit/browser/templates/library.html.erb`.
16
+ - `lib/ucode/audit/browser/templates/library.css` — inlined.
17
+ - `lib/ucode/audit/browser/templates/library.js` — inlined; minimal.
18
+ - `spec/ucode/audit/browser/library_page_spec.rb`.
19
+
20
+ ## When this runs
21
+
22
+ The library browser is generated at the parent level when:
23
+
24
+ - `ucode audit library <dir>` runs (audits a directory of fonts).
25
+ - `ucode audit browser --input output/font_audit` runs (regenerates
26
+ browser HTML from existing audits without re-auditing).
27
+
28
+ Both invocations should produce (or update) `output/font_audit/index.html`.
29
+
30
+ ## Template structure
31
+
32
+ ### Header
33
+
34
+ - Library summary stats: total fonts audited, total codepoints across
35
+ all fonts (with double-counting note), date range of audits, ucode
36
+ version.
37
+
38
+ ### Filter / sort controls
39
+
40
+ Lightweight, vanilla JS:
41
+
42
+ - Search box (filter by family name or postscript name).
43
+ - Sort dropdown: by name / by coverage % / by codepoint count / by
44
+ audit date.
45
+ - Filter by status: complete only / partial only / has discrepancies.
46
+
47
+ ### Card grid
48
+
49
+ One card per audited font. Each card shows:
50
+
51
+ - Font name (large).
52
+ - Foundry + version (small).
53
+ - Coverage bar (visual: % of baseline covered).
54
+ - Quick stats: `2,857 cps / 24 blocks / 5 scripts`.
55
+ - Status badges: COMPLETE blocks count, PARTIAL blocks count,
56
+ DISCREPANCIES (if any).
57
+ - "Open" link → `<label>/index.html`.
58
+
59
+ Clicking the card opens the face's per-face browser.
60
+
61
+ ### Optional: comparison view
62
+
63
+ Out of scope for v1. The card grid is the v1 deliverable; comparison
64
+ (a la `ucode audit compare` but visual) can come later as a separate
65
+ TODO.
66
+
67
+ ## Inline data
68
+
69
+ Same pattern as TODO 14: inline the library summary JSON into a
70
+ `<script type="application/json" id="library-overview">` block so the
71
+ page renders without any fetch. The summary is small (one entry per
72
+ font; even 1,000 fonts × 200 bytes ≈ 200KB).
73
+
74
+ ## JS behavior
75
+
76
+ ~100 lines vanilla JS:
77
+
78
+ - Read inlined overview.
79
+ - Render card grid.
80
+ - Wire search/sort/filter to re-render on input.
81
+ - Open card click → navigate to `<label>/index.html`.
82
+
83
+ No lazy fetching needed at the library level — all data is inlined.
84
+
85
+ ## Acceptance
86
+
87
+ - `Ucode::Audit::Browser::LibraryPage.new(reports:, output_root:).write`
88
+ produces `<output_root>/index.html`.
89
+ - The page lists all input reports as cards.
90
+ - Search/sort/filter work without page reload.
91
+ - Clicking a card navigates to that face's per-face page.
92
+ - Library page is fully self-contained (no external resources).
93
+ - Spec asserts the page contains each font's family name and the
94
+ total font count.
95
+ - Rubocop clean.
96
+
97
+ ## References
98
+
99
+ - Face browser: `TODO.new/14-html-face-browser.md`
100
+ - Emitter (library mode): `TODO.new/13-directory-emitter.md`
101
+ - CLI: `TODO.new/16-cli-audit-subcommands.md` (`ucode audit library`
102
+ auto-generates this; `ucode audit browser` regenerates)
@@ -0,0 +1,142 @@
1
+ # 16 — CLI audit subcommands
2
+
3
+ ## Goal
4
+
5
+ Wire the audit pipeline to `bin/ucode` as a unified `ucode audit`
6
+ namespace. Replace `ucode font-coverage` (the v0.1 name). The CLI is
7
+ a thin Thor front-end; real logic lives in `Ucode::Commands::Audit::*Command`.
8
+
9
+ ## Files to create
10
+
11
+ - `lib/ucode/commands/audit_font_command.rb`
12
+ - `lib/ucode/commands/audit_collection_command.rb`
13
+ - `lib/ucode/commands/audit_library_command.rb`
14
+ - `lib/ucode/commands/audit_compare_command.rb`
15
+ - `lib/ucode/commands/audit_browser_command.rb`
16
+ - Update `lib/ucode/cli.rb` to register the new `audit` namespace.
17
+
18
+ Plus specs for each command (in-process, no shell-out).
19
+
20
+ ## CLI surface
21
+
22
+ ### `ucode audit font PATH [options]`
23
+
24
+ Single face audit. PATH is a font file or a fontist-resolvable name
25
+ (`label=/path/to/font.ttf` for direct, bare name for fontist find).
26
+
27
+ Options:
28
+ - `--label LABEL` — output directory name (default: postscript_name or font file basename).
29
+ - `--unicode-version VERSION` — baseline version (default: ucode's default).
30
+ - `--verbose` — emit per-codepoint detail under `codepoints/`.
31
+ - `--with-glyphs` — emit per-codepoint SVG under `glyphs/` (renders from audited font).
32
+ - `--brief` — cheap-extractor-only mode.
33
+ - `--output DIR` — output root (default: `output/font_audit`).
34
+ - `--browse` — also generate the face HTML browser.
35
+ - `--format text|json|yaml` — stdout format when no `--output`.
36
+
37
+ Output: `<output>/<label>/` directory tree (per TODO 13).
38
+
39
+ ### `ucode audit collection PATH [options]`
40
+
41
+ TTC/OTC/dfong. Same options as `audit font` plus:
42
+
43
+ - `--font-index N` — audit only face N (output behaves like single face).
44
+
45
+ Output: `<output>/<source_label>/00-<face>/...` per TODO 13.
46
+
47
+ ### `ucode audit library DIR [options]`
48
+
49
+ Walk a directory of fonts (recursive optional).
50
+
51
+ Options:
52
+ - `--recursive` — walk into subdirectories.
53
+ - `--parallel N` — parallel face audits (default: ucode's default).
54
+ - All `audit font` options apply per-face.
55
+ - `--browse` — also generate the library HTML browser at `<output>/index.html`.
56
+
57
+ Output: per-face directories + library-level index.
58
+
59
+ ### `ucode audit compare LEFT RIGHT [options]`
60
+
61
+ Diff two audits. LEFT and RIGHT each may be:
62
+
63
+ - A path to a font file (audited on-the-fly).
64
+ - A path to a saved audit directory (reads its `index.json`).
65
+ - A path to a saved `index.json` directly.
66
+
67
+ Options:
68
+ - `--format text|json` — output format.
69
+ - `--output FILE` — write to file (default: stdout).
70
+
71
+ Output: `AuditDiff` rendered via TODO 12.
72
+
73
+ ### `ucode audit browser [options]`
74
+
75
+ Regenerate HTML browsers from existing JSON audits.
76
+
77
+ Options:
78
+ - `--input DIR` — audit root (default: `output/font_audit`).
79
+ - `--faces-only` — regenerate per-face `index.html` only.
80
+ - `--library-only` — regenerate library-level `index.html` only.
81
+
82
+ Output: HTML files only; no JSON rewrites.
83
+
84
+ ## Rename of `ucode font-coverage`
85
+
86
+ `ucode font-coverage` is removed (not aliased) — nothing external
87
+ depends on it yet. The current `RealFonts` subsystem that powers it
88
+ becomes the `Ucode::Audit::FontLocator` + `Ucode::Audit::CoverageAuditor`
89
+ internally; the CLI surface is the new `audit` namespace.
90
+
91
+ `output/font_coverage/` directory name is renamed to
92
+ `output/font_audit/` in the same PR. Anyone with stale output from
93
+ v0.1 can delete it manually.
94
+
95
+ ## Command class pattern
96
+
97
+ Each `Commands::Audit::*Command` is a pure Ruby class with a `#run`
98
+ method. No Thor knowledge inside. Thor (in `lib/ucode/cli.rb`) just
99
+ parses args and constructs the command. This pattern matches the
100
+ existing `Ucode::Commands::*Command` classes.
101
+
102
+ ```ruby
103
+ module Ucode
104
+ module Commands
105
+ class AuditFontCommand
106
+ def initialize(font_path, label:, unicode_version:, verbose:, with_glyphs:, brief:, output_root:, browse:)
107
+ # ...
108
+ end
109
+
110
+ def run
111
+ # Resolve font, build Context, run extractors, emit.
112
+ end
113
+ end
114
+ end
115
+ end
116
+ ```
117
+
118
+ ## Acceptance
119
+
120
+ - All 5 commands exist and are spec'd in-process.
121
+ - `ucode audit font <fixture.ttf>` produces the directory tree at
122
+ `output/font_audit/<label>/`.
123
+ - `ucode audit font <fixture.ttf> --browse` additionally produces
124
+ `index.html` that opens correctly.
125
+ - `ucode audit collection <fixture.ttc>` produces one tree per face.
126
+ - `ucode audit library spec/fixtures/fonts/` audits every fixture font
127
+ and produces a library-level index.
128
+ - `ucode audit compare` works with all three input forms (font,
129
+ directory, json file).
130
+ - `ucode audit browser` regenerates HTML without re-auditing.
131
+ - `ucode font-coverage` is gone (verified: `bin/ucode help` does not
132
+ list it).
133
+ - No `double()` in specs.
134
+ - Rubocop clean.
135
+
136
+ ## References
137
+
138
+ - Architecture: `docs/architecture.md` §"CLI"
139
+ - Existing CLI: `lib/ucode/cli.rb`, `lib/ucode/commands/`
140
+ - All upstream TODOs: 06-15
141
+ - Removed: `ucode font-coverage` (current implementation in
142
+ `lib/ucode/glyphs/real_fonts/` and the current CLI registration)
@@ -0,0 +1,147 @@
1
+ # 17 — Fontisan: delete audit subsystem
2
+
3
+ ## Goal
4
+
5
+ After ucode's audit (TODOs 06-16) produces byte-equivalent or richer
6
+ output, delete fontisan's audit subsystem. fontisan keeps only its
7
+ font-parsing primitives.
8
+
9
+ **Precondition:** ucode audit has shipped, been validated against
10
+ real-world fonts, and fontist.org has confirmed the new contract
11
+ works. Do NOT execute this TODO until those gates pass.
12
+
13
+ ## Files to delete in fontisan
14
+
15
+ ```
16
+ fontisan/lib/fontisan/audit.rb
17
+ fontisan/lib/fontisan/audit/ # entire directory
18
+ fontisan/lib/fontisan/commands/audit_command.rb
19
+ fontisan/lib/fontisan/commands/audit_compare_command.rb
20
+ fontisan/lib/fontisan/commands/audit_library_command.rb
21
+ fontisan/lib/fontisan/models/audit.rb
22
+ fontisan/lib/fontisan/models/audit/ # entire directory
23
+ fontisan/lib/fontisan/formatters/audit_text_renderer.rb
24
+ fontisan/lib/fontisan/formatters/audit_diff_text_renderer.rb
25
+ fontisan/lib/fontisan/formatters/library_summary_text_renderer.rb
26
+ fontisan/spec/commands/audit_command_spec.rb
27
+ fontisan/spec/commands/audit_compare_command_spec.rb
28
+ fontisan/spec/commands/audit_library_command_spec.rb
29
+ fontisan/spec/audit/ # entire directory
30
+ fontisan/spec/models/audit/ # entire directory
31
+ fontisan/spec/formatters/ # audit-related specs only
32
+ fontisan/PROPOSAL.font-audit.md
33
+ fontisan/TODO.audit/ # entire directory
34
+ ```
35
+
36
+ ## Files to edit in fontisan
37
+
38
+ ### `fontisan/lib/fontisan/commands.rb`
39
+
40
+ Remove the `autoload :AuditCommand` and similar lines.
41
+
42
+ ### `fontisan/lib/fontisan/models.rb`
43
+
44
+ Remove the `autoload :Audit` line.
45
+
46
+ ### `fontisan/lib/fontisan/cli.rb`
47
+
48
+ Remove the `audit`, `audit-compare`, `audit-library` Thor method
49
+ registrations. Remove the `audit` namespace.
50
+
51
+ ### `fontisan/exe/fontisan`
52
+
53
+ No direct changes — the CLI class dispatch handles it. Verify
54
+ `fontisan help` no longer shows audit commands.
55
+
56
+ ### `fontisan/lib/fontisan/formatters.rb`
57
+
58
+ Remove the audit-related autoloads.
59
+
60
+ ### `fontisan/README.adoc`
61
+
62
+ Remove the `fontisan audit` section. Replace with a pointer to
63
+ `ucode audit`:
64
+
65
+ ```adoc
66
+ == Auditing fonts
67
+
68
+ The font audit functionality (per-face coverage reports, library
69
+ summaries, audit diffs) has moved to the `ucode` gem.
70
+
71
+ ucode audit font path/to/font.ttf
72
+ ucode audit library path/to/fonts/
73
+
74
+ See https://github.com/fontist/ucode for the full audit documentation.
75
+ ```
76
+
77
+ ### `fontisan/CHANGELOG.md`
78
+
79
+ Add entry under the next release:
80
+
81
+ ```markdown
82
+ ### Breaking
83
+ - Removed `fontisan audit`, `fontisan audit-compare`, `fontisan audit-library`.
84
+ Migrated to the `ucode` gem. Run `ucode audit font <path>` instead.
85
+ - Removed `Fontisan::Audit::*`, `Fontisan::Models::Audit::*`,
86
+ `Fontisan::Commands::AuditCommand`, and audit formatters. Use
87
+ `Ucode::Audit::*` instead.
88
+ ```
89
+
90
+ ### `fontisan/fontisan.gemspec`
91
+
92
+ No changes — fontisan doesn't depend on ucode and shouldn't (ucode
93
+ depends on fontisan for parsing).
94
+
95
+ ## Deprecation window
96
+
97
+ Per `docs/FONTISAN_MIGRATION.md` convention: one release cycle of
98
+ deprecation warnings before removal.
99
+
100
+ - **Release N-1** (deprecation): the audit commands emit a one-line
101
+ warning on stderr: "fontisan audit is deprecated and will be removed
102
+ in fontisan X.Y; use `ucode audit` instead." Commands still work.
103
+ - **Release N** (removal, this TODO): the commands are gone. Anyone
104
+ still calling them gets a clear `NoMethodError` / Thor "unknown
105
+ command" with the migration pointer.
106
+
107
+ If the deprecation window has not yet been open in fontisan, do this
108
+ TODO in two commits: first add the deprecation warnings, then (after
109
+ a release cycle) delete the code.
110
+
111
+ ## Boundary preservation
112
+
113
+ What stays in fontisan:
114
+
115
+ - All font-parsing code (FontLoader, FormatDetector, sfnt_table,
116
+ glyf/CFF/name/cmap/OS-2/head/hhea/post/fpgm/prep/cvt/gasp/COLR/CPAL/
117
+ SVG/CBDT/CBLC/sbix/fvar/gvar/STAT/avar/GSUB/GPOS readers).
118
+ - The existing non-audit commands (info, scripts, features, unicode,
119
+ convert, subset).
120
+ - The existing non-audit formatters.
121
+
122
+ If ucode's audit extractors call any fontisan internal that becomes
123
+ unused after this TODO, leave it in fontisan anyway (it may have
124
+ non-audit consumers we don't know about). ucode's API surface into
125
+ fontisan is documented in `docs/FONTISAN_MIGRATION.md`.
126
+
127
+ ## Acceptance
128
+
129
+ - `bundle exec fontisan help` shows no `audit` commands.
130
+ - `bundle exec ruby -e "require 'fontisan'; Fontisan::Audit"` raises
131
+ NameError.
132
+ - `bundle exec ruby -e "require 'fontisan'; Fontisan::Models::Audit"`
133
+ raises NameError.
134
+ - `bundle exec rspec` in fontisan passes (no audit-spec failures
135
+ because the audit specs are also deleted).
136
+ - `bundle exec rubocop` in fontisan clean (no dangling autoloads).
137
+ - README + CHANGELOG updated.
138
+ - No regressions in fontisan's non-audit test suite.
139
+
140
+ ## References
141
+
142
+ - Pre-condition: TODOs 06-16 all merged and validated
143
+ - ucode audit API surface: `docs/FONTISAN_MIGRATION.md`
144
+ - Companion TODO: `TODO.new/18-fontisan-cleanup-ucd.md` (delete UCD
145
+ subsystem from fontisan; do this in the same PR or the immediate
146
+ next one)
147
+ - Companion TODO: `TODO.new/19-fontisan-docs-update.md`
@@ -0,0 +1,156 @@
1
+ # 18 — Fontisan: delete UCD subsystem
2
+
3
+ ## Goal
4
+
5
+ Once fontisan's audit has moved to ucode (TODO 17) AND no other
6
+ fontisan consumer uses fontisan's UCD code, delete fontisan's entire
7
+ UCD subsystem. ucode becomes the sole UCD implementation across the
8
+ fontist org.
9
+
10
+ **Preconditions:**
11
+ 1. TODOs 06-17 merged. fontisan no longer has any audit code.
12
+ 2. `grep -r "Fontisan::Ucd" --include="*.rb"` across all fontist-org
13
+ repos returns zero hits in production code. (Run this grep
14
+ immediately before executing this TODO; if non-zero, stop and
15
+ migrate the stragglers first.)
16
+
17
+ ## Files to delete in fontisan
18
+
19
+ ```
20
+ fontisan/lib/fontisan/ucd.rb
21
+ fontisan/lib/fontisan/ucd/ # entire directory
22
+ aggregator.rb
23
+ cache_manager.rb
24
+ download_error.rb
25
+ downloader.rb
26
+ version_resolver.rb
27
+ index_builder.rb
28
+ index.rb
29
+ database.rb
30
+ db_builder.rb
31
+ config/ucd.yml
32
+ fontisan/lib/fontisan/models/ucd.rb
33
+ fontisan/lib/fontisan/models/ucd/ # entire directory
34
+ ucd.rb
35
+ ucd_char.rb
36
+ fontisan/lib/fontisan/cli/ucd_cli.rb
37
+ fontisan/spec/ucd/ # entire directory
38
+ fontisan/spec/cli/ucd_cli_spec.rb
39
+ fontisan/spec/models/ucd/ # entire directory
40
+ ```
41
+
42
+ ## Files to edit in fontisan
43
+
44
+ ### `fontisan/lib/fontisan/models.rb`
45
+
46
+ Remove the `autoload :Ucd` line.
47
+
48
+ ### `fontisan/lib/fontisan/cli.rb`
49
+
50
+ Remove the `ucd` Thor subcommand registration.
51
+
52
+ ### `fontisan/exe/fontisan`
53
+
54
+ No direct changes — CLI dispatch handles it. Verify `fontisan help` no
55
+ longer shows `fontisan ucd`.
56
+
57
+ ### `fontisan/lib/fontisan.rb`
58
+
59
+ Remove any UCD autoloads or requires.
60
+
61
+ ### `fontisan/fontisan.gemspec`
62
+
63
+ Remove UCD-specific deps if any became unused (e.g. `nokogiri` if it
64
+ was only for UCDXML parsing — check `grep -r "nokogiri" fontisan/lib`
65
+ before removing).
66
+
67
+ ### `fontisan/README.adoc`
68
+
69
+ Remove the `fontisan ucd` section. Add a pointer to ucode:
70
+
71
+ ```adoc
72
+ == Unicode Character Database
73
+
74
+ fontisan no longer carries its own UCD database or downloader. Use
75
+ the `ucode` gem for UCD data, block/script lookups, and Unicode
76
+ version resolution.
77
+
78
+ ucode cache list
79
+ ucode lookup block U+0041
80
+
81
+ See https://github.com/fontist/ucode.
82
+ ```
83
+
84
+ ### `fontisan/CHANGELOG.md`
85
+
86
+ ```markdown
87
+ ### Breaking
88
+ - Removed `fontisan ucd` CLI subcommand. Use `ucode` for UCD data.
89
+ - Removed `Fontisan::Ucd::*` (CacheManager, Database, DbBuilder,
90
+ IndexBuilder, Index, Aggregator, Downloader, VersionResolver,
91
+ RangeEntry, Config, Errors).
92
+ - Removed `Fontisan::Models::Ucd::*` (Ucd, UcdChar).
93
+ - Removed automatic download of `ucd.all.flat.zip`.
94
+ ```
95
+
96
+ ## Deprecation window
97
+
98
+ This is the second migration tracked in `docs/FONTISAN_MIGRATION.md`.
99
+ The runbook there calls for a compat-shim release window (Phase B-D).
100
+ Verify the shim has shipped and consumers have migrated before
101
+ deleting.
102
+
103
+ The compat shim at `fontisan/lib/fontisan/ucd.rb` (if shipped per
104
+ `docs/guide/fontisan_migration.md`) is deleted in this TODO. The
105
+ shim's job was to bridge `Fontisan::Ucd::*` calls to `Ucode::*`; once
106
+ no callers remain, the shim is dead code.
107
+
108
+ ## Boundary preservation
109
+
110
+ What stays in fontisan:
111
+
112
+ - All font-parsing code (unchanged).
113
+ - All non-audit, non-UCD commands (info, scripts, features, unicode,
114
+ convert, subset).
115
+
116
+ Wait — `fontisan unicode` command. Does it depend on `Fontisan::Ucd`?
117
+ Check before deleting. If yes, migrate it to use `Ucode::*` (and add
118
+ `ucode` as a runtime dep of fontisan for this command only), OR move
119
+ the command to ucode entirely.
120
+
121
+ Pre-execution check: `grep -r "Fontisan::Ucd" fontisan/lib/fontisan/commands/`.
122
+ If non-zero, address each caller first.
123
+
124
+ ## Cache cleanup
125
+
126
+ Document for users that the old cache at
127
+ `~/.config/fontisan/unicode/` is now stale and can be deleted:
128
+
129
+ ```bash
130
+ rm -rf ~/.config/fontisan/unicode/
131
+ ```
132
+
133
+ Add this to the CHANGELOG. Do NOT delete it programmatically — that's
134
+ the user's disk and the user's call.
135
+
136
+ ## Acceptance
137
+
138
+ - `bundle exec fontisan help` shows no `ucd` command.
139
+ - `bundle exec ruby -e "require 'fontisan'; Fontisan::Ucd"` raises
140
+ NameError.
141
+ - `bundle exec ruby -e "require 'fontisan'; Fontisan::Models::Ucd"`
142
+ raises NameError.
143
+ - `find fontisan/lib -name "*.rb" -exec grep -l "Fontisan::Ucd" {} \;`
144
+ returns nothing.
145
+ - `bundle exec rspec` in fontisan passes (no UCD-spec failures because
146
+ UCD specs are also deleted).
147
+ - `bundle exec rubocop` in fontisan clean.
148
+ - README + CHANGELOG updated.
149
+
150
+ ## References
151
+
152
+ - Pre-condition: TODOs 06-17 merged
153
+ - Migration runbook: `docs/FONTISAN_MIGRATION.md`
154
+ - Migration guide: `docs/guide/fontisan_migration.md`
155
+ - Companion: `TODO.new/17-fontisan-cleanup-audit.md` (do this first)
156
+ - Companion: `TODO.new/19-fontisan-docs-update.md`
@@ -0,0 +1,155 @@
1
+ # 19 — Fontisan: docs and shim update
2
+
3
+ ## Goal
4
+
5
+ Update fontisan's docs to reflect the post-migration reality: ucode
6
+ owns the audit and UCD subsystems; fontisan is a pure font parser.
7
+ Also handle the compat-shim lifecycle (introduce, warn, remove).
8
+
9
+ ## Scope
10
+
11
+ Three doc surfaces in fontisan need updating:
12
+
13
+ 1. `fontisan/README.adoc` — the main readme. Remove audit + UCD
14
+ sections; add "What moved to ucode" section.
15
+ 2. `fontisan/CHANGELOG.md` — breaking-change entries under the next
16
+ release.
17
+ 3. `fontisan/docs/` — any audit/UCD guides. Either delete or rewrite
18
+ as migration guides.
19
+
20
+ Plus the compat-shim code (per `docs/FONTISAN_MIGRATION.md` Phase B-D)
21
+ introduced ahead of TODOs 17-18.
22
+
23
+ ## Compat shim lifecycle
24
+
25
+ Per `docs/FONTISAN_MIGRATION.md`:
26
+
27
+ ### Phase B (introduce shim, ahead of TODO 17-18)
28
+
29
+ Add `fontisan/lib/fontisan/ucd.rb` and per-class shim files. Each shim
30
+ delegates to `Ucode::*` and emits a deprecation warning on first
31
+ access.
32
+
33
+ Also add `fontisan/lib/fontisan/audit.rb` shim that delegates
34
+ `Fontisan::Audit::*` and `Fontisan::Models::Audit::*` to
35
+ `Ucode::Audit::*` and `Ucode::Models::Audit::*`.
36
+
37
+ Add `ucode` as a runtime dep of fontisan in `fontisan.gemspec`:
38
+
39
+ ```ruby
40
+ spec.add_dependency "ucode", "~> 0.2"
41
+ ```
42
+
43
+ This is the only release where fontisan depends on ucode. The dep
44
+ goes away when the shim is removed (TODO 17 + 18).
45
+
46
+ ### Phase C (one release cycle of warnings)
47
+
48
+ User-facing callers see warnings on stderr:
49
+
50
+ ```
51
+ fontisan: Fontisan::Ucd::CacheManager is deprecated; use Ucode::Cache.
52
+ Called from /path/to/caller.rb:42.
53
+ ```
54
+
55
+ Track via the deprecation-warning tracker (often just a GitHub issue
56
+ per quarter).
57
+
58
+ ### Phase D (TODOs 17 + 18 execute)
59
+
60
+ Delete the shim. Remove the `ucode` runtime dep. The next fontisan
61
+ release ships without audit or UCD code at all.
62
+
63
+ ## README rewrite outline
64
+
65
+ ```adoc
66
+ = Fontisan
67
+
68
+ Fontisan is a Ruby library for reading and manipulating font files
69
+ (TrueType, OpenType, CFF, WOFF, WOFF2, TrueType Collection, Type 1).
70
+
71
+ == What's here
72
+
73
+ - Font loading + format detection.
74
+ - Per-table readers: name, cmap, glyf, CFF, OS/2, head, hhea, post,
75
+ fpgm, prep, cvt, gasp, COLR, CPAL, SVG, CBDT, CBLC, sbix, fvar,
76
+ gvar, STAT, avar, GSUB, GPOS.
77
+ - Subsetter, converter, validator.
78
+
79
+ == What's not here (moved to ucode)
80
+
81
+ The following functionality has migrated to the `ucode` gem:
82
+
83
+ - *Font audit reports* (`fontisan audit`). Use `ucode audit font`.
84
+ - *Unicode Character Database* (`fontisan ucd`). Use `ucode` directly
85
+ for UCD data, block/script lookup, and Unicode version resolution.
86
+
87
+ See https://github.com/fontist/ucode for the audit + UCD docs.
88
+
89
+ == Installation
90
+ ...
91
+ ```
92
+
93
+ ## CHANGELOG entry
94
+
95
+ Under the release that executes TODOs 17 + 18:
96
+
97
+ ```markdown
98
+ ## [Unreleased]
99
+
100
+ ### Breaking — audit + UCD migrated to ucode
101
+
102
+ The following were removed from fontisan and are now provided by the
103
+ `ucode` gem (https://github.com/fontist/ucode):
104
+
105
+ - CLI: `fontisan audit`, `fontisan audit-compare`, `fontisan audit-library`,
106
+ `fontisan ucd`.
107
+ - Modules: `Fontisan::Audit::*`, `Fontisan::Models::Audit::*`,
108
+ `Fontisan::Ucd::*`, `Fontisan::Models::Ucd::*`.
109
+ - Formatters: `Fontisan::Formatters::AuditTextRenderer`,
110
+ `AuditDiffTextRenderer`, `LibrarySummaryTextRenderer`.
111
+
112
+ ### Migration
113
+
114
+ Replace:
115
+
116
+ fontisan audit path/to/font.ttf
117
+
118
+ with:
119
+
120
+ ucode audit font path/to/font.ttf
121
+
122
+ Replace `Fontisan::Ucd::Database.open(version)` with
123
+ `Ucode::Database.open(version)`. Full mapping table in ucode's
124
+ `docs/guide/fontisan_migration.md`.
125
+
126
+ The last fontisan release with audit + UCD code was X.Y.Z. The
127
+ release before that (X.Y.Z-1) emitted deprecation warnings.
128
+ ```
129
+
130
+ ## Doc files to update in fontisan
131
+
132
+ Audit the existing `fontisan/docs/`:
133
+
134
+ - Any file mentioning `fontisan audit` → rewrite or delete.
135
+ - Any file mentioning `fontisan ucd` → rewrite or delete.
136
+ - Add `fontisan/docs/migrating_to_ucode.md` if a longer-form migration
137
+ guide is wanted (cross-link to ucode's
138
+ `docs/guide/fontisan_migration.md`).
139
+
140
+ ## Acceptance
141
+
142
+ - fontisan's README has no `audit` or `ucd` command sections.
143
+ - fontisan's CHANGELOG has clear breaking entries pointing at ucode.
144
+ - fontisan's `docs/` has no stale audit/UCD references.
145
+ - The compat shim (if introduced in Phase B) is removed in Phase D.
146
+ - `fontisan.gemspec` does not list `ucode` as a runtime dep after
147
+ Phase D.
148
+ - All fontisan specs pass; all fontisan rubocop checks pass.
149
+
150
+ ## References
151
+
152
+ - Companion: `TODO.new/17-fontisan-cleanup-audit.md`
153
+ - Companion: `TODO.new/18-fontisan-cleanup-ucd.md`
154
+ - Migration runbook: `docs/FONTISAN_MIGRATION.md`
155
+ - Migration guide: `docs/guide/fontisan_migration.md`