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,74 @@
|
|
|
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/library_emitter"
|
|
11
|
+
|
|
12
|
+
module Ucode
|
|
13
|
+
module Audit
|
|
14
|
+
module Browser
|
|
15
|
+
# Renders the library-level `index.html` — a card grid of all
|
|
16
|
+
# audited fonts in a library, with search/sort/filter controls.
|
|
17
|
+
#
|
|
18
|
+
# The page inlines the same JSON shape that
|
|
19
|
+
# {Emitter::LibraryEmitter} writes to `index.json`, plus inlined
|
|
20
|
+
# CSS and JS. Clicking a card navigates to that face's
|
|
21
|
+
# per-face browser (`<label>/index.html`).
|
|
22
|
+
#
|
|
23
|
+
# Self-contained: no external resources. The entire
|
|
24
|
+
# `output/font_audit/` tree is portable as a unit.
|
|
25
|
+
#
|
|
26
|
+
# Two construction modes — pass exactly one of:
|
|
27
|
+
# - `summary:` — a live {Models::Audit::LibrarySummary}. The
|
|
28
|
+
# JSON shape is derived via {Emitter::LibraryEmitter#build_index}.
|
|
29
|
+
# - `library_json:` — a pre-built JSON string of the library
|
|
30
|
+
# overview shape. Used by {Commands::AuditBrowserCommand}
|
|
31
|
+
# when regenerating HTML from an existing `index.json`.
|
|
32
|
+
class LibraryPage
|
|
33
|
+
include Ucode::Repo::AtomicWrites
|
|
34
|
+
|
|
35
|
+
# @param summary [Models::Audit::LibrarySummary, nil]
|
|
36
|
+
# @param library_json [String, nil] pre-built library overview JSON
|
|
37
|
+
def initialize(summary: nil, library_json: nil)
|
|
38
|
+
raise ArgumentError, "pass exactly one of summary: / library_json:" \
|
|
39
|
+
unless summary.nil? ^ library_json.nil?
|
|
40
|
+
|
|
41
|
+
@summary = summary
|
|
42
|
+
@library_json = library_json
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Write the rendered page to `<library_root>/index.html`.
|
|
46
|
+
# @param output_root [String, Pathname]
|
|
47
|
+
# @return [Boolean] true if written, false if skipped
|
|
48
|
+
def write(output_root)
|
|
49
|
+
path = Ucode::Audit::Emitter::Paths.library_html_path(output_root)
|
|
50
|
+
write_atomic(path, render)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Render the page as a string.
|
|
54
|
+
# @return [String]
|
|
55
|
+
def render
|
|
56
|
+
Template.new(:library).render(
|
|
57
|
+
library_json: library_json,
|
|
58
|
+
page_title: "ucode audit library",
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def library_json
|
|
65
|
+
@library_json ||=
|
|
66
|
+
begin
|
|
67
|
+
hash = Ucode::Audit::Emitter::LibraryEmitter.new.build_index(@summary)
|
|
68
|
+
JSON.generate(hash)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require "ucode/repo/atomic_writes"
|
|
6
|
+
require "ucode/audit/browser"
|
|
7
|
+
require "ucode/audit/browser/template"
|
|
8
|
+
require "ucode/audit/browser/glyph_panel"
|
|
9
|
+
require "ucode/audit/emitter/paths"
|
|
10
|
+
|
|
11
|
+
module Ucode
|
|
12
|
+
module Audit
|
|
13
|
+
module Browser
|
|
14
|
+
# Renders one standalone per-block HTML gallery of missing
|
|
15
|
+
# glyphs at `<face_dir>/missing/<BLOCK>.html` (TODO 26).
|
|
16
|
+
#
|
|
17
|
+
# Every touched block with at least one missing codepoint gets
|
|
18
|
+
# one page. The gallery is fully static — inlined CSS, no
|
|
19
|
+
# client-side fetch — so it works via `file://` and is what
|
|
20
|
+
# fontist.org can iframe or screenshot for the "what's missing"
|
|
21
|
+
# widget.
|
|
22
|
+
#
|
|
23
|
+
# SVGs are inlined from the co-located universal-set build
|
|
24
|
+
# (when {GlyphPanel#available?}). When the universal set is
|
|
25
|
+
# absent, the page renders codepoint IDs only — no errors, no
|
|
26
|
+
# broken-image placeholders.
|
|
27
|
+
#
|
|
28
|
+
# For very large missing-codepoint sets (CJK can be thousands),
|
|
29
|
+
# the page emits at most +page_size+ thumbnails inline; an
|
|
30
|
+
# overflow notice replaces the rest.
|
|
31
|
+
class MissingGlyphPage
|
|
32
|
+
include Ucode::Repo::AtomicWrites
|
|
33
|
+
|
|
34
|
+
DEFAULT_PAGE_SIZE = 500
|
|
35
|
+
|
|
36
|
+
# @param block_name [String] Unicode block name (verbatim)
|
|
37
|
+
# @param missing_codepoints [Array<Integer>] codepoints the
|
|
38
|
+
# font does not cover for this block
|
|
39
|
+
# @param glyph_panel [GlyphPanel] service that reads the
|
|
40
|
+
# universal-set manifest + glyphs dir
|
|
41
|
+
# @param page_size [Integer] max thumbnails emitted inline
|
|
42
|
+
def initialize(block_name:, missing_codepoints:, glyph_panel:,
|
|
43
|
+
page_size: DEFAULT_PAGE_SIZE)
|
|
44
|
+
@block_name = block_name
|
|
45
|
+
@missing_codepoints = missing_codepoints
|
|
46
|
+
@glyph_panel = glyph_panel
|
|
47
|
+
@page_size = page_size
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param face_dir [String, Pathname]
|
|
51
|
+
# @return [Boolean] true if written, false if skipped (identical)
|
|
52
|
+
def write(face_dir)
|
|
53
|
+
path = Ucode::Audit::Emitter::Paths.missing_glyph_page_under(face_dir, @block_name)
|
|
54
|
+
write_atomic(path, render)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [String] rendered HTML
|
|
58
|
+
def render
|
|
59
|
+
Template.new(:missing_glyph_page).render(
|
|
60
|
+
block_name: @block_name,
|
|
61
|
+
panels: panel_data,
|
|
62
|
+
visible_count: visible.size,
|
|
63
|
+
total_count: @missing_codepoints.size,
|
|
64
|
+
overflow_count: overflow_count,
|
|
65
|
+
universal_set_available: @glyph_panel.available?,
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def panel_data
|
|
72
|
+
visible.take(@page_size).map { |cp| @glyph_panel.to_hash(cp) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def visible
|
|
76
|
+
@missing_codepoints.sort
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def overflow_count
|
|
80
|
+
return 0 if @missing_codepoints.size <= @page_size
|
|
81
|
+
|
|
82
|
+
@missing_codepoints.size - @page_size
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
require "ucode/audit/browser"
|
|
7
|
+
|
|
8
|
+
module Ucode
|
|
9
|
+
module Audit
|
|
10
|
+
module Browser
|
|
11
|
+
# Minimal ERB wrapper for the browser templates.
|
|
12
|
+
#
|
|
13
|
+
# Reads an `.erb` template plus its sibling `.css` and `.js`
|
|
14
|
+
# assets from {Browser::TEMPLATE_DIR}, renders them with the
|
|
15
|
+
# supplied binding, and returns a complete HTML document.
|
|
16
|
+
#
|
|
17
|
+
# Templates are plain ERB — no partials, no layouts, no helpers
|
|
18
|
+
# beyond what the binding provides. The CSS/JS assets are
|
|
19
|
+
# inlined into the rendered HTML so the page is fully
|
|
20
|
+
# self-contained (no external requests).
|
|
21
|
+
class Template
|
|
22
|
+
# @param name [String, Symbol] template name without extension
|
|
23
|
+
# (e.g. `:face`, `:library`)
|
|
24
|
+
def initialize(name)
|
|
25
|
+
@name = name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param locals [Hash{Symbol=>Object}] variables exposed in the
|
|
29
|
+
# template binding
|
|
30
|
+
# @return [String] rendered HTML
|
|
31
|
+
def render(locals = {})
|
|
32
|
+
erb = ERB.new(read("#{@name}.html.erb"), trim_mode: "-")
|
|
33
|
+
erb.result_with_hash(locals.merge(
|
|
34
|
+
_css: read("#{@name}.css"),
|
|
35
|
+
_js: read("#{@name}.js"),
|
|
36
|
+
))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def read(filename)
|
|
42
|
+
Browser.const_get(:TEMPLATE_DIR).join(filename).read
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #fafafa;
|
|
3
|
+
--fg: #1a1a1a;
|
|
4
|
+
--muted: #5a5a5a;
|
|
5
|
+
--card-bg: #ffffff;
|
|
6
|
+
--border: #d8d8d8;
|
|
7
|
+
--accent: #2962ff;
|
|
8
|
+
--coverage-complete: #1b5e20;
|
|
9
|
+
--coverage-strong: #66bb6a;
|
|
10
|
+
--coverage-weak: #fdd835;
|
|
11
|
+
--coverage-none: #c62828;
|
|
12
|
+
--coverage-na: #b0b0b0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root {
|
|
17
|
+
--bg: #1a1a1a;
|
|
18
|
+
--fg: #e8e8e8;
|
|
19
|
+
--muted: #b0b0b0;
|
|
20
|
+
--card-bg: #2a2a2a;
|
|
21
|
+
--border: #3a3a3a;
|
|
22
|
+
--accent: #82b1ff;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
* { box-sizing: border-box; }
|
|
27
|
+
body {
|
|
28
|
+
margin: 0;
|
|
29
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
30
|
+
background: var(--bg);
|
|
31
|
+
color: var(--fg);
|
|
32
|
+
line-height: 1.5;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.page-header {
|
|
36
|
+
padding: 1.5rem 2rem;
|
|
37
|
+
border-bottom: 1px solid var(--border);
|
|
38
|
+
background: var(--card-bg);
|
|
39
|
+
}
|
|
40
|
+
.page-header h1 { margin: 0 0 0.25rem; font-size: 1.5rem; }
|
|
41
|
+
.tagline { margin: 0; color: var(--muted); font-size: 0.875rem; }
|
|
42
|
+
|
|
43
|
+
main { padding: 1.5rem 2rem; max-width: 1200px; margin: 0 auto; }
|
|
44
|
+
|
|
45
|
+
section { margin-bottom: 2rem; }
|
|
46
|
+
|
|
47
|
+
.hint {
|
|
48
|
+
background: #fff3cd; color: #664d03;
|
|
49
|
+
padding: 0.75rem 1rem; border-radius: 4px;
|
|
50
|
+
font-size: 0.875rem;
|
|
51
|
+
}
|
|
52
|
+
@media (prefers-color-scheme: dark) {
|
|
53
|
+
.hint { background: #4d3a00; color: #ffe082; }
|
|
54
|
+
}
|
|
55
|
+
.hidden { display: none; }
|
|
56
|
+
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; }
|
|
57
|
+
|
|
58
|
+
.card {
|
|
59
|
+
background: var(--card-bg);
|
|
60
|
+
border: 1px solid var(--border);
|
|
61
|
+
border-radius: 6px;
|
|
62
|
+
padding: 1rem 1.25rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.identity-grid {
|
|
66
|
+
display: grid;
|
|
67
|
+
grid-template-columns: max-content 1fr;
|
|
68
|
+
gap: 0.4rem 1.5rem;
|
|
69
|
+
font-size: 0.875rem;
|
|
70
|
+
}
|
|
71
|
+
.identity-grid dt {
|
|
72
|
+
color: var(--muted);
|
|
73
|
+
font-weight: 500;
|
|
74
|
+
text-align: right;
|
|
75
|
+
}
|
|
76
|
+
.identity-grid dd { margin: 0; word-break: break-all; }
|
|
77
|
+
|
|
78
|
+
.totals {
|
|
79
|
+
display: grid;
|
|
80
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
81
|
+
gap: 1rem;
|
|
82
|
+
margin-top: 1rem;
|
|
83
|
+
}
|
|
84
|
+
.total-cell {
|
|
85
|
+
background: var(--bg);
|
|
86
|
+
border: 1px solid var(--border);
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
padding: 0.75rem 1rem;
|
|
89
|
+
}
|
|
90
|
+
.total-cell .label { color: var(--muted); font-size: 0.75rem; }
|
|
91
|
+
.total-cell .value { font-size: 1.25rem; font-weight: 600; }
|
|
92
|
+
|
|
93
|
+
.plane-band {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 0.5rem;
|
|
97
|
+
margin: 0.25rem 0;
|
|
98
|
+
}
|
|
99
|
+
.plane-band .label {
|
|
100
|
+
width: 60px;
|
|
101
|
+
color: var(--muted);
|
|
102
|
+
font-size: 0.75rem;
|
|
103
|
+
text-align: right;
|
|
104
|
+
}
|
|
105
|
+
.plane-band .strip {
|
|
106
|
+
flex: 1;
|
|
107
|
+
height: 12px;
|
|
108
|
+
background: var(--coverage-na);
|
|
109
|
+
border-radius: 2px;
|
|
110
|
+
display: flex;
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
}
|
|
113
|
+
.plane-band .strip span {
|
|
114
|
+
height: 100%;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.block-table {
|
|
118
|
+
width: 100%;
|
|
119
|
+
border-collapse: collapse;
|
|
120
|
+
font-size: 0.875rem;
|
|
121
|
+
}
|
|
122
|
+
.block-table th,
|
|
123
|
+
.block-table td {
|
|
124
|
+
text-align: left;
|
|
125
|
+
padding: 0.5rem 0.75rem;
|
|
126
|
+
border-bottom: 1px solid var(--border);
|
|
127
|
+
}
|
|
128
|
+
.block-table th {
|
|
129
|
+
background: var(--bg);
|
|
130
|
+
color: var(--muted);
|
|
131
|
+
font-weight: 500;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
user-select: none;
|
|
134
|
+
}
|
|
135
|
+
.block-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
136
|
+
.block-table tr.block-row { cursor: pointer; }
|
|
137
|
+
.block-table tr.block-row:hover { background: rgba(41, 98, 255, 0.08); }
|
|
138
|
+
|
|
139
|
+
.status {
|
|
140
|
+
display: inline-block;
|
|
141
|
+
padding: 2px 8px;
|
|
142
|
+
border-radius: 12px;
|
|
143
|
+
font-size: 0.7rem;
|
|
144
|
+
font-weight: 600;
|
|
145
|
+
letter-spacing: 0.03em;
|
|
146
|
+
}
|
|
147
|
+
.status-COMPLETE { background: var(--coverage-complete); color: #fff; }
|
|
148
|
+
.status-PARTIAL { background: var(--coverage-weak); color: #4a3500; }
|
|
149
|
+
.status-UNCOVERED_ASSIGNED { background: var(--coverage-none); color: #fff; }
|
|
150
|
+
.status-NO_ASSIGNED_IN_BLOCK,
|
|
151
|
+
.status-OUTSIDE_BASELINE { background: var(--coverage-na); color: #fff; }
|
|
152
|
+
|
|
153
|
+
.cp-chips {
|
|
154
|
+
display: flex;
|
|
155
|
+
flex-wrap: wrap;
|
|
156
|
+
gap: 4px;
|
|
157
|
+
margin-top: 0.5rem;
|
|
158
|
+
}
|
|
159
|
+
.cp-chip {
|
|
160
|
+
background: var(--bg);
|
|
161
|
+
border: 1px solid var(--border);
|
|
162
|
+
border-radius: 3px;
|
|
163
|
+
padding: 2px 6px;
|
|
164
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
165
|
+
font-size: 0.7rem;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
}
|
|
168
|
+
.cp-chip:hover { background: var(--accent); color: #fff; }
|
|
169
|
+
|
|
170
|
+
.detail-panel {
|
|
171
|
+
background: var(--bg);
|
|
172
|
+
border: 1px solid var(--border);
|
|
173
|
+
border-radius: 4px;
|
|
174
|
+
padding: 1rem;
|
|
175
|
+
margin-top: 0.5rem;
|
|
176
|
+
}
|
|
177
|
+
.detail-panel dt { color: var(--muted); font-size: 0.75rem; }
|
|
178
|
+
.glyph-preview {
|
|
179
|
+
background: #fff;
|
|
180
|
+
border: 1px solid var(--border);
|
|
181
|
+
padding: 0.5rem;
|
|
182
|
+
margin-top: 0.5rem;
|
|
183
|
+
display: inline-block;
|
|
184
|
+
min-width: 120px;
|
|
185
|
+
min-height: 60px;
|
|
186
|
+
}
|
|
187
|
+
.glyph-preview svg { max-width: 240px; max-height: 120px; display: block; }
|
|
188
|
+
.universal-set-preview { display: block; }
|
|
189
|
+
.universal-set-preview .hint { margin: 0 0 0.5rem; }
|
|
190
|
+
@media (prefers-color-scheme: dark) {
|
|
191
|
+
.glyph-preview { background: #f0f0f0; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.discrepancies-list {
|
|
195
|
+
list-style: disc;
|
|
196
|
+
padding-left: 1.5rem;
|
|
197
|
+
font-size: 0.875rem;
|
|
198
|
+
}
|
|
199
|
+
.discrepancies-list li { margin-bottom: 0.25rem; }
|
|
200
|
+
.discrepancies-list .kind { font-family: ui-monospace, monospace; color: var(--accent); }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title><%= page_title %> — ucode audit</title>
|
|
7
|
+
<style>
|
|
8
|
+
<%= _css %>
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body data-verbose="<%= verbose %>" data-with-glyphs="<%= with_glyphs %>"
|
|
12
|
+
data-universal-set-available="<%= universal_set["available"] == true %>"
|
|
13
|
+
data-universal-set-glyphs-dir="<%= universal_set["glyphs_dir"] || "" %>"
|
|
14
|
+
data-universal-set-manifest-path="<%= universal_set["manifest_path"] || "" %>">
|
|
15
|
+
<header class="page-header">
|
|
16
|
+
<h1><%= page_title %></h1>
|
|
17
|
+
<p class="tagline">ucode font audit — self-contained page</p>
|
|
18
|
+
</header>
|
|
19
|
+
|
|
20
|
+
<main id="audit" aria-live="polite">
|
|
21
|
+
<noscript>
|
|
22
|
+
<p class="hint">This page requires JavaScript for interactive block drilldown.
|
|
23
|
+
The overview below is read from inlined JSON and renders without JS.</p>
|
|
24
|
+
</noscript>
|
|
25
|
+
<p class="hint hidden" id="file-url-hint">
|
|
26
|
+
Open via a local server (e.g. <code>python3 -m http.server</code> in this
|
|
27
|
+
directory) for full functionality. Block detail and glyph fetches are
|
|
28
|
+
blocked under <code>file://</code>.
|
|
29
|
+
</p>
|
|
30
|
+
<section id="overview" aria-busy="true"></section>
|
|
31
|
+
<section id="blocks"></section>
|
|
32
|
+
<section id="detail" class="hidden"></section>
|
|
33
|
+
<section id="discrepancies" class="hidden"></section>
|
|
34
|
+
</main>
|
|
35
|
+
|
|
36
|
+
<script type="application/json" id="audit-overview"><%= overview_json %></script>
|
|
37
|
+
<script>
|
|
38
|
+
<%= _js %>
|
|
39
|
+
</script>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|