ucode 0.2.2 → 0.3.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/Rakefile +1 -1
- data/config/unicode17_universal_glyph_set.yml +1 -1
- data/lib/ucode/cli.rb +1 -35
- data/lib/ucode/code_chart/extractor.rb +1 -9
- data/lib/ucode/code_chart/writer.rb +1 -1
- data/lib/ucode/commands/build.rb +3 -26
- data/lib/ucode/commands/canonical_build.rb +5 -8
- data/lib/ucode/commands/universal_set.rb +5 -3
- data/lib/ucode/commands.rb +0 -1
- data/lib/ucode/coordinator/enrichment/bidi.rb +35 -0
- data/lib/ucode/coordinator/enrichment/binary.rb +38 -0
- data/lib/ucode/coordinator/enrichment/casing.rb +55 -0
- data/lib/ucode/coordinator/enrichment/cjk.rb +49 -0
- data/lib/ucode/coordinator/enrichment/display.rb +36 -0
- data/lib/ucode/coordinator/enrichment/emoji.rb +36 -0
- data/lib/ucode/coordinator/enrichment/identity.rb +42 -0
- data/lib/ucode/coordinator/enrichment/indic.rb +32 -0
- data/lib/ucode/coordinator/enrichment/names.rb +63 -0
- data/lib/ucode/coordinator/enrichment/segmentation.rb +34 -0
- data/lib/ucode/coordinator/enrichment.rb +51 -0
- data/lib/ucode/coordinator/range_lookup.rb +65 -0
- data/lib/ucode/coordinator.rb +4 -276
- data/lib/ucode/error.rb +0 -8
- data/lib/ucode/glyphs/embedded_fonts/catalog.rb +32 -299
- data/lib/ucode/glyphs/embedded_fonts/codepoint_mapper.rb +130 -0
- data/lib/ucode/glyphs/embedded_fonts/content_stream_correlator.rb +25 -124
- data/lib/ucode/glyphs/embedded_fonts/font_entry.rb +0 -1
- data/lib/ucode/glyphs/embedded_fonts/pdf_indexer.rb +236 -0
- data/lib/ucode/glyphs/embedded_fonts/{source.rb → pdf_location.rb} +5 -5
- data/lib/ucode/glyphs/embedded_fonts/positional_matcher.rb +162 -0
- data/lib/ucode/glyphs/embedded_fonts/raw_font_descriptor.rb +24 -0
- data/lib/ucode/glyphs/embedded_fonts/renderer.rb +0 -2
- data/lib/ucode/glyphs/embedded_fonts/trace_correlator.rb +116 -0
- data/lib/ucode/glyphs/embedded_fonts/trace_glyph.rb +27 -0
- data/lib/ucode/glyphs/embedded_fonts/trace_parser.rb +50 -0
- data/lib/ucode/glyphs/embedded_fonts/trace_runner.rb +53 -0
- data/lib/ucode/glyphs/embedded_fonts/writer.rb +0 -4
- data/lib/ucode/glyphs/embedded_fonts.rb +9 -1
- data/lib/ucode/glyphs/pdf_fetcher.rb +7 -50
- data/lib/ucode/glyphs/resolver_factory.rb +45 -0
- data/lib/ucode/glyphs/sources/pillar1_embedded_tounicode.rb +1 -1
- data/lib/ucode/glyphs.rb +5 -14
- data/lib/ucode/version.rb +1 -1
- data/lib/ucode.rb +0 -2
- metadata +24 -16
- data/lib/ucode/commands/glyphs.rb +0 -94
- data/lib/ucode/glyphs/cell_extractor.rb +0 -130
- data/lib/ucode/glyphs/dvisvgm_renderer.rb +0 -29
- data/lib/ucode/glyphs/grid.rb +0 -30
- data/lib/ucode/glyphs/grid_detector.rb +0 -165
- data/lib/ucode/glyphs/monolith_page_map.rb +0 -181
- data/lib/ucode/glyphs/mutool_renderer.rb +0 -28
- data/lib/ucode/glyphs/page_renderer.rb +0 -234
- data/lib/ucode/glyphs/path_bbox.rb +0 -62
- data/lib/ucode/glyphs/pdf2svg_renderer.rb +0 -26
- data/lib/ucode/glyphs/pdftocairo_renderer.rb +0 -32
- data/lib/ucode/glyphs/pipeline.rb +0 -105
- data/lib/ucode/glyphs/writer.rb +0 -250
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6674c0e1a8cab0ef2ea7782878be4571e68603843742de78a43da6a6499220c
|
|
4
|
+
data.tar.gz: 416b0965b6fa4e1e22ef9c431a7c27a16093ba90b708224b7e9c9e1f15ac2c49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 361d385dd8b5bb04d3ce528ddda0bed64b8c5db46a1ef7e2f77c79ba5c4cf41f67235e8e55d5a38ec2957e1650f7fa4610b50722a299f2dfd67f900bd25e66c0
|
|
7
|
+
data.tar.gz: 1049d2b20c925da363339af6bcf1b98fd1d7fcf93a50adf9bde2240314eb272f89af6981a6604d8e7b5fa8b075b6c36deb68d42e691425f18fa70fca94a4286e
|
data/Rakefile
CHANGED
data/lib/ucode/cli.rb
CHANGED
|
@@ -109,34 +109,6 @@ module Ucode
|
|
|
109
109
|
puts JSON.pretty_generate(result)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
# ─────────────── glyphs ───────────────
|
|
113
|
-
desc "glyphs [VERSION]", "Extract per-codepoint SVGs from Code Charts PDFs (experimental)"
|
|
114
|
-
long_desc <<~LONG
|
|
115
|
-
EXPERIMENTAL in v0.1. The cell extractor currently includes cell-border
|
|
116
|
-
decorations alongside the actual character outline, so the output is not
|
|
117
|
-
yet suitable for end-user display. Opt in with --include-glyphs to run
|
|
118
|
-
the pipeline anyway; otherwise it returns a skipped payload.
|
|
119
|
-
LONG
|
|
120
|
-
option :to, type: :string, default: "./output"
|
|
121
|
-
option :block, type: :array, desc: "Limit to these block ids"
|
|
122
|
-
option :force, type: :boolean, default: false
|
|
123
|
-
option :monolith, type: :string, default: "CodeCharts.pdf",
|
|
124
|
-
desc: "Path to CodeCharts.pdf for fallback slicing"
|
|
125
|
-
option :include_glyphs, type: :boolean, default: false,
|
|
126
|
-
desc: "Opt into the experimental v0.1 pipeline"
|
|
127
|
-
def glyphs(version = nil)
|
|
128
|
-
result = Commands::GlyphsCommand.new.call(
|
|
129
|
-
VersionResolver.resolve(version),
|
|
130
|
-
output_root: options[:to],
|
|
131
|
-
block_filter: options[:block],
|
|
132
|
-
force: options[:force],
|
|
133
|
-
monolith_path: options[:monolith],
|
|
134
|
-
include_glyphs: options[:include_glyphs],
|
|
135
|
-
warn: $stderr,
|
|
136
|
-
)
|
|
137
|
-
puts JSON.pretty_generate(result)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
112
|
# ─────────────── site ───────────────
|
|
141
113
|
class Site < Thor
|
|
142
114
|
desc "init", "Copy the Vitepress scaffold into site/"
|
|
@@ -345,22 +317,16 @@ module Ucode
|
|
|
345
317
|
subcommand "cache", Cache
|
|
346
318
|
|
|
347
319
|
# ─────────────── build ───────────────
|
|
348
|
-
desc "build [VERSION]", "Full pipeline: fetch + parse +
|
|
320
|
+
desc "build [VERSION]", "Full pipeline: fetch + parse + site"
|
|
349
321
|
option :to, type: :string, default: "./output"
|
|
350
322
|
option :site, type: :string, default: nil, desc: "Build the site here (skipped if nil)"
|
|
351
|
-
option :monolith, type: :string, default: "CodeCharts.pdf"
|
|
352
323
|
option :force_fetch, type: :boolean, default: false
|
|
353
|
-
option :include_glyphs, type: :boolean, default: false,
|
|
354
|
-
desc: "Opt into the experimental v0.1 glyph step"
|
|
355
324
|
def build(version = nil)
|
|
356
325
|
result = Commands::BuildCommand.new.call(
|
|
357
326
|
version,
|
|
358
327
|
output_root: options[:to],
|
|
359
328
|
site_root: options[:site],
|
|
360
|
-
monolith_path: options[:monolith],
|
|
361
329
|
force_fetch: options[:force_fetch],
|
|
362
|
-
include_glyphs: options[:include_glyphs],
|
|
363
|
-
warn: $stderr,
|
|
364
330
|
)
|
|
365
331
|
puts JSON.pretty_generate(result)
|
|
366
332
|
end
|
|
@@ -2,14 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "pathname"
|
|
4
4
|
|
|
5
|
-
require "ucode/error"
|
|
6
|
-
require "ucode/glyphs/embedded_fonts/catalog"
|
|
7
|
-
require "ucode/glyphs/embedded_fonts/renderer"
|
|
8
|
-
require "ucode/glyphs/embedded_fonts/source"
|
|
9
|
-
require "ucode/glyphs/resolver"
|
|
10
|
-
require "ucode/glyphs/sources/pillar1_embedded_tounicode"
|
|
11
|
-
require "ucode/glyphs/sources/tier1_real_font"
|
|
12
|
-
|
|
13
5
|
module Ucode
|
|
14
6
|
module CodeChart
|
|
15
7
|
# Walks every assigned codepoint in a block and returns one
|
|
@@ -108,7 +100,7 @@ module Ucode
|
|
|
108
100
|
end
|
|
109
101
|
|
|
110
102
|
def embedded_pillar_sources
|
|
111
|
-
embedded_source = Glyphs::EmbeddedFonts::
|
|
103
|
+
embedded_source = Glyphs::EmbeddedFonts::PdfLocation.new(
|
|
112
104
|
pdf: @pdf_path, cache_dir: @cache_dir,
|
|
113
105
|
)
|
|
114
106
|
catalog = Glyphs::EmbeddedFonts::Catalog.new(embedded_source)
|
|
@@ -47,7 +47,7 @@ module Ucode
|
|
|
47
47
|
# @param ucd_version [String, nil] UCD version to stamp on
|
|
48
48
|
# provenance. nil = resolved via {VersionResolver.resolve(nil)}.
|
|
49
49
|
# @param cache_dir [Pathname, String, nil] font-stream cache
|
|
50
|
-
# directory for the EmbeddedFonts::
|
|
50
|
+
# directory for the EmbeddedFonts::PdfLocation.
|
|
51
51
|
# @param now [Time, nil] timestamp override (for tests).
|
|
52
52
|
# @param pillar3_source, tier1_sources: forwarded to the Extractor.
|
|
53
53
|
def initialize(output_root:, pdf_path:, ucd_version: nil,
|
data/lib/ucode/commands/build.rb
CHANGED
|
@@ -8,36 +8,23 @@ require "ucode/version_resolver"
|
|
|
8
8
|
module Ucode
|
|
9
9
|
module Commands
|
|
10
10
|
# `ucode build` — full pipeline: fetch (ucd + unihan + charts) →
|
|
11
|
-
# parse →
|
|
12
|
-
# is idempotent and safe to re-run.
|
|
11
|
+
# parse → site. Resumable: each step is idempotent and safe to re-run.
|
|
13
12
|
#
|
|
14
13
|
# Resolves the version intent once at the top and threads the
|
|
15
|
-
# resolved string through every sub-command.
|
|
16
|
-
# 2026-06-29 architecture review.
|
|
17
|
-
#
|
|
18
|
-
# **Glyph step is opt-in as of v0.1** because the SVG cell extractor
|
|
19
|
-
# is still experimental. Pass `include_glyphs: true` to enable it;
|
|
20
|
-
# otherwise the glyphs step is recorded as skipped.
|
|
14
|
+
# resolved string through every sub-command.
|
|
21
15
|
class BuildCommand
|
|
22
16
|
# @param version_intent [nil, :default, :latest, String]
|
|
23
17
|
# @param output_root [String, Pathname]
|
|
24
18
|
# @param site_root [String, Pathname, nil] if nil, skip site build
|
|
25
|
-
# @param monolith_path [String, Pathname, nil] CodeCharts.pdf fallback
|
|
26
19
|
# @param force_fetch [Boolean] re-download sources
|
|
27
|
-
# @param include_glyphs [Boolean] opt into the experimental glyph
|
|
28
|
-
# step (default false)
|
|
29
|
-
# @param warn [IO, nil] forwarded to GlyphsCommand when enabled
|
|
30
20
|
# @return [Hash] aggregated step results
|
|
31
21
|
def call(version_intent, output_root:, site_root: nil,
|
|
32
|
-
|
|
33
|
-
include_glyphs: false, warn: nil)
|
|
22
|
+
force_fetch: false)
|
|
34
23
|
version = VersionResolver.resolve(version_intent)
|
|
35
24
|
steps = {}
|
|
36
25
|
|
|
37
26
|
steps[:fetch] = run_fetch(version, force: force_fetch)
|
|
38
27
|
steps[:parse] = ParseCommand.new.call(version, output_root: output_root)
|
|
39
|
-
steps[:glyphs] = run_glyphs(version, output_root, monolith_path,
|
|
40
|
-
include_glyphs: include_glyphs, warn: warn)
|
|
41
28
|
steps[:site] = run_site(output_root, site_root) if site_root
|
|
42
29
|
|
|
43
30
|
{ version: version, steps: steps }
|
|
@@ -54,16 +41,6 @@ module Ucode
|
|
|
54
41
|
}
|
|
55
42
|
end
|
|
56
43
|
|
|
57
|
-
def run_glyphs(version, output_root, monolith_path, include_glyphs:, warn:)
|
|
58
|
-
GlyphsCommand.new.call(
|
|
59
|
-
version,
|
|
60
|
-
output_root: output_root,
|
|
61
|
-
monolith_path: monolith_path || "CodeCharts.pdf",
|
|
62
|
-
include_glyphs: include_glyphs,
|
|
63
|
-
warn: warn,
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
44
|
def run_site(output_root, site_root)
|
|
68
45
|
SiteCommand.new.build(output_root: output_root, site_root: site_root)
|
|
69
46
|
end
|
|
@@ -20,10 +20,7 @@ module Ucode
|
|
|
20
20
|
# `index.json` + `glyph.svg` atomically, accumulate per-tier +
|
|
21
21
|
# per-block stats, and emit `output/build-report.json`.
|
|
22
22
|
#
|
|
23
|
-
# This is the
|
|
24
|
-
# in {GlyphsCommand}. The two coexist until the v0.1 pipeline is
|
|
25
|
-
# removed (TODOs 17-19); CanonicalBuildCommand is the path forward
|
|
26
|
-
# for production dataset runs.
|
|
23
|
+
# This is the production path for dataset runs.
|
|
27
24
|
#
|
|
28
25
|
# == Pre-conditions (per TODO 21)
|
|
29
26
|
#
|
|
@@ -121,10 +118,10 @@ module Ucode
|
|
|
121
118
|
end
|
|
122
119
|
|
|
123
120
|
def build_resolver(version, source_config_path)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
Glyphs::ResolverFactory.build(
|
|
122
|
+
version: version,
|
|
123
|
+
source_config_path: source_config_path,
|
|
124
|
+
)
|
|
128
125
|
end
|
|
129
126
|
|
|
130
127
|
def source_config_path_or_default(path)
|
|
@@ -126,9 +126,11 @@ module Ucode
|
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def build_resolver(_version, config_path, database)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
Glyphs::ResolverFactory.build(
|
|
130
|
+
version: _version,
|
|
131
|
+
source_config_path: config_path,
|
|
132
|
+
database: database,
|
|
133
|
+
)
|
|
132
134
|
end
|
|
133
135
|
|
|
134
136
|
def codepoint_enum(version)
|
data/lib/ucode/commands.rb
CHANGED
|
@@ -9,7 +9,6 @@ module Ucode
|
|
|
9
9
|
module Commands
|
|
10
10
|
autoload :FetchCommand, "ucode/commands/fetch"
|
|
11
11
|
autoload :ParseCommand, "ucode/commands/parse"
|
|
12
|
-
autoload :GlyphsCommand, "ucode/commands/glyphs"
|
|
13
12
|
autoload :SiteCommand, "ucode/commands/site"
|
|
14
13
|
autoload :LookupCommand, "ucode/commands/lookup"
|
|
15
14
|
autoload :CacheCommand, "ucode/commands/cache"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Bidirectional behaviour: mirroring glyph and paired bracket info.
|
|
7
|
+
module Bidi
|
|
8
|
+
class << self
|
|
9
|
+
# @param cp [Ucode::Models::CodePoint]
|
|
10
|
+
# @param indices [Ucode::Coordinator::Indices]
|
|
11
|
+
def enrich(cp, indices)
|
|
12
|
+
mirroring = indices.bidi_mirroring[cp.cp]
|
|
13
|
+
brackets = indices.bidi_brackets[cp.cp]
|
|
14
|
+
return unless mirroring || brackets
|
|
15
|
+
|
|
16
|
+
cp.bidi ||= Ucode::Models::CodePoint::Bidi.new
|
|
17
|
+
apply_mirroring(cp, mirroring) if mirroring
|
|
18
|
+
apply_brackets(cp, brackets) if brackets
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def apply_mirroring(cp, mirroring)
|
|
24
|
+
cp.bidi.mirroring_glyph_id = mirroring.mirrored_id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def apply_brackets(cp, brackets)
|
|
28
|
+
cp.bidi.paired_bracket_type = brackets.type
|
|
29
|
+
cp.bidi.paired_bracket_id = brackets.paired_id
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Binary properties: DerivedCoreProperties (core) + PropList (extra).
|
|
7
|
+
# Both contribute to the same `cp.binary_properties` array.
|
|
8
|
+
module Binary
|
|
9
|
+
class << self
|
|
10
|
+
def enrich(cp, indices)
|
|
11
|
+
assign_core(cp, indices)
|
|
12
|
+
assign_extra(cp, indices)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def assign_core(cp, indices)
|
|
18
|
+
records = indices.binary_properties[cp.cp]
|
|
19
|
+
return unless records && !records.empty?
|
|
20
|
+
|
|
21
|
+
cp.binary_properties = records.map(&:property_short)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# PropList carries binary properties beyond DerivedCoreProperties
|
|
25
|
+
# (White_Space, Hyphen, Variation_Selector, etc.). Merge into the
|
|
26
|
+
# same binary_properties list, deduped.
|
|
27
|
+
def assign_extra(cp, indices)
|
|
28
|
+
extras = RangeLookup.all_range_values(cp.cp, indices.extra_binary_properties)
|
|
29
|
+
return if extras.empty?
|
|
30
|
+
|
|
31
|
+
cp.binary_properties.concat(extras)
|
|
32
|
+
cp.binary_properties.uniq!
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Context-sensitive case mappings and case folding for comparison.
|
|
7
|
+
module Casing
|
|
8
|
+
class << self
|
|
9
|
+
def enrich(cp, indices)
|
|
10
|
+
assign_special_casing(cp, indices)
|
|
11
|
+
assign_case_folding(cp, indices)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# NOTE: do not uniq the *_ids arrays — a mapping like U+00DF → "SS"
|
|
17
|
+
# legitimately contains two U+0053 entries and they must be
|
|
18
|
+
# preserved in order. Conditions, by contrast, are categorical
|
|
19
|
+
# tags (Final_Sigma, tr, After_I) and deduping them is correct.
|
|
20
|
+
def assign_special_casing(cp, indices)
|
|
21
|
+
rules = indices.special_casing[cp.cp]
|
|
22
|
+
return unless rules && !rules.empty?
|
|
23
|
+
|
|
24
|
+
cp.casing ||= Ucode::Models::CodePoint::Casing.new
|
|
25
|
+
apply_casing_rules(cp.casing, rules)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def apply_casing_rules(casing, rules)
|
|
29
|
+
casing.full_upper_ids = rules.flat_map(&:upper_ids)
|
|
30
|
+
casing.full_lower_ids = rules.flat_map(&:lower_ids)
|
|
31
|
+
casing.full_title_ids = rules.flat_map(&:title_ids)
|
|
32
|
+
casing.conditions = rules.flat_map(&:conditions).uniq
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def assign_case_folding(cp, indices)
|
|
36
|
+
rules = indices.case_folding[cp.cp]
|
|
37
|
+
return unless rules && !rules.empty?
|
|
38
|
+
|
|
39
|
+
cp.case_folding ||= Ucode::Models::CodePoint::CaseFolding.new
|
|
40
|
+
rules.each { |rule| apply_folding_rule(cp, rule) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def apply_folding_rule(cp, rule)
|
|
44
|
+
case rule.status
|
|
45
|
+
when "C" then cp.case_folding.common_id = rule.mapping_ids.first
|
|
46
|
+
when "S" then cp.case_folding.simple_id = rule.mapping_ids.first
|
|
47
|
+
when "T" then cp.case_folding.turkic_id = rule.mapping_ids.first
|
|
48
|
+
when "F" then cp.case_folding.full_ids = rule.mapping_ids
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# CJK-specific data: Unihan readings, KangXi radical mapping,
|
|
7
|
+
# Hangul syllable type.
|
|
8
|
+
module CJK
|
|
9
|
+
class << self
|
|
10
|
+
def enrich(cp, indices)
|
|
11
|
+
assign_unihan(cp, indices)
|
|
12
|
+
assign_cjk_radical(cp, indices)
|
|
13
|
+
assign_hangul(cp, indices)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def assign_unihan(cp, indices)
|
|
19
|
+
entry = indices.unihan[cp.cp]
|
|
20
|
+
return unless entry
|
|
21
|
+
|
|
22
|
+
cp.unihan = entry
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def assign_cjk_radical(cp, indices)
|
|
26
|
+
radicals = indices.cjk_radicals[cp.id]
|
|
27
|
+
return unless radicals && !radicals.empty?
|
|
28
|
+
|
|
29
|
+
radicals.each do |radical|
|
|
30
|
+
cp.relationships << Ucode::Models::Relationship::CrossReference.new(
|
|
31
|
+
target_ids: [radical.cjk_radical_id],
|
|
32
|
+
description: "KangXi radical ##{radical.radical_number}",
|
|
33
|
+
source: "cjk_radicals",
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def assign_hangul(cp, indices)
|
|
39
|
+
tuple = RangeLookup.find_in_range(cp.cp, indices.hangul_syllable_type)
|
|
40
|
+
return unless tuple
|
|
41
|
+
|
|
42
|
+
cp.hangul ||= Ucode::Models::CodePoint::HangulSyllable.new
|
|
43
|
+
cp.hangul.type = tuple.value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Display layout properties: Line Break class, East Asian Width,
|
|
7
|
+
# Vertical Orientation. All three are range+value files, looked up
|
|
8
|
+
# via bsearch on sorted arrays of ExtractedProperties::Tuple.
|
|
9
|
+
module Display
|
|
10
|
+
class << self
|
|
11
|
+
def enrich(cp, indices)
|
|
12
|
+
lb = lookup_value(cp, indices.line_break)
|
|
13
|
+
eaw = lookup_value(cp, indices.east_asian_width)
|
|
14
|
+
vo = lookup_value(cp, indices.vertical_orientation)
|
|
15
|
+
return if lb.nil? && eaw.nil? && vo.nil?
|
|
16
|
+
|
|
17
|
+
cp.display ||= Ucode::Models::CodePoint::Display.new
|
|
18
|
+
apply_values(cp.display, lb, eaw, vo)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def lookup_value(cp, ranges)
|
|
24
|
+
RangeLookup.find_in_range(cp.cp, ranges)&.value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def apply_values(display, lb, eaw, vo)
|
|
28
|
+
display.line_break_class = lb if lb
|
|
29
|
+
display.east_asian_width = eaw if eaw
|
|
30
|
+
display.vertical_orientation = vo if vo
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Emoji property bundle. Each Emoji_* property from emoji-data.txt
|
|
7
|
+
# flips the matching boolean on the Emoji sub-model.
|
|
8
|
+
module Emoji
|
|
9
|
+
class << self
|
|
10
|
+
def enrich(cp, indices)
|
|
11
|
+
return unless RangeLookup.find_in_range(cp.cp, indices.emoji_properties)
|
|
12
|
+
|
|
13
|
+
props = RangeLookup.all_range_values(cp.cp, indices.emoji_properties)
|
|
14
|
+
return if props.empty?
|
|
15
|
+
|
|
16
|
+
cp.emoji ||= Ucode::Models::CodePoint::Emoji.new
|
|
17
|
+
props.each { |prop| apply_property(cp, prop) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def apply_property(cp, prop)
|
|
23
|
+
case prop
|
|
24
|
+
when "Emoji" then cp.emoji.is_emoji = true
|
|
25
|
+
when "Emoji_Presentation" then cp.emoji.is_presentation_default = true
|
|
26
|
+
when "Emoji_Modifier" then cp.emoji.is_modifier = true
|
|
27
|
+
when "Emoji_Modifier_Base" then cp.emoji.is_base = true
|
|
28
|
+
when "Emoji_Component" then cp.emoji.is_component = true
|
|
29
|
+
when "Extended_Pictographic" then cp.emoji.is_extended_pictographic = true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Basic identity properties: primary script, script extensions,
|
|
7
|
+
# and the Unicode version when the codepoint was introduced.
|
|
8
|
+
module Identity
|
|
9
|
+
class << self
|
|
10
|
+
def enrich(cp, indices)
|
|
11
|
+
assign_script(cp, indices)
|
|
12
|
+
assign_script_extensions(cp, indices)
|
|
13
|
+
assign_age(cp, indices)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def assign_script(cp, indices)
|
|
19
|
+
script = RangeLookup.find_in_range(cp.cp, indices.scripts)
|
|
20
|
+
return unless script
|
|
21
|
+
|
|
22
|
+
cp.script_code = script.code || script.name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def assign_script_extensions(cp, indices)
|
|
26
|
+
tuples = indices.script_extensions[cp.cp]
|
|
27
|
+
return unless tuples && !tuples.empty?
|
|
28
|
+
|
|
29
|
+
tuples.each { |tuple| cp.script_extensions << tuple.script_code }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def assign_age(cp, indices)
|
|
33
|
+
record = indices.derived_age[cp.cp]
|
|
34
|
+
return unless record
|
|
35
|
+
|
|
36
|
+
cp.age = record.age
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Indic script shaping categories: positional and syllabic.
|
|
7
|
+
module Indic
|
|
8
|
+
class << self
|
|
9
|
+
def enrich(cp, indices)
|
|
10
|
+
positional = lookup_value(cp, indices.indic_positional)
|
|
11
|
+
syllabic = lookup_value(cp, indices.indic_syllabic)
|
|
12
|
+
return if positional.nil? && syllabic.nil?
|
|
13
|
+
|
|
14
|
+
cp.indic ||= Ucode::Models::CodePoint::Indic.new
|
|
15
|
+
apply_values(cp.indic, positional, syllabic)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def lookup_value(cp, ranges)
|
|
21
|
+
RangeLookup.find_in_range(cp.cp, ranges)&.value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def apply_values(indic, positional, syllabic)
|
|
25
|
+
indic.positional_category = positional if positional
|
|
26
|
+
indic.syllabic_category = syllabic if syllabic
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# Human-curated name annotations: cross-references, informal aliases,
|
|
7
|
+
# footnotes from NamesList.txt; formal name aliases from
|
|
8
|
+
# NameAliases.txt; standardized variation sequences.
|
|
9
|
+
module Names
|
|
10
|
+
class << self
|
|
11
|
+
def enrich(cp, indices)
|
|
12
|
+
assign_names_list(cp, indices)
|
|
13
|
+
assign_name_aliases(cp, indices)
|
|
14
|
+
assign_standardized_variants(cp, indices)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def assign_names_list(cp, indices)
|
|
20
|
+
entry = indices.names_list[cp.cp]
|
|
21
|
+
return unless entry
|
|
22
|
+
|
|
23
|
+
cp.names_list = entry
|
|
24
|
+
cp.relationships.concat(entry.cross_references)
|
|
25
|
+
cp.relationships.concat(entry.sample_sequences)
|
|
26
|
+
cp.relationships.concat(entry.compatibility_equivalents)
|
|
27
|
+
cp.relationships.concat(entry.informal_aliases)
|
|
28
|
+
cp.relationships.concat(entry.footnotes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def assign_name_aliases(cp, indices)
|
|
32
|
+
aliases = indices.name_aliases[cp.cp]
|
|
33
|
+
return unless aliases && !aliases.empty?
|
|
34
|
+
|
|
35
|
+
aliases.each do |alias_record|
|
|
36
|
+
cp.relationships << Ucode::Models::Relationship::InformalAlias.new(
|
|
37
|
+
description: alias_record.text,
|
|
38
|
+
source: "name_aliases",
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def assign_standardized_variants(cp, indices)
|
|
44
|
+
variants = indices.standardized_variants[cp.id]
|
|
45
|
+
return unless variants && !variants.empty?
|
|
46
|
+
|
|
47
|
+
cp.standardized_variants = variants
|
|
48
|
+
variants.each { |v| add_variant_relationship(cp, v) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def add_variant_relationship(cp, variant)
|
|
52
|
+
cp.relationships << Ucode::Models::Relationship::VariationSequence.new(
|
|
53
|
+
target_ids: [variant.base_id, variant.variation_selector_id],
|
|
54
|
+
description: variant.description,
|
|
55
|
+
contexts: variant.contexts,
|
|
56
|
+
source: "standardized_variants",
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
module Enrichment
|
|
6
|
+
# UAX #29 text segmentation: Grapheme / Word / Sentence break class.
|
|
7
|
+
module Segmentation
|
|
8
|
+
class << self
|
|
9
|
+
def enrich(cp, indices)
|
|
10
|
+
grapheme = lookup_value(cp, indices.grapheme_break)
|
|
11
|
+
word = lookup_value(cp, indices.word_break)
|
|
12
|
+
sentence = lookup_value(cp, indices.sentence_break)
|
|
13
|
+
return if grapheme.nil? && word.nil? && sentence.nil?
|
|
14
|
+
|
|
15
|
+
cp.break_segmentation ||= Ucode::Models::CodePoint::BreakSegmentation.new
|
|
16
|
+
apply_values(cp.break_segmentation, grapheme, word, sentence)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def lookup_value(cp, ranges)
|
|
22
|
+
RangeLookup.find_in_range(cp.cp, ranges)&.value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def apply_values(seg, grapheme, word, sentence)
|
|
26
|
+
seg.grapheme = grapheme if grapheme
|
|
27
|
+
seg.word = word if word
|
|
28
|
+
seg.sentence = sentence if sentence
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|