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,293 @@
|
|
|
1
|
+
---
|
|
2
|
+
unicode_version: 17.0.0
|
|
3
|
+
ucode_version: 0.1.1
|
|
4
|
+
generated_at: '2026-06-28T00:00:00Z'
|
|
5
|
+
default_sources:
|
|
6
|
+
- kind: fontist
|
|
7
|
+
label: noto-sans
|
|
8
|
+
priority: 1
|
|
9
|
+
license: OFL
|
|
10
|
+
provenance: Universal fallback for Latin/Cyrillic/Greek/symbols and any block without
|
|
11
|
+
a specialist entry below
|
|
12
|
+
map:
|
|
13
|
+
CJK_Unified_Ideographs_Extension_A:
|
|
14
|
+
sources:
|
|
15
|
+
- kind: path
|
|
16
|
+
label: FSung-2
|
|
17
|
+
priority: 1
|
|
18
|
+
license: OFL
|
|
19
|
+
path: "~/Downloads/全宋體/FSung-2.ttf"
|
|
20
|
+
- kind: fontist
|
|
21
|
+
label: noto-sans-cjk-jp
|
|
22
|
+
priority: 99
|
|
23
|
+
CJK_Unified_Ideographs:
|
|
24
|
+
sources:
|
|
25
|
+
- kind: path
|
|
26
|
+
label: FSung-1
|
|
27
|
+
priority: 1
|
|
28
|
+
license: OFL
|
|
29
|
+
provenance: Taiwan MOE 全宋體; covers U+4E00..U+9FFF core
|
|
30
|
+
path: "~/Downloads/全宋體/FSung-1.ttf"
|
|
31
|
+
- kind: fontist
|
|
32
|
+
label: noto-sans-cjk-jp
|
|
33
|
+
priority: 99
|
|
34
|
+
provenance: Catch-all fallback for CJK codepoints FSung misses
|
|
35
|
+
Sidetic:
|
|
36
|
+
sources:
|
|
37
|
+
- kind: fontist
|
|
38
|
+
label: lentariso
|
|
39
|
+
priority: 1
|
|
40
|
+
license: OFL
|
|
41
|
+
provenance: Lentariso >=1.029 (github.com/Bry10022/Lentariso); 26/26 cmap-verified
|
|
42
|
+
- kind: fontist
|
|
43
|
+
label: noto-sans-sidetic
|
|
44
|
+
priority: 2
|
|
45
|
+
Sharada_Supplement:
|
|
46
|
+
sources:
|
|
47
|
+
- kind: fontist
|
|
48
|
+
label: noto-sans-sharada
|
|
49
|
+
priority: 1
|
|
50
|
+
license: OFL
|
|
51
|
+
Tolong_Siki:
|
|
52
|
+
sources:
|
|
53
|
+
- kind: fontist
|
|
54
|
+
label: noto-sans-tolong-siki
|
|
55
|
+
priority: 1
|
|
56
|
+
license: OFL
|
|
57
|
+
Egyptian_Hieroglyphs:
|
|
58
|
+
sources:
|
|
59
|
+
- kind: path
|
|
60
|
+
label: UniHieroglyphica
|
|
61
|
+
priority: 1
|
|
62
|
+
license: OFL
|
|
63
|
+
provenance: suignard.com; authoritative for Egyptian Hieroglyphs
|
|
64
|
+
path: data/fonts/UniHieroglyphica.ttf
|
|
65
|
+
Egyptian_Hieroglyph_Format_Controls:
|
|
66
|
+
sources:
|
|
67
|
+
- kind: path
|
|
68
|
+
label: Egyptian-Text
|
|
69
|
+
priority: 1
|
|
70
|
+
license: OFL
|
|
71
|
+
provenance: microsoft/font-tools; OFL
|
|
72
|
+
path: data/fonts/EgyptianText-Regular.ttf
|
|
73
|
+
Egyptian_Hieroglyphs_Extended-A:
|
|
74
|
+
sources:
|
|
75
|
+
- kind: path
|
|
76
|
+
label: UniHieroglyphica
|
|
77
|
+
priority: 1
|
|
78
|
+
license: OFL
|
|
79
|
+
path: data/fonts/UniHieroglyphica.ttf
|
|
80
|
+
Beria_Erfe:
|
|
81
|
+
sources:
|
|
82
|
+
- kind: fontist
|
|
83
|
+
label: kedebideri
|
|
84
|
+
priority: 1
|
|
85
|
+
license: OFL
|
|
86
|
+
provenance: Kedebideri 3.001 (software.sil.org/kedebideri); 50/50 cmap-verified
|
|
87
|
+
Tangut:
|
|
88
|
+
sources:
|
|
89
|
+
- kind: fontist
|
|
90
|
+
label: noto-sans-tangut
|
|
91
|
+
priority: 1
|
|
92
|
+
license: OFL
|
|
93
|
+
Tangut_Components:
|
|
94
|
+
sources:
|
|
95
|
+
- kind: fontist
|
|
96
|
+
label: noto-sans-tangut
|
|
97
|
+
priority: 1
|
|
98
|
+
license: OFL
|
|
99
|
+
Tangut_Supplement:
|
|
100
|
+
sources:
|
|
101
|
+
- kind: fontist
|
|
102
|
+
label: noto-sans-tangut
|
|
103
|
+
priority: 1
|
|
104
|
+
license: OFL
|
|
105
|
+
Symbols_for_Legacy_Computing_Supplement:
|
|
106
|
+
sources:
|
|
107
|
+
- kind: fontist
|
|
108
|
+
label: babelstone-pseudographica
|
|
109
|
+
priority: 1
|
|
110
|
+
license: OFL
|
|
111
|
+
provenance: BabelStone; partial Unicode 17 coverage
|
|
112
|
+
Miscellaneous_Symbols_Supplement:
|
|
113
|
+
sources:
|
|
114
|
+
- kind: fontist
|
|
115
|
+
label: noto-sans-symbols-2
|
|
116
|
+
priority: 1
|
|
117
|
+
license: OFL
|
|
118
|
+
Musical_Symbols:
|
|
119
|
+
sources:
|
|
120
|
+
- kind: fontist
|
|
121
|
+
label: noto-music
|
|
122
|
+
priority: 1
|
|
123
|
+
license: OFL
|
|
124
|
+
Tai_Yo:
|
|
125
|
+
sources:
|
|
126
|
+
- kind: path
|
|
127
|
+
label: NotoSerifTaiYo
|
|
128
|
+
priority: 1
|
|
129
|
+
license: OFL
|
|
130
|
+
provenance: translationcommons.org; proven via correlate-v4 Pillar 2 extraction
|
|
131
|
+
path: data/fonts/NotoSerifTaiYo.ttf
|
|
132
|
+
Adlam:
|
|
133
|
+
sources:
|
|
134
|
+
- kind: path
|
|
135
|
+
label: NotoSansAdlam
|
|
136
|
+
priority: 1
|
|
137
|
+
license: OFL
|
|
138
|
+
provenance: Noto Sans Adlam fixture; 88/88 cmap-verified at Unicode 16 baseline
|
|
139
|
+
path: spec/fixtures/fonts/NotoSansAdlam-Regular.ttf
|
|
140
|
+
Alchemical_Symbols:
|
|
141
|
+
sources:
|
|
142
|
+
- kind: fontist
|
|
143
|
+
label: noto-sans-symbols
|
|
144
|
+
priority: 1
|
|
145
|
+
license: OFL
|
|
146
|
+
- kind: fontist
|
|
147
|
+
label: symbola
|
|
148
|
+
priority: 2
|
|
149
|
+
Supplemental_Arrows-C:
|
|
150
|
+
sources:
|
|
151
|
+
- kind: fontist
|
|
152
|
+
label: symbola
|
|
153
|
+
priority: 1
|
|
154
|
+
CJK_Unified_Ideographs_Extension_B:
|
|
155
|
+
sources:
|
|
156
|
+
- kind: path
|
|
157
|
+
label: FSung-2
|
|
158
|
+
priority: 1
|
|
159
|
+
license: OFL
|
|
160
|
+
path: "~/Downloads/全宋體/FSung-2.ttf"
|
|
161
|
+
- kind: fontist
|
|
162
|
+
label: noto-sans-cjk-jp
|
|
163
|
+
priority: 99
|
|
164
|
+
CJK_Unified_Ideographs_Extension_C:
|
|
165
|
+
sources:
|
|
166
|
+
- kind: path
|
|
167
|
+
label: FSung-2
|
|
168
|
+
priority: 1
|
|
169
|
+
license: OFL
|
|
170
|
+
path: "~/Downloads/全宋體/FSung-2.ttf"
|
|
171
|
+
- kind: fontist
|
|
172
|
+
label: noto-sans-cjk-jp
|
|
173
|
+
priority: 99
|
|
174
|
+
CJK_Unified_Ideographs_Extension_D:
|
|
175
|
+
sources:
|
|
176
|
+
- kind: path
|
|
177
|
+
label: FSung-2
|
|
178
|
+
priority: 1
|
|
179
|
+
license: OFL
|
|
180
|
+
path: "~/Downloads/全宋體/FSung-2.ttf"
|
|
181
|
+
- kind: fontist
|
|
182
|
+
label: noto-sans-cjk-jp
|
|
183
|
+
priority: 99
|
|
184
|
+
CJK_Unified_Ideographs_Extension_E:
|
|
185
|
+
sources:
|
|
186
|
+
- kind: path
|
|
187
|
+
label: FSung-3
|
|
188
|
+
priority: 1
|
|
189
|
+
license: OFL
|
|
190
|
+
path: "~/Downloads/全宋體/FSung-3.ttf"
|
|
191
|
+
- kind: fontist
|
|
192
|
+
label: noto-sans-cjk-jp
|
|
193
|
+
priority: 99
|
|
194
|
+
CJK_Unified_Ideographs_Extension_F:
|
|
195
|
+
sources:
|
|
196
|
+
- kind: path
|
|
197
|
+
label: FSung-3
|
|
198
|
+
priority: 1
|
|
199
|
+
license: OFL
|
|
200
|
+
path: "~/Downloads/全宋體/FSung-3.ttf"
|
|
201
|
+
- kind: fontist
|
|
202
|
+
label: noto-sans-cjk-jp
|
|
203
|
+
priority: 99
|
|
204
|
+
CJK_Unified_Ideographs_Extension_I:
|
|
205
|
+
sources:
|
|
206
|
+
- kind: path
|
|
207
|
+
label: FSung-3
|
|
208
|
+
priority: 1
|
|
209
|
+
license: OFL
|
|
210
|
+
path: "~/Downloads/全宋體/FSung-3.ttf"
|
|
211
|
+
CJK_Unified_Ideographs_Extension_G:
|
|
212
|
+
sources:
|
|
213
|
+
- kind: path
|
|
214
|
+
label: FSung-3
|
|
215
|
+
priority: 1
|
|
216
|
+
license: OFL
|
|
217
|
+
path: "~/Downloads/全宋體/FSung-3.ttf"
|
|
218
|
+
- kind: fontist
|
|
219
|
+
label: noto-sans-cjk-jp
|
|
220
|
+
priority: 99
|
|
221
|
+
CJK_Unified_Ideographs_Extension_H:
|
|
222
|
+
sources:
|
|
223
|
+
- kind: path
|
|
224
|
+
label: FSung-3
|
|
225
|
+
priority: 1
|
|
226
|
+
license: OFL
|
|
227
|
+
provenance: FSung-3 cmap-verified 4192/4192 for Ext H
|
|
228
|
+
path: "~/Downloads/全宋體/FSung-3.ttf"
|
|
229
|
+
CJK_Unified_Ideographs_Extension_J:
|
|
230
|
+
sources:
|
|
231
|
+
- kind: path
|
|
232
|
+
label: FSung-3
|
|
233
|
+
priority: 1
|
|
234
|
+
license: OFL
|
|
235
|
+
provenance: FSung-3 cmap-verified 4298/4298 for Ext J (Unicode 17 new block)
|
|
236
|
+
path: "~/Downloads/全宋體/FSung-3.ttf"
|
|
237
|
+
Egyptian_Hieroglyphs_Extended-B:
|
|
238
|
+
sources:
|
|
239
|
+
- kind: fontist
|
|
240
|
+
label: UniHieroglyphica
|
|
241
|
+
priority: 1
|
|
242
|
+
license: OFL
|
|
243
|
+
provenance: suignard.com/UniHieroglyphica v16; authoritative for Egyptian Hieroglyphs
|
|
244
|
+
Extended-B (UC17 new block)
|
|
245
|
+
Arabic_Extended-B:
|
|
246
|
+
sources:
|
|
247
|
+
- kind: fontist
|
|
248
|
+
label: noto-sans-arabic
|
|
249
|
+
priority: 1
|
|
250
|
+
license: OFL
|
|
251
|
+
provenance: UC17 additions to Arabic Extended-B
|
|
252
|
+
Arabic_Extended-C:
|
|
253
|
+
sources:
|
|
254
|
+
- kind: fontist
|
|
255
|
+
label: noto-sans-arabic
|
|
256
|
+
priority: 1
|
|
257
|
+
license: OFL
|
|
258
|
+
provenance: Arabic Extended-C (UC17 new block)
|
|
259
|
+
Telugu:
|
|
260
|
+
sources:
|
|
261
|
+
- kind: fontist
|
|
262
|
+
label: noto-sans-telugu
|
|
263
|
+
priority: 1
|
|
264
|
+
license: OFL
|
|
265
|
+
provenance: UC17 Telugu addition
|
|
266
|
+
Kannada:
|
|
267
|
+
sources:
|
|
268
|
+
- kind: fontist
|
|
269
|
+
label: noto-sans-kannada
|
|
270
|
+
priority: 1
|
|
271
|
+
license: OFL
|
|
272
|
+
provenance: UC17 Kannada addition
|
|
273
|
+
Chess_Symbols:
|
|
274
|
+
sources:
|
|
275
|
+
- kind: fontist
|
|
276
|
+
label: noto-sans-symbols-2
|
|
277
|
+
priority: 1
|
|
278
|
+
license: OFL
|
|
279
|
+
provenance: UC17 additions to Chess Symbols
|
|
280
|
+
Transport_and_Map_Symbols:
|
|
281
|
+
sources:
|
|
282
|
+
- kind: fontist
|
|
283
|
+
label: noto-sans-symbols-2
|
|
284
|
+
priority: 1
|
|
285
|
+
license: OFL
|
|
286
|
+
provenance: UC17 additions to Transport and Map Symbols
|
|
287
|
+
Symbols_and_Pictographs_Extended-A:
|
|
288
|
+
sources:
|
|
289
|
+
- kind: fontist
|
|
290
|
+
label: noto-sans-symbols-2
|
|
291
|
+
priority: 1
|
|
292
|
+
license: OFL
|
|
293
|
+
provenance: UC17 additions to Symbols and Pictographs Extended-A
|
|
@@ -3,55 +3,72 @@
|
|
|
3
3
|
module Ucode
|
|
4
4
|
module Audit
|
|
5
5
|
# Produces one {Models::Audit::BlockSummary} per touched Unicode block
|
|
6
|
-
# for a font's cmap codepoint set, compared against a
|
|
7
|
-
#
|
|
6
|
+
# for a font's cmap codepoint set, compared against a
|
|
7
|
+
# {CoverageReference}.
|
|
8
8
|
#
|
|
9
|
-
# Pure transformation: takes
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# Pure transformation: takes a reference + the font's codepoint list,
|
|
10
|
+
# returns BlockSummary[]. No I/O beyond the reference's lookups, no
|
|
11
|
+
# mutation of inputs.
|
|
12
12
|
#
|
|
13
|
-
# The "assigned" set for a block
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
13
|
+
# The "assigned" set for a block comes from
|
|
14
|
+
# `reference.entries_for_block(name)`. For a {UcdOnlyReference}
|
|
15
|
+
# that's every codepoint in the block's UCD ranges. For a
|
|
16
|
+
# {UniversalSetReference} it's every codepoint the universal glyph
|
|
17
|
+
# set built a glyph for in that block — each entry carries tier +
|
|
18
|
+
# source provenance that gets attached to the missing-codepoint
|
|
19
|
+
# list (TODO 25).
|
|
17
20
|
class BlockAggregator
|
|
18
|
-
# @param
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
# @param reference [CoverageReference, Ucode::Database, nil]
|
|
22
|
+
# pluggable baseline. For backwards compatibility a raw
|
|
23
|
+
# Ucode::Database is still accepted and wrapped in a
|
|
24
|
+
# {UcdOnlyReference} at construction time. When nil, #call
|
|
25
|
+
# returns an empty array.
|
|
26
|
+
def initialize(reference)
|
|
27
|
+
@reference = coerce_reference(reference)
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
# @param codepoints [Enumerable<Integer>]
|
|
26
31
|
# @return [Array<Models::Audit::BlockSummary>] sorted by first_cp
|
|
27
32
|
def call(codepoints)
|
|
28
|
-
return [] if @
|
|
33
|
+
return [] if @reference.nil? || codepoints.empty?
|
|
29
34
|
|
|
30
35
|
grouped = group_by_block(codepoints)
|
|
31
|
-
grouped.
|
|
36
|
+
grouped.filter_map { |name, covered| build_summary(name, covered) }
|
|
32
37
|
.sort_by(&:first_cp)
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
private
|
|
36
41
|
|
|
42
|
+
def coerce_reference(input)
|
|
43
|
+
return nil if input.nil?
|
|
44
|
+
return input if input.is_a?(CoverageReference)
|
|
45
|
+
|
|
46
|
+
UcdOnlyReference.new(database: input)
|
|
47
|
+
end
|
|
48
|
+
|
|
37
49
|
def group_by_block(codepoints)
|
|
38
50
|
codepoints.each_with_object(Hash.new { |h, k| h[k] = [] }) do |cp, acc|
|
|
39
|
-
name =
|
|
51
|
+
name = block_name_for(cp)
|
|
40
52
|
acc[name] << cp if name
|
|
41
53
|
end
|
|
42
54
|
end
|
|
43
55
|
|
|
56
|
+
def block_name_for(codepoint)
|
|
57
|
+
@reference.block_name_for(codepoint)
|
|
58
|
+
end
|
|
59
|
+
|
|
44
60
|
def build_summary(name, covered_cps)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
first_cp =
|
|
49
|
-
last_cp =
|
|
50
|
-
assigned_set =
|
|
61
|
+
entries = @reference.entries_for_block(name)
|
|
62
|
+
return nil if entries.empty?
|
|
63
|
+
|
|
64
|
+
first_cp = entries.first.codepoint
|
|
65
|
+
last_cp = entries.last.codepoint
|
|
66
|
+
assigned_set = entries.to_set(&:codepoint)
|
|
51
67
|
covered_set = covered_cps.to_set & assigned_set
|
|
52
68
|
missing_set = assigned_set - covered_set
|
|
69
|
+
missing_sorted = missing_set.sort
|
|
53
70
|
|
|
54
|
-
|
|
71
|
+
kwargs = {
|
|
55
72
|
name: name,
|
|
56
73
|
first_cp: first_cp,
|
|
57
74
|
last_cp: last_cp,
|
|
@@ -65,14 +82,25 @@ module Ucode
|
|
|
65
82
|
covered_count: covered_set.size,
|
|
66
83
|
total_assigned: assigned_set.size,
|
|
67
84
|
),
|
|
68
|
-
missing_codepoints:
|
|
85
|
+
missing_codepoints: missing_sorted,
|
|
69
86
|
covered_codepoints: covered_set.sort,
|
|
70
|
-
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
provenance = @reference.provenance_for(missing_sorted)
|
|
90
|
+
kwargs[:missing_codepoint_provenance] = provenance_rows(missing_sorted, provenance) if provenance
|
|
91
|
+
|
|
92
|
+
Models::Audit::BlockSummary.new(**kwargs)
|
|
71
93
|
end
|
|
72
94
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
def provenance_rows(codepoints, rows)
|
|
96
|
+
return [] if rows.nil? || rows.empty?
|
|
97
|
+
|
|
98
|
+
codepoints.zip(rows).map do |cp, row|
|
|
99
|
+
Models::Audit::CodepointProvenance.new(
|
|
100
|
+
codepoint: cp,
|
|
101
|
+
tier: row[:tier],
|
|
102
|
+
source: row[:source],
|
|
103
|
+
)
|
|
76
104
|
end
|
|
77
105
|
end
|
|
78
106
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
require "ucode/repo/atomic_writes"
|
|
7
|
+
require "ucode/audit/browser"
|
|
8
|
+
require "ucode/audit/browser/template"
|
|
9
|
+
require "ucode/audit/emitter/paths"
|
|
10
|
+
require "ucode/audit/emitter/index_emitter"
|
|
11
|
+
|
|
12
|
+
module Ucode
|
|
13
|
+
module Audit
|
|
14
|
+
module Browser
|
|
15
|
+
# Renders one face's `index.html` — a fully self-contained
|
|
16
|
+
# browser page for one audited font.
|
|
17
|
+
#
|
|
18
|
+
# The page inlines the same JSON shape that {Emitter::IndexEmitter}
|
|
19
|
+
# writes to `index.json`, plus inlined CSS and JS. Opening the
|
|
20
|
+
# file via `file://` renders the overview immediately; lazy
|
|
21
|
+
# fetches of the per-block chunks (`blocks/<NAME>.json`,
|
|
22
|
+
# `codepoints/<NAME>.json`, `glyphs/U+XXXX.svg`) work when the
|
|
23
|
+
# directory is served over HTTP.
|
|
24
|
+
#
|
|
25
|
+
# Self-contained: no external CSS, no external JS, no CDN.
|
|
26
|
+
# Portable: the entire `<label>/` directory can be moved/served
|
|
27
|
+
# whole and the page still works.
|
|
28
|
+
#
|
|
29
|
+
# Two construction modes — pass exactly one of:
|
|
30
|
+
# - `report:` — a live {Models::Audit::AuditReport}. The JSON
|
|
31
|
+
# shape is derived via {Emitter::IndexEmitter#build_index}.
|
|
32
|
+
# Used by {Emitter::FaceDirectory} when emitting alongside
|
|
33
|
+
# the audit.
|
|
34
|
+
# - `overview_json:` — a pre-built JSON string of the overview
|
|
35
|
+
# shape. Used by {Commands::AuditBrowserCommand} when
|
|
36
|
+
# regenerating HTML from an existing `index.json`.
|
|
37
|
+
class FacePage
|
|
38
|
+
include Ucode::Repo::AtomicWrites
|
|
39
|
+
|
|
40
|
+
# @param report [Models::Audit::AuditReport, nil]
|
|
41
|
+
# @param overview_json [String, nil] pre-built overview JSON
|
|
42
|
+
# @param verbose [Boolean] when true, the rendered page
|
|
43
|
+
# advertises per-block codepoint detail chunks
|
|
44
|
+
# @param with_glyphs [Boolean] when true, the rendered page
|
|
45
|
+
# advertises that `glyphs/U+XXXX.svg` chunks exist
|
|
46
|
+
# @param universal_set_root [String, Pathname, nil] when both
|
|
47
|
+
# this and `face_dir:` are present and the root exists, the
|
|
48
|
+
# inlined overview JSON carries a `universal_set` section
|
|
49
|
+
# with relative paths. The face browser JS uses these to
|
|
50
|
+
# fetch universal-set glyphs for missing-codepoint chips.
|
|
51
|
+
# @param face_dir [String, Pathname, nil] destination face
|
|
52
|
+
# directory. Required when `universal_set_root:` is set and
|
|
53
|
+
# the caller wants relative paths resolved; otherwise
|
|
54
|
+
# optional. `write(face_dir)` overrides this.
|
|
55
|
+
def initialize(report: nil, overview_json: nil, verbose: false,
|
|
56
|
+
with_glyphs: false, universal_set_root: nil, face_dir: nil)
|
|
57
|
+
raise ArgumentError, "pass exactly one of report: / overview_json:" \
|
|
58
|
+
unless report.nil? ^ overview_json.nil?
|
|
59
|
+
|
|
60
|
+
@report = report
|
|
61
|
+
@overview_json = overview_json
|
|
62
|
+
@verbose = verbose
|
|
63
|
+
@with_glyphs = with_glyphs
|
|
64
|
+
@universal_set_root = universal_set_root
|
|
65
|
+
@face_dir = face_dir
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Write the rendered page to `<face_dir>/index.html`.
|
|
69
|
+
# @param face_dir [String, Pathname]
|
|
70
|
+
# @return [Boolean] true if written, false if skipped
|
|
71
|
+
def write(face_dir)
|
|
72
|
+
@face_dir = Pathname.new(face_dir)
|
|
73
|
+
write_atomic(@face_dir.join("index.html"), render)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Render the page as a string. Useful in tests.
|
|
77
|
+
# @return [String]
|
|
78
|
+
def render
|
|
79
|
+
Template.new(:face).render(
|
|
80
|
+
overview_json: overview_json,
|
|
81
|
+
page_title: page_title,
|
|
82
|
+
verbose: @verbose,
|
|
83
|
+
with_glyphs: @with_glyphs,
|
|
84
|
+
universal_set: universal_set_section,
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def overview_hash
|
|
91
|
+
return @overview_hash if @overview_hash
|
|
92
|
+
return JSON.parse(@overview_json) if @overview_json
|
|
93
|
+
|
|
94
|
+
@overview_hash = Ucode::Audit::Emitter::IndexEmitter.new.build_index(
|
|
95
|
+
@report,
|
|
96
|
+
universal_set_root: @face_dir ? @universal_set_root : nil,
|
|
97
|
+
face_dir: @face_dir,
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def overview_json
|
|
102
|
+
return @overview_json if @overview_json
|
|
103
|
+
|
|
104
|
+
JSON.generate(overview_hash)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def universal_set_section
|
|
108
|
+
overview_hash["universal_set"] || { "available" => false }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def page_title
|
|
112
|
+
@report ? report_title : derived_title
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def report_title
|
|
116
|
+
[@report.family_name, @report.subfamily_name].compact.join(" ")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def derived_title
|
|
120
|
+
font = JSON.parse(@overview_json)["font"] || {}
|
|
121
|
+
[font["family_name"], font["subfamily_name"]].compact.join(" ")
|
|
122
|
+
rescue JSON::ParserError
|
|
123
|
+
"ucode audit"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
require "ucode/audit/browser"
|
|
7
|
+
|
|
8
|
+
module Ucode
|
|
9
|
+
module Audit
|
|
10
|
+
module Browser
|
|
11
|
+
# Service that builds per-codepoint glyph panel data for the
|
|
12
|
+
# missing-glyph reporter (TODO 26).
|
|
13
|
+
#
|
|
14
|
+
# Two consumers:
|
|
15
|
+
#
|
|
16
|
+
# - {MissingGlyphPage} — standalone per-block gallery; calls
|
|
17
|
+
# {#to_hash} once per missing codepoint to inline SVG markup.
|
|
18
|
+
# - The face browser JS — via the `universal_set` section that
|
|
19
|
+
# {Emitter::IndexEmitter} embeds in `index.json`. The JS
|
|
20
|
+
# fetches `<glyphs_dir>/U+XXXX.svg` at runtime using those
|
|
21
|
+
# paths; this class doesn't need to be loaded client-side.
|
|
22
|
+
#
|
|
23
|
+
# Reads the universal-set manifest once and builds a
|
|
24
|
+
# codepoint → {Models::UniversalSetEntry} index so per-codepoint
|
|
25
|
+
# lookups are O(1). SVG markup is read on demand from
|
|
26
|
+
# `<universal_set_root>/glyphs/U+XXXX.svg`.
|
|
27
|
+
#
|
|
28
|
+
# When the universal-set root is `nil` or unreachable on disk,
|
|
29
|
+
# {#available?} returns false and {#to_hash} returns a minimal
|
|
30
|
+
# stub with `available: false`, `svg: nil`. Consumers render a
|
|
31
|
+
# text-only fallback in that case — the surrounding page still
|
|
32
|
+
# works.
|
|
33
|
+
class GlyphPanel
|
|
34
|
+
GLYPHS_DIRNAME = "glyphs"
|
|
35
|
+
MANIFEST_FILENAME = "manifest.json"
|
|
36
|
+
private_constant :GLYPHS_DIRNAME, :MANIFEST_FILENAME
|
|
37
|
+
|
|
38
|
+
# @param universal_set_root [String, Pathname, nil] root of the
|
|
39
|
+
# universal-set build (e.g. "output/universal_glyph_set").
|
|
40
|
+
# nil when no set is co-located.
|
|
41
|
+
def initialize(universal_set_root:)
|
|
42
|
+
@root = universal_set_root.nil? ? nil : Pathname.new(universal_set_root)
|
|
43
|
+
@available = set_available?
|
|
44
|
+
@entries_by_cp = @available ? build_entries_index : {}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Boolean] true when the universal-set root, manifest,
|
|
48
|
+
# and glyphs directory are all reachable on disk
|
|
49
|
+
def available?
|
|
50
|
+
@available
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param codepoint [Integer]
|
|
54
|
+
# @return [Hash] panel payload:
|
|
55
|
+
# - "codepoint" => Integer
|
|
56
|
+
# - "id" => "U+XXXX"
|
|
57
|
+
# - "available" => Boolean (per-codepoint glyph file exists)
|
|
58
|
+
# - "svg" => String markup, or nil when the SVG file
|
|
59
|
+
# is missing or the universal set is unavailable
|
|
60
|
+
# - "tier" => String (e.g. "tier-1"), or nil
|
|
61
|
+
# - "source" => String (e.g. "noto-sans"), or nil
|
|
62
|
+
def to_hash(codepoint)
|
|
63
|
+
{
|
|
64
|
+
"codepoint" => codepoint,
|
|
65
|
+
"id" => cp_id(codepoint),
|
|
66
|
+
"available" => glyph_available?(codepoint),
|
|
67
|
+
"svg" => read_svg(codepoint),
|
|
68
|
+
"tier" => entry_for(codepoint)&.tier,
|
|
69
|
+
"source" => entry_for(codepoint)&.source,
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
attr_reader :root
|
|
76
|
+
|
|
77
|
+
def set_available?
|
|
78
|
+
return false if root.nil?
|
|
79
|
+
|
|
80
|
+
root.directory? && manifest_path.exist? && glyphs_dir.directory?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def manifest_path
|
|
84
|
+
root.join(MANIFEST_FILENAME)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def glyphs_dir
|
|
88
|
+
root.join(GLYPHS_DIRNAME)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def glyph_path(codepoint)
|
|
92
|
+
glyphs_dir.join("#{cp_id(codepoint)}.svg")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def glyph_available?(codepoint)
|
|
96
|
+
return false unless @available
|
|
97
|
+
|
|
98
|
+
glyph_path(codepoint).exist?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def read_svg(codepoint)
|
|
102
|
+
return nil unless @available
|
|
103
|
+
|
|
104
|
+
path = glyph_path(codepoint)
|
|
105
|
+
path.exist? ? path.read : nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def entry_for(codepoint)
|
|
109
|
+
@entries_by_cp[codepoint]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_entries_index
|
|
113
|
+
hash = JSON.parse(manifest_path.read)
|
|
114
|
+
manifest = Ucode::Models::UniversalSetManifest.from_hash(hash)
|
|
115
|
+
manifest.entries.to_h { |e| [e.codepoint, e] }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def cp_id(codepoint)
|
|
119
|
+
format("U+%04X", codepoint.to_i)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|