package pdf import "core:mem" import "core:mem/virtual" import "core:testing" // Tests for font_type1.odin. // // THE FIXTURES // // T1_FONT_A and T1_FONT_B below are two complete Type 1 font programs, emitted // as byte literals by a throwaway Python script. The script assembled the // PostScript by hand, encoded each charstring with the Type 1 number and // operator encoding, enciphered the charstrings at r = 4330 and the Private // dictionary at r = 55665, and chose the eexec random padding so that the first // four ciphertext bytes are neither whitespace nor all hex digits -- the // condition that lets a reader tell the binary form from the .pfa one. // // Font A carries, in /CharStrings order: // // 0 .notdef 0 500 hsbw endchar // 1 A sidebearing 50, a 100x200 box // 2 B one FLEX, driven through the standard Subrs 0/1/2 // 3 C hint replacement: OtherSubr 3 must hand 4 back to the callsubr // 4 D A's outline with a zero sidebearing // 5 Aacute seac of A (65) and acute (194), asb = acute's own sidebearing // 6 acute sidebearing 20, a triangle // 7 E rrcurveto, hvcurveto, vhcurveto and a div // // and Subrs 0-2 as every Type 1 font defines them, Subrs[3] the do-nothing // subroutine hint replacement falls back to, and Subrs[4] a distinctive // `0 100 rlineto return` so that a test can tell 4 from 3. Its /Encoding uses // the `dup / put` form. // // Font B has no eexec layer at all -- its Private dictionary is plain text, // with the charstrings still enciphered -- the `/Encoding StandardEncoding def` // shorthand, and a /FontMatrix that is not the default. // // The PFB, hex-eexec and truncated forms are derived from Font A in the tests // below rather than being three more literals; T1A_CLEAR_LEN and T1A_BIN_LEN // are where its three parts meet. // --------------------------------------------------------------------------- // Fixture // // Every font under test is parsed into a local arena, destroyed on the way out // whatever happened, so a failing assertion cannot leak. // --------------------------------------------------------------------------- @(private = "file") T1_Fixture :: struct { arena: virtual.Arena, alloc: mem.Allocator, } @(private = "file") t1f_init :: proc(f: ^T1_Fixture) { _ = virtual.arena_init_growing(&f.arena) f.alloc = virtual.arena_allocator(&f.arena) } @(private = "file") t1f_destroy :: proc(f: ^T1_Fixture) { virtual.arena_destroy(&f.arena) } // t1f_font parses a font and fails the test if it did not come back. @(private = "file") t1f_font :: proc(t: ^testing.T, f: ^T1_Fixture, data: []byte, label: string) -> ^Type1_Font { font, err := t1_parse(data, f.alloc) if !testing.expectf(t, err == .None && font != nil, "%s: t1_parse gave %v", label, err) { return nil } return font } // t1f_glyph runs one named glyph and returns its outline. @(private = "file") t1f_glyph :: proc(t: ^testing.T, f: ^T1_Fixture, font: ^Type1_Font, name: string) -> Path { p := path_make(f.alloc) gid, ok := t1_gid_for_name(font, name) if !testing.expectf(t, ok, "glyph /%s is missing from the font", name) { return p } err := t1_glyph_path(font, gid, &p) testing.expectf(t, err == .None, "/%s: t1_glyph_path gave %v", name, err) return p } // t1f_expect_path compares an outline element by element. Both arrays are // checked, so a path with the right points under the wrong verbs fails. @(private = "file") t1f_expect_path :: proc(t: ^testing.T, p: ^Path, verbs: []Path_Verb, pts: []Point, label: string) { ok := len(p.verbs) == len(verbs) && len(p.points) == len(pts) if ok { for v, i in verbs { if p.verbs[i] != v { ok = false break } } } if ok { for q, i in pts { if abs(p.points[i].x - q.x) > 1e-9 || abs(p.points[i].y - q.y) > 1e-9 { ok = false break } } } testing.expectf(t, ok, "%s:\n got %v %v\n want %v %v", label, p.verbs[:], p.points[:], verbs, pts) } @(private = "file", rodata) T1_FONT_A := [1723]u8{ 0x25, 0x21, 0x46, 0x6f, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x31, 0x2d, 0x31, 0x2e, 0x30, 0x3a, 0x20, 0x4f, 0x64, 0x69, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x20, 0x30, 0x30, 0x31, 0x2e, 0x30, 0x30, 0x31, 0x0a, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x20, 0x2f, 0x4f, 0x64, 0x69, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x20, 0x5b, 0x30, 0x2e, 0x30, 0x30, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x2e, 0x30, 0x30, 0x31, 0x20, 0x30, 0x20, 0x30, 0x5d, 0x20, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x32, 0x35, 0x36, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x0a, 0x30, 0x20, 0x31, 0x20, 0x32, 0x35, 0x35, 0x20, 0x7b, 0x31, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x65, 0x78, 0x63, 0x68, 0x20, 0x2f, 0x2e, 0x6e, 0x6f, 0x74, 0x64, 0x65, 0x66, 0x20, 0x70, 0x75, 0x74, 0x7d, 0x20, 0x66, 0x6f, 0x72, 0x0a, 0x64, 0x75, 0x70, 0x20, 0x36, 0x35, 0x20, 0x2f, 0x41, 0x20, 0x70, 0x75, 0x74, 0x0a, 0x64, 0x75, 0x70, 0x20, 0x36, 0x36, 0x20, 0x2f, 0x42, 0x20, 0x70, 0x75, 0x74, 0x0a, 0x64, 0x75, 0x70, 0x20, 0x36, 0x37, 0x20, 0x2f, 0x43, 0x20, 0x70, 0x75, 0x74, 0x0a, 0x64, 0x75, 0x70, 0x20, 0x36, 0x38, 0x20, 0x2f, 0x44, 0x20, 0x70, 0x75, 0x74, 0x0a, 0x64, 0x75, 0x70, 0x20, 0x31, 0x39, 0x33, 0x20, 0x2f, 0x41, 0x61, 0x63, 0x75, 0x74, 0x65, 0x20, 0x70, 0x75, 0x74, 0x0a, 0x64, 0x75, 0x70, 0x20, 0x31, 0x39, 0x34, 0x20, 0x2f, 0x61, 0x63, 0x75, 0x74, 0x65, 0x20, 0x70, 0x75, 0x74, 0x0a, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x50, 0x61, 0x69, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x20, 0x30, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x20, 0x31, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x42, 0x42, 0x6f, 0x78, 0x20, 0x7b, 0x30, 0x20, 0x30, 0x20, 0x31, 0x30, 0x30, 0x30, 0x20, 0x31, 0x30, 0x30, 0x30, 0x7d, 0x20, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x64, 0x69, 0x63, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x65, 0x65, 0x78, 0x65, 0x63, 0x0a, 0xbe, 0x77, 0xeb, 0xcb, 0x37, 0xf8, 0xbf, 0x92, 0x8f, 0x7e, 0xc3, 0x33, 0x57, 0x7b, 0x74, 0xb0, 0xdd, 0xd4, 0x81, 0xda, 0x47, 0x92, 0x94, 0xae, 0x29, 0x54, 0x75, 0xfe, 0x04, 0xc3, 0xf4, 0xc2, 0x73, 0x76, 0xeb, 0x6e, 0x25, 0xbc, 0xe3, 0x4e, 0x14, 0xca, 0xb0, 0xc6, 0x40, 0x8a, 0x7e, 0xd7, 0x9b, 0x7d, 0x61, 0x3d, 0x1e, 0xe3, 0xf4, 0xfc, 0x7d, 0xa5, 0xd9, 0x52, 0x83, 0x3a, 0xe2, 0xd5, 0x6c, 0x88, 0xd0, 0xf6, 0xe5, 0x90, 0x76, 0x79, 0x6e, 0x2c, 0x49, 0x2d, 0x62, 0xa3, 0x35, 0x18, 0x36, 0x1e, 0x20, 0x9e, 0x8a, 0x3d, 0x49, 0xe7, 0x0b, 0x84, 0xc6, 0x5d, 0xbb, 0x6b, 0xf1, 0xf9, 0x15, 0xbf, 0x48, 0xf6, 0xcc, 0x2d, 0xbe, 0xc9, 0x38, 0x04, 0xa9, 0x3b, 0x2c, 0x22, 0xaa, 0x60, 0xc8, 0x56, 0x48, 0xe0, 0x38, 0xa9, 0xa5, 0xb3, 0xae, 0x94, 0xaa, 0x21, 0xdd, 0xdc, 0x32, 0x4a, 0x8f, 0x95, 0x7a, 0xde, 0x87, 0x37, 0x9c, 0x1d, 0x8f, 0x26, 0x2a, 0x8e, 0xa0, 0x48, 0x4f, 0x16, 0x8c, 0x61, 0x67, 0xb6, 0x3b, 0xe5, 0xf5, 0x35, 0x8d, 0xab, 0x9f, 0x3e, 0x9a, 0xe6, 0x86, 0xd1, 0xce, 0xd1, 0x6f, 0x95, 0x5f, 0xc8, 0xb9, 0x0e, 0x16, 0x3c, 0xcb, 0x6b, 0xfb, 0x17, 0x46, 0x81, 0x41, 0x8e, 0x63, 0x89, 0x4d, 0x3d, 0xdd, 0x38, 0x51, 0x0c, 0x6a, 0x23, 0x44, 0xee, 0xe8, 0x4d, 0x2d, 0xc5, 0xeb, 0xb4, 0xe3, 0x1e, 0x60, 0x1b, 0xe2, 0x24, 0x6a, 0xaa, 0x60, 0xad, 0x04, 0x65, 0x85, 0x8a, 0xcf, 0x6b, 0x36, 0x11, 0xf7, 0xbd, 0x53, 0xab, 0xbb, 0xb1, 0xb0, 0x46, 0x35, 0xd4, 0x3b, 0x68, 0x2f, 0xef, 0x72, 0x5b, 0xc0, 0x86, 0x69, 0xeb, 0x5c, 0x53, 0x0c, 0xa9, 0x5b, 0xcd, 0x7d, 0x6a, 0x74, 0x7c, 0x3b, 0x3f, 0xd3, 0xef, 0x11, 0x88, 0xf0, 0xfb, 0x70, 0x42, 0xb8, 0x0b, 0x0e, 0x77, 0x2f, 0x33, 0x21, 0x2d, 0xf5, 0xd1, 0xaf, 0xb5, 0x2b, 0x82, 0x76, 0x3d, 0x3c, 0x81, 0x45, 0xf3, 0x6f, 0xe2, 0x87, 0xdf, 0x76, 0xe5, 0x7e, 0x0a, 0x60, 0xe1, 0xed, 0x9e, 0xf6, 0x8f, 0x3e, 0x2d, 0x9a, 0x74, 0x75, 0x1e, 0x27, 0x8e, 0xad, 0x60, 0x04, 0x78, 0x21, 0xd5, 0xe3, 0x84, 0xbf, 0xd0, 0xa3, 0x60, 0x6d, 0x42, 0xef, 0xb6, 0x21, 0xcd, 0x07, 0x34, 0x1e, 0xab, 0x62, 0x2a, 0x75, 0x5e, 0x26, 0xe9, 0x26, 0xf0, 0x3e, 0x2a, 0xf8, 0x32, 0x87, 0x91, 0xa2, 0xf0, 0xd0, 0x39, 0xd0, 0x84, 0xa2, 0xb5, 0x90, 0x19, 0xcb, 0x76, 0x41, 0x7a, 0x23, 0x7c, 0x9d, 0x19, 0xea, 0x35, 0x27, 0x36, 0xf2, 0xbe, 0x8a, 0x9e, 0x8e, 0x3a, 0xfa, 0xe6, 0x21, 0x20, 0xcd, 0x2f, 0x1f, 0xe6, 0x0c, 0x48, 0x6a, 0xd7, 0x61, 0x38, 0x02, 0xc1, 0xf9, 0xfa, 0xe1, 0x6b, 0xc5, 0x28, 0x34, 0xa5, 0xf7, 0xcb, 0x05, 0x10, 0xa3, 0x13, 0xd5, 0x75, 0xc5, 0xdf, 0x9e, 0x4a, 0xde, 0xa8, 0x5d, 0x9f, 0x8c, 0x7c, 0x4e, 0xd1, 0xea, 0x15, 0x35, 0x8f, 0x98, 0x18, 0xf5, 0x87, 0x7c, 0x39, 0xcd, 0xcd, 0x9f, 0x0a, 0x33, 0xfe, 0x83, 0xc9, 0x73, 0xe6, 0x52, 0xd3, 0xda, 0x97, 0x2c, 0xe1, 0x1d, 0xc1, 0x56, 0x7a, 0x3b, 0x37, 0x6c, 0x2a, 0xcf, 0x5b, 0x6e, 0xf2, 0xe1, 0x61, 0x49, 0x11, 0x0f, 0x00, 0x2e, 0x35, 0xbe, 0xa4, 0x35, 0xcf, 0x43, 0x10, 0xf4, 0x98, 0xfa, 0xcc, 0x56, 0x52, 0xb1, 0x4b, 0xe9, 0x8a, 0xfd, 0x7f, 0x99, 0xe8, 0xa0, 0x7c, 0x97, 0x94, 0x95, 0xf7, 0xa5, 0xeb, 0x92, 0x8e, 0xc6, 0xe8, 0x01, 0x66, 0x96, 0x68, 0x4d, 0x2e, 0x61, 0x42, 0xd0, 0x2d, 0x32, 0x44, 0x91, 0xea, 0x57, 0x6f, 0x6c, 0xc0, 0x4f, 0x82, 0x56, 0x9c, 0x0c, 0x04, 0xe7, 0xad, 0xed, 0x53, 0x76, 0xdb, 0x5e, 0x5c, 0x80, 0x57, 0x00, 0x08, 0xab, 0x0d, 0x74, 0xca, 0x44, 0xd8, 0x0b, 0xd9, 0x96, 0x57, 0xed, 0x7f, 0x84, 0xf0, 0xdb, 0xd5, 0x13, 0xc8, 0xe7, 0xbb, 0x97, 0xfa, 0x53, 0x5a, 0x18, 0x64, 0xfa, 0x9c, 0xb6, 0x94, 0xf7, 0x36, 0xb8, 0x3e, 0xfb, 0x09, 0xd1, 0xd5, 0x91, 0x2d, 0xdb, 0x3d, 0xa7, 0x2d, 0xe6, 0xc0, 0x83, 0xe4, 0xfd, 0xff, 0xa0, 0x20, 0x4a, 0x4e, 0xbf, 0xbb, 0x8b, 0x6f, 0xad, 0xab, 0xf4, 0xa5, 0xc6, 0xf4, 0x43, 0xd0, 0x5e, 0x89, 0x33, 0xa0, 0x56, 0x84, 0xb9, 0xff, 0x63, 0xbf, 0xd1, 0x40, 0x3c, 0xa1, 0x03, 0xd7, 0x3f, 0x4d, 0xfb, 0xf1, 0x00, 0x52, 0x3c, 0xda, 0xdb, 0x02, 0xdc, 0x1c, 0xc0, 0x35, 0x4c, 0xce, 0xce, 0x84, 0x8d, 0x94, 0x23, 0xc4, 0x06, 0x00, 0x64, 0x60, 0x6c, 0xb6, 0xcb, 0x71, 0x0f, 0xd0, 0xd3, 0xa4, 0x06, 0xba, 0x3a, 0x70, 0x5e, 0x8b, 0x12, 0x7e, 0x81, 0x94, 0x58, 0x6e, 0xbb, 0x08, 0x41, 0x92, 0x39, 0xde, 0xae, 0x86, 0x1f, 0xdd, 0xa4, 0xd0, 0xad, 0xce, 0x44, 0xa0, 0x67, 0x0e, 0x10, 0x0a, 0x4b, 0x1f, 0x2c, 0x00, 0xc5, 0x37, 0xa7, 0x9f, 0xf7, 0xda, 0x8d, 0x9d, 0xe0, 0x13, 0xcb, 0xbf, 0x53, 0x75, 0xb1, 0xf6, 0x74, 0x59, 0x41, 0x87, 0x9b, 0xe4, 0xab, 0xb5, 0xfd, 0x05, 0x44, 0x0f, 0x61, 0x86, 0x29, 0x5a, 0x30, 0x03, 0x80, 0x68, 0xa1, 0x34, 0x39, 0x65, 0x2d, 0x8c, 0xdd, 0x4b, 0x57, 0x8a, 0x94, 0xd8, 0x36, 0xd1, 0xdd, 0xfc, 0x73, 0xe4, 0xef, 0x45, 0xd5, 0xa0, 0x42, 0x35, 0x01, 0x38, 0xfa, 0x89, 0xd8, 0x92, 0xa2, 0xd2, 0xfe, 0x3b, 0x4d, 0x7e, 0x87, 0x9a, 0x18, 0x05, 0xe1, 0x46, 0x4b, 0xa1, 0x58, 0x51, 0x5f, 0x0d, 0x38, 0xd8, 0x63, 0xb2, 0x0c, 0x3f, 0x34, 0xcc, 0x87, 0xa5, 0x7d, 0xd9, 0xce, 0x40, 0xfd, 0x05, 0x1d, 0x7d, 0x45, 0x89, 0x11, 0xa7, 0x8f, 0xab, 0x68, 0x81, 0xc5, 0x84, 0x04, 0xfc, 0x68, 0x93, 0x2b, 0x49, 0x1e, 0xa6, 0xf3, 0x99, 0x23, 0x98, 0x0a, 0x1f, 0x84, 0x3e, 0x13, 0x70, 0x62, 0xdc, 0xc8, 0xd7, 0x27, 0x39, 0xa2, 0xdf, 0x8f, 0xfe, 0x52, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x74, 0x6f, 0x6d, 0x61, 0x72, 0x6b, 0x0a, } // Byte offsets inside T1_FONT_A: the cleartext runs [0, 379), the // eexec ciphertext [379, 1190), and the trailer the rest. @(private = "file") T1A_CLEAR_LEN :: 379 @(private = "file") T1A_BIN_LEN :: 811 @(private = "file", rodata) T1_FONT_B := [393]u8{ 0x25, 0x21, 0x46, 0x6f, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x31, 0x2d, 0x31, 0x2e, 0x30, 0x3a, 0x20, 0x4f, 0x64, 0x69, 0x6e, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x20, 0x30, 0x30, 0x31, 0x2e, 0x30, 0x30, 0x31, 0x0a, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x20, 0x2f, 0x4f, 0x64, 0x69, 0x6e, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x20, 0x5b, 0x30, 0x2e, 0x30, 0x30, 0x32, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x2e, 0x30, 0x30, 0x32, 0x20, 0x30, 0x20, 0x30, 0x5d, 0x20, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x46, 0x6f, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x20, 0x31, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x64, 0x69, 0x63, 0x74, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x2f, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x35, 0x20, 0x64, 0x69, 0x63, 0x74, 0x20, 0x64, 0x75, 0x70, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x0a, 0x2f, 0x52, 0x44, 0x20, 0x7b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x65, 0x78, 0x63, 0x68, 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x6f, 0x70, 0x7d, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x6c, 0x65, 0x6e, 0x49, 0x56, 0x20, 0x34, 0x20, 0x64, 0x65, 0x66, 0x0a, 0x2f, 0x53, 0x75, 0x62, 0x72, 0x73, 0x20, 0x30, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x4e, 0x44, 0x0a, 0x32, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x2f, 0x43, 0x68, 0x61, 0x72, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x32, 0x20, 0x64, 0x69, 0x63, 0x74, 0x20, 0x64, 0x75, 0x70, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x0a, 0x2f, 0x2e, 0x6e, 0x6f, 0x74, 0x64, 0x65, 0x66, 0x20, 0x39, 0x20, 0x52, 0x44, 0x20, 0x10, 0xbf, 0x31, 0x70, 0x79, 0xc8, 0x2f, 0x06, 0x80, 0x20, 0x4e, 0x44, 0x0a, 0x2f, 0x41, 0x20, 0x31, 0x39, 0x20, 0x52, 0x44, 0x20, 0x10, 0xbf, 0x31, 0x70, 0x67, 0x54, 0x36, 0x79, 0x9a, 0x79, 0xaa, 0x38, 0xca, 0xcd, 0xa1, 0x72, 0x86, 0xcd, 0xc9, 0x20, 0x4e, 0x44, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, } // 0 500 hsbw endchar @(private = "file", rodata) T1T_NOTDEF := [?]u8{ 0x8b, 0xf8, 0x88, 0x0d, 0x0e, } // return @(private = "file", rodata) T1T_RETURN := [?]u8{ 0x0b, } // 0 400 hsbw 0 0 rmoveto 100 hlineto closepath endchar @(private = "file", rodata) T1T_LINE := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0xef, 0x06, 0x09, 0x0e, } // 0 400 hsbw 0 100 400 128 130 seac @(private = "file", rodata) T1T_SEAC_UNDEF := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0xef, 0xf8, 0x24, 0xf7, 0x14, 0xf7, 0x16, 0x0c, 0x06, } // 0 400 hsbw 0 100 400 -5 999 seac @(private = "file", rodata) T1T_SEAC_OOR := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0xef, 0xf8, 0x24, 0x86, 0xfa, 0x7b, 0x0c, 0x06, } // 0 400 hsbw 0 0 rmoveto 1 callsubr closepath endchar @(private = "file", rodata) T1T_VIA_SUBR := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0x8c, 0x0a, 0x09, 0x0e, } // 100 hlineto 100 vlineto return @(private = "file", rodata) T1T_SUBR_BOX := [?]u8{ 0xef, 0x06, 0xef, 0x07, 0x0b, } // 0 400 hsbw 0 0 rmoveto 0 callsubr closepath endchar @(private = "file", rodata) T1T_DEEP := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0x8b, 0x0a, 0x09, 0x0e, } // 0 callsubr return @(private = "file", rodata) T1T_SELFCALL := [?]u8{ 0x8b, 0x0a, 0x0b, } // 0 400 hsbw rlineto rrcurveto 0 0 rmoveto 100 hlineto closepath endchar @(private = "file", rodata) T1T_STARVED := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x05, 0x08, 0x8b, 0x8b, 0x15, 0xef, 0x06, 0x09, 0x0e, } // 0 400 hsbw 0 0 rmoveto raw:15 100 hlineto closepath endchar @(private = "file", rodata) T1T_UNKNOWN_OP := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0x0f, 0xef, 0x06, 0x09, 0x0e, } // 0 400 hsbw 0 0 rmoveto 0 1 callothersubr 10 10 rmoveto 10 10 rmoveto 10 10 rmoveto 0 0 0 3 0 callothersubr pop pop setcurrentpoint closepath endchar @(private = "file", rodata) T1T_HALF_FLEX := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0x8b, 0x8c, 0x0c, 0x10, 0x95, 0x95, 0x15, 0x95, 0x95, 0x15, 0x95, 0x95, 0x15, 0x8b, 0x8b, 0x8b, 0x8e, 0x8b, 0x0c, 0x10, 0x0c, 0x11, 0x0c, 0x11, 0x0c, 0x21, 0x09, 0x0e, } // 0 400 hsbw 0 0 rmoveto raw:255 raw:0 raw:0 @(private = "file", rodata) T1T_TRUNC_NUM := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0xff, 0x00, 0x00, } // 0 400 hsbw 0 0 rmoveto 99 callsubr -1 callsubr 100 hlineto closepath endchar @(private = "file", rodata) T1T_BAD_SUBR := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0xee, 0x0a, 0x8a, 0x0a, 0xef, 0x06, 0x09, 0x0e, } // 0 400 hsbw 0 0 rmoveto 100 20 2 14 callothersubr pop pop rlineto closepath endchar @(private = "file", rodata) T1T_OTHERSUBR := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0xef, 0x9f, 0x8d, 0x99, 0x0c, 0x10, 0x0c, 0x11, 0x0c, 0x11, 0x05, 0x09, 0x0e, } // 0 400 hsbw 0 0 rmoveto 100 pop rlineto closepath endchar @(private = "file", rodata) T1T_BARE_POP := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0xef, 0x0c, 0x11, 0x05, 0x09, 0x0e, } // 0 400 hsbw 0 0 rmoveto 100 0 div 20 rlineto closepath endchar @(private = "file", rodata) T1T_DIV_ZERO := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0x8b, 0x15, 0xef, 0x8b, 0x0c, 0x0c, 0x9f, 0x05, 0x09, 0x0e, } // 10 480 hsbw 0 0 rmoveto 40 0 rlineto 0 60 rlineto closepath endchar @(private = "file", rodata) T1T_FONTB_A := [?]u8{ 0x95, 0xf8, 0x74, 0x0d, 0x8b, 0x8b, 0x15, 0xb3, 0x8b, 0x05, 0x8b, 0xc7, 0x05, 0x09, 0x0e, } // endchar @(private = "file", rodata) T1T_ENDCHAR := [?]u8{ 0x0e, } // 0 700 hsbw 0 0 rmoveto 107 -107 rlineto 108 -108 rlineto 1131 -1131 rlineto 32768 -32768 rlineto closepath endchar @(private = "file", rodata) T1T_NUMBERS := [?]u8{ 0x8b, 0xf9, 0x50, 0x0d, 0x8b, 0x8b, 0x15, 0xf6, 0x20, 0x05, 0xf7, 0x00, 0xfb, 0x00, 0x05, 0xfa, 0xff, 0xfe, 0xff, 0x05, 0xff, 0x00, 0x00, 0x80, 0x00, 0xff, 0xff, 0xff, 0x80, 0x00, 0x05, 0x09, 0x0e, } // 0 600 hsbw 0 0 rmoveto 100 hlineto 0 200 rmoveto 100 hlineto endchar @(private = "file", rodata) T1T_TWO_SUBS := [?]u8{ 0x8b, 0xf8, 0xec, 0x0d, 0x8b, 0x8b, 0x15, 0xef, 0x06, 0x8b, 0xf7, 0x5c, 0x15, 0xef, 0x06, 0x0e, } // 0 400 hsbw 0 100 400 999 -5 seac @(private = "file", rodata) T1T_SEAC_HIGH := [?]u8{ 0x8b, 0xf8, 0x24, 0x0d, 0x8b, 0xef, 0xf8, 0x24, 0xfa, 0x7b, 0x86, 0x0c, 0x06, } // --------------------------------------------------------------------------- // Container and dictionary parsing // --------------------------------------------------------------------------- @(test) test_t1_parse_raw :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } testing.expectf(t, t1_glyph_count(font) == 8, "glyph count %d, want 8", t1_glyph_count(font)) testing.expectf(t, font.name == "OdinTest", "/FontName %q", font.name) // Font A declares /lenIV 5 -- a value the format allows and no font uses // by accident -- and puts it in the first line of the encrypted private // dictionary, right against the four bytes of random eexec padding. A // reader that forgets to discard the padding finds no /lenIV, falls back to // the default 4, and mis-decodes every charstring in the font. testing.expectf(t, font.len_iv == 5, "/lenIV %d, want 5", font.len_iv) testing.expect(t, !font.truncated, "an intact font reported truncation") m := t1_font_matrix(font) testing.expectf(t, m == Matrix{0.001, 0, 0, 0.001, 0, 0}, "/FontMatrix %v", m) // /CharStrings order is the order the dictionary was written in. want := [?]string{".notdef", "A", "B", "C", "D", "Aacute", "acute", "E"} for name, i in want { gid, ok := t1_gid_for_name(font, name) testing.expectf(t, ok && int(gid) == i, "/%s resolved to %d,%v; want %d", name, gid, ok, i) } if _, ok := t1_gid_for_name(font, "Zcaron"); ok { testing.expect(t, false, "a glyph the font does not have resolved anyway") } if _, ok := t1_gid_for_name(font, ""); ok { testing.expect(t, false, "the empty name resolved to a glyph") } // Five Subrs, and the two the flex and hint tests lean on are non-empty. testing.expectf(t, len(font.subrs) == 5, "Subrs count %d, want 5", len(font.subrs)) if len(font.subrs) == 5 { testing.expect(t, len(font.subrs[0]) > 0, "Subrs[0] decoded empty") testing.expect(t, len(font.subrs[4]) > 0, "Subrs[4] decoded empty") } } @(test) test_t1_encoding_array_form :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } enc := t1_builtin_encoding(font) if !testing.expect(t, enc != nil, "font A declares an /Encoding but none came back") { return } testing.expectf(t, enc[65] == "A", "code 65 is %q", enc[65]) testing.expectf(t, enc[66] == "B", "code 66 is %q", enc[66]) testing.expectf(t, enc[68] == "D", "code 68 is %q", enc[68]) testing.expectf(t, enc[193] == "Aacute", "code 193 is %q", enc[193]) testing.expectf(t, enc[194] == "acute", "code 194 is %q", enc[194]) // The `0 1 255 {1 index exch /.notdef put} for` loop is not executed, and // its `/.notdef put` must not be mistaken for a `dup / put`. testing.expectf(t, enc[32] == "", "code 32 is %q, want undefined", enc[32]) testing.expectf(t, enc[0] == "", "code 0 is %q, want undefined", enc[0]) } // Font B exercises three things at once: a font shipped with the eexec layer // already stripped, the /Encoding StandardEncoding shorthand, and a // /FontMatrix that is not the default. @(test) test_t1_no_eexec_and_standard_encoding :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_B[:], "font B") if font == nil { return } testing.expectf(t, t1_glyph_count(font) == 2, "glyph count %d, want 2", t1_glyph_count(font)) testing.expectf(t, font.name == "OdinPlain", "/FontName %q", font.name) m := t1_font_matrix(font) testing.expectf(t, m == Matrix{0.002, 0, 0, 0.002, 0, 0}, "/FontMatrix %v", m) enc := t1_builtin_encoding(font) if !testing.expect(t, enc != nil, "/Encoding StandardEncoding produced no encoding") { return } testing.expectf(t, enc[65] == "A", "code 65 is %q", enc[65]) testing.expectf(t, enc[194] == "acute", "code 194 is %q", enc[194]) testing.expectf(t, enc[32] == "space", "code 32 is %q", enc[32]) testing.expectf(t, enc[127] == "", "code 127 is %q, want undefined", enc[127]) p := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &p, {.Move, .Line, .Line, .Close}, {{10, 0}, {50, 0}, {50, 60}}, "font B /A") testing.expectf(t, t1_glyph_advance(font, 1) == 480, "advance %v", t1_glyph_advance(font, 1)) } // A font with no /Encoding at all must say so rather than claiming an empty // one: the caller has to know to fall back to StandardEncoding. @(test) test_t1_no_encoding_reports_none :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ names = {".notdef", "A"}, chars = {T1T_NOTDEF[:], T1T_LINE[:]}, no_encoding = true, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "no /Encoding") if font == nil { return } testing.expect(t, t1_builtin_encoding(font) == nil, "a font with no /Encoding claimed to have one") // ... and still has its glyphs. p := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &p, {.Move, .Line, .Close}, {{0, 0}, {100, 0}}, "/A") } @(test) test_t1_pfb_segments :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) raw := t1f_font(t, &f, T1_FONT_A[:], "font A raw") pfb := t1f_font(t, &f, t1t_pfb(T1_FONT_A[:], f.alloc), "font A as PFB") if raw == nil || pfb == nil { return } t1f_expect_same(t, raw, pfb, &f, "PFB") // A segment header that claims more bytes than the file holds must be // clamped to what is there. The eexec cipher is a stream, so the glyphs // before the join still decode and the font comes out whole. lying := t1t_pfb(T1_FONT_A[:], f.alloc) at := 6 + T1A_CLEAR_LEN + 2 lying[at] = 0xFF lying[at + 1] = 0xFF lying[at + 2] = 0xFF lying[at + 3] = 0x7F clamped := t1f_font(t, &f, lying, "font A as PFB with a lying segment length") if clamped != nil { t1f_expect_same(t, raw, clamped, &f, "clamped PFB") } } @(test) test_t1_hex_eexec :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) raw := t1f_font(t, &f, T1_FONT_A[:], "font A raw") hex := t1f_font(t, &f, t1t_hex_form(T1_FONT_A[:], f.alloc), "font A as PFA") if raw == nil || hex == nil { return } t1f_expect_same(t, raw, hex, &f, "hex eexec") } // --------------------------------------------------------------------------- // The charstring interpreter // --------------------------------------------------------------------------- @(test) test_t1_glyph_outline :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } a := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &a, {.Move, .Line, .Line, .Line, .Close}, {{50, 0}, {150, 0}, {150, 200}, {50, 200}}, "/A") // /E covers rrcurveto and the two mixed curve operators, and a div. e := t1f_glyph(t, &f, font, "E") t1f_expect_path(t, &e, {.Move, .Curve, .Curve, .Curve, .Line, .Close}, { {0, 0}, {10, 20}, {40, 60}, {90, 120}, // rrcurveto {100, 120}, {120, 150}, {120, 190}, // hvcurveto {120, 200}, {140, 230}, {180, 230}, // vhcurveto {230, 230}, // 200 4 div 0 rlineto }, "/E") } // hsbw does two jobs, and the second one is easy to drop: the sidebearing it // declares translates the whole outline. /A and /D are the same drawing // commands under sidebearings of 50 and 0. @(test) test_t1_hsbw_sidebearing_translates :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } a := t1f_glyph(t, &f, font, "A") d := t1f_glyph(t, &f, font, "D") t1f_expect_path(t, &d, {.Move, .Line, .Line, .Line, .Close}, {{0, 0}, {100, 0}, {100, 200}, {0, 200}}, "/D") if !testing.expectf(t, len(a.points) == len(d.points), "/A has %d points, /D has %d", len(a.points), len(d.points)) { return } for pt, i in d.points { shifted := Point{pt.x + 50, pt.y} testing.expectf(t, a.points[i] == shifted, "/A point %d is %v; want /D's %v moved by the sidebearing to %v", i, a.points[i], pt, shifted) } // Both widths come from the same hsbw, and the sidebearing is not it. testing.expectf(t, t1_glyph_advance(font, 1) == 600, "/A advance %v", t1_glyph_advance(font, 1)) testing.expectf(t, t1_glyph_advance(font, 4) == 600, "/D advance %v", t1_glyph_advance(font, 4)) } // The seven rmovetos a FLEX collects are control points, not seven subpaths. // A reader that draws them as movetos loses the curve. @(test) test_t1_flex_becomes_two_curves :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } b := t1f_glyph(t, &f, font, "B") t1f_expect_path(t, &b, {.Move, .Curve, .Curve, .Close}, { {0, 0}, {20, 30}, {40, 60}, {60, 60}, {80, 60}, {100, 30}, {120, 0}, }, "/B") // The reference point, the first of the seven, is not on the outline. for pt in b.points { testing.expectf(t, pt != Point{50, 50}, "the FLEX reference point (50,50) reached the outline") } } // OtherSubr 3 hands the subroutine number it was given back to the `pop` and // `callsubr` that follow. Answering 3 instead -- what the non-interpreting // fallback pushes -- runs the do-nothing Subrs[3] every font carries and // silently drops the replacement, which is why /C's outline is the assertion. @(test) test_t1_hint_replacement_calls_the_named_subr :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } c := t1f_glyph(t, &f, font, "C") t1f_expect_path(t, &c, {.Move, .Line, .Line, .Line, .Close}, {{0, 0}, {100, 0}, {100, 100}, {0, 100}}, "/C") } // seac draws two other glyphs, named by their StandardEncoding codes. The // accent's offset is the composite's own sidebearing plus adx, less the // accent's sidebearing asb -- adx measures from sidebearing point to // sidebearing point, not origin to origin. The three terms here are 50, 100 // and 20, so every plausible mis-reading of that formula (adx alone, adx-asb, // sb+adx) lands somewhere else. @(test) test_t1_seac :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } acc := t1f_glyph(t, &f, font, "acute") t1f_expect_path(t, &acc, {.Move, .Line, .Line, .Close}, {{20, 0}, {50, 0}, {35, 40}}, "/acute standing alone") // The composite's hsbw sidebearing is 50, asb is 20, adx is 100 and ady is // 400, so the accent moves by (50 + 100 - 20, 400) = (130, 400). comp := t1f_glyph(t, &f, font, "Aacute") t1f_expect_path(t, &comp, {.Move, .Line, .Line, .Line, .Close, .Move, .Line, .Line, .Close}, { {50, 0}, {150, 0}, {150, 200}, {50, 200}, {150, 400}, {180, 400}, {165, 440}, }, "/Aacute") // The composite's own hsbw still gives the width. gid, _ := t1_gid_for_name(font, "Aacute") testing.expectf(t, t1_glyph_advance(font, gid) == 600, "/Aacute advance %v, want the composite's own 600", t1_glyph_advance(font, gid)) } // A seac naming codes StandardEncoding leaves undefined, or codes outside it // altogether, must draw nothing rather than read past the end of the table. @(test) test_t1_seac_unknown_codes :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ names = {".notdef", "A", "B", "C"}, chars = {T1T_NOTDEF[:], T1T_SEAC_UNDEF[:], T1T_SEAC_OOR[:], T1T_SEAC_HIGH[:]}, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "seac with unknown codes") if font == nil { return } for name in ([?]string{"A", "B", "C"}) { p := t1f_glyph(t, &f, font, name) testing.expectf(t, len(p.verbs) == 0, "/%s: a seac to nowhere drew %d elements", name, len(p.verbs)) } } @(test) test_t1_advances :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) font := t1f_font(t, &f, T1_FONT_A[:], "font A") if font == nil { return } want := [?]struct { name: string, w: f64, }{ {".notdef", 500}, {"A", 600}, {"B", 500}, {"C", 400}, {"D", 600}, {"Aacute", 600}, {"acute", 300}, {"E", 700}, } for c in want { gid, ok := t1_gid_for_name(font, c.name) if !testing.expectf(t, ok, "/%s missing", c.name) { continue } got := t1_glyph_advance(font, gid) testing.expectf(t, got == c.w, "/%s advance %v, want %v", c.name, got, c.w) // Asked twice, answered from the memo the second time. testing.expectf(t, t1_glyph_advance(font, gid) == c.w, "/%s advance changed when asked again", c.name) } // An index past the end is 0, not a read out of bounds. testing.expect(t, t1_glyph_advance(font, 900) == 0, "advance for a missing gid was not 0") p := path_make(f.alloc) testing.expect(t, t1_glyph_path(font, 900, &p) == .No_Glyph, "drawing a missing gid did not report .No_Glyph") testing.expect(t, t1_glyph_path(font, 0, nil) == .Invalid_Argument, "drawing into a nil path did not report .Invalid_Argument") } // --------------------------------------------------------------------------- // Damage // --------------------------------------------------------------------------- // A truncated eexec block yields the glyphs that were decoded before the cut. @(test) test_t1_truncated_eexec :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) full := t1f_font(t, &f, T1_FONT_A[:], "font A") if full == nil { return } saw_partial := false for cut in ([?]int{40, 120, 200, 280, 360, 440}) { end := T1A_CLEAR_LEN + T1A_BIN_LEN - cut font, err := t1_parse(T1_FONT_A[:end], f.alloc) if err != .None || font == nil { continue } n := t1_glyph_count(font) testing.expectf(t, n <= t1_glyph_count(full), "cut %d: %d glyphs, more than the intact font's %d", cut, n, t1_glyph_count(full)) if n >= 1 && n < t1_glyph_count(full) { saw_partial = true testing.expectf(t, font.truncated, "cut %d lost glyphs without recording truncation", cut) } // Every glyph but the last one it kept -- which may itself have been // cut in half -- must decode exactly as it does in the intact font. for gid in 0 ..< max(n - 1, 0) { name := font.names[gid] ref, ok := t1_gid_for_name(full, name) if !ok { continue } got := path_make(f.alloc) reference := path_make(f.alloc) _ = t1_glyph_path(font, u16(gid), &got) _ = t1_glyph_path(full, ref, &reference) t1f_expect_path(t, &got, reference.verbs[:], reference.points[:], name) } } testing.expect(t, saw_partial, "no truncation lost a glyph, so this test proves nothing about truncation") } // Truncating anywhere at all must give an error or a usable font, never a // crash, a hang, or an out-of-bounds read while drawing what survived. @(test) test_t1_truncation_is_never_fatal :: proc(t: ^testing.T) { scratch: virtual.Arena if virtual.arena_init_growing(&scratch) != nil { return } defer virtual.arena_destroy(&scratch) alloc := virtual.arena_allocator(&scratch) for end := 1; end <= len(T1_FONT_A); end += 3 { font, err := t1_parse(T1_FONT_A[:end], alloc) if err == .None && font != nil { for gid in 0 ..< t1_glyph_count(font) { p := path_make(alloc) _ = t1_glyph_path(font, u16(gid), &p) _ = t1_glyph_advance(font, u16(gid)) } } virtual.arena_free_all(&scratch) } testing.expect(t, true, "reached the end without crashing or hanging") } // A byte flipped anywhere must be survivable too: this is what a subsetter's // off-by-one length does to a /FontFile stream. @(test) test_t1_bit_flips_are_never_fatal :: proc(t: ^testing.T) { scratch: virtual.Arena if virtual.arena_init_growing(&scratch) != nil { return } defer virtual.arena_destroy(&scratch) alloc := virtual.arena_allocator(&scratch) buf := make([]byte, len(T1_FONT_A), alloc) for at := 0; at < len(T1_FONT_A); at += 7 { copy(buf, T1_FONT_A[:]) buf[at] ~= 0xFF font, err := t1_parse(buf, alloc) if err == .None && font != nil { for gid in 0 ..< t1_glyph_count(font) { p := path_make(alloc) _ = t1_glyph_path(font, u16(gid), &p) _ = t1_glyph_advance(font, u16(gid)) } } } testing.expect(t, true, "reached the end without crashing or hanging") } @(test) test_t1_rejects_junk :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) cases := [?][]byte{ {}, transmute([]byte)string("not a font at all"), transmute([]byte)string("%!FontType1-1.0\ncurrentfile eexec\n"), // A PFB header and nothing else. {0x80, 0x01, 0x10, 0x00, 0x00, 0x00}, {0x80, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x41, 0x42}, // /CharStrings with no entries. transmute([]byte)string("/CharStrings 4 dict dup begin\nend\n"), } for c, i in cases { font, err := t1_parse(c, f.alloc) testing.expectf(t, font == nil && err == .Bad_Type1, "case %d gave %v, %v; want nil and .Bad_Type1", i, font, err) } } // A /Subrs or /CharStrings count that disagrees with the entries that follow // is ignored in favour of the entries. @(test) test_t1_counts_that_lie :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) // /A draws through Subrs[1], so a Subrs array cut to the declared count // would lose the outline rather than merely a hint. for declared in ([?][2]int{{99, 40}, {1, 1}}) { spec := T1t_Spec{ subrs = {T1T_RETURN[:], T1T_SUBR_BOX[:]}, names = {".notdef", "A"}, chars = {T1T_NOTDEF[:], T1T_VIA_SUBR[:]}, declared_subrs = declared[0], declared_chars = declared[1], } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "lying counts") if font == nil { continue } testing.expectf(t, t1_glyph_count(font) == 2, "declared %d glyphs, wrote 2, got %d", declared[1], t1_glyph_count(font)) p := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &p, {.Move, .Line, .Line, .Close}, {{0, 0}, {100, 0}, {100, 100}}, "/A through Subrs[1]") } } // A charstring shorter than /lenIV has no plaintext left once the padding is // dropped. It must come back as an empty glyph, not a slice out of range. @(test) test_t1_charstring_shorter_than_leniv :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ names = {".notdef", "A", "B"}, chars = {T1T_NOTDEF[:], nil, T1T_ENDCHAR[:]}, // Two padding bytes written where /lenIV says four, so /A (nothing but // padding) and /B (padding plus one byte) are both too short. pad = 2, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "short charstrings") if font == nil { return } testing.expectf(t, t1_glyph_count(font) == 3, "glyph count %d", t1_glyph_count(font)) for name in ([?]string{"A", "B"}) { p := t1f_glyph(t, &f, font, name) testing.expectf(t, len(p.verbs) == 0, "/%s drew %d elements", name, len(p.verbs)) gid, _ := t1_gid_for_name(font, name) testing.expectf(t, t1_glyph_advance(font, gid) == 0, "/%s advance was not 0", name) } } // A negative /lenIV is how the tools that strip the charstring cipher record // that they did; the charstrings are then plain text. @(test) test_t1_negative_leniv_is_plaintext :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ names = {".notdef", "A"}, chars = {T1T_NOTDEF[:], T1T_LINE[:]}, len_iv = -1, no_encipher = true, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "/lenIV -1") if font == nil { return } testing.expectf(t, font.len_iv == -1, "/lenIV read as %d", font.len_iv) p := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &p, {.Move, .Line, .Close}, {{0, 0}, {100, 0}}, "/A with plaintext charstrings") } // --------------------------------------------------------------------------- // Interpreter bounds // --------------------------------------------------------------------------- @(test) test_t1_interpreter_bounds :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) // 200 numbers pushed with no operator to consume them: well past the // operand stack, and the run has to stop rather than write off the end. flood := make([dynamic]byte, f.alloc) append(&flood, ..T1T_LINE[:4]) // 0 400 hsbw append(&flood, 0x8b, 0x8b, 0x15) // 0 0 rmoveto for _ in 0 ..< 200 { append(&flood, 0x8b) } append(&flood, 0xef, 0x06, 0x09, 0x0e) // 100 hlineto closepath endchar spec := T1t_Spec{ // Subrs[0] calls itself; only the depth cap ends it. subrs = {T1T_SELFCALL[:]}, names = {".notdef", "deep", "flood", "starved", "unknown", "halfflex", "truncnum"}, chars = { T1T_NOTDEF[:], T1T_DEEP[:], flood[:], T1T_STARVED[:], T1T_UNKNOWN_OP[:], T1T_HALF_FLEX[:], T1T_TRUNC_NUM[:], }, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "bounds") if font == nil { return } testing.expectf(t, t1_glyph_count(font) == 7, "glyph count %d", t1_glyph_count(font)) for gid in 0 ..< t1_glyph_count(font) { p := path_make(f.alloc) err := t1_glyph_path(font, u16(gid), &p) testing.expectf(t, err == .None, "/%s gave %v", font.names[gid], err) } // Stepping over one bad operator must not abandon the rest of the glyph: // both of these draw the line that follows the damage. for name in ([?]string{"starved", "unknown"}) { p := t1f_glyph(t, &f, font, name) t1f_expect_path(t, &p, {.Move, .Line, .Close}, {{0, 0}, {100, 0}}, name) } // A FLEX abandoned after three of its seven points joins its ends rather // than leaving a gap across the glyph. half := t1f_glyph(t, &f, font, "halfflex") t1f_expect_path(t, &half, {.Move, .Line, .Close}, {{0, 0}, {30, 30}}, "halfflex") } // Every branch of the Type 1 number encoding, at the boundaries between them: // one byte for -107..107, two for the two 108..1131 forms, and a 255 prefix // with a 32-bit two's complement integer behind it -- not the 16.16 fixed // point that the same prefix means in a Type 2 charstring. @(test) test_t1_number_encoding :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ names = {".notdef", "A"}, chars = {T1T_NOTDEF[:], T1T_NUMBERS[:]}, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "number encoding") if font == nil { return } p := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &p, {.Move, .Line, .Line, .Line, .Line, .Close}, {{0, 0}, {107, -107}, {215, -215}, {1346, -1346}, {34114, -34114}}, "/A") testing.expectf(t, t1_glyph_advance(font, 1) == 700, "advance %v", t1_glyph_advance(font, 1)) } // Glyph outlines are always closed, and fonts that leave the closepath out // before a moveto are common enough that closing at the moveto rather than // trusting them is the only way a stroked text render mode looks right. @(test) test_t1_moveto_closes_the_previous_subpath :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ names = {".notdef", "A"}, chars = {T1T_NOTDEF[:], T1T_TWO_SUBS[:]}, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "two subpaths, no closepath") if font == nil { return } p := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &p, {.Move, .Line, .Close, .Move, .Line, .Close}, {{0, 0}, {100, 0}, {100, 200}, {200, 200}}, "/A") } // A `callsubr` naming a subroutine that is not there must be stepped over. @(test) test_t1_callsubr_out_of_range :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ subrs = {T1T_RETURN[:]}, names = {".notdef", "A"}, chars = {T1T_NOTDEF[:], T1T_BAD_SUBR[:]}, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "callsubr out of range") if font == nil { return } p := t1f_glyph(t, &f, font, "A") t1f_expect_path(t, &p, {.Move, .Line, .Close}, {{0, 0}, {100, 0}}, "/A") } // The three ways `callothersubr` and `pop` are asked for something this reader // has no procedure for. @(test) test_t1_othersubr_edges :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) spec := T1t_Spec{ names = {".notdef", "unknown", "barepop", "divzero"}, chars = {T1T_NOTDEF[:], T1T_OTHERSUBR[:], T1T_BARE_POP[:], T1T_DIV_ZERO[:]}, } font := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "othersubr edges") if font == nil { return } // An OtherSubr this reader does not implement leaves its two arguments for // the two pops, exactly as the PostScript procedure would have. unknown := t1f_glyph(t, &f, font, "unknown") t1f_expect_path(t, &unknown, {.Move, .Line, .Close}, {{0, 0}, {100, 20}}, "unknown othersubr") // `pop` with nothing waiting pushes a zero rather than reading past the // end of the results. bare := t1f_glyph(t, &f, font, "barepop") t1f_expect_path(t, &bare, {.Move, .Line, .Close}, {{0, 0}, {100, 0}}, "bare pop") // div by zero yields zero: a NaN reaching the rasteriser turns a fill into // an unbounded scanline walk. div := t1f_glyph(t, &f, font, "divzero") t1f_expect_path(t, &div, {.Move, .Line, .Close}, {{0, 0}, {0, 20}}, "div by zero") } // --------------------------------------------------------------------------- // Test-side font builder // // The two golden fonts cover the container; these cover the damaged shapes // that are awkward to freeze as a literal. The builder writes a font with no // eexec layer, so the only cipher it needs is the charstring one -- and // test_t1_builder_agrees_with_the_golden_font ties its output to font B. // --------------------------------------------------------------------------- @(private = "file") T1t_Spec :: struct { subrs: [][]byte, names: []string, chars: [][]byte, // 0 means "write the real count". declared_subrs: int, declared_chars: int, // 0 means the format's default of 4. len_iv: int, // Padding bytes actually written; 0 means "as many as len_iv says". pad: int, no_encipher: bool, no_encoding: bool, } @(private = "file") t1t_int :: proc(out: ^[dynamic]byte, v: int) { if v < 0 { append(out, '-') t1t_int(out, -v) return } if v >= 10 { t1t_int(out, v / 10) } append(out, byte('0' + v % 10)) } @(private = "file") t1t_str :: proc(out: ^[dynamic]byte, s: string) { append(out, ..transmute([]byte)s) } // t1t_encipher is the Type 1 cipher run forwards (Type 1 spec, 7.3). @(private = "file") t1t_encipher :: proc(out: ^[dynamic]byte, plain: []byte, key: u16, pad: int) { r := u32(key) emit :: proc(out: ^[dynamic]byte, r: ^u32, p: byte) { c := byte(u32(p) ~ (r^ >> 8)) append(out, c) r^ = ((u32(c) + r^) * 52845 + 22719) & 0xFFFF } for _ in 0 ..< pad { emit(out, &r, 0) } for p in plain { emit(out, &r, p) } } @(private = "file") t1t_entry :: proc(out: ^[dynamic]byte, spec: ^T1t_Spec, code: []byte, allocator: mem.Allocator) { body := make([dynamic]byte, allocator) if spec.no_encipher { append(&body, ..code) } else { pad := spec.pad if pad == 0 { pad = spec.len_iv == 0 ? 4 : spec.len_iv } t1t_encipher(&body, code, T1_CHARSTRING_KEY, pad) } t1t_int(out, len(body)) t1t_str(out, " RD ") append(out, ..body[:]) t1t_str(out, " ND\n") } // t1t_assemble writes a complete Type 1 program with a plaintext Private dict. @(private = "file") t1t_assemble :: proc(spec: ^T1t_Spec, allocator: mem.Allocator) -> []byte { out := make([dynamic]byte, allocator) t1t_str(&out, "%!FontType1-1.0: OdinBuilt 001.001\n") t1t_str(&out, "/FontName /OdinBuilt def\n") t1t_str(&out, "/FontMatrix [0.001 0 0 0.001 0 0] readonly def\n") if !spec.no_encoding { t1t_str(&out, "/Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n") t1t_str(&out, "dup 65 /A put\nreadonly def\n") } t1t_str(&out, "/FontType 1 def\ncurrentdict end\n") t1t_str(&out, "/Private 8 dict dup begin\n") t1t_str(&out, "/RD {string currentfile exch readstring pop} executeonly def\n") t1t_str(&out, "/lenIV ") t1t_int(&out, spec.len_iv == 0 ? 4 : spec.len_iv) t1t_str(&out, " def\n") t1t_str(&out, "/Subrs ") t1t_int(&out, spec.declared_subrs != 0 ? spec.declared_subrs : len(spec.subrs)) t1t_str(&out, " array\n") for code, i in spec.subrs { t1t_str(&out, "dup ") t1t_int(&out, i) t1t_str(&out, " ") t1t_entry(&out, spec, code, allocator) } t1t_str(&out, "ND\n") t1t_str(&out, "2 index /CharStrings ") t1t_int(&out, spec.declared_chars != 0 ? spec.declared_chars : len(spec.names)) t1t_str(&out, " dict dup begin\n") for name, i in spec.names { append(&out, '/') t1t_str(&out, name) append(&out, ' ') t1t_entry(&out, spec, i < len(spec.chars) ? spec.chars[i] : nil, allocator) } t1t_str(&out, "end\nend\n") return out[:] } // The builder and the golden font B must agree on the same glyph. If the // cipher in the builder were wrong, this is where it would show. @(test) test_t1_builder_agrees_with_the_golden_font :: proc(t: ^testing.T) { f := T1_Fixture{} t1f_init(&f) defer t1f_destroy(&f) golden := t1f_font(t, &f, T1_FONT_B[:], "font B") if golden == nil { return } // T1T_FONTB_A is the same charstring font B carries for /A. spec := T1t_Spec{ names = {".notdef", "A"}, chars = {T1T_NOTDEF[:], T1T_FONTB_A[:]}, } built := t1f_font(t, &f, t1t_assemble(&spec, f.alloc), "builder") if built == nil { return } a := t1f_glyph(t, &f, golden, "A") b := t1f_glyph(t, &f, built, "A") testing.expect(t, len(a.verbs) == 4, "font B's /A did not decode, so the comparison is vacuous") t1f_expect_path(t, &b, a.verbs[:], a.points[:], "the builder's /A against font B's") } // --------------------------------------------------------------------------- // Deriving the PFB and .pfa forms from font A // --------------------------------------------------------------------------- @(private = "file") t1t_pfb :: proc(src: []byte, allocator: mem.Allocator) -> []byte { out := make([dynamic]byte, 0, len(src) + 32, allocator) hdr :: proc(out: ^[dynamic]byte, kind: byte, n: int) { append(out, 0x80, kind, byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)) } seg1 := src[:T1A_CLEAR_LEN] seg2 := src[T1A_CLEAR_LEN:T1A_CLEAR_LEN + T1A_BIN_LEN] seg3 := src[T1A_CLEAR_LEN + T1A_BIN_LEN:] hdr(&out, 1, len(seg1)) append(&out, ..seg1) hdr(&out, 2, len(seg2)) append(&out, ..seg2) hdr(&out, 1, len(seg3)) append(&out, ..seg3) append(&out, 0x80, 3) return out[:] } // t1t_hex_form rewrites the eexec ciphertext as the hex ASCII a .pfa carries, // wrapped in lines the way every real one is. @(private = "file") t1t_hex_form :: proc(src: []byte, allocator: mem.Allocator) -> []byte { hex := "0123456789abcdef" out := make([dynamic]byte, 0, len(src) * 2 + 64, allocator) append(&out, ..src[:T1A_CLEAR_LEN]) for b, i in src[T1A_CLEAR_LEN:T1A_CLEAR_LEN + T1A_BIN_LEN] { append(&out, hex[b >> 4]) append(&out, hex[b & 15]) if i % 32 == 31 { append(&out, '\n') } } append(&out, '\n') append(&out, ..src[T1A_CLEAR_LEN + T1A_BIN_LEN:]) return out[:] } // t1f_expect_same asserts two parses of the same font agree glyph for glyph. @(private = "file") t1f_expect_same :: proc(t: ^testing.T, a, b: ^Type1_Font, f: ^T1_Fixture, label: string) { if !testing.expectf(t, t1_glyph_count(a) == t1_glyph_count(b), "%s: %d glyphs against %d", label, t1_glyph_count(a), t1_glyph_count(b)) { return } testing.expectf(t, t1_glyph_count(a) == 8, "%s: only %d glyphs, so the comparison is thin", label, t1_glyph_count(a)) for gid in 0 ..< t1_glyph_count(a) { pa := path_make(f.alloc) pb := path_make(f.alloc) _ = t1_glyph_path(a, u16(gid), &pa) _ = t1_glyph_path(b, u16(gid), &pb) t1f_expect_path(t, &pb, pa.verbs[:], pa.points[:], label) testing.expectf(t, t1_glyph_advance(a, u16(gid)) == t1_glyph_advance(b, u16(gid)), "%s: /%s advance differs", label, a.names[gid]) } ea := t1_builtin_encoding(a) eb := t1_builtin_encoding(b) if ea != nil && eb != nil { testing.expectf(t, ea^ == eb^, "%s: encodings differ", label) } else { testing.expectf(t, ea == eb, "%s: one form has an encoding and the other does not", label) } }