ucode 0.2.3 → 0.3.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/config/unicode17_universal_glyph_set.yml +1 -1
- data/lib/ucode/cli.rb +12 -0
- data/lib/ucode/code_chart/extractor.rb +1 -9
- data/lib/ucode/code_chart/writer.rb +1 -1
- data/lib/ucode/commands/canonical_build.rb +4 -4
- data/lib/ucode/commands/emit_metadata.rb +62 -0
- data/lib/ucode/commands/universal_set.rb +5 -3
- data/lib/ucode/commands.rb +1 -0
- 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 +5 -0
- data/lib/ucode/glyphs/embedded_fonts/catalog.rb +32 -376
- 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 +54 -168
- data/lib/ucode/glyphs/embedded_fonts/writer.rb +0 -4
- data/lib/ucode/glyphs/embedded_fonts.rb +5 -1
- 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 +1 -0
- data/lib/ucode/unicode/block.rb +28 -0
- data/lib/ucode/unicode/catalog.rb +135 -0
- data/lib/ucode/unicode/metadata/v15_0_0.rb +354 -0
- data/lib/ucode/unicode/metadata/v15_1_0.rb +355 -0
- data/lib/ucode/unicode/metadata/v16_0_0.rb +365 -0
- data/lib/ucode/unicode/metadata/v17_0_0.rb +374 -0
- data/lib/ucode/unicode/metadata_writer.rb +136 -0
- data/lib/ucode/unicode/plane.rb +23 -0
- data/lib/ucode/unicode.rb +85 -0
- data/lib/ucode/version.rb +1 -1
- data/lib/ucode.rb +2 -0
- metadata +30 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39b510b33a2215d748cbf04e9794149209ae500fae5e9fdce455703c08b3bc7c
|
|
4
|
+
data.tar.gz: 7a4c6ac03258db64ac786c5fd324d5232205ca8cda18865fbc083e55eb091709
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a03ec1efe4bb8396e3b7405cafacbc674fdb6efe4cfa01c9b41e621a30a823893af287eb73c6d7b1fce450d302700b5a7147824116ecd4c8b2f5544ad30fa3f
|
|
7
|
+
data.tar.gz: e68b9a8a08ad174b4b38f8ab03c73a32e1fb16a37caff25164a8ea91f160c25a8e1272e288c00993396b3db5d24b4d8551dd5ef0ea3526e8c878dbc98550a5f0
|
data/lib/ucode/cli.rb
CHANGED
|
@@ -651,6 +651,18 @@ module Ucode
|
|
|
651
651
|
puts JSON.pretty_generate(result.to_h)
|
|
652
652
|
end
|
|
653
653
|
|
|
654
|
+
desc "emit-metadata [VERSION]", "Generate frozen Ruby metadata module from UCD data"
|
|
655
|
+
option :gem_root, type: :string, default: nil,
|
|
656
|
+
desc: "Gem root for output path (default: auto-detect)"
|
|
657
|
+
def emit_metadata(version = nil)
|
|
658
|
+
version_str = VersionResolver.resolve(version)
|
|
659
|
+
result = Commands::EmitMetadataCommand.new.call(
|
|
660
|
+
version_str,
|
|
661
|
+
gem_root: options[:gem_root],
|
|
662
|
+
)
|
|
663
|
+
puts JSON.pretty_generate(result)
|
|
664
|
+
end
|
|
665
|
+
|
|
654
666
|
private
|
|
655
667
|
|
|
656
668
|
def result_to_h(result)
|
|
@@ -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,
|
|
@@ -118,10 +118,10 @@ module Ucode
|
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def build_resolver(version, source_config_path)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
Glyphs::ResolverFactory.build(
|
|
122
|
+
version: version,
|
|
123
|
+
source_config_path: source_config_path,
|
|
124
|
+
)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
def source_config_path_or_default(path)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Ucode
|
|
6
|
+
module Commands
|
|
7
|
+
# `ucode emit-metadata` — generates frozen Ruby metadata modules
|
|
8
|
+
# from cached UCD text files.
|
|
9
|
+
#
|
|
10
|
+
# Run after `ucode fetch ucd <version>` to produce the metadata
|
|
11
|
+
# module that ships with the gem. The output is written to
|
|
12
|
+
# `lib/ucode/unicode/metadata/<vXX_Y_Z>.rb` and must be committed.
|
|
13
|
+
class EmitMetadataCommand
|
|
14
|
+
# @param version [String] e.g. "17.0.0"
|
|
15
|
+
# @param gem_root [String, Pathname, nil] gem root for output path
|
|
16
|
+
# resolution; defaults to the conventional location.
|
|
17
|
+
# @return [Hash] { version:, path:, bytes:, blocks:, assigned_count: }
|
|
18
|
+
def call(version, gem_root: nil)
|
|
19
|
+
ucd_dir = Cache.ucd_dir(version)
|
|
20
|
+
raise Ucode::Error, "UCD not cached for #{version}. Run: ucode fetch ucd #{version}" unless ucd_dir.exist?
|
|
21
|
+
|
|
22
|
+
source = Ucode::Unicode::MetadataWriter.generate(
|
|
23
|
+
ucd_dir: ucd_dir, version: version,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
out_path = resolve_output_path(version, gem_root)
|
|
27
|
+
write_atomic(out_path, source)
|
|
28
|
+
|
|
29
|
+
metadata = Ucode::Unicode::MetadataWriter
|
|
30
|
+
metadata.version_to_module(version)
|
|
31
|
+
{
|
|
32
|
+
version: version,
|
|
33
|
+
path: out_path.to_s,
|
|
34
|
+
bytes: source.bytesize,
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def resolve_output_path(version, gem_root)
|
|
41
|
+
filename = Ucode::Unicode::MetadataWriter.version_to_filename(version)
|
|
42
|
+
base = gem_root ? Pathname.new(gem_root) : default_gem_root
|
|
43
|
+
dir = base.join("lib", "ucode", "unicode", "metadata")
|
|
44
|
+
dir.mkpath unless dir.exist?
|
|
45
|
+
dir.join("#{filename}.rb")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def default_gem_root
|
|
49
|
+
Pathname.new(__dir__).join("..", "..", "..")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def write_atomic(path, content)
|
|
53
|
+
return if path.exist? && path.read == content
|
|
54
|
+
|
|
55
|
+
path.dirname.mkpath
|
|
56
|
+
tmp = path.sub_ext(".rb.tmp")
|
|
57
|
+
tmp.write(content)
|
|
58
|
+
tmp.rename(path.to_s)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -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
|
@@ -13,6 +13,7 @@ module Ucode
|
|
|
13
13
|
autoload :LookupCommand, "ucode/commands/lookup"
|
|
14
14
|
autoload :CacheCommand, "ucode/commands/cache"
|
|
15
15
|
autoload :BuildCommand, "ucode/commands/build"
|
|
16
|
+
autoload :EmitMetadataCommand, "ucode/commands/emit_metadata"
|
|
16
17
|
autoload :CanonicalBuildCommand, "ucode/commands/canonical_build"
|
|
17
18
|
autoload :FontCoverageCommand, "ucode/commands/font_coverage"
|
|
18
19
|
autoload :UniversalSet, "ucode/commands/universal_set"
|
|
@@ -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
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
class Coordinator
|
|
5
|
+
# Registry of per-codepoint enrichment concerns. Each module under
|
|
6
|
+
# {Enrichment} owns one slice of the UCD/Unihan merge: Script, Bidi,
|
|
7
|
+
# Casing, Names, CJK, Display, Segmentation, Indic, Emoji, Binary.
|
|
8
|
+
#
|
|
9
|
+
# The registry is an ordered Array of modules. {Coordinator#enrich}
|
|
10
|
+
# iterates it, calling `enrich(cp, indices)` on each. New UCD
|
|
11
|
+
# properties land as a new module + one line in {REGISTRY}.
|
|
12
|
+
#
|
|
13
|
+
# Each module is pure: it reads from {Indices} and mutates the
|
|
14
|
+
# CodePoint model. Range lookups go through {RangeLookup}.
|
|
15
|
+
module Enrichment
|
|
16
|
+
autoload :Identity, "ucode/coordinator/enrichment/identity"
|
|
17
|
+
autoload :Bidi, "ucode/coordinator/enrichment/bidi"
|
|
18
|
+
autoload :Casing, "ucode/coordinator/enrichment/casing"
|
|
19
|
+
autoload :Binary, "ucode/coordinator/enrichment/binary"
|
|
20
|
+
autoload :Names, "ucode/coordinator/enrichment/names"
|
|
21
|
+
autoload :CJK, "ucode/coordinator/enrichment/cjk"
|
|
22
|
+
autoload :Display, "ucode/coordinator/enrichment/display"
|
|
23
|
+
autoload :Segmentation, "ucode/coordinator/enrichment/segmentation"
|
|
24
|
+
autoload :Indic, "ucode/coordinator/enrichment/indic"
|
|
25
|
+
autoload :Emoji, "ucode/coordinator/enrichment/emoji"
|
|
26
|
+
|
|
27
|
+
# Order matters only for determinism — each module sets disjoint
|
|
28
|
+
# fields on the CodePoint model. Preserved from the original flat
|
|
29
|
+
# dispatch for stable diff comparisons.
|
|
30
|
+
REGISTRY = [
|
|
31
|
+
Identity,
|
|
32
|
+
Bidi,
|
|
33
|
+
Casing,
|
|
34
|
+
Binary,
|
|
35
|
+
Names,
|
|
36
|
+
CJK,
|
|
37
|
+
Display,
|
|
38
|
+
Segmentation,
|
|
39
|
+
Indic,
|
|
40
|
+
Emoji,
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
# Apply every enrichment concern to `cp`, in registry order.
|
|
44
|
+
# @param cp [Ucode::Models::CodePoint]
|
|
45
|
+
# @param indices [Ucode::Coordinator::Indices]
|
|
46
|
+
def self.apply(cp, indices)
|
|
47
|
+
REGISTRY.each { |mod| mod.enrich(cp, indices) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|