ucode 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/Gemfile.lock +2 -2
  4. data/TODO.full/00-README.md +116 -0
  5. data/TODO.full/01-panglyph-vision.md +112 -0
  6. data/TODO.full/02-panglyph-repo-bootstrap.md +184 -0
  7. data/TODO.full/03-panglyph-font-builder.md +201 -0
  8. data/TODO.full/04-panglyph-publish-pipeline.md +126 -0
  9. data/TODO.full/05-ucode-0-1-1-release.md +139 -0
  10. data/TODO.full/06-fontisan-remove-audit.md +142 -0
  11. data/TODO.full/07-fontisan-remove-ucd.md +125 -0
  12. data/TODO.full/08-archive-private-bin-build.md +143 -0
  13. data/TODO.full/09-archive-public-structure.md +164 -0
  14. data/TODO.full/10-fontist-org-woff-glyphs.md +131 -0
  15. data/TODO.full/11-fontist-org-audit-coverage.md +140 -0
  16. data/TODO.full/12-implementation-order.md +216 -0
  17. data/TODO.full/13-fontisan-font-writer-api.md +189 -0
  18. data/TODO.full/14-fontisan-table-writers.md +66 -0
  19. data/TODO.full/15-panglyph-builder-real.md +82 -0
  20. data/TODO.full/16-archive-public-sync-workflows.md +167 -0
  21. data/TODO.full/17-fontist-org-font-picker.md +73 -0
  22. data/TODO.full/18-comprehensive-spec-coverage.md +64 -0
  23. data/TODO.full/19-ucode-0-1-2-patch.md +32 -0
  24. data/TODO.full/20-fontisan-0-2-23-release.md +52 -0
  25. data/TODO.new/00-README.md +30 -0
  26. data/TODO.new/23-universal-glyph-set-source-map.md +312 -0
  27. data/TODO.new/24-universal-glyph-set-build.md +189 -0
  28. data/TODO.new/25-font-audit-against-universal-set.md +195 -0
  29. data/TODO.new/26-missing-glyph-reporter.md +189 -0
  30. data/TODO.new/27-fontist-org-consumer-integration.md +200 -0
  31. data/TODO.new/28-implementation-order-update.md +187 -0
  32. data/TODO.new/29-universal-set-curation-uc17.md +312 -0
  33. data/TODO.new/30-tier1-font-acquisition.md +241 -0
  34. data/TODO.new/31-universal-set-production-build.md +205 -0
  35. data/TODO.new/32-uc17-coverage-matrix.md +165 -0
  36. data/TODO.new/33-specialist-font-acquisition-refresh.md +138 -0
  37. data/TODO.new/34-pillar2-content-stream-correlator.md +147 -0
  38. data/TODO.new/35-universal-set-production-run.md +160 -0
  39. data/TODO.new/36-per-font-coverage-audit.md +145 -0
  40. data/TODO.new/37-coverage-highlight-reporter.md +125 -0
  41. data/TODO.new/38-fontist-org-glyph-consumer.md +141 -0
  42. data/TODO.new/39-implementation-order-update-32-38.md +258 -0
  43. data/TODO.new/40-archive-private-uses-ucode-audit.md +124 -0
  44. data/TODO.new/41-ucode-unicode-archive-bridge.md +160 -0
  45. data/config/specialist_fonts.yml +102 -0
  46. data/config/unicode17_tier1_fonts.yml +42 -0
  47. data/config/unicode17_universal_glyph_set.yml +293 -0
  48. data/lib/ucode/audit/block_aggregator.rb +57 -29
  49. data/lib/ucode/audit/browser/face_page.rb +128 -0
  50. data/lib/ucode/audit/browser/glyph_panel.rb +124 -0
  51. data/lib/ucode/audit/browser/library_page.rb +74 -0
  52. data/lib/ucode/audit/browser/missing_glyph_page.rb +87 -0
  53. data/lib/ucode/audit/browser/template.rb +47 -0
  54. data/lib/ucode/audit/browser/templates/face.css +200 -0
  55. data/lib/ucode/audit/browser/templates/face.html.erb +41 -0
  56. data/lib/ucode/audit/browser/templates/face.js +298 -0
  57. data/lib/ucode/audit/browser/templates/library.css +119 -0
  58. data/lib/ucode/audit/browser/templates/library.html.erb +42 -0
  59. data/lib/ucode/audit/browser/templates/library.js +99 -0
  60. data/lib/ucode/audit/browser/templates/missing_glyph_page.css +119 -0
  61. data/lib/ucode/audit/browser/templates/missing_glyph_page.html.erb +58 -0
  62. data/lib/ucode/audit/browser/templates/missing_glyph_page.js +2 -0
  63. data/lib/ucode/audit/browser.rb +32 -0
  64. data/lib/ucode/audit/context.rb +27 -1
  65. data/lib/ucode/audit/coverage_reference.rb +103 -0
  66. data/lib/ucode/audit/differ.rb +121 -0
  67. data/lib/ucode/audit/emitter/block_emitter.rb +52 -0
  68. data/lib/ucode/audit/emitter/codepoint_emitter.rb +87 -0
  69. data/lib/ucode/audit/emitter/collection_emitter.rb +80 -0
  70. data/lib/ucode/audit/emitter/face_directory.rb +212 -0
  71. data/lib/ucode/audit/emitter/glyph_emitter.rb +48 -0
  72. data/lib/ucode/audit/emitter/index_emitter.rb +149 -0
  73. data/lib/ucode/audit/emitter/library_emitter.rb +96 -0
  74. data/lib/ucode/audit/emitter/paths.rb +312 -0
  75. data/lib/ucode/audit/emitter/plane_emitter.rb +29 -0
  76. data/lib/ucode/audit/emitter/script_emitter.rb +29 -0
  77. data/lib/ucode/audit/emitter.rb +29 -0
  78. data/lib/ucode/audit/extractors/aggregations.rb +31 -2
  79. data/lib/ucode/audit/face_auditor.rb +86 -0
  80. data/lib/ucode/audit/formatters/audit_diff_text.rb +112 -0
  81. data/lib/ucode/audit/formatters/audit_text.rb +411 -0
  82. data/lib/ucode/audit/formatters/color.rb +48 -0
  83. data/lib/ucode/audit/formatters/library_summary_text.rb +98 -0
  84. data/lib/ucode/audit/formatters/text_formatter.rb +83 -0
  85. data/lib/ucode/audit/formatters.rb +23 -0
  86. data/lib/ucode/audit/library_aggregator.rb +86 -0
  87. data/lib/ucode/audit/library_auditor.rb +105 -0
  88. data/lib/ucode/audit/release/emitter.rb +152 -0
  89. data/lib/ucode/audit/release/face_card.rb +93 -0
  90. data/lib/ucode/audit/release/formula_audits.rb +50 -0
  91. data/lib/ucode/audit/release/library_index_builder.rb +78 -0
  92. data/lib/ucode/audit/release/manifest_builder.rb +127 -0
  93. data/lib/ucode/audit/release.rb +42 -0
  94. data/lib/ucode/audit/ucd_only_reference.rb +81 -0
  95. data/lib/ucode/audit/universal_set_reference.rb +136 -0
  96. data/lib/ucode/audit.rb +31 -0
  97. data/lib/ucode/cli.rb +339 -33
  98. data/lib/ucode/commands/audit/browser_command.rb +82 -0
  99. data/lib/ucode/commands/audit/collection_command.rb +103 -0
  100. data/lib/ucode/commands/audit/compare_command.rb +188 -0
  101. data/lib/ucode/commands/audit/font_command.rb +140 -0
  102. data/lib/ucode/commands/audit/library_command.rb +87 -0
  103. data/lib/ucode/commands/audit/reference_builder.rb +64 -0
  104. data/lib/ucode/commands/audit.rb +20 -0
  105. data/lib/ucode/commands/block_feed.rb +73 -0
  106. data/lib/ucode/commands/canonical_build.rb +138 -0
  107. data/lib/ucode/commands/fetch.rb +37 -1
  108. data/lib/ucode/commands/release.rb +115 -0
  109. data/lib/ucode/commands/universal_set.rb +211 -0
  110. data/lib/ucode/commands.rb +5 -0
  111. data/lib/ucode/coordinator/indices.rb +11 -0
  112. data/lib/ucode/coordinator.rb +138 -5
  113. data/lib/ucode/error.rb +30 -2
  114. data/lib/ucode/fetch/font_fetcher/result.rb +39 -0
  115. data/lib/ucode/fetch/font_fetcher.rb +16 -0
  116. data/lib/ucode/fetch/specialist_font_fetcher.rb +280 -0
  117. data/lib/ucode/fetch.rb +7 -3
  118. data/lib/ucode/glyphs/real_fonts/cmap_cache.rb +74 -0
  119. data/lib/ucode/glyphs/real_fonts.rb +1 -0
  120. data/lib/ucode/glyphs/resolver.rb +62 -0
  121. data/lib/ucode/glyphs/source.rb +48 -0
  122. data/lib/ucode/glyphs/source_builder.rb +61 -0
  123. data/lib/ucode/glyphs/source_config/coverage_assertion.rb +79 -0
  124. data/lib/ucode/glyphs/source_config/gap_report.rb +54 -0
  125. data/lib/ucode/glyphs/source_config.rb +104 -0
  126. data/lib/ucode/glyphs/sources/pillar1_embedded_tounicode.rb +63 -0
  127. data/lib/ucode/glyphs/sources/pillar3_last_resort.rb +51 -0
  128. data/lib/ucode/glyphs/sources/tier1_real_font.rb +104 -0
  129. data/lib/ucode/glyphs/sources.rb +20 -0
  130. data/lib/ucode/glyphs/universal_set/builder.rb +161 -0
  131. data/lib/ucode/glyphs/universal_set/coverage_report.rb +139 -0
  132. data/lib/ucode/glyphs/universal_set/idempotency.rb +86 -0
  133. data/lib/ucode/glyphs/universal_set/manifest_accumulator.rb +195 -0
  134. data/lib/ucode/glyphs/universal_set/manifest_writer.rb +61 -0
  135. data/lib/ucode/glyphs/universal_set/pre_build_check.rb +197 -0
  136. data/lib/ucode/glyphs/universal_set/validator.rb +204 -0
  137. data/lib/ucode/glyphs/universal_set.rb +45 -0
  138. data/lib/ucode/glyphs.rb +6 -0
  139. data/lib/ucode/models/audit/baseline.rb +6 -0
  140. data/lib/ucode/models/audit/block_summary.rb +7 -0
  141. data/lib/ucode/models/audit/codepoint_provenance.rb +39 -0
  142. data/lib/ucode/models/audit/release_face.rb +42 -0
  143. data/lib/ucode/models/audit/release_formula.rb +33 -0
  144. data/lib/ucode/models/audit/release_manifest.rb +43 -0
  145. data/lib/ucode/models/audit/release_universal_set.rb +37 -0
  146. data/lib/ucode/models/audit.rb +9 -0
  147. data/lib/ucode/models/block.rb +2 -0
  148. data/lib/ucode/models/build_report.rb +109 -0
  149. data/lib/ucode/models/codepoint/glyph.rb +42 -0
  150. data/lib/ucode/models/codepoint.rb +3 -0
  151. data/lib/ucode/models/glyph_source.rb +86 -0
  152. data/lib/ucode/models/glyph_source_map.rb +138 -0
  153. data/lib/ucode/models/specialist_font.rb +70 -0
  154. data/lib/ucode/models/specialist_font_manifest.rb +48 -0
  155. data/lib/ucode/models/unihan_entry.rb +81 -9
  156. data/lib/ucode/models/unihan_field.rb +21 -0
  157. data/lib/ucode/models/universal_set_entry.rb +47 -0
  158. data/lib/ucode/models/universal_set_manifest.rb +78 -0
  159. data/lib/ucode/models/validation_report.rb +99 -0
  160. data/lib/ucode/models.rb +9 -0
  161. data/lib/ucode/parsers/named_sequences.rb +5 -5
  162. data/lib/ucode/parsers/unihan.rb +50 -19
  163. data/lib/ucode/repo/aggregate_writer.rb +34 -2
  164. data/lib/ucode/repo/block_feed_emitter.rb +153 -0
  165. data/lib/ucode/repo/build_report_accumulator.rb +138 -0
  166. data/lib/ucode/repo/build_report_writer.rb +46 -0
  167. data/lib/ucode/repo/build_validator.rb +229 -0
  168. data/lib/ucode/repo/codepoint_writer.rb +50 -1
  169. data/lib/ucode/repo/paths.rb +8 -0
  170. data/lib/ucode/repo.rb +4 -0
  171. data/lib/ucode/version.rb +1 -1
  172. data/schema/block-feed.output.schema.yml +134 -0
  173. metadata +143 -2
  174. data/ucode.gemspec +0 -56
