2172 lines
86 KiB
Odin
2172 lines
86 KiB
Odin
package pdf
|
||
|
||
import "core:testing"
|
||
|
||
// Tests for font.odin: the PDF font dictionary, its encodings, its widths and
|
||
// its CID machinery.
|
||
//
|
||
// The Document fixtures -- `cf_open`, `cf_parse`, `cf_stream`, `cf_close` --
|
||
// live in function_test.odin.
|
||
//
|
||
// The font programs here are assembled from byte literals rather than taken
|
||
// from real files, because what is under test is the *dictionary's* effect on
|
||
// them: which cmap subtable a symbolic flag selects, which name a
|
||
// /Differences entry substitutes, which glyph a CID reaches. The programs
|
||
// themselves are tested in font_truetype_test.odin and font_cff_test.odin, so
|
||
// the ones built here are the smallest that can answer a lookup -- often a
|
||
// cmap and a glyph count and nothing else.
|
||
//
|
||
// Everything is allocated from the Document's arena, so a failing assertion
|
||
// cannot leak: `close` reclaims the lot however the test left.
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Byte assembly
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(private = "file")
|
||
fn_alloc :: proc(doc: ^Document, n: int) -> []byte {
|
||
return make([]byte, n, doc.allocator)
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_w16 :: proc(b: []byte, off: int, v: u16) {
|
||
b[off] = u8(v >> 8)
|
||
b[off + 1] = u8(v)
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_w32 :: proc(b: []byte, off: int, v: u32) {
|
||
b[off] = u8(v >> 24)
|
||
b[off + 1] = u8(v >> 16)
|
||
b[off + 2] = u8(v >> 8)
|
||
b[off + 3] = u8(v)
|
||
}
|
||
|
||
@(private = "file")
|
||
Fn_Table :: struct {
|
||
tag: string,
|
||
data: []byte,
|
||
}
|
||
|
||
// fn_sfnt writes a table directory around `tables`.
|
||
@(private = "file")
|
||
fn_sfnt :: proc(doc: ^Document, tables: []Fn_Table, sfnt: u32 = 0x00010000) -> []byte {
|
||
total := 12 + 16 * len(tables)
|
||
for tb in tables {
|
||
total += (len(tb.data) + 3) & ~int(3)
|
||
}
|
||
out := fn_alloc(doc, total)
|
||
fn_w32(out, 0, sfnt)
|
||
fn_w16(out, 4, u16(len(tables)))
|
||
|
||
pos := 12 + 16 * len(tables)
|
||
for tb, i in tables {
|
||
rec := 12 + 16 * i
|
||
for k in 0 ..< 4 {
|
||
out[rec + k] = tb.tag[k] if k < len(tb.tag) else ' '
|
||
}
|
||
fn_w32(out, rec + 8, u32(pos))
|
||
fn_w32(out, rec + 12, u32(len(tb.data)))
|
||
copy(out[pos:], tb.data)
|
||
pos += (len(tb.data) + 3) & ~int(3)
|
||
}
|
||
return out
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_head :: proc(doc: ^Document, upem: u16) -> []byte {
|
||
out := fn_alloc(doc, 54)
|
||
fn_w16(out, 18, upem)
|
||
return out // indexToLocFormat 0: short loca
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_maxp :: proc(doc: ^Document, num_glyphs: u16) -> []byte {
|
||
out := fn_alloc(doc, 32)
|
||
fn_w32(out, 0, 0x00010000)
|
||
fn_w16(out, 4, num_glyphs)
|
||
return out
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_hhea :: proc(doc: ^Document, n_metrics: u16) -> []byte {
|
||
out := fn_alloc(doc, 36)
|
||
fn_w16(out, 34, n_metrics)
|
||
return out
|
||
}
|
||
|
||
// fn_hmtx writes one longHorMetric per advance: the advance itself and a zero
|
||
// left side bearing.
|
||
@(private = "file")
|
||
fn_hmtx :: proc(doc: ^Document, advances: []u16) -> []byte {
|
||
out := fn_alloc(doc, 4 * len(advances))
|
||
for a, i in advances {
|
||
fn_w16(out, 4 * i, a)
|
||
}
|
||
return out
|
||
}
|
||
|
||
@(private = "file")
|
||
Fn_Cmap_Rec :: struct {
|
||
plat, enc: u16,
|
||
sub: []byte,
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_cmap :: proc(doc: ^Document, recs: ..Fn_Cmap_Rec) -> []byte {
|
||
total := 4 + 8 * len(recs)
|
||
for r in recs {
|
||
total += len(r.sub)
|
||
}
|
||
out := fn_alloc(doc, total)
|
||
fn_w16(out, 2, u16(len(recs)))
|
||
pos := 4 + 8 * len(recs)
|
||
for r, i in recs {
|
||
rec := 4 + 8 * i
|
||
fn_w16(out, rec, r.plat)
|
||
fn_w16(out, rec + 2, r.enc)
|
||
fn_w32(out, rec + 4, u32(pos))
|
||
copy(out[pos:], r.sub)
|
||
pos += len(r.sub)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// Fn_Seg is one format-4 segment: code `lo` maps to glyph `gid`, and the
|
||
// codes up to `hi` map to the glyphs after it. The idDelta the table actually
|
||
// stores is the difference, which is what the builder computes.
|
||
@(private = "file")
|
||
Fn_Seg :: struct {
|
||
lo, hi, gid: u16,
|
||
}
|
||
|
||
// fn_cmap4 builds a format 4 subtable out of delta segments, appending the
|
||
// 0xFFFF terminator the format requires. idRangeOffset is zero throughout, so
|
||
// every segment is a pure delta -- the glyphIdArray indirection is
|
||
// font_truetype.odin's business and is tested there.
|
||
@(private = "file")
|
||
fn_cmap4 :: proc(doc: ^Document, segs: ..Fn_Seg) -> []byte {
|
||
n := len(segs) + 1
|
||
out := fn_alloc(doc, 16 + 8 * n)
|
||
fn_w16(out, 0, 4)
|
||
fn_w16(out, 2, u16(len(out)))
|
||
fn_w16(out, 6, u16(2 * n))
|
||
end_p := 14
|
||
start_p := end_p + 2 * n + 2
|
||
delta_p := start_p + 2 * n
|
||
for s, i in segs {
|
||
fn_w16(out, end_p + 2 * i, s.hi)
|
||
fn_w16(out, start_p + 2 * i, s.lo)
|
||
fn_w16(out, delta_p + 2 * i, u16((u32(s.gid) + 0x10000 - u32(s.lo)) & 0xFFFF))
|
||
}
|
||
fn_w16(out, end_p + 2 * (n - 1), 0xFFFF)
|
||
fn_w16(out, start_p + 2 * (n - 1), 0xFFFF)
|
||
fn_w16(out, delta_p + 2 * (n - 1), 1)
|
||
return out
|
||
}
|
||
|
||
// fn_cmap0 is the 262-byte byte-encoding table.
|
||
@(private = "file")
|
||
fn_cmap0 :: proc(doc: ^Document, pairs: ..[2]u8) -> []byte {
|
||
out := fn_alloc(doc, 262)
|
||
fn_w16(out, 2, 262)
|
||
for pr in pairs {
|
||
out[6 + int(pr[0])] = pr[1]
|
||
}
|
||
return out
|
||
}
|
||
|
||
// fn_post2 writes a version 2.0 `post` table naming every glyph from its own
|
||
// string pool, so none of the names collide with the standard Macintosh set.
|
||
@(private = "file")
|
||
fn_post2 :: proc(doc: ^Document, names: []string) -> []byte {
|
||
pool := 0
|
||
for nm in names {
|
||
pool += 1 + len(nm)
|
||
}
|
||
out := fn_alloc(doc, 32 + 2 + 2 * len(names) + pool)
|
||
fn_w32(out, 0, 0x00020000)
|
||
fn_w16(out, 32, u16(len(names)))
|
||
p := 34 + 2 * len(names)
|
||
for nm, i in names {
|
||
fn_w16(out, 34 + 2 * i, u16(258 + i))
|
||
out[p] = u8(len(nm))
|
||
copy(out[p + 1:], nm)
|
||
p += 1 + len(nm)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// A blank glyph: zero contours, so `loca` can point at it and `glyf` is
|
||
// non-empty, which is what makes tt_has_glyf true.
|
||
@(private = "file")
|
||
FN_GLYF_BLANK := [?]u8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||
|
||
// fn_ttf assembles a TrueType font. `cmaps` may be empty, in which case the
|
||
// font has no `cmap` table at all.
|
||
@(private = "file")
|
||
fn_ttf :: proc(doc: ^Document, num_glyphs: int, upem: u16, cmaps: []Fn_Cmap_Rec,
|
||
post: []byte = nil, advances: []u16 = nil) -> []byte {
|
||
tables := make([dynamic]Fn_Table, 0, 8, doc.allocator)
|
||
append(&tables, Fn_Table{"head", fn_head(doc, upem)})
|
||
append(&tables, Fn_Table{"maxp", fn_maxp(doc, u16(num_glyphs))})
|
||
// Every glyph is the same blank, so `loca` repeats one offset pair.
|
||
loca := fn_alloc(doc, 2 * (num_glyphs + 1))
|
||
for i in 0 ..= num_glyphs {
|
||
fn_w16(loca, 2 * i, u16(min(i, 1) * (len(FN_GLYF_BLANK) / 2)))
|
||
}
|
||
append(&tables, Fn_Table{"loca", loca})
|
||
append(&tables, Fn_Table{"glyf", FN_GLYF_BLANK[:]})
|
||
if len(cmaps) > 0 {
|
||
append(&tables, Fn_Table{"cmap", fn_cmap(doc, ..cmaps)})
|
||
}
|
||
if len(post) > 0 {
|
||
append(&tables, Fn_Table{"post", post})
|
||
}
|
||
if len(advances) > 0 {
|
||
append(&tables, Fn_Table{"hhea", fn_hhea(doc, u16(len(advances)))})
|
||
append(&tables, Fn_Table{"hmtx", fn_hmtx(doc, advances)})
|
||
}
|
||
return fn_sfnt(doc, tables[:])
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// A minimal CFF
|
||
//
|
||
// Header, Name INDEX, Top DICT INDEX, an empty String and GSubr INDEX, a
|
||
// format 0 charset and a CharStrings INDEX. Every Top DICT offset is written
|
||
// in the five-byte integer form so that the dictionary's length does not
|
||
// depend on the values in it, which is what lets the layout be computed in one
|
||
// pass.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// FN_CS is one charstring: "400 50 50 rmoveto endchar". rmoveto takes two
|
||
// operands, so the third is the glyph's width (T2 3.1) -- 400, since
|
||
// nominalWidthX defaults to zero. Six bytes, and it draws a single point.
|
||
@(private = "file")
|
||
FN_CS := [?]u8{0xF8, 0x24, 0xBD, 0xBD, 0x15, 0x0E}
|
||
|
||
// FN_CFF_MATRIX is a Top DICT FontMatrix of [0.0005 0 0 0.0005 0 0]: 2000
|
||
// units per em rather than the CFF default of 1000. Every operand is written
|
||
// in a fixed-width form -- the nibble-packed real of CFF Table 5 for the two
|
||
// scales, the five-byte integer for the four zeros -- so that the dictionary's
|
||
// length does not depend on its contents.
|
||
@(private = "file")
|
||
FN_CFF_MATRIX := [?]u8{
|
||
0x1E, 0x0A, 0x00, 0x05, 0xFF, // 0.0005
|
||
0x1D, 0x00, 0x00, 0x00, 0x00, // 0
|
||
0x1D, 0x00, 0x00, 0x00, 0x00, // 0
|
||
0x1E, 0x0A, 0x00, 0x05, 0xFF, // 0.0005
|
||
0x1D, 0x00, 0x00, 0x00, 0x00, // 0
|
||
0x1D, 0x00, 0x00, 0x00, 0x00, // 0
|
||
0x0C, 0x07, // FontMatrix
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_cff :: proc(doc: ^Document, ids: []u16, is_cid: bool, own_matrix := false) -> []byte {
|
||
ng := len(ids) + 1 // .notdef plus one per id
|
||
dict_len := 12 + (17 if is_cid else 0) + (len(FN_CFF_MATRIX) if own_matrix else 0)
|
||
charset_off := 4 + 6 + (5 + dict_len) + 2 + 2
|
||
charset_len := 1 + 2 * len(ids)
|
||
cs_off := charset_off + charset_len
|
||
cs_len := 3 + (ng + 1) + len(FN_CS) * ng
|
||
out := fn_alloc(doc, cs_off + cs_len)
|
||
|
||
out[0], out[1], out[2], out[3] = 1, 0, 4, 1 // header
|
||
|
||
// Name INDEX: one name, "T".
|
||
p := 4
|
||
fn_w16(out, p, 1)
|
||
out[p + 2] = 1
|
||
out[p + 3] = 1
|
||
out[p + 4] = 2
|
||
out[p + 5] = 'T'
|
||
p += 6
|
||
|
||
// Top DICT INDEX.
|
||
fn_w16(out, p, 1)
|
||
out[p + 2] = 1
|
||
out[p + 3] = 1
|
||
out[p + 4] = u8(1 + dict_len)
|
||
d := p + 5
|
||
if is_cid {
|
||
// ROS: two SIDs and a supplement, then the operator. Its presence is
|
||
// the whole of what makes a CFF CID-keyed.
|
||
for k in 0 ..< 3 {
|
||
out[d + 5 * k] = 29
|
||
}
|
||
out[d + 15], out[d + 16] = 12, 30
|
||
d += 17
|
||
}
|
||
if own_matrix {
|
||
copy(out[d:], FN_CFF_MATRIX[:])
|
||
d += len(FN_CFF_MATRIX)
|
||
}
|
||
out[d] = 29
|
||
fn_w32(out, d + 1, u32(charset_off))
|
||
out[d + 5] = 15 // charset
|
||
out[d + 6] = 29
|
||
fn_w32(out, d + 7, u32(cs_off))
|
||
out[d + 11] = 17 // CharStrings
|
||
p += 5 + dict_len
|
||
|
||
fn_w16(out, p, 0) // String INDEX, empty
|
||
fn_w16(out, p + 2, 0) // Global Subr INDEX, empty
|
||
|
||
// charset, format 0: one SID (or CID) per glyph from glyph 1 up.
|
||
out[charset_off] = 0
|
||
for id, i in ids {
|
||
fn_w16(out, charset_off + 1 + 2 * i, id)
|
||
}
|
||
|
||
// CharStrings INDEX.
|
||
fn_w16(out, cs_off, u16(ng))
|
||
out[cs_off + 2] = 1
|
||
for i in 0 ..= ng {
|
||
out[cs_off + 3 + i] = u8(1 + i * len(FN_CS))
|
||
}
|
||
q := cs_off + 3 + ng + 1
|
||
for _ in 0 ..< ng {
|
||
copy(out[q:], FN_CS[:])
|
||
q += len(FN_CS)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Font fixtures
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(private = "file")
|
||
fn_dict :: proc(doc: ^Document, src: string) -> ^Dict {
|
||
d, _ := to_dict(cf_parse(doc, src))
|
||
return d
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_load :: proc(t: ^testing.T, doc: ^Document, d: ^Dict, loc := #caller_location) -> ^Font {
|
||
f, err := font_load(doc, d)
|
||
testing.expectf(t, err == .None, "font_load: %v", err, loc = loc)
|
||
testing.expect(t, f != nil, "font_load returned no font", loc = loc)
|
||
return f
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_font :: proc(t: ^testing.T, doc: ^Document, src: string, loc := #caller_location) -> ^Font {
|
||
return fn_load(t, doc, fn_dict(doc, src), loc)
|
||
}
|
||
|
||
// fn_simple builds a simple font whose descriptor carries `flags` and the
|
||
// program `prog` under `key`, which is how every embedded-program test here is
|
||
// set up.
|
||
@(private = "file")
|
||
fn_simple :: proc(t: ^testing.T, doc: ^Document, font_src, desc_src, key: string,
|
||
prog: []byte, loc := #caller_location) -> ^Font {
|
||
d := fn_dict(doc, font_src)
|
||
desc := fn_dict(doc, desc_src)
|
||
if prog != nil {
|
||
dict_set(desc, key, cf_stream(doc, "<< >>", prog))
|
||
}
|
||
dict_set(d, "FontDescriptor", desc)
|
||
return fn_load(t, doc, d, loc)
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_expect_width :: proc(t: ^testing.T, f: ^Font, code: int, want: f64, what: string,
|
||
loc := #caller_location) {
|
||
if f == nil || code < 0 || code > 255 {
|
||
testing.expectf(t, false, "%s: no font", what, loc = loc)
|
||
return
|
||
}
|
||
got := f.code_width[code]
|
||
testing.expectf(t, got == want, "%s: width of code %d is %v, wanted %v",
|
||
what, code, got, want, loc = loc)
|
||
}
|
||
|
||
@(private = "file")
|
||
fn_expect_gid :: proc(t: ^testing.T, f: ^Font, code: int, want: u32, what: string,
|
||
loc := #caller_location) {
|
||
if f == nil || code < 0 || code > 255 {
|
||
testing.expectf(t, false, "%s: no font", what, loc = loc)
|
||
return
|
||
}
|
||
got := f.code_gid[code]
|
||
testing.expectf(t, got == want, "%s: gid for code %d is %d, wanted %d",
|
||
what, code, got, want, loc = loc)
|
||
}
|
||
|
||
// fn_decode runs a string through the font and hands back the codes. The
|
||
// dynamic array is the caller's to delete.
|
||
@(private = "file")
|
||
fn_decode :: proc(f: ^Font, s: string) -> [dynamic]Font_Code {
|
||
out := make([dynamic]Font_Code, 0, 8)
|
||
font_decode(f, transmute([]byte)s, &out)
|
||
return out
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Standard 14 metrics
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_std14_widths :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// No /Widths, no /FontDescriptor: 9.6.2.2 lets a standard font omit both,
|
||
// and the AFM metrics are then the only widths there are.
|
||
f := fn_font(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.std_index == STD_HELVETICA, "Helvetica was not recognised")
|
||
testing.expect(t, !f.symbolic, "Helvetica is not a symbolic font")
|
||
|
||
fn_expect_width(t, f, 'A', 667, "Helvetica A")
|
||
fn_expect_width(t, f, ' ', 278, "Helvetica space")
|
||
fn_expect_width(t, f, 'W', 944, "Helvetica W")
|
||
// StandardEncoding is what a font with no /Encoding falls back to.
|
||
testing.expect_value(t, f.enc_names['A'], "A")
|
||
testing.expect_value(t, f.enc_names[0xA5], "yen")
|
||
fn_expect_width(t, f, 0xA5, 556, "Helvetica yen")
|
||
}
|
||
|
||
@(test)
|
||
test_font_std14_subset_tag_and_alias :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A subset tag and a Monotype alias both have to resolve, or a font that
|
||
// is Times in everything but spelling loses every width it has.
|
||
f := fn_font(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /ABCDEF+Times-Roman >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.std_index == STD_TIMES_ROMAN, "subset tag was not stripped")
|
||
fn_expect_width(t, f, 'A', 722, "Times A")
|
||
|
||
g := fn_font(t, doc, "<< /Type /Font /Subtype /TrueType /BaseFont /Arial,Bold >>")
|
||
if g == nil { return }
|
||
testing.expect(t, g.std_index == STD_HELVETICA_BOLD, "Arial,Bold is Helvetica-Bold")
|
||
}
|
||
|
||
@(test)
|
||
test_font_std14_symbol_builtin_encoding :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Symbol's built-in encoding is its own, not StandardEncoding: code 0x41
|
||
// is Alpha, and its width is Alpha's.
|
||
f := fn_font(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Symbol >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.symbolic, "Symbol is a symbolic font")
|
||
testing.expect_value(t, f.enc_names[0x41], "Alpha")
|
||
fn_expect_width(t, f, 0x41, 722, "Symbol Alpha")
|
||
fn_expect_width(t, f, ' ', 250, "Symbol space")
|
||
}
|
||
|
||
@(test)
|
||
test_font_widths_beat_metrics :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /Widths is the file speaking about its own font and wins over the AFM,
|
||
// even for a standard 14 name. Codes outside /FirstChar../LastChar fall
|
||
// through to the metrics.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica " +
|
||
"/FirstChar 65 /LastChar 67 /Widths [100 200 300] >>")
|
||
if f == nil { return }
|
||
fn_expect_width(t, f, 'A', 100, "widths[0]")
|
||
fn_expect_width(t, f, 'B', 200, "widths[1]")
|
||
fn_expect_width(t, f, 'C', 300, "widths[2]")
|
||
fn_expect_width(t, f, 'D', 722, "past LastChar falls back to the AFM")
|
||
}
|
||
|
||
@(test)
|
||
test_font_missing_width :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A font that is not one of the fourteen has no metrics to fall back on,
|
||
// so /MissingWidth is what an unlisted code is worth.
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Whatever " +
|
||
"/FirstChar 65 /LastChar 65 /Widths [400] >>",
|
||
"<< /Flags 32 /MissingWidth 555 >>", "", nil)
|
||
if f == nil { return }
|
||
fn_expect_width(t, f, 'A', 400, "listed code")
|
||
fn_expect_width(t, f, 'B', 555, "unlisted code")
|
||
fn_expect_width(t, f, 0, 555, "code 0")
|
||
|
||
// With no /MissingWidth the answer is zero, not a guess.
|
||
g := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Whatever " +
|
||
"/FirstChar 65 /LastChar 65 /Widths [400] >>",
|
||
"<< /Flags 32 >>", "", nil)
|
||
if g == nil { return }
|
||
fn_expect_width(t, g, 'B', 0, "no MissingWidth")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Encodings
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_base_encoding_and_differences :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /BaseEncoding replaces the built-in encoding wholesale; /Differences
|
||
// then overrides individual codes of it. Both have to be visible in the
|
||
// result, so the assertions below check a code from each.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding " +
|
||
"<< /BaseEncoding /WinAnsiEncoding /Differences [65 /bullet /Euro] >> >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, f.enc_names[65], "bullet")
|
||
testing.expect_value(t, f.enc_names[66], "Euro")
|
||
testing.expect_value(t, f.enc_names[67], "C") // WinAnsi, untouched
|
||
testing.expect_value(t, f.enc_names[0xA5], "yen") // WinAnsi 0xA5
|
||
fn_expect_width(t, f, 65, 350, "bullet")
|
||
fn_expect_width(t, f, 66, 556, "Euro")
|
||
fn_expect_width(t, f, 67, 722, "C")
|
||
}
|
||
|
||
@(test)
|
||
test_font_encoding_name_form :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /Encoding as a bare name. MacRoman and WinAnsi disagree about 0xA5,
|
||
// which is what makes this assertion able to tell them apart.
|
||
mac := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /MacRomanEncoding >>")
|
||
if mac == nil { return }
|
||
testing.expect_value(t, mac.enc_names[0xA5], "bullet")
|
||
fn_expect_width(t, mac, 0xA5, 350, "MacRoman 0xA5")
|
||
|
||
win := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>")
|
||
if win == nil { return }
|
||
testing.expect_value(t, win.enc_names[0xA5], "yen")
|
||
}
|
||
|
||
@(test)
|
||
test_font_unknown_encoding_name_keeps_builtin :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// An /Encoding this library does not know is not a reason to reset the
|
||
// font to StandardEncoding -- for Symbol that would replace every glyph
|
||
// name with a Latin one the font does not have.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Symbol /Encoding /PDFDocEncoding >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, f.enc_names[0x41], "Alpha")
|
||
|
||
// A /Differences over the same unknown name still applies.
|
||
g := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Symbol /Encoding " +
|
||
"<< /BaseEncoding /Nonsense /Differences [65 /beta] >> >>")
|
||
if g == nil { return }
|
||
testing.expect_value(t, g.enc_names[0x41], "beta")
|
||
testing.expect_value(t, g.enc_names[0x42], "Beta")
|
||
}
|
||
|
||
@(test)
|
||
test_font_differences_malformed :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Codes outside 0..255, a real where an integer belongs, and a trailing
|
||
// name with no code before it. None of them may write out of bounds, and
|
||
// the entries around them still have to land.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding " +
|
||
"<< /Differences [-3 /A 300 /B 66.0 /X /Y (str) /Z] >> >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, f.enc_names[66], "X")
|
||
testing.expect_value(t, f.enc_names[67], "Y")
|
||
testing.expect_value(t, f.enc_names[68], "Z")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// TrueType: the symbolic rules (9.6.5.4)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_symbolic_truetype_symbol_cmap :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A (3,0) table mapping the bare byte 0x42 and the private-use 0xF041.
|
||
// Both conventions occur, so both codes have to resolve.
|
||
sym := fn_cmap4(doc, Fn_Seg{0x42, 0x42, 4}, Fn_Seg{0xF041, 0xF041, 3})
|
||
prog := fn_ttf(doc, 10, 1000, []Fn_Cmap_Rec{{3, 0, sym}})
|
||
|
||
// The /Encoding is present AND deliberately wrong: a symbolic font's
|
||
// codes do not go through it. If it were consulted, code 0x41 would look
|
||
// for "A" in a font whose only cmap is (3,0) and find nothing.
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Wingdings " +
|
||
"/Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, f.symbolic, "the descriptor said symbolic")
|
||
testing.expect(t, f.embedded && !f.program_failed, "the program should have parsed")
|
||
fn_expect_gid(t, f, 0x42, 4, "bare code through the (3,0) cmap")
|
||
fn_expect_gid(t, f, 0x41, 3, "0xF000 + code through the (3,0) cmap")
|
||
fn_expect_gid(t, f, 0x43, 0, "a code the symbol cmap does not have")
|
||
}
|
||
|
||
@(test)
|
||
test_font_symbolic_truetype_mac_fallback :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// No (3,0) at all: a symbolic font then goes through the (1,0) Mac Roman
|
||
// table with the raw code, which is the other half of the rule.
|
||
prog := fn_ttf(doc, 10, 1000, []Fn_Cmap_Rec{{1, 0, fn_cmap0(doc, {0x41, 7}, {0xA5, 8})}})
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Weird >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
fn_expect_gid(t, f, 0x41, 7, "(1,0) Mac Roman")
|
||
fn_expect_gid(t, f, 0xA5, 8, "(1,0) Mac Roman, high byte")
|
||
fn_expect_gid(t, f, 0x42, 0, "not in the Mac table")
|
||
}
|
||
|
||
@(test)
|
||
test_font_symbolic_truetype_symbol_beats_mac :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Both tables present and deliberately disagreeing. 9.6.5.4 orders them:
|
||
// the (3,0) symbol table is consulted first and the (1,0) Mac Roman one
|
||
// only answers what it left. A font carrying both is the ordinary shape
|
||
// of a Windows-authored symbol font, and reading them the other way round
|
||
// silently swaps every glyph on the page for a different one -- the sort
|
||
// of failure that renders and still looks wrong.
|
||
sym := fn_cmap4(doc, Fn_Seg{0x41, 0x41, 5}, Fn_Seg{0xF042, 0xF042, 6})
|
||
mac := fn_cmap0(doc, {0x41, 7}, {0x42, 8}, {0x43, 9})
|
||
prog := fn_ttf(doc, 12, 1000, []Fn_Cmap_Rec{{3, 0, sym}, {1, 0, mac}})
|
||
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Both >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, f.symbolic, "the descriptor said symbolic")
|
||
fn_expect_gid(t, f, 0x41, 5, "the bare code in (3,0) beats (1,0)")
|
||
fn_expect_gid(t, f, 0x42, 6, "0xF000 + code in (3,0) also beats (1,0)")
|
||
fn_expect_gid(t, f, 0x43, 9, "and (1,0) still answers what (3,0) does not")
|
||
}
|
||
|
||
@(test)
|
||
test_font_nonsymbolic_truetype_name_to_unicode :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// The non-symbolic route: code -> name (through the encoding) -> Unicode
|
||
// (through the AGL) -> glyph (through the (3,1) cmap). /Differences puts
|
||
// "C" on code 65, so 65 must reach C's glyph and not A's.
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'C', 1}) // A->1, B->2, C->3
|
||
prog := fn_ttf(doc, 10, 1000, []Fn_Cmap_Rec{{3, 1, uni}})
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Plain /Encoding " +
|
||
"<< /BaseEncoding /WinAnsiEncoding /Differences [65 /C] >> >>",
|
||
"<< /Flags 32 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, !f.symbolic, "flag 32 is nonsymbolic")
|
||
fn_expect_gid(t, f, 'A', 3, "/Differences put C on code 65")
|
||
fn_expect_gid(t, f, 'B', 2, "B through WinAnsi and the (3,1) cmap")
|
||
fn_expect_gid(t, f, 'C', 3, "C likewise")
|
||
fn_expect_gid(t, f, 'D', 0, "the cmap has no D")
|
||
}
|
||
|
||
@(test)
|
||
test_font_nonsymbolic_truetype_post_names :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A subsetter's names are not in the AGL, so the Unicode route fails and
|
||
// the `post` table is the only thing left that knows what "g99" is.
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'A', 1})
|
||
post := fn_post2(doc, []string{".notdef", "one", "g99", "three"})
|
||
prog := fn_ttf(doc, 4, 1000, []Fn_Cmap_Rec{{3, 1, uni}}, post)
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Sub /Encoding " +
|
||
"<< /Differences [65 /g99] >> >>",
|
||
"<< /Flags 32 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
fn_expect_gid(t, f, 'A', 2, "the post table names glyph 2 g99")
|
||
}
|
||
|
||
@(test)
|
||
test_font_nonsymbolic_truetype_name_through_macroman :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// 9.6.5.4's third route, and the only one this font has: one cmap, (1,0),
|
||
// and no `post` names, which is the shape of anything authored on a Mac
|
||
// or run through a subsetter. The name has to be carried BACK through
|
||
// MacRomanEncoding to get the code that table is keyed by. WinAnsi puts
|
||
// "eacute" on 0xE9 and MacRomanEncoding puts it on 0x8E, so the glyph
|
||
// wanted is the one at 0x8E; looking the raw 0xE9 up in the same table
|
||
// finds MacRoman's "Egrave" instead -- a real glyph, the wrong letter,
|
||
// and a page that renders and is silently wrong.
|
||
mac := fn_cmap0(doc, {0x8E, 1}, {0xE9, 2}, {0x41, 3})
|
||
prog := fn_ttf(doc, 4, 1000, []Fn_Cmap_Rec{{1, 0, mac}})
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /MacOnly " +
|
||
"/Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, !f.symbolic, "flag 32 is nonsymbolic")
|
||
fn_expect_gid(t, f, 0xE9, 1, "WinAnsi eacute through MacRoman's 0x8E")
|
||
// The other direction, so that a fix which merely offsets the code cannot
|
||
// pass: WinAnsi 0xC8 is "Egrave", which MacRomanEncoding puts on 0xE9.
|
||
fn_expect_gid(t, f, 0xC8, 2, "WinAnsi Egrave through MacRoman's 0xE9")
|
||
fn_expect_gid(t, f, 0x41, 3, "ASCII, where the two encodings agree")
|
||
fn_expect_gid(t, f, 0xEA, 0, "a code neither route reaches")
|
||
}
|
||
|
||
@(test)
|
||
test_font_truetype_no_cmap_is_gid :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A stripped subset with no cmap at all. Every viewer reads the code as a
|
||
// glyph index here, and there is nothing else it could mean.
|
||
prog := fn_ttf(doc, 6, 1000, nil)
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Sub /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
fn_expect_gid(t, f, 3, 3, "code as glyph index")
|
||
fn_expect_gid(t, f, 5, 5, "code as glyph index")
|
||
fn_expect_gid(t, f, 6, 0, "past the glyph count there is no glyph")
|
||
fn_expect_gid(t, f, 200, 0, "far past the glyph count")
|
||
}
|
||
|
||
@(test)
|
||
test_font_truetype_units_per_em :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Glyph space for a TrueType font is 1/unitsPerEm, not 1/1000: a 2048-unit
|
||
// font drawn through a thousandths matrix comes out twice too large.
|
||
prog := fn_ttf(doc, 4, 2048, nil)
|
||
f := fn_simple(t, doc, "<< /Type /Font /Subtype /TrueType /BaseFont /Big >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
m := font_matrix(f)
|
||
testing.expect_value(t, m.a, 1.0 / 2048.0)
|
||
testing.expect_value(t, m.d, 1.0 / 2048.0)
|
||
testing.expect(t, !font_is_type3(f), "a TrueType font is not Type3")
|
||
}
|
||
|
||
@(test)
|
||
test_font_truetype_hmtx_advance :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// No /Widths and no /MissingWidth: the program's own advances are the
|
||
// last thing left, and they have to be scaled out of font units into the
|
||
// thousandths every PDF width is in. 1024/2048 of an em is 500.
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'A', 1})
|
||
prog := fn_ttf(doc, 3, 2048, []Fn_Cmap_Rec{{3, 1, uni}}, nil, []u16{0, 1024, 512})
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /NoWidths /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
fn_expect_gid(t, f, 'A', 1, "A is glyph 1")
|
||
fn_expect_width(t, f, 'A', 500, "hmtx advance scaled to 1/1000")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// CFF programs
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_cff_simple_by_name :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A name-keyed Type1C: SIDs 34, 35 and 36 are "A", "B" and "C" among the
|
||
// CFF standard strings, so the charset names glyphs 1..3 after them and
|
||
// the encoding's names are what find them.
|
||
prog := fn_cff(doc, []u16{34, 35, 36}, false)
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Emb /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 >>", "FontFile3", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, f.cff != nil, "the CFF should have parsed")
|
||
testing.expect(t, !f.program_failed, "no failure expected")
|
||
fn_expect_gid(t, f, 'A', 1, "charset SID 34 is A")
|
||
fn_expect_gid(t, f, 'C', 3, "charset SID 36 is C")
|
||
fn_expect_gid(t, f, 'D', 0, "the font has no D")
|
||
|
||
// A CFF's own FontMatrix is the glyph space, and this one has none, so it
|
||
// is the 1/1000 default rather than anything the sfnt would have said.
|
||
testing.expect_value(t, font_matrix(f).a, 0.001)
|
||
|
||
p := path_make()
|
||
defer path_destroy(&p)
|
||
err := font_glyph_path(f, 1, &p)
|
||
testing.expectf(t, err == .None, "font_glyph_path: %v", err)
|
||
testing.expect(t, !path_is_empty(&p), "the charstring draws a point")
|
||
}
|
||
|
||
@(test)
|
||
test_font_cff_cid_charset :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A CID-keyed CFF: its charset holds CIDs, not name SIDs, so the CID a
|
||
// string decodes to has to be looked up in it rather than used as a glyph
|
||
// index. Glyph 2 is CID 20 here, and reading CID 20 as a GID would draw
|
||
// glyph 20, which does not exist.
|
||
prog := fn_cff(doc, []u16{10, 20, 30}, true)
|
||
cid := fn_dict(doc, "<< /Type /Font /Subtype /CIDFontType0 /DW 600 >>")
|
||
desc := fn_dict(doc, "<< /Flags 4 >>")
|
||
dict_set(desc, "FontFile3", cf_stream(doc, "<< /Subtype /CIDFontType0C >>", prog))
|
||
dict_set(cid, "FontDescriptor", desc)
|
||
f := fn_type0(t, doc, "<< /Type /Font /Subtype /Type0 /Encoding /Identity-H >>", cid)
|
||
if f == nil { return }
|
||
testing.expect(t, f.cff != nil && f.cff.is_cid, "a CID-keyed CFF was expected")
|
||
|
||
codes := fn_decode(f, "\x00\x14\x00\x0a\x00\x63")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 3, "three two-byte codes") { return }
|
||
testing.expect_value(t, codes[0].cid, 20)
|
||
testing.expect_value(t, codes[0].gid, 2)
|
||
testing.expect_value(t, codes[1].cid, 10)
|
||
testing.expect_value(t, codes[1].gid, 1)
|
||
testing.expect_value(t, codes[2].gid, 0) // CID 99 is in no charset entry
|
||
}
|
||
|
||
@(test)
|
||
test_font_opentype_cff :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /FontFile3 /OpenType with CFF outlines: an sfnt wrapper with a `CFF `
|
||
// table and no `glyf`. Both readers stay live -- the wrapper answers
|
||
// character lookups and the CFF draws -- so the font must end up with the
|
||
// CFF's matrix and a working glyph path.
|
||
cff := fn_cff(doc, []u16{34, 35}, false)
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'A', 1})
|
||
otf := fn_sfnt(doc, []Fn_Table{
|
||
{"head", fn_head(doc, 2048)},
|
||
{"maxp", fn_maxp(doc, 3)},
|
||
{"cmap", fn_cmap(doc, Fn_Cmap_Rec{3, 1, uni})},
|
||
{"CFF ", cff},
|
||
}, 0x4F54544F)
|
||
|
||
d := fn_dict(doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Otf /Encoding /WinAnsiEncoding >>")
|
||
desc := fn_dict(doc, "<< /Flags 32 >>")
|
||
dict_set(desc, "FontFile3", cf_stream(doc, "<< /Subtype /OpenType >>", otf))
|
||
dict_set(d, "FontDescriptor", desc)
|
||
f := fn_load(t, doc, d)
|
||
if f == nil { return }
|
||
testing.expect(t, f.ttf != nil, "the sfnt wrapper should have parsed")
|
||
testing.expect(t, f.cff != nil, "its CFF table should have parsed too")
|
||
testing.expect(t, !f.program_failed, "no failure expected")
|
||
// 1/2048 would be the sfnt's answer; the CFF's own matrix is the right one.
|
||
testing.expect_value(t, font_matrix(f).a, 0.001)
|
||
fn_expect_gid(t, f, 'A', 1, "through the sfnt cmap")
|
||
|
||
p := path_make()
|
||
defer path_destroy(&p)
|
||
testing.expect_value(t, font_glyph_path(f, 1, &p), Error.None)
|
||
testing.expect(t, !path_is_empty(&p), "the CFF charstring draws")
|
||
}
|
||
|
||
@(test)
|
||
test_font_program_sniffed_not_declared :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A bare CFF filed under /FontFile2, which says TrueType. The bytes are
|
||
// unambiguous and the dictionary is wrong; producers do this and every
|
||
// viewer believes the bytes.
|
||
prog := fn_cff(doc, []u16{34}, false)
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Lying /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, f.cff != nil, "the CFF should have been recognised by its header")
|
||
testing.expect(t, f.ttf == nil, "it is not a TrueType font")
|
||
fn_expect_gid(t, f, 'A', 1, "and its glyph is still reachable")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// A program that will not parse
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_corrupt_program_keeps_widths :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// The rule the whole file is built around: a font program that cannot be
|
||
// read costs the glyphs and nothing else. The widths, the encoding and the
|
||
// Unicode values all still have to come out, because the page still has to
|
||
// lay out and extract.
|
||
junk := fn_alloc(doc, 64)
|
||
for i in 0 ..< len(junk) {
|
||
junk[i] = u8(i * 7 + 3)
|
||
}
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Broken /FirstChar 65 " +
|
||
"/LastChar 66 /Widths [321 654] /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 /MissingWidth 7 >>", "FontFile2", junk)
|
||
if f == nil { return }
|
||
testing.expect(t, f.program_failed, "program_failed must be set")
|
||
testing.expect(t, !f.embedded, "nothing was embedded that could be read")
|
||
testing.expect(t, f.ttf == nil && f.cff == nil && f.t1 == nil, "no reader should be live")
|
||
fn_expect_width(t, f, 'A', 321, "widths survive a broken program")
|
||
fn_expect_width(t, f, 'B', 654, "widths survive a broken program")
|
||
fn_expect_width(t, f, 'C', 7, "and so does /MissingWidth")
|
||
testing.expect_value(t, f.enc_names['A'], "A")
|
||
|
||
codes := fn_decode(f, "AB")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 2, "two codes") { return }
|
||
testing.expect_value(t, codes[0].unicode, 'A')
|
||
testing.expect_value(t, codes[0].width, 321)
|
||
|
||
p := path_make()
|
||
defer path_destroy(&p)
|
||
testing.expect_value(t, font_glyph_path(f, 1, &p), Error.No_Glyph)
|
||
testing.expect(t, path_is_empty(&p), "nothing may be drawn")
|
||
testing.expect(t, doc.is_damaged, "a repair should have been recorded")
|
||
}
|
||
|
||
@(test)
|
||
test_font_empty_program :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
|
||
"<< /Flags 32 >>", "FontFile", nil)
|
||
if f == nil { return }
|
||
// No /FontFile at all is not a failure: it is a non-embedded font, and
|
||
// the flag has to distinguish the two or a caller cannot tell a missing
|
||
// program from a broken one.
|
||
testing.expect(t, !f.program_failed, "no program is not a failed program")
|
||
testing.expect(t, !f.embedded, "and it is not embedded either")
|
||
fn_expect_width(t, f, 'A', 667, "the AFM still measures it")
|
||
|
||
// A /FontFile that is present but decodes to nothing is a different case:
|
||
// the file said there was a program and there is not.
|
||
gd := fn_dict(doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
gdesc := fn_dict(doc, "<< /Flags 32 >>")
|
||
dict_set(gdesc, "FontFile", cf_stream(doc, "<< >>", nil))
|
||
dict_set(gd, "FontDescriptor", gdesc)
|
||
g := fn_load(t, doc, gd)
|
||
if g == nil { return }
|
||
testing.expect(t, g.program_failed, "an empty program is a failed one")
|
||
fn_expect_width(t, g, 'A', 667, "and the metrics still measure it")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Composite fonts
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(private = "file")
|
||
fn_type0 :: proc(t: ^testing.T, doc: ^Document, font_src: string, cid: ^Dict,
|
||
loc := #caller_location) -> ^Font {
|
||
d := fn_dict(doc, font_src)
|
||
a := new_array(doc.allocator)
|
||
array_push(a, cid)
|
||
dict_set(d, "DescendantFonts", a)
|
||
return fn_load(t, doc, d, loc)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_identity_h :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /BaseFont /X /Encoding /Identity-H " +
|
||
"/DescendantFonts [<< /Subtype /CIDFontType2 /DW 1000 >>] >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.kind == .Type0, "Type0")
|
||
testing.expect(t, !f.vertical, "Identity-H is horizontal")
|
||
|
||
// Two bytes per code, big-endian, and the CID is the code.
|
||
codes := fn_decode(f, "\x00\x41\x01\x02\xff")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 3, "two whole codes and a truncated one") { return }
|
||
testing.expect_value(t, codes[0].code, 0x0041)
|
||
testing.expect_value(t, codes[0].cid, 0x0041)
|
||
testing.expect_value(t, codes[0].gid, 0x0041)
|
||
testing.expect_value(t, codes[0].nbytes, 2)
|
||
testing.expect_value(t, codes[1].code, 0x0102)
|
||
testing.expect_value(t, codes[1].width, 1000)
|
||
// A trailing half-code still consumes the byte, or the caller loops.
|
||
testing.expect_value(t, codes[2].nbytes, 1)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_both_forms :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Both /W forms in one array: "c [w ...]" for CIDs 1 and 2, then
|
||
// "c_first c_last w" for 5..7. Everything else is /DW.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 1000 /W [1 [100 200] 5 7 300] >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_cid_width(f, 1), 100)
|
||
testing.expect_value(t, font_cid_width(f, 2), 200)
|
||
testing.expect_value(t, font_cid_width(f, 3), 1000)
|
||
testing.expect_value(t, font_cid_width(f, 4), 1000)
|
||
testing.expect_value(t, font_cid_width(f, 5), 300)
|
||
testing.expect_value(t, font_cid_width(f, 6), 300)
|
||
testing.expect_value(t, font_cid_width(f, 7), 300)
|
||
testing.expect_value(t, font_cid_width(f, 8), 1000)
|
||
testing.expect_value(t, font_cid_width(f, 0), 1000)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_overlapping :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Overlapping ranges are malformed but occur: a broad run with a narrow
|
||
// correction written over part of it. The correction is the range that
|
||
// starts later, and it is the one that has to win.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 50 /W [1 10 300 4 6 900] >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_cid_width(f, 3), 300)
|
||
testing.expect_value(t, font_cid_width(f, 4), 900)
|
||
testing.expect_value(t, font_cid_width(f, 6), 900)
|
||
testing.expect_value(t, font_cid_width(f, 7), 300)
|
||
testing.expect_value(t, font_cid_width(f, 11), 50)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_malformed :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A name where a number belongs, a range that runs backwards, and a CID
|
||
// past the 16-bit ceiling. None may be fatal and none may swallow the
|
||
// entries after them.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 7 /W [/Bogus 20 18 400 99999 [1] 30 [600]] >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_cid_width(f, 19), 400)
|
||
testing.expect_value(t, font_cid_width(f, 30), 600)
|
||
testing.expect_value(t, font_cid_width(f, 31), 7)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_cid_to_gid_stream :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /CIDToGIDMap as a stream of big-endian u16 indexed by CID.
|
||
m := fn_alloc(doc, 6)
|
||
fn_w16(m, 0, 0)
|
||
fn_w16(m, 2, 5)
|
||
fn_w16(m, 4, 7)
|
||
cid := fn_dict(doc, "<< /Subtype /CIDFontType2 /DW 1000 >>")
|
||
dict_set(cid, "CIDToGIDMap", cf_stream(doc, "<< >>", m))
|
||
f := fn_type0(t, doc, "<< /Type /Font /Subtype /Type0 /Encoding /Identity-H >>", cid)
|
||
if f == nil { return }
|
||
testing.expect_value(t, len(f.cid_to_gid), 3)
|
||
testing.expect_value(t, font_cid_gid(f, 0), 0)
|
||
testing.expect_value(t, font_cid_gid(f, 1), 5)
|
||
testing.expect_value(t, font_cid_gid(f, 2), 7)
|
||
// Past the end of the map the CID has no glyph -- the table is not an
|
||
// "identity from here on" promise.
|
||
testing.expect_value(t, font_cid_gid(f, 3), 0)
|
||
|
||
codes := fn_decode(f, "\x00\x02")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 1, "one code") { return }
|
||
testing.expect_value(t, codes[0].gid, 7)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_cid_to_gid_identity :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /CIDToGIDMap /Identity >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, len(f.cid_to_gid), 0)
|
||
testing.expect_value(t, font_cid_gid(f, 1234), 1234)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_vertical :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Identity-V: the writing mode comes from the CMap, not from the font
|
||
// dictionary. With no /W2 the displacement is DW2[1] and the position
|
||
// vector is (w0/2, DW2[0]).
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-V /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 800 >>] >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.vertical, "Identity-V is vertical")
|
||
testing.expect_value(t, f.default_w1, -1000)
|
||
testing.expect_value(t, f.default_vy, 880)
|
||
|
||
codes := fn_decode(f, "\x00\x41")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 1, "one code") { return }
|
||
testing.expect_value(t, codes[0].width, -1000)
|
||
testing.expect_value(t, codes[0].vx, 400)
|
||
testing.expect_value(t, codes[0].vy, 880)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w2 :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /W2's two forms. Its entries are triples: w1y, then the position
|
||
// vector. /DW2 replaces the defaults for everything they do not cover.
|
||
// The two runs are written out of order, which /W2 is allowed to be and
|
||
// which a binary search over them is not.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-V /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 500 /DW2 [700 -900] " +
|
||
"/W2 [5 6 -820 120 920 1 [-800 100 900 -810 110 910]] >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, f.default_vy, 700)
|
||
testing.expect_value(t, f.default_w1, -900)
|
||
|
||
w, vx, vy := font_cid_vmetric(f, 1, 500)
|
||
testing.expect_value(t, w, -800)
|
||
testing.expect_value(t, vx, 100)
|
||
testing.expect_value(t, vy, 900)
|
||
|
||
w, vx, vy = font_cid_vmetric(f, 2, 500)
|
||
testing.expect_value(t, w, -810)
|
||
testing.expect_value(t, vx, 110)
|
||
|
||
w, vx, vy = font_cid_vmetric(f, 6, 500)
|
||
testing.expect_value(t, w, -820)
|
||
testing.expect_value(t, vx, 120)
|
||
testing.expect_value(t, vy, 920)
|
||
|
||
// Uncovered: the /DW2 defaults, with the position vector at half the
|
||
// horizontal width.
|
||
w, vx, vy = font_cid_vmetric(f, 40, 500)
|
||
testing.expect_value(t, w, -900)
|
||
testing.expect_value(t, vx, 250)
|
||
testing.expect_value(t, vy, 700)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_embedded_cmap :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// An embedded CMap stream with a one-byte and a two-byte codespace. A
|
||
// composite font's codes are not all two bytes wide, and a decoder that
|
||
// assumes they are reads the rest of the string at the wrong offset.
|
||
src := `/CIDInit /ProcSet findresource begin
|
||
begincmap
|
||
2 begincodespacerange
|
||
<00> <7f>
|
||
<8140> <9ffc>
|
||
endcodespacerange
|
||
2 begincidrange
|
||
<20> <7e> 1
|
||
<8140> <8145> 100
|
||
endcidrange
|
||
endcmap
|
||
end`
|
||
d := fn_dict(doc, "<< /Type /Font /Subtype /Type0 /BaseFont /Mixed >>")
|
||
dict_set(d, "Encoding", cf_stream(doc, "<< >>", transmute([]byte)src))
|
||
a := new_array(doc.allocator)
|
||
array_push(a, fn_dict(doc, "<< /Subtype /CIDFontType2 /DW 1000 >>"))
|
||
dict_set(d, "DescendantFonts", a)
|
||
f := fn_load(t, doc, d)
|
||
if f == nil { return }
|
||
|
||
codes := fn_decode(f, "A\x81\x42B")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 3, "one byte, two bytes, one byte") { return }
|
||
testing.expect_value(t, codes[0].nbytes, 1)
|
||
testing.expect_value(t, codes[0].cid, 1 + 0x41 - 0x20)
|
||
testing.expect_value(t, codes[1].nbytes, 2)
|
||
testing.expect_value(t, codes[1].code, 0x8142)
|
||
testing.expect_value(t, codes[1].cid, 102)
|
||
testing.expect_value(t, codes[2].nbytes, 1)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_degrades :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// No /DescendantFonts and no /Encoding. Neither is recoverable, but the
|
||
// font still has to decode text: /ToUnicode does not need either of them,
|
||
// and extraction is the last thing to give up.
|
||
f := fn_font(t, doc, "<< /Type /Font /Subtype /Type0 /BaseFont /Nothing >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.cmap != nil, "Identity-H stands in for a missing /Encoding")
|
||
testing.expect(t, doc.is_damaged, "and the substitution is a repair")
|
||
|
||
codes := fn_decode(f, "\x00\x41\x00\x42")
|
||
defer delete(codes)
|
||
testing.expect_value(t, len(codes), 2)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_predefined_fallback :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A CJK CMap this library does not ship. cmap.odin substitutes a two-byte
|
||
// identity and flags it; what matters here is that the font still
|
||
// segments its string two bytes at a time instead of one.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /UniJIS-UCS2-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType0 /DW 1000 >>] >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.cmap != nil && f.cmap.predefined_fallback, "the fallback flag")
|
||
codes := fn_decode(f, "\x30\x42\x30\x44")
|
||
defer delete(codes)
|
||
testing.expect_value(t, len(codes), 2)
|
||
testing.expect_value(t, codes[0].code, 0x3042)
|
||
|
||
// A "-V" name is vertical even when the mapping itself is a substitute.
|
||
g := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /UniJIS-UCS2-V /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType0 >>] >>")
|
||
if g == nil { return }
|
||
testing.expect(t, g.vertical, "a -V CMap is vertical")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Type3 (9.6.4)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_type3_matrix :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A Type3 font's /FontMatrix is its own and is NOT 1/1000. This one draws
|
||
// in hundredths, which is a scale factor of ten against the thousandths
|
||
// every other font uses: honouring it is the difference between text at
|
||
// the right size and text ten times too small.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type3 /FontMatrix [0.01 0 0 0.01 0 0] " +
|
||
"/FontBBox [0 0 100 100] /FirstChar 97 /LastChar 98 /Widths [75 50] " +
|
||
"/CharProcs << >> /Encoding << /Differences [97 /square /tri] >> >>")
|
||
if f == nil { return }
|
||
testing.expect(t, font_is_type3(f), "font_is_type3")
|
||
m := font_matrix(f)
|
||
testing.expect_value(t, m.a, 0.01)
|
||
testing.expect_value(t, m.d, 0.01)
|
||
testing.expect_value(t, m.e, 0.0)
|
||
testing.expect_value(t, f.enc_names[97], "square")
|
||
testing.expect_value(t, f.enc_names[98], "tri")
|
||
// Widths are in that same glyph space, untouched: 75 of these units is
|
||
// 0.75 of a text space unit, which only the matrix can say.
|
||
fn_expect_width(t, f, 97, 75, "Type3 width")
|
||
fn_expect_width(t, f, 98, 50, "Type3 width")
|
||
fn_expect_width(t, f, 99, 0, "an unlisted Type3 code")
|
||
|
||
// A Type3 glyph is a content stream, so there is no outline to hand back.
|
||
p := path_make()
|
||
defer path_destroy(&p)
|
||
testing.expect_value(t, font_glyph_path(f, 97, &p), Error.No_Glyph)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type3_char_procs :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
procs := fn_dict(doc, "<< >>")
|
||
dict_set(procs, "square", cf_stream(doc, "<< >>", cf_bytes("0 0 10 10 re f")))
|
||
d := fn_dict(doc,
|
||
"<< /Type /Font /Subtype /Type3 /FontMatrix [0.001 0 0 0.001 0 0] " +
|
||
"/Encoding << /Differences [65 /square] >> >>")
|
||
dict_set(d, "CharProcs", procs)
|
||
dict_set(d, "Resources", fn_dict(doc, "<< /ProcSet [/PDF] >>"))
|
||
f := fn_load(t, doc, d)
|
||
if f == nil { return }
|
||
|
||
testing.expect(t, f.char_procs != nil, "/CharProcs")
|
||
testing.expect(t, f.t3_resources != nil, "/Resources")
|
||
// The interpreter finds a glyph by the NAME the encoding gives its code,
|
||
// which is the only route a Type3 font has.
|
||
testing.expect(t, font_type3_proc(f, 65) != nil, "code 65 is /square")
|
||
testing.expect(t, font_type3_proc(f, 66) == nil, "code 66 names nothing")
|
||
testing.expect(t, font_type3_proc(f, 9999) == nil, "an impossible code")
|
||
|
||
// A Type3 font has no program to index, so the code stands in as the
|
||
// glyph id: the interpreter still has to carry something through to the
|
||
// device, and the code is the only identifier there is.
|
||
codes := fn_decode(f, "AB")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 2, "two codes") { return }
|
||
testing.expect_value(t, codes[0].gid, 65)
|
||
testing.expect_value(t, codes[1].gid, 66)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type3_bad_matrix :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A singular /FontMatrix would collapse every glyph to a line. It is not
|
||
// recoverable as written, so the usual thousandths stand in and a repair
|
||
// is recorded.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type3 /FontMatrix [0 0 0 0 0 0] /CharProcs << >> >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_matrix(f).a, 0.001)
|
||
testing.expect(t, doc.is_damaged, "a repair should have been recorded")
|
||
|
||
// A /FontMatrix that is too short is the same case.
|
||
g := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type3 /FontMatrix [0.01 0 0] /CharProcs << >> >>")
|
||
if g == nil { return }
|
||
testing.expect_value(t, font_matrix(g).a, 0.001)
|
||
}
|
||
|
||
@(test)
|
||
test_font_kind_guessed :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A font dictionary with no /Subtype is malformed and perfectly readable:
|
||
// its other keys say what it is.
|
||
f := fn_font(t, doc, "<< /Type /Font /CharProcs << >> /FontMatrix [1 0 0 1 0 0] >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.kind == .Type3, "/CharProcs makes it Type3")
|
||
testing.expect_value(t, font_matrix(f).a, 1.0)
|
||
|
||
g := fn_font(t, doc,
|
||
"<< /Type /Font /DescendantFonts [<< /Subtype /CIDFontType2 >>] >>")
|
||
if g == nil { return }
|
||
testing.expect(t, g.kind == .Type0, "/DescendantFonts makes it Type0")
|
||
|
||
h := fn_font(t, doc, "<< /Type /Font /BaseFont /Helvetica >>")
|
||
if h == nil { return }
|
||
testing.expect(t, h.kind == .Type1, "and a simple font is the default")
|
||
fn_expect_width(t, h, 'A', 667, "which still measures")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Decoding
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_is_space :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Word spacing applies to a single-byte code 32 and to nothing else
|
||
// (9.3.3). A two-byte 0x0020 is an ordinary CID, and taking it for a
|
||
// space stretches every CJK line that happens to contain one.
|
||
simple := fn_font(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
if simple == nil { return }
|
||
sc := fn_decode(simple, "A B")
|
||
defer delete(sc)
|
||
if !testing.expect(t, len(sc) == 3, "three codes") { return }
|
||
testing.expect(t, !sc[0].is_space, "A is not a space")
|
||
testing.expect(t, sc[1].is_space, "a one-byte 32 IS a space")
|
||
testing.expect(t, !sc[2].is_space, "B is not a space")
|
||
|
||
comp := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 >>] >>")
|
||
if comp == nil { return }
|
||
cc := fn_decode(comp, "\x00\x20\x20\x20")
|
||
defer delete(cc)
|
||
if !testing.expect(t, len(cc) == 2, "two two-byte codes") { return }
|
||
testing.expect_value(t, cc[0].code, 0x0020)
|
||
testing.expect(t, !cc[0].is_space, "a two-byte 32 is NOT a space")
|
||
testing.expect(t, !cc[1].is_space, "nor is 0x2020")
|
||
|
||
// The half of 9.3.3 that the rule is actually written for: word spacing
|
||
// applies to "a composite font that defines code 32 as a single-byte
|
||
// code" too. The test above passes just as well if the code keys on the
|
||
// font being composite; only this one pins it to the CODE's width, which
|
||
// is what the format says. A one-byte codespace is what every
|
||
// /Encoding-with-a-one-byte-range CMap gives, and a Type0 font over such
|
||
// a CMap has to justify its spaces like any other.
|
||
src := `begincmap
|
||
1 begincodespacerange
|
||
<00> <ff>
|
||
endcodespacerange
|
||
1 begincidrange
|
||
<00> <ff> 0
|
||
endcidrange
|
||
endcmap`
|
||
d := fn_dict(doc, "<< /Type /Font /Subtype /Type0 /BaseFont /OneByte >>")
|
||
dict_set(d, "Encoding", cf_stream(doc, "<< >>", transmute([]byte)src))
|
||
a := new_array(doc.allocator)
|
||
array_push(a, fn_dict(doc, "<< /Subtype /CIDFontType2 /DW 500 >>"))
|
||
dict_set(d, "DescendantFonts", a)
|
||
one := fn_load(t, doc, d)
|
||
if one == nil { return }
|
||
oc := fn_decode(one, "A B")
|
||
defer delete(oc)
|
||
if !testing.expect(t, len(oc) == 3, "three one-byte codes") { return }
|
||
testing.expect_value(t, oc[1].nbytes, 1)
|
||
testing.expect(t, !oc[0].is_space, "A is not a space")
|
||
testing.expect(t, oc[1].is_space, "a ONE-byte 32 is a space even in a Type0 font")
|
||
testing.expect(t, !oc[2].is_space, "B is not a space")
|
||
}
|
||
|
||
@(test)
|
||
test_font_decode_fills_everything :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /FirstChar 65 " +
|
||
"/LastChar 66 /Widths [123 456] >>")
|
||
if f == nil { return }
|
||
codes := fn_decode(f, "AB")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 2, "two codes") { return }
|
||
testing.expect_value(t, codes[0].code, 'A')
|
||
testing.expect_value(t, codes[0].cid, 'A')
|
||
testing.expect_value(t, codes[0].nbytes, 1)
|
||
testing.expect_value(t, codes[0].width, 123)
|
||
testing.expect_value(t, codes[0].unicode, 'A')
|
||
testing.expect_value(t, codes[1].width, 456)
|
||
testing.expect_value(t, codes[1].unicode, 'B')
|
||
}
|
||
|
||
@(test)
|
||
test_font_decode_clears_output :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
f := fn_font(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
if f == nil { return }
|
||
out := make([dynamic]Font_Code, 0, 8)
|
||
defer delete(out)
|
||
|
||
font_decode(f, cf_bytes("ABC"), &out)
|
||
testing.expect_value(t, len(out), 3)
|
||
// The buffer is meant to be reused across every string on a page, so the
|
||
// second call must not append to the first.
|
||
font_decode(f, cf_bytes("X"), &out)
|
||
testing.expect_value(t, len(out), 1)
|
||
testing.expect_value(t, out[0].code, 'X')
|
||
font_decode(f, nil, &out)
|
||
testing.expect_value(t, len(out), 0)
|
||
}
|
||
|
||
@(test)
|
||
test_font_to_unicode_wins :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /ToUnicode is the producer saying what it meant, and it beats the glyph
|
||
// name: a subset font's names are frequently lies and its /ToUnicode is
|
||
// the only thing that makes the text extractable.
|
||
tu := `begincmap
|
||
1 begincodespacerange <00> <ff> endcodespacerange
|
||
2 beginbfchar
|
||
<41> <2660>
|
||
<42> <0066006600690065>
|
||
endbfchar
|
||
endcmap`
|
||
d := fn_dict(doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
dict_set(d, "ToUnicode", cf_stream(doc, "<< >>", transmute([]byte)tu))
|
||
f := fn_load(t, doc, d)
|
||
if f == nil { return }
|
||
testing.expect(t, f.to_unicode != nil, "/ToUnicode should have loaded")
|
||
|
||
codes := fn_decode(f, "ABC")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 3, "three codes") { return }
|
||
testing.expect_value(t, codes[0].unicode, '♠')
|
||
// A code that stands for several characters reports the first of them;
|
||
// the whole string is cmap_lookup_text's to give.
|
||
testing.expect_value(t, codes[1].unicode, 'f')
|
||
// A code /ToUnicode says nothing about falls back to the glyph name.
|
||
testing.expect_value(t, codes[2].unicode, 'C')
|
||
}
|
||
|
||
@(test)
|
||
test_font_unicode_routes :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// With no /ToUnicode the glyph name is the route, through the AGL. A
|
||
// "uniXXXX" name works the same way, and a name that means nothing at all
|
||
// leaves the code with no character rather than a guessed one.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding " +
|
||
"<< /Differences [65 /Alpha 66 /uni0416 67 /g42] >> >>")
|
||
if f == nil { return }
|
||
codes := fn_decode(f, "ABC")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 3, "three codes") { return }
|
||
testing.expect_value(t, codes[0].unicode, 'Α')
|
||
testing.expect_value(t, codes[1].unicode, 'Ж')
|
||
// "g42" is not a name the AGL knows. The code is a printable ASCII byte
|
||
// in a non-symbolic font, which is the last-resort reading.
|
||
testing.expect_value(t, codes[2].unicode, 'C')
|
||
}
|
||
|
||
@(test)
|
||
test_font_symbolic_unicode_not_guessed :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// The ASCII last resort must not fire for a symbolic font: code 0x41 in
|
||
// a dingbat font is a dingbat, and calling it "A" puts nonsense into
|
||
// extracted text rather than leaving a gap that a reader can see.
|
||
sym := fn_cmap4(doc, Fn_Seg{0xF041, 0xF041, 3})
|
||
prog := fn_ttf(doc, 6, 1000, []Fn_Cmap_Rec{{3, 0, sym}})
|
||
// The name is deliberately not one std_font_lookup recognises: matching
|
||
// ZapfDingbats would hand the font that AFM's encoding and the code WOULD
|
||
// then have a name, which is a different test.
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Icons >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
codes := fn_decode(f, "A")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 1, "one code") { return }
|
||
testing.expect_value(t, codes[0].gid, 3)
|
||
testing.expect_value(t, codes[0].unicode, rune(-1))
|
||
}
|
||
|
||
@(test)
|
||
test_font_unicode_from_post_names :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A symbolic font with no PDF encoding still has names in its `post`
|
||
// table, and they are the only thing that can make its text extractable.
|
||
sym := fn_cmap4(doc, Fn_Seg{0xF041, 0xF041, 2})
|
||
post := fn_post2(doc, []string{".notdef", "one", "Sigma"})
|
||
prog := fn_ttf(doc, 3, 1000, []Fn_Cmap_Rec{{3, 0, sym}}, post)
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Sym >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
codes := fn_decode(f, "A")
|
||
defer delete(codes)
|
||
if !testing.expect(t, len(codes) == 1, "one code") { return }
|
||
testing.expect_value(t, codes[0].gid, 2)
|
||
testing.expect_value(t, codes[0].unicode, rune(0x03A3))
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Caching, arguments and clamping
|
||
// ---------------------------------------------------------------------------
|
||
|
||
@(test)
|
||
test_font_cached_on_document :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A page that selects the same font two hundred times must parse its
|
||
// embedded program once, so the cache is keyed by the dictionary itself.
|
||
d := fn_dict(doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
a := fn_load(t, doc, d)
|
||
b := fn_load(t, doc, d)
|
||
testing.expect(t, a == b && a != nil, "the same dictionary must yield the same Font")
|
||
|
||
other := fn_dict(doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
c := fn_load(t, doc, other)
|
||
testing.expect(t, c != a, "a different dictionary is a different font")
|
||
}
|
||
|
||
@(test)
|
||
test_font_nil_arguments :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
_, e1 := font_load(nil, nil)
|
||
testing.expect_value(t, e1, Error.Invalid_Argument)
|
||
_, e2 := font_load(doc, nil)
|
||
testing.expect_value(t, e2, Error.Invalid_Argument)
|
||
|
||
testing.expect_value(t, font_matrix(nil).a, 0.001)
|
||
testing.expect(t, !font_is_type3(nil), "nil is not a Type3 font")
|
||
testing.expect(t, font_type3_proc(nil, 0) == nil, "nil has no char procs")
|
||
|
||
p := path_make()
|
||
defer path_destroy(&p)
|
||
testing.expect_value(t, font_glyph_path(nil, 0, &p), Error.Invalid_Argument)
|
||
|
||
f := fn_font(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
|
||
testing.expect_value(t, font_glyph_path(f, 0, nil), Error.Invalid_Argument)
|
||
// A glyph index wider than the u16 every program indexes with.
|
||
testing.expect_value(t, font_glyph_path(f, 0x10000, &p), Error.No_Glyph)
|
||
|
||
// A nil output buffer is a caller mistake, not a crash.
|
||
font_decode(f, cf_bytes("A"), nil)
|
||
out := make([dynamic]Font_Code, 0, 4)
|
||
defer delete(out)
|
||
font_decode(nil, cf_bytes("A"), &out)
|
||
testing.expect_value(t, len(out), 0)
|
||
}
|
||
|
||
@(test)
|
||
test_font_width_clamping :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A width from a file is attacker-controlled and ends up multiplied into
|
||
// the text matrix. Nothing below the device line may see an infinity, so
|
||
// an absurd width is clamped rather than carried.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Whatever /FirstChar 65 " +
|
||
"/LastChar 67 /Widths [1e300 -1e300 12] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, f.code_width['A'], f64(MAX_GLYPH_WIDTH))
|
||
testing.expect_value(t, f.code_width['B'], -f64(MAX_GLYPH_WIDTH))
|
||
testing.expect_value(t, f.code_width['C'], 12)
|
||
testing.expect(t, is_finite(f.code_width['A']), "no infinity may survive")
|
||
|
||
g := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 1e300 /W [1 1 -1e300] >>] >>")
|
||
if g == nil { return }
|
||
testing.expect_value(t, font_cid_width(g, 1), -f64(MAX_GLYPH_WIDTH))
|
||
testing.expect_value(t, font_cid_width(g, 2), f64(MAX_GLYPH_WIDTH))
|
||
}
|
||
|
||
@(test)
|
||
test_font_descriptor_flags :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// 9.8.2, table 121: FixedPitch is bit 1, Serif bit 2, Symbolic bit 3,
|
||
// Nonsymbolic bit 6, Italic bit 7. Producers set Symbolic and Nonsymbolic
|
||
// together often enough that the pair has to be read as a whole, and a
|
||
// font claiming both is claiming nothing.
|
||
f := fn_simple(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /X >>",
|
||
"<< /Flags 67 >>", "", nil) // 1 | 2 | 64
|
||
if f == nil { return }
|
||
testing.expect(t, f.fixed_pitch, "FixedPitch")
|
||
testing.expect(t, f.serif, "Serif")
|
||
testing.expect(t, f.italic, "Italic")
|
||
testing.expect(t, !f.symbolic, "not symbolic")
|
||
|
||
g := fn_simple(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /X >>",
|
||
"<< /Flags 4 >>", "", nil)
|
||
if g == nil { return }
|
||
testing.expect(t, g.symbolic, "Symbolic alone")
|
||
|
||
h := fn_simple(t, doc, "<< /Type /Font /Subtype /Type1 /BaseFont /X >>",
|
||
"<< /Flags 36 >>", "", nil) // 4 | 32
|
||
if h == nil { return }
|
||
testing.expect(t, !h.symbolic, "Symbolic and Nonsymbolic together is neither")
|
||
}
|
||
|
||
@(test)
|
||
test_font_symbolic_truetype_unicode_only_cmap :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A subset that sets the Symbolic flag and then ships a (3,1) cmap and
|
||
// nothing else. It is one of the most common shapes there is -- TeX and
|
||
// several web-to-PDF tools emit it -- and both prescribed routes are dead
|
||
// ends for it: there is no (3,0), no (1,0), and no /Encoding to give the
|
||
// code a name. Reading the code as a character is what is left.
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'C', 11})
|
||
prog := fn_ttf(doc, 20, 1000, []Fn_Cmap_Rec{{3, 1, uni}})
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Subset >>",
|
||
"<< /Flags 4 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, f.symbolic, "the descriptor said symbolic")
|
||
fn_expect_gid(t, f, 'A', 11, "the code read as a character")
|
||
fn_expect_gid(t, f, 'C', 13, "likewise")
|
||
// The fallback is a last resort, not a licence: a code the cmap does not
|
||
// have is still nothing, and NOT a glyph index (the font has a cmap).
|
||
fn_expect_gid(t, f, 'D', 0, "no glyph, and not a glyph index either")
|
||
}
|
||
|
||
@(test)
|
||
test_font_cid_keyed_cff_as_simple_font :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A CID-keyed CFF embedded under a /Type1 dictionary. It has no glyph
|
||
// names and no encoding of its own (CFF 12), so the name route and the
|
||
// built-in-encoding route both find nothing; reading the code as a CID
|
||
// through the charset is the only thing that recovers the glyphs, and
|
||
// without it the page comes out blank.
|
||
prog := fn_cff(doc, []u16{65, 66, 67}, true)
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Odd /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 4 >>", "FontFile3", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, f.cff != nil && f.cff.is_cid, "a CID-keyed CFF")
|
||
fn_expect_gid(t, f, 'A', 1, "code 65 is CID 65, which the charset puts at glyph 1")
|
||
fn_expect_gid(t, f, 'C', 3, "code 67 likewise")
|
||
fn_expect_gid(t, f, 'D', 0, "and a code the charset has no CID for")
|
||
}
|
||
|
||
@(test)
|
||
test_font_sfnt_under_a_type1c_subtype :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /FontFile3 with /Subtype /Type1C holding an sfnt-wrapped OpenType font.
|
||
// The dictionary is wrong and the four magic bytes are not: believing the
|
||
// subtype here would hand an sfnt to the CFF reader, which would reject
|
||
// it and lose a font that is perfectly readable.
|
||
cff := fn_cff(doc, []u16{34}, false)
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'A', 1})
|
||
otf := fn_sfnt(doc, []Fn_Table{
|
||
{"head", fn_head(doc, 1000)},
|
||
{"maxp", fn_maxp(doc, 2)},
|
||
{"cmap", fn_cmap(doc, Fn_Cmap_Rec{3, 1, uni})},
|
||
{"CFF ", cff},
|
||
}, 0x4F54544F)
|
||
|
||
d := fn_dict(doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Mislabelled /Encoding /WinAnsiEncoding >>")
|
||
desc := fn_dict(doc, "<< /Flags 32 >>")
|
||
dict_set(desc, "FontFile3", cf_stream(doc, "<< /Subtype /Type1C >>", otf))
|
||
dict_set(d, "FontDescriptor", desc)
|
||
f := fn_load(t, doc, d)
|
||
if f == nil { return }
|
||
testing.expect(t, f.ttf != nil, "the sfnt wrapper should have parsed")
|
||
testing.expect(t, f.cff != nil, "and so should its CFF table")
|
||
testing.expect(t, !f.program_failed, "nothing failed here")
|
||
fn_expect_gid(t, f, 'A', 1, "and the glyph is reachable")
|
||
}
|
||
|
||
@(test)
|
||
test_font_missing_width_is_the_last_word :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /MissingWidth is consulted before the embedded program's own advances:
|
||
// a descriptor that carries one is the file being explicit about codes it
|
||
// did not list, and the program's hmtx is the guess made when it is not.
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'A', 1})
|
||
prog := fn_ttf(doc, 3, 1000, []Fn_Cmap_Rec{{3, 1, uni}}, nil, []u16{0, 900, 900})
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Explicit /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 /MissingWidth 42 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
fn_expect_width(t, f, 'A', 42, "/MissingWidth beats the hmtx advance")
|
||
|
||
// Without one, the program's advance is what is left.
|
||
g := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Implicit /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 >>", "FontFile2", prog)
|
||
if g == nil { return }
|
||
fn_expect_width(t, g, 'A', 900, "and without one the hmtx advance is")
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Type1 programs
|
||
//
|
||
// A Type1 font is a PostScript header, the token `eexec`, and an enciphered
|
||
// Private dictionary whose /CharStrings are themselves enciphered. Both
|
||
// ciphers are four lines each, so the font below is assembled rather than
|
||
// pasted in as a byte literal: what is under test is the dictionary's use of
|
||
// the program's built-in encoding, and a literal would hide which byte was
|
||
// which.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// fn_t1_encrypt is the Type 1 cipher (Type 1 spec, 7.3), used with key 55665
|
||
// for the eexec block and 4330 for a charstring. `pad` random bytes are
|
||
// prefixed to the plaintext, which is what the decipherer drops.
|
||
@(private = "file")
|
||
fn_t1_encrypt :: proc(doc: ^Document, plain: []byte, key: u16, pad: int) -> []byte {
|
||
out := fn_alloc(doc, len(plain) + pad)
|
||
r := u32(key)
|
||
for i in 0 ..< len(out) {
|
||
// A padding byte that is not a hex digit keeps the eexec block from
|
||
// being mistaken for the hex-ASCII form a .pfa uses.
|
||
p := u8(0x7A) if i < pad else plain[i - pad]
|
||
c := p ~ u8(r >> 8)
|
||
out[i] = c
|
||
r = ((u32(c) + r) * 52845 + 22719) & 0xFFFF
|
||
}
|
||
return out
|
||
}
|
||
|
||
// FN_T1_CS is "0 500 hsbw 50 50 rmoveto closepath endchar": a glyph 500 units
|
||
// wide that draws a single point. Nine bytes, thirteen once the four bytes of
|
||
// /lenIV padding are enciphered in front of it.
|
||
@(private = "file")
|
||
FN_T1_CS := [?]u8{0x8B, 0xF8, 0x88, 0x0D, 0xBD, 0xBD, 0x15, 0x09, 0x0E}
|
||
|
||
// fn_type1 builds a font program with three glyphs -- .notdef, Aacute and
|
||
// beta -- and a built-in encoding putting the last two on codes 65 and 66.
|
||
// Its /FontMatrix is 2000 units per em rather than the usual 1000, so a
|
||
// caller that assumes thousandths gets visibly wrong widths.
|
||
@(private = "file")
|
||
fn_type1 :: proc(doc: ^Document) -> []byte {
|
||
cs := fn_t1_encrypt(doc, FN_T1_CS[:], 4330, 4)
|
||
|
||
priv := make([dynamic]u8, 0, 256, doc.allocator)
|
||
append_string(&priv, "dup /Private 8 dict dup begin\n/lenIV 4 def\nend\n")
|
||
append_string(&priv, "/CharStrings 3 dict dup begin\n")
|
||
for name in ([]string{"/.notdef ", "/Aacute ", "/beta "}) {
|
||
append_string(&priv, name, "13 RD ")
|
||
append(&priv, ..cs)
|
||
append_string(&priv, " ND\n")
|
||
}
|
||
append_string(&priv, "end\nend\n")
|
||
|
||
body := fn_t1_encrypt(doc, priv[:], 55665, 4)
|
||
out := make([dynamic]u8, 0, 512, doc.allocator)
|
||
append_string(&out,
|
||
"%!PS-AdobeFont-1.0: Test 001.001\n" +
|
||
"/FontName /Test def\n" +
|
||
"/FontMatrix [0.0005 0 0 0.0005 0 0] readonly def\n" +
|
||
"/Encoding 256 array\n" +
|
||
"0 1 255 {1 index exch /.notdef put} for\n" +
|
||
"dup 65 /Aacute put\n" +
|
||
"dup 66 /beta put\n" +
|
||
"readonly def\n" +
|
||
"currentdict end\n" +
|
||
"currentfile eexec ")
|
||
append(&out, ..body)
|
||
append_string(&out, "\n0000000000000000\ncleartomark\n")
|
||
return out[:]
|
||
}
|
||
|
||
@(test)
|
||
test_font_type1_builtin_encoding :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// With no /Encoding in the font dictionary, the program's own encoding is
|
||
// the encoding. A font that ignored it would put StandardEncoding's names
|
||
// on the codes and find neither of this font's two glyphs.
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Test >>",
|
||
"<< /Flags 4 >>", "FontFile", fn_type1(doc))
|
||
if f == nil { return }
|
||
testing.expect(t, f.t1 != nil, "the Type1 program should have parsed")
|
||
testing.expect(t, !f.program_failed && f.embedded, "and it is embedded")
|
||
testing.expect_value(t, f.enc_names[65], "Aacute")
|
||
testing.expect_value(t, f.enc_names[66], "beta")
|
||
fn_expect_gid(t, f, 65, 1, "Aacute is the second charstring")
|
||
fn_expect_gid(t, f, 66, 2, "beta is the third")
|
||
|
||
// 2000 units per em, and the program's own advances have to come back in
|
||
// the thousandths a PDF width is in: 500 of these units is a quarter em,
|
||
// which is 250 of those.
|
||
testing.expect_value(t, font_matrix(f).a, 0.0005)
|
||
fn_expect_width(t, f, 65, 250, "hsbw width through the FontMatrix")
|
||
|
||
p := path_make()
|
||
defer path_destroy(&p)
|
||
testing.expect_value(t, font_glyph_path(f, 1, &p), Error.None)
|
||
testing.expect(t, !path_is_empty(&p), "the charstring draws a point")
|
||
}
|
||
|
||
@(test)
|
||
test_font_type1_differences_over_builtin :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /Differences is applied over the program's built-in encoding, not over
|
||
// StandardEncoding: code 65 becomes beta, and code 66 keeps the built-in
|
||
// name the program gave it.
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Test /Encoding " +
|
||
"<< /Differences [65 /beta] >> >>",
|
||
"<< /Flags 4 >>", "FontFile", fn_type1(doc))
|
||
if f == nil { return }
|
||
testing.expect_value(t, f.enc_names[65], "beta")
|
||
testing.expect_value(t, f.enc_names[66], "beta")
|
||
fn_expect_gid(t, f, 65, 2, "/Differences moved beta onto code 65")
|
||
fn_expect_gid(t, f, 66, 2, "and the built-in encoding still names 66")
|
||
|
||
// A /BaseEncoding replaces the built-in one wholesale, and this font has
|
||
// none of StandardEncoding's names, so nothing resolves.
|
||
g := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Test " +
|
||
"/Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 4 >>", "FontFile", fn_type1(doc))
|
||
if g == nil { return }
|
||
testing.expect_value(t, g.enc_names[65], "A")
|
||
fn_expect_gid(t, g, 65, 0, "the font has no glyph called A")
|
||
}
|
||
|
||
// FN_GLYF_SQUARE is one contour of four on-curve points at (0,0) (100,0)
|
||
// (100,100) (0,100). The same 22 bytes font_truetype_test.odin builds its
|
||
// outlines from; here they exist only so that there is a real outline to
|
||
// route a font_glyph_path call to.
|
||
@(private = "file")
|
||
FN_GLYF_SQUARE := [?]u8{
|
||
0x00, 0x01,
|
||
0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x64,
|
||
0x00, 0x03,
|
||
0x00, 0x00,
|
||
0x31, 0x33, 0x35, 0x21,
|
||
0x64, 0xFF, 0x9C,
|
||
0x64,
|
||
}
|
||
|
||
@(test)
|
||
test_font_glyph_path_truetype :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A font with real `glyf` outlines has to be drawn from them. The two
|
||
// readers a Font can hold are not interchangeable, and reaching for the
|
||
// wrong one draws nothing at all.
|
||
loca := fn_alloc(doc, 6)
|
||
fn_w16(loca, 0, 0)
|
||
fn_w16(loca, 2, 0)
|
||
fn_w16(loca, 4, u16(len(FN_GLYF_SQUARE) / 2)) // short loca stores halves
|
||
uni := fn_cmap4(doc, Fn_Seg{'A', 'A', 1})
|
||
prog := fn_sfnt(doc, []Fn_Table{
|
||
{"head", fn_head(doc, 1000)},
|
||
{"maxp", fn_maxp(doc, 2)},
|
||
{"loca", loca},
|
||
{"glyf", FN_GLYF_SQUARE[:]},
|
||
{"cmap", fn_cmap(doc, Fn_Cmap_Rec{3, 1, uni})},
|
||
})
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /TrueType /BaseFont /Sq /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 >>", "FontFile2", prog)
|
||
if f == nil { return }
|
||
fn_expect_gid(t, f, 'A', 1, "A is glyph 1")
|
||
|
||
p := path_make()
|
||
defer path_destroy(&p)
|
||
testing.expect_value(t, font_glyph_path(f, 1, &p), Error.None)
|
||
testing.expect(t, len(p.points) >= 4, "the square has four points")
|
||
testing.expect_value(t, p.points[0], Point{0, 0})
|
||
// Glyph space, untouched by the font matrix: 100 font units, not 0.1.
|
||
testing.expect_value(t, p.points[1], Point{100, 0})
|
||
|
||
// A glyph the font does not have draws nothing rather than failing.
|
||
path_clear(&p)
|
||
testing.expect_value(t, font_glyph_path(f, 9, &p), Error.No_Glyph)
|
||
}
|
||
|
||
@(test)
|
||
test_font_descendant_written_directly :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// 9.7.6.1 requires /DescendantFonts to be a one-element array. Producers
|
||
// write the dictionary itself, and it means the same thing: refusing it
|
||
// would cost every width and every glyph in the font.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"<< /Subtype /CIDFontType2 /DW 640 /W [3 3 900] >> >>")
|
||
if f == nil { return }
|
||
testing.expect(t, f.descendant != nil, "the descendant should have been found")
|
||
testing.expect_value(t, font_cid_width(f, 1), 640)
|
||
testing.expect_value(t, font_cid_width(f, 3), 900)
|
||
}
|
||
|
||
@(test)
|
||
test_font_cff_font_matrix_and_advance :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A CFF that declares its own FontMatrix. The 1/1000 default is what all
|
||
// but a handful of fonts use, which is exactly why the handful have to be
|
||
// honoured: at 2000 units per em, a font drawn through thousandths comes
|
||
// out twice too big, and the charstring's own advance -- which is in those
|
||
// same units -- has to be halved on its way into a PDF width.
|
||
prog := fn_cff(doc, []u16{34, 35}, false, true)
|
||
f := fn_simple(t, doc,
|
||
"<< /Type /Font /Subtype /Type1 /BaseFont /Wide /Encoding /WinAnsiEncoding >>",
|
||
"<< /Flags 32 >>", "FontFile3", prog)
|
||
if f == nil { return }
|
||
testing.expect(t, f.cff != nil, "the CFF should have parsed")
|
||
testing.expect_value(t, font_matrix(f).a, 0.0005)
|
||
testing.expect_value(t, font_matrix(f).d, 0.0005)
|
||
// The charstring declares 400 glyph-space units, which is a fifth of this
|
||
// font's em, which is 200 thousandths.
|
||
fn_expect_width(t, f, 'A', 200, "charstring advance through the FontMatrix")
|
||
}
|
||
|
||
@(test)
|
||
test_font_widths_array_clamped :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A /Widths array longer than the 256 codes that exist. The length came
|
||
// out of the file, so it is clamped rather than believed; the entries
|
||
// that can actually be indexed still have to be the right ones.
|
||
a := new_array(doc.allocator, 512)
|
||
for i in 0 ..< 400 {
|
||
array_push(a, i64(1000 + i))
|
||
}
|
||
d := fn_dict(doc, "<< /Type /Font /Subtype /Type1 /BaseFont /Long /FirstChar 0 >>")
|
||
dict_set(d, "Widths", a)
|
||
f := fn_load(t, doc, d)
|
||
if f == nil { return }
|
||
testing.expect_value(t, len(f.widths), 256)
|
||
fn_expect_width(t, f, 0, 1000, "the first entry")
|
||
fn_expect_width(t, f, 255, 1255, "the last code that exists")
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_out_of_order :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// /W is not required to be sorted and files do write it backwards. The
|
||
// lookup is a binary search, so the entries have to be put in order first
|
||
// or every width past the first is wrong.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 5 /W [90 92 900 50 52 500 10 12 100] >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_cid_width(f, 11), 100)
|
||
testing.expect_value(t, font_cid_width(f, 51), 500)
|
||
testing.expect_value(t, font_cid_width(f, 91), 900)
|
||
testing.expect_value(t, font_cid_width(f, 60), 5)
|
||
testing.expect_value(t, font_cid_width(f, 9), 5)
|
||
testing.expect_value(t, font_cid_width(f, 93), 5)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_broad_range_split :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// A narrow correction written inside a broad run splits it in three: the
|
||
// part before, the correction, and the part after. Dropping the part after
|
||
// -- the easy mistake, since it is the half that is not adjacent to
|
||
// anything -- silently gives every CID above the correction the default
|
||
// width, which is most of the font.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 7 /W [1 100 300 50 50 900] >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_cid_width(f, 1), 300)
|
||
testing.expect_value(t, font_cid_width(f, 49), 300)
|
||
testing.expect_value(t, font_cid_width(f, 50), 900)
|
||
testing.expect_value(t, font_cid_width(f, 51), 300)
|
||
testing.expect_value(t, font_cid_width(f, 100), 300)
|
||
testing.expect_value(t, font_cid_width(f, 101), 7)
|
||
testing.expect_value(t, font_cid_width(f, 0), 7)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_many_overrides :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// One run over the whole CID space with forty single-CID corrections
|
||
// written inside it -- a perfectly legal way to say "everything is 300
|
||
// except these". The answer for a CID above all forty is still 300, and
|
||
// it lies forty entries behind where a search by starting CID lands: any
|
||
// resolution that scans back a fixed distance from there gets it wrong.
|
||
a := new_array(doc.allocator, 256)
|
||
array_push(a, i64(0))
|
||
array_push(a, i64(2000))
|
||
array_push(a, i64(300))
|
||
for c in 1 ..= 40 {
|
||
array_push(a, i64(c))
|
||
array_push(a, i64(c))
|
||
array_push(a, i64(900))
|
||
}
|
||
cid := fn_dict(doc, "<< /Subtype /CIDFontType2 /DW 5 >>")
|
||
dict_set(cid, "W", a)
|
||
f := fn_type0(t, doc, "<< /Type /Font /Subtype /Type0 /Encoding /Identity-H >>", cid)
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_cid_width(f, 7), 900)
|
||
testing.expect_value(t, font_cid_width(f, 40), 900)
|
||
testing.expect_value(t, font_cid_width(f, 41), 300)
|
||
testing.expect_value(t, font_cid_width(f, 500), 300)
|
||
testing.expect_value(t, font_cid_width(f, 2000), 300)
|
||
testing.expect_value(t, font_cid_width(f, 2001), 5)
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w2_out_of_order :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// The same treatment for /W2: written backwards, with a correction inside
|
||
// a broad run.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-V /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 500 /W2 [20 20 -700 200 750 1 100 -900 50 880] >>] >>")
|
||
if f == nil { return }
|
||
w, vx, _ := font_cid_vmetric(f, 20, 500)
|
||
testing.expect_value(t, w, -700)
|
||
testing.expect_value(t, vx, 200)
|
||
w, vx, _ = font_cid_vmetric(f, 19, 500)
|
||
testing.expect_value(t, w, -900)
|
||
testing.expect_value(t, vx, 50)
|
||
w, vx, _ = font_cid_vmetric(f, 21, 500)
|
||
testing.expect_value(t, w, -900)
|
||
w, _, _ = font_cid_vmetric(f, 101, 500)
|
||
testing.expect_value(t, w, -1000) // the /DW2 default
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_nested_overlaps :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Three runs nested inside one another, each narrower and later than the
|
||
// last: 0..100 is A, 10..20 is B written over it, and 11..12 is C written
|
||
// over that. Every CID has exactly one right answer --
|
||
//
|
||
// 0..9 A, 10 B, 11..12 C, 13..20 B, 21..100 A
|
||
//
|
||
// -- and the shape is what catches an implementation that keeps only one
|
||
// leftover piece per correction: the part of A above 20 is left stranded
|
||
// behind the part of B above 12, and both have to come back.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 7 /W [0 100 100 10 20 200 11 12 300] >>] >>")
|
||
if f == nil { return }
|
||
for pair in ([][2]f64{
|
||
{0, 100}, {9, 100}, {10, 200}, {11, 300}, {12, 300},
|
||
{13, 200}, {20, 200}, {21, 100}, {100, 100}, {101, 7},
|
||
}) {
|
||
got := font_cid_width(f, u32(pair[0]))
|
||
testing.expectf(t, got == pair[1], "CID %v is %v wide, wanted %v",
|
||
pair[0], got, pair[1])
|
||
}
|
||
}
|
||
|
||
@(test)
|
||
test_font_type0_w_identical_and_adjacent :: proc(t: ^testing.T) {
|
||
doc := cf_open(t)
|
||
defer cf_close(t, doc)
|
||
|
||
// Two runs covering exactly the same CIDs, and a run that abuts another
|
||
// without overlapping it. Neither may lose a CID or invent one.
|
||
f := fn_font(t, doc,
|
||
"<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /DescendantFonts " +
|
||
"[<< /Subtype /CIDFontType2 /DW 7 /W [5 9 100 5 9 200 10 14 300] >>] >>")
|
||
if f == nil { return }
|
||
testing.expect_value(t, font_cid_width(f, 4), 7)
|
||
// One of the two identical runs wins; which is the file's problem, but a
|
||
// CID they both cover must not fall through to /DW.
|
||
w := font_cid_width(f, 7)
|
||
testing.expectf(t, w == 100 || w == 200, "CID 7 fell through to /DW: %v", w)
|
||
testing.expect_value(t, font_cid_width(f, 9), w)
|
||
testing.expect_value(t, font_cid_width(f, 10), 300)
|
||
testing.expect_value(t, font_cid_width(f, 14), 300)
|
||
testing.expect_value(t, font_cid_width(f, 15), 7)
|
||
}
|