Module: Fontisan::Ufo::Compile::CbdtCblc

Defined in:
lib/fontisan/ufo/compile/cbdt_cblc.rb

Overview

Builds the OpenType CBDT (Color Bitmap Data) and CBLC (Color Bitmap Location) tables.

Together they describe per-strike bitmap strikes for color emoji fonts (Noto Color Emoji, Twemoji Mozilla). Each strike stores a bitmap glyph at a specific ppem + resolution.

This implementation handles the common case: one strike per ppem, with indexSubTable format 3 (variable) and bitmaps stored directly in the CBDT.

Constant Summary collapse

VERSION =
3

Class Method Summary collapse

Class Method Details

.build(strikes:) ⇒ Hash<String,String>

Returns { "CBDT" => bytes, "CBLC" => bytes }.

Parameters:

  • strikes (Array<Hash>)

    each strike with: :ppem (Integer) :resolution (Integer, ppi) :glyphs (Array with :origin_x, :origin_y, :data bytes)

Returns:

  • (Hash<String,String>)

    { "CBDT" => bytes, "CBLC" => bytes }



27
28
29
30
31
# File 'lib/fontisan/ufo/compile/cbdt_cblc.rb', line 27

def self.build(strikes:)
  return nil if strikes.nil? || strikes.empty?

  { "CBDT" => build_cbdt(strikes), "CBLC" => build_cblc(strikes) }
end

.build_cbdt(strikes) ⇒ Object

CBDT: version(2) + numStrikes(4) + strike data



34
35
36
37
38
39
# File 'lib/fontisan/ufo/compile/cbdt_cblc.rb', line 34

def self.build_cbdt(strikes)
  io = +""
  io << [VERSION, strikes.size].pack("nN")
  strikes.each { |s| io << serialize_strike(s) }
  io
end

.build_cblc(strikes) ⇒ Object

CBLC: version(2) + numSizes(4) + size offsets(4*N) + size tables



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/fontisan/ufo/compile/cbdt_cblc.rb', line 42

def self.build_cblc(strikes)
  num_sizes = strikes.size
  header = [VERSION, num_sizes].pack("nN")
  offsets_placeholder = Array.new(num_sizes, 0).pack("N*")
  io = +""
  io << header
  io << offsets_placeholder

  size_table_start = io.bytesize
  strike_offsets = []
  strikes.each do |s|
    strike_offsets << (io.bytesize - size_table_start)
    io << build_sbit_line_metrics(s[:ppem] || 16, s[:glyphs]&.size || 0)
    io << build_index_sub_table_array(s)
  end

  # Patch offsets
  real_offsets = strike_offsets.map { |o| o + size_table_start }
  io[8, real_offsets.pack("N*").bytesize] = real_offsets.pack("N*")

  io
end

.build_index_sub_table_array(strike) ⇒ Object

indexSubTableArray + indexSubTable (format 3, variable)



74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/fontisan/ufo/compile/cbdt_cblc.rb', line 74

def self.build_index_sub_table_array(strike)
  glyphs = strike[:glyphs] || []
  num_glyphs = [glyphs.size, 1].max
  # indexSubTableArray: firstGlyphID(2) + lastGlyphID(2) + additionalOffset(4)
  array_header = [0, num_glyphs - 1, 8].pack("nnN")
  # indexSubTable format 3: format(2) + imageFormat(2) + imageDataOffset(4)
  # + bigGlyphMetrics(8) + offsetArray(numGlyphs × 4)
  subtable = +""
  subtable << [3, 1, 4 + 8 + num_glyphs * 4].pack("nnN")
  subtable << [16, 16, 0, 12, 16, 8, 0, 16].pack("C8")
  offsets = Array.new(num_glyphs) { |i| i * 100 }
  subtable << offsets.pack("N*")
  array_header + subtable
end

.build_sbit_line_metrics(ppem, num_glyphs) ⇒ Object

SbitLineMetrics (12 bytes): height, width, hori/vert bearings



66
67
68
69
70
71
# File 'lib/fontisan/ufo/compile/cbdt_cblc.rb', line 66

def self.build_sbit_line_metrics(ppem, num_glyphs)
  io = +""
  io << [ppem & 0xFF, 0, ppem & 0xFF, 0, 0, 0, ppem & 0xFF, 0, 0, 0, 0].pack("C11")
  io << [12 + num_glyphs * 8].pack("n")
  io
end

.serialize_strike(strike) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
# File 'lib/fontisan/ufo/compile/cbdt_cblc.rb', line 89

def self.serialize_strike(strike)
  glyphs = strike[:glyphs] || []
  io = +""
  glyphs.each do |g|
    next if g.nil?

    io << [g[:origin_x] || 0, g[:origin_y] || 0].pack("nn")
    io << (g[:data] || "")
  end
  io
end