@@ -0,0 +1,125 @@
1
+ # 07 — fontisan: remove UCD/UCDXML subsystem
2
+
3
+ ## Goal
4
+
5
+ Strip the UCD/UCDXML parsing subsystem out of `fontisan`. ucode now
6
+ owns UCD parsing (`ucode parse`, `ucode fetch ucd`); fontisan's
7
+ `lib/fontisan/ucd/`, `lib/fontisan/models/ucd/`, and `config/ucd.yml`
8
+ are dead code that misleads consumers about who's the UCD authority.
9
+
10
+ Pairs with TODO 06 (audit removal). Both land together as the 0.3.0
11
+ release of fontisan.
12
+
13
+ ## Why now
14
+
15
+ - ucode's UCD parse is the canonical path. It's faster, more complete
16
+ (parses NamesList, Unihan, all auxiliary + extracted files), and
17
+ ships its own UCD cache.
18
+ - fontisan's UCD subsystem depended on `ucd.all.flat.xml` — which was
19
+ removed from the Unicode distribution in favor of UAX#44 text files.
20
+ The "real-shape-parsing" branch was an attempt to fix this; ucode
21
+ superseded it.
22
+ - Leaving UCD in fontisan creates two sources of truth for "what does
23
+ U+XXXX mean" — exactly the anti-pattern the audit migration fought.
24
+
25
+ ## Scope
26
+
27
+ ### Code
28
+
29
+ ```
30
+ lib/fontisan/ucd.rb # DELETE
31
+ lib/fontisan/ucd/ # DELETE entire directory
32
+ index_builder.rb
33
+ xml_parser.rb
34
+ text_file_parser.rb
35
+ ...
36
+ lib/fontisan/models/ucd.rb # DELETE
37
+ lib/fontisan/models/ucd/ # DELETE entire directory
38
+ block.rb
39
+ script.rb
40
+ codepoint.rb
41
+ ...
42
+ config/ucd.yml # DELETE (was auto-downloaded UCD config)
43
+ lib/fontisan/commands/ucdxml_command.rb # DELETE (if exists)
44
+ ```
45
+
46
+ ### Tests
47
+
48
+ ```
49
+ spec/fontisan/ucd/ # DELETE
50
+ spec/fontisan/models/ucd/ # DELETE
51
+ spec/fontisan/commands/ucdxml_command_spec.rb # DELETE (if exists)
52
+ spec/fixtures/ucd/ # DELETE (test fixtures)
53
+ spec/fixtures/ucd.all.flat.xml # DELETE
54
+ spec/fixtures/ucd.all.flat.zip # DELETE
55
+ ```
56
+
57
+ ### CLI registration
58
+
59
+ `lib/fontisan/cli.rb` — remove any UCD-related subcommands
60
+ (`ucdxml` if present).
61
+
62
+ ### Documentation
63
+
64
+ - `README.md` — remove UCD references
65
+ - `docs/ucd.md` (if exists) — delete
66
+
67
+ ## Branch context
68
+
69
+ The current `fix/ucdxml-real-shape-parsing` branch in fontisan was an
70
+ attempt to revive the UCD-XML parser. That work is now superseded by
71
+ ucode's UAX#44 text-file parsing (more reliable, doesn't depend on the
72
+ removed `.flat.xml` artifact).
73
+
74
+ This TODO **abandons** `fix/ucdxml-real-shape-parsing` and removes the
75
+ subsystem entirely. The branch's exploration work is preserved in git
76
+ history but not merged.
77
+
78
+ ## Migration path
79
+
80
+ Consumers currently calling `Fontisan::UCD::IndexBuilder` etc. should
81
+ switch to ucode's API:
82
+
83
+ ```ruby
84
+ # Before (fontisan 0.2.x):
85
+ index = Fontisan::UCD::IndexBuilder.new(version: "17.0.0").build
86
+ index.block_for_cp(0x4E00) # => "CJK_Unified_Ideographs"
87
+
88
+ # After (ucode 0.1.1+):
89
+ Ucode::Coordinator.new.indices_for(
90
+ ucd_dir: "/path/to/ucd",
91
+ unihan_dir: "/path/to/unihan"
92
+ ).blocks # => sorted array of Block records
93
+ ```
94
+
95
+ Document this in the CHANGELOG entry (combined with TODO 06's entry).
96
+
97
+ ## Version bump
98
+
99
+ Same release as TODO 06: **0.3.0** (breaking change).
100
+
101
+ ## Acceptance
102
+
103
+ - [ ] `lib/fontisan/ucd.rb` and `lib/fontisan/ucd/` deleted
104
+ - [ ] `lib/fontisan/models/ucd.rb` and `lib/fontisan/models/ucd/` deleted
105
+ - [ ] `config/ucd.yml` deleted
106
+ - [ ] Any `ucdxml` CLI subcommand removed
107
+ - [ ] All UCD-related specs + fixtures deleted
108
+ - [ ] `bundle exec rspec` passes
109
+ - [ ] `bundle exec rubocop` clean
110
+ - [ ] CHANGELOG entry documents the removal + migration path
111
+ - [ ] `fix/ucdxml-real-shape-parsing` branch marked as abandoned (closed
112
+ with "superseded by ucode" comment)
113
+
114
+ ## Dependencies / blockers
115
+
116
+ - **TODO.new 10** — Aggregations-UCD rewrite (must be complete; verified
117
+ by ucode's specs passing with real Unicode 17 data).
118
+ - **TODO 06** — companion audit removal; both ship in 0.3.0.
119
+
120
+ ## References
121
+
122
+ - `lib/fontisan/ucd.rb` (slated for deletion)
123
+ - [TODO.new/10](../TODO.new/10-aggregations-ucd-rewrite.md) — ucode UCD aggregations
124
+ - [TODO 06](06-fontisan-remove-audit.md) — companion cleanup
125
+ - `fix/ucdxml-real-shape-parsing` branch (to be abandoned)
@@ -0,0 +1,143 @@
1
+ # 08 — fontist-archive-private bin/build uses ucode audit + fontisan convert
2
+
3
+ ## Goal
4
+
5
+ Refactor `fontist-archive-private/bin/build` so it:
6
+
7
+ 1. Calls `ucode audit font` (instead of the dead `Fontisan::Commands::AuditCommand`)
8
+ for every matched font face.
9
+ 2. Calls `fontisan ConvertCommand` (unchanged) for WOFF specimen
10
+ generation (open-license only).
11
+ 3. Drops the UCD-stub hack (lines 13-21) — ucode has its own UCD.
12
+
13
+ Pairs with TODO.new/40 (same refactor). This file is the production
14
+ checklist + acceptance criteria.
15
+
16
+ ## Why now
17
+
18
+ - TODO 06 + TODO 07 land together, removing fontisan's audit + UCD.
19
+ - bin/build currently `require "fontisan"` and references
20
+ `Fontisan::Commands::AuditCommand` — will break loudly after 0.3.0.
21
+ - The UCD stub hack returns empty aggregations — every audit YAML
22
+ currently has `blocks: []`, `unicode_scripts: []`. Consumers see
23
+ per-font cmap but no per-block fill ratios.
24
+
25
+ ## Scope
26
+
27
+ ### Phase A — Swap audit invocation
28
+
29
+ 1. **Replace the audit call** (lines ~100-115 of `bin/build`):
30
+
31
+ ```ruby
32
+ # OLD (broken — uses removed AuditCommand + UCD stub hack)
33
+ cmd = Fontisan::Commands::AuditCommand.new(
34
+ face_path, font_index: font_index, no_codepoints: false
35
+ )
36
+ report = cmd.run
37
+ report = report.is_a?(Array) ? report.first : report
38
+ File.write(audit_path, report.to_yaml)
39
+ ```
40
+
41
+ with:
42
+
43
+ ```ruby
44
+ # NEW — shell out to ucode (decouples bin/build from ucode's internal API)
45
+ args = ["ucode", "audit", "font", face_path,
46
+ "--font-index", font_index.to_s,
47
+ "--output", audit_path]
48
+ args += ["--reference-universal-set", universal_set_path] if universal_set_path
49
+ success = system(*args,
50
+ out: verbose ? $stdout : File::NULL,
51
+ err: verbose ? $stderr : File::NULL)
52
+ warn "WARN audit #{slug}: ucode exited #{$?.exitstatus}" if verbose && !success
53
+ ```
54
+
55
+ 2. **Keep the WOFF call** (lines ~125-140) — `fontisan ConvertCommand`
56
+ stays as-is.
57
+
58
+ 3. **Remove the UCD stub hack** (lines 13-21 of `bin/build`):
59
+
60
+ ```ruby
61
+ # DELETE THIS:
62
+ module Fontisan
63
+ module Audit
64
+ class Context
65
+ def ucd
66
+ @ucd ||= { version: nil, blocks_index: nil, scripts_index: nil,
67
+ warning: "ucd_skipped" }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ ```
73
+
74
+ 4. **Update Gemfile**:
75
+
76
+ ```ruby
77
+ # fontist-archive-private/Gemfile
78
+ source "https://rubygems.org"
79
+ gem "ucode", "~> 0.1" # NEW (audit tool)
80
+ gem "fontisan", "~> 0.3" # BUMPED (audit + UCD removed)
81
+ gem "excavate" # unchanged (archive extraction)
82
+ gem "rake" # dev
83
+ ```
84
+
85
+ ### Phase B — Universal-set reference (after TODO.new 35)
86
+
87
+ 5. Once the universal glyph set is published (TODO.new 35 + TODO.new 41),
88
+ pass `--reference-universal-set=<path>` so audits include per-block
89
+ coverage comparison against the canonical glyphs.
90
+
91
+ 6. The universal set lives in `fontist-archive-public/unicode/universal-glyph-set/`.
92
+ bin/build checks it out shallow before invoking audits:
93
+
94
+ ```ruby
95
+ # Pseudocode — runs once per CI workflow, not per formula
96
+ universal_set_path = sync_universal_set_from_public_archive
97
+ ```
98
+
99
+ ### Phase C — CI workflow updates
100
+
101
+ 7. `.github/workflows/build.yml`:
102
+ - Bump fontisan gem to 0.3.0+
103
+ - Add ucode gem (0.1.1+) install step
104
+ - Add a pre-step that fetches the universal set from
105
+ `fontist-archive-public/unicode/universal-glyph-set/`
106
+ - Otherwise matrix unchanged (ubuntu for google/sil/manual, macOS
107
+ for macos)
108
+
109
+ ### Phase D — Test fixtures + acceptance
110
+
111
+ 8. Add a fixture-driven test: small formula YAML + small TTF → run
112
+ bin/build locally → assert audit YAML has:
113
+ - Populated `blocks:` array (not empty)
114
+ - Populated `unicode_scripts:` array (not empty)
115
+ - `ucode_version:` field (not `fontisan_version:` for audit producer)
116
+ - `coverage:` section when universal-set reference is passed
117
+
118
+ ## Acceptance
119
+
120
+ - [ ] `bin/build` invokes `ucode audit font` (not Fontisan)
121
+ - [ ] UCD stub hack removed
122
+ - [ ] Audit YAMLs include populated `blocks:` and `unicode_scripts:`
123
+ - [ ] Audit YAML carries `ucode_version` field (replaces `fontisan_version`)
124
+ - [ ] WOFF conversion still works (`Fontisan::ConvertCommand`)
125
+ - [ ] Gemfile lists `ucode` and `fontisan` (0.3.0+) as runtime deps
126
+ - [ ] At least one formula end-to-end produces a complete audit YAML
127
+ - [ ] GHA workflow runs to completion on a small formula subset
128
+
129
+ ## Dependencies / blockers
130
+
131
+ - **TODO 05** — ucode 0.1.1 published (bin/build will `gem install ucode`)
132
+ - **TODO 06** + **TODO 07** — fontisan 0.3.0 published (bin/build can't
133
+ require both old and new fontisan)
134
+ - **TODO.new 35** + **TODO.new 41** — universal-set production + archive
135
+ bridge (Phase B depends on these)
136
+
137
+ ## References
138
+
139
+ - `fontist-archive-private/bin/build` — current implementation
140
+ - [TODO 06](06-fontisan-remove-audit.md) — fontisan audit removal
141
+ - [TODO 07](07-fontisan-remove-ucd.md) — fontisan UCD removal
142
+ - [TODO.new 40](../TODO.new/40-archive-private-uses-ucode-audit.md) — earlier sketch
143
+ - `fontist.org/coverage-architecture.md` — updated architecture doc
@@ -0,0 +1,164 @@
1
+ # 09 — fontist-archive-public structure: coverage/ + woff/ + unicode/ + panglyph/
2
+
3
+ ## Goal
4
+
5
+ Define the canonical directory structure of `fontist-archive-public`
6
+ once all data streams are wired. Today it has `coverage/` (fontisan
7
+ audit YAMLs) + `woff/` (open-license specimens) + `fonts.json`. We
8
+ need to add `unicode/` (ucode's Unicode data) and `panglyph/` (the
9
+ universal font).
10
+
11
+ ## Why a separate TODO
12
+
13
+ Three independent pipelines now feed fontist-archive-public:
14
+
15
+ 1. **fontist-archive-private CI** (per-formula) → `coverage/` + `woff/`
16
+ 2. **ucode CI** (per-Unicode-version) → `unicode/block-feed/` +
17
+ `unicode/universal-glyph-set/` + `unicode/codepoints-{version}.tar.zst`
18
+ 3. **panglyph CI** (per-release) → `panglyph/v{X.Y.Z}/` + `manifest.json`
19
+
20
+ Each is owned by a different repo and syncs on a different cadence.
21
+ This TODO defines how they coexist in one archive without colliding.
22
+
23
+ ## Target structure
24
+
25
+ ```
26
+ fontist-archive-public/
27
+ ├── README.md # canonical index
28
+
29
+ ├── coverage/ # ← from fontist-archive-private CI
30
+ │ └── {formula_slug}/{PSName}.yaml # per-face audit YAMLs
31
+ │ google/abeezee/ABeeZee-Regular.yaml
32
+ │ manual/inter/Inter-Bold.yaml
33
+ │ macos/...
34
+
35
+ ├── woff/ # ← from fontist-archive-private CI (open-license only)
36
+ │ └── {formula_slug}/{PSName}.woff2
37
+ │ google/abeezee/ABeeZee-Regular.woff2
38
+ │ (NO macos/ — proprietary)
39
+
40
+ ├── fonts.json # font registry (canonical name → formula slugs)
41
+ ├── font-metadata.json # per-face metadata (weight, style, etc.)
42
+
43
+ ├── unicode/ # ← from ucode CI (TODO.new 41)
44
+ │ ├── block-feed/ # compact per-block Unicode data feed
45
+ │ │ ├── unicode-blocks.json
46
+ │ │ ├── unicode-version.json
47
+ │ │ └── unicode/blocks/<slug>.json
48
+ │ │
49
+ │ ├── universal-glyph-set/ # one SVG per codepoint (TODO.new 35)
50
+ │ │ ├── manifest.json # version, counts, generatedAt
51
+ │ │ ├── entries/U+XXXX.json # per-glyph provenance
52
+ │ │ └── glyphs/U+XXXX.svg
53
+ │ │
54
+ │ ├── codepoints-{version}.tar.zst # per-codepoint detailed JSONs (TODO.new 41 §Phase A.3)
55
+ │ └── codepoints-index.json # quick lookup: cp_int → {block, name, ...}
56
+
57
+ ├── panglyph/ # ← from panglyph CI (TODO 04)
58
+ │ ├── manifest.json # latest version + version index
59
+ │ └── v17.0.0/ # per-release directory
60
+ │ ├── panglyph-unicode17.ttf
61
+ │ ├── panglyph-unicode17.woff2
62
+ │ ├── panglyph-unicode17.otf
63
+ │ ├── coverage-report.json
64
+ │ └── source-manifest.json # OFL provenance per source font
65
+
66
+ ├── bin/
67
+ │ ├── sync-from-private # existing: pulls coverage/ + woff/
68
+ │ ├── sync-from-ucode # NEW: pulls unicode/
69
+ │ └── sync-from-panglyph # NEW: pulls panglyph/
70
+
71
+ └── .github/workflows/
72
+ ├── sync-private.yml # triggers on archive-private push
73
+ ├── sync-ucode.yml # triggers on ucode publish workflow
74
+ └── sync-panglyph.yml # triggers on panglyph tag
75
+ ```
76
+
77
+ ## Three sync workflows
78
+
79
+ ### sync-private.yml (existing — minor update)
80
+
81
+ Triggers on push to `fontist/fontist-archive-private` main.
82
+ - Clones private shallow
83
+ - Copies `coverage/` (ALL audit YAML — metadata is public)
84
+ - Copies `woff/` (open-license only — checks each formula's license)
85
+ - Updates `fonts.json` + `font-metadata.json`
86
+ - Commits + pushes to public
87
+
88
+ ### sync-ucode.yml (NEW — TODO.new 41)
89
+
90
+ Triggers on workflow_run of `fontist/ucode`'s `publish-unicode-archive.yml`.
91
+ - Clones ucode's published artifacts (Release asset OR direct git push
92
+ from ucode CI — design decision in TODO.new 41)
93
+ - Syncs `unicode/block-feed/`, `unicode/universal-glyph-set/`
94
+ - Replaces `unicode/codepoints-{version}.tar.zst`
95
+ - Updates `unicode/codepoints-index.json` (regenerated from per-cp JSONs)
96
+
97
+ ### sync-panglyph.yml (NEW — TODO 04)
98
+
99
+ Triggers on tag push to `fontist/panglyph`.
100
+ - Clones panglyph Release assets
101
+ - Creates `panglyph/v{X.Y.Z}/` directory
102
+ - Updates `panglyph/manifest.json` (latest version pointer)
103
+
104
+ ## Conflict resolution
105
+
106
+ The three workflows can run concurrently. They write to disjoint
107
+ directories (`coverage/`, `unicode/`, `panglyph/`), so git conflicts
108
+ are unlikely. If two syncs race, the second one's commit fails cleanly
109
+ (Git pre-receive hook rejects non-fast-forward) and re-runs on the
110
+ next trigger.
111
+
112
+ ## Manifest of manifests
113
+
114
+ Top-level `archive-public/manifest.json`:
115
+
116
+ ```json
117
+ {
118
+ "updated_at": "2026-...",
119
+ "coverage": {
120
+ "total_formulas": 4283,
121
+ "total_faces": 12000,
122
+ "last_sync": "2026-..."
123
+ },
124
+ "woff": {
125
+ "total_faces": 9500,
126
+ "last_sync": "2026-..."
127
+ },
128
+ "unicode": {
129
+ "ucd_version": "17.0.0",
130
+ "block_count": 346,
131
+ "codepoint_count": 299382,
132
+ "universal_set_built_at": "2026-..."
133
+ },
134
+ "panglyph": {
135
+ "latest": "17.0.0",
136
+ "released_at": "2026-..."
137
+ }
138
+ }
139
+ ```
140
+
141
+ fontist.org's fetch-data.sh reads this to display "data refreshed X
142
+ hours ago" on the site.
143
+
144
+ ## Acceptance
145
+
146
+ - [ ] `unicode/` directory exists with block-feed + universal-glyph-set
147
+ - [ ] `panglyph/` directory exists with at least one version
148
+ - [ ] Three sync workflows exist + run independently
149
+ - [ ] `archive-public/manifest.json` reflects the current state
150
+ - [ ] No git conflicts when 2+ syncs run concurrently (disjoint paths)
151
+ - [ ] fontist.org's fetch-data.sh can pull all four data streams
152
+
153
+ ## Dependencies / blockers
154
+
155
+ - **TODO.new 41** — ucode → archive bridge (the `unicode/` sync)
156
+ - **TODO 04** — panglyph publish (the `panglyph/` sync)
157
+ - **TODO 08** — archive-private uses ucode (the `coverage/` sync content changes)
158
+
159
+ ## References
160
+
161
+ - `fontist/fontist-archive-public` repo (current state)
162
+ - `fontist.org/scripts/fetch-data.sh` — consumer of all four streams
163
+ - [TODO.new 41](../TODO.new/41-ucode-unicode-archive-bridge.md) — ucode publishing
164
+ - [TODO 04](04-panglyph-publish-pipeline.md) — panglyph publishing
@@ -0,0 +1,131 @@
1
+ # 10 — fontist.org: per-font WOFF glyph rendering (open-license fonts)
2
+
3
+ ## Goal
4
+
5
+ Render font specimens on fontist.org using actual WOFF files from
6
+ `fontist-archive-public/woff/`. Currently the site has the WOFFs in
7
+ `public/fonts/*.woff2` but the unicode browser uses system fallback
8
+ fonts (via `displayChar(cp, category)`) which renders tofu for rare
9
+ scripts.
10
+
11
+ For open-license fonts, fontist.org should inject `@font-face` for the
12
+ specific font slug and render chars using that font directly.
13
+
14
+ ## Why a separate TODO
15
+
16
+ The fontist.org unicode browser today shows:
17
+ - Block grid: chars rendered via system fallback
18
+ - Char detail: same system fallback
19
+
20
+ Users want to see "what does this codepoint look like in Inter / Noto
21
+ Sans / FSung?" — not "what does my OS render it as?"
22
+
23
+ For open-license fonts (Noto family, Google Fonts, OFL SIL fonts),
24
+ fontist-archive-public/woff/ has the WOFF2 file. fontist.org can
25
+ inject `@font-face` per-font and render codepoints using the active
26
+ font.
27
+
28
+ For proprietary fonts (Apple system fonts, Microsoft core fonts), the
29
+ WOFFs aren't redistributable and the site can't serve them. Those
30
+ fonts show coverage data only (TODO 11).
31
+
32
+ ## Scope
33
+
34
+ ### Phase A — Font injection helper
35
+
36
+ 1. New composable: `src/composables/useFontFace.ts`:
37
+
38
+ ```typescript
39
+ export function useFontFace(slug: string, familyName?: string) {
40
+ const css = `
41
+ @font-face {
42
+ font-family: '${familyName || slug}';
43
+ src: url('/fonts/${slug}.woff2') format('woff2');
44
+ font-display: swap;
45
+ }
46
+ `
47
+ // Inject into document head (deduped by slug)
48
+ // ...
49
+ return { fontFamily: `'${familyName || slug}'` }
50
+ }
51
+ ```
52
+
53
+ 2. Used by:
54
+ - `UnicodeBlockGrid.vue` when the user picks an active font
55
+ - `UnicodeCharPage.vue` to render the specimen glyph using the
56
+ active font
57
+ - `FontStylePage.vue` to render specimens in the font detail page
58
+
59
+ 3. **Active font state** — top-level Vue ref (Pinia store or
60
+ provide/inject). User selects via a font picker on the unicode
61
+ browser pages.
62
+
63
+ ### Phase B — Font picker UI
64
+
65
+ 4. New component: `src/components/FontPicker.vue`:
66
+ - Lists all open-license fonts in `public/fonts/`
67
+ - Search box + family grouping
68
+ - On select: sets active font + persists to localStorage
69
+
70
+ 5. Position on unicode browser:
71
+ - Top of `/unicode` page (sticky)
72
+ - Per-font pages (`/fonts/{slug}/unicode`) — auto-selects that font
73
+
74
+ ### Phase C — Block grid rendering with active font
75
+
76
+ 6. `UnicodeBlockGrid.vue` — accept a `fontSlug` prop. When set:
77
+ - Inject `@font-face` via useFontFace
78
+ - Apply `font-family: var(--active-font)` to each cell
79
+ - Cells with chars the active font doesn't cover get a visual
80
+ indicator (grayed-out / strikethrough)
81
+
82
+ 7. **Coverage overlay** — small indicator on each cell showing whether
83
+ the active font covers it (green dot = yes, gray dot = no). Source:
84
+ ucode audit data (TODO 11).
85
+
86
+ ### Phase D — Char detail rendering
87
+
88
+ 8. `UnicodeCharPage.vue` — when an active font is set:
89
+ - The large glyph at top renders in the active font (not system fallback)
90
+ - Show a small badge: "Rendered in {font name}" + a toggle to switch
91
+ to "system fallback" for comparison
92
+
93
+ 9. When no active font: keep current behavior (system fallback).
94
+
95
+ ### Phase E — Performance
96
+
97
+ 10. **Lazy font loading** — WOFF2 files can be large (Noto Sans CJK JP
98
+ is 18MB). Don't preload all fonts; only load the active font's WOFF.
99
+
100
+ 11. **Font preloading hints** for the most-commonly-picked fonts
101
+ (Noto Sans, Inter, etc.) via `<link rel="preload">` on the home
102
+ page.
103
+
104
+ 12. **CDN-friendly URLs** — `public/fonts/{slug}.woff2` should have
105
+ long cache headers + immutable filenames (sha-suffixed? TODO
106
+ separate).
107
+
108
+ ## Acceptance
109
+
110
+ - [ ] `useFontFace` composable exists + injects `@font-face` cleanly
111
+ - [ ] `FontPicker.vue` lists all open-license WOFFs in `public/fonts/`
112
+ - [ ] Block grid renders cells in the active font
113
+ - [ ] Char detail renders the large glyph in the active font
114
+ - [ ] Per-cell coverage indicator (green = covered, gray = missing)
115
+ - [ ] Lazy font loading (no preload of unused WOFFs)
116
+ - [ ] Active font persists across page navigation (localStorage)
117
+
118
+ ## Dependencies / blockers
119
+
120
+ - **fontist-archive-public** — must have `woff/` populated (TODO 09)
121
+ - **TODO 11** — coverage data from ucode audit (for the green/gray
122
+ indicators)
123
+
124
+ ## References
125
+
126
+ - `src/components/UnicodeBlockGrid.vue` — current grid component
127
+ - `src/pages/UnicodeCharPage.vue` — current char page
128
+ - `src/pages/FontStylePage.vue` — existing font detail page (may
129
+ already inject @font-face for specimens — reuse the pattern)
130
+ - `public/fonts/*.woff2` — existing WOFF specimens
131
+ - [TODO 11](11-fontist-org-audit-coverage.md) — coverage data layer