Module: Fontisan::Ufo::Compile::Cff2

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

Overview

Builds the OpenType CFF2 table from UFO glyphs.

CFF2 is simpler than CFF1: no Name INDEX, String INDEX, Encoding, or Charset. The Top DICT references CharStrings and a Font DICT INDEX (which wraps at least one Font DICT pointing to a Private DICT).

Layout (static font, single Font DICT, empty Private DICT):

Header (5 bytes)
Top DICT (variable — offsets to CharStrings + Font DICT INDEX)
Global Subr INDEX (4 bytes — empty)
CharStrings INDEX (variable)
Font DICT INDEX (variable — wraps one Font DICT)
Font DICT: DICT with Private operator [0, 0] (empty Private)

The Top DICT offsets depend on the Top DICT's own size — a circular dependency resolved with fixed-point iteration (the same pattern as the CFF1 builder).

Constant Summary collapse

CHARSTRINGS_OPERATOR =

CFF2 Top DICT operator encodings.

17
VARIATION_STORE_OPERATOR =

0x11

24
FONT_DICT_INDEX_OPERATOR =

0x18

[12, 36].freeze
PRIVATE_OPERATOR =

0x12

18

Class Method Summary collapse

Class Method Details

.build(_font, glyphs:, variation_store: nil) ⇒ String

Returns CFF2 table bytes.

Parameters:

  • font (Fontisan::Ufo::Font)
  • glyphs (Array<Fontisan::Ufo::Glyph>)

    in gid order

  • variation_store (String, nil) (defaults to: nil)

    ItemVariationStore bytes for variable CFF2 fonts. When present, embedded between the GlobalSubr INDEX and CharStrings INDEX, and referenced from the Top DICT via operator 24.

Returns:

  • (String)

    CFF2 table bytes



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/fontisan/ufo/compile/cff2.rb', line 41

def self.build(_font, glyphs:, variation_store: nil)
  charstrings = glyphs.map { |g| charstring_for(g) }
  global_subr_index = empty_global_subr_index
  font_dict = build_font_dict(private_size: 0, private_offset: 0)
  font_dict_index = Tables::Cff2::IndexBuilder.build([font_dict])
  vs_bytes = variation_store&.b

  # Fixed-point iteration: encode Top DICT, compute offsets, repeat.
  top_dict = encode_top_dict(
    charstrings_offset: 0, font_dict_index_offset: 0,
    variation_store_offset: vs_bytes ? 0 : nil
  )
  layout = compute_layout(top_dict:, charstrings:, global_subr_index:,
                          font_dict_index:, variation_store: vs_bytes)

  10.times do
    top_dict = encode_top_dict(
      charstrings_offset: layout[:charstrings_offset],
      font_dict_index_offset: layout[:font_dict_index_offset],
      variation_store_offset: layout[:variation_store_offset],
    )
    next_layout = compute_layout(top_dict:, charstrings:, global_subr_index:,
                                 font_dict_index:, variation_store: vs_bytes)
    break if same_offsets?(layout, next_layout)

    layout = next_layout
  end

  assemble(layout:)
end