Files
pdf-odin-llm/font_cff.odin
T

1825 lines
64 KiB
Odin
Raw Normal View History

2026-09-25 23:32:29 +02:00
package pdf
import "core:math"
import "core:mem"
import "core:slice"
import "core:strconv"
// CFF (Compact Font Format) and its Type 2 charstrings.
//
// This file knows nothing about PDF: bytes in, outlines out. A CFF reaches it
// from three directions -- a /FontFile3 with /Subtype /Type1C or /CIDFontType0C,
// the `CFF ` table of an OpenType font, and a bare .cff file -- and none of
// those distinctions survive the call. That is what keeps it separately
// testable, and it is why nothing here may reference Document, Dict or Object.
//
// References are to Adobe's "The Compact Font Format Specification" (technical
// note 5176, version 1.0, 4 December 2003) and "The Type 2 Charstring Format"
// (technical note 5177, 16 March 2000), cited below as CFF and T2.
//
// Subsetters mangle font programs more thoroughly than any other part of a
// PDF: offsets that point past the end, INDEXes whose offset arrays run
// backwards, charstrings that call subroutines that do not exist, and
// occasionally two subroutines that call each other. Every structure read here
// is therefore clamped rather than trusted, and the interpreter is bounded in
// depth, stack and total operations. A glyph that cannot be decoded draws what
// was decoded before the fault; it never takes the page down with it.
//
// MEMORY: `cff_parse` takes an `allocator` and keeps from it the Cff_Font, the
// offset array of every INDEX it reads, and the per-glyph charset and lookup
// tables. Everything else -- names, charstrings, subroutines -- is a sub-slice
// of `data`, which the caller owns and must keep alive for as long as the font.
// There is no destructor: the intended allocator is the Document's arena, and
// a failed parse likewise leaves what it had already allocated for the arena to
// reclaim. Reading a glyph allocates nothing at all; the interpreter's state is
// the caller's stack frame.
// ---------------------------------------------------------------------------
// Limits
//
// Everything below is bounded by a value that came out of the file, so every
// one of them is capped. The charstring limits are the spec's own; the rest
// are chosen to be far above any real font and far below anything that hurts.
// ---------------------------------------------------------------------------
// T2 3.1: "the maximum depth of subroutine calls is 10".
@(private)
CFF_MAX_SUBR_DEPTH :: 10
// T2 Appendix B: the interpreter's operand stack holds 48 numbers.
@(private)
CFF_MAX_STACK :: 48
// Total charstring operators executed for one glyph, seac and all subroutine
// calls included. Subroutines cannot loop, but two of them may call each other,
// and the depth cap alone still allows 10 nested copies of a long charstring.
@(private)
CFF_MAX_CHARSTRING_OPS :: 100_000
// T2 4: the transient array is 32 elements.
@(private)
CFF_MAX_TRANSIENT :: 32
// CFF 4: "operators may be preceded by up to a maximum of 48 operands".
@(private)
CFF_MAX_DICT_OPERANDS :: 48
// Longest real number a DICT may spell out, in characters. Reals in a DICT are
// font matrices and blue values; 64 digits is already absurd.
@(private)
CFF_MAX_REAL_CHARS :: 64
// FDSelect stores the font-DICT index in one byte, so no more than 256 of them
// can ever be selected however many the FDArray lists.
@(private)
CFF_MAX_FDS :: 256
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
// Cff_Index is a CFF INDEX: a counted array of variable-length items.
//
// `offsets` holds count+1 *absolute* positions into `data`, already clamped to
// be non-decreasing and inside the font, so `cff_index_item` needs no further
// validation and can never produce a slice that escapes the font.
@(private)
Cff_Index :: struct {
data: []byte,
offsets: []u32,
count: int,
// First byte after the INDEX, so the next one can be read from here.
end: int,
}
// Cff_Private is one Private DICT: the widths every charstring in its scope is
// relative to, and the local subroutines it may call. A non-CID font has one;
// a CIDFont has one per entry in the FDArray.
@(private)
Cff_Private :: struct {
subrs: Cff_Index,
subr_bias: int,
default_width: f64,
nominal_width: f64,
}
// Cff_Name_Entry is one (glyph name, GID) pair of the by-name lookup table,
// which is sorted so `cff_gid_for_name` is a binary search rather than a walk
// over every glyph in the font for every code in the encoding.
@(private)
Cff_Name_Entry :: struct {
name: string,
gid: u16,
}
// Cff_Font is the parsed form of a font program.
//
// `data` is borrowed, not copied: every name, every charstring and every
// subroutine is a sub-slice of it, so it must stay alive and unmodified for as
// long as the font does. Everything else comes from the allocator passed to
// `cff_parse`.
Cff_Font :: struct {
data: []byte,
// The four INDEXes at the head of the file.
names: Cff_Index,
top_dicts: Cff_Index,
strings: Cff_Index,
gsubrs: Cff_Index,
gsubr_bias: int,
// The glyph outlines themselves.
charstrings: Cff_Index,
// Top DICT.
font_matrix: Matrix,
font_bbox: Rect,
charstring_type: int,
is_cid: bool,
// The Private DICT of a non-CID font, or of FD 0 when there is no
// FDSelect. `fds` and `fd_select` are empty for a non-CID font.
priv: Cff_Private,
fds: []Cff_Private,
// One font-DICT index per glyph. Empty means every glyph uses fds[0], or
// `priv` when there is no FDArray either.
fd_select: []u8,
// charset: SID per glyph for a name-keyed font, CID per glyph for a
// CIDFont. Always `glyph_count` long once parsing succeeds.
charset: []u16,
// Sorted lookup tables. Exactly one of the two is built, according to what
// the charset holds.
by_name: []Cff_Name_Entry,
// (cid << 16) | gid, sorted, so a binary search on the high half finds the
// lowest GID for a CID.
by_cid: []u32,
// The font's built-in encoding: the glyph each byte code selects, and the
// name it selects it by. A CIDFont has neither (CFF 12: "CIDFonts do not
// use the Encoding operator").
code_to_gid: [256]u16,
enc_names: [256]string,
}
// ---------------------------------------------------------------------------
// Bounds-checked readers
//
// Every one of these returns zero past the end rather than trapping. Structural
// code checks the length it needs before it reads, so a zero here is always a
// value the file really contains -- except in the recovery paths, where zero is
// exactly the harmless answer wanted.
// ---------------------------------------------------------------------------
@(private)
cff_u16 :: proc "contextless" (d: []byte, p: int) -> u16 {
if p < 0 || p + 1 >= len(d) { return 0 }
return u16(d[p]) << 8 | u16(d[p + 1])
}
// cff_offset reads a big-endian unsigned value `size` bytes wide, as INDEX
// offset arrays and DICT offsets are written.
@(private)
cff_offset :: proc "contextless" (d: []byte, p: int, size: int) -> u32 {
if size < 1 || size > 4 || p < 0 || p + size > len(d) { return 0 }
v: u32 = 0
for i in 0 ..< size {
v = v << 8 | u32(d[p + i])
}
return v
}
// ---------------------------------------------------------------------------
// INDEX
// ---------------------------------------------------------------------------
// cff_index_read parses the INDEX at `pos`. The offset array is materialised as
// absolute positions so that later reads need no arithmetic and no checks.
//
// A count of zero is the whole structure: CFF 5 says an empty INDEX is two
// bytes with no offSize byte at all, and writing the extra byte anyway is a
// mistake that would push every following structure one byte out.
@(private)
cff_index_read :: proc(data: []byte, pos: int, allocator: mem.Allocator) -> (idx: Cff_Index, err: Error) {
idx.data = data
idx.end = pos
if pos < 0 || pos + 2 > len(data) {
return idx, .Truncated
}
count := int(cff_u16(data, pos))
idx.end = pos + 2
if count == 0 {
return idx, .None
}
if pos + 3 > len(data) {
return idx, .Truncated
}
off_size := int(data[pos + 2])
if off_size < 1 || off_size > 4 {
return idx, .Bad_Cff
}
table := pos + 3
// count <= 65535 and off_size <= 4, so this cannot overflow, and checking
// it against the file is what bounds the allocation below.
need := (count + 1) * off_size
if need > len(data) - table {
return idx, .Truncated
}
// CFF 5: offsets are relative to the byte preceding the item data, so the
// first is always 1.
base := table + need - 1
limit := u32(len(data) - base)
offsets := make([]u32, count + 1, allocator)
prev: u32 = 1
for i in 0 ..= count {
v := cff_offset(data, table + i * off_size, off_size)
// Clamping instead of rejecting: a subset font with one bad offset
// still draws every other glyph, and the item this produces is empty
// rather than out of bounds.
if v < prev { v = prev }
if v > limit { v = limit }
offsets[i] = u32(base) + v
prev = v
}
idx.offsets = offsets
idx.count = count
idx.end = int(offsets[count])
return idx, .None
}
@(private)
cff_index_item :: proc(idx: ^Cff_Index, i: int) -> []byte {
if i < 0 || i >= idx.count || len(idx.offsets) < idx.count + 1 {
return nil
}
lo, hi := int(idx.offsets[i]), int(idx.offsets[i + 1])
if lo > hi || hi > len(idx.data) {
return nil
}
return idx.data[lo:hi]
}
// cff_subr_bias is the constant added to a callsubr/callgsubr index before it
// selects an entry. T2 4.7: subroutine numbers are biased so that the small
// single-byte encodings reach the middle of the array, where a subsetter puts
// the subroutines it uses most.
@(private)
cff_subr_bias :: proc "contextless" (count: int) -> int {
switch {
case count < 1240: return 107
case count < 33900: return 1131
case: return 32768
}
}
// ---------------------------------------------------------------------------
// DICT
//
// A DICT is a flat byte string of operands followed by the operator they
// belong to -- postfix, with no nesting and no keys other than the operator
// number. CFF 4 gives the operand encoding, which is not the same as the one
// charstrings use: a DICT has the 5-byte 29 integer and the nibble-packed 30
// real, and no 16.16 fixed.
// ---------------------------------------------------------------------------
// Operators are keyed as their byte, with the two-byte "12 x" escapes carried
// as 0x0C00 | x, so one switch covers both forms.
@(private)
CFF_ESC :: 0x0C00
@(private)
Cff_Dict_Iter :: struct {
data: []byte,
pos: int,
ops: [CFF_MAX_DICT_OPERANDS]f64,
n: int,
}
// cff_dict_next advances to the next operator, leaving its operands in
// `it.ops[:it.n]`. It returns ok = false at the end of the DICT and at the
// first byte that cannot be a valid operand or operator, because past that
// point the operand/operator alternation has lost sync and anything read would
// be invented.
@(private)
cff_dict_next :: proc(it: ^Cff_Dict_Iter) -> (op: u16, ok: bool) {
it.n = 0
for it.pos < len(it.data) {
b0 := it.data[it.pos]
switch {
case b0 == 12:
if it.pos + 1 >= len(it.data) { return 0, false }
op = CFF_ESC | u16(it.data[it.pos + 1])
it.pos += 2
return op, true
case b0 <= 21:
// 22..27 and 31 are reserved and never appear as operators.
if b0 >= 22 { return 0, false }
op = u16(b0)
it.pos += 1
return op, true
case b0 == 28:
if it.pos + 2 >= len(it.data) { return 0, false }
v := i16(u16(it.data[it.pos + 1]) << 8 | u16(it.data[it.pos + 2]))
cff_dict_push(it, f64(v))
it.pos += 3
case b0 == 29:
if it.pos + 4 >= len(it.data) { return 0, false }
u := u32(it.data[it.pos + 1]) << 24 | u32(it.data[it.pos + 2]) << 16 |
u32(it.data[it.pos + 3]) << 8 | u32(it.data[it.pos + 4])
cff_dict_push(it, f64(i32(u)))
it.pos += 5
case b0 == 30:
v, next := cff_dict_real(it.data, it.pos + 1)
cff_dict_push(it, v)
it.pos = next
case b0 >= 32 && b0 <= 246:
cff_dict_push(it, f64(int(b0) - 139))
it.pos += 1
case b0 >= 247 && b0 <= 250:
if it.pos + 1 >= len(it.data) { return 0, false }
cff_dict_push(it, f64((int(b0) - 247) * 256 + int(it.data[it.pos + 1]) + 108))
it.pos += 2
case b0 >= 251 && b0 <= 254:
if it.pos + 1 >= len(it.data) { return 0, false }
cff_dict_push(it, f64(-(int(b0) - 251) * 256 - int(it.data[it.pos + 1]) - 108))
it.pos += 2
case:
// 31 and 255 are reserved operand bytes.
return 0, false
}
}
return 0, false
}
// cff_dict_push drops operands past the 48th rather than growing: the format
// forbids more, and every operator this reader cares about takes its arguments
// from the front.
@(private)
cff_dict_push :: proc(it: ^Cff_Dict_Iter, v: f64) {
if it.n >= CFF_MAX_DICT_OPERANDS { return }
it.ops[it.n] = is_finite(v) ? v : 0
it.n += 1
}
// cff_dict_real decodes the nibble-packed real of CFF Table 5, returning the
// value and the position just past its terminating nibble. A malformed one
// yields whatever prefix parsed, which is what keeps a damaged /FontMatrix from
// costing the whole font.
@(private)
cff_dict_real :: proc(d: []byte, start: int) -> (value: f64, next: int) {
buf: [CFF_MAX_REAL_CHARS]u8
n := 0
put :: proc(buf: ^[CFF_MAX_REAL_CHARS]u8, n: ^int, c: u8) {
if n^ < len(buf) {
buf[n^] = c
n^ += 1
}
}
p := start
done := false
for p < len(d) && !done {
b := d[p]
p += 1
for shift := uint(4); ; shift -= 4 {
nib := (b >> shift) & 0xF
switch nib {
case 0x0 ..= 0x9: put(&buf, &n, u8('0') + nib)
case 0xA: put(&buf, &n, '.')
case 0xB: put(&buf, &n, 'E')
case 0xC: put(&buf, &n, 'E'); put(&buf, &n, '-')
case 0xD: // reserved; skip
case 0xE: put(&buf, &n, '-')
case 0xF: done = true
}
if shift == 0 || done { break }
}
}
v, ok := strconv.parse_f64(string(buf[:n]))
if !ok || !is_finite(v) {
return 0, p
}
return v, p
}
// ---------------------------------------------------------------------------
// Top DICT and Private DICT
// ---------------------------------------------------------------------------
@(private)
Cff_Top :: struct {
charset: int,
encoding: int,
charstrings: int,
priv_size: int,
priv_off: int,
fdarray: int,
fdselect: int,
is_cid: bool,
charstring_type: int,
font_matrix: Matrix,
has_matrix: bool,
font_bbox: Rect,
}
@(private)
cff_top_dict_parse :: proc(d: []byte) -> (top: Cff_Top) {
top.charset = -1
top.encoding = -1
top.charstrings = -1
top.fdarray = -1
top.fdselect = -1
top.priv_off = -1
top.charstring_type = 2
// CFF Table 9: the default FontMatrix is 0.001 units per em.
top.font_matrix = Matrix{0.001, 0, 0, 0.001, 0, 0}
it := Cff_Dict_Iter{data = d}
for {
op, ok := cff_dict_next(&it)
if !ok { break }
switch op {
case 15: if it.n >= 1 { top.charset = cff_dict_int(&it, 0) }
case 16: if it.n >= 1 { top.encoding = cff_dict_int(&it, 0) }
case 17: if it.n >= 1 { top.charstrings = cff_dict_int(&it, 0) }
case 18:
if it.n >= 2 {
top.priv_size = cff_dict_int(&it, 0)
top.priv_off = cff_dict_int(&it, 1)
}
case 5:
if it.n >= 4 {
top.font_bbox = rect_normalize(Rect{it.ops[0], it.ops[1], it.ops[2], it.ops[3]})
}
case CFF_ESC | 6: if it.n >= 1 { top.charstring_type = cff_dict_int(&it, 0) }
case CFF_ESC | 7:
if it.n >= 6 {
m := Matrix{it.ops[0], it.ops[1], it.ops[2], it.ops[3], it.ops[4], it.ops[5]}
if matrix_is_finite(m) {
top.font_matrix = m
top.has_matrix = true
}
}
case CFF_ESC | 30: top.is_cid = true // ROS
case CFF_ESC | 36: if it.n >= 1 { top.fdarray = cff_dict_int(&it, 0) }
case CFF_ESC | 37: if it.n >= 1 { top.fdselect = cff_dict_int(&it, 0) }
}
}
return
}
// cff_dict_int reads an operand as a file offset or count. Values outside
// int32 cannot be a position in a font and become -1, which every caller
// already treats as "absent".
@(private)
cff_dict_int :: proc(it: ^Cff_Dict_Iter, i: int) -> int {
if i < 0 || i >= it.n { return -1 }
v := it.ops[i]
if !is_finite(v) || v < f64(min(i32)) || v > f64(max(i32)) { return -1 }
return int(v)
}
// cff_private_parse reads the Private DICT at (off, size) and the local Subrs
// INDEX it points at. Subrs is an offset from the start of the Private DICT
// itself, not from the start of the font (CFF Table 23).
@(private)
cff_private_parse :: proc(data: []byte, off, size: int, allocator: mem.Allocator) -> (p: Cff_Private) {
p.subr_bias = cff_subr_bias(0)
if off < 0 || size <= 0 || off >= len(data) { return }
end := off + size
if end > len(data) || end < off { end = len(data) }
subrs_off := -1
it := Cff_Dict_Iter{data = data[off:end]}
for {
op, ok := cff_dict_next(&it)
if !ok { break }
switch op {
case 19: if it.n >= 1 { subrs_off = cff_dict_int(&it, 0) }
case 20: if it.n >= 1 { p.default_width = it.ops[0] }
case 21: if it.n >= 1 { p.nominal_width = it.ops[0] }
}
}
if subrs_off > 0 && off + subrs_off < len(data) {
idx, err := cff_index_read(data, off + subrs_off, allocator)
if err == .None {
p.subrs = idx
}
}
p.subr_bias = cff_subr_bias(p.subrs.count)
return
}
// ---------------------------------------------------------------------------
// The standard strings
//
// CFF Appendix A. SIDs below 391 name one of these instead of indexing the
// String INDEX, which is how a font that uses only ISO-Adobe glyph names
// carries no strings at all. The order is the specification's and is load
// bearing: the SID is the position.
// ---------------------------------------------------------------------------
@(private)
CFF_N_STD_STRINGS :: 391
@(private)
CFF_STD_STRINGS := [CFF_N_STD_STRINGS]string{
".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent",
"ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus",
"comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine", "colon", "semicolon", "less",
"equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright",
"asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g",
"h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde",
"exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section",
"currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft",
"guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl",
"periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase",
"quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown",
"grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent",
"dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash",
"AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae",
"dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior",
"logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn",
"onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters",
"twosuperior", "registered", "minus", "eth", "multiply", "threesuperior",
"copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring",
"Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron",
"aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde",
"ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute",
"icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex",
"odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex",
"udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall",
"Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall",
"Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
"onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle",
"threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
"sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior",
"threequartersemdash", "periodsuperior", "questionsmall", "asuperior",
"bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
"tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior",
"Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall",
"Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall",
"Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall",
"Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah",
"Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall",
"Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall",
"Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior",
"Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall",
"oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird",
"twothirds", "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior",
"sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior",
"oneinferior", "twoinferior", "threeinferior", "fourinferior",
"fiveinferior", "sixinferior", "seveninferior", "eightinferior",
"nineinferior", "centinferior", "dollarinferior", "periodinferior",
"commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall",
"Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall",
"Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall",
"Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall",
"Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall",
"Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall",
"Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall",
"Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000",
"001.001", "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium",
"Regular", "Roman", "Semibold",
}
// cff_sid_string resolves a SID to its glyph or text name. An out-of-range SID
// yields "" rather than a name from somewhere else in the font.
@(private)
cff_sid_string :: proc(f: ^Cff_Font, sid: u16) -> string {
if int(sid) < CFF_N_STD_STRINGS {
return CFF_STD_STRINGS[sid]
}
idx := &f.strings
item := cff_index_item(idx, int(sid) - CFF_N_STD_STRINGS)
return string(item)
}
// ---------------------------------------------------------------------------
// charset
// ---------------------------------------------------------------------------
// cff_charset_parse fills gid -> SID (or, for a CIDFont, gid -> CID).
//
// Offsets 0, 1 and 2 name the predefined ISOAdobe, Expert and ExpertSubset
// charsets (CFF Appendix C). Only ISOAdobe is tabulated, as the identity it
// is; a font using one of the two Expert charsets loses its glyph *names*, and
// so any lookup by name, but keeps every GID and therefore every outline. No
// PDF-embedded CFF in the corpus uses them, and a table recited wrongly would
// be worse than one that is honestly absent.
@(private)
cff_charset_parse :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) {
// Every per-glyph allocation in this file is sized by the CharStrings
// count, which an INDEX writes as a Card16 and so can never exceed 65535
// however damaged the font is.
n := f.charstrings.count
if n <= 0 { return }
cs := make([]u16, n, allocator)
f.charset = cs
if off <= 2 {
// Identity: SID i for glyph i, which is exactly ISOAdobe for the 229
// glyphs it defines, and the identity CID mapping a CIDFont with no
// charset needs.
limit := n
if !f.is_cid && limit > 229 { limit = 229 }
for i in 0 ..< limit {
cs[i] = u16(i)
}
return
}
if off >= len(f.data) { return }
d := f.data
format := d[off]
p := off + 1
// Glyph 0 is always .notdef and is not listed.
gid := 1
switch format {
case 0:
for gid < n && p + 1 < len(d) {
cs[gid] = cff_u16(d, p)
p += 2
gid += 1
}
case 1, 2:
step := format == 1 ? 3 : 4
for gid < n && p + step <= len(d) {
first := int(cff_u16(d, p))
n_left := format == 1 ? int(d[p + 2]) : int(cff_u16(d, p + 2))
p += step
for i := 0; i <= n_left && gid < n; i += 1 {
v := first + i
// SIDs and CIDs are 16 bit; a range that would run past that
// is a corrupt one, so it stops here instead of wrapping.
if v > 0xFFFF { break }
cs[gid] = u16(v)
gid += 1
}
}
case:
// An unknown format leaves every glyph nameless, which is recoverable:
// outlines are indexed by GID and do not need the charset at all.
}
}
// ---------------------------------------------------------------------------
// Encoding
// ---------------------------------------------------------------------------
// cff_encoding_parse fills the code -> glyph tables from the font's built-in
// Encoding. CFF 12 gives offsets 0 and 1 to the predefined Standard and Expert
// encodings; anything larger is a custom encoding in format 0 or 1, either of
// which may carry supplements.
@(private)
cff_encoding_parse :: proc(f: ^Cff_Font, off: int) {
// CIDFonts have no encoding at all: their charstrings are selected by CID.
if f.is_cid { return }
n := f.charstrings.count
if off <= 1 || off >= len(f.data) {
// Standard (and, for want of a table, Expert) encoding: the code names
// a glyph, and the charset says which glyph carries that name.
std := encoding_standard()
for code in 0 ..< 256 {
name := std[code]
if name == "" { continue }
gid, ok := cff_gid_for_name(f, name)
if !ok { continue }
f.code_to_gid[code] = gid
f.enc_names[code] = name
}
return
}
d := f.data
format := d[off]
p := off + 1
switch format & 0x7F {
case 0:
if p >= len(d) { return }
n_codes := int(d[p])
p += 1
for i in 0 ..< n_codes {
if p >= len(d) { break }
code := int(d[p])
p += 1
gid := i + 1
if gid < n {
f.code_to_gid[code] = u16(gid)
}
}
case 1:
if p >= len(d) { return }
n_ranges := int(d[p])
p += 1
gid := 1
for i := 0; i < n_ranges && p + 1 < len(d); i += 1 {
first := int(d[p])
n_left := int(d[p + 1])
p += 2
for k := 0; k <= n_left && gid < n; k += 1 {
code := first + k
if code > 255 { break }
f.code_to_gid[code] = u16(gid)
gid += 1
}
}
case:
return
}
// CFF 12: the high bit of the format byte says a supplement list follows,
// mapping extra codes to glyphs by NAME rather than by position.
if format & 0x80 != 0 && p < len(d) {
n_sups := int(d[p])
p += 1
for i := 0; i < n_sups && p + 2 < len(d); i += 1 {
code := int(d[p])
sid := cff_u16(d, p + 1)
p += 3
name := cff_sid_string(f, sid)
if name == "" { continue }
f.enc_names[code] = name
if gid, ok := cff_gid_for_name(f, name); ok {
f.code_to_gid[code] = gid
}
}
}
// Fill in the names of everything the format-0/1 part mapped by position.
for code in 0 ..< 256 {
if f.enc_names[code] == "" && f.code_to_gid[code] != 0 {
f.enc_names[code] = cff_glyph_name(f, f.code_to_gid[code])
}
}
}
// ---------------------------------------------------------------------------
// FDSelect and FDArray (CIDFonts)
// ---------------------------------------------------------------------------
// cff_fdselect_parse fills one font-DICT index per glyph. A glyph the table
// does not cover keeps index 0, which is the only safe guess: it at least has
// a Private DICT.
@(private)
cff_fdselect_parse :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) {
n := f.charstrings.count
if n <= 0 || off <= 0 || off >= len(f.data) { return }
d := f.data
format := d[off]
sel := make([]u8, n, allocator)
f.fd_select = sel
switch format {
case 0:
p := off + 1
for gid in 0 ..< n {
if p + gid >= len(d) { break }
sel[gid] = d[p + gid]
}
case 3:
n_ranges := int(cff_u16(d, off + 1))
p := off + 3
// Each range is 3 bytes and the array is followed by a 2-byte sentinel.
for i := 0; i < n_ranges && p + 4 < len(d); i += 1 {
first := int(cff_u16(d, p))
fd := d[p + 2]
next := int(cff_u16(d, p + 3))
p += 3
if next > n { next = n }
for gid := first; gid < next && gid < n; gid += 1 {
if gid >= 0 {
sel[gid] = fd
}
}
}
case:
// Unknown format: every glyph falls back to font DICT 0.
}
}
@(private)
cff_fdarray_parse :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) {
if off <= 0 || off >= len(f.data) { return }
idx, err := cff_index_read(f.data, off, allocator)
if err != .None || idx.count == 0 { return }
count := min(idx.count, CFF_MAX_FDS)
fds := make([]Cff_Private, count, allocator)
for i in 0 ..< count {
fd_dict := cff_index_item(&idx, i)
priv_off, priv_size := -1, 0
it := Cff_Dict_Iter{data = fd_dict}
for {
op, ok := cff_dict_next(&it)
if !ok { break }
if op == 18 && it.n >= 2 {
priv_size = cff_dict_int(&it, 0)
priv_off = cff_dict_int(&it, 1)
}
}
fds[i] = cff_private_parse(f.data, priv_off, priv_size, allocator)
}
f.fds = fds
}
// ---------------------------------------------------------------------------
// Parsing the font
// ---------------------------------------------------------------------------
// cff_parse reads a CFF font program.
//
// `data` is borrowed: the returned font holds sub-slices of it for names,
// charstrings and subroutines, so it must outlive the font and must not be
// modified. Everything else the font owns comes from `allocator` and is
// released only by releasing that allocator -- the intended use is an arena,
// which is what the Document hands fonts. That holds for a failed parse too:
// what was allocated before the failure is left for the arena to reclaim.
//
// An error is returned only when there is nothing usable at all: no header, no
// Top DICT, or no CharStrings INDEX. Everything else degrades -- a broken
// charset costs glyph names, a broken Private DICT costs local subroutines and
// the default width, and neither stops the outlines from being read.
@(private)
cff_parse :: proc(data: []byte, allocator := context.allocator) -> (^Cff_Font, Error) {
// CFF 6: the header is major, minor, hdrSize, offSize.
if len(data) < 4 {
return nil, .Truncated
}
if data[0] != 1 {
// CFF2 (major version 2) drops the Name, String and Encoding INDEXes
// and changes the charstring operators. Reading it as CFF1 would
// produce nonsense, so it is refused rather than guessed at.
return nil, .Bad_Cff
}
hdr_size := int(data[2])
// A hdrSize smaller than the header itself is a subsetter bug; the four
// bytes the format defines are still there, so start after them.
if hdr_size < 4 { hdr_size = 4 }
if hdr_size > len(data) {
return nil, .Truncated
}
f := new(Cff_Font, allocator)
f.data = data
f.font_matrix = Matrix{0.001, 0, 0, 0.001, 0, 0}
f.charstring_type = 2
err: Error
f.names, err = cff_index_read(data, hdr_size, allocator)
if err != .None { return nil, err }
f.top_dicts, err = cff_index_read(data, f.names.end, allocator)
if err != .None { return nil, err }
// A damaged String or GlobalSubr INDEX costs names and subroutines but
// nothing structural: the Top DICT gives every other position absolutely.
f.strings, _ = cff_index_read(data, f.top_dicts.end, allocator)
f.gsubrs, _ = cff_index_read(data, f.strings.end, allocator)
f.gsubr_bias = cff_subr_bias(f.gsubrs.count)
top_data := cff_index_item(&f.top_dicts, 0)
if len(top_data) == 0 {
return nil, .Bad_Cff
}
top := cff_top_dict_parse(top_data)
f.is_cid = top.is_cid
f.charstring_type = top.charstring_type
f.font_bbox = top.font_bbox
f.font_matrix = top.font_matrix
if top.charstrings <= 0 || top.charstrings >= len(data) {
return nil, .Bad_Cff
}
f.charstrings, err = cff_index_read(data, top.charstrings, allocator)
if err != .None {
return nil, .Bad_Cff
}
if f.charstrings.count == 0 {
return nil, .Bad_Cff
}
f.priv = cff_private_parse(data, top.priv_off, top.priv_size, allocator)
if top.fdarray > 0 {
cff_fdarray_parse(f, top.fdarray, allocator)
}
if top.fdselect > 0 {
cff_fdselect_parse(f, top.fdselect, allocator)
}
// CFF 10: a CIDFont's FDArray entries may carry their own FontMatrix. When
// the Top DICT has none of its own, the first font DICT's is the font's.
if !top.has_matrix && len(f.fds) > 0 {
if m, ok := cff_fd_matrix(f, top.fdarray, allocator); ok {
f.font_matrix = m
}
}
cff_charset_parse(f, top.charset, allocator)
cff_build_lookups(f, allocator)
cff_encoding_parse(f, top.encoding)
return f, .None
}
// cff_fd_matrix re-reads the FDArray for a FontMatrix on its first entry. It is
// a second pass over a structure already parsed because the matrix is the one
// thing in a font DICT that is not per-glyph, and hoisting it into
// Cff_Private would put it on every entry to serve one.
@(private)
cff_fd_matrix :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) -> (Matrix, bool) {
idx, err := cff_index_read(f.data, off, allocator)
if err != .None || idx.count == 0 { return MATRIX_IDENTITY, false }
it := Cff_Dict_Iter{data = cff_index_item(&idx, 0)}
for {
op, ok := cff_dict_next(&it)
if !ok { break }
if op == CFF_ESC | 7 && it.n >= 6 {
m := Matrix{it.ops[0], it.ops[1], it.ops[2], it.ops[3], it.ops[4], it.ops[5]}
if matrix_is_finite(m) {
return m, true
}
}
}
return MATRIX_IDENTITY, false
}
// cff_build_lookups builds whichever of the two reverse maps the charset calls
// for. Both are sorted arrays rather than hash tables: they are built once,
// searched a few hundred times, and a sorted array of 16-bit keys is smaller
// than the font's own offset array.
@(private)
cff_build_lookups :: proc(f: ^Cff_Font, allocator: mem.Allocator) {
n := len(f.charset)
if n == 0 { return }
if f.is_cid {
table := make([]u32, n, allocator)
for gid in 0 ..< n {
table[gid] = u32(f.charset[gid]) << 16 | u32(gid)
}
// Sorting the packed pair orders by CID and then by GID, so a binary
// search that walks left over equal CIDs lands on the lowest GID.
slice.sort(table)
f.by_cid = table
return
}
table := make([]Cff_Name_Entry, n, allocator)
for gid in 0 ..< n {
table[gid] = Cff_Name_Entry{cff_sid_string(f, f.charset[gid]), u16(gid)}
}
slice.sort_by(table, proc(a, b: Cff_Name_Entry) -> bool {
if a.name != b.name { return a.name < b.name }
return a.gid < b.gid
})
f.by_name = table
}
// ---------------------------------------------------------------------------
// Accessors
//
// Package-private, like every other font-program reader: font.odin is the only
// caller, and the library's public API is the one pdf.odin's doc comment names.
// ---------------------------------------------------------------------------
@(private)
cff_glyph_count :: proc(f: ^Cff_Font) -> int {
if f == nil { return 0 }
return f.charstrings.count
}
// cff_font_matrix maps glyph space to text space. CFF Table 9 defaults it to
// 1/1000, which is what all but a handful of fonts use.
@(private)
cff_font_matrix :: proc(f: ^Cff_Font) -> Matrix {
if f == nil { return Matrix{0.001, 0, 0, 0.001, 0, 0} }
return f.font_matrix
}
// cff_gid_for_name finds the glyph with this name. A CIDFont has no glyph
// names -- its charset holds CIDs -- and always reports false.
@(private)
cff_gid_for_name :: proc(f: ^Cff_Font, name: string) -> (u16, bool) {
if f == nil || name == "" || len(f.by_name) == 0 { return 0, false }
lo, hi := 0, len(f.by_name)
for lo < hi {
mid := lo + (hi - lo) / 2
if f.by_name[mid].name < name {
lo = mid + 1
} else {
hi = mid
}
}
if lo < len(f.by_name) && f.by_name[lo].name == name {
return f.by_name[lo].gid, true
}
return 0, false
}
// cff_gid_for_cid maps a CID to a glyph through the charset, which for a
// CIDFont holds CIDs rather than name SIDs (CFF 18). For a font that is not a
// CIDFont the CID is the glyph index, which is what a PDF using Identity-H
// with a non-CID CFF expects.
@(private)
cff_gid_for_cid :: proc(f: ^Cff_Font, cid: u16) -> (u16, bool) {
if f == nil { return 0, false }
if !f.is_cid {
if int(cid) < f.charstrings.count { return cid, true }
return 0, false
}
lo, hi := 0, len(f.by_cid)
key := u32(cid) << 16
for lo < hi {
mid := lo + (hi - lo) / 2
if f.by_cid[mid] < key {
lo = mid + 1
} else {
hi = mid
}
}
if lo < len(f.by_cid) && f.by_cid[lo] >> 16 == u32(cid) {
return u16(f.by_cid[lo] & 0xFFFF), true
}
return 0, false
}
// cff_glyph_name is the name the charset gives a glyph, or "" for a CIDFont,
// an out-of-range glyph or a SID that names nothing.
@(private)
cff_glyph_name :: proc(f: ^Cff_Font, gid: u16) -> string {
if f == nil || f.is_cid || int(gid) >= len(f.charset) { return "" }
return cff_sid_string(f, f.charset[gid])
}
// cff_builtin_encoding is the font's own code-to-glyph-name table, the same
// shape the built-in encodings in font_standard.odin have, so a simple font
// can apply /Differences over a copy of it. Entries are "" where the encoding
// selects nothing, and a CIDFont has no encoding at all.
//
// Not one of the seven procedures the render-layer contract lists; it is here
// because a symbolic Type1C font with a custom Encoding cannot be drawn
// without it, exactly as a Type1 font cannot be drawn without
// `t1_builtin_encoding`.
@(private)
cff_builtin_encoding :: proc(f: ^Cff_Font) -> ^[256]string {
if f == nil { return nil }
return &f.enc_names
}
// cff_gid_for_code resolves a byte code through the font's built-in encoding.
// The direct route: a custom Encoding names glyphs by position, so going via
// the name would lose any glyph the charset does not name.
@(private)
cff_gid_for_code :: proc(f: ^Cff_Font, code: byte) -> (u16, bool) {
if f == nil { return 0, false }
gid := f.code_to_gid[code]
if gid == 0 { return 0, false }
return gid, true
}
// ---------------------------------------------------------------------------
// The Type 2 charstring interpreter
//
// T2 is a stack machine whose operators are the drawing commands. Three of its
// rules are where implementations go wrong, so they are stated here once:
//
// 1. The glyph's width is an optional extra leading argument on the FIRST
// stack-clearing operator and nowhere else, expressed as a difference from
// nominalWidthX. Its presence is inferred from the argument count being one
// more than the operator takes -- for the stem operators, from the count
// being odd.
// 2. hintmask and cntrmask take the arguments left on the stack as an implied
// vstem declaration before reading their mask, which is ceil(numStems/8)
// bytes of the charstring itself rather than an operand.
// 3. Subroutine numbers are biased by 107, 1131 or 32768 according to how many
// subroutines the INDEX holds, so that the one-byte operand range reaches
// the middle of the array.
// ---------------------------------------------------------------------------
@(private)
Cff_Interp :: struct {
f: ^Cff_Font,
// nil to run for the width alone, which is what cff_glyph_advance wants:
// no outline is built and the run stops at the first stack-clearing
// operator.
out: ^Path,
priv: ^Cff_Private,
stack: [CFF_MAX_STACK]f64,
sp: int,
trans: [CFF_MAX_TRANSIENT]f64,
x, y: f64,
n_stems: int,
width: f64,
have_width: bool,
width_only: bool,
open: bool,
done: bool,
seac: [4]f64,
have_seac: bool,
allow_seac: bool,
fuel: int,
seed: u32,
}
@(private)
cff_push :: proc(ip: ^Cff_Interp, v: f64) -> Error {
if ip.sp >= CFF_MAX_STACK {
// T2 Appendix B fixes the stack at 48. A charstring that overruns it
// is either corrupt or hostile; either way the rest of it cannot be
// trusted, so the glyph stops here with what it has drawn so far.
return .Bad_Cff
}
ip.stack[ip.sp] = is_finite(v) ? v : 0
ip.sp += 1
return .None
}
// cff_take_width settles the width question on the first stack-clearing
// operator. `nargs` is how many arguments the operator itself takes; one more
// on the stack means the first is the width.
@(private)
cff_take_width :: proc(ip: ^Cff_Interp, nargs: int) {
if ip.have_width { return }
ip.have_width = true
if ip.sp > nargs && ip.priv != nil {
w := ip.priv.nominal_width + ip.stack[0]
if is_finite(w) { ip.width = w }
}
}
@(private)
cff_moveto :: proc(ip: ^Cff_Interp) {
if ip.out != nil {
// Every subpath of a charstring is closed: T2 has no explicit close
// operator, a moveto ends the previous contour.
if ip.open { path_close(ip.out) }
path_move_to(ip.out, ip.x, ip.y)
}
ip.open = true
}
@(private)
cff_lineto :: proc(ip: ^Cff_Interp) {
if ip.out != nil { path_line_to(ip.out, ip.x, ip.y) }
}
@(private)
cff_curveto :: proc(ip: ^Cff_Interp, x1, y1, x2, y2: f64) {
if ip.out != nil { path_curve_to(ip.out, x1, y1, x2, y2, ip.x, ip.y) }
}
// cff_int reads an operand that is going to be used as an index or a code.
// Anything that is not a small whole number cannot be either.
@(private)
cff_int :: proc(v: f64) -> (int, bool) {
if !is_finite(v) || v < -1e9 || v > 1e9 { return 0, false }
return int(v), true
}
@(private)
cff_run :: proc(ip: ^Cff_Interp, code: []byte, depth: int) -> Error {
if depth > CFF_MAX_SUBR_DEPTH {
return .Bad_Cff
}
s := &ip.stack
i := 0
for i < len(code) {
if ip.done { return .None }
// Once the width is known there is nothing else a width-only run wants,
// and stopping here is what keeps cff_glyph_advance from interpreting
// whole outlines.
if ip.width_only && ip.have_width {
ip.done = true
return .None
}
ip.fuel -= 1
if ip.fuel <= 0 { return .Bad_Cff }
b0 := code[i]
// -- operands (T2 4) ------------------------------------------------
if b0 == 28 {
if i + 2 >= len(code) { return .Bad_Cff }
cff_push(ip, f64(i16(u16(code[i + 1]) << 8 | u16(code[i + 2])))) or_return
i += 3
continue
}
if b0 >= 32 {
switch {
case b0 <= 246:
cff_push(ip, f64(int(b0) - 139)) or_return
i += 1
case b0 <= 250:
if i + 1 >= len(code) { return .Bad_Cff }
cff_push(ip, f64((int(b0) - 247) * 256 + int(code[i + 1]) + 108)) or_return
i += 2
case b0 <= 254:
if i + 1 >= len(code) { return .Bad_Cff }
cff_push(ip, f64(-(int(b0) - 251) * 256 - int(code[i + 1]) - 108)) or_return
i += 2
case:
// 255 is a 16.16 fixed-point number, the only fractional
// operand a charstring has.
if i + 4 >= len(code) { return .Bad_Cff }
u := u32(code[i + 1]) << 24 | u32(code[i + 2]) << 16 |
u32(code[i + 3]) << 8 | u32(code[i + 4])
cff_push(ip, f64(i32(u)) / 65536) or_return
i += 5
}
continue
}
i += 1
// -- operators ------------------------------------------------------
switch b0 {
case 1, 3, 18, 23: // hstem, vstem, hstemhm, vstemhm
cff_take_width(ip, (ip.sp / 2) * 2)
ip.n_stems += ip.sp / 2
ip.sp = 0
case 19, 20: // hintmask, cntrmask
// The arguments still on the stack are an implied vstem list.
cff_take_width(ip, (ip.sp / 2) * 2)
ip.n_stems += ip.sp / 2
ip.sp = 0
n_bytes := (ip.n_stems + 7) / 8
if i + n_bytes > len(code) { return .Bad_Cff }
i += n_bytes
case 21: // rmoveto
cff_take_width(ip, 2)
if ip.sp >= 2 {
ip.x += s[ip.sp - 2]
ip.y += s[ip.sp - 1]
}
cff_moveto(ip)
ip.sp = 0
case 22: // hmoveto
cff_take_width(ip, 1)
if ip.sp >= 1 { ip.x += s[ip.sp - 1] }
cff_moveto(ip)
ip.sp = 0
case 4: // vmoveto
cff_take_width(ip, 1)
if ip.sp >= 1 { ip.y += s[ip.sp - 1] }
cff_moveto(ip)
ip.sp = 0
case 5: // rlineto
for k := 0; k + 1 < ip.sp; k += 2 {
ip.x += s[k]
ip.y += s[k + 1]
cff_lineto(ip)
}
ip.sp = 0
case 6, 7: // hlineto, vlineto
horiz := b0 == 6
for k in 0 ..< ip.sp {
if horiz { ip.x += s[k] } else { ip.y += s[k] }
cff_lineto(ip)
horiz = !horiz
}
ip.sp = 0
case 8: // rrcurveto
for k := 0; k + 5 < ip.sp; k += 6 {
cff_rrcurve(ip, s[k], s[k+1], s[k+2], s[k+3], s[k+4], s[k+5])
}
ip.sp = 0
case 24: // rcurveline
k := 0
for k + 6 <= ip.sp - 2 {
cff_rrcurve(ip, s[k], s[k+1], s[k+2], s[k+3], s[k+4], s[k+5])
k += 6
}
if k + 2 <= ip.sp {
ip.x += s[k]
ip.y += s[k + 1]
cff_lineto(ip)
}
ip.sp = 0
case 25: // rlinecurve
k := 0
for k + 2 <= ip.sp - 6 {
ip.x += s[k]
ip.y += s[k + 1]
cff_lineto(ip)
k += 2
}
if k + 6 <= ip.sp {
cff_rrcurve(ip, s[k], s[k+1], s[k+2], s[k+3], s[k+4], s[k+5])
}
ip.sp = 0
case 26: // vvcurveto
k := 0
dx1 := 0.0
if ip.sp % 4 == 1 {
dx1 = s[0]
k = 1
}
for ; k + 3 < ip.sp; k += 4 {
x1 := ip.x + dx1
y1 := ip.y + s[k]
x2 := x1 + s[k + 1]
y2 := y1 + s[k + 2]
ip.x = x2
ip.y = y2 + s[k + 3]
cff_curveto(ip, x1, y1, x2, y2)
dx1 = 0
}
ip.sp = 0
case 27: // hhcurveto
k := 0
dy1 := 0.0
if ip.sp % 4 == 1 {
dy1 = s[0]
k = 1
}
for ; k + 3 < ip.sp; k += 4 {
x1 := ip.x + s[k]
y1 := ip.y + dy1
x2 := x1 + s[k + 1]
y2 := y1 + s[k + 2]
ip.x = x2 + s[k + 3]
ip.y = y2
cff_curveto(ip, x1, y1, x2, y2)
dy1 = 0
}
ip.sp = 0
case 30, 31: // vhcurveto, hvcurveto
horiz := b0 == 31
k := 0
for k + 3 < ip.sp {
// The odd 5th argument of the last group is the other axis of
// the final point, which is otherwise implied to be unchanged.
last := ip.sp - k == 5
x1, y1, x2, y2: f64
if horiz {
x1 = ip.x + s[k]
y1 = ip.y
x2 = x1 + s[k + 1]
y2 = y1 + s[k + 2]
ip.y = y2 + s[k + 3]
ip.x = last ? x2 + s[k + 4] : x2
} else {
x1 = ip.x
y1 = ip.y + s[k]
x2 = x1 + s[k + 1]
y2 = y1 + s[k + 2]
ip.x = x2 + s[k + 3]
ip.y = last ? y2 + s[k + 4] : y2
}
cff_curveto(ip, x1, y1, x2, y2)
k += 4
horiz = !horiz
}
ip.sp = 0
case 10, 29: // callsubr, callgsubr
if ip.sp < 1 { return .Bad_Cff }
ip.sp -= 1
n, ok := cff_int(s[ip.sp])
if !ok { return .Bad_Cff }
idx: ^Cff_Index
bias: int
if b0 == 10 {
if ip.priv == nil { return .Bad_Cff }
idx = &ip.priv.subrs
bias = ip.priv.subr_bias
} else {
idx = &ip.f.gsubrs
bias = ip.f.gsubr_bias
}
body := cff_index_item(idx, n + bias)
if body == nil { return .Bad_Cff }
cff_run(ip, body, depth + 1) or_return
if ip.done { return .None }
case 11: // return
return .None
case 14: // endchar
// endchar takes nothing, or the four arguments of the deprecated
// seac composition, either of which may be preceded by the width.
if !ip.have_width {
ip.have_width = true
if (ip.sp == 1 || ip.sp == 5) && ip.priv != nil {
w := ip.priv.nominal_width + s[0]
if is_finite(w) { ip.width = w }
}
}
if ip.sp >= 4 && ip.allow_seac {
ip.seac = {s[ip.sp - 4], s[ip.sp - 3], s[ip.sp - 2], s[ip.sp - 1]}
ip.have_seac = true
}
if ip.open && ip.out != nil { path_close(ip.out) }
ip.open = false
ip.sp = 0
ip.done = true
return .None
case 12: // escape
if i >= len(code) { return .Bad_Cff }
b1 := code[i]
i += 1
cff_escape(ip, b1) or_return
case:
// 0, 2, 9, 13, 15, 16 and 17 are reserved. Reaching one means the
// operand/operator alternation has lost sync, and anything read
// after it would be invented.
return .Bad_Cff
}
}
return .None
}
// cff_rrcurve is one relative cubic: two relative control points and a
// relative end point.
@(private)
cff_rrcurve :: proc(ip: ^Cff_Interp, dxa, dya, dxb, dyb, dxc, dyc: f64) {
x1 := ip.x + dxa
y1 := ip.y + dya
x2 := x1 + dxb
y2 := y1 + dyb
ip.x = x2 + dxc
ip.y = y2 + dyc
cff_curveto(ip, x1, y1, x2, y2)
}
// cff_escape runs the two-byte "12 x" operators: the four flex variants and
// the arithmetic, storage and conditional operators.
//
// The arithmetic set exists for multiple-master interpolation and is close to
// unused in practice, but a charstring that reaches an unimplemented operator
// would silently draw the wrong shape, so all of it is here.
@(private)
cff_escape :: proc(ip: ^Cff_Interp, b1: u8) -> Error {
s := &ip.stack
switch b1 {
case 35: // flex
if ip.sp >= 13 {
cff_rrcurve(ip, s[0], s[1], s[2], s[3], s[4], s[5])
cff_rrcurve(ip, s[6], s[7], s[8], s[9], s[10], s[11])
}
ip.sp = 0
case 34: // hflex
if ip.sp >= 7 {
y0 := ip.y
x1 := ip.x + s[0]
y1 := ip.y
x2 := x1 + s[1]
y2 := y1 + s[2]
ip.x = x2 + s[3]
ip.y = y2
cff_curveto(ip, x1, y1, x2, y2)
x3 := ip.x + s[4]
y3 := ip.y
x4 := x3 + s[5]
y4 := y0
ip.x = x4 + s[6]
ip.y = y0
cff_curveto(ip, x3, y3, x4, y4)
}
ip.sp = 0
case 36: // hflex1
if ip.sp >= 9 {
y0 := ip.y
x1 := ip.x + s[0]
y1 := ip.y + s[1]
x2 := x1 + s[2]
y2 := y1 + s[3]
ip.x = x2 + s[4]
ip.y = y2
cff_curveto(ip, x1, y1, x2, y2)
x3 := ip.x + s[5]
y3 := ip.y
x4 := x3 + s[6]
y4 := y3 + s[7]
ip.x = x4 + s[8]
ip.y = y0
cff_curveto(ip, x3, y3, x4, y4)
}
ip.sp = 0
case 37: // flex1
if ip.sp >= 11 {
x0 := ip.x
y0 := ip.y
dx, dy := 0.0, 0.0
for k in 0 ..< 5 {
dx += s[k * 2]
dy += s[k * 2 + 1]
}
x1 := ip.x + s[0]
y1 := ip.y + s[1]
x2 := x1 + s[2]
y2 := y1 + s[3]
ip.x = x2 + s[4]
ip.y = y2 + s[5]
cff_curveto(ip, x1, y1, x2, y2)
x3 := ip.x + s[6]
y3 := ip.y + s[7]
x4 := x3 + s[8]
y4 := y3 + s[9]
// The 11th argument is whichever coordinate moved further overall;
// the other returns to where the flex started.
if abs(dx) > abs(dy) {
ip.x = x4 + s[10]
ip.y = y0
} else {
ip.x = x0
ip.y = y4 + s[10]
}
cff_curveto(ip, x3, y3, x4, y4)
}
ip.sp = 0
case 3: cff_binary(ip, .And)
case 4: cff_binary(ip, .Or)
case 5: cff_unary(ip, .Not)
case 9: cff_unary(ip, .Abs)
case 10: cff_binary(ip, .Add)
case 11: cff_binary(ip, .Sub)
case 12: cff_binary(ip, .Div)
case 14: cff_unary(ip, .Neg)
case 15: cff_binary(ip, .Eq)
case 24: cff_binary(ip, .Mul)
case 26: cff_unary(ip, .Sqrt)
case 18: // drop
if ip.sp > 0 { ip.sp -= 1 }
case 20: // put
if ip.sp >= 2 {
ip.sp -= 2
if j, ok := cff_int(s[ip.sp + 1]); ok && j >= 0 && j < CFF_MAX_TRANSIENT {
ip.trans[j] = s[ip.sp]
}
}
case 21: // get
if ip.sp >= 1 {
v := 0.0
if j, ok := cff_int(s[ip.sp - 1]); ok && j >= 0 && j < CFF_MAX_TRANSIENT {
v = ip.trans[j]
}
s[ip.sp - 1] = v
}
case 22: // ifelse
if ip.sp >= 4 {
ip.sp -= 3
s[ip.sp - 1] = s[ip.sp + 1] <= s[ip.sp + 2] ? s[ip.sp - 1] : s[ip.sp]
}
case 23: // random
// A deterministic generator: the operator only promises a value in
// (0,1], and a reader that renders the same glyph differently twice is
// worse than one that is boringly repeatable.
ip.seed = ip.seed * 1664525 + 1013904223
cff_push(ip, f64(ip.seed >> 8) / f64(1 << 24) * 0.999 + 0.001) or_return
case 27: // dup
if ip.sp >= 1 {
cff_push(ip, s[ip.sp - 1]) or_return
}
case 28: // exch
if ip.sp >= 2 {
s[ip.sp - 1], s[ip.sp - 2] = s[ip.sp - 2], s[ip.sp - 1]
}
case 29: // index
if ip.sp >= 1 {
j, ok := cff_int(s[ip.sp - 1])
if !ok || j < 0 { j = 0 }
if j >= ip.sp - 1 {
s[ip.sp - 1] = 0
} else {
s[ip.sp - 1] = s[ip.sp - 2 - j]
}
}
case 30: // roll
cff_roll(ip)
case:
// Reserved escapes. As with the one-byte operators, continuing past
// one would be guesswork.
return .Bad_Cff
}
return .None
}
@(private)
Cff_Unary :: enum u8 { Not, Abs, Neg, Sqrt }
@(private)
Cff_Binary :: enum u8 { And, Or, Add, Sub, Div, Eq, Mul }
@(private)
cff_unary :: proc(ip: ^Cff_Interp, op: Cff_Unary) {
if ip.sp < 1 { return }
a := ip.stack[ip.sp - 1]
r := 0.0
switch op {
case .Not: r = a == 0 ? 1 : 0
case .Abs: r = abs(a)
case .Neg: r = -a
// A negative operand has no square root, and a NaN would poison every
// coordinate it later touches.
case .Sqrt: r = a > 0 ? math.sqrt(a) : 0
}
ip.stack[ip.sp - 1] = is_finite(r) ? r : 0
}
@(private)
cff_binary :: proc(ip: ^Cff_Interp, op: Cff_Binary) {
if ip.sp < 2 { return }
a := ip.stack[ip.sp - 2]
b := ip.stack[ip.sp - 1]
ip.sp -= 1
r := 0.0
switch op {
case .And: r = (a != 0 && b != 0) ? 1 : 0
case .Or: r = (a != 0 || b != 0) ? 1 : 0
case .Add: r = a + b
case .Sub: r = a - b
case .Div: r = b != 0 ? a / b : 0
case .Eq: r = a == b ? 1 : 0
case .Mul: r = a * b
}
ip.stack[ip.sp - 1] = is_finite(r) ? r : 0
}
// cff_roll rotates the top N elements of the stack by J places, upwards for a
// positive J (T2 4.4).
@(private)
cff_roll :: proc(ip: ^Cff_Interp) {
if ip.sp < 2 { return }
j_raw := ip.stack[ip.sp - 1]
n_raw := ip.stack[ip.sp - 2]
ip.sp -= 2
n, n_ok := cff_int(n_raw)
j, j_ok := cff_int(j_raw)
if !n_ok || !j_ok || n <= 0 || n > ip.sp { return }
j %= n
if j < 0 { j += n }
if j == 0 { return }
tmp: [CFF_MAX_STACK]f64
base := ip.sp - n
for k in 0 ..< n {
tmp[(k + j) % n] = ip.stack[base + k]
}
for k in 0 ..< n {
ip.stack[base + k] = tmp[k]
}
}
// ---------------------------------------------------------------------------
// Glyphs
// ---------------------------------------------------------------------------
// cff_private_for is the Private DICT a glyph's charstring runs against. A
// CIDFont picks it per glyph through the FDSelect, which is why a CID font read
// with the wrong FDSelect draws with the wrong subroutines and produces the
// characteristic garbage of a mis-parsed CJK font.
@(private)
cff_private_for :: proc(f: ^Cff_Font, gid: u16) -> ^Cff_Private {
if len(f.fds) > 0 {
fd := 0
if int(gid) < len(f.fd_select) { fd = int(f.fd_select[gid]) }
if fd >= len(f.fds) { fd = 0 }
return &f.fds[fd]
}
return &f.priv
}
@(private)
cff_charstring :: proc(f: ^Cff_Font, gid: u16) -> ([]byte, bool) {
if int(gid) >= f.charstrings.count { return nil, false }
return cff_index_item(&f.charstrings, int(gid)), true
}
// cff_execute runs one glyph's charstring into `ip`, which the caller supplies
// so the 700-odd bytes of interpreter state stay on its stack.
@(private)
cff_execute :: proc(
ip: ^Cff_Interp,
f: ^Cff_Font,
gid: u16,
out: ^Path,
width_only: bool,
allow_seac: bool,
) -> Error {
ip^ = Cff_Interp{
f = f,
out = out,
width_only = width_only,
allow_seac = allow_seac,
fuel = CFF_MAX_CHARSTRING_OPS,
seed = 0x2545_F491,
}
ip.priv = cff_private_for(f, gid)
ip.width = ip.priv.default_width
if f.charstring_type != 2 {
// CharstringType 1 means the charstrings are Type 1, which is a
// different language with a different interpreter. The font still
// reports its widths and its glyph names; only outlines are lost.
return .Not_Implemented
}
cs, ok := cff_charstring(f, gid)
if !ok { return .No_Glyph }
err := cff_run(ip, cs, 0)
// A charstring that runs off its end without an endchar still leaves a
// usable outline, so its last contour is closed here.
if ip.open && out != nil {
path_close(out)
ip.open = false
}
return err
}
// cff_glyph_path appends the outline of `gid` to `out`, in glyph space -- the
// charstring's own units, which `cff_font_matrix` maps to text space.
//
// The path is appended, not assigned, so a caller may accumulate several
// glyphs. When an error is returned, `out` holds whatever was decoded before
// the fault: a partly drawn glyph is usually still recognisable, and is always
// better than a page that stops.
@(private)
cff_glyph_path :: proc(f: ^Cff_Font, gid: u16, out: ^Path) -> Error {
if f == nil || out == nil { return .Invalid_Argument }
ip: Cff_Interp
cff_execute(&ip, f, gid, out, false, true) or_return
if ip.have_seac {
return cff_seac_compose(f, ip.seac, out)
}
return .None
}
// cff_seac_compose draws the two components of a seac -- the deprecated
// "standard encoding accented character", which lives on as the four-argument
// form of endchar (T2 4.1). The two components are named by their positions in
// the Standard Encoding, not by anything in this font's own encoding.
@(private)
cff_seac_compose :: proc(f: ^Cff_Font, args: [4]f64, out: ^Path) -> Error {
adx, ady := args[0], args[1]
bcode, b_ok := cff_int(args[2])
acode, a_ok := cff_int(args[3])
if !b_ok || !a_ok || bcode < 0 || bcode > 255 || acode < 0 || acode > 255 {
return .Bad_Cff
}
std := encoding_standard()
bgid, has_b := cff_gid_for_name(f, std[bcode])
agid, has_a := cff_gid_for_name(f, std[acode])
if !has_b || !has_a { return .No_Glyph }
sub: Cff_Interp
cff_execute(&sub, f, bgid, out, false, false) or_return
mark := len(out.points)
cff_execute(&sub, f, agid, out, false, false) or_return
if !is_finite(adx) || !is_finite(ady) { return .None }
shift := Point{adx, ady}
for k in mark ..< len(out.points) {
out.points[k] += shift
}
if mark < len(out.points) {
out.cur += shift
out.start += shift
}
return .None
}
// cff_glyph_advance is the glyph's horizontal advance in glyph space.
//
// A CFF keeps widths in the charstrings themselves rather than in a table: the
// width is either defaultWidthX, or an optional first argument expressed as a
// difference from nominalWidthX. Finding it therefore means interpreting the
// charstring, but only as far as its first stack-clearing operator.
@(private)
cff_glyph_advance :: proc(f: ^Cff_Font, gid: u16) -> f64 {
if f == nil { return 0 }
ip: Cff_Interp
if err := cff_execute(&ip, f, gid, nil, true, false); err == .No_Glyph {
return 0
}
return ip.width
}