Primitives for the Compact Font Format container.
CFF (Adobe TN#5176) is the PostScript-outline format carried inside an
OpenType font's CFF table. This module owns the container: INDEX
structures and DICT operand encoding. It deliberately does not interpret what
those operands mean — that is the caller's job, and the two callers want
different things:
Tincture.Font.TTFreads a CFF table for metadata (font name, FontBBox, StemV, weight).Tincture.PDF.Serializerewrites a CFF table when subsetting an embedded OpenType font, which additionally needs byte offsets and sizes so it can patch the top DICT.
Both previously carried their own copy of these primitives, in modules that
never referenced each other. Five of the eight duplicated functions had
already drifted — most consequentially parse_dict_number/1, where operator
255 (16.16 fixed) produced a bare float in one copy and a normalised integer
in the other for the same input bytes. The reconciled behaviour is documented
per function below.
Every function returns :error rather than raising: font files are untrusted
input and a malformed table must degrade, not crash the render.
Summary
Functions
Decode a packed run of INDEX offsets, each off_size bytes wide.
Convert a raw 16.16 fixed-point integer to a number.
Returns true when list never decreases.
Trim a string value read from a font table, returning nil when it is empty.
Parse a single DICT operand from the head of data.
Parse a CFF INDEX from the start of data.
Slice INDEX object data using an already-decoded offset list.
Parse a CFF real (BCD) number, the payload following operator 30.
Types
@type index() :: %{ objects: [binary()], rest: binary(), size: non_neg_integer(), offsets: [non_neg_integer()], objects_data_offset: non_neg_integer() }
A decoded INDEX.
size is the INDEX's total byte length and objects_data_offset the offset
from the start of the INDEX to the first object's data. Both are needed to
patch offsets when rewriting a CFF table; readers can ignore them.
Functions
@spec decode_index_offsets(binary(), 1..4) :: {:ok, [non_neg_integer()]} | :error
Decode a packed run of INDEX offsets, each off_size bytes wide.
off_size is bounded to 1..4 by the CFF specification. The bound is enforced
here rather than left to callers: with off_size == 0 the decode loop would
consume nothing per iteration and never terminate.
Convert a raw 16.16 fixed-point integer to a number.
Whole values come back as integers so that a FontBBox of 1 does not become
1.0 purely because of how it was encoded.
Returns true when list never decreases.
Used to validate INDEX offset tables, where a decrease would mean an object with negative length.
Trim a string value read from a font table, returning nil when it is empty.
Font tables routinely carry padded or blank strings where a field is unset; nil is more useful downstream than an empty binary.
Parse a single DICT operand from the head of data.
Handles every CFF DICT number encoding: single-byte (32..246), two-byte positive (247..250) and negative (251..254), 16-bit (28), 32-bit (29), 16.16 fixed (255) and real/BCD (30).
Operator 255 is normalised through fixed_16_16_to_number/1, so a whole
value comes back as an integer rather than a float. The other historical
copy returned a bare value / 65_536, which made 1 and 1.0 depend on
which module happened to parse the byte.
Parse a CFF INDEX from the start of data.
An INDEX is a count, an offset size, count + 1 offsets, then the object
data. A zero count is legal and yields no objects.
@spec parse_index_objects([non_neg_integer()], pos_integer(), binary()) :: {:ok, [binary()], binary(), non_neg_integer()} | :error
Slice INDEX object data using an already-decoded offset list.
Returns the objects, the remaining binary after the INDEX, and the size of the object data region.
CFF INDEX offsets are 1-based, so the first must be at least 1 and the list must be nondecreasing. Those two conditions together also guarantee every object has a non-negative length, so no per-object check is needed.
@spec parse_real_number(binary()) :: {:ok, number(), binary(), pos_integer()} | :error
Parse a CFF real (BCD) number, the payload following operator 30.
Returns the value, the remaining binary, and how many bytes the encoding consumed. The byte count matters when rewriting a DICT in place, where an operand's span has to be known to patch around it.