1622 lines
63 KiB
Odin
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|