ucode 0.2.1 → 0.2.3

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -2
  3. data/README.md +66 -20
  4. data/Rakefile +18 -7
  5. data/config/unicode17_universal_glyph_set.yml +1 -1
  6. data/lib/ucode/audit/reference_factory.rb +1 -1
  7. data/lib/ucode/cli.rb +10 -42
  8. data/lib/ucode/code_chart/extractor.rb +3 -5
  9. data/lib/ucode/code_chart/provenance.rb +6 -5
  10. data/lib/ucode/code_chart/sidecar.rb +2 -2
  11. data/lib/ucode/code_chart/writer.rb +1 -1
  12. data/lib/ucode/code_chart.rb +1 -1
  13. data/lib/ucode/commands/build.rb +3 -26
  14. data/lib/ucode/commands/canonical_build.rb +1 -4
  15. data/lib/ucode/commands/fetch.rb +1 -1
  16. data/lib/ucode/commands/lookup.rb +1 -1
  17. data/lib/ucode/commands/parse.rb +1 -1
  18. data/lib/ucode/commands.rb +0 -1
  19. data/lib/ucode/coordinator/indices.rb +2 -2
  20. data/lib/ucode/error.rb +0 -8
  21. data/lib/ucode/fetch/code_charts.rb +2 -3
  22. data/lib/ucode/fetch/http.rb +12 -14
  23. data/lib/ucode/glyphs/embedded_fonts/catalog.rb +81 -4
  24. data/lib/ucode/glyphs/embedded_fonts/trace_correlator.rb +230 -0
  25. data/lib/ucode/glyphs/embedded_fonts/trace_glyph.rb +27 -0
  26. data/lib/ucode/glyphs/embedded_fonts/trace_parser.rb +50 -0
  27. data/lib/ucode/glyphs/embedded_fonts/trace_runner.rb +53 -0
  28. data/lib/ucode/glyphs/embedded_fonts.rb +4 -0
  29. data/lib/ucode/glyphs/pdf_fetcher.rb +7 -50
  30. data/lib/ucode/glyphs.rb +4 -14
  31. data/lib/ucode/repo/aggregate_writer.rb +1 -1
  32. data/lib/ucode/repo/writers/blocks_writer.rb +13 -13
  33. data/lib/ucode/repo/writers/enums_writer.rb +2 -2
  34. data/lib/ucode/repo/writers/indexes_writer.rb +4 -4
  35. data/lib/ucode/repo/writers/manifest_writer.rb +4 -4
  36. data/lib/ucode/repo/writers/named_sequences_writer.rb +1 -1
  37. data/lib/ucode/repo/writers/planes_writer.rb +17 -17
  38. data/lib/ucode/repo/writers/relationships_writer.rb +1 -1
  39. data/lib/ucode/repo/writers/scripts_writer.rb +6 -6
  40. data/lib/ucode/repo/writers.rb +1 -1
  41. data/lib/ucode/version.rb +1 -1
  42. data/lib/ucode.rb +0 -2
  43. data/ucode.gemspec +6 -1
  44. metadata +10 -19
  45. data/lib/ucode/commands/glyphs.rb +0 -94
  46. data/lib/ucode/glyphs/cell_extractor.rb +0 -130
  47. data/lib/ucode/glyphs/dvisvgm_renderer.rb +0 -29
  48. data/lib/ucode/glyphs/grid.rb +0 -30
  49. data/lib/ucode/glyphs/grid_detector.rb +0 -165
  50. data/lib/ucode/glyphs/monolith_page_map.rb +0 -181
  51. data/lib/ucode/glyphs/mutool_renderer.rb +0 -28
  52. data/lib/ucode/glyphs/page_renderer.rb +0 -221
  53. data/lib/ucode/glyphs/path_bbox.rb +0 -62
  54. data/lib/ucode/glyphs/pdf2svg_renderer.rb +0 -26
  55. data/lib/ucode/glyphs/pdftocairo_renderer.rb +0 -32
  56. data/lib/ucode/glyphs/pipeline.rb +0 -106
  57. data/lib/ucode/glyphs/writer.rb +0 -250
