Files
pdf-odin-llm/font_cff_test.odin
T
2026-09-25 23:32:29 +02:00

1622 lines
63 KiB
Odin

package pdf
import "core:mem"
import "core:mem/virtual"
import "core:testing"
// ---------------------------------------------------------------------------
// Fixture
//
// Every font under test is assembled into a local arena and read out of it, so
// a failing assertion cannot leak: the arena goes away on the way out whatever
// happened.
// ---------------------------------------------------------------------------
@(private = "file")
Cf_Fix :: struct {
arena: virtual.Arena,
alloc: mem.Allocator,
}
@(private = "file")
cff_fix_init :: proc(f: ^Cf_Fix) {
_ = virtual.arena_init_growing(&f.arena)
f.alloc = virtual.arena_allocator(&f.arena)
}
@(private = "file")
cff_fix_destroy :: proc(f: ^Cf_Fix) {
virtual.arena_destroy(&f.arena)
}
// ---------------------------------------------------------------------------
// Building fonts to read back
//
// The builder writes every DICT offset in the fixed 5-byte form, so a DICT's
// length does not change when its offsets do and the layout can be computed in
// one pass. It is deliberately not the reader run backwards: offsets, INDEX
// offSize selection and the Subrs-relative-to-Private rule are all written out
// here independently, and one hand-checked font (CFF_MINIMAL below) is a byte
// literal that does not go through the builder at all.
// ---------------------------------------------------------------------------
@(private = "file")
cb_index :: proc(items: [][]byte, alloc: mem.Allocator) -> []byte {
if len(items) == 0 {
return cb_bytes({0, 0}, alloc)
}
total := 1
for it in items { total += len(it) }
off_size := 4
switch {
case total <= 0xFF: off_size = 1
case total <= 0xFFFF: off_size = 2
}
out := make([dynamic]u8, 0, 16, alloc)
append(&out, u8(len(items) >> 8), u8(len(items)))
append(&out, u8(off_size))
cb_off :: proc(out: ^[dynamic]u8, v, size: int) {
for i := size - 1; i >= 0; i -= 1 {
append(out, u8(v >> uint(8 * i)))
}
}
off := 1
cb_off(&out, off, off_size)
for it in items {
off += len(it)
cb_off(&out, off, off_size)
}
for it in items { append(&out, ..it) }
return out[:]
}
@(private = "file")
cb_bytes :: proc(b: []u8, alloc: mem.Allocator) -> []byte {
out := make([]byte, len(b), alloc)
copy(out, b)
return out
}
@(private = "file")
cb_cat :: proc(alloc: mem.Allocator, parts: ..[]byte) -> []byte {
n := 0
for p in parts { n += len(p) }
out := make([]byte, n, alloc)
i := 0
for p in parts {
copy(out[i:], p)
i += len(p)
}
return out
}
// cb_int is the 5-byte DICT integer, used for every offset so that patching one
// cannot change a DICT's length.
@(private = "file")
cb_int :: proc(v: int) -> [5]u8 {
u := u32(i32(v))
return {29, u8(u >> 24), u8(u >> 16), u8(u >> 8), u8(u)}
}
// cb_dict appends `operands... operator`, the postfix order a DICT is written
// in. An operator above 255 is one of the two-byte 12/x escapes.
@(private = "file")
cb_dict :: proc(out: ^[dynamic]u8, op: int, operands: ..int) {
for v in operands {
b := cb_int(v)
append(out, ..b[:])
}
if op >= 0x0C00 {
append(out, 12, u8(op & 0xFF))
} else {
append(out, u8(op))
}
}
@(private = "file")
Cff_Spec :: struct {
charstrings: [][]byte,
charset: []byte, // raw charset table; empty means predefined 0
encoding: []byte, // raw Encoding table; empty means predefined 0
strings: [][]byte,
gsubrs: [][]byte,
// Non-CID: one Private DICT, whose extra operators and local subrs these
// are.
priv_extra: []byte,
subrs: [][]byte,
// CID: an FDArray of this many Private DICTs, plus an FDSelect. `ros` puts
// the ROS operator in the Top DICT, which is what marks a CIDFont.
ros: bool,
fdselect: []byte,
fd_extra: [][]byte,
fd_subrs: [][][]byte,
top_extra: []byte, // raw extra Top DICT bytes, e.g. a FontMatrix
}
// cb_private builds one Private DICT together with the local Subrs INDEX that
// follows it. The Subrs operand is an offset from the start of the Private
// DICT itself (CFF Table 23), which is what `size` below stands for.
@(private = "file")
cb_private :: proc(extra: []byte, subrs: [][]byte, alloc: mem.Allocator) -> (blob: []byte, dict_len: int) {
d := make([dynamic]u8, 0, 32, alloc)
append(&d, ..extra)
if len(subrs) > 0 {
cb_dict(&d, 19, 0)
dict_len = len(d)
// Patch the placeholder now that the DICT's own length is known.
patch := cb_int(dict_len)
copy(d[len(d) - 6:], patch[:])
return cb_cat(alloc, d[:], cb_index(subrs, alloc)), dict_len
}
return d[:], len(d)
}
@(private = "file")
cb_build :: proc(spec: Cff_Spec, alloc: mem.Allocator) -> []byte {
hdr := cb_bytes({1, 0, 4, 1}, alloc)
names := cb_index({transmute([]byte)string("Test")}, alloc)
strings_idx := cb_index(spec.strings, alloc)
gsubrs_idx := cb_index(spec.gsubrs, alloc)
cs_idx := cb_index(spec.charstrings, alloc)
n_fd := len(spec.fd_subrs)
if len(spec.fd_extra) > n_fd { n_fd = len(spec.fd_extra) }
// Private DICTs, and the font DICTs that point at them for a CIDFont.
privs := make([][]byte, max(n_fd, 1), alloc)
priv_dict_lens := make([]int, max(n_fd, 1), alloc)
if n_fd == 0 {
privs[0], priv_dict_lens[0] = cb_private(spec.priv_extra, spec.subrs, alloc)
} else {
for i in 0 ..< n_fd {
extra: []byte
if i < len(spec.fd_extra) { extra = spec.fd_extra[i] }
subrs: [][]byte
if i < len(spec.fd_subrs) { subrs = spec.fd_subrs[i] }
privs[i], priv_dict_lens[i] = cb_private(extra, subrs, alloc)
}
}
// A font DICT is always "size offset 18", 12 bytes, whatever the offsets.
fd_dicts := make([][]byte, n_fd, alloc)
for i in 0 ..< n_fd {
d := make([dynamic]u8, 0, 16, alloc)
cb_dict(&d, 18, priv_dict_lens[i], 0)
fd_dicts[i] = d[:]
}
fdarray_idx := n_fd > 0 ? cb_index(fd_dicts, alloc) : nil
top :: proc(spec: Cff_Spec, charset, encoding, fdselect, charstrings, fdarray, priv_off, priv_len: int,
alloc: mem.Allocator) -> []byte {
d := make([dynamic]u8, 0, 64, alloc)
append(&d, ..spec.top_extra)
if spec.ros {
// ROS: two SIDs and a supplement number. The values do not matter;
// its presence is what declares a CIDFont.
cb_dict(&d, 0x0C00 | 30, 391, 392, 0)
}
if len(spec.charset) > 0 { cb_dict(&d, 15, charset) }
if len(spec.encoding) > 0 { cb_dict(&d, 16, encoding) }
cb_dict(&d, 17, charstrings)
if fdarray > 0 {
cb_dict(&d, 0x0C00 | 36, fdarray)
if len(spec.fdselect) > 0 { cb_dict(&d, 0x0C00 | 37, fdselect) }
} else {
cb_dict(&d, 18, priv_len, priv_off)
}
return d[:]
}
// First pass: sizes only. Every offset is written 5 bytes wide, so the
// lengths this produces are the lengths the real thing has.
top0 := top(spec, 0, 0, 0, 0, n_fd > 0 ? 1 : 0, 0, priv_dict_lens[0], alloc)
head := len(hdr) + len(names) + len(cb_index({top0}, alloc)) + len(strings_idx) + len(gsubrs_idx)
charset_off := head
encoding_off := charset_off + len(spec.charset)
fdselect_off := encoding_off + len(spec.encoding)
cs_off := fdselect_off + len(spec.fdselect)
fdarray_off := cs_off + len(cs_idx)
priv_offs := make([]int, len(privs), alloc)
p := fdarray_off + len(fdarray_idx)
for i in 0 ..< len(privs) {
priv_offs[i] = p
p += len(privs[i])
}
// Second pass: the same DICTs with their offsets filled in.
for i in 0 ..< n_fd {
d := make([dynamic]u8, 0, 16, alloc)
cb_dict(&d, 18, priv_dict_lens[i], priv_offs[i])
fd_dicts[i] = d[:]
}
if n_fd > 0 { fdarray_idx = cb_index(fd_dicts, alloc) }
top1 := top(spec, charset_off, encoding_off, fdselect_off, cs_off,
n_fd > 0 ? fdarray_off : 0, priv_offs[0], priv_dict_lens[0], alloc)
tops := cb_index({top1}, alloc)
out := make([dynamic]u8, 0, 256, alloc)
append(&out, ..hdr)
append(&out, ..names)
append(&out, ..tops)
append(&out, ..strings_idx)
append(&out, ..gsubrs_idx)
append(&out, ..spec.charset)
append(&out, ..spec.encoding)
append(&out, ..spec.fdselect)
append(&out, ..cs_idx)
if fdarray_idx != nil { append(&out, ..fdarray_idx) }
for i in 0 ..< len(privs) {
if n_fd == 0 && len(privs[i]) == 0 { break }
append(&out, ..privs[i])
}
return out[:]
}
// cf_one builds a font whose glyph 1 is the given charstring, which is what
// most of the operator tests want. Glyph 0 is an empty .notdef.
@(private = "file")
cf_one :: proc(t: ^testing.T, fx: ^Cf_Fix, code: []byte, spec := Cff_Spec{}) -> ^Cff_Font {
s := spec
s.charstrings = {{14}, code}
data := cb_build(s, fx.alloc)
f, err := cff_parse(data, fx.alloc)
testing.expect_value(t, err, Error.None)
return f
}
// cf_path runs a glyph and hands back the outline, so a test reads as
// "charstring in, points out".
@(private = "file")
cf_path :: proc(t: ^testing.T, fx: ^Cf_Fix, code: []byte, spec := Cff_Spec{}) -> (Path, Error) {
f := cf_one(t, fx, code, spec)
p := path_make(fx.alloc)
if f == nil { return p, .Bad_Cff }
err := cff_glyph_path(f, 1, &p)
return p, err
}
// cf_expect compares an outline against hand-computed verbs and points.
@(private = "file")
cf_expect :: proc(t: ^testing.T, p: ^Path, verbs: []Path_Verb, pts: []Point, loc := #caller_location) {
if !testing.expectf(t, len(p.verbs) == len(verbs), "verbs: got %v, want %v", p.verbs, verbs, loc = loc) {
return
}
for v, i in verbs {
testing.expectf(t, p.verbs[i] == v, "verb %d: got %v, want %v", i, p.verbs[i], v, loc = loc)
}
if !testing.expectf(t, len(p.points) == len(pts), "points: got %v, want %v", p.points, pts, loc = loc) {
return
}
for want, i in pts {
got := p.points[i]
ok := abs(got.x - want.x) < 1e-9 && abs(got.y - want.y) < 1e-9
testing.expectf(t, ok, "point %d: got %v, want %v", i, got, want, loc = loc)
}
}
// cs_n is the one-byte charstring operand for a small integer: T2 4 encodes
// -107..107 as the single byte v+139.
@(private = "file")
cs_n :: proc(v: int) -> u8 {
// The assertion is against a bug in a test rather than in the library: a
// value out of range would silently truncate into some other operand or,
// worse, into an operator.
assert(v >= -107 && v <= 107, "cs_n takes -107..107")
return u8(v + 139)
}
// ---------------------------------------------------------------------------
// A font written out by hand
//
// Generated once by a throwaway script and checked by hand against CFF 6-19.
// Two glyphs: an empty .notdef, and glyph 1 -- named "A" by a format 0 charset
// -- drawing the rectangle (100,200)-(400,600) with an explicit width of 500.
// The Private DICT sets defaultWidthX 250 and nominalWidthX 100, so glyph 1's
// advance is 100+500 and glyph 0's is 250.
// ---------------------------------------------------------------------------
@(private = "file")
CFF_MINIMAL := [83]u8{
// header
0x01, 0x00, 0x04, 0x01,
// Name INDEX: one item, "Test"
0x00, 0x01, 0x01, 0x01, 0x05, 0x54, 0x65, 0x73, 0x74,
// Top DICT INDEX: charset 45, CharStrings 48, Private (12 bytes at 71)
0x00, 0x01, 0x01, 0x01, 0x18, 0x1D, 0x00, 0x00, 0x00, 0x2D, 0x0F, 0x1D,
0x00, 0x00, 0x00, 0x30, 0x11, 0x1D, 0x00, 0x00, 0x00, 0x0C, 0x1D, 0x00,
0x00, 0x00, 0x47, 0x12,
// String INDEX: empty, and so two bytes long
0x00, 0x00,
// GlobalSubr INDEX: empty
0x00, 0x00,
// charset: format 0, glyph 1 is SID 34 = "A"
0x00, 0x00, 0x22,
// CharStrings INDEX: {endchar}, {500 100 200 rmoveto 300 hlineto
// 400 vlineto -300 hlineto endchar}
0x00, 0x02, 0x01, 0x01, 0x02, 0x12, 0x0E, 0xF8, 0x88, 0xEF, 0xF7, 0x5C,
0x15, 0xF7, 0xC0, 0x06, 0xF8, 0x24, 0x07, 0xFB, 0xC0, 0x06, 0x0E,
// Private DICT: defaultWidthX 250, nominalWidthX 100
0x1D, 0x00, 0x00, 0x00, 0xFA, 0x14, 0x1D, 0x00, 0x00, 0x00, 0x64, 0x15,
}
@(test)
cff_test_minimal_font :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
f, err := cff_parse(CFF_MINIMAL[:], fx.alloc)
testing.expect_value(t, err, Error.None)
if f == nil { return }
testing.expect_value(t, cff_glyph_count(f), 2)
testing.expect_value(t, f.is_cid, false)
testing.expect_value(t, cff_font_matrix(f), Matrix{0.001, 0, 0, 0.001, 0, 0})
gid, ok := cff_gid_for_name(f, "A")
testing.expect_value(t, ok, true)
testing.expect_value(t, gid, u16(1))
_, missing := cff_gid_for_name(f, "B")
testing.expect_value(t, missing, false)
// The built-in encoding is the Standard one, which puts "A" at code 65.
code_gid, code_ok := cff_gid_for_code(f, 65)
testing.expect_value(t, code_ok, true)
testing.expect_value(t, code_gid, u16(1))
testing.expect_value(t, cff_builtin_encoding(f)[65], "A")
p := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 1, &p), Error.None)
cf_expect(t, &p,
{.Move, .Line, .Line, .Line, .Close},
{{100, 200}, {400, 200}, {400, 600}, {100, 600}})
testing.expect_value(t, cff_glyph_advance(f, 1), 600.0)
testing.expect_value(t, cff_glyph_advance(f, 0), 250.0)
// Past the end of the CharStrings INDEX: no glyph, and no crash.
testing.expect_value(t, cff_glyph_advance(f, 9), 0.0)
q := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 9, &q), Error.No_Glyph)
}
// ---------------------------------------------------------------------------
// INDEX
// ---------------------------------------------------------------------------
@(test)
cff_test_index_empty :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// CFF 5: an empty INDEX is a count of zero and nothing else -- no offSize
// byte. Reading one as three bytes would push every later structure out.
data := []byte{0xAA, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x5A}
idx, err := cff_index_read(data, 1, fx.alloc)
testing.expect_value(t, err, Error.None)
testing.expect_value(t, idx.count, 0)
testing.expect_value(t, idx.end, 3)
next, err2 := cff_index_read(data, idx.end, fx.alloc)
testing.expect_value(t, err2, Error.None)
testing.expect_value(t, next.count, 1)
testing.expect_value(t, string(cff_index_item(&next, 0)), "Z")
}
@(test)
cff_test_index_items :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// count 3, offSize 1, offsets 1,3,5,6 over "abcde"
data := []byte{0x00, 0x03, 0x01, 1, 3, 5, 6, 'a', 'b', 'c', 'd', 'e'}
idx, err := cff_index_read(data, 0, fx.alloc)
testing.expect_value(t, err, Error.None)
testing.expect_value(t, idx.count, 3)
testing.expect_value(t, string(cff_index_item(&idx, 0)), "ab")
testing.expect_value(t, string(cff_index_item(&idx, 1)), "cd")
testing.expect_value(t, string(cff_index_item(&idx, 2)), "e")
testing.expect_value(t, idx.end, len(data))
testing.expect_value(t, len(cff_index_item(&idx, 3)), 0)
testing.expect_value(t, len(cff_index_item(&idx, -1)), 0)
// Two-byte offsets over the same items.
wide := []byte{0x00, 0x02, 0x02, 0, 1, 0, 3, 0, 6, 'a', 'b', 'c', 'd', 'e'}
idx2, err2 := cff_index_read(wide, 0, fx.alloc)
testing.expect_value(t, err2, Error.None)
testing.expect_value(t, string(cff_index_item(&idx2, 0)), "ab")
testing.expect_value(t, string(cff_index_item(&idx2, 1)), "cde")
}
@(test)
cff_test_index_bad_offsets :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// Offsets that run off the end are clamped to it, so the items they name
// come back empty instead of out of bounds.
past := []byte{0x00, 0x02, 0x01, 1, 200, 250, 'a', 'b', 'c'}
idx, err := cff_index_read(past, 0, fx.alloc)
testing.expect_value(t, err, Error.None)
testing.expect_value(t, string(cff_index_item(&idx, 0)), "abc")
testing.expect_value(t, len(cff_index_item(&idx, 1)), 0)
testing.expect(t, idx.end <= len(past), "INDEX ended past the data")
// Offsets that run backwards are clamped to be non-decreasing.
back := []byte{0x00, 0x02, 0x01, 3, 5, 2, 'a', 'b', 'c', 'd', 'e'}
idx2, err2 := cff_index_read(back, 0, fx.alloc)
testing.expect_value(t, err2, Error.None)
testing.expect_value(t, string(cff_index_item(&idx2, 0)), "cd")
testing.expect_value(t, len(cff_index_item(&idx2, 1)), 0)
// A count that cannot fit its own offset array is truncation, not a
// 400 MB allocation.
huge := []byte{0xFF, 0xFF, 0x04, 1, 2, 3}
_, err3 := cff_index_read(huge, 0, fx.alloc)
testing.expect_value(t, err3, Error.Truncated)
// offSize outside 1..4 is not recoverable: the offsets cannot be located.
bad := []byte{0x00, 0x01, 0x09, 1, 2, 'a'}
_, err4 := cff_index_read(bad, 0, fx.alloc)
testing.expect_value(t, err4, Error.Bad_Cff)
for cut in 0 ..= len(past) {
_, _ = cff_index_read(past[:cut], 0, fx.alloc)
}
}
@(test)
cff_test_subr_bias :: proc(t: ^testing.T) {
// T2 4.7. The thresholds are exclusive on the low side: 1239 subroutines
// still bias by 107.
testing.expect_value(t, cff_subr_bias(0), 107)
testing.expect_value(t, cff_subr_bias(1239), 107)
testing.expect_value(t, cff_subr_bias(1240), 1131)
testing.expect_value(t, cff_subr_bias(33899), 1131)
testing.expect_value(t, cff_subr_bias(33900), 32768)
}
// ---------------------------------------------------------------------------
// DICT
// ---------------------------------------------------------------------------
@(test)
cff_test_dict_operands :: proc(t: ^testing.T) {
// Every integer form CFF 4 defines, then a two-byte operator.
data := []byte{
139, // 0
32, // -107
246, // 107
247, 0, // 108
250, 255, // 1131
251, 0, // -108
254, 255, // -1131
28, 0x7F, 0xFF, // 32767
28, 0x80, 0x00, // -32768
29, 0x00, 0x01, 0x00, 0x00, // 65536
29, 0xFF, 0xFF, 0xFF, 0xFF, // -1
12, 7, // operator 12 7
}
it := Cff_Dict_Iter{data = data}
op, ok := cff_dict_next(&it)
testing.expect_value(t, ok, true)
testing.expect_value(t, op, u16(CFF_ESC | 7))
testing.expect_value(t, it.n, 11)
want := []f64{0, -107, 107, 108, 1131, -108, -1131, 32767, -32768, 65536, -1}
for v, i in want {
testing.expect_value(t, it.ops[i], v)
}
_, more := cff_dict_next(&it)
testing.expect_value(t, more, false)
}
@(test)
cff_test_dict_reals :: proc(t: ^testing.T) {
// CFF Table 5: nibbles 0-9 are digits, a is '.', b is 'E', c is 'E-',
// e is '-', f ends the number.
check :: proc(t: ^testing.T, bytes: []byte, want: f64, loc := #caller_location) {
it := Cff_Dict_Iter{data = bytes}
op, ok := cff_dict_next(&it)
testing.expectf(t, ok, "no operator", loc = loc)
testing.expectf(t, op == 5, "operator %v", op, loc = loc)
if it.n < 1 {
testing.expectf(t, false, "no operand", loc = loc)
return
}
testing.expectf(t, abs(it.ops[0] - want) < 1e-12,
"got %v, want %v", it.ops[0], want, loc = loc)
}
check(t, {30, 0x0A, 0x00, 0x05, 0xFF, 5}, 0.0005) // 0.0005
check(t, {30, 0x1A, 0x5C, 0x3F, 5}, 0.0015) // 1.5E-3
check(t, {30, 0xE2, 0xA5, 0xFF, 5}, -2.5) // -2.5
check(t, {30, 0x1B, 0x2F, 5}, 100) // 1E2
check(t, {30, 0x0F, 5}, 0) // "0"
// A real that never terminates runs to the end of the DICT rather than
// past it; the operator is then missing, which ends the iteration.
it := Cff_Dict_Iter{data = {30, 0x11, 0x11}}
_, ok := cff_dict_next(&it)
testing.expect_value(t, ok, false)
}
@(test)
cff_test_dict_desync :: proc(t: ^testing.T) {
// A reserved operand byte means the operand/operator alternation has been
// lost; the iteration stops rather than inventing values.
it := Cff_Dict_Iter{data = {139, 31, 139, 5}}
_, ok := cff_dict_next(&it)
testing.expect_value(t, ok, false)
// A truncated multi-byte operand is not read past the end.
it2 := Cff_Dict_Iter{data = {29, 1, 2}}
_, ok2 := cff_dict_next(&it2)
testing.expect_value(t, ok2, false)
it3 := Cff_Dict_Iter{data = {12}}
_, ok3 := cff_dict_next(&it3)
testing.expect_value(t, ok3, false)
}
// ---------------------------------------------------------------------------
// Charstring operators
//
// Every expected point below is worked out by hand from T2 4.1 and 4.2. The
// glyph starts at the origin with an empty path, and endchar closes the last
// contour, which is why every outline ends in a Close.
// ---------------------------------------------------------------------------
@(test)
cff_test_moveto_lineto :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// 10 20 rmoveto 30 40 rlineto -30 0 rlineto endchar
p, err := cf_path(t, &fx, {cs_n(10), cs_n(20), 21,
cs_n(30), cs_n(40), 5,
cs_n(-30), cs_n(0), 5, 14})
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Line, .Line, .Close}, {{10, 20}, {40, 60}, {10, 60}})
// 50 hmoveto 10 hlineto endchar
p2, err2 := cf_path(t, &fx, {cs_n(50), 22, cs_n(10), 6, 14})
testing.expect_value(t, err2, Error.None)
cf_expect(t, &p2, {.Move, .Line, .Close}, {{50, 0}, {60, 0}})
// 50 vmoveto 10 vlineto endchar
p3, err3 := cf_path(t, &fx, {cs_n(50), 4, cs_n(10), 7, 14})
testing.expect_value(t, err3, Error.None)
cf_expect(t, &p3, {.Move, .Line, .Close}, {{0, 50}, {0, 60}})
// A second rmoveto ends the first contour and begins another. Its
// displacement is from the current point -- where the line ended, not
// where the previous move began.
p4, err4 := cf_path(t, &fx, {cs_n(10), cs_n(10), 21, cs_n(5), cs_n(0), 5,
cs_n(20), cs_n(20), 21, cs_n(5), cs_n(0), 5, 14})
testing.expect_value(t, err4, Error.None)
cf_expect(t, &p4,
{.Move, .Line, .Close, .Move, .Line, .Close},
{{10, 10}, {15, 10}, {35, 30}, {40, 30}})
}
@(test)
cff_test_alternating_lines :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// hlineto alternates h,v,h... starting horizontal, one argument each.
p, err := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, cs_n(10), cs_n(20), cs_n(30), 6, 14})
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Line, .Line, .Line, .Close},
{{0, 0}, {10, 0}, {10, 20}, {40, 20}})
// vlineto starts vertical.
p2, err2 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, cs_n(10), cs_n(20), 7, 14})
testing.expect_value(t, err2, Error.None)
cf_expect(t, &p2, {.Move, .Line, .Line, .Close}, {{0, 0}, {0, 10}, {20, 10}})
}
@(test)
cff_test_curves :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// rrcurveto: three relative points.
p, err := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(10), cs_n(20), cs_n(0), cs_n(10), cs_n(-10), 8, 14})
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Curve, .Close}, {{0, 0}, {10, 10}, {30, 10}, {40, 0}})
// hhcurveto, even: both tangents horizontal.
p2, err2 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(20), cs_n(30), cs_n(40), 27, 14})
testing.expect_value(t, err2, Error.None)
cf_expect(t, &p2, {.Move, .Curve, .Close}, {{0, 0}, {10, 0}, {30, 30}, {70, 30}})
// hhcurveto, odd: the leading argument is dy1, on the first control point
// of the first curve only.
p3, err3 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(5), cs_n(10), cs_n(20), cs_n(30), cs_n(40), 27, 14})
testing.expect_value(t, err3, Error.None)
cf_expect(t, &p3, {.Move, .Curve, .Close}, {{0, 0}, {10, 5}, {30, 35}, {70, 35}})
// vvcurveto, odd: the leading argument is dx1.
p4, err4 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(5), cs_n(10), cs_n(20), cs_n(30), cs_n(40), 26, 14})
testing.expect_value(t, err4, Error.None)
cf_expect(t, &p4, {.Move, .Curve, .Close}, {{0, 0}, {5, 10}, {25, 40}, {25, 80}})
// Two curves from one hhcurveto: dy1 applies only to the first.
p5, err5 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(5), cs_n(10), cs_n(20), cs_n(30), cs_n(40),
cs_n(1), cs_n(2), cs_n(3), cs_n(4), 27, 14})
testing.expect_value(t, err5, Error.None)
cf_expect(t, &p5, {.Move, .Curve, .Curve, .Close},
{{0, 0}, {10, 5}, {30, 35}, {70, 35}, {71, 35}, {73, 38}, {77, 38}})
}
@(test)
cff_test_alternating_curves :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// hvcurveto, four arguments: starts horizontal, ends vertical.
p, err := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(20), cs_n(30), cs_n(40), 31, 14})
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Curve, .Close}, {{0, 0}, {10, 0}, {30, 30}, {30, 70}})
// Five: the odd last argument is the other coordinate of the final point.
p2, err2 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(20), cs_n(30), cs_n(40), cs_n(50), 31, 14})
testing.expect_value(t, err2, Error.None)
cf_expect(t, &p2, {.Move, .Curve, .Close}, {{0, 0}, {10, 0}, {30, 30}, {80, 70}})
// Eight: two curves, the second starting vertical.
p3, err3 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(20), cs_n(30), cs_n(40),
cs_n(50), cs_n(60), cs_n(70), cs_n(80), 31, 14})
testing.expect_value(t, err3, Error.None)
cf_expect(t, &p3, {.Move, .Curve, .Curve, .Close},
{{0, 0}, {10, 0}, {30, 30}, {30, 70}, {30, 120}, {90, 190}, {170, 190}})
// vhcurveto is the same alternation started the other way round.
p4, err4 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(20), cs_n(30), cs_n(40), 30, 14})
testing.expect_value(t, err4, Error.None)
cf_expect(t, &p4, {.Move, .Curve, .Close}, {{0, 0}, {0, 10}, {20, 40}, {60, 40}})
}
@(test)
cff_test_curve_line_mixtures :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// rcurveline: curves, then exactly one line from the last two arguments.
p, err := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(10),
cs_n(20), cs_n(0), 24, 14})
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Curve, .Line, .Close},
{{0, 0}, {10, 10}, {20, 20}, {30, 30}, {50, 30}})
// rlinecurve: lines, then exactly one curve from the last six.
p2, err2 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(0),
cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(10), 25, 14})
testing.expect_value(t, err2, Error.None)
cf_expect(t, &p2, {.Move, .Line, .Curve, .Close},
{{0, 0}, {10, 0}, {20, 10}, {30, 20}, {40, 30}})
// Two lines then the curve.
p3, err3 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(0), cs_n(0), cs_n(10),
cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(10), 25, 14})
testing.expect_value(t, err3, Error.None)
cf_expect(t, &p3, {.Move, .Line, .Line, .Curve, .Close},
{{0, 0}, {10, 0}, {10, 10}, {20, 20}, {30, 30}, {40, 40}})
}
@(test)
cff_test_flex :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// flex: two curves and a flex depth that is read and ignored.
p, err := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(10), cs_n(-10),
cs_n(10), cs_n(-10), cs_n(10), cs_n(10), cs_n(10), cs_n(10),
cs_n(50), 12, 35, 14})
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Curve, .Curve, .Close},
{{0, 0}, {10, 10}, {20, 20}, {30, 10}, {40, 0}, {50, 10}, {60, 20}})
// hflex: seven arguments, and the curve returns to the starting y.
p2, err2 := cf_path(t, &fx, {cs_n(0), cs_n(5), 21,
cs_n(10), cs_n(20), cs_n(30), cs_n(40), cs_n(50), cs_n(60), cs_n(70),
12, 34, 14})
testing.expect_value(t, err2, Error.None)
cf_expect(t, &p2, {.Move, .Curve, .Curve, .Close},
{{0, 5}, {10, 5}, {30, 35}, {70, 35}, {120, 35}, {180, 5}, {250, 5}})
// hflex1: nine arguments, and again the end returns to the starting y.
p3, err3 := cf_path(t, &fx, {cs_n(0), cs_n(7), 21,
cs_n(10), cs_n(5), cs_n(20), cs_n(30), cs_n(40),
cs_n(50), cs_n(60), cs_n(-20), cs_n(70), 12, 36, 14})
testing.expect_value(t, err3, Error.None)
cf_expect(t, &p3, {.Move, .Curve, .Curve, .Close},
{{0, 7}, {10, 12}, {30, 42}, {70, 42}, {120, 42}, {180, 22}, {250, 7}})
// flex1: the eleventh argument goes on whichever axis moved further; here
// dx totals 150 and dy totals 5, so it is dx6 and y returns to the start.
p4, err4 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(10), cs_n(5), cs_n(20), cs_n(10), cs_n(30), cs_n(-5),
cs_n(40), cs_n(-10), cs_n(50), cs_n(5), cs_n(60), 12, 37, 14})
testing.expect_value(t, err4, Error.None)
cf_expect(t, &p4, {.Move, .Curve, .Curve, .Close},
{{0, 0}, {10, 5}, {30, 15}, {60, 10}, {100, 0}, {150, 5}, {210, 0}})
// flex1 the other way up: dy dominates, so the last argument is dy6 and x
// returns to where the flex began.
p5, err5 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21,
cs_n(5), cs_n(10), cs_n(10), cs_n(20), cs_n(-5), cs_n(30),
cs_n(-10), cs_n(40), cs_n(5), cs_n(50), cs_n(60), 12, 37, 14})
testing.expect_value(t, err5, Error.None)
cf_expect(t, &p5, {.Move, .Curve, .Curve, .Close},
{{0, 0}, {5, 10}, {15, 30}, {10, 60}, {0, 100}, {5, 150}, {0, 210}})
}
// ---------------------------------------------------------------------------
// The width argument
// ---------------------------------------------------------------------------
// cf_widths builds a Private DICT carrying just defaultWidthX and nominalWidthX.
@(private = "file")
cf_widths :: proc(fx: ^Cf_Fix, default_w, nominal_w: int) -> []byte {
d := make([dynamic]u8, 0, 16, fx.alloc)
cb_dict(&d, 20, default_w)
cb_dict(&d, 21, nominal_w)
return d[:]
}
@(test)
cff_test_width_argument :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
spec := Cff_Spec{priv_extra = cf_widths(&fx, 250, 100)}
// Two arguments to rmoveto: no width, so defaultWidthX stands.
f1 := cf_one(t, &fx, {cs_n(100), cs_n(60), 21, 14}, spec)
testing.expect_value(t, cff_glyph_advance(f1, 1), 250.0)
// Three: the first is the width, as a difference from nominalWidthX, and
// the move still uses the last two.
f2 := cf_one(t, &fx, {cs_n(50), cs_n(100), cs_n(60), 21, 14}, spec)
testing.expect_value(t, cff_glyph_advance(f2, 1), 150.0)
p2 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f2, 1, &p2), Error.None)
cf_expect(t, &p2, {.Move, .Close}, {{100, 60}})
// One argument to hmoveto is the displacement; two means a width first.
f3 := cf_one(t, &fx, {cs_n(60), cs_n(100), 22, 14}, spec)
testing.expect_value(t, cff_glyph_advance(f3, 1), 160.0)
p3 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f3, 1, &p3), Error.None)
cf_expect(t, &p3, {.Move, .Close}, {{100, 0}})
// An odd argument count on a stem operator means a width.
f4 := cf_one(t, &fx, {cs_n(70), cs_n(10), cs_n(20), 1, cs_n(0), cs_n(0), 21, 14}, spec)
testing.expect_value(t, cff_glyph_advance(f4, 1), 170.0)
// endchar alone may carry one.
f5 := cf_one(t, &fx, {cs_n(80), 14}, spec)
testing.expect_value(t, cff_glyph_advance(f5, 1), 180.0)
// An empty charstring never says anything about its width.
f6 := cf_one(t, &fx, {}, spec)
testing.expect_value(t, cff_glyph_advance(f6, 1), 250.0)
// Only the FIRST stack-clearing operator may carry a width. The three
// arguments of the second rmoveto are a displacement from the current
// point and nothing else.
f7 := cf_one(t, &fx, {cs_n(10), cs_n(20), 21, cs_n(50), cs_n(100), cs_n(60), 21, 14}, spec)
testing.expect_value(t, cff_glyph_advance(f7, 1), 250.0)
// cff_glyph_advance stops at the first stack-clearing operator, so it
// could not see a second width even if one were taken. Running the whole
// charstring is what holds the rule to "the first operator, and no other".
ip: Cff_Interp
testing.expect_value(t, cff_execute(&ip, f7, 1, nil, false, false), Error.None)
testing.expect_value(t, ip.width, 250.0)
p7 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f7, 1, &p7), Error.None)
cf_expect(t, &p7, {.Move, .Close, .Move, .Close}, {{10, 20}, {110, 80}})
}
// ---------------------------------------------------------------------------
// Hints
// ---------------------------------------------------------------------------
@(test)
cff_test_hintmask_implicit_vstems :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// Eight hstem hints, then a hintmask with two arguments left on the stack.
// Those are an implied vstem, making nine hints and so a two-byte mask.
// The second mask byte here is 0x15, which is the rmoveto operator: a
// reader that consumed only one mask byte would execute it and leave an
// extra contour in the outline.
code := make([dynamic]u8, 0, 40, fx.alloc)
for i in 1 ..= 16 { append(&code, cs_n(i)) }
append(&code, 18) // hstemhm: 8 hints
append(&code, cs_n(30), cs_n(40), 19, 0xFF, 0x15) // hintmask + 2 mask bytes
append(&code, cs_n(10), cs_n(20), 21, 14) // 10 20 rmoveto endchar
p, err := cf_path(t, &fx, code[:])
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Close}, {{10, 20}})
// A mask that runs past the end of the charstring is a fault, not a read
// past the end of the buffer.
_, err2 := cf_path(t, &fx, {cs_n(10), cs_n(20), 1, 19})
testing.expect_value(t, err2, Error.Bad_Cff)
// cntrmask counts and skips exactly as hintmask does.
code2 := make([dynamic]u8, 0, 40, fx.alloc)
for i in 1 ..= 16 { append(&code2, cs_n(i)) }
append(&code2, 18)
append(&code2, cs_n(30), cs_n(40), 20, 0xFF, 0x15)
append(&code2, cs_n(10), cs_n(20), 21, 14)
p3, err3 := cf_path(t, &fx, code2[:])
testing.expect_value(t, err3, Error.None)
cf_expect(t, &p3, {.Move, .Close}, {{10, 20}})
// A width may precede the implied vstem arguments, and is recognised by
// the argument count being odd.
code4 := make([dynamic]u8, 0, 8, fx.alloc)
append(&code4, cs_n(90), cs_n(10), cs_n(20), 19, 0x80)
append(&code4, cs_n(0), cs_n(0), 21, 14)
f4 := cf_one(t, &fx, code4[:], Cff_Spec{priv_extra = cf_widths(&fx, 250, 100)})
testing.expect_value(t, cff_glyph_advance(f4, 1), 190.0)
}
// ---------------------------------------------------------------------------
// Subroutines
// ---------------------------------------------------------------------------
// cf_subrs builds `count` subroutines of which only the first does anything,
// so that which index a biased operand selects is visible in the outline.
@(private = "file")
cf_subrs :: proc(fx: ^Cf_Fix, count: int, first: []byte) -> [][]byte {
out := make([][]byte, count, fx.alloc)
out[0] = first
return out
}
@(test)
cff_test_subr_bias_thresholds :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// 10 20 rmoveto, then return to the caller.
draw := []byte{cs_n(10), cs_n(20), 21, 11}
want_verbs := []Path_Verb{.Move, .Close}
want_pts := []Point{{10, 20}}
// 1239 local subroutines: bias 107, so operand -107 is subroutine 0.
f1 := cf_one(t, &fx, {cs_n(-107), 10, 14}, Cff_Spec{subrs = cf_subrs(&fx, 1239, draw)})
p1 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f1, 1, &p1), Error.None)
cf_expect(t, &p1, want_verbs, want_pts)
// 1240: the bias becomes 1131. The same operand now selects subroutine
// 1024, which is empty, and -1131 selects subroutine 0.
spec2 := Cff_Spec{subrs = cf_subrs(&fx, 1240, draw)}
f2 := cf_one(t, &fx, {cs_n(-107), 10, 14}, spec2)
p2 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f2, 1, &p2), Error.None)
testing.expect_value(t, len(p2.verbs), 0)
f3 := cf_one(t, &fx, {254, 255, 10, 14}, spec2)
p3 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f3, 1, &p3), Error.None)
cf_expect(t, &p3, want_verbs, want_pts)
// 33900: the bias becomes 32768, reached with the two-byte 28 operand.
spec4 := Cff_Spec{subrs = cf_subrs(&fx, 33900, draw)}
f4 := cf_one(t, &fx, {28, 0x80, 0x00, 10, 14}, spec4)
p4 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f4, 1, &p4), Error.None)
cf_expect(t, &p4, want_verbs, want_pts)
f5 := cf_one(t, &fx, {254, 255, 10, 14}, spec4)
p5 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f5, 1, &p5), Error.None)
testing.expect_value(t, len(p5.verbs), 0)
// Global subroutines are biased by their own count, independently.
f6 := cf_one(t, &fx, {cs_n(-107), 29, 14}, Cff_Spec{gsubrs = cf_subrs(&fx, 3, draw)})
p6 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f6, 1, &p6), Error.None)
cf_expect(t, &p6, want_verbs, want_pts)
// A subroutine index outside the INDEX is an error, not a wild read.
f7 := cf_one(t, &fx, {cs_n(107), 29, 14}, Cff_Spec{gsubrs = cf_subrs(&fx, 3, draw)})
p7 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f7, 1, &p7), Error.Bad_Cff)
}
@(test)
cff_test_subr_cycles_terminate :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// A subroutine that calls itself: bounded by the depth cap, not by luck.
self := []byte{cs_n(-107), 29, 11}
f := cf_one(t, &fx, {cs_n(-107), 29, 14}, Cff_Spec{gsubrs = cf_subrs(&fx, 1, self)})
p := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 1, &p), Error.Bad_Cff)
// Two subroutines that call each other.
pair := make([][]byte, 2, fx.alloc)
pair[0] = {cs_n(-106), 29, 11} // -106 + 107 = 1
pair[1] = {cs_n(-107), 29, 11} // -107 + 107 = 0
f2 := cf_one(t, &fx, {cs_n(-107), 29, 14}, Cff_Spec{gsubrs = pair})
p2 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f2, 1, &p2), Error.Bad_Cff)
// Depth exactly at the cap is still allowed: ten nested calls draw.
nest := make([][]byte, 12, fx.alloc)
nest[11] = {cs_n(10), cs_n(20), 21, 11}
for i in 0 ..< 11 {
nest[i] = {cs_n(i + 1 - 107), 29, 11}
}
f3 := cf_one(t, &fx, {cs_n(-107), 29, 14}, Cff_Spec{gsubrs = nest})
p3 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f3, 1, &p3), Error.None)
cf_expect(t, &p3, {.Move, .Close}, {{10, 20}})
}
@(test)
cff_test_stack_overflow :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// 48 operands is the limit T2 sets, and 24 line segments come out of it.
ok_code := make([dynamic]u8, 0, 64, fx.alloc)
append(&ok_code, cs_n(0), cs_n(0), 21)
for _ in 0 ..< 48 { append(&ok_code, cs_n(1)) }
append(&ok_code, 5, 14)
p, err := cf_path(t, &fx, ok_code[:])
testing.expect_value(t, err, Error.None)
testing.expect_value(t, len(p.verbs), 26) // one move, 24 lines, one close
// 49 overruns it, and the glyph stops rather than writing past the stack.
bad := make([dynamic]u8, 0, 64, fx.alloc)
append(&bad, cs_n(0), cs_n(0), 21)
for _ in 0 ..< 49 { append(&bad, cs_n(1)) }
append(&bad, 5, 14)
_, err2 := cf_path(t, &fx, bad[:])
testing.expect_value(t, err2, Error.Bad_Cff)
// The same overrun spread across a subroutine call.
push := make([dynamic]u8, 0, 64, fx.alloc)
for _ in 0 ..< 40 { append(&push, cs_n(1)) }
append(&push, 11)
long := make([dynamic]u8, 0, 16, fx.alloc)
for _ in 0 ..< 40 { append(&long, cs_n(1)) }
append(&long, cs_n(-107), 29, 5, 14)
f := cf_one(t, &fx, long[:], Cff_Spec{gsubrs = cf_subrs(&fx, 1, push[:])})
p3 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 1, &p3), Error.Bad_Cff)
}
@(private = "file")
cf_build_parse :: proc(t: ^testing.T, fx: ^Cf_Fix, spec: Cff_Spec) -> ^Cff_Font {
data := cb_build(spec, fx.alloc)
f, err := cff_parse(data, fx.alloc)
testing.expect_value(t, err, Error.None)
return f
}
@(test)
cff_test_fuel_stops_exponential_calls :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// Nine subroutines, each calling the next ten times. Nothing here is
// cyclic and nothing exceeds the depth cap, but the call tree has 10^8
// leaves, so only the operation budget stops it.
subrs := make([][]byte, 9, fx.alloc)
subrs[8] = {cs_n(1), cs_n(1), 21, 11}
for i in 0 ..< 8 {
b := make([dynamic]u8, 0, 24, fx.alloc)
for _ in 0 ..< 10 {
append(&b, cs_n(i + 1 - 107), 29)
}
append(&b, 11)
subrs[i] = b[:]
}
f := cf_one(t, &fx, {cs_n(-107), 29, 14}, Cff_Spec{gsubrs = subrs})
p := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 1, &p), Error.Bad_Cff)
}
// ---------------------------------------------------------------------------
// seac
// ---------------------------------------------------------------------------
@(test)
cff_test_seac :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
base := []byte{cs_n(0), cs_n(0), 21, cs_n(10), cs_n(0), 5, 14}
accent := []byte{cs_n(0), cs_n(0), 21, cs_n(5), cs_n(0), 5, 14}
// 100 200 65 194 endchar: compose the glyphs the Standard Encoding puts at
// codes 65 ("A") and 194 ("acute"), the accent displaced by (100,200).
composite := []byte{239, 247, 92, cs_n(65), 247, 86, 14}
spec := Cff_Spec{
charstrings = {{14}, base, accent, composite},
// format 0: SIDs 34 "A", 125 "acute", 1 "space"
charset = {0, 0, 34, 0, 125, 0, 1},
}
f := cf_build_parse(t, &fx, spec)
if f == nil { return }
gid, ok := cff_gid_for_name(f, "acute")
testing.expect_value(t, ok, true)
testing.expect_value(t, gid, u16(2))
p := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 3, &p), Error.None)
cf_expect(t, &p,
{.Move, .Line, .Close, .Move, .Line, .Close},
{{0, 0}, {10, 0}, {100, 200}, {105, 200}})
// The same composition with a width in front of it: endchar's four seac
// arguments are the LAST four, not the first four.
with_width := []byte{cs_n(55), 239, 247, 92, cs_n(65), 247, 86, 14}
spec_w := spec
spec_w.charstrings = {{14}, base, accent, with_width}
spec_w.priv_extra = cf_widths(&fx, 250, 100)
fw := cf_build_parse(t, &fx, spec_w)
testing.expect_value(t, cff_glyph_advance(fw, 3), 155.0)
pw := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(fw, 3, &pw), Error.None)
cf_expect(t, &pw,
{.Move, .Line, .Close, .Move, .Line, .Close},
{{0, 0}, {10, 0}, {100, 200}, {105, 200}})
// A seac naming a glyph this font does not have degrades to No_Glyph
// rather than drawing something arbitrary.
missing := []byte{239, 247, 92, cs_n(66), 247, 86, 14}
spec2 := spec
spec2.charstrings = {{14}, base, accent, missing}
f2 := cf_build_parse(t, &fx, spec2)
p2 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f2, 3, &p2), Error.No_Glyph)
}
// ---------------------------------------------------------------------------
// CIDFonts
// ---------------------------------------------------------------------------
@(test)
cff_test_cid_font :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
fd0 := make([][]byte, 1, fx.alloc)
fd0[0] = {cs_n(10), cs_n(20), 21, 11}
fd1 := make([][]byte, 1, fx.alloc)
fd1[0] = {cs_n(30), cs_n(40), 21, 11}
call := []byte{cs_n(-107), 10, 14}
spec := Cff_Spec{
ros = true,
charstrings = {{14}, call, call, {14}},
// charset format 2: one range, first CID 100, nLeft 2, so glyphs 1..3
// carry CIDs 100..102.
charset = {2, 0, 100, 0, 2},
// FDSelect format 3: glyphs 0-1 use font DICT 0, glyphs 2-3 use 1.
fdselect = {3, 0, 2, 0, 0, 0, 0, 2, 1, 0, 4},
fd_extra = {cf_widths(&fx, 111, 0), cf_widths(&fx, 222, 0)},
fd_subrs = {fd0, fd1},
}
f := cf_build_parse(t, &fx, spec)
if f == nil { return }
testing.expect_value(t, f.is_cid, true)
testing.expect_value(t, len(f.fds), 2)
// The charset maps CIDs, not names.
check :: proc(t: ^testing.T, f: ^Cff_Font, cid, want: u16, loc := #caller_location) {
gid, ok := cff_gid_for_cid(f, cid)
testing.expectf(t, ok, "CID %d not found", cid, loc = loc)
testing.expectf(t, gid == want, "CID %d -> gid %d, want %d", cid, gid, want, loc = loc)
}
check(t, f, 100, 1)
check(t, f, 101, 2)
check(t, f, 102, 3)
_, has99 := cff_gid_for_cid(f, 99)
testing.expect_value(t, has99, false)
_, has_name := cff_gid_for_name(f, "A")
testing.expect_value(t, has_name, false)
testing.expect_value(t, cff_builtin_encoding(f)[65], "")
// Each glyph takes its local subroutines from its own font DICT: the same
// charstring draws different things in the two FDSelect ranges.
p1 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 1, &p1), Error.None)
cf_expect(t, &p1, {.Move, .Close}, {{10, 20}})
p2 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 2, &p2), Error.None)
cf_expect(t, &p2, {.Move, .Close}, {{30, 40}})
// And its widths.
testing.expect_value(t, cff_glyph_advance(f, 1), 111.0)
testing.expect_value(t, cff_glyph_advance(f, 2), 222.0)
// FDSelect format 0 is one byte per glyph.
spec0 := spec
spec0.fdselect = {0, 0, 0, 1, 1}
f0 := cf_build_parse(t, &fx, spec0)
q1 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f0, 1, &q1), Error.None)
cf_expect(t, &q1, {.Move, .Close}, {{10, 20}})
q2 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f0, 2, &q2), Error.None)
cf_expect(t, &q2, {.Move, .Close}, {{30, 40}})
// A truncated FDSelect leaves the glyphs it did not reach on font DICT 0
// instead of reading past its end.
specx := spec
specx.fdselect = {3, 0, 9, 0, 0, 0}
fx2 := cf_build_parse(t, &fx, specx)
q3 := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(fx2, 2, &q3), Error.None)
cf_expect(t, &q3, {.Move, .Close}, {{10, 20}})
}
@(test)
cff_test_cid_identity_charset :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// A CIDFont with no charset of its own maps CID to GID directly, which is
// what a subset font using Identity-H expects.
fd0 := make([][]byte, 1, fx.alloc)
fd0[0] = {cs_n(1), cs_n(2), 21, 11}
f := cf_build_parse(t, &fx, Cff_Spec{
ros = true,
charstrings = {{14}, {14}, {14}},
fd_subrs = {fd0},
})
if f == nil { return }
gid, ok := cff_gid_for_cid(f, 2)
testing.expect_value(t, ok, true)
testing.expect_value(t, gid, u16(2))
_, past := cff_gid_for_cid(f, 3)
testing.expect_value(t, past, false)
// A font that is not a CIDFont treats the CID as the glyph index, which is
// what a PDF pairing Identity-H with a name-keyed CFF means by it.
f2 := cf_one(t, &fx, {14})
g2, ok2 := cff_gid_for_cid(f2, 1)
testing.expect_value(t, ok2, true)
testing.expect_value(t, g2, u16(1))
_, ok3 := cff_gid_for_cid(f2, 2)
testing.expect_value(t, ok3, false)
}
// ---------------------------------------------------------------------------
// charset, Encoding, strings and the FontMatrix
// ---------------------------------------------------------------------------
@(test)
cff_test_charset_formats :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
four := [][]byte{{14}, {14}, {14}, {14}}
expect_abc :: proc(t: ^testing.T, f: ^Cff_Font, loc := #caller_location) {
names := []string{"A", "B", "C"}
for name, i in names {
gid, ok := cff_gid_for_name(f, name)
testing.expectf(t, ok, "%s missing", name, loc = loc)
testing.expectf(t, gid == u16(i + 1), "%s -> %d", name, gid, loc = loc)
}
}
// Format 0 lists one SID per glyph.
expect_abc(t, cf_build_parse(t, &fx, Cff_Spec{
charstrings = four, charset = {0, 0, 34, 0, 35, 0, 36}}))
// Format 1 lists ranges with a one-byte nLeft.
expect_abc(t, cf_build_parse(t, &fx, Cff_Spec{
charstrings = four, charset = {1, 0, 34, 2}}))
// Format 2 is the same with a two-byte nLeft.
expect_abc(t, cf_build_parse(t, &fx, Cff_Spec{
charstrings = four, charset = {2, 0, 34, 0, 2}}))
// A charset that stops early leaves the rest of the glyphs unnamed rather
// than reading past it.
f := cf_build_parse(t, &fx, Cff_Spec{charstrings = four, charset = {0, 0, 34}})
gid, ok := cff_gid_for_name(f, "A")
testing.expect_value(t, ok, true)
testing.expect_value(t, gid, u16(1))
_, has_b := cff_gid_for_name(f, "B")
testing.expect_value(t, has_b, false)
// A name that sorts between two the font does have must not come back as
// whichever neighbour the search landed on.
named := cf_build_parse(t, &fx, Cff_Spec{charstrings = four, charset = {0, 0, 34, 0, 35, 0, 36}})
for probe in ([]string{"AA", "0", "ZZ", "Bb", ""}) {
gid2, ok2 := cff_gid_for_name(named, probe)
testing.expectf(t, !ok2, "%q matched glyph %d", probe, gid2)
}
// With no charset at all the predefined ISOAdobe order applies, in which
// SID 1 is "space".
f2 := cf_build_parse(t, &fx, Cff_Spec{charstrings = four})
sp, sp_ok := cff_gid_for_name(f2, "space")
testing.expect_value(t, sp_ok, true)
testing.expect_value(t, sp, u16(1))
ex, ex_ok := cff_gid_for_name(f2, "exclam")
testing.expect_value(t, ex_ok, true)
testing.expect_value(t, ex, u16(2))
// A SID at or past 391 names an entry of the String INDEX.
f3 := cf_build_parse(t, &fx, Cff_Spec{
charstrings = four,
charset = {0, 1, 135, 1, 136, 0, 36}, // SIDs 391, 392, 36
strings = {transmute([]byte)string("Custom"), transmute([]byte)string("Other")},
})
cu, cu_ok := cff_gid_for_name(f3, "Custom")
testing.expect_value(t, cu_ok, true)
testing.expect_value(t, cu, u16(1))
ot, ot_ok := cff_gid_for_name(f3, "Other")
testing.expect_value(t, ot_ok, true)
testing.expect_value(t, ot, u16(2))
// A SID past the end of the String INDEX names nothing at all.
testing.expect_value(t, cff_sid_string(f3, 400), "")
testing.expect_value(t, cff_sid_string(f3, 0), ".notdef")
testing.expect_value(t, cff_sid_string(f3, 390), "Semibold")
}
@(test)
cff_test_encoding_formats :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
four := [][]byte{{14}, {14}, {14}, {14}}
charset := []byte{0, 0, 34, 0, 35, 0, 36} // A, B, C
// Format 0: a code per glyph, in glyph order.
f := cf_build_parse(t, &fx, Cff_Spec{
charstrings = four, charset = charset, encoding = {0, 2, 0x41, 0x42}})
g, ok := cff_gid_for_code(f, 0x41)
testing.expect_value(t, ok, true)
testing.expect_value(t, g, u16(1))
g2, ok2 := cff_gid_for_code(f, 0x42)
testing.expect_value(t, ok2, true)
testing.expect_value(t, g2, u16(2))
testing.expect_value(t, cff_builtin_encoding(f)[0x41], "A")
_, unset := cff_gid_for_code(f, 0x43)
testing.expect_value(t, unset, false)
// Format 1: ranges of consecutive codes.
f2 := cf_build_parse(t, &fx, Cff_Spec{
charstrings = four, charset = charset, encoding = {1, 1, 0x41, 1}})
h1, h1ok := cff_gid_for_code(f2, 0x41)
testing.expect_value(t, h1ok, true)
testing.expect_value(t, h1, u16(1))
h2, h2ok := cff_gid_for_code(f2, 0x42)
testing.expect_value(t, h2ok, true)
testing.expect_value(t, h2, u16(2))
// The high bit of the format byte adds supplements, which name their glyph
// by SID rather than by position.
f3 := cf_build_parse(t, &fx, Cff_Spec{
charstrings = four, charset = charset,
encoding = {0x80, 1, 0x41, 1, 0x2A, 0, 34}})
s1, s1ok := cff_gid_for_code(f3, 0x2A)
testing.expect_value(t, s1ok, true)
testing.expect_value(t, s1, u16(1))
testing.expect_value(t, cff_builtin_encoding(f3)[0x2A], "A")
// A truncated encoding table stops where the data does.
f4 := cf_build_parse(t, &fx, Cff_Spec{
charstrings = four, charset = charset, encoding = {0, 9, 0x41}})
t1, t1ok := cff_gid_for_code(f4, 0x41)
testing.expect_value(t, t1ok, true)
testing.expect_value(t, t1, u16(1))
}
@(test)
cff_test_font_matrix :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// No FontMatrix operator: 1/1000 units per em.
testing.expect_value(t, cff_font_matrix(cf_one(t, &fx, {14})),
Matrix{0.001, 0, 0, 0.001, 0, 0})
// 0.0005 0 0 0.0005 0 0 FontMatrix, the first and fourth written as the
// nibble-packed reals CFF Table 5 defines.
d := make([dynamic]u8, 0, 32, fx.alloc)
half := []u8{30, 0x0A, 0x00, 0x05, 0xFF} // 0.0005
zero := cb_int(0)
append(&d, ..half)
append(&d, ..zero[:])
append(&d, ..zero[:])
append(&d, ..half)
append(&d, ..zero[:])
append(&d, ..zero[:])
append(&d, 12, 7)
f := cf_one(t, &fx, {14}, Cff_Spec{top_extra = d[:]})
testing.expect_value(t, cff_font_matrix(f), Matrix{0.0005, 0, 0, 0.0005, 0, 0})
testing.expect_value(t, cff_font_matrix(nil), Matrix{0.001, 0, 0, 0.001, 0, 0})
}
// ---------------------------------------------------------------------------
// Arithmetic, storage and conditionals
// ---------------------------------------------------------------------------
@(test)
cff_test_arithmetic_operators :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// Each case computes one relative line from the origin, so the end point
// is the operator's result.
check :: proc(t: ^testing.T, fx: ^Cf_Fix, tail: []byte, want: Point, loc := #caller_location) {
code := make([dynamic]u8, 0, 32, fx.alloc)
append(&code, cs_n(0), cs_n(0), 21)
append(&code, ..tail)
append(&code, 5, 14)
p, err := cf_path(t, fx, code[:])
testing.expectf(t, err == .None, "error %v", err, loc = loc)
if len(p.points) < 2 {
testing.expectf(t, false, "no line drawn: %v", p.points, loc = loc)
return
}
got := p.points[1]
testing.expectf(t, abs(got.x - want.x) < 1e-9 && abs(got.y - want.y) < 1e-9,
"got %v, want %v", got, want, loc = loc)
}
// (5 + 3) * 2, then 0
check(t, &fx, {cs_n(5), cs_n(3), 12, 10, cs_n(2), 12, 24, cs_n(0)}, {16, 0})
// 10 - 3, and 20 / 4
check(t, &fx, {cs_n(10), cs_n(3), 12, 11, cs_n(20), cs_n(4), 12, 12}, {7, 5})
// division by zero yields zero rather than an infinity
check(t, &fx, {cs_n(10), cs_n(0), 12, 12, cs_n(0)}, {0, 0})
// -7 neg, and abs of -9
check(t, &fx, {cs_n(-7), 12, 14, cs_n(-9), 12, 9}, {7, 9})
// sqrt 16, and sqrt of a negative is zero, not a NaN
check(t, &fx, {cs_n(16), 12, 26, cs_n(-4), 12, 26}, {4, 0})
// exch swaps the top two
check(t, &fx, {cs_n(10), cs_n(20), 12, 28}, {20, 10})
// dup, then drop the copy
check(t, &fx, {cs_n(11), 12, 27, 12, 18, cs_n(3)}, {11, 3})
// index 1 reaches past the top of the stack
check(t, &fx, {cs_n(7), cs_n(8), cs_n(1), 12, 29, cs_n(0), 5, cs_n(0)}, {7, 8})
// put 42 at 0, then get it back
check(t, &fx, {cs_n(42), cs_n(0), 12, 20, cs_n(0), 12, 21, cs_n(3)}, {42, 3})
// 10 20 1 2 ifelse: 1 <= 2, so the first of the two is kept
check(t, &fx, {cs_n(10), cs_n(20), cs_n(1), cs_n(2), 12, 22, cs_n(0)}, {10, 0})
// 10 20 3 2 ifelse: 3 > 2, so the second is kept
check(t, &fx, {cs_n(10), cs_n(20), cs_n(3), cs_n(2), 12, 22, cs_n(0)}, {20, 0})
// and / or / not on the truth of a number
check(t, &fx, {cs_n(1), cs_n(0), 12, 3, cs_n(1), cs_n(0), 12, 4}, {0, 1})
check(t, &fx, {cs_n(0), 12, 5, cs_n(4), cs_n(4), 12, 15}, {1, 1})
// 3 elements rolled up by 1: 1 2 3 -> 3 1 2
check(t, &fx, {cs_n(1), cs_n(2), cs_n(3), cs_n(3), cs_n(1), 12, 30,
12, 18, 5, cs_n(0), cs_n(0)}, {3, 1})
// A roll of more elements than the stack holds does nothing.
check(t, &fx, {cs_n(4), cs_n(9), cs_n(40), cs_n(1), 12, 30}, {4, 9})
// The 16.16 fixed operand is the only fractional number a charstring has.
p, err := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, 255, 0x00, 0x01, 0x80, 0x00, cs_n(0), 5, 14})
testing.expect_value(t, err, Error.None)
cf_expect(t, &p, {.Move, .Line, .Close}, {{0, 0}, {1.5, 0}})
}
// ---------------------------------------------------------------------------
// Malformed input
// ---------------------------------------------------------------------------
@(test)
cff_test_rejects_unreadable :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
_, e1 := cff_parse({}, fx.alloc)
testing.expect_value(t, e1, Error.Truncated)
_, e2 := cff_parse({1, 0, 4}, fx.alloc)
testing.expect_value(t, e2, Error.Truncated)
// CFF2 is a different format with different charstring operators, so it is
// refused rather than misread as CFF1.
cff2 := make([]byte, len(CFF_MINIMAL), fx.alloc)
copy(cff2, CFF_MINIMAL[:])
cff2[0] = 2
_, e3 := cff_parse(cff2, fx.alloc)
testing.expect_value(t, e3, Error.Bad_Cff)
// A header claiming to be longer than the file.
short := make([]byte, len(CFF_MINIMAL), fx.alloc)
copy(short, CFF_MINIMAL[:])
short[2] = 0xFF
_, e4 := cff_parse(short, fx.alloc)
testing.expect_value(t, e4, Error.Truncated)
// A CharStrings offset past the end of the font.
no_cs := make([]byte, len(CFF_MINIMAL), fx.alloc)
copy(no_cs, CFF_MINIMAL[:])
no_cs[29] = 0xFF // the low byte of the CharStrings offset operand
_, e5 := cff_parse(no_cs, fx.alloc)
testing.expect_value(t, e5, Error.Bad_Cff)
}
@(test)
cff_test_charstring_index_past_end :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// The CharStrings INDEX at offset 48 has one-byte offsets at 51..53. The
// last one is pushed past the end of the font: the item it names is
// clamped, and the glyph either draws or fails, but nothing is read out of
// bounds.
data := make([]byte, len(CFF_MINIMAL), fx.alloc)
copy(data, CFF_MINIMAL[:])
data[53] = 0xFF
f, err := cff_parse(data, fx.alloc)
testing.expect_value(t, err, Error.None)
if f == nil { return }
testing.expect_value(t, cff_glyph_count(f), 2)
p := path_make(fx.alloc)
_ = cff_glyph_path(f, 1, &p)
_ = cff_glyph_advance(f, 1)
// The first offset pushed past the second, so every item is empty.
data2 := make([]byte, len(CFF_MINIMAL), fx.alloc)
copy(data2, CFF_MINIMAL[:])
data2[51] = 0xF0
f2, err2 := cff_parse(data2, fx.alloc)
testing.expect_value(t, err2, Error.None)
if f2 == nil { return }
q := path_make(fx.alloc)
_ = cff_glyph_path(f2, 1, &q)
}
@(test)
cff_test_reserved_operators :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// Reserved one-byte operator.
_, e1 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, 2, 14})
testing.expect_value(t, e1, Error.Bad_Cff)
// Reserved two-byte escape.
_, e2 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, 12, 40, 14})
testing.expect_value(t, e2, Error.Bad_Cff)
// An escape with no second byte.
_, e3 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, 12})
testing.expect_value(t, e3, Error.Bad_Cff)
// An operand cut short by the end of the charstring.
_, e4 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, 28, 1})
testing.expect_value(t, e4, Error.Bad_Cff)
_, e5 := cf_path(t, &fx, {cs_n(0), cs_n(0), 21, 255, 1, 2})
testing.expect_value(t, e5, Error.Bad_Cff)
// A charstring that simply runs out still leaves its outline closed.
p, e6 := cf_path(t, &fx, {cs_n(10), cs_n(20), 21, cs_n(5), cs_n(0), 5})
testing.expect_value(t, e6, Error.None)
cf_expect(t, &p, {.Move, .Line, .Close}, {{10, 20}, {15, 20}})
}
@(test)
cff_test_charstring_type_1 :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// A CFF whose charstrings are Type 1 is a different language; the font
// still loads, so widths and names survive, but outlines do not.
d := make([dynamic]u8, 0, 16, fx.alloc)
cb_dict(&d, 0x0C00 | 6, 1)
f := cf_one(t, &fx, {cs_n(10), cs_n(20), 21, 14}, Cff_Spec{top_extra = d[:]})
if f == nil { return }
testing.expect_value(t, f.charstring_type, 1)
p := path_make(fx.alloc)
testing.expect_value(t, cff_glyph_path(f, 1, &p), Error.Not_Implemented)
testing.expect_value(t, len(p.verbs), 0)
}
@(test)
cff_test_truncation_and_bit_flips :: proc(t: ^testing.T) {
fx: Cf_Fix
cff_fix_init(&fx)
defer cff_fix_destroy(&fx)
// Every prefix of a font, and every byte of it in turn set to three
// different values: the reader must come back either way, and any font it
// does return must survive having every glyph walked.
walk :: proc(fx: ^Cf_Fix, data: []byte) {
f, err := cff_parse(data, fx.alloc)
if err != .None || f == nil { return }
for gid in 0 ..< min(cff_glyph_count(f), 8) {
p := path_make(fx.alloc)
_ = cff_glyph_path(f, u16(gid), &p)
_ = cff_glyph_advance(f, u16(gid))
path_destroy(&p)
}
_, _ = cff_gid_for_name(f, "A")
_, _ = cff_gid_for_cid(f, 1)
_ = cff_font_matrix(f)
}
for cut in 0 ..= len(CFF_MINIMAL) {
walk(&fx, CFF_MINIMAL[:cut])
}
scratch := make([]byte, len(CFF_MINIMAL), fx.alloc)
values := []u8{0x00, 0x80, 0xFF}
for i in 0 ..< len(CFF_MINIMAL) {
for v in values {
copy(scratch, CFF_MINIMAL[:])
scratch[i] = v
walk(&fx, scratch)
}
}
// The same over a font with subroutines, a custom encoding and a CIDFont
// layout, which reach the parts of the reader the minimal font does not.
fd0 := make([][]byte, 1, fx.alloc)
fd0[0] = {cs_n(1), cs_n(2), 21, 11}
rich := cb_build(Cff_Spec{
ros = true,
charstrings = {{14}, {cs_n(-107), 10, 14}, {14}},
charset = {2, 0, 100, 0, 1},
fdselect = {3, 0, 1, 0, 0, 0, 0, 3},
fd_extra = {cf_widths(&fx, 111, 5)},
fd_subrs = {fd0},
}, fx.alloc)
for cut in 0 ..= len(rich) {
walk(&fx, rich[:cut])
}
scratch2 := make([]byte, len(rich), fx.alloc)
for i in 0 ..< len(rich) {
for v in values {
copy(scratch2, rich)
scratch2[i] = v
walk(&fx, scratch2)
}
}
}