Class: Fontisan::Stitcher::Source

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/stitcher/source.rb

Overview

Wraps a source font (UFO or loaded TTF/OTF) behind a single extraction API used by the selectors.

For UFO sources, glyphs are accessed by name directly. For TTF or OTF sources, individual glyphs are extracted on demand from the BinData tables (glyf/loca/head for TTF, CFF for OTF). This is O(1) per glyph rather than the previous O(n) full-donor conversion.

CBDT/CBLC sources (e.g. NotoColorEmoji) are detected via #bitmap_mode. When a source is :cbdt, the Stitcher propagates the raw CBDT/CBLC tables into the output instead of extracting outlines. The glyph data lives in the bitmap tables, not in glyf.

Codepoint remap

Some donor fonts ship glyphs at non-canonical codepoints — a keyboard-mapped font whose "A" is at U+0041, or a PUA-allocated pre-Unicode font. Pass a remap: hash at construction to expose those glyphs at their canonical codepoints without mutating the donor font's cmap:

Source.new(font, remap: { 0x41 => 0x11DB0 })

With a remap set, the source answers gid_for_codepoint for the target codepoints (0x11DB0 in the example) by translating them to the donor's source codepoints (0x41). Source codepoints not listed in the remap are hidden — only the remapped coverage is exposed. This matches the typical "use this donor for exactly these output characters" intent, and avoids leaking the donor's ASCII/PUA noise into the output.

Constant Summary collapse

MAX_COMPOUND_DEPTH =
32

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(font, remap: nil) ⇒ Source

Returns a new instance of Source.

Parameters:

  • font (TrueTypeFont, OpenTypeFont, Ufo::Font)

    donor font

  • remap (Hash{Integer=>Integer}, nil) (defaults to: nil)

    optional => target_codepoint. When present (even empty), source codepoints not listed are hidden from lookups. Pass nil (the default) for the passthrough behavior — every donor codepoint is exposed as-is.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/fontisan/stitcher/source.rb', line 47

def initialize(font, remap: nil)
  @font = font
  @bin_data_cache = nil

  # Distinguish "no remap kwarg" (nil → passthrough) from
  # "explicit empty remap" ({} → drop everything). The latter
  # still flips the source into remapped mode, just with no
  # entries — every donor codepoint gets filtered out.
  return unless remap

  @remap = remap.to_h.transform_keys(&:to_i).transform_values(&:to_i)
  @inverse_remap = @remap.each_with_object({}) do |(src, target), h|
    h[target] = src
  end
end

Instance Attribute Details

#fontObject (readonly)

Returns the value of attribute font.



39
40
41
# File 'lib/fontisan/stitcher/source.rb', line 39

def font
  @font
end

#remapObject (readonly)

Returns the value of attribute remap.



39
40
41
# File 'lib/fontisan/stitcher/source.rb', line 39

def remap
  @remap
end

Instance Method Details

#bitmap_modeSymbol

Detect how this source stores glyph data.

  • :glyf — TrueType outlines (glyf table present)
  • :cbdt — Color bitmaps (CBDT + CBLC tables, no glyf)
  • :mixed — Both glyf and CBDT
  • :none — UFO source or neither table present

Returns:

  • (Symbol)


81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/fontisan/stitcher/source.rb', line 81

def bitmap_mode
  return :none if @font.is_a?(Fontisan::Ufo::Font)
  return :none unless @font.respond_to?(:has_table?)

  has_cbdt = @font.has_table?("CBDT") && @font.has_table?("CBLC")
  has_glyf = @font.has_table?("glyf") || @font.has_table?("CFF ")
  return :mixed if has_cbdt && has_glyf
  return :cbdt if has_cbdt
  return :glyf if has_glyf

  :none
end

#formatSymbol

Returns :ufo, :ttf, :otf.

Returns:

  • (Symbol)

    :ufo, :ttf, :otf



64
65
66
67
68
69
70
71
# File 'lib/fontisan/stitcher/source.rb', line 64

def format
  case @font
  when Fontisan::Ufo::Font then :ufo
  when Fontisan::TrueTypeFont then :ttf
  when Fontisan::OpenTypeFont then :otf
  else :unknown
  end
end

#gid_for_codepoint(codepoint) ⇒ Integer?

Find the gid for a Unicode codepoint in this source.

When remap was set at construction, codepoint is treated as a target codepoint — the source codepoint that maps to it via remap is what actually gets looked up. Returns nil for codepoints the remap does not cover.

Parameters:

  • codepoint (Integer)

Returns:

  • (Integer, nil)


103
104
105
106
107
108
109
110
111
# File 'lib/fontisan/stitcher/source.rb', line 103

def gid_for_codepoint(codepoint)
  src_cp = source_codepoint_for(codepoint)
  return nil unless src_cp

  case @font
  when Fontisan::Ufo::Font then ufo_gid_for(src_cp)
  else bin_data_gid_for(src_cp)
  end
end

#glyph_for_gid(gid) ⇒ Fontisan::Ufo::Glyph?

Extract a glyph by gid.

For TTF/OTF sources, this is O(1) per glyph: it parses just the requested glyph from glyf/CFF on demand, not the entire donor. The full-donor conversion is avoided entirely.

For CBDT sources, returns a placeholder glyph (no contours) since the glyph data is in the bitmap tables, not outlines.

Parameters:

  • gid (Integer)

Returns:



124
125
126
127
128
129
# File 'lib/fontisan/stitcher/source.rb', line 124

def glyph_for_gid(gid)
  case @font
  when Fontisan::Ufo::Font then ufo_glyph_at(gid)
  else extract_single_glyph_from_bindata(gid)
  end
end

#glyph_width(gid) ⇒ Integer

Width of a specific glyph (extracted from hmtx). Falls back to 0 if hmtx is missing.

Parameters:

  • gid (Integer)

Returns:

  • (Integer)


147
148
149
150
# File 'lib/fontisan/stitcher/source.rb', line 147

def glyph_width(gid)
  widths = bin_data_widths
  widths[gid] || 0
end

#raw_table_bytes(tag) ⇒ String?

Raw table bytes from the loaded font (for passthrough).

Parameters:

  • tag (String)

    4-byte table tag (e.g. "CBDT", "CBLC")

Returns:

  • (String, nil)

    raw bytes or nil if table not present



134
135
136
137
138
139
140
141
# File 'lib/fontisan/stitcher/source.rb', line 134

def raw_table_bytes(tag)
  sfnt_table = @font.table(tag)
  return nil unless sfnt_table

  sfnt_table.raw_data
rescue StandardError
  nil
end