Class: Ucode::Glyphs::Writer

Inherits:
Object
  • Object
show all
Includes:
Repo::AtomicWrites
Defined in:
lib/ucode/glyphs/writer.rb

Overview

Writes ‘glyph.svg` for every codepoint in a block by orchestrating the per-block pipeline: render PDF page → detect grid → extract each cell → write atomic file.

The Writer is page-driven: the caller hands it a ‘page_map` (`{ page_num => first_cp_on_that_page }`) so the writer knows what codepoint each detected cell anchor corresponds to. This is the one piece of state the Writer can’t derive on its own — pdftocairo converts the row’s codepoint labels to outlined glyphs, so they aren’t readable as text.

Idempotent: re-runs are no-ops via ‘Repo::AtomicWrites` (byte comparison; same content is skipped). Safe to re-run on the whole output tree.

Atomic: writes go through ‘<path>.tmp` + rename. A crash mid- write leaves either the old file or no file, never a truncated one.

**Placeholder for assigned codepoints with no glyph**: when a codepoint is listed in ‘block.codepoint_ids` but no cell is found on any rendered page, a small placeholder SVG is written so the site can render a “no official glyph” badge. Counted in the tally as `placeholder`.

Pure-ish: takes a renderer instance (defaults to the first available system renderer) and a fetcher; both are injectable for tests. The only I/O is the renderer, the writer’s output_root, and any optional cache.

Instance Method Summary collapse

Methods included from Repo::AtomicWrites

#same_content?, #to_pretty_json, #write_atomic

Constructor Details

#initialize(output_root:, renderer: PageRenderer.default, parallel_workers: 4) ⇒ Writer

Returns a new instance of Writer.

Parameters:

  • output_root (String, Pathname)
  • renderer (Ucode::Glyphs::PageRenderer) (defaults to: PageRenderer.default)

    concrete renderer class

  • parallel_workers (Integer) (defaults to: 4)

    worker pool size for #write_all



54
55
56
57
58
# File 'lib/ucode/glyphs/writer.rb', line 54

def initialize(output_root:, renderer: PageRenderer.default, parallel_workers: 4)
  @output_root = Pathname.new(output_root)
  @renderer = renderer
  @parallel_workers = parallel_workers
end

Instance Method Details

#write_all(specs) ⇒ Hash

Drain a list of block-spec hashes through the worker pool. Each spec has the same shape as #write_block’s kwargs:

{ block:, pdf_path:, page_map: }

Parameters:

  • specs (Array<Hash>)

Returns:

  • (Hash)

    aggregated tally across all blocks



128
129
130
131
132
# File 'lib/ucode/glyphs/writer.rb', line 128

def write_all(specs)
  return drain_inline(specs) if @parallel_workers <= 1

  drain_threaded(specs)
end

#write_block(block:, pdf_path:, page_map:, strict: false) ⇒ Hash

Process every page in ‘page_map`, writing glyph.svg for each codepoint that (a) falls inside the block’s range and (b) has a detectable glyph on the page.

Parameters:

  • block (Ucode::Models::Block)
  • pdf_path (String, Pathname)
  • page_map (Hash{Integer => Integer})

    page_num => first cp on that page

  • strict (Boolean) (defaults to: false)

    raise GlyphError when the PDF is missing or no grid is detected on any page; when false, returns a tally with ‘no_grid` set and writes placeholders for assigned cps.

Returns:

  • (Hash)

    tally { written: N, skipped: N, empty: N, placeholder: N, no_grid: N }



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/ucode/glyphs/writer.rb', line 72

def write_block(block:, pdf_path:, page_map:, strict: false)
  unless pdf_path && Pathname.new(pdf_path).exist?
    raise_missing_pdf!(block, pdf_path) if strict
    return placeholder_pass(block, zero_tally.tap { |h| h[:no_grid] = 1 })
  end

  tally = zero_tally
  page_map.each do |page_num, first_cp|
    merge_tally!(tally, write_page(block: block, pdf_path: pdf_path,
                                    page_num: page_num, first_cp: first_cp))
  end
  placeholder_pass(block, tally)
end

#write_page(block:, pdf_path:, page_num:, first_cp:) ⇒ Hash

Render one page, detect its grid, write every cell whose codepoint falls inside ‘block`’s range.

Parameters:

  • block (Ucode::Models::Block)
  • pdf_path (String, Pathname)
  • page_num (Integer)

    1-based PDF page number

  • first_cp (Integer)

    codepoint of the grid’s top-left cell

Returns:

  • (Hash)

    tally



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/ucode/glyphs/writer.rb', line 94

def write_page(block:, pdf_path:, page_num:, first_cp:)
  svg_doc = render_page(pdf_path, page_num)
  return no_grid_tally unless svg_doc

  grid = GridDetector.detect(svg_doc, block_first_cp: first_cp)
  return no_grid_tally unless grid

  counts = zero_tally
  extractor = CellExtractor.new(svg_doc)
  grid.rows.times do |row|
    grid.columns.times do |col|
      cp = grid.codepoint_at(row, col)
      next unless cp && block.covers?(cp)

      cell_svg = extractor.extract(grid, cp)
      if cell_svg.nil?
        counts[:empty] += 1
        next
      end

      written = write_glyph(block, cp, cell_svg)
      counts[written ? :written : :skipped] += 1
    end
  end
  counts
end