1825 lines
64 KiB
Odin
1825 lines
64 KiB
Odin
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
|
|
}
|