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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
module Glyphs
|
|
5
|
+
module EmbeddedFonts
|
|
6
|
+
# Correlates specimen glyphs (CID font without `/ToUnicode`) to
|
|
7
|
+
# their Unicode codepoints via positional matching against hex
|
|
8
|
+
# codepoint labels on the same chart page.
|
|
9
|
+
#
|
|
10
|
+
# Adapter for the `mutool trace` XML format: parses {TraceGlyph}
|
|
11
|
+
# arrays, partitions into specimens and labels, auto-detects the
|
|
12
|
+
# label font by proximity, then delegates matching to
|
|
13
|
+
# {PositionalMatcher}.
|
|
14
|
+
#
|
|
15
|
+
# The label font auto-detection is the only piece of "intelligence"
|
|
16
|
+
# in this adapter — everything else is format translation. The
|
|
17
|
+
# matching algorithm lives in {PositionalMatcher} and is shared
|
|
18
|
+
# with {ContentStreamCorrelator}.
|
|
19
|
+
class TraceCorrelator
|
|
20
|
+
# Proximity radius (in PDF points) for counting how often each
|
|
21
|
+
# non-specimen font's hex-char glyphs appear near a specimen.
|
|
22
|
+
# Code Charts dedicate one small font to the codepoint labels;
|
|
23
|
+
# body text and headers are farther away.
|
|
24
|
+
LABEL_PROXIMITY_RADIUS = 50.0
|
|
25
|
+
private_constant :LABEL_PROXIMITY_RADIUS
|
|
26
|
+
|
|
27
|
+
# @param specimen_font_name [String] the BaseFont name of the
|
|
28
|
+
# CID font whose glyphs need correlation
|
|
29
|
+
def initialize(specimen_font_name:)
|
|
30
|
+
@specimen_font_name = specimen_font_name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param trace_glyphs [Array<TraceGlyph>]
|
|
34
|
+
# @return [Hash{Integer=>Integer}] codepoint => gid
|
|
35
|
+
def correlate(trace_glyphs)
|
|
36
|
+
specimens = select_specimens(trace_glyphs)
|
|
37
|
+
return {} if specimens.empty?
|
|
38
|
+
|
|
39
|
+
labels = select_labels(trace_glyphs)
|
|
40
|
+
return {} if labels.empty?
|
|
41
|
+
|
|
42
|
+
PositionalMatcher.match(
|
|
43
|
+
specimens.map { |g| to_position(g) },
|
|
44
|
+
labels.map { |g| to_position(g) },
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def select_specimens(trace_glyphs)
|
|
51
|
+
trace_glyphs.select { |g| g.font_name == @specimen_font_name }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def select_labels(trace_glyphs)
|
|
55
|
+
label_font = detect_label_font(trace_glyphs)
|
|
56
|
+
return [] unless label_font
|
|
57
|
+
|
|
58
|
+
trace_glyphs.select { |g| hex_char_from?(g, label_font) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def hex_char_from?(glyph, font_name)
|
|
62
|
+
glyph.font_name == font_name && glyph.unicode&.match?(/\A[0-9A-Fa-f]\z/)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_position(glyph)
|
|
66
|
+
PositionalMatcher::Position.new(
|
|
67
|
+
x: glyph.x,
|
|
68
|
+
y: glyph.y,
|
|
69
|
+
font_ref: glyph.font_name,
|
|
70
|
+
glyph_id: glyph.gid,
|
|
71
|
+
text: glyph.unicode,
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The label font is the non-specimen font whose hex-char glyphs
|
|
76
|
+
# appear most often in close proximity to specimen glyphs.
|
|
77
|
+
# Code Charts dedicate one small font to the codepoint labels;
|
|
78
|
+
# body text, headers, and character names use other fonts that
|
|
79
|
+
# may also contain hex chars but are not co-located with
|
|
80
|
+
# specimens.
|
|
81
|
+
def detect_label_font(trace_glyphs)
|
|
82
|
+
specimens = select_specimens(trace_glyphs)
|
|
83
|
+
return nil if specimens.empty?
|
|
84
|
+
|
|
85
|
+
candidates = select_hex_candidates(trace_glyphs)
|
|
86
|
+
return nil if candidates.empty?
|
|
87
|
+
|
|
88
|
+
counts = proximity_counts(specimens, candidates)
|
|
89
|
+
return nil if counts.empty?
|
|
90
|
+
|
|
91
|
+
counts.max_by { |_, n| n }.first
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def select_hex_candidates(trace_glyphs)
|
|
95
|
+
trace_glyphs.select do |g|
|
|
96
|
+
g.font_name != @specimen_font_name &&
|
|
97
|
+
g.unicode&.match?(/\A[0-9A-Fa-f]\z/)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def proximity_counts(specimens, candidates)
|
|
102
|
+
counts = Hash.new(0)
|
|
103
|
+
radius_sq = LABEL_PROXIMITY_RADIUS * LABEL_PROXIMITY_RADIUS
|
|
104
|
+
specimens.each do |spec|
|
|
105
|
+
candidates.each do |g|
|
|
106
|
+
dx = spec.x - g.x
|
|
107
|
+
dy = spec.y - g.y
|
|
108
|
+
counts[g.font_name] += 1 if dx * dx + dy * dy < radius_sq
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
counts
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
module Glyphs
|
|
5
|
+
module EmbeddedFonts
|
|
6
|
+
# Value object for one glyph emitted by `mutool trace`.
|
|
7
|
+
#
|
|
8
|
+
# Each `<g>` element in the trace XML maps to one TraceGlyph:
|
|
9
|
+
#
|
|
10
|
+
# <g unicode="�" glyph="174" x="237.06" y="673.92" adv=".62"/>
|
|
11
|
+
#
|
|
12
|
+
# The `font_name` is inherited from the enclosing `<span>`:
|
|
13
|
+
#
|
|
14
|
+
# <span font="GPJAHB+WolofGaraySansSerif" ...>
|
|
15
|
+
# <g .../>
|
|
16
|
+
# </span>
|
|
17
|
+
TraceGlyph = Struct.new(
|
|
18
|
+
:font_name,
|
|
19
|
+
:gid,
|
|
20
|
+
:x,
|
|
21
|
+
:y,
|
|
22
|
+
:unicode,
|
|
23
|
+
keyword_init: true,
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
|
|
5
|
+
module Ucode
|
|
6
|
+
module Glyphs
|
|
7
|
+
module EmbeddedFonts
|
|
8
|
+
# Parses the XML output of `mutool trace <pdf> <page>` into an
|
|
9
|
+
# array of {TraceGlyph} instances.
|
|
10
|
+
#
|
|
11
|
+
# The trace XML uses a flat `<span font="...">` → `<g glyph="..."
|
|
12
|
+
# x="..." y="..." unicode="..."/>` structure. Nokogiri walks
|
|
13
|
+
# the tree; the parser maps each `<g>` to a TraceGlyph,
|
|
14
|
+
# inheriting the font_name from the enclosing span.
|
|
15
|
+
#
|
|
16
|
+
# Pure function — no I/O, no PDF access. Callers inject the XML
|
|
17
|
+
# string (typically from {TraceRunner}).
|
|
18
|
+
module TraceParser
|
|
19
|
+
class << self
|
|
20
|
+
# @param xml [String] raw mutool trace XML
|
|
21
|
+
# @return [Array<TraceGlyph>] one per `<g>` element; empty
|
|
22
|
+
# if the XML is empty or has no `<g>` elements
|
|
23
|
+
def parse(xml)
|
|
24
|
+
return [] if xml.nil? || xml.strip.empty?
|
|
25
|
+
|
|
26
|
+
doc = Nokogiri::XML(xml)
|
|
27
|
+
doc.css("span").flat_map { |span| glyphs_in_span(span) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def glyphs_in_span(span)
|
|
33
|
+
font_name = span[:font]
|
|
34
|
+
span.css("g").map { |g| build_glyph(font_name, g) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_glyph(font_name, g)
|
|
38
|
+
TraceGlyph.new(
|
|
39
|
+
font_name: font_name,
|
|
40
|
+
gid: g[:glyph]&.to_i,
|
|
41
|
+
x: g[:x]&.to_f,
|
|
42
|
+
y: g[:y]&.to_f,
|
|
43
|
+
unicode: g[:unicode],
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
require "ucode/error"
|
|
7
|
+
|
|
8
|
+
module Ucode
|
|
9
|
+
module Glyphs
|
|
10
|
+
module EmbeddedFonts
|
|
11
|
+
# Thin I/O wrapper around `mutool trace <pdf> <page>`.
|
|
12
|
+
#
|
|
13
|
+
# Runs mutool on the given pages, captures the XML output,
|
|
14
|
+
# delegates parsing to {TraceParser}, and returns a flat
|
|
15
|
+
# `Array<TraceGlyph>` across all pages.
|
|
16
|
+
#
|
|
17
|
+
# The only class in the trace pipeline that touches the
|
|
18
|
+
# filesystem / spawns subprocesses. Everything upstream
|
|
19
|
+
# (parser, correlator) is pure.
|
|
20
|
+
class TraceRunner
|
|
21
|
+
# @param pdf_path [Pathname, String]
|
|
22
|
+
def initialize(pdf_path)
|
|
23
|
+
@pdf_path = Pathname.new(pdf_path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param page_numbers [Array<Integer>] 1-based PDF page numbers
|
|
27
|
+
# @return [Array<TraceGlyph>]
|
|
28
|
+
def trace(page_numbers)
|
|
29
|
+
page_numbers.flat_map { |page| trace_page(page) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def trace_page(page)
|
|
35
|
+
xml = run_mutool(page)
|
|
36
|
+
TraceParser.parse(xml)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def run_mutool(page)
|
|
40
|
+
out, err, status = Open3.capture3(
|
|
41
|
+
"mutool", "trace", @pdf_path.to_s, page.to_s,
|
|
42
|
+
)
|
|
43
|
+
unless status.success?
|
|
44
|
+
raise Ucode::EmbeddedFontsMissingError,
|
|
45
|
+
"mutool trace failed: #{(out + err).strip}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
out + err
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -36,12 +36,20 @@ module Ucode
|
|
|
36
36
|
# `mutool info` (font enumeration) and `mutool show -b -o` (raw
|
|
37
37
|
# stream extraction).
|
|
38
38
|
module EmbeddedFonts
|
|
39
|
-
autoload :
|
|
39
|
+
autoload :PdfLocation, "ucode/glyphs/embedded_fonts/pdf_location"
|
|
40
40
|
autoload :ToUnicode, "ucode/glyphs/embedded_fonts/tounicode"
|
|
41
41
|
autoload :FontEntry, "ucode/glyphs/embedded_fonts/font_entry"
|
|
42
|
+
autoload :RawFontDescriptor, "ucode/glyphs/embedded_fonts/raw_font_descriptor"
|
|
43
|
+
autoload :PdfIndexer, "ucode/glyphs/embedded_fonts/pdf_indexer"
|
|
44
|
+
autoload :CodepointMapper, "ucode/glyphs/embedded_fonts/codepoint_mapper"
|
|
42
45
|
autoload :Catalog, "ucode/glyphs/embedded_fonts/catalog"
|
|
43
46
|
autoload :ContentStreamCorrelator,
|
|
44
47
|
"ucode/glyphs/embedded_fonts/content_stream_correlator"
|
|
48
|
+
autoload :PositionalMatcher, "ucode/glyphs/embedded_fonts/positional_matcher"
|
|
49
|
+
autoload :TraceGlyph, "ucode/glyphs/embedded_fonts/trace_glyph"
|
|
50
|
+
autoload :TraceParser, "ucode/glyphs/embedded_fonts/trace_parser"
|
|
51
|
+
autoload :TraceCorrelator, "ucode/glyphs/embedded_fonts/trace_correlator"
|
|
52
|
+
autoload :TraceRunner, "ucode/glyphs/embedded_fonts/trace_runner"
|
|
45
53
|
autoload :Svg, "ucode/glyphs/embedded_fonts/svg"
|
|
46
54
|
autoload :Renderer, "ucode/glyphs/embedded_fonts/renderer"
|
|
47
55
|
autoload :Writer, "ucode/glyphs/embedded_fonts/writer"
|
|
@@ -1,42 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "pathname"
|
|
4
|
-
require "open3"
|
|
5
4
|
|
|
6
5
|
require "ucode/cache"
|
|
7
6
|
require "ucode/fetch/code_charts"
|
|
8
|
-
require "ucode/glyphs/monolith_page_map"
|
|
9
7
|
|
|
10
8
|
module Ucode
|
|
11
9
|
module Glyphs
|
|
12
10
|
# Resolves a Unicode block to its source PDF on disk.
|
|
13
11
|
#
|
|
14
|
-
#
|
|
12
|
+
# Source: the per-block PDF cached at
|
|
15
13
|
# `<cache>/<version>/pdfs/U<XXXX>.pdf` (downloaded from
|
|
16
14
|
# `unicode.org/charts/PDF/` by `Ucode::Fetch::CodeCharts`).
|
|
17
|
-
#
|
|
18
|
-
# Fallback: slice the page range from the monolith `CodeCharts.pdf`.
|
|
19
|
-
# The page range is resolved by `MonolithPageMap` from the PDF's
|
|
20
|
-
# bookmark outline, cached under `data/codecharts_page_map.json`.
|
|
21
15
|
class PdfFetcher
|
|
22
16
|
# @param version [String] UCD version, used as the cache namespace.
|
|
23
|
-
|
|
24
|
-
# `CodeCharts.pdf`. Pass nil to disable monolith fallback.
|
|
25
|
-
# @param blocks [Array<Ucode::Models::Block>] required for monolith
|
|
26
|
-
# fallback — used to match bookmark titles to block first-cps.
|
|
27
|
-
# @param page_map_cache [String, Pathname, nil] where to read/write
|
|
28
|
-
# the monolith page-map JSON cache.
|
|
29
|
-
def initialize(version, monolith_path: nil, blocks: [], page_map_cache: nil)
|
|
17
|
+
def initialize(version)
|
|
30
18
|
@version = version
|
|
31
|
-
@monolith_path = monolith_path && Pathname.new(monolith_path)
|
|
32
|
-
@blocks = blocks
|
|
33
|
-
@page_map_cache = page_map_cache
|
|
34
19
|
end
|
|
35
20
|
|
|
36
21
|
# Resolve the per-block PDF for `block_first_cp`, fetching from the
|
|
37
22
|
# network if missing. Returns the local PDF path, or nil if the
|
|
38
|
-
# block's PDF is unavailable (network failure
|
|
39
|
-
# monolith lacks the requested block).
|
|
23
|
+
# block's PDF is unavailable (network failure).
|
|
40
24
|
#
|
|
41
25
|
# @param block_first_cp [Integer] first codepoint of the block;
|
|
42
26
|
# also the PDF's URL slug per unicode.org's naming convention.
|
|
@@ -47,9 +31,7 @@ module Ucode
|
|
|
47
31
|
return path if path.exist? && !force
|
|
48
32
|
|
|
49
33
|
download(block_first_cp)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
slice_from_monolith(block_first_cp)
|
|
34
|
+
path if path.exist?
|
|
53
35
|
end
|
|
54
36
|
|
|
55
37
|
private
|
|
@@ -65,38 +47,13 @@ module Ucode
|
|
|
65
47
|
def download(block_first_cp)
|
|
66
48
|
Fetch::CodeCharts.call(@version, block_first_cps: [block_first_cp])
|
|
67
49
|
rescue StandardError => e
|
|
68
|
-
# Network failures
|
|
69
|
-
# swallow programming errors (NoMethodError
|
|
70
|
-
# failures (network, checksum, HTTP).
|
|
50
|
+
# Network failures return nil so callers can fall back to other
|
|
51
|
+
# tiers. We do not swallow programming errors (NoMethodError
|
|
52
|
+
# etc.) — only fetch failures (network, checksum, HTTP).
|
|
71
53
|
return if e.is_a?(Ucode::FetchError)
|
|
72
54
|
|
|
73
55
|
raise
|
|
74
56
|
end
|
|
75
|
-
|
|
76
|
-
def slice_from_monolith(block_first_cp)
|
|
77
|
-
return unless @monolith_path&.exist?
|
|
78
|
-
|
|
79
|
-
entry = page_map[block_first_cp]
|
|
80
|
-
return unless entry && entry.start_page && entry.end_page
|
|
81
|
-
|
|
82
|
-
slice_pages(entry.start_page, entry.end_page, per_block_path(block_first_cp))
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def page_map
|
|
86
|
-
@page_map ||= MonolithPageMap.load(
|
|
87
|
-
monolith_path: @monolith_path,
|
|
88
|
-
blocks: @blocks,
|
|
89
|
-
cache_path: @page_map_cache,
|
|
90
|
-
)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def slice_pages(start_page, end_page, out_path)
|
|
94
|
-
out_path.dirname.mkpath
|
|
95
|
-
cmd = ["pdftk", @monolith_path.to_s, "cat",
|
|
96
|
-
"#{start_page}-#{end_page}", "output", out_path.to_s]
|
|
97
|
-
_out, status = Open3.capture2e(*cmd)
|
|
98
|
-
status.success? ? out_path : nil
|
|
99
|
-
end
|
|
100
57
|
end
|
|
101
58
|
end
|
|
102
59
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Ucode
|
|
6
|
+
module Glyphs
|
|
7
|
+
# Single injection point for the 4-tier {Resolver}.
|
|
8
|
+
#
|
|
9
|
+
# Both CanonicalBuildCommand and UniversalSet::BuildCommand need the
|
|
10
|
+
# same shape: open a Database, load the SourceConfig, run a
|
|
11
|
+
# SourceBuilder, wrap the resulting tier-1 sources in a Resolver.
|
|
12
|
+
# Extracting it here gives tests one seam to mock (or bypass) and
|
|
13
|
+
# prevents drift between the two call sites.
|
|
14
|
+
module ResolverFactory
|
|
15
|
+
DEFAULT_INSTALL = false
|
|
16
|
+
private_constant :DEFAULT_INSTALL
|
|
17
|
+
|
|
18
|
+
# @param version [String] UCD version, used to open the Database
|
|
19
|
+
# when one is not supplied.
|
|
20
|
+
# @param source_config_path [String, Pathname, nil] override path
|
|
21
|
+
# to the Tier 1 font config YAML; nil uses the default.
|
|
22
|
+
# @param install [Boolean] pass through to SourceBuilder#tier1_sources
|
|
23
|
+
# — whether to fontist-install missing fonts eagerly.
|
|
24
|
+
# @param database [Ucode::Database, nil] an already-open Database,
|
|
25
|
+
# to skip re-opening when the caller already has one.
|
|
26
|
+
# @return [Ucode::Glyphs::Resolver]
|
|
27
|
+
def self.build(version:, source_config_path: nil,
|
|
28
|
+
install: DEFAULT_INSTALL, database: nil)
|
|
29
|
+
db = database || Ucode::Database.open(version)
|
|
30
|
+
config = SourceConfig.new(path: resolve_config_path(source_config_path))
|
|
31
|
+
builder = SourceBuilder.new(config: config, database: db)
|
|
32
|
+
Resolver.new(sources: builder.tier1_sources(install: install))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
def self.resolve_config_path(path)
|
|
37
|
+
return SourceConfig::DEFAULT_PATH if path.nil?
|
|
38
|
+
return path if path.is_a?(Pathname)
|
|
39
|
+
|
|
40
|
+
Pathname.new(path)
|
|
41
|
+
end
|
|
42
|
+
private_class_method :resolve_config_path
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -31,7 +31,7 @@ module Ucode
|
|
|
31
31
|
# @param renderer [EmbeddedFonts::Renderer] the renderer to
|
|
32
32
|
# delegate to. Callers typically construct it with the
|
|
33
33
|
# {EmbeddedFonts::Catalog} built from the resolved Code
|
|
34
|
-
# Charts {EmbeddedFonts::
|
|
34
|
+
# Charts {EmbeddedFonts::PdfLocation}. To enable pillar-2
|
|
35
35
|
# fallback, that Catalog must be constructed with
|
|
36
36
|
# +correlator_configs:+.
|
|
37
37
|
def initialize(renderer:)
|
data/lib/ucode/glyphs.rb
CHANGED
|
@@ -3,29 +3,20 @@
|
|
|
3
3
|
module Ucode
|
|
4
4
|
# Glyphs — converts Code Charts PDF pages into per-codepoint SVGs.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# The current pipeline is the 4-tier sourcing strategy:
|
|
7
|
+
# Tier 1 (real fonts) → Pillar 1 (embedded CIDFont + ToUnicode) →
|
|
8
|
+
# Pillar 2 (positional correlation) → Pillar 3 (Last Resort UFO).
|
|
9
|
+
# See {EmbeddedFonts} for Pillar 1 + 2 and {LastResort} for Pillar 3.
|
|
8
10
|
#
|
|
9
11
|
# Vector extraction only. NEVER run OCR.
|
|
10
12
|
module Glyphs
|
|
11
13
|
autoload :PdfFetcher, "ucode/glyphs/pdf_fetcher"
|
|
12
|
-
autoload :PageRenderer, "ucode/glyphs/page_renderer"
|
|
13
|
-
autoload :MutoolRenderer, "ucode/glyphs/mutool_renderer"
|
|
14
|
-
autoload :Pdf2svgRenderer, "ucode/glyphs/pdf2svg_renderer"
|
|
15
|
-
autoload :DvisvgmRenderer, "ucode/glyphs/dvisvgm_renderer"
|
|
16
|
-
autoload :PdftocairoRenderer, "ucode/glyphs/pdftocairo_renderer"
|
|
17
|
-
autoload :Grid, "ucode/glyphs/grid"
|
|
18
|
-
autoload :PathBbox, "ucode/glyphs/path_bbox"
|
|
19
|
-
autoload :GridDetector, "ucode/glyphs/grid_detector"
|
|
20
|
-
autoload :CellExtractor, "ucode/glyphs/cell_extractor"
|
|
21
|
-
autoload :MonolithPageMap, "ucode/glyphs/monolith_page_map"
|
|
22
|
-
autoload :Writer, "ucode/glyphs/writer"
|
|
23
|
-
autoload :Pipeline, "ucode/glyphs/pipeline"
|
|
24
14
|
autoload :LastResort, "ucode/glyphs/last_resort"
|
|
25
15
|
autoload :EmbeddedFonts, "ucode/glyphs/embedded_fonts"
|
|
26
16
|
autoload :RealFonts, "ucode/glyphs/real_fonts"
|
|
27
17
|
autoload :Source, "ucode/glyphs/source"
|
|
28
18
|
autoload :Resolver, "ucode/glyphs/resolver"
|
|
19
|
+
autoload :ResolverFactory, "ucode/glyphs/resolver_factory"
|
|
29
20
|
autoload :SourceConfig, "ucode/glyphs/source_config"
|
|
30
21
|
autoload :SourceBuilder, "ucode/glyphs/source_builder"
|
|
31
22
|
autoload :Sources, "ucode/glyphs/sources"
|
data/lib/ucode/version.rb
CHANGED
data/lib/ucode.rb
CHANGED
|
@@ -32,8 +32,6 @@ module Ucode
|
|
|
32
32
|
autoload :UnknownVersionError, "ucode/error"
|
|
33
33
|
autoload :UnknownBlockError, "ucode/error"
|
|
34
34
|
autoload :GlyphError, "ucode/error"
|
|
35
|
-
autoload :PdfRenderError, "ucode/error"
|
|
36
|
-
autoload :GridDetectionError, "ucode/error"
|
|
37
35
|
autoload :LastResortMissingError, "ucode/error"
|
|
38
36
|
autoload :EmbeddedFontsMissingError, "ucode/error"
|
|
39
37
|
autoload :CodeChartNotFoundError, "ucode/error"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ucode
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -321,7 +321,6 @@ files:
|
|
|
321
321
|
- lib/ucode/commands/canonical_build.rb
|
|
322
322
|
- lib/ucode/commands/fetch.rb
|
|
323
323
|
- lib/ucode/commands/font_coverage.rb
|
|
324
|
-
- lib/ucode/commands/glyphs.rb
|
|
325
324
|
- lib/ucode/commands/lookup.rb
|
|
326
325
|
- lib/ucode/commands/parse.rb
|
|
327
326
|
- lib/ucode/commands/release.rb
|
|
@@ -329,7 +328,19 @@ files:
|
|
|
329
328
|
- lib/ucode/commands/universal_set.rb
|
|
330
329
|
- lib/ucode/config.rb
|
|
331
330
|
- lib/ucode/coordinator.rb
|
|
331
|
+
- lib/ucode/coordinator/enrichment.rb
|
|
332
|
+
- lib/ucode/coordinator/enrichment/bidi.rb
|
|
333
|
+
- lib/ucode/coordinator/enrichment/binary.rb
|
|
334
|
+
- lib/ucode/coordinator/enrichment/casing.rb
|
|
335
|
+
- lib/ucode/coordinator/enrichment/cjk.rb
|
|
336
|
+
- lib/ucode/coordinator/enrichment/display.rb
|
|
337
|
+
- lib/ucode/coordinator/enrichment/emoji.rb
|
|
338
|
+
- lib/ucode/coordinator/enrichment/identity.rb
|
|
339
|
+
- lib/ucode/coordinator/enrichment/indic.rb
|
|
340
|
+
- lib/ucode/coordinator/enrichment/names.rb
|
|
341
|
+
- lib/ucode/coordinator/enrichment/segmentation.rb
|
|
332
342
|
- lib/ucode/coordinator/indices.rb
|
|
343
|
+
- lib/ucode/coordinator/range_lookup.rb
|
|
333
344
|
- lib/ucode/database.rb
|
|
334
345
|
- lib/ucode/db_builder.rb
|
|
335
346
|
- lib/ucode/error.rb
|
|
@@ -342,19 +353,23 @@ files:
|
|
|
342
353
|
- lib/ucode/fetch/ucd_zip.rb
|
|
343
354
|
- lib/ucode/fetch/unihan_zip.rb
|
|
344
355
|
- lib/ucode/glyphs.rb
|
|
345
|
-
- lib/ucode/glyphs/cell_extractor.rb
|
|
346
|
-
- lib/ucode/glyphs/dvisvgm_renderer.rb
|
|
347
356
|
- lib/ucode/glyphs/embedded_fonts.rb
|
|
348
357
|
- lib/ucode/glyphs/embedded_fonts/catalog.rb
|
|
358
|
+
- lib/ucode/glyphs/embedded_fonts/codepoint_mapper.rb
|
|
349
359
|
- lib/ucode/glyphs/embedded_fonts/content_stream_correlator.rb
|
|
350
360
|
- lib/ucode/glyphs/embedded_fonts/font_entry.rb
|
|
361
|
+
- lib/ucode/glyphs/embedded_fonts/pdf_indexer.rb
|
|
362
|
+
- lib/ucode/glyphs/embedded_fonts/pdf_location.rb
|
|
363
|
+
- lib/ucode/glyphs/embedded_fonts/positional_matcher.rb
|
|
364
|
+
- lib/ucode/glyphs/embedded_fonts/raw_font_descriptor.rb
|
|
351
365
|
- lib/ucode/glyphs/embedded_fonts/renderer.rb
|
|
352
|
-
- lib/ucode/glyphs/embedded_fonts/source.rb
|
|
353
366
|
- lib/ucode/glyphs/embedded_fonts/svg.rb
|
|
354
367
|
- lib/ucode/glyphs/embedded_fonts/tounicode.rb
|
|
368
|
+
- lib/ucode/glyphs/embedded_fonts/trace_correlator.rb
|
|
369
|
+
- lib/ucode/glyphs/embedded_fonts/trace_glyph.rb
|
|
370
|
+
- lib/ucode/glyphs/embedded_fonts/trace_parser.rb
|
|
371
|
+
- lib/ucode/glyphs/embedded_fonts/trace_runner.rb
|
|
355
372
|
- lib/ucode/glyphs/embedded_fonts/writer.rb
|
|
356
|
-
- lib/ucode/glyphs/grid.rb
|
|
357
|
-
- lib/ucode/glyphs/grid_detector.rb
|
|
358
373
|
- lib/ucode/glyphs/last_resort.rb
|
|
359
374
|
- lib/ucode/glyphs/last_resort/cmap_index.rb
|
|
360
375
|
- lib/ucode/glyphs/last_resort/contents.rb
|
|
@@ -363,14 +378,7 @@ files:
|
|
|
363
378
|
- lib/ucode/glyphs/last_resort/source.rb
|
|
364
379
|
- lib/ucode/glyphs/last_resort/svg.rb
|
|
365
380
|
- lib/ucode/glyphs/last_resort/writer.rb
|
|
366
|
-
- lib/ucode/glyphs/monolith_page_map.rb
|
|
367
|
-
- lib/ucode/glyphs/mutool_renderer.rb
|
|
368
|
-
- lib/ucode/glyphs/page_renderer.rb
|
|
369
|
-
- lib/ucode/glyphs/path_bbox.rb
|
|
370
|
-
- lib/ucode/glyphs/pdf2svg_renderer.rb
|
|
371
381
|
- lib/ucode/glyphs/pdf_fetcher.rb
|
|
372
|
-
- lib/ucode/glyphs/pdftocairo_renderer.rb
|
|
373
|
-
- lib/ucode/glyphs/pipeline.rb
|
|
374
382
|
- lib/ucode/glyphs/real_fonts.rb
|
|
375
383
|
- lib/ucode/glyphs/real_fonts/block_coverage.rb
|
|
376
384
|
- lib/ucode/glyphs/real_fonts/cmap_cache.rb
|
|
@@ -380,6 +388,7 @@ files:
|
|
|
380
388
|
- lib/ucode/glyphs/real_fonts/unicode_17_blocks.rb
|
|
381
389
|
- lib/ucode/glyphs/real_fonts/writer.rb
|
|
382
390
|
- lib/ucode/glyphs/resolver.rb
|
|
391
|
+
- lib/ucode/glyphs/resolver_factory.rb
|
|
383
392
|
- lib/ucode/glyphs/source.rb
|
|
384
393
|
- lib/ucode/glyphs/source_builder.rb
|
|
385
394
|
- lib/ucode/glyphs/source_config.rb
|
|
@@ -397,7 +406,6 @@ files:
|
|
|
397
406
|
- lib/ucode/glyphs/universal_set/manifest_writer.rb
|
|
398
407
|
- lib/ucode/glyphs/universal_set/pre_build_check.rb
|
|
399
408
|
- lib/ucode/glyphs/universal_set/validator.rb
|
|
400
|
-
- lib/ucode/glyphs/writer.rb
|
|
401
409
|
- lib/ucode/index.rb
|
|
402
410
|
- lib/ucode/index_builder.rb
|
|
403
411
|
- lib/ucode/models.rb
|