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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +72 -0
- data/Gemfile.lock +2 -2
- data/TODO.full/00-README.md +116 -0
- data/TODO.full/01-panglyph-vision.md +112 -0
- data/TODO.full/02-panglyph-repo-bootstrap.md +184 -0
- data/TODO.full/03-panglyph-font-builder.md +201 -0
- data/TODO.full/04-panglyph-publish-pipeline.md +126 -0
- data/TODO.full/05-ucode-0-1-1-release.md +139 -0
- data/TODO.full/06-fontisan-remove-audit.md +142 -0
- data/TODO.full/07-fontisan-remove-ucd.md +125 -0
- data/TODO.full/08-archive-private-bin-build.md +143 -0
- data/TODO.full/09-archive-public-structure.md +164 -0
- data/TODO.full/10-fontist-org-woff-glyphs.md +131 -0
- data/TODO.full/11-fontist-org-audit-coverage.md +140 -0
- data/TODO.full/12-implementation-order.md +216 -0
- data/TODO.full/13-fontisan-font-writer-api.md +189 -0
- data/TODO.full/14-fontisan-table-writers.md +66 -0
- data/TODO.full/15-panglyph-builder-real.md +82 -0
- data/TODO.full/16-archive-public-sync-workflows.md +167 -0
- data/TODO.full/17-fontist-org-font-picker.md +73 -0
- data/TODO.full/18-comprehensive-spec-coverage.md +64 -0
- data/TODO.full/19-ucode-0-1-2-patch.md +32 -0
- data/TODO.full/20-fontisan-0-2-23-release.md +52 -0
- data/TODO.new/00-README.md +30 -0
- data/TODO.new/23-universal-glyph-set-source-map.md +312 -0
- data/TODO.new/24-universal-glyph-set-build.md +189 -0
- data/TODO.new/25-font-audit-against-universal-set.md +195 -0
- data/TODO.new/26-missing-glyph-reporter.md +189 -0
- data/TODO.new/27-fontist-org-consumer-integration.md +200 -0
- data/TODO.new/28-implementation-order-update.md +187 -0
- data/TODO.new/29-universal-set-curation-uc17.md +312 -0
- data/TODO.new/30-tier1-font-acquisition.md +241 -0
- data/TODO.new/31-universal-set-production-build.md +205 -0
- data/TODO.new/32-uc17-coverage-matrix.md +165 -0
- data/TODO.new/33-specialist-font-acquisition-refresh.md +138 -0
- data/TODO.new/34-pillar2-content-stream-correlator.md +147 -0
- data/TODO.new/35-universal-set-production-run.md +160 -0
- data/TODO.new/36-per-font-coverage-audit.md +145 -0
- data/TODO.new/37-coverage-highlight-reporter.md +125 -0
- data/TODO.new/38-fontist-org-glyph-consumer.md +141 -0
- data/TODO.new/39-implementation-order-update-32-38.md +258 -0
- data/TODO.new/40-archive-private-uses-ucode-audit.md +124 -0
- data/TODO.new/41-ucode-unicode-archive-bridge.md +160 -0
- data/config/specialist_fonts.yml +102 -0
- data/config/unicode17_tier1_fonts.yml +42 -0
- data/config/unicode17_universal_glyph_set.yml +293 -0
- data/lib/ucode/audit/block_aggregator.rb +57 -29
- data/lib/ucode/audit/browser/face_page.rb +128 -0
- data/lib/ucode/audit/browser/glyph_panel.rb +124 -0
- data/lib/ucode/audit/browser/library_page.rb +74 -0
- data/lib/ucode/audit/browser/missing_glyph_page.rb +87 -0
- data/lib/ucode/audit/browser/template.rb +47 -0
- data/lib/ucode/audit/browser/templates/face.css +200 -0
- data/lib/ucode/audit/browser/templates/face.html.erb +41 -0
- data/lib/ucode/audit/browser/templates/face.js +298 -0
- data/lib/ucode/audit/browser/templates/library.css +119 -0
- data/lib/ucode/audit/browser/templates/library.html.erb +42 -0
- data/lib/ucode/audit/browser/templates/library.js +99 -0
- data/lib/ucode/audit/browser/templates/missing_glyph_page.css +119 -0
- data/lib/ucode/audit/browser/templates/missing_glyph_page.html.erb +58 -0
- data/lib/ucode/audit/browser/templates/missing_glyph_page.js +2 -0
- data/lib/ucode/audit/browser.rb +32 -0
- data/lib/ucode/audit/context.rb +27 -1
- data/lib/ucode/audit/coverage_reference.rb +103 -0
- data/lib/ucode/audit/differ.rb +121 -0
- data/lib/ucode/audit/emitter/block_emitter.rb +52 -0
- data/lib/ucode/audit/emitter/codepoint_emitter.rb +87 -0
- data/lib/ucode/audit/emitter/collection_emitter.rb +80 -0
- data/lib/ucode/audit/emitter/face_directory.rb +212 -0
- data/lib/ucode/audit/emitter/glyph_emitter.rb +48 -0
- data/lib/ucode/audit/emitter/index_emitter.rb +149 -0
- data/lib/ucode/audit/emitter/library_emitter.rb +96 -0
- data/lib/ucode/audit/emitter/paths.rb +312 -0
- data/lib/ucode/audit/emitter/plane_emitter.rb +29 -0
- data/lib/ucode/audit/emitter/script_emitter.rb +29 -0
- data/lib/ucode/audit/emitter.rb +29 -0
- data/lib/ucode/audit/extractors/aggregations.rb +31 -2
- data/lib/ucode/audit/face_auditor.rb +86 -0
- data/lib/ucode/audit/formatters/audit_diff_text.rb +112 -0
- data/lib/ucode/audit/formatters/audit_text.rb +411 -0
- data/lib/ucode/audit/formatters/color.rb +48 -0
- data/lib/ucode/audit/formatters/library_summary_text.rb +98 -0
- data/lib/ucode/audit/formatters/text_formatter.rb +83 -0
- data/lib/ucode/audit/formatters.rb +23 -0
- data/lib/ucode/audit/library_aggregator.rb +86 -0
- data/lib/ucode/audit/library_auditor.rb +105 -0
- data/lib/ucode/audit/release/emitter.rb +152 -0
- data/lib/ucode/audit/release/face_card.rb +93 -0
- data/lib/ucode/audit/release/formula_audits.rb +50 -0
- data/lib/ucode/audit/release/library_index_builder.rb +78 -0
- data/lib/ucode/audit/release/manifest_builder.rb +127 -0
- data/lib/ucode/audit/release.rb +42 -0
- data/lib/ucode/audit/ucd_only_reference.rb +81 -0
- data/lib/ucode/audit/universal_set_reference.rb +136 -0
- data/lib/ucode/audit.rb +31 -0
- data/lib/ucode/cli.rb +339 -33
- data/lib/ucode/commands/audit/browser_command.rb +82 -0
- data/lib/ucode/commands/audit/collection_command.rb +103 -0
- data/lib/ucode/commands/audit/compare_command.rb +188 -0
- data/lib/ucode/commands/audit/font_command.rb +140 -0
- data/lib/ucode/commands/audit/library_command.rb +87 -0
- data/lib/ucode/commands/audit/reference_builder.rb +64 -0
- data/lib/ucode/commands/audit.rb +20 -0
- data/lib/ucode/commands/block_feed.rb +73 -0
- data/lib/ucode/commands/canonical_build.rb +138 -0
- data/lib/ucode/commands/fetch.rb +37 -1
- data/lib/ucode/commands/release.rb +115 -0
- data/lib/ucode/commands/universal_set.rb +211 -0
- data/lib/ucode/commands.rb +5 -0
- data/lib/ucode/coordinator/indices.rb +11 -0
- data/lib/ucode/coordinator.rb +138 -5
- data/lib/ucode/error.rb +30 -2
- data/lib/ucode/fetch/font_fetcher/result.rb +39 -0
- data/lib/ucode/fetch/font_fetcher.rb +16 -0
- data/lib/ucode/fetch/specialist_font_fetcher.rb +280 -0
- data/lib/ucode/fetch.rb +7 -3
- data/lib/ucode/glyphs/real_fonts/cmap_cache.rb +74 -0
- data/lib/ucode/glyphs/real_fonts.rb +1 -0
- data/lib/ucode/glyphs/resolver.rb +62 -0
- data/lib/ucode/glyphs/source.rb +48 -0
- data/lib/ucode/glyphs/source_builder.rb +61 -0
- data/lib/ucode/glyphs/source_config/coverage_assertion.rb +79 -0
- data/lib/ucode/glyphs/source_config/gap_report.rb +54 -0
- data/lib/ucode/glyphs/source_config.rb +104 -0
- data/lib/ucode/glyphs/sources/pillar1_embedded_tounicode.rb +63 -0
- data/lib/ucode/glyphs/sources/pillar3_last_resort.rb +51 -0
- data/lib/ucode/glyphs/sources/tier1_real_font.rb +104 -0
- data/lib/ucode/glyphs/sources.rb +20 -0
- data/lib/ucode/glyphs/universal_set/builder.rb +161 -0
- data/lib/ucode/glyphs/universal_set/coverage_report.rb +139 -0
- data/lib/ucode/glyphs/universal_set/idempotency.rb +86 -0
- data/lib/ucode/glyphs/universal_set/manifest_accumulator.rb +195 -0
- data/lib/ucode/glyphs/universal_set/manifest_writer.rb +61 -0
- data/lib/ucode/glyphs/universal_set/pre_build_check.rb +197 -0
- data/lib/ucode/glyphs/universal_set/validator.rb +204 -0
- data/lib/ucode/glyphs/universal_set.rb +45 -0
- data/lib/ucode/glyphs.rb +6 -0
- data/lib/ucode/models/audit/baseline.rb +6 -0
- data/lib/ucode/models/audit/block_summary.rb +7 -0
- data/lib/ucode/models/audit/codepoint_provenance.rb +39 -0
- data/lib/ucode/models/audit/release_face.rb +42 -0
- data/lib/ucode/models/audit/release_formula.rb +33 -0
- data/lib/ucode/models/audit/release_manifest.rb +43 -0
- data/lib/ucode/models/audit/release_universal_set.rb +37 -0
- data/lib/ucode/models/audit.rb +9 -0
- data/lib/ucode/models/block.rb +2 -0
- data/lib/ucode/models/build_report.rb +109 -0
- data/lib/ucode/models/codepoint/glyph.rb +42 -0
- data/lib/ucode/models/codepoint.rb +3 -0
- data/lib/ucode/models/glyph_source.rb +86 -0
- data/lib/ucode/models/glyph_source_map.rb +138 -0
- data/lib/ucode/models/specialist_font.rb +70 -0
- data/lib/ucode/models/specialist_font_manifest.rb +48 -0
- data/lib/ucode/models/unihan_entry.rb +81 -9
- data/lib/ucode/models/unihan_field.rb +21 -0
- data/lib/ucode/models/universal_set_entry.rb +47 -0
- data/lib/ucode/models/universal_set_manifest.rb +78 -0
- data/lib/ucode/models/validation_report.rb +99 -0
- data/lib/ucode/models.rb +9 -0
- data/lib/ucode/parsers/named_sequences.rb +5 -5
- data/lib/ucode/parsers/unihan.rb +50 -19
- data/lib/ucode/repo/aggregate_writer.rb +34 -2
- data/lib/ucode/repo/block_feed_emitter.rb +153 -0
- data/lib/ucode/repo/build_report_accumulator.rb +138 -0
- data/lib/ucode/repo/build_report_writer.rb +46 -0
- data/lib/ucode/repo/build_validator.rb +229 -0
- data/lib/ucode/repo/codepoint_writer.rb +50 -1
- data/lib/ucode/repo/paths.rb +8 -0
- data/lib/ucode/repo.rb +4 -0
- data/lib/ucode/version.rb +1 -1
- data/schema/block-feed.output.schema.yml +134 -0
- metadata +143 -2
- data/ucode.gemspec +0 -56
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# 03 — Panglyph font builder (outline extraction + assembly)
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Implement the actual font-build pipeline: read ucode's universal-set
|
|
6
|
+
manifest, extract outlines from Tier 1 source fonts via fontisan,
|
|
7
|
+
assemble them into a single OpenType/TrueType font, and write the
|
|
8
|
+
result to disk in TTF / WOFF2 / OTF formats.
|
|
9
|
+
|
|
10
|
+
This is the heart of panglyph. The CLI commands (`build`, `validate`,
|
|
11
|
+
`publish`) are thin wrappers around these classes.
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
ucode universal-set manifest
|
|
17
|
+
│
|
|
18
|
+
│ {cp: 65, source: {font: "noto-sans", gid: 36}}
|
|
19
|
+
│ {cp: 19968, source: {font: "FSung-1", gid: 18432}}
|
|
20
|
+
│ {cp: 128512, source: {font: "NotoSerifTaiYo", gid: 41}}
|
|
21
|
+
│ ...
|
|
22
|
+
▼
|
|
23
|
+
┌──────────────────────────────────────────────┐
|
|
24
|
+
│ Builder │
|
|
25
|
+
│ - reads manifest │
|
|
26
|
+
│ - groups codepoints by source font │
|
|
27
|
+
│ - for each source font: │
|
|
28
|
+
│ OutlineExtractor.extract_many(font, cps)│
|
|
29
|
+
│ - passes {cp → outline} to FontAssembler │
|
|
30
|
+
└──────────────────────────────────────────────┘
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
┌──────────────────────────────────────────────┐
|
|
34
|
+
│ FontAssembler │
|
|
35
|
+
│ - opens an empty font skeleton │
|
|
36
|
+
│ - for each codepoint: │
|
|
37
|
+
│ - allocates a GID in panglyph │
|
|
38
|
+
│ - writes the outline into glyf/CFF │
|
|
39
|
+
│ - sets cmap[cp] = GID │
|
|
40
|
+
│ - finalizes: OS/2, name, head, hhea, hmtx │
|
|
41
|
+
│ - writes panglyph-unicode17.ttf │
|
|
42
|
+
└──────────────────────────────────────────────┘
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
┌──────────────────────────────────────────────┐
|
|
46
|
+
│ Woff2Writer (via fontisan ConvertCommand) │
|
|
47
|
+
│ - reads the TTF │
|
|
48
|
+
│ - writes panglyph-unicode17.woff2 │
|
|
49
|
+
└──────────────────────────────────────────────┘
|
|
50
|
+
│
|
|
51
|
+
▼
|
|
52
|
+
┌──────────────────────────────────────────────┐
|
|
53
|
+
│ CoverageReport │
|
|
54
|
+
│ - summarizes per-block source breakdown │
|
|
55
|
+
│ - writes coverage-report.json │
|
|
56
|
+
└──────────────────────────────────────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Scope
|
|
60
|
+
|
|
61
|
+
### Phase A — Outline extraction (`OutlineExtractor`)
|
|
62
|
+
|
|
63
|
+
1. **Open source font** via `Fontisan::Font.open(path)`.
|
|
64
|
+
2. **For each codepoint** in the source's responsibility list:
|
|
65
|
+
a. Look up GID via `font.cmap.unicode_map[cp]`
|
|
66
|
+
b. Get the outline:
|
|
67
|
+
- TrueType: `font.glyf.glyphs[gid]` → contour points
|
|
68
|
+
- CFF/OpenType: `font.cff.charstrings[gid]` → charstring program
|
|
69
|
+
c. Normalize to a uniform internal representation
|
|
70
|
+
(`Panglyph::Outline` struct with point arrays + flags).
|
|
71
|
+
3. **Return** `{ cp => Outline }` hash.
|
|
72
|
+
|
|
73
|
+
#### Edge cases
|
|
74
|
+
|
|
75
|
+
- **Composite glyphs** (TrueType): if a glyph references other glyphs
|
|
76
|
+
via component references, recursively flatten into a single outline.
|
|
77
|
+
Track "this glyph was a composite" in the report.
|
|
78
|
+
- **TTC collections**: pass `font_index:` to `Fontisan::Font.open`.
|
|
79
|
+
- **Missing cmap entry**: the Tier 1 font claims to cover this cp but
|
|
80
|
+
its cmap doesn't actually map it. Log + skip; the universal set's
|
|
81
|
+
pre-check (TODO.new/35) catches this upstream.
|
|
82
|
+
|
|
83
|
+
### Phase B — Font assembly (`FontAssembler`)
|
|
84
|
+
|
|
85
|
+
4. **Initialize an empty font skeleton** with the standard required
|
|
86
|
+
tables: `cmap`, `head`, `hhea`, `hmtx`, `maxp`, `name`, `OS/2`,
|
|
87
|
+
`glyf`, `loca`, `post`. (For OTF: `CFF ` instead of `glyf`/`loca`.)
|
|
88
|
+
5. **For each codepoint**:
|
|
89
|
+
a. Allocate next GID (`maxp.numGlyphs += 1`).
|
|
90
|
+
b. Write the outline to `glyf` (or `CFF `).
|
|
91
|
+
c. Add `cmap` subtable 4 entry: `unicode_map[cp] = gid`.
|
|
92
|
+
d. Add `hmtx` entry (advance width from source font; default 1000
|
|
93
|
+
if missing).
|
|
94
|
+
e. Add `name` entries — actually, only the font-family / version
|
|
95
|
+
name needs to be set; per-glyph names aren't required.
|
|
96
|
+
6. **Finalize metadata**:
|
|
97
|
+
- `name`: family="panglyph Unicode 17", subfamily="Regular",
|
|
98
|
+
full="panglyph-unicode17", version="Version 17.0.0"
|
|
99
|
+
- `OS/2`: usWeightClass=400, fsSelection=REGULAR, ulUnicodeRange1..4
|
|
100
|
+
bit-packed for every range panglyph covers
|
|
101
|
+
- `head`: unitsPerEm=1000 (or 2048 for CJK-heavy), lowestRecPPEM=8
|
|
102
|
+
- `hhea`: numberOfHMetrics matches glyphs
|
|
103
|
+
7. **Compute checksums + write final TTF**.
|
|
104
|
+
|
|
105
|
+
#### fontisan extension required
|
|
106
|
+
|
|
107
|
+
fontisan today only READS fonts + converts (WOFF). It needs new APIs:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# lib/fontisan/font_writer.rb (NEW)
|
|
111
|
+
class Fontisan::FontWriter
|
|
112
|
+
def initialize(format: :ttf)
|
|
113
|
+
@tables = {}
|
|
114
|
+
@format = format
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def set_cmap(unicode_to_gid_map)
|
|
118
|
+
# ...
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def add_glyph(gid, outline, advance_width: 1000, lsb: 0)
|
|
122
|
+
# ...
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def set_name_records(records)
|
|
126
|
+
# ...
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def write_to(path)
|
|
130
|
+
# computes checksums, writes head/glyf/loca/cmap/etc.
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This is a meaningful fontisan addition. Tracked separately as a
|
|
136
|
+
fontisan TODO (see TODO.new/19 — fontisan docs update).
|
|
137
|
+
|
|
138
|
+
### Phase C — WOFF2 conversion (`Woff2Writer`)
|
|
139
|
+
|
|
140
|
+
8. Use `Fontisan::Commands::ConvertCommand.new(ttf_path, to: "woff2", output: ...)`.
|
|
141
|
+
No new code needed; just shell out from panglyph.
|
|
142
|
+
|
|
143
|
+
### Phase D — Coverage report (`CoverageReport`)
|
|
144
|
+
|
|
145
|
+
9. Walk the built font's cmap.
|
|
146
|
+
10. Compare against ucode's universal-set codepoint list.
|
|
147
|
+
11. Bucket per-block + per-source-tier:
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"total_codepoints": 299382,
|
|
151
|
+
"covered": 297415,
|
|
152
|
+
"missing": 1967,
|
|
153
|
+
"by_block": [
|
|
154
|
+
{ "block_id": "Egyptian_Hieroglyphs_Extended-B", "covered": 599, "missing": 1, "tier": 1 }
|
|
155
|
+
],
|
|
156
|
+
"by_tier": [
|
|
157
|
+
{ "tier": 1, "count": 295000 },
|
|
158
|
+
{ "tier": 2, "count": 2415 },
|
|
159
|
+
{ "tier": 3, "count": 1967 }
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Performance considerations
|
|
165
|
+
|
|
166
|
+
- 299,382 glyph extractions × ~1ms each = ~5 minutes of extraction.
|
|
167
|
+
- Parallelize per-source-font: each source font's extractions are
|
|
168
|
+
independent, so use parallel gem or `Concurrent::Map`.
|
|
169
|
+
- Memory: hold all outlines in memory at once (~1.2GB estimated). May
|
|
170
|
+
need to stream to disk for low-memory CI runners (use a temp sqlite
|
|
171
|
+
database keyed by GID).
|
|
172
|
+
|
|
173
|
+
## Specs
|
|
174
|
+
|
|
175
|
+
- **Builder** — fixture: 5 codepoints from 2 source fonts; assert
|
|
176
|
+
output font's cmap matches the manifest.
|
|
177
|
+
- **OutlineExtractor** — fixture: a real font with known glyf layout;
|
|
178
|
+
assert extracted outline matches a known-good serialization.
|
|
179
|
+
- **FontAssembler** — fixture: 3 outlines; assert output TTF parses
|
|
180
|
+
via `Fontisan::Font.open` and has the expected cmap.
|
|
181
|
+
- **Woff2Writer** — fixture: a known TTF; assert WOFF2 output
|
|
182
|
+
byte-identical to a fontisan-direct conversion (sanity check).
|
|
183
|
+
- **CoverageReport** — fixture: a built font with intentional gaps;
|
|
184
|
+
assert report correctly identifies missing codepoints.
|
|
185
|
+
|
|
186
|
+
## Acceptance
|
|
187
|
+
|
|
188
|
+
- [ ] `panglyph build 17.0.0` produces a valid TTF, WOFF2, and OTF
|
|
189
|
+
- [ ] The built font's cmap contains every codepoint in the universal
|
|
190
|
+
set's manifest
|
|
191
|
+
- [ ] `fontisan Font.open(panglyph.ttf)` succeeds (no corruption)
|
|
192
|
+
- [ ] Coverage report JSON validates against schema
|
|
193
|
+
- [ ] Build completes in <30 minutes on a single runner
|
|
194
|
+
- [ ] Built font renders correctly in a browser (manual smoke test)
|
|
195
|
+
|
|
196
|
+
## References
|
|
197
|
+
|
|
198
|
+
- [TODO 01](01-panglyph-vision.md) — vision
|
|
199
|
+
- [TODO 02](02-panglyph-repo-bootstrap.md) — repo skeleton
|
|
200
|
+
- [TODO.new/24](../TODO.new/24-universal-glyph-set-build.md) — ucode's universal-set SVG output (panglyph input)
|
|
201
|
+
- fontisan Font.open API (for reading source fonts)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# 04 — Panglyph publish pipeline (release to fontist-archive-public)
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
When `panglyph build` completes, publish the resulting TTF/WOFF2/OTF
|
|
6
|
+
artifacts to `fontist-archive-public/panglyph/`. This makes panglyph
|
|
7
|
+
available to fontist.org and any other consumer that pulls from the
|
|
8
|
+
public archive.
|
|
9
|
+
|
|
10
|
+
## Why a separate TODO
|
|
11
|
+
|
|
12
|
+
The build itself is local (TODO 03). Publishing involves:
|
|
13
|
+
- Authenticating to fontist-archive-public via GHA bot token
|
|
14
|
+
- Atomic sync (don't leave the archive half-updated)
|
|
15
|
+
- Versioning (multiple panglyph versions can coexist)
|
|
16
|
+
- Manifest update (so consumers can discover what's available)
|
|
17
|
+
|
|
18
|
+
These concerns are distinct from the build, and any failure here
|
|
19
|
+
shouldn't invalidate the build itself.
|
|
20
|
+
|
|
21
|
+
## Scope
|
|
22
|
+
|
|
23
|
+
### Phase A — `panglyph publish` command
|
|
24
|
+
|
|
25
|
+
1. New CLI subcommand: `panglyph publish [VERSION]`.
|
|
26
|
+
|
|
27
|
+
2. Behavior:
|
|
28
|
+
- Clone `fontist/fontist-archive-public` shallow into a temp dir.
|
|
29
|
+
- Copy artifacts:
|
|
30
|
+
```
|
|
31
|
+
panglyph-unicode17-17.0.0.ttf → archive-public/panglyph/v17.0.0/panglyph-unicode17.ttf
|
|
32
|
+
panglyph-unicode17-17.0.0.woff2 → archive-public/panglyph/v17.0.0/panglyph-unicode17.woff2
|
|
33
|
+
panglyph-unicode17-17.0.0.otf → archive-public/panglyph/v17.0.0/panglyph-unicode17.otf
|
|
34
|
+
coverage-report.json → archive-public/panglyph/v17.0.0/coverage-report.json
|
|
35
|
+
source-manifest.json → archive-public/panglyph/v17.0.0/source-manifest.json
|
|
36
|
+
```
|
|
37
|
+
- Update top-level `archive-public/panglyph/manifest.json`:
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"latest": "17.0.0",
|
|
41
|
+
"versions": {
|
|
42
|
+
"17.0.0": {
|
|
43
|
+
"ucd_version": "17.0.0",
|
|
44
|
+
"panglyph_version": "17.0.0",
|
|
45
|
+
"released_at": "2026-...",
|
|
46
|
+
"coverage": { "covered": 297415, "total": 299382, "percentage": 99.3 },
|
|
47
|
+
"artifacts": {
|
|
48
|
+
"ttf": "v17.0.0/panglyph-unicode17.ttf",
|
|
49
|
+
"woff2": "v17.0.0/panglyph-unicode17.woff2",
|
|
50
|
+
"otf": "v17.0.0/panglyph-unicode17.otf"
|
|
51
|
+
},
|
|
52
|
+
"sha256": { "ttf": "...", "woff2": "...", "otf": "..." }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
- Commit + push via GHA bot identity:
|
|
58
|
+
```
|
|
59
|
+
Author: fontist-bot <bot@fontist.org>
|
|
60
|
+
Message: panglyph: publish v17.0.0
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
3. **Atomicity**: write to a temp dir first, then `git mv` into place.
|
|
64
|
+
If any step fails, the archive is not left in a half-published state.
|
|
65
|
+
|
|
66
|
+
### Phase B — Source manifest
|
|
67
|
+
|
|
68
|
+
4. The build emits a `source-manifest.json` recording which Tier 1
|
|
69
|
+
source font contributed each glyph:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"sources": [
|
|
74
|
+
{ "label": "noto-sans", "license": "OFL", "url": "...", "sha256": "...", "glyphs_contributed": 1107 },
|
|
75
|
+
{ "label": "FSung-1", "license": "OFL", "path": "data/fonts/FSung-1.ttf", "sha256": "...", "glyphs_contributed": 20992 },
|
|
76
|
+
...
|
|
77
|
+
],
|
|
78
|
+
"total_sources": 17,
|
|
79
|
+
"total_glyphs": 297415
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
5. This manifest is the **provenance record** for the published font.
|
|
84
|
+
Anyone redistributing panglyph can verify OFL compliance.
|
|
85
|
+
|
|
86
|
+
### Phase C — GitHub Release
|
|
87
|
+
|
|
88
|
+
6. In addition to pushing to fontist-archive-public, the CI workflow
|
|
89
|
+
(TODO 02) creates a GitHub Release on the `panglyph` repo:
|
|
90
|
+
- Tag: `v17.0.0`
|
|
91
|
+
- Title: `panglyph Unicode 17.0.0`
|
|
92
|
+
- Body: copy of the coverage report + source manifest summary
|
|
93
|
+
- Assets: the TTF / WOFF2 / OTF files + JSON manifests
|
|
94
|
+
|
|
95
|
+
7. Why both? `fontist-archive-public/panglyph/` is the canonical
|
|
96
|
+
machine-readable source. GitHub Releases is the human-discoverable
|
|
97
|
+
download page (people search GitHub for "panglyph" and find the
|
|
98
|
+
release directly).
|
|
99
|
+
|
|
100
|
+
### Phase D — Idempotency + re-publishing
|
|
101
|
+
|
|
102
|
+
8. If the same version is re-published (e.g. fixing a build bug):
|
|
103
|
+
- Detect existing version directory
|
|
104
|
+
- Compare new artifacts' sha256 to existing
|
|
105
|
+
- If identical, no-op (idempotent)
|
|
106
|
+
- If different, bump patch version (`17.0.0` → `17.0.1`) and publish
|
|
107
|
+
as new version. Old version stays for rollback.
|
|
108
|
+
|
|
109
|
+
9. **NEVER overwrite a published version in-place.** Consumers may have
|
|
110
|
+
cached the old artifacts. Bumping patch is the only safe update.
|
|
111
|
+
|
|
112
|
+
## Acceptance
|
|
113
|
+
|
|
114
|
+
- [ ] `panglyph publish 17.0.0` updates fontist-archive-public/panglyph/
|
|
115
|
+
- [ ] `manifest.json` reflects the new version
|
|
116
|
+
- [ ] Source manifest records every contributing font + its sha256
|
|
117
|
+
- [ ] GitHub Release exists on `fontist/panglyph` with the right tag
|
|
118
|
+
- [ ] Re-publishing a built version is a no-op (idempotent)
|
|
119
|
+
- [ ] Failed publish leaves archive-public unchanged (atomic)
|
|
120
|
+
|
|
121
|
+
## References
|
|
122
|
+
|
|
123
|
+
- [TODO 02](02-panglyph-repo-bootstrap.md) — CI workflow
|
|
124
|
+
- [TODO 03](03-panglyph-font-builder.md) — build artifacts
|
|
125
|
+
- [TODO.new/41](../TODO.new/41-ucode-unicode-archive-bridge.md) — archive bridge pattern
|
|
126
|
+
- [TODO 09](09-archive-public-structure.md) — overall archive-public structure
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# 05 — ucode 0.1.1 patch release
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Cut the first public ucode gem release: **0.1.0 → 0.1.1**. Unblocks
|
|
6
|
+
downstream consumers (fontist-archive-private, panglyph) that depend
|
|
7
|
+
on a published gem.
|
|
8
|
+
|
|
9
|
+
## Why 0.1.1 (not 0.1.0)
|
|
10
|
+
|
|
11
|
+
0.1.0 was the initial commit (`Initial release: ucode 0.1.0`). The
|
|
12
|
+
codebase has since gained:
|
|
13
|
+
- BlockFeedEmitter (renamed from FontistConsumerEmitter)
|
|
14
|
+
- 4-tier canonical resolver
|
|
15
|
+
- Universal-set build infrastructure
|
|
16
|
+
- Real UCD 17.0.0 parse pipeline
|
|
17
|
+
- Audit subsystem ported from fontisan
|
|
18
|
+
- Block-feed shape schema
|
|
19
|
+
- Categorized Unihan data model
|
|
20
|
+
|
|
21
|
+
A patch bump (0.1.1) signals "iteration on the initial release" without
|
|
22
|
+
claiming API stability (which would warrant 0.2.0).
|
|
23
|
+
|
|
24
|
+
## Scope
|
|
25
|
+
|
|
26
|
+
### Phase A — Pre-release prep
|
|
27
|
+
|
|
28
|
+
1. **Update `lib/ucode/version.rb`**:
|
|
29
|
+
```ruby
|
|
30
|
+
module Ucode
|
|
31
|
+
VERSION = "0.1.1"
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Create `CHANGELOG.md`** (doesn't exist yet) with the 0.1.1 entry:
|
|
36
|
+
```markdown
|
|
37
|
+
# Changelog
|
|
38
|
+
|
|
39
|
+
## 0.1.1 — 2026-06-XX
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- BlockFeedEmitter: emits a compact per-block Unicode data feed
|
|
43
|
+
(renamed from FontistConsumerEmitter — the data is plain Unicode
|
|
44
|
+
data, not consumer-specific).
|
|
45
|
+
- Categorized Unihan model: 8 typed collections matching the Unihan
|
|
46
|
+
file structure (Dictionary Indices, Readings, Variants, etc.).
|
|
47
|
+
- Real-font Tier 1 source map for the universal glyph set (~17
|
|
48
|
+
specialists + Noto family default).
|
|
49
|
+
- Pillar 1 + Pillar 2 glyph extraction via 4-tier resolver.
|
|
50
|
+
- Per-codepoint properties from `extracted/` and `auxiliary/` UCD
|
|
51
|
+
files (display, segmentation, Indic, Hangul, Emoji, full binary
|
|
52
|
+
properties list).
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- NamedSequences parser field order (real UAX#44 is `Name; cps...`).
|
|
56
|
+
- BlockFeedEmitter canonical path (`blocks/<ID>/index.json` not
|
|
57
|
+
`blocks/<ID>.json`).
|
|
58
|
+
- fontist.org char page route params for combining/bidiclass.
|
|
59
|
+
- Vite dev server case-sensitive codepoints/ path (lowercase hex).
|
|
60
|
+
- Vue route-watcher for per-char data on navigation.
|
|
61
|
+
|
|
62
|
+
### Removed
|
|
63
|
+
- All references to "fontist-consumer" naming from ucode (now
|
|
64
|
+
"block-feed"). The data emitted is Unicode data, not
|
|
65
|
+
consumer-specific.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
3. **Update `ucode.gemspec`** if any metadata needs changing (likely
|
|
69
|
+
nothing — `spec.metadata` is already correct).
|
|
70
|
+
|
|
71
|
+
4. **Run full test suite + rubocop** to make sure 0.1.1 actually ships
|
|
72
|
+
green:
|
|
73
|
+
```bash
|
|
74
|
+
bundle exec rspec
|
|
75
|
+
bundle exec rubocop
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Phase B — Branch + PR
|
|
79
|
+
|
|
80
|
+
5. Create release branch off `main`:
|
|
81
|
+
```bash
|
|
82
|
+
git checkout main && git pull
|
|
83
|
+
git checkout -b release/0.1.1
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
6. Apply changes: version bump + CHANGELOG.
|
|
87
|
+
|
|
88
|
+
7. Push + open PR:
|
|
89
|
+
```bash
|
|
90
|
+
git push -u origin release/0.1.1
|
|
91
|
+
gh pr create --title "Release ucode 0.1.1" --body "..."
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Phase C — Merge + tag + publish
|
|
95
|
+
|
|
96
|
+
8. **HARD GATE**: require explicit user authorization to merge + tag +
|
|
97
|
+
push to rubygems. Per global rules:
|
|
98
|
+
- NEVER push tags without user authorization
|
|
99
|
+
- NEVER merge to main without user authorization
|
|
100
|
+
|
|
101
|
+
9. After user says "merge + tag + release":
|
|
102
|
+
```bash
|
|
103
|
+
gh pr merge --merge release/0.1.1 # or rebase per user pref
|
|
104
|
+
git checkout main && git pull
|
|
105
|
+
git tag v0.1.1
|
|
106
|
+
git push origin v0.1.1
|
|
107
|
+
bundle exec rake release # pushes to rubygems.org
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
10. Verify on RubyGems:
|
|
111
|
+
```bash
|
|
112
|
+
gem search ucode --remote
|
|
113
|
+
# → ucode (0.1.1)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
11. Create GitHub Release on the tag with the CHANGELOG entry as body.
|
|
117
|
+
|
|
118
|
+
### Phase D — Downstream notifications
|
|
119
|
+
|
|
120
|
+
12. Once 0.1.1 is on RubyGems, downstream consumers can update:
|
|
121
|
+
- `fontist/Gemfile` in fontist-archive-private: bump `ucode` to `~> 0.1`
|
|
122
|
+
- `panglyph.gemspec`: same
|
|
123
|
+
- `fontisan` Gemfile if it consumes ucode (it shouldn't after TODO 06/07)
|
|
124
|
+
|
|
125
|
+
## Acceptance
|
|
126
|
+
|
|
127
|
+
- [ ] `lib/ucode/version.rb` says `0.1.1`
|
|
128
|
+
- [ ] `CHANGELOG.md` exists with the 0.1.1 entry
|
|
129
|
+
- [ ] All specs pass; rubocop clean
|
|
130
|
+
- [ ] PR opened against main, green CI
|
|
131
|
+
- [ ] **WAIT for explicit user authorization before merge/tag/release**
|
|
132
|
+
- [ ] After release: `gem install ucode` works; `ucode --version` prints `0.1.1`
|
|
133
|
+
- [ ] GitHub Release exists at `fontist/ucode/releases/tag/v0.1.1`
|
|
134
|
+
|
|
135
|
+
## References
|
|
136
|
+
|
|
137
|
+
- `lib/ucode/version.rb` — version constant
|
|
138
|
+
- `ucode.gemspec` — gem metadata
|
|
139
|
+
- Global rule: NEVER push tags / merge to main without authorization
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# 06 — fontisan: remove AuditCommand (and audit/ namespace)
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Strip the audit subsystem out of `fontisan`. ucode now owns font
|
|
6
|
+
auditing (`ucode audit font`); fontisan's `AuditCommand`,
|
|
7
|
+
`AuditLibraryCommand`, `AuditCompareCommand`, and the entire
|
|
8
|
+
`lib/fontisan/audit/` and `lib/fontisan/models/audit/` namespaces
|
|
9
|
+
are dead code that misleads consumers.
|
|
10
|
+
|
|
11
|
+
The current `fontist-archive-private/bin/build` script still references
|
|
12
|
+
`Fontisan::Commands::AuditCommand` — it'll break loudly once this lands,
|
|
13
|
+
which is the point. TODO 08 wires bin/build to call `ucode audit font`
|
|
14
|
+
instead.
|
|
15
|
+
|
|
16
|
+
## Why now
|
|
17
|
+
|
|
18
|
+
- ucode's audit is the canonical path (TODO.new 06-12 ported it).
|
|
19
|
+
- fontisan's audit is unmaintained and uses a UCD-stub hack (lines
|
|
20
|
+
13-21 of `fontist-archive-private/bin/build`) that papers over the
|
|
21
|
+
fact that fontisan can't actually do UCD aggregation anymore.
|
|
22
|
+
- Leaving it creates confusion: which is the source of truth?
|
|
23
|
+
|
|
24
|
+
## Scope
|
|
25
|
+
|
|
26
|
+
The audit removal touches these paths in `fontist/fontisan`:
|
|
27
|
+
|
|
28
|
+
### Code
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
lib/fontisan/audit.rb # DELETE
|
|
32
|
+
lib/fontisan/audit/ # DELETE entire directory
|
|
33
|
+
cli/
|
|
34
|
+
formatters/
|
|
35
|
+
library_auditor.rb
|
|
36
|
+
differ.rb
|
|
37
|
+
face_card.rb
|
|
38
|
+
extractor.rb
|
|
39
|
+
...
|
|
40
|
+
lib/fontisan/models/audit.rb # DELETE
|
|
41
|
+
lib/fontisan/models/audit/ # DELETE entire directory
|
|
42
|
+
lib/fontisan/formatters/audit_text_renderer.rb # DELETE
|
|
43
|
+
lib/fontisan/formatters/audit_diff_text_renderer.rb # DELETE
|
|
44
|
+
lib/fontisan/commands/audit_command.rb # DELETE
|
|
45
|
+
lib/fontisan/commands/audit_compare_command.rb # DELETE
|
|
46
|
+
lib/fontisan/commands/audit_library_command.rb # DELETE
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Tests
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
spec/fontisan/audit/ # DELETE
|
|
53
|
+
spec/fontisan/models/audit/ # DELETE
|
|
54
|
+
spec/fontisan/commands/audit_command_spec.rb # DELETE
|
|
55
|
+
spec/fontisan/commands/audit_compare_command_spec.rb # DELETE
|
|
56
|
+
spec/fontisan/commands/audit_library_command_spec.rb # DELETE
|
|
57
|
+
spec/fontisan/cli/audit_cli_spec.rb # DELETE
|
|
58
|
+
spec/fontisan/formatters/audit_text_renderer_spec.rb # DELETE
|
|
59
|
+
spec/fontisan/formatters/audit_diff_text_renderer_spec.rb # DELETE
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### CLI registration
|
|
63
|
+
|
|
64
|
+
`lib/fontisan/cli.rb` registers audit subcommands. Remove those
|
|
65
|
+
registrations — keep `convert`, `info`, `dump-table`, `features`,
|
|
66
|
+
`glyphs`, `export`.
|
|
67
|
+
|
|
68
|
+
### Documentation
|
|
69
|
+
|
|
70
|
+
- `README.md` — remove audit references; slim to "fontisan parses
|
|
71
|
+
fonts and converts between formats"
|
|
72
|
+
- `docs/` — delete `audit.md`, `audit-format.md`, etc.
|
|
73
|
+
|
|
74
|
+
## Migration path
|
|
75
|
+
|
|
76
|
+
Consumers currently calling `Fontisan::Commands::AuditCommand.new(path).run`
|
|
77
|
+
should switch to `ucode audit font <path>`. The output YAML shape is
|
|
78
|
+
identical (ucode's audit was ported from fontisan's); only the tool
|
|
79
|
+
name changes.
|
|
80
|
+
|
|
81
|
+
Document this in the CHANGELOG entry:
|
|
82
|
+
|
|
83
|
+
```markdown
|
|
84
|
+
## 0.3.0 — 2026-XX-XX
|
|
85
|
+
|
|
86
|
+
### Removed — Audit subsystem moved to ucode
|
|
87
|
+
|
|
88
|
+
The audit pipeline (`AuditCommand`, `AuditLibraryCommand`,
|
|
89
|
+
`AuditCompareCommand`, and all `lib/fontisan/audit/` support) has
|
|
90
|
+
been removed. ucode now owns font auditing.
|
|
91
|
+
|
|
92
|
+
Migration:
|
|
93
|
+
|
|
94
|
+
# Before (fontisan 0.2.x):
|
|
95
|
+
Fontisan::Commands::AuditCommand.new(path).run
|
|
96
|
+
|
|
97
|
+
# After (ucode 0.1.1+):
|
|
98
|
+
bundle exec ucode audit font <path>
|
|
99
|
+
# or via API:
|
|
100
|
+
Ucode::Commands::Audit::FontCommand.new.call(path: path)
|
|
101
|
+
|
|
102
|
+
The audit YAML output shape is unchanged; existing consumers don't
|
|
103
|
+
need to update their parsers.
|
|
104
|
+
|
|
105
|
+
For the CI pipeline that runs audits per formula, see
|
|
106
|
+
fontist-archive-private's `bin/build` (now calls `ucode audit font`).
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Version bump
|
|
110
|
+
|
|
111
|
+
This is a breaking change → **minor version bump** (0.2.22 → 0.3.0).
|
|
112
|
+
Per SemVer: removing public APIs is a breaking change in 0.x.
|
|
113
|
+
|
|
114
|
+
## Acceptance
|
|
115
|
+
|
|
116
|
+
- [ ] `lib/fontisan/audit.rb` and `lib/fontisan/audit/` are deleted
|
|
117
|
+
- [ ] `lib/fontisan/models/audit.rb` and `lib/fontisan/models/audit/` deleted
|
|
118
|
+
- [ ] `lib/fontisan/commands/audit_*.rb` deleted (3 files)
|
|
119
|
+
- [ ] `lib/fontisan/formatters/audit_*.rb` deleted (2 files)
|
|
120
|
+
- [ ] `lib/fontisan/cli.rb` no longer registers audit subcommands
|
|
121
|
+
- [ ] All audit-related specs deleted
|
|
122
|
+
- [ ] `bundle exec rspec` passes (existing fontisan specs unrelated to audit)
|
|
123
|
+
- [ ] `bundle exec rubocop` clean
|
|
124
|
+
- [ ] `fontisan --help` no longer shows audit subcommands
|
|
125
|
+
- [ ] CHANGELOG entry documents the removal + migration path
|
|
126
|
+
- [ ] Version bumped to 0.3.0
|
|
127
|
+
- [ ] PR opened against main
|
|
128
|
+
|
|
129
|
+
## Dependencies / blockers
|
|
130
|
+
|
|
131
|
+
- **TODO.new 06-12** — these ported fontisan's audit subsystem into ucode.
|
|
132
|
+
Verify that port is complete before this removal lands (otherwise the
|
|
133
|
+
audit functionality disappears entirely).
|
|
134
|
+
- **TODO 08** — `fontist-archive-private/bin/build` will break when this
|
|
135
|
+
merges. TODO 08 should land first OR in the same PR-bundle.
|
|
136
|
+
|
|
137
|
+
## References
|
|
138
|
+
|
|
139
|
+
- `lib/fontisan/audit.rb` (slated for deletion)
|
|
140
|
+
- [TODO.new/06](../TODO.new/06-audit-namespace-skeleton.md) — ucode audit port (must be complete)
|
|
141
|
+
- [TODO 07](07-fontisan-remove-ucd.md) — companion cleanup (UCD/UCDXML removal)
|
|
142
|
+
- [TODO 08](08-archive-private-bin-build.md) — pipeline migration
|