Files

1428 lines
56 KiB
Odin
Raw Permalink Normal View History

2026-09-25 23:32:29 +02:00
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 <code> /<name> 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 <code> /<name> 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)
}
}