@@ -34,11 +34,11 @@ module Ucode
34
34
  def write
35
35
  count = 0
36
36
  count += 1 if write_atomic(Paths.names_index_path(@output_root),
37
- to_pretty_json(@names))
37
+ to_pretty_json(@names))
38
38
  count += 1 if write_atomic(Paths.labels_index_path(@output_root),
39
- to_pretty_json(@labels))
39
+ to_pretty_json(@labels))
40
40
  count += 1 if write_atomic(codepoint_to_block_path,
41
- to_pretty_json(@cp_to_block))
41
+ to_pretty_json(@cp_to_block))
42
42
  count
43
43
  end
44
44
 
@@ -50,4 +50,4 @@ module Ucode
50
50
  end
51
51
  end
52
52
  end
53
- end
53
+ end
@@ -45,10 +45,10 @@ module Ucode
45
45
  def write
46
46
  path = Paths.manifest_path(@output_root)
47
47
  content = {
48
- "ucd_version" => @ucd_version,
48
+ "ucd_version" => @ucd_version,
49
49
  "codepoint_count" => @codepoint_count,
50
- "glyph_count" => @glyph_count,
51
- "schema_version" => SCHEMA_VERSION,
50
+ "glyph_count" => @glyph_count,
51
+ "schema_version" => SCHEMA_VERSION,
52
52
  }
53
53
  ts = preserved_or_new_timestamp(path, content)
54
54
  payload = content.merge("generated_at" => ts)
@@ -75,4 +75,4 @@ module Ucode
75
75
  end
76
76
  end
77
77
  end
78
- end
78
+ end
@@ -44,4 +44,4 @@ module Ucode
44
44
  end
45
45
  end
46
46
  end
47
- end
47
+ end
@@ -16,16 +16,16 @@ module Ucode
16
16
  # Static metadata for the 17 Unicode planes. Planes 4–13 are
17
17
  # unassigned in Unicode 17; their entries use placeholder names.
18
18
  PLANE_TABLE = {
19
- 0 => ["Basic Multilingual Plane", "BMP"],
20
- 1 => ["Supplementary Multilingual Plane", "SMP"],
21
- 2 => ["Supplementary Ideographic Plane", "SIP"],
22
- 3 => ["Tertiary Ideographic Plane", "TIP"],
23
- 4 => ["Unassigned Plane 4", "—"],
24
- 5 => ["Unassigned Plane 5", "—"],
25
- 6 => ["Unassigned Plane 6", "—"],
26
- 7 => ["Unassigned Plane 7", "—"],
27
- 8 => ["Unassigned Plane 8", "—"],
28
- 9 => ["Unassigned Plane 9", "—"],
19
+ 0 => ["Basic Multilingual Plane", "BMP"],
20
+ 1 => ["Supplementary Multilingual Plane", "SMP"],
21
+ 2 => ["Supplementary Ideographic Plane", "SIP"],
22
+ 3 => ["Tertiary Ideographic Plane", "TIP"],
23
+ 4 => ["Unassigned Plane 4", "—"],
24
+ 5 => ["Unassigned Plane 5", "—"],
25
+ 6 => ["Unassigned Plane 6", "—"],
26
+ 7 => ["Unassigned Plane 7", "—"],
27
+ 8 => ["Unassigned Plane 8", "—"],
28
+ 9 => ["Unassigned Plane 9", "—"],
29
29
  10 => ["Unassigned Plane 10", "—"],
30
30
  11 => ["Unassigned Plane 11", "—"],
31
31
  12 => ["Unassigned Plane 12", "—"],
@@ -68,15 +68,15 @@ module Ucode
68
68
  range_first = plane_number * 0x10000
69
69
  range_last = range_first + 0xFFFF
70
70
  to_pretty_json(
71
- "number" => plane_number,
72
- "name" => name,
73
- "abbrev" => abbrev,
74
- "range_first" => range_first,
75
- "range_last" => range_last,
76
- "block_ids" => block_ids,
71
+ "number" => plane_number,
72
+ "name" => name,
73
+ "abbrev" => abbrev,
74
+ "range_first" => range_first,
75
+ "range_last" => range_last,
76
+ "block_ids" => block_ids,
77
77
  )
78
78
  end
79
79
  end
80
80
  end
81
81
  end
82
- end
82
+ end
@@ -68,4 +68,4 @@ module Ucode
68
68
  end
69
69
  end
70
70
  end
71
- end
71
+ end
@@ -41,14 +41,14 @@ module Ucode
41
41
 
42
42
  def script_payload(code, ranges)
43
43
  to_pretty_json(
44
- "code" => code,
45
- "name" => ranges.first&.name,
46
- "range_first" => ranges.map(&:range_first).min,
47
- "range_last" => ranges.map(&:range_last).max,
48
- "codepoint_ids" => (@script_codepoint_ids[code] || []),
44
+ "code" => code,
45
+ "name" => ranges.first&.name,
46
+ "range_first" => ranges.map(&:range_first).min,
47
+ "range_last" => ranges.map(&:range_last).max,
48
+ "codepoint_ids" => @script_codepoint_ids[code] || [],
49
49
  )
50
50
  end
51
51
  end
52
52
  end
53
53
  end
54
- end
54
+ end
@@ -17,4 +17,4 @@ module Ucode
17
17
  autoload :ManifestWriter, "ucode/repo/writers/manifest_writer"
18
18
  end
19
19
  end
20
- end
20
+ end
data/lib/ucode/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ucode
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
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"
data/ucode.gemspec CHANGED
@@ -45,7 +45,12 @@ Gem::Specification.new do |spec|
45
45
  spec.require_paths = ["lib"]
46
46
 
47
47
  spec.add_dependency "base64"
48
- spec.add_dependency "fontisan", "~> 0.2"
48
+ # Pin fontisan to 0.2.22 — 0.2.23+ removed
49
+ # `Fontisan::Commands::AuditCommand` (used by RealFonts::CoverageAuditor)
50
+ # and 0.4.x removed the entire Audit subsystem. Until
51
+ # CoverageAuditor is rewritten against the new fontisan API, this
52
+ # pin keeps the existing audit path working.
53
+ spec.add_dependency "fontisan", "= 0.2.22"
49
54
  spec.add_dependency "fontist", "~> 3.0"
50
55
  spec.add_dependency "logger"
51
56
  spec.add_dependency "lutaml-model", "~> 0.8"
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.2.1
4
+ version: 0.2.3
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-06-30 00:00:00.000000000 Z
11
+ date: 2026-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: fontisan
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: 0.2.22
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
40
+ version: 0.2.22
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: fontist
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -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
@@ -342,8 +341,6 @@ files:
342
341
  - lib/ucode/fetch/ucd_zip.rb
343
342
  - lib/ucode/fetch/unihan_zip.rb
344
343
  - lib/ucode/glyphs.rb
345
- - lib/ucode/glyphs/cell_extractor.rb
346
- - lib/ucode/glyphs/dvisvgm_renderer.rb
347
344
  - lib/ucode/glyphs/embedded_fonts.rb
348
345
  - lib/ucode/glyphs/embedded_fonts/catalog.rb
349
346
  - lib/ucode/glyphs/embedded_fonts/content_stream_correlator.rb
@@ -352,9 +349,11 @@ files:
352
349
  - lib/ucode/glyphs/embedded_fonts/source.rb
353
350
  - lib/ucode/glyphs/embedded_fonts/svg.rb
354
351
  - lib/ucode/glyphs/embedded_fonts/tounicode.rb
352
+ - lib/ucode/glyphs/embedded_fonts/trace_correlator.rb
353
+ - lib/ucode/glyphs/embedded_fonts/trace_glyph.rb
354
+ - lib/ucode/glyphs/embedded_fonts/trace_parser.rb
355
+ - lib/ucode/glyphs/embedded_fonts/trace_runner.rb
355
356
  - lib/ucode/glyphs/embedded_fonts/writer.rb
356
- - lib/ucode/glyphs/grid.rb
357
- - lib/ucode/glyphs/grid_detector.rb
358
357
  - lib/ucode/glyphs/last_resort.rb
359
358
  - lib/ucode/glyphs/last_resort/cmap_index.rb
360
359
  - lib/ucode/glyphs/last_resort/contents.rb
@@ -363,14 +362,7 @@ files:
363
362
  - lib/ucode/glyphs/last_resort/source.rb
364
363
  - lib/ucode/glyphs/last_resort/svg.rb
365
364
  - 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
365
  - lib/ucode/glyphs/pdf_fetcher.rb
372
- - lib/ucode/glyphs/pdftocairo_renderer.rb
373
- - lib/ucode/glyphs/pipeline.rb
374
366
  - lib/ucode/glyphs/real_fonts.rb
375
367
  - lib/ucode/glyphs/real_fonts/block_coverage.rb
376
368
  - lib/ucode/glyphs/real_fonts/cmap_cache.rb
@@ -397,7 +389,6 @@ files:
397
389
  - lib/ucode/glyphs/universal_set/manifest_writer.rb
398
390
  - lib/ucode/glyphs/universal_set/pre_build_check.rb
399
391
  - lib/ucode/glyphs/universal_set/validator.rb
400
- - lib/ucode/glyphs/writer.rb
401
392
  - lib/ucode/index.rb
402
393
  - lib/ucode/index_builder.rb
403
394
  - lib/ucode/models.rb
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
-
5
- require "ucode/glyphs"
6
-
7
- module Ucode
8
- module Commands
9
- # `ucode glyphs` — extract per-codepoint SVGs from Code Charts PDFs.
10
- # Thin Thor-facing wrapper around {Ucode::Glyphs::Pipeline}:
11
- # opt-in gate + experimental warning live here; the pipeline
12
- # assembly (block loading, fetcher, per-block specs) lives in
13
- # {Ucode::Glyphs::Pipeline}.
14
- #
15
- # **Status (v0.1): EXPERIMENTAL.** The cell-extraction pipeline
16
- # currently includes cell-border decorations alongside the actual
17
- # character outline because the Code Charts PDFs composite the two
18
- # into a single glyph definition. The output is therefore not yet
19
- # suitable for end-user display. The command is retained so the
20
- # pipeline can be iterated on without churning the CLI surface, but
21
- # callers MUST opt in via `include_glyphs: true` (CLI: `--include-glyphs`)
22
- # and will receive a printed warning. Tracked for v0.2.
23
- #
24
- # Takes a resolved version string; CLI callers resolve via
25
- # {VersionResolver.resolve} once and thread it through. See
26
- # Candidate 4 of the 2026-06-29 architecture review.
27
- class GlyphsCommand
28
- ExperimentalWarning = "ucode glyphs is experimental in v0.1: " \
29
- "extracted SVGs include cell-border decorations " \
30
- "alongside the character outline."
31
- private_constant :ExperimentalWarning
32
-
33
- class << self
34
- # @return [String] the experimental-status banner. Exposed so the
35
- # CLI and BuildCommand surface the same message verbatim.
36
- def experimental_warning
37
- ExperimentalWarning
38
- end
39
- end
40
-
41
- # @param version [String] resolved UCD version
42
- # @param output_root [String, Pathname]
43
- # @param block_filter [Array<String>, nil] block ids to limit to;
44
- # nil = every block
45
- # @param force [Boolean] re-fetch PDFs even when cached
46
- # @param monolith_path [String, Pathname, nil] path to CodeCharts.pdf
47
- # for fallback slicing; defaults to ./CodeCharts.pdf
48
- # @param include_glyphs [Boolean] opt-in for the experimental v0.1
49
- # pipeline. When false (default), the command returns a `skipped`
50
- # payload without touching disk.
51
- # @param warn [IO, nil] when provided, the experimental warning is
52
- # written here exactly once before work begins.
53
- # @return [Hash] aggregated Writer tally + version, or a `skipped`
54
- # payload when opt-in is false.
55
- def call(version, output_root:,
56
- block_filter: nil, force: false,
57
- monolith_path: Glyphs::Pipeline::DEFAULT_MONOLITH_PATH,
58
- include_glyphs: false, warn: nil)
59
- return skipped(version) unless include_glyphs
60
-
61
- warn&.puts(ExperimentalWarning)
62
-
63
- pipeline = Glyphs::Pipeline.new(
64
- version: version,
65
- block_filter: block_filter,
66
- monolith_path: monolith_path,
67
- )
68
- specs = pipeline.build_specs(force: force)
69
-
70
- writer = Glyphs::Writer.new(
71
- output_root: Pathname.new(output_root),
72
- parallel_workers: workers,
73
- )
74
- tally = writer.write_all(specs)
75
- tally.merge(version: version, block_count: specs.size)
76
- end
77
-
78
- private
79
-
80
- def workers
81
- Ucode.configuration.parallel_workers
82
- end
83
-
84
- def skipped(version)
85
- {
86
- version: version,
87
- skipped: true,
88
- reason: :experimental_v0_1,
89
- warning: ExperimentalWarning,
90
- }
91
- end
92
- end
93
- end
94
- end
@@ -1,130 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "nokogiri"
4
-
5
- require "ucode/glyphs/path_bbox"
6
-
7
- module Ucode
8
- module Glyphs
9
- # Extracts a single character cell from a Code Charts SVG page and
10
- # returns a normalized standalone SVG containing only that cell's
11
- # vector paths.
12
- #
13
- # The cell is identified by codepoint. The extractor asks the Grid
14
- # for the cell's anchor position, finds the `<use>` element placed
15
- # at that position, resolves its glyph definition from `<defs>`,
16
- # and emits a fresh `<svg>` whose viewBox is `0 0 1000 1000` and
17
- # whose body is the glyph's `<path>` data translated and scaled to
18
- # fit that viewBox with a small margin.
19
- #
20
- # Vector-only. Never rasterizes, never OCRs. If the cell is empty
21
- # (no character glyph placed there, e.g. unassigned codepoint or
22
- # control character), the extractor returns nil.
23
- class CellExtractor
24
- ViewBoxSize = 1000.0
25
- MarginRatio = 0.1
26
- private_constant :ViewBoxSize, :MarginRatio
27
-
28
- # @param doc [Nokogiri::XML::Document] the rendered Code Charts page
29
- def initialize(doc)
30
- @doc = doc
31
- @glyph_cache = {}
32
- end
33
-
34
- # @param grid [Ucode::Glyphs::Grid]
35
- # @param codepoint [Integer]
36
- # @return [Nokogiri::XML::Document, nil] a standalone `<svg>` doc
37
- # with viewBox `0 0 1000 1000`, or nil if the cell is empty.
38
- def extract(grid, codepoint)
39
- anchor = grid.cell_position(codepoint)
40
- return nil unless anchor
41
-
42
- use_node = find_use_at(anchor, grid)
43
- return nil unless use_node
44
-
45
- path_data = collect_paths(use_node["xlink:href"] || use_node["href"])
46
- return nil if path_data.empty?
47
-
48
- bbox = PathBbox.estimate(path_data.join(" "))
49
- return nil if bbox.empty?
50
-
51
- build_svg(path_data, bbox, use_node["x"].to_f, use_node["y"].to_f)
52
- end
53
-
54
- private
55
-
56
- def find_use_at(anchor, grid)
57
- tolerance_x = grid.column_pitch / 2
58
- tolerance_y = grid.row_pitch / 2
59
-
60
- candidates = @doc.css("use").select do |node|
61
- href = node["xlink:href"] || node["href"] || ""
62
- href.start_with?("#glyph-") &&
63
- (node["x"].to_f - anchor[0]).abs <= tolerance_x &&
64
- (node["y"].to_f - anchor[1]).abs <= tolerance_y
65
- end
66
-
67
- candidates.min_by do |node|
68
- dx = node["x"].to_f - anchor[0]
69
- dy = node["y"].to_f - anchor[1]
70
- (dx * dx) + (dy * dy)
71
- end
72
- end
73
-
74
- def collect_paths(href)
75
- return [] unless href
76
-
77
- glyph_id = href.sub(/\A#/, "")
78
- node = glyph_definition(glyph_id)
79
- return [] unless node
80
-
81
- node.css("path").map { |p| p["d"] }.compact
82
- end
83
-
84
- def glyph_definition(glyph_id)
85
- return @glyph_cache[glyph_id] if @glyph_cache.key?(glyph_id)
86
-
87
- @glyph_cache[glyph_id] = @doc.at_css("defs ##{glyph_id}")
88
- end
89
-
90
- def build_svg(path_data, glyph_bbox, place_x, place_y)
91
- placed = PathBbox::Result.new(
92
- min_x: place_x + glyph_bbox.min_x,
93
- min_y: place_y + glyph_bbox.min_y,
94
- max_x: place_x + glyph_bbox.max_x,
95
- max_y: place_y + glyph_bbox.max_y,
96
- )
97
-
98
- width = placed.width
99
- height = placed.height
100
- return nil if width <= 0 || height <= 0
101
-
102
- content_size = ViewBoxSize * (1.0 - (2.0 * MarginRatio))
103
- scale = [content_size / width, content_size / height].min
104
- offset_x = (ViewBoxSize - (width * scale)) / 2.0
105
- offset_y = (ViewBoxSize - (height * scale)) / 2.0
106
- translate_x = offset_x - (placed.min_x * scale)
107
- translate_y = offset_y - (placed.min_y * scale)
108
-
109
- builder = Nokogiri::XML::Document.new
110
- root = builder.create_element(
111
- "svg",
112
- xmlns: "http://www.w3.org/2000/svg",
113
- viewBox: "0 0 #{ViewBoxSize.to_i} #{ViewBoxSize.to_i}",
114
- width: ViewBoxSize.to_i,
115
- height: ViewBoxSize.to_i,
116
- )
117
- group = builder.create_element(
118
- "g",
119
- transform: "scale(#{format('%.6f', scale)}) translate(#{format('%.6f', translate_x)}, #{format('%.6f', translate_y)})",
120
- )
121
- path_data.each do |d|
122
- group.add_child(builder.create_element("path", d: d, fill: "black"))
123
- end
124
- root.add_child(group)
125
- builder.add_child(root)
126
- builder
127
- end
128
- end
129
- end
130
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "ucode/glyphs/page_renderer"
4
-
5
- module Ucode
6
- module Glyphs
7
- # `dvisvgm` — originally a DVI-to-SVG converter, also handles PDF.
8
- # The `--no-fonts` flag forces outline-only output (no font subsetting
9
- # artifacts), which is what we want for vector glyph extraction.
10
- #
11
- # Command: `dvisvgm --pdf --no-fonts --page=<n> <in.pdf> -o <out.svg>`
12
- class DvisvgmRenderer < PageRenderer
13
- class << self
14
- def renderer_name
15
- :dvisvgm
16
- end
17
-
18
- def binary_name
19
- :dvisvgm
20
- end
21
-
22
- def build_command(pdf_path, page_num, out_path)
23
- ["dvisvgm", "--pdf", "--no-fonts", "--page=#{page_num}",
24
- pdf_path.to_s, "-o", out_path.to_s]
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ucode
4
- module Glyphs
5
- Grid = Struct.new(
6
- :origin_x, :origin_y,
7
- :column_pitch, :row_pitch,
8
- :columns, :rows,
9
- :block_first_cp,
10
- keyword_init: true,
11
- ) do
12
- def cell_position(codepoint)
13
- offset = codepoint - block_first_cp
14
- return nil if offset.negative?
15
-
16
- row, col = offset.divmod(columns)
17
- return nil if row >= rows
18
-
19
- [origin_x + (col * column_pitch), origin_y + (row * row_pitch)]
20
- end
21
-
22
- def codepoint_at(row, col)
23
- return nil if row.negative? || row >= rows
24
- return nil if col.negative? || col >= columns
25
-
26
- block_first_cp + (row * columns) + col
27
- end
28
- end
29
- end
30
- end