ucode 0.1.0 → 0.2.0
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/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/reference_factory.rb +66 -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 +32 -0
- data/lib/ucode/cli.rb +366 -41
- 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.rb +19 -0
- data/lib/ucode/commands/block_feed.rb +73 -0
- data/lib/ucode/commands/build.rb +4 -0
- data/lib/ucode/commands/canonical_build.rb +137 -0
- data/lib/ucode/commands/fetch.rb +49 -15
- data/lib/ucode/commands/glyphs.rb +25 -67
- data/lib/ucode/commands/lookup.rb +11 -11
- data/lib/ucode/commands/parse.rb +7 -5
- data/lib/ucode/commands/release.rb +114 -0
- data/lib/ucode/commands/universal_set.rb +207 -0
- data/lib/ucode/commands.rb +5 -0
- data/lib/ucode/coordinator/indices.rb +49 -2
- 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/pipeline.rb +106 -0
- 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 +7 -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 +82 -288
- 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/writers/blocks_writer.rb +73 -0
- data/lib/ucode/repo/writers/enums_writer.rb +38 -0
- data/lib/ucode/repo/writers/indexes_writer.rb +53 -0
- data/lib/ucode/repo/writers/manifest_writer.rb +78 -0
- data/lib/ucode/repo/writers/named_sequences_writer.rb +47 -0
- data/lib/ucode/repo/writers/planes_writer.rb +82 -0
- data/lib/ucode/repo/writers/relationships_writer.rb +71 -0
- data/lib/ucode/repo/writers/scripts_writer.rb +54 -0
- data/lib/ucode/repo/writers.rb +20 -0
- data/lib/ucode/repo.rb +5 -0
- data/lib/ucode/version.rb +1 -1
- data/schema/block-feed.output.schema.yml +134 -0
- metadata +158 -4
- data/Gemfile.lock +0 -406
data/lib/ucode/cli.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "thor"
|
|
4
4
|
|
|
5
5
|
require "ucode/commands"
|
|
6
|
+
require "ucode/version_resolver"
|
|
6
7
|
|
|
7
8
|
module Ucode
|
|
8
9
|
# Top-level CLI entry.
|
|
@@ -10,6 +11,12 @@ module Ucode
|
|
|
10
11
|
# **Thin Thor**: every method delegates to a `Commands::*Command`
|
|
11
12
|
# class and only formats the result. The Command classes are pure
|
|
12
13
|
# and testable in-process — Thor never holds business logic.
|
|
14
|
+
#
|
|
15
|
+
# **Version resolution lives here** — each top-level command resolves
|
|
16
|
+
# the user-supplied intent (nil / :default / :latest / explicit string)
|
|
17
|
+
# exactly once via `VersionResolver.resolve` and threads the resolved
|
|
18
|
+
# string into the dispatched Command. Sub-commands never re-resolve.
|
|
19
|
+
# See Candidate 4 of the 2026-06-29 architecture review.
|
|
13
20
|
class Cli < Thor
|
|
14
21
|
package_name "ucode"
|
|
15
22
|
|
|
@@ -28,13 +35,17 @@ module Ucode
|
|
|
28
35
|
desc "ucd [VERSION]", "Download UCD.zip"
|
|
29
36
|
option :force, type: :boolean, default: false, desc: "Re-download even if cached"
|
|
30
37
|
def ucd(version = nil)
|
|
31
|
-
puts format_result Commands::FetchCommand.new.fetch_ucd(
|
|
38
|
+
puts format_result Commands::FetchCommand.new.fetch_ucd(
|
|
39
|
+
VersionResolver.resolve(version), force: options[:force],
|
|
40
|
+
)
|
|
32
41
|
end
|
|
33
42
|
|
|
34
43
|
desc "unihan [VERSION]", "Download Unihan.zip"
|
|
35
44
|
option :force, type: :boolean, default: false
|
|
36
45
|
def unihan(version = nil)
|
|
37
|
-
puts format_result Commands::FetchCommand.new.fetch_unihan(
|
|
46
|
+
puts format_result Commands::FetchCommand.new.fetch_unihan(
|
|
47
|
+
VersionResolver.resolve(version), force: options[:force],
|
|
48
|
+
)
|
|
38
49
|
end
|
|
39
50
|
|
|
40
51
|
desc "charts [VERSION]", "Download per-block Code Charts PDFs"
|
|
@@ -43,7 +54,26 @@ module Ucode
|
|
|
43
54
|
def charts(version = nil)
|
|
44
55
|
cps = options[:block]&.map { |id| block_id_to_first_cp(id) }&.compact
|
|
45
56
|
puts format_result Commands::FetchCommand.new
|
|
46
|
-
.fetch_charts(version,
|
|
57
|
+
.fetch_charts(VersionResolver.resolve(version),
|
|
58
|
+
block_first_cps: cps, force: options[:force])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
desc "fonts", "Download specialist Tier 1 fonts (config/specialist_fonts.yml)"
|
|
62
|
+
option :manifest, type: :string,
|
|
63
|
+
desc: "Override manifest path (default config/specialist_fonts.yml)"
|
|
64
|
+
option :label, type: :string, desc: "Fetch only this font by label"
|
|
65
|
+
option :allow_proprietary, type: :boolean, default: false,
|
|
66
|
+
desc: "Permit non-OFL licensed fonts"
|
|
67
|
+
option :dry_run, type: :boolean, default: false,
|
|
68
|
+
desc: "Plan only; no network or disk writes"
|
|
69
|
+
def fonts
|
|
70
|
+
result = Commands::FetchCommand.new.fetch_fonts(
|
|
71
|
+
manifest_path: options[:manifest],
|
|
72
|
+
only_label: options[:label],
|
|
73
|
+
allow_proprietary: options[:allow_proprietary],
|
|
74
|
+
dry_run: options[:dry_run],
|
|
75
|
+
)
|
|
76
|
+
puts format_fonts_result(result)
|
|
47
77
|
end
|
|
48
78
|
|
|
49
79
|
private
|
|
@@ -58,6 +88,11 @@ module Ucode
|
|
|
58
88
|
def format_result(result)
|
|
59
89
|
JSON.pretty_generate(result)
|
|
60
90
|
end
|
|
91
|
+
|
|
92
|
+
def format_fonts_result(result)
|
|
93
|
+
clean = result.merge(results: result[:results].map { |r| r.to_h.compact })
|
|
94
|
+
JSON.pretty_generate(clean)
|
|
95
|
+
end
|
|
61
96
|
end
|
|
62
97
|
|
|
63
98
|
desc "fetch", "Download UCD sources"
|
|
@@ -67,7 +102,9 @@ module Ucode
|
|
|
67
102
|
desc "parse [VERSION]", "Stream UCD → output/"
|
|
68
103
|
option :to, type: :string, default: "./output", desc: "Output directory"
|
|
69
104
|
def parse(version = nil)
|
|
70
|
-
result = Commands::ParseCommand.new.call(
|
|
105
|
+
result = Commands::ParseCommand.new.call(
|
|
106
|
+
VersionResolver.resolve(version), output_root: options[:to],
|
|
107
|
+
)
|
|
71
108
|
puts JSON.pretty_generate(result)
|
|
72
109
|
end
|
|
73
110
|
|
|
@@ -88,7 +125,7 @@ module Ucode
|
|
|
88
125
|
desc: "Opt into the experimental v0.1 pipeline"
|
|
89
126
|
def glyphs(version = nil)
|
|
90
127
|
result = Commands::GlyphsCommand.new.call(
|
|
91
|
-
version,
|
|
128
|
+
VersionResolver.resolve(version),
|
|
92
129
|
output_root: options[:to],
|
|
93
130
|
block_filter: options[:block],
|
|
94
131
|
force: options[:force],
|
|
@@ -127,7 +164,9 @@ module Ucode
|
|
|
127
164
|
def block(codepoint)
|
|
128
165
|
cp = parse_cp(codepoint)
|
|
129
166
|
with_db_handling do
|
|
130
|
-
result = Commands::LookupCommand.new.lookup_block(
|
|
167
|
+
result = Commands::LookupCommand.new.lookup_block(
|
|
168
|
+
VersionResolver.resolve(options[:version]), codepoint: cp,
|
|
169
|
+
)
|
|
131
170
|
puts "#{format("U+%04X", cp)} → #{result.block || "(unassigned)"}"
|
|
132
171
|
end
|
|
133
172
|
end
|
|
@@ -137,7 +176,9 @@ module Ucode
|
|
|
137
176
|
def script(codepoint)
|
|
138
177
|
cp = parse_cp(codepoint)
|
|
139
178
|
with_db_handling do
|
|
140
|
-
result = Commands::LookupCommand.new.lookup_script(
|
|
179
|
+
result = Commands::LookupCommand.new.lookup_script(
|
|
180
|
+
VersionResolver.resolve(options[:version]), codepoint: cp,
|
|
181
|
+
)
|
|
141
182
|
puts "#{format("U+%04X", cp)} → #{result.script || "(none)"}"
|
|
142
183
|
end
|
|
143
184
|
end
|
|
@@ -149,7 +190,8 @@ module Ucode
|
|
|
149
190
|
cp = parse_cp(codepoint)
|
|
150
191
|
with_db_handling do
|
|
151
192
|
result = Commands::LookupCommand.new
|
|
152
|
-
.lookup_char(options[:version],
|
|
193
|
+
.lookup_char(VersionResolver.resolve(options[:version]),
|
|
194
|
+
codepoint: cp, output_root: options[:from])
|
|
153
195
|
puts "#{format("U+%04X", cp)} block=#{result.block_id} glyph=#{result.glyph_path}"
|
|
154
196
|
end
|
|
155
197
|
end
|
|
@@ -222,50 +264,333 @@ module Ucode
|
|
|
222
264
|
puts JSON.pretty_generate(result)
|
|
223
265
|
end
|
|
224
266
|
|
|
225
|
-
# ───────────────
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
267
|
+
# ─────────────── audit ───────────────
|
|
268
|
+
class Audit < Thor
|
|
269
|
+
desc "font PATH", "Audit a single font (or fontist formula name)"
|
|
270
|
+
option :label, type: :string, default: nil,
|
|
271
|
+
desc: "Output directory name (default: postscript_name)"
|
|
272
|
+
option :unicode_version, type: :string, default: nil
|
|
273
|
+
option :output, type: :string, default: "./output"
|
|
274
|
+
option :verbose, type: :boolean, default: false,
|
|
275
|
+
desc: "Emit per-codepoint detail chunks"
|
|
276
|
+
option :with_glyphs, type: :boolean, default: false,
|
|
277
|
+
desc: "Emit per-codepoint SVG chunks (no-op until TODO 20)"
|
|
278
|
+
option :brief, type: :boolean, default: false,
|
|
279
|
+
desc: "Cheap-extractor-only mode"
|
|
280
|
+
option :browse, type: :boolean, default: false,
|
|
281
|
+
desc: "Also write the self-contained HTML browser"
|
|
282
|
+
option :no_install, type: :boolean, default: false,
|
|
283
|
+
desc: "Don't auto-install missing fonts via fontist"
|
|
284
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
285
|
+
desc: "Path to universal-set manifest (or 'none'); " \
|
|
286
|
+
"default: output/universal_glyph_set/manifest.json " \
|
|
287
|
+
"if present, else UCD-only"
|
|
288
|
+
option :universal_set_root, type: :string, default: nil,
|
|
289
|
+
desc: "Path to universal-set build root (e.g. " \
|
|
290
|
+
"output/universal_glyph_set). Required for " \
|
|
291
|
+
"--with-missing-glyph-pages."
|
|
292
|
+
option :with_missing_glyph_pages, type: :boolean, default: false,
|
|
293
|
+
desc: "Emit per-block missing-glyph galleries " \
|
|
294
|
+
"(requires --browse + --universal-set-root)"
|
|
295
|
+
def font(path)
|
|
296
|
+
reference = Ucode::Audit::ReferenceFactory.build_from_cli(
|
|
297
|
+
flag: options[:reference_universal_set],
|
|
298
|
+
version: options[:unicode_version],
|
|
299
|
+
)
|
|
300
|
+
result = Commands::Audit::FontCommand.new.call(
|
|
301
|
+
path,
|
|
302
|
+
label: options[:label],
|
|
303
|
+
unicode_version: options[:unicode_version],
|
|
304
|
+
verbose: options[:verbose],
|
|
305
|
+
with_glyphs: options[:with_glyphs],
|
|
306
|
+
brief: options[:brief],
|
|
307
|
+
output_root: options[:output],
|
|
308
|
+
browse: options[:browse],
|
|
309
|
+
install: !options[:no_install],
|
|
310
|
+
reference: reference,
|
|
311
|
+
universal_set_root: options[:universal_set_root],
|
|
312
|
+
with_missing_glyph_pages: options[:with_missing_glyph_pages],
|
|
313
|
+
)
|
|
314
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
desc "collection PATH", "Audit a TTC/OTC/dfong collection"
|
|
318
|
+
option :font_index, type: :numeric, default: nil,
|
|
319
|
+
desc: "Audit only face N (single-face output)"
|
|
320
|
+
option :label, type: :string, default: nil
|
|
321
|
+
option :unicode_version, type: :string, default: nil
|
|
322
|
+
option :output, type: :string, default: "./output"
|
|
323
|
+
option :verbose, type: :boolean, default: false
|
|
324
|
+
option :with_glyphs, type: :boolean, default: false
|
|
325
|
+
option :brief, type: :boolean, default: false
|
|
326
|
+
option :browse, type: :boolean, default: false
|
|
327
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
328
|
+
desc: "Path to universal-set manifest (or 'none')"
|
|
329
|
+
option :universal_set_root, type: :string, default: nil,
|
|
330
|
+
desc: "Path to universal-set build root"
|
|
331
|
+
option :with_missing_glyph_pages, type: :boolean, default: false,
|
|
332
|
+
desc: "Emit per-block missing-glyph galleries"
|
|
333
|
+
def collection(path)
|
|
334
|
+
reference = Ucode::Audit::ReferenceFactory.build_from_cli(
|
|
335
|
+
flag: options[:reference_universal_set],
|
|
336
|
+
version: options[:unicode_version],
|
|
337
|
+
)
|
|
338
|
+
result = Commands::Audit::CollectionCommand.new.call(
|
|
339
|
+
path,
|
|
340
|
+
font_index: options[:font_index],
|
|
341
|
+
label: options[:label],
|
|
342
|
+
unicode_version: options[:unicode_version],
|
|
343
|
+
verbose: options[:verbose],
|
|
344
|
+
with_glyphs: options[:with_glyphs],
|
|
345
|
+
brief: options[:brief],
|
|
346
|
+
output_root: options[:output],
|
|
347
|
+
browse: options[:browse],
|
|
348
|
+
reference: reference,
|
|
349
|
+
universal_set_root: options[:universal_set_root],
|
|
350
|
+
with_missing_glyph_pages: options[:with_missing_glyph_pages],
|
|
351
|
+
)
|
|
352
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
desc "library DIR", "Walk a directory of fonts and audit each"
|
|
356
|
+
option :recursive, type: :boolean, default: false
|
|
357
|
+
option :unicode_version, type: :string, default: nil
|
|
358
|
+
option :output, type: :string, default: "./output"
|
|
359
|
+
option :verbose, type: :boolean, default: false
|
|
360
|
+
option :with_glyphs, type: :boolean, default: false
|
|
361
|
+
option :brief, type: :boolean, default: false
|
|
362
|
+
option :browse, type: :boolean, default: false,
|
|
363
|
+
desc: "Also write the library + face HTML browsers"
|
|
364
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
365
|
+
desc: "Path to universal-set manifest (or 'none')"
|
|
366
|
+
option :universal_set_root, type: :string, default: nil,
|
|
367
|
+
desc: "Path to universal-set build root"
|
|
368
|
+
option :with_missing_glyph_pages, type: :boolean, default: false,
|
|
369
|
+
desc: "Emit per-block missing-glyph galleries"
|
|
370
|
+
def library(dir)
|
|
371
|
+
reference = Ucode::Audit::ReferenceFactory.build_from_cli(
|
|
372
|
+
flag: options[:reference_universal_set],
|
|
373
|
+
version: options[:unicode_version],
|
|
374
|
+
)
|
|
375
|
+
result = Commands::Audit::LibraryCommand.new.call(
|
|
376
|
+
dir,
|
|
377
|
+
recursive: options[:recursive],
|
|
378
|
+
unicode_version: options[:unicode_version],
|
|
379
|
+
verbose: options[:verbose],
|
|
380
|
+
with_glyphs: options[:with_glyphs],
|
|
381
|
+
brief: options[:brief],
|
|
382
|
+
output_root: options[:output],
|
|
383
|
+
browse: options[:browse],
|
|
384
|
+
reference: reference,
|
|
385
|
+
universal_set_root: options[:universal_set_root],
|
|
386
|
+
with_missing_glyph_pages: options[:with_missing_glyph_pages],
|
|
387
|
+
)
|
|
388
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
desc "compare LEFT RIGHT", "Diff two audits"
|
|
392
|
+
option :unicode_version, type: :string, default: nil
|
|
393
|
+
option :output, type: :string, default: nil,
|
|
394
|
+
desc: "Write text diff to file (default: stdout)"
|
|
395
|
+
def compare(left, right)
|
|
396
|
+
result = Commands::Audit::CompareCommand.new.call(
|
|
397
|
+
left, right,
|
|
398
|
+
unicode_version: options[:unicode_version],
|
|
399
|
+
output_file: options[:output],
|
|
400
|
+
)
|
|
401
|
+
if result.error
|
|
402
|
+
warn "compare failed: #{result.error}"
|
|
403
|
+
exit 1
|
|
404
|
+
elsif options[:output].nil?
|
|
405
|
+
puts result.text
|
|
406
|
+
else
|
|
407
|
+
puts "wrote #{options[:output]}"
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
desc "browser", "Regenerate HTML browsers from existing JSON audits"
|
|
412
|
+
option :input, type: :string, default: "./output/font_audit"
|
|
413
|
+
option :faces_only, type: :boolean, default: false
|
|
414
|
+
option :library_only, type: :boolean, default: false
|
|
415
|
+
def browser
|
|
416
|
+
result = Commands::Audit::BrowserCommand.new.call(
|
|
417
|
+
input: options[:input],
|
|
418
|
+
faces_only: options[:faces_only],
|
|
419
|
+
library_only: options[:library_only],
|
|
420
|
+
)
|
|
421
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
422
|
+
end
|
|
233
423
|
|
|
234
|
-
|
|
424
|
+
private
|
|
425
|
+
|
|
426
|
+
def result_to_h(result)
|
|
427
|
+
return { error: result.error } if result.error
|
|
428
|
+
|
|
429
|
+
result.to_h.compact.transform_values do |v|
|
|
430
|
+
v.is_a?(Struct) ? v.to_h : v
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
desc "audit", "Audit font coverage against the Unicode baseline"
|
|
436
|
+
subcommand "audit", Audit
|
|
437
|
+
|
|
438
|
+
# ─────────────── universal-set ───────────────
|
|
439
|
+
class UniversalSetCmd < Thor
|
|
440
|
+
desc "build [VERSION]", "Materialize the universal glyph set (one SVG per assigned codepoint)"
|
|
441
|
+
option :to, type: :string, default: "./output/universal_glyph_set",
|
|
442
|
+
desc: "Output directory"
|
|
443
|
+
option :source_config, type: :string, default: nil,
|
|
444
|
+
desc: "Path to a Tier 1 source config YAML " \
|
|
445
|
+
"(default: config/unicode17_universal_glyph_set.yml)"
|
|
446
|
+
option :block, type: :string, default: nil,
|
|
447
|
+
desc: "Limit the build to one block (canonical underscore form)"
|
|
448
|
+
option :parallel, type: :numeric, default: nil,
|
|
449
|
+
desc: "Worker pool size (default: Ucode.configuration.parallel_workers)"
|
|
450
|
+
def build(version = nil)
|
|
451
|
+
result = Commands::UniversalSet::BuildCommand.new.call(
|
|
452
|
+
VersionResolver.resolve(version),
|
|
453
|
+
output_root: options[:to],
|
|
454
|
+
source_config_path: options[:source_config],
|
|
455
|
+
block_filter: options[:block],
|
|
456
|
+
parallel_workers: options[:parallel] || Ucode.configuration.parallel_workers,
|
|
457
|
+
)
|
|
458
|
+
puts JSON.pretty_generate(result)
|
|
459
|
+
rescue Ucode::UniversalSetPreBuildError => e
|
|
460
|
+
warn "pre-build validation failed:"
|
|
461
|
+
warn JSON.pretty_generate(e.context)
|
|
462
|
+
exit 1
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
desc "pre-check [VERSION]", "Validate source config + fonts + coverage assertion before a build"
|
|
466
|
+
option :source_config, type: :string, default: nil,
|
|
467
|
+
desc: "Path to a Tier 1 source config YAML"
|
|
468
|
+
def pre_check(version = nil)
|
|
469
|
+
report = Commands::UniversalSet::PreCheckCommand.new.call(
|
|
470
|
+
VersionResolver.resolve(version),
|
|
471
|
+
source_config_path: options[:source_config],
|
|
472
|
+
)
|
|
473
|
+
puts JSON.pretty_generate(report.to_h)
|
|
474
|
+
rescue Ucode::UniversalSetPreBuildError => e
|
|
475
|
+
warn "pre-build validation failed:"
|
|
476
|
+
warn JSON.pretty_generate(e.context)
|
|
477
|
+
exit 1
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
desc "report [VERSION]", "Emit per-tier / per-block / gaps reports from an existing manifest"
|
|
481
|
+
option :from, type: :string, default: "./output/universal_glyph_set",
|
|
482
|
+
desc: "Output directory holding manifest.json"
|
|
483
|
+
def report(version = nil)
|
|
484
|
+
result = Commands::UniversalSet::ReportCommand.new.call(
|
|
485
|
+
VersionResolver.resolve(version),
|
|
486
|
+
output_root: options[:from],
|
|
487
|
+
)
|
|
488
|
+
puts JSON.pretty_generate(result)
|
|
489
|
+
end
|
|
235
490
|
|
|
236
|
-
|
|
237
|
-
|
|
491
|
+
desc "validate [OUTPUT_ROOT]", "Run post-build structural validation on a manifest + glyphs dir"
|
|
492
|
+
option :version, type: :string, default: nil,
|
|
493
|
+
desc: "Unicode version (stamps the report; defaults to manifest)"
|
|
494
|
+
def validate(output_root = "./output/universal_glyph_set")
|
|
495
|
+
result = Commands::UniversalSet::ValidateCommand.new.call(
|
|
496
|
+
output_root,
|
|
497
|
+
version: options[:version] && VersionResolver.resolve(options[:version]),
|
|
498
|
+
)
|
|
499
|
+
puts JSON.pretty_generate(result)
|
|
500
|
+
exit 1 unless result[:passed]
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
desc "universal-set", "Build and inspect the universal glyph set reference"
|
|
505
|
+
subcommand "universal-set", UniversalSetCmd
|
|
238
506
|
|
|
239
|
-
|
|
507
|
+
# ─────────────── release ───────────────
|
|
508
|
+
desc "release", "Assemble the fontist.org release tree from per-formula audits"
|
|
509
|
+
long_desc <<~LONG
|
|
510
|
+
Walks a directory of per-formula font subdirectories and produces
|
|
511
|
+
the fontist.org-consumable release tree at
|
|
512
|
+
`<output>/font_audit_release/`. The release tree contains:
|
|
513
|
+
|
|
514
|
+
audit/<slug>/<postscript_name>/ — per-face audit subtrees
|
|
515
|
+
universal_glyph_set/ — pre-staged universal set
|
|
516
|
+
library.json — formula + face card index
|
|
517
|
+
manifest.json — versions, sha256s, totals
|
|
518
|
+
|
|
519
|
+
The universal-set directory is NOT copied by this command; the
|
|
520
|
+
CI collector is expected to pre-stage it under
|
|
521
|
+
`<output>/font_audit_release/universal_glyph_set/`.
|
|
240
522
|
LONG
|
|
241
|
-
option :
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
523
|
+
option :from, type: :string, required: true,
|
|
524
|
+
desc: "Directory of per-formula font subdirectories"
|
|
525
|
+
option :output, type: :string, default: "./output",
|
|
526
|
+
desc: "Parent of the release root"
|
|
527
|
+
option :universal_set, type: :string, default: nil,
|
|
528
|
+
desc: "Path to the universal_glyph_set directory " \
|
|
529
|
+
"(default: <release_root>/universal_glyph_set)"
|
|
530
|
+
option :unicode_version, type: :string, default: nil
|
|
531
|
+
option :brief, type: :boolean, default: false
|
|
532
|
+
option :browse, type: :boolean, default: true,
|
|
533
|
+
desc: "Also write per-face HTML browsers + missing-glyph pages"
|
|
534
|
+
option :source_config_sha256, type: :string, default: nil,
|
|
535
|
+
desc: "sha256 of the Tier 1 source-config YAML"
|
|
536
|
+
option :reference_universal_set, type: :string, default: nil,
|
|
537
|
+
desc: "Path to universal-set manifest (or 'none') " \
|
|
538
|
+
"for the per-face coverage reference"
|
|
539
|
+
def release
|
|
540
|
+
reference = Ucode::Audit::ReferenceFactory.build_from_cli(
|
|
541
|
+
flag: options[:reference_universal_set],
|
|
542
|
+
version: options[:unicode_version],
|
|
543
|
+
)
|
|
544
|
+
result = Commands::ReleaseCommand.new.call(
|
|
545
|
+
from: options[:from],
|
|
546
|
+
output_root: options[:output],
|
|
547
|
+
universal_set_root: options[:universal_set],
|
|
548
|
+
unicode_version: options[:unicode_version],
|
|
549
|
+
brief: options[:brief],
|
|
550
|
+
browse: options[:browse],
|
|
551
|
+
source_config_sha256: options[:source_config_sha256],
|
|
552
|
+
reference: reference,
|
|
553
|
+
)
|
|
554
|
+
puts JSON.pretty_generate(result_to_h(result))
|
|
555
|
+
end
|
|
246
556
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
557
|
+
# ─────────────── block-feed ───────────────
|
|
558
|
+
desc "block-feed", "Emit per-block Unicode data feed from ucode output"
|
|
559
|
+
long_desc <<~LONG
|
|
560
|
+
Translates ucode's canonical output tree into a compact per-block
|
|
561
|
+
Unicode data feed:
|
|
562
|
+
|
|
563
|
+
<target>/unicode-blocks.json
|
|
564
|
+
<target>/unicode-version.json
|
|
565
|
+
<target>/unicode/blocks/<slug>.json
|
|
566
|
+
|
|
567
|
+
Each per-block file contains the codepoints in that block with
|
|
568
|
+
their compact metadata (name, general category, script, combining
|
|
569
|
+
class, bidi class, mirrored flag). Block slugs are derived from
|
|
570
|
+
the block name via the standard slug algorithm.
|
|
571
|
+
LONG
|
|
572
|
+
option :ucode_output, type: :string, default: "./output",
|
|
573
|
+
desc: "ucode's output/ directory"
|
|
574
|
+
option :target, type: :string, default: "./output/block-feed",
|
|
575
|
+
desc: "Target directory for emitted files"
|
|
576
|
+
option :unicode_version, type: :string, default: nil,
|
|
577
|
+
desc: "UCD version stamp (default: from manifest)"
|
|
578
|
+
def block_feed
|
|
579
|
+
result = Commands::BlockFeedCommand.new.call(
|
|
580
|
+
ucode_output_root: options[:ucode_output],
|
|
581
|
+
block_feed_output_root: options[:target],
|
|
582
|
+
unicode_version: options[:unicode_version],
|
|
251
583
|
)
|
|
252
|
-
puts JSON.pretty_generate(
|
|
584
|
+
puts JSON.pretty_generate(result.to_h)
|
|
253
585
|
end
|
|
254
586
|
|
|
255
587
|
private
|
|
256
588
|
|
|
257
589
|
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
|
-
}
|
|
590
|
+
return { error: result.error } if result.error
|
|
591
|
+
|
|
592
|
+
result.to_h.compact.transform_values do |v|
|
|
593
|
+
v.is_a?(Struct) ? v.to_h : v
|
|
269
594
|
end
|
|
270
595
|
end
|
|
271
596
|
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
|