Vivid.OpenType.CMap (vivid v1.0.0)

Copy Markdown View Source

Reads a font's character map: the table which says which glyph draws which codepoint.

Formats 4 and 12 are read. Format 4 reaches essentially every font in circulation - of 697 fonts surveyed on a development machine, 696 carried a format 4 subtable at platform 3, encoding 1 - but it can only address the basic multilingual plane, because its codepoints are sixteen bits. Format 12 uses thirty two, which is what emoji and the rarer CJK characters need.

A font usually carries both, describing the same glyphs twice, so a format 12 subtable is preferred wherever there is one: it's a superset, and choosing the format 4 subtable would silently lose every codepoint above U+FFFF.

Failing that, subtables are preferred in the order Windows Unicode, Unicode, then Windows symbol. Symbol subtables are format 4 like the others, and are how icon fonts address their glyphs, generally somewhere in the U+F000 private use area.

Summary

Functions

Parse a cmap table, returning a map of codepoint to glyph index.

Functions

parse(cmap)

@spec parse(binary()) ::
  {:ok, %{required(char()) => non_neg_integer()}} | {:error, String.t()}

Parse a cmap table, returning a map of codepoint to glyph index.

Codepoints which map to glyph zero are left out, since glyph zero is the .notdef glyph every font uses to mean "no glyph for this".

Examples

A single segment mapping A, B and C to glyphs 2, 3 and 4 by adding a delta to the codepoint.

iex> subtable = <<4::16, 32::16, 0::16, 4::16, 4::16, 1::16, 0::16,
...>              0x43::16, 0xFFFF::16, 0::16, 0x41::16, 0xFFFF::16,
...>              -63::16-signed, 1::16-signed, 0::16, 0::16>>
...> Vivid.OpenType.CMap.parse(<<0::16, 1::16, 3::16, 1::16, 12::32>> <> subtable)
{:ok, %{65 => 2, 66 => 3, 67 => 4}}

A segment which indexes the glyph array instead of adding a delta, which is what a font does when its glyphs aren't in codepoint order.

iex> subtable = <<4::16, 36::16, 0::16, 4::16, 4::16, 1::16, 0::16,
...>              0x42::16, 0xFFFF::16, 0::16, 0x41::16, 0xFFFF::16,
...>              0::16-signed, 1::16-signed, 4::16, 0::16, 7::16, 9::16>>
...> Vivid.OpenType.CMap.parse(<<0::16, 1::16, 3::16, 1::16, 12::32>> <> subtable)
{:ok, %{65 => 7, 66 => 9}}

A format 12 subtable, whose thirty two bit codepoints can reach the emoji. Each group maps a run of codepoints onto a run of glyphs.

iex> subtable = <<12::16, 0::16, 28::32, 0::32, 1::32,
...>              0x1F600::32, 0x1F602::32, 5::32>>
...> Vivid.OpenType.CMap.parse(<<0::16, 1::16, 3::16, 10::16, 12::32>> <> subtable)
{:ok, %{0x1F600 => 5, 0x1F601 => 6, 0x1F602 => 7}}

A font carrying both - which most do - is read through the format 12 subtable, since taking the format 4 one would silently lose everything above U+FFFF. Here the two disagree about A on purpose, so you can tell which was used.

iex> format_4 = <<4::16, 32::16, 0::16, 4::16, 4::16, 1::16, 0::16,
...>              0x41::16, 0xFFFF::16, 0::16, 0x41::16, 0xFFFF::16,
...>              -64::16-signed, 1::16-signed, 0::16, 0::16>>
...> format_12 = <<12::16, 0::16, 40::32, 0::32, 2::32,
...>               0x41::32, 0x41::32, 9::32, 0x1F600::32, 0x1F600::32, 5::32>>
...> records = <<3::16, 1::16, 20::32, 3::16, 10::16, 52::32>>
...> Vivid.OpenType.CMap.parse(<<0::16, 2::16>> <> records <> format_4 <> format_12)
{:ok, %{0x41 => 9, 0x1F600 => 5}}