Reads the Compact Font Format table carried by OpenType fonts with PostScript
outlines - the ones whose files begin with OTTO.
Roughly one font in seven uses these rather than TrueType outlines: of 697 fonts surveyed on a development machine, 94 were CFF, which is every Adobe-derived family among them.
CFF stores almost everything in two structures. An INDEX is a count, a table of offsets, and the data those offsets divide up. A DICT is a run of operands followed by the operator they belong to, which is backwards from most formats and the main thing to keep in mind when reading this module.
Glyphs themselves are Type 2 charstrings, interpreted by
Vivid.CFF.Charstring.
Summary
Functions
Parse a DICT into a map of operator to its operands.
Split an INDEX into its objects, returning them and whatever follows.
Parse a CFF table.
Types
Functions
Parse a DICT into a map of operator to its operands.
Operands come before the operator they belong to. Operators are a single byte, except that a byte of 12 escapes into a second byte, in which case the key is a two element tuple.
Example
139 encodes zero, and 17 is the operator saying where the charstrings are.
iex> Vivid.CFF.dict(<<139, 17>>)
%{17 => [0]}
Split an INDEX into its objects, returning them and whatever follows.
Examples
iex> Vivid.CFF.index(<<2::16, 1::8, 1::8, 3::8, 5::8, "ab", "cd", "rest">>)
{["ab", "cd"], "rest"}An empty INDEX is just a zero count.
iex> Vivid.CFF.index(<<0::16, "rest">>)
{[], "rest"}
Parse a CFF table.
Example
A minimal font, built up a piece at a time. The Top DICT is the only part carrying anything: operand 21, then operator 17, meaning "the charstrings are 21 bytes in" - which is exactly where they land once the four INDEXes before them are counted.
iex> header = <<1, 0, 4, 1>>
...> name_index = <<0::16>>
...> top_dict = <<29::8, 21::32, 17::8>>
...> top_dict_index = <<1::16, 1::8, 1::8, byte_size(top_dict) + 1::8>> <> top_dict
...> string_index = <<0::16>>
...> gsubr_index = <<0::16>>
...> charstring = <<139, 139, 21, 14>>
...> charstrings = <<1::16, 1::8, 1::8, byte_size(charstring) + 1::8>> <> charstring
...> {:ok, parsed} =
...> Vivid.CFF.parse(
...> header <> name_index <> top_dict_index <> string_index <> gsubr_index <> charstrings
...> )
...> tuple_size(parsed.charstrings)
1