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
data/lib/ucode/cli.rb
CHANGED
|
@@ -46,6 +46,24 @@ module Ucode
|
|
|
46
46
|
.fetch_charts(version, block_first_cps: cps, force: options[:force])
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
desc "fonts", "Download specialist Tier 1 fonts (config/specialist_fonts.yml)"
|
|
50
|
+
option :manifest, type: :string,
|
|
51
|
+
desc: "Override manifest path (default config/specialist_fonts.yml)"
|
|
52
|
+
option :label, type: :string, desc: "Fetch only this font by label"
|
|
53
|
+
option :allow_proprietary, type: :boolean, default: false,
|
|
54
|
+
desc: "Permit non-OFL licensed fonts"
|
|
55
|
+
option :dry_run, type: :boolean, default: false,
|
|
56
|
+
desc: "Plan only; no network or disk writes"
|
|
57
|
+
def fonts
|
|
58
|
+
result = Commands::FetchCommand.new.fetch_fonts(
|
|
59
|
+
manifest_path: options[:manifest],
|
|
60
|
+
only_label: options[:label],
|
|
61
|
+
allow_proprietary: options[:allow_proprietary],
|
|
62
|
+
dry_run: options[:dry_run],
|
|
63
|
+
)
|
|
64
|
+
puts format_fonts_result(result)
|
|
65
|
+
end
|
|
66
|
+
|
|
49
67
|
private
|
|
50
68
|
|
|
51
69
|
def block_id_to_first_cp(id)
|
|
@@ -58,6 +76,11 @@ module Ucode
|
|
|
58
76
|
def format_result(result)
|
|
59
77
|
JSON.pretty_generate(result)
|
|
60
78
|
end
|
|
79
|
+
|
|
80
|
+
def format_fonts_result(result)
|
|
81
|
+
clean = result.merge(results: result[:results].map { |r| r.to_h.compact })
|
|
82
|
+
JSON.pretty_generate(clean)
|
|
83
|
+
end
|
|
61
84
|
end
|
|
62
85
|
|
|
63
86
|
desc "fetch", "Download UCD sources"
|
|
@@ -222,50 +245,333 @@ module Ucode
|
|
|
222
245
|
puts JSON.pretty_generate(result)
|
|
223
246
|
end
|
|
224
247
|
|
|
225
|
-
# ───────────────
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
248
|
+
# ─────────────── audit ───────────────
|
|
249
|
+
class Audit < Thor
|
|
250
|
+
desc "font PATH", "Audit a single font (or fontist formula name)"
|
|
251
|
+
option :label, type: :string, default: nil,
|
|
252
|
+
desc: "Output directory name (default: postscript_name)"
|
|
253
|
+
option :unicode_version, type: :string, default: nil
|
|
254
|
+
option :output, type: :string, default: "./output"
|
|
255
|
+
option :verbose, type: :boolean, default: false,
|
|
256
|
+
desc: "Emit per-codepoint detail chunks"
|
|
257
|
+
option :with_glyphs, type: :boolean, default: false,
|
|
258
|
+
desc: "Emit per-codepoint SVG chunks (no-op until TODO 20)"
|
|
259
|
+
option :brief, type: :boolean, default: false,
|
|
260
|
+
desc: "Cheap-extractor-only mode"
|
|
261
|
+
option :browse, type: :boolean, default: false,
|
|
262
|
+
desc: "Also write the self-contained HTML browser"
|
|
263
|
+
option :no_install, type: :boolean, default: false,
|
|
264
|
+
desc: "Don't auto-install missing fonts via fontist"
|
|
265
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
266
|
+
desc: "Path to universal-set manifest (or 'none'); " \
|
|
267
|
+
"default: output/universal_glyph_set/manifest.json " \
|
|
268
|
+
"if present, else UCD-only"
|
|
269
|
+
option :universal_set_root, type: :string, default: nil,
|
|
270
|
+
desc: "Path to universal-set build root (e.g. " \
|
|
271
|
+
"output/universal_glyph_set). Required for " \
|
|
272
|
+
"--with-missing-glyph-pages."
|
|
273
|
+
option :with_missing_glyph_pages, type: :boolean, default: false,
|
|
274
|
+
desc: "Emit per-block missing-glyph galleries " \
|
|
275
|
+
"(requires --browse + --universal-set-root)"
|
|
276
|
+
def font(path)
|
|
277
|
+
reference = Commands::Audit::ReferenceBuilder.build(
|
|
278
|
+
flag: options[:reference_universal_set],
|
|
279
|
+
version: options[:unicode_version],
|
|
280
|
+
)
|
|
281
|
+
result = Commands::Audit::FontCommand.new.call(
|
|
282
|
+
path,
|
|
283
|
+
label: options[:label],
|
|
284
|
+
unicode_version: options[:unicode_version],
|
|
285
|
+
verbose: options[:verbose],
|
|
286
|
+
with_glyphs: options[:with_glyphs],
|
|
287
|
+
brief: options[:brief],
|
|
288
|
+
output_root: options[:output],
|
|
289
|
+
browse: options[:browse],
|
|
290
|
+
install: !options[:no_install],
|
|
291
|
+
reference: reference,
|
|
292
|
+
universal_set_root: options[:universal_set_root],
|
|
293
|
+
with_missing_glyph_pages: options[:with_missing_glyph_pages],
|
|
294
|
+
)
|
|
295
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
desc "collection PATH", "Audit a TTC/OTC/dfong collection"
|
|
299
|
+
option :font_index, type: :numeric, default: nil,
|
|
300
|
+
desc: "Audit only face N (single-face output)"
|
|
301
|
+
option :label, type: :string, default: nil
|
|
302
|
+
option :unicode_version, type: :string, default: nil
|
|
303
|
+
option :output, type: :string, default: "./output"
|
|
304
|
+
option :verbose, type: :boolean, default: false
|
|
305
|
+
option :with_glyphs, type: :boolean, default: false
|
|
306
|
+
option :brief, type: :boolean, default: false
|
|
307
|
+
option :browse, type: :boolean, default: false
|
|
308
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
309
|
+
desc: "Path to universal-set manifest (or 'none')"
|
|
310
|
+
option :universal_set_root, type: :string, default: nil,
|
|
311
|
+
desc: "Path to universal-set build root"
|
|
312
|
+
option :with_missing_glyph_pages, type: :boolean, default: false,
|
|
313
|
+
desc: "Emit per-block missing-glyph galleries"
|
|
314
|
+
def collection(path)
|
|
315
|
+
reference = Commands::Audit::ReferenceBuilder.build(
|
|
316
|
+
flag: options[:reference_universal_set],
|
|
317
|
+
version: options[:unicode_version],
|
|
318
|
+
)
|
|
319
|
+
result = Commands::Audit::CollectionCommand.new.call(
|
|
320
|
+
path,
|
|
321
|
+
font_index: options[:font_index],
|
|
322
|
+
label: options[:label],
|
|
323
|
+
unicode_version: options[:unicode_version],
|
|
324
|
+
verbose: options[:verbose],
|
|
325
|
+
with_glyphs: options[:with_glyphs],
|
|
326
|
+
brief: options[:brief],
|
|
327
|
+
output_root: options[:output],
|
|
328
|
+
browse: options[:browse],
|
|
329
|
+
reference: reference,
|
|
330
|
+
universal_set_root: options[:universal_set_root],
|
|
331
|
+
with_missing_glyph_pages: options[:with_missing_glyph_pages],
|
|
332
|
+
)
|
|
333
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
desc "library DIR", "Walk a directory of fonts and audit each"
|
|
337
|
+
option :recursive, type: :boolean, default: false
|
|
338
|
+
option :unicode_version, type: :string, default: nil
|
|
339
|
+
option :output, type: :string, default: "./output"
|
|
340
|
+
option :verbose, type: :boolean, default: false
|
|
341
|
+
option :with_glyphs, type: :boolean, default: false
|
|
342
|
+
option :brief, type: :boolean, default: false
|
|
343
|
+
option :browse, type: :boolean, default: false,
|
|
344
|
+
desc: "Also write the library + face HTML browsers"
|
|
345
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
346
|
+
desc: "Path to universal-set manifest (or 'none')"
|
|
347
|
+
option :universal_set_root, type: :string, default: nil,
|
|
348
|
+
desc: "Path to universal-set build root"
|
|
349
|
+
option :with_missing_glyph_pages, type: :boolean, default: false,
|
|
350
|
+
desc: "Emit per-block missing-glyph galleries"
|
|
351
|
+
def library(dir)
|
|
352
|
+
reference = Commands::Audit::ReferenceBuilder.build(
|
|
353
|
+
flag: options[:reference_universal_set],
|
|
354
|
+
version: options[:unicode_version],
|
|
355
|
+
)
|
|
356
|
+
result = Commands::Audit::LibraryCommand.new.call(
|
|
357
|
+
dir,
|
|
358
|
+
recursive: options[:recursive],
|
|
359
|
+
unicode_version: options[:unicode_version],
|
|
360
|
+
verbose: options[:verbose],
|
|
361
|
+
with_glyphs: options[:with_glyphs],
|
|
362
|
+
brief: options[:brief],
|
|
363
|
+
output_root: options[:output],
|
|
364
|
+
browse: options[:browse],
|
|
365
|
+
reference: reference,
|
|
366
|
+
universal_set_root: options[:universal_set_root],
|
|
367
|
+
with_missing_glyph_pages: options[:with_missing_glyph_pages],
|
|
368
|
+
)
|
|
369
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
desc "compare LEFT RIGHT", "Diff two audits"
|
|
373
|
+
option :unicode_version, type: :string, default: nil
|
|
374
|
+
option :output, type: :string, default: nil,
|
|
375
|
+
desc: "Write text diff to file (default: stdout)"
|
|
376
|
+
def compare(left, right)
|
|
377
|
+
result = Commands::Audit::CompareCommand.new.call(
|
|
378
|
+
left, right,
|
|
379
|
+
unicode_version: options[:unicode_version],
|
|
380
|
+
output_file: options[:output],
|
|
381
|
+
)
|
|
382
|
+
if result.error
|
|
383
|
+
warn "compare failed: #{result.error}"
|
|
384
|
+
exit 1
|
|
385
|
+
elsif options[:output].nil?
|
|
386
|
+
puts result.text
|
|
387
|
+
else
|
|
388
|
+
puts "wrote #{options[:output]}"
|
|
389
|
+
end
|
|
390
|
+
end
|
|
233
391
|
|
|
234
|
-
|
|
392
|
+
desc "browser", "Regenerate HTML browsers from existing JSON audits"
|
|
393
|
+
option :input, type: :string, default: "./output/font_audit"
|
|
394
|
+
option :faces_only, type: :boolean, default: false
|
|
395
|
+
option :library_only, type: :boolean, default: false
|
|
396
|
+
def browser
|
|
397
|
+
result = Commands::Audit::BrowserCommand.new.call(
|
|
398
|
+
input: options[:input],
|
|
399
|
+
faces_only: options[:faces_only],
|
|
400
|
+
library_only: options[:library_only],
|
|
401
|
+
)
|
|
402
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
403
|
+
end
|
|
235
404
|
|
|
236
|
-
|
|
237
|
-
Kedebideri=/tmp/kedebideri/Kedebideri-3.001/Kedebideri-Regular.ttf
|
|
405
|
+
private
|
|
238
406
|
|
|
239
|
-
|
|
407
|
+
def result_to_h(result)
|
|
408
|
+
return { error: result.error } if result.error
|
|
409
|
+
|
|
410
|
+
result.to_h.compact.transform_values do |v|
|
|
411
|
+
v.is_a?(Struct) ? v.to_h : v
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
desc "audit", "Audit font coverage against the Unicode baseline"
|
|
417
|
+
subcommand "audit", Audit
|
|
418
|
+
|
|
419
|
+
# ─────────────── universal-set ───────────────
|
|
420
|
+
class UniversalSetCmd < Thor
|
|
421
|
+
desc "build [VERSION]", "Materialize the universal glyph set (one SVG per assigned codepoint)"
|
|
422
|
+
option :to, type: :string, default: "./output/universal_glyph_set",
|
|
423
|
+
desc: "Output directory"
|
|
424
|
+
option :source_config, type: :string, default: nil,
|
|
425
|
+
desc: "Path to a Tier 1 source config YAML " \
|
|
426
|
+
"(default: config/unicode17_universal_glyph_set.yml)"
|
|
427
|
+
option :block, type: :string, default: nil,
|
|
428
|
+
desc: "Limit the build to one block (canonical underscore form)"
|
|
429
|
+
option :parallel, type: :numeric, default: nil,
|
|
430
|
+
desc: "Worker pool size (default: Ucode.configuration.parallel_workers)"
|
|
431
|
+
def build(version = nil)
|
|
432
|
+
result = Commands::UniversalSet::BuildCommand.new.call(
|
|
433
|
+
version,
|
|
434
|
+
output_root: options[:to],
|
|
435
|
+
source_config_path: options[:source_config],
|
|
436
|
+
block_filter: options[:block],
|
|
437
|
+
parallel_workers: options[:parallel] || Ucode.configuration.parallel_workers,
|
|
438
|
+
)
|
|
439
|
+
puts JSON.pretty_generate(result)
|
|
440
|
+
rescue Ucode::UniversalSetPreBuildError => e
|
|
441
|
+
warn "pre-build validation failed:"
|
|
442
|
+
warn JSON.pretty_generate(e.context)
|
|
443
|
+
exit 1
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
desc "pre-check [VERSION]", "Validate source config + fonts + coverage assertion before a build"
|
|
447
|
+
option :source_config, type: :string, default: nil,
|
|
448
|
+
desc: "Path to a Tier 1 source config YAML"
|
|
449
|
+
def pre_check(version = nil)
|
|
450
|
+
report = Commands::UniversalSet::PreCheckCommand.new.call(
|
|
451
|
+
version,
|
|
452
|
+
source_config_path: options[:source_config],
|
|
453
|
+
)
|
|
454
|
+
puts JSON.pretty_generate(report.to_h)
|
|
455
|
+
rescue Ucode::UniversalSetPreBuildError => e
|
|
456
|
+
warn "pre-build validation failed:"
|
|
457
|
+
warn JSON.pretty_generate(e.context)
|
|
458
|
+
exit 1
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
desc "report [VERSION]", "Emit per-tier / per-block / gaps reports from an existing manifest"
|
|
462
|
+
option :from, type: :string, default: "./output/universal_glyph_set",
|
|
463
|
+
desc: "Output directory holding manifest.json"
|
|
464
|
+
def report(version = nil)
|
|
465
|
+
result = Commands::UniversalSet::ReportCommand.new.call(
|
|
466
|
+
version,
|
|
467
|
+
output_root: options[:from],
|
|
468
|
+
)
|
|
469
|
+
puts JSON.pretty_generate(result)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
desc "validate [OUTPUT_ROOT]", "Run post-build structural validation on a manifest + glyphs dir"
|
|
473
|
+
option :version, type: :string, default: nil,
|
|
474
|
+
desc: "Unicode version (stamps the report; defaults to manifest)"
|
|
475
|
+
def validate(output_root = "./output/universal_glyph_set")
|
|
476
|
+
result = Commands::UniversalSet::ValidateCommand.new.call(
|
|
477
|
+
output_root,
|
|
478
|
+
version_intent: options[:version],
|
|
479
|
+
)
|
|
480
|
+
puts JSON.pretty_generate(result)
|
|
481
|
+
exit 1 unless result[:passed]
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
desc "universal-set", "Build and inspect the universal glyph set reference"
|
|
486
|
+
subcommand "universal-set", UniversalSetCmd
|
|
487
|
+
|
|
488
|
+
# ─────────────── release ───────────────
|
|
489
|
+
desc "release", "Assemble the fontist.org release tree from per-formula audits"
|
|
490
|
+
long_desc <<~LONG
|
|
491
|
+
Walks a directory of per-formula font subdirectories and produces
|
|
492
|
+
the fontist.org-consumable release tree at
|
|
493
|
+
`<output>/font_audit_release/`. The release tree contains:
|
|
494
|
+
|
|
495
|
+
audit/<slug>/<postscript_name>/ — per-face audit subtrees
|
|
496
|
+
universal_glyph_set/ — pre-staged universal set
|
|
497
|
+
library.json — formula + face card index
|
|
498
|
+
manifest.json — versions, sha256s, totals
|
|
499
|
+
|
|
500
|
+
The universal-set directory is NOT copied by this command; the
|
|
501
|
+
CI collector is expected to pre-stage it under
|
|
502
|
+
`<output>/font_audit_release/universal_glyph_set/`.
|
|
240
503
|
LONG
|
|
241
|
-
option :
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
504
|
+
option :from, type: :string, required: true,
|
|
505
|
+
desc: "Directory of per-formula font subdirectories"
|
|
506
|
+
option :output, type: :string, default: "./output",
|
|
507
|
+
desc: "Parent of the release root"
|
|
508
|
+
option :universal_set, type: :string, default: nil,
|
|
509
|
+
desc: "Path to the universal_glyph_set directory " \
|
|
510
|
+
"(default: <release_root>/universal_glyph_set)"
|
|
511
|
+
option :unicode_version, type: :string, default: nil
|
|
512
|
+
option :brief, type: :boolean, default: false
|
|
513
|
+
option :browse, type: :boolean, default: true,
|
|
514
|
+
desc: "Also write per-face HTML browsers + missing-glyph pages"
|
|
515
|
+
option :source_config_sha256, type: :string, default: nil,
|
|
516
|
+
desc: "sha256 of the Tier 1 source-config YAML"
|
|
517
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
518
|
+
desc: "Path to universal-set manifest (or 'none') " \
|
|
519
|
+
"for the per-face coverage reference"
|
|
520
|
+
def release
|
|
521
|
+
reference = Commands::Audit::ReferenceBuilder.build(
|
|
522
|
+
flag: options[:reference_universal_set],
|
|
523
|
+
version: options[:unicode_version],
|
|
524
|
+
)
|
|
525
|
+
result = Commands::ReleaseCommand.new.call(
|
|
526
|
+
from: options[:from],
|
|
527
|
+
output_root: options[:output],
|
|
528
|
+
universal_set_root: options[:universal_set],
|
|
529
|
+
unicode_version: options[:unicode_version],
|
|
530
|
+
brief: options[:brief],
|
|
531
|
+
browse: options[:browse],
|
|
532
|
+
source_config_sha256: options[:source_config_sha256],
|
|
533
|
+
reference: reference,
|
|
534
|
+
)
|
|
535
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
536
|
+
end
|
|
246
537
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
538
|
+
# ─────────────── block-feed ───────────────
|
|
539
|
+
desc "block-feed", "Emit per-block Unicode data feed from ucode output"
|
|
540
|
+
long_desc <<~LONG
|
|
541
|
+
Translates ucode's canonical output tree into a compact per-block
|
|
542
|
+
Unicode data feed:
|
|
543
|
+
|
|
544
|
+
<target>/unicode-blocks.json
|
|
545
|
+
<target>/unicode-version.json
|
|
546
|
+
<target>/unicode/blocks/<slug>.json
|
|
547
|
+
|
|
548
|
+
Each per-block file contains the codepoints in that block with
|
|
549
|
+
their compact metadata (name, general category, script, combining
|
|
550
|
+
class, bidi class, mirrored flag). Block slugs are derived from
|
|
551
|
+
the block name via the standard slug algorithm.
|
|
552
|
+
LONG
|
|
553
|
+
option :ucode_output, type: :string, default: "./output",
|
|
554
|
+
desc: "ucode's output/ directory"
|
|
555
|
+
option :target, type: :string, default: "./output/block-feed",
|
|
556
|
+
desc: "Target directory for emitted files"
|
|
557
|
+
option :unicode_version, type: :string, default: nil,
|
|
558
|
+
desc: "UCD version stamp (default: from manifest)"
|
|
559
|
+
def block_feed
|
|
560
|
+
result = Commands::BlockFeedCommand.new.call(
|
|
561
|
+
ucode_output_root: options[:ucode_output],
|
|
562
|
+
block_feed_output_root: options[:target],
|
|
563
|
+
unicode_version: options[:unicode_version],
|
|
251
564
|
)
|
|
252
|
-
puts JSON.pretty_generate(
|
|
565
|
+
puts JSON.pretty_generate(result.to_h)
|
|
253
566
|
end
|
|
254
567
|
|
|
255
568
|
private
|
|
256
569
|
|
|
257
570
|
def result_to_h(result)
|
|
258
|
-
if result.error
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
spec: result.spec,
|
|
263
|
-
label: result.located.name,
|
|
264
|
-
source: result.located.path.to_s,
|
|
265
|
-
via: result.located.via,
|
|
266
|
-
output_path: result.output_path.to_s,
|
|
267
|
-
complete_blocks: result.complete_blocks,
|
|
268
|
-
}
|
|
571
|
+
return { error: result.error } if result.error
|
|
572
|
+
|
|
573
|
+
result.to_h.compact.transform_values do |v|
|
|
574
|
+
v.is_a?(Struct) ? v.to_h : v
|
|
269
575
|
end
|
|
270
576
|
end
|
|
271
577
|
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
require "ucode/audit"
|
|
7
|
+
require "ucode/audit/browser"
|
|
8
|
+
require "ucode/audit/emitter/paths"
|
|
9
|
+
|
|
10
|
+
module Ucode
|
|
11
|
+
module Commands
|
|
12
|
+
module Audit
|
|
13
|
+
# `ucode audit browser` — regenerate HTML browsers from existing
|
|
14
|
+
# JSON audits, without re-running extractors.
|
|
15
|
+
#
|
|
16
|
+
# Walks the audit root and rewrites only `.html` files. Useful
|
|
17
|
+
# when the audit ran without `--browse`, or after a CSS/JS
|
|
18
|
+
# template tweak. No JSON is rewritten.
|
|
19
|
+
#
|
|
20
|
+
# Scopes:
|
|
21
|
+
# - default: regenerate both library-level + all face pages.
|
|
22
|
+
# - `faces_only: true` — only per-face pages.
|
|
23
|
+
# - `library_only: true` — only the library-level page.
|
|
24
|
+
class BrowserCommand
|
|
25
|
+
FaceRegen = Struct.new(:label, :path, :written, keyword_init: true)
|
|
26
|
+
|
|
27
|
+
Result = Struct.new(:input, :library_html, :faces, :error,
|
|
28
|
+
keyword_init: true)
|
|
29
|
+
|
|
30
|
+
# @param input [String, Pathname] audit root path. Must be a
|
|
31
|
+
# directory containing either `<input>/index.json` (library
|
|
32
|
+
# root) or per-face subdirectories each with their own
|
|
33
|
+
# `index.json` (face root). Either way the root is treated
|
|
34
|
+
# as the library root.
|
|
35
|
+
# @param faces_only [Boolean]
|
|
36
|
+
# @param library_only [Boolean]
|
|
37
|
+
# @return [Result]
|
|
38
|
+
def call(input:, faces_only: false, library_only: false)
|
|
39
|
+
audit_root = Pathname.new(input)
|
|
40
|
+
|
|
41
|
+
library_html =
|
|
42
|
+
library_only || !faces_only ? write_library(audit_root) : nil
|
|
43
|
+
faces =
|
|
44
|
+
faces_only || !library_only ? write_faces(audit_root) : []
|
|
45
|
+
|
|
46
|
+
Result.new(input: audit_root.to_s, library_html: library_html&.to_s,
|
|
47
|
+
faces: faces)
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
Result.new(input: input.to_s, error: "#{e.class}: #{e.message}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# The library index.json sits at `<audit_root>/index.json`;
|
|
55
|
+
# the library index.html is written to the same directory.
|
|
56
|
+
# {Browser::LibraryPage#write} takes the output_root (one
|
|
57
|
+
# level up) so we pass `audit_root.parent`.
|
|
58
|
+
def write_library(audit_root)
|
|
59
|
+
index_json = audit_root.join("index.json")
|
|
60
|
+
return nil unless index_json.exist?
|
|
61
|
+
|
|
62
|
+
Ucode::Audit::Browser::LibraryPage.new(library_json: index_json.read)
|
|
63
|
+
.write(audit_root.parent)
|
|
64
|
+
Ucode::Audit::Emitter::Paths.library_html_path(audit_root.parent)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def write_faces(audit_root)
|
|
68
|
+
audit_root.children.select(&:directory?).filter_map do |face_dir|
|
|
69
|
+
json = face_dir.join("index.json")
|
|
70
|
+
next unless json.exist?
|
|
71
|
+
|
|
72
|
+
written = Ucode::Audit::Browser::FacePage.new(overview_json: json.read)
|
|
73
|
+
.write(face_dir)
|
|
74
|
+
FaceRegen.new(label: face_dir.basename.to_s,
|
|
75
|
+
path: face_dir.join("index.html").to_s,
|
|
76
|
+
written: written)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require "fontisan"
|
|
6
|
+
|
|
7
|
+
require "ucode/commands/audit/font_command"
|
|
8
|
+
require "ucode/audit/face_auditor"
|
|
9
|
+
|
|
10
|
+
module Ucode
|
|
11
|
+
module Commands
|
|
12
|
+
module Audit
|
|
13
|
+
# `ucode audit collection PATH` — explicit collection audit.
|
|
14
|
+
# Wraps {FontCommand} with two collection-specific behaviors:
|
|
15
|
+
#
|
|
16
|
+
# - Validates the source is actually a collection
|
|
17
|
+
# (TTC/OTC/dfong). Errors out otherwise.
|
|
18
|
+
# - Supports `font_index:` to audit only one face of the
|
|
19
|
+
# collection, producing a single-face tree.
|
|
20
|
+
#
|
|
21
|
+
# For unspecified collection options, delegates to FontCommand.
|
|
22
|
+
class CollectionCommand
|
|
23
|
+
# @param font_path [String, Pathname] must be a collection source.
|
|
24
|
+
# @param font_index [Integer, nil] if set, audit only this face.
|
|
25
|
+
# @param kwargs [Hash] forwarded to {FontCommand#call}.
|
|
26
|
+
# @return [FontCommand::Result] when auditing all faces, or a
|
|
27
|
+
# single-face variant when `font_index:` is set.
|
|
28
|
+
def call(font_path, font_index: nil, **kwargs)
|
|
29
|
+
raise CollectionRequiredError, font_path unless collection?(font_path)
|
|
30
|
+
return audit_single_face(font_path, font_index, kwargs) if font_index
|
|
31
|
+
|
|
32
|
+
font_command.call(font_path, **kwargs)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def collection?(path)
|
|
38
|
+
Fontisan::FontLoader.collection?(path.to_s)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def audit_single_face(font_path, index, kwargs)
|
|
42
|
+
output_root = kwargs.fetch(:output_root)
|
|
43
|
+
options = build_options(kwargs)
|
|
44
|
+
report = Ucode::Audit::FaceAuditor.new(font_path.to_s, options: options,
|
|
45
|
+
mode: mode_from(kwargs),
|
|
46
|
+
font_index: index,
|
|
47
|
+
reference: kwargs[:reference]).call
|
|
48
|
+
|
|
49
|
+
directory = Ucode::Audit::Emitter::FaceDirectory.new(
|
|
50
|
+
output_root: output_root,
|
|
51
|
+
verbose: kwargs.fetch(:verbose, false),
|
|
52
|
+
with_glyphs: kwargs.fetch(:with_glyphs, false),
|
|
53
|
+
emit_browser: kwargs.fetch(:browse, false),
|
|
54
|
+
universal_set_root: kwargs[:universal_set_root],
|
|
55
|
+
with_missing_glyph_pages: kwargs.fetch(:with_missing_glyph_pages, false),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
label = sanitize(kwargs[:label] || report.postscript_name || "face-#{index}")
|
|
59
|
+
face_dir = directory.emit_face(label: label, report: report)
|
|
60
|
+
|
|
61
|
+
FontCommand::Result.new(
|
|
62
|
+
spec: font_path.to_s,
|
|
63
|
+
label: label,
|
|
64
|
+
output_dir: face_dir.to_s,
|
|
65
|
+
faces: [FontCommand::FaceOutcome.new(
|
|
66
|
+
label: label,
|
|
67
|
+
postscript_name: report.postscript_name,
|
|
68
|
+
output_dir: face_dir.to_s,
|
|
69
|
+
)],
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_options(kwargs)
|
|
74
|
+
opts = {}
|
|
75
|
+
opts[:ucd_version] = kwargs[:unicode_version] if kwargs[:unicode_version]
|
|
76
|
+
opts[:audit_brief] = true if kwargs[:brief]
|
|
77
|
+
opts
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def mode_from(kwargs)
|
|
81
|
+
kwargs[:brief] ? :brief : :full
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def sanitize(name)
|
|
85
|
+
(name || "face").to_s.gsub(/[^A-Za-z0-9._-]/, "_")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def font_command
|
|
89
|
+
@font_command ||= FontCommand.new
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Raised by {CollectionCommand} when the input is not a
|
|
94
|
+
# collection source.
|
|
95
|
+
class CollectionRequiredError < StandardError
|
|
96
|
+
# @param path [String, Pathname]
|
|
97
|
+
def initialize(path)
|
|
98
|
+
super("#{path} is not a collection (TTC/OTC/dfong) source")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|