initial commit
This commit is contained in:
@@ -0,0 +1,845 @@
|
||||
package pdf
|
||||
|
||||
import "core:testing"
|
||||
|
||||
// Every width asserted here was read out of the Adobe Core 14 AFM file named in
|
||||
// the comment above it, not remembered. A width that is wrong by one unit is
|
||||
// invisible on the first glyph of a line and unmissable by the last, so these
|
||||
// are spot checks against the source of truth rather than against this file's
|
||||
// own tables.
|
||||
|
||||
@(test)
|
||||
test_std_font_widths_match_afm :: proc(t: ^testing.T) {
|
||||
cases := [?]struct {
|
||||
font: int,
|
||||
glyph: string,
|
||||
width: f64,
|
||||
}{
|
||||
// Courier.afm
|
||||
{STD_COURIER, "space", 600}, {STD_COURIER, "A", 600}, {STD_COURIER, "W", 600}, {STD_COURIER, "a", 600},
|
||||
{STD_COURIER, "m", 600}, {STD_COURIER, "one", 600}, {STD_COURIER, "comma", 600}, {STD_COURIER, "period", 600},
|
||||
{STD_COURIER, "quoteright", 600}, {STD_COURIER, "quotesingle", 600}, {STD_COURIER, "fraction", 600}, {STD_COURIER, "germandbls", 600},
|
||||
{STD_COURIER, "Euro", 600}, {STD_COURIER, "ydieresis", 600},
|
||||
// Courier-Bold.afm
|
||||
{STD_COURIER_BOLD, "space", 600}, {STD_COURIER_BOLD, "A", 600}, {STD_COURIER_BOLD, "W", 600}, {STD_COURIER_BOLD, "a", 600},
|
||||
{STD_COURIER_BOLD, "m", 600}, {STD_COURIER_BOLD, "one", 600}, {STD_COURIER_BOLD, "comma", 600}, {STD_COURIER_BOLD, "period", 600},
|
||||
{STD_COURIER_BOLD, "quoteright", 600}, {STD_COURIER_BOLD, "quotesingle", 600}, {STD_COURIER_BOLD, "fraction", 600}, {STD_COURIER_BOLD, "germandbls", 600},
|
||||
{STD_COURIER_BOLD, "Euro", 600}, {STD_COURIER_BOLD, "ydieresis", 600},
|
||||
// Courier-Oblique.afm
|
||||
{STD_COURIER_OBLIQUE, "space", 600}, {STD_COURIER_OBLIQUE, "A", 600}, {STD_COURIER_OBLIQUE, "W", 600}, {STD_COURIER_OBLIQUE, "a", 600},
|
||||
{STD_COURIER_OBLIQUE, "m", 600}, {STD_COURIER_OBLIQUE, "one", 600}, {STD_COURIER_OBLIQUE, "comma", 600}, {STD_COURIER_OBLIQUE, "period", 600},
|
||||
{STD_COURIER_OBLIQUE, "quoteright", 600}, {STD_COURIER_OBLIQUE, "quotesingle", 600}, {STD_COURIER_OBLIQUE, "fraction", 600}, {STD_COURIER_OBLIQUE, "germandbls", 600},
|
||||
{STD_COURIER_OBLIQUE, "Euro", 600}, {STD_COURIER_OBLIQUE, "ydieresis", 600},
|
||||
// Courier-BoldOblique.afm
|
||||
{STD_COURIER_BOLD_OBLIQUE, "space", 600}, {STD_COURIER_BOLD_OBLIQUE, "A", 600}, {STD_COURIER_BOLD_OBLIQUE, "W", 600}, {STD_COURIER_BOLD_OBLIQUE, "a", 600},
|
||||
{STD_COURIER_BOLD_OBLIQUE, "m", 600}, {STD_COURIER_BOLD_OBLIQUE, "one", 600}, {STD_COURIER_BOLD_OBLIQUE, "comma", 600}, {STD_COURIER_BOLD_OBLIQUE, "period", 600},
|
||||
{STD_COURIER_BOLD_OBLIQUE, "quoteright", 600}, {STD_COURIER_BOLD_OBLIQUE, "quotesingle", 600}, {STD_COURIER_BOLD_OBLIQUE, "fraction", 600}, {STD_COURIER_BOLD_OBLIQUE, "germandbls", 600},
|
||||
{STD_COURIER_BOLD_OBLIQUE, "Euro", 600}, {STD_COURIER_BOLD_OBLIQUE, "ydieresis", 600},
|
||||
// Helvetica.afm
|
||||
{STD_HELVETICA, "space", 278}, {STD_HELVETICA, "A", 667}, {STD_HELVETICA, "W", 944}, {STD_HELVETICA, "a", 556},
|
||||
{STD_HELVETICA, "m", 833}, {STD_HELVETICA, "one", 556}, {STD_HELVETICA, "comma", 278}, {STD_HELVETICA, "period", 278},
|
||||
{STD_HELVETICA, "quoteright", 222}, {STD_HELVETICA, "quotesingle", 191}, {STD_HELVETICA, "fraction", 167}, {STD_HELVETICA, "germandbls", 611},
|
||||
{STD_HELVETICA, "Euro", 556}, {STD_HELVETICA, "ydieresis", 500},
|
||||
// Helvetica-Bold.afm
|
||||
{STD_HELVETICA_BOLD, "space", 278}, {STD_HELVETICA_BOLD, "A", 722}, {STD_HELVETICA_BOLD, "W", 944}, {STD_HELVETICA_BOLD, "a", 556},
|
||||
{STD_HELVETICA_BOLD, "m", 889}, {STD_HELVETICA_BOLD, "one", 556}, {STD_HELVETICA_BOLD, "comma", 278}, {STD_HELVETICA_BOLD, "period", 278},
|
||||
{STD_HELVETICA_BOLD, "quoteright", 278}, {STD_HELVETICA_BOLD, "quotesingle", 238}, {STD_HELVETICA_BOLD, "fraction", 167}, {STD_HELVETICA_BOLD, "germandbls", 611},
|
||||
{STD_HELVETICA_BOLD, "Euro", 556}, {STD_HELVETICA_BOLD, "ydieresis", 556},
|
||||
// Helvetica-Oblique.afm
|
||||
{STD_HELVETICA_OBLIQUE, "space", 278}, {STD_HELVETICA_OBLIQUE, "A", 667}, {STD_HELVETICA_OBLIQUE, "W", 944}, {STD_HELVETICA_OBLIQUE, "a", 556},
|
||||
{STD_HELVETICA_OBLIQUE, "m", 833}, {STD_HELVETICA_OBLIQUE, "one", 556}, {STD_HELVETICA_OBLIQUE, "comma", 278}, {STD_HELVETICA_OBLIQUE, "period", 278},
|
||||
{STD_HELVETICA_OBLIQUE, "quoteright", 222}, {STD_HELVETICA_OBLIQUE, "quotesingle", 191}, {STD_HELVETICA_OBLIQUE, "fraction", 167}, {STD_HELVETICA_OBLIQUE, "germandbls", 611},
|
||||
{STD_HELVETICA_OBLIQUE, "Euro", 556}, {STD_HELVETICA_OBLIQUE, "ydieresis", 500},
|
||||
// Helvetica-BoldOblique.afm
|
||||
{STD_HELVETICA_BOLD_OBLIQUE, "space", 278}, {STD_HELVETICA_BOLD_OBLIQUE, "A", 722}, {STD_HELVETICA_BOLD_OBLIQUE, "W", 944}, {STD_HELVETICA_BOLD_OBLIQUE, "a", 556},
|
||||
{STD_HELVETICA_BOLD_OBLIQUE, "m", 889}, {STD_HELVETICA_BOLD_OBLIQUE, "one", 556}, {STD_HELVETICA_BOLD_OBLIQUE, "comma", 278}, {STD_HELVETICA_BOLD_OBLIQUE, "period", 278},
|
||||
{STD_HELVETICA_BOLD_OBLIQUE, "quoteright", 278}, {STD_HELVETICA_BOLD_OBLIQUE, "quotesingle", 238}, {STD_HELVETICA_BOLD_OBLIQUE, "fraction", 167}, {STD_HELVETICA_BOLD_OBLIQUE, "germandbls", 611},
|
||||
{STD_HELVETICA_BOLD_OBLIQUE, "Euro", 556}, {STD_HELVETICA_BOLD_OBLIQUE, "ydieresis", 556},
|
||||
// Times-Roman.afm
|
||||
{STD_TIMES_ROMAN, "space", 250}, {STD_TIMES_ROMAN, "A", 722}, {STD_TIMES_ROMAN, "W", 944}, {STD_TIMES_ROMAN, "a", 444},
|
||||
{STD_TIMES_ROMAN, "m", 778}, {STD_TIMES_ROMAN, "one", 500}, {STD_TIMES_ROMAN, "comma", 250}, {STD_TIMES_ROMAN, "period", 250},
|
||||
{STD_TIMES_ROMAN, "quoteright", 333}, {STD_TIMES_ROMAN, "quotesingle", 180}, {STD_TIMES_ROMAN, "fraction", 167}, {STD_TIMES_ROMAN, "germandbls", 500},
|
||||
{STD_TIMES_ROMAN, "Euro", 500}, {STD_TIMES_ROMAN, "ydieresis", 500},
|
||||
// Times-Bold.afm
|
||||
{STD_TIMES_BOLD, "space", 250}, {STD_TIMES_BOLD, "A", 722}, {STD_TIMES_BOLD, "W", 1000}, {STD_TIMES_BOLD, "a", 500},
|
||||
{STD_TIMES_BOLD, "m", 833}, {STD_TIMES_BOLD, "one", 500}, {STD_TIMES_BOLD, "comma", 250}, {STD_TIMES_BOLD, "period", 250},
|
||||
{STD_TIMES_BOLD, "quoteright", 333}, {STD_TIMES_BOLD, "quotesingle", 278}, {STD_TIMES_BOLD, "fraction", 167}, {STD_TIMES_BOLD, "germandbls", 556},
|
||||
{STD_TIMES_BOLD, "Euro", 500}, {STD_TIMES_BOLD, "ydieresis", 500},
|
||||
// Times-Italic.afm
|
||||
{STD_TIMES_ITALIC, "space", 250}, {STD_TIMES_ITALIC, "A", 611}, {STD_TIMES_ITALIC, "W", 833}, {STD_TIMES_ITALIC, "a", 500},
|
||||
{STD_TIMES_ITALIC, "m", 722}, {STD_TIMES_ITALIC, "one", 500}, {STD_TIMES_ITALIC, "comma", 250}, {STD_TIMES_ITALIC, "period", 250},
|
||||
{STD_TIMES_ITALIC, "quoteright", 333}, {STD_TIMES_ITALIC, "quotesingle", 214}, {STD_TIMES_ITALIC, "fraction", 167}, {STD_TIMES_ITALIC, "germandbls", 500},
|
||||
{STD_TIMES_ITALIC, "Euro", 500}, {STD_TIMES_ITALIC, "ydieresis", 444},
|
||||
// Times-BoldItalic.afm
|
||||
{STD_TIMES_BOLD_ITALIC, "space", 250}, {STD_TIMES_BOLD_ITALIC, "A", 667}, {STD_TIMES_BOLD_ITALIC, "W", 889}, {STD_TIMES_BOLD_ITALIC, "a", 500},
|
||||
{STD_TIMES_BOLD_ITALIC, "m", 778}, {STD_TIMES_BOLD_ITALIC, "one", 500}, {STD_TIMES_BOLD_ITALIC, "comma", 250}, {STD_TIMES_BOLD_ITALIC, "period", 250},
|
||||
{STD_TIMES_BOLD_ITALIC, "quoteright", 333}, {STD_TIMES_BOLD_ITALIC, "quotesingle", 278}, {STD_TIMES_BOLD_ITALIC, "fraction", 167}, {STD_TIMES_BOLD_ITALIC, "germandbls", 500},
|
||||
{STD_TIMES_BOLD_ITALIC, "Euro", 500}, {STD_TIMES_BOLD_ITALIC, "ydieresis", 444},
|
||||
// Symbol.afm
|
||||
{STD_SYMBOL, "space", 250}, {STD_SYMBOL, "exclam", 333}, {STD_SYMBOL, "universal", 713}, {STD_SYMBOL, "numbersign", 500},
|
||||
{STD_SYMBOL, "alpha", 631}, {STD_SYMBOL, "beta", 549}, {STD_SYMBOL, "gamma", 411}, {STD_SYMBOL, "Alpha", 722},
|
||||
{STD_SYMBOL, "Omega", 768}, {STD_SYMBOL, "summation", 713}, {STD_SYMBOL, "integral", 274}, {STD_SYMBOL, "radical", 549},
|
||||
{STD_SYMBOL, "infinity", 713}, {STD_SYMBOL, "copyrightserif", 790}, {STD_SYMBOL, "apple", 790},
|
||||
// ZapfDingbats.afm
|
||||
{STD_ZAPF_DINGBATS, "space", 278}, {STD_ZAPF_DINGBATS, "a1", 974}, {STD_ZAPF_DINGBATS, "a5", 789}, {STD_ZAPF_DINGBATS, "a10", 692},
|
||||
{STD_ZAPF_DINGBATS, "a60", 789}, {STD_ZAPF_DINGBATS, "a89", 390}, {STD_ZAPF_DINGBATS, "a100", 668}, {STD_ZAPF_DINGBATS, "a101", 732},
|
||||
{STD_ZAPF_DINGBATS, "a109", 626}, {STD_ZAPF_DINGBATS, "a191", 918},
|
||||
}
|
||||
for c in cases {
|
||||
w, ok := std_font_width(c.font, c.glyph)
|
||||
testing.expectf(t, ok, "%s has no width for %q", std_font_name(c.font), c.glyph)
|
||||
testing.expectf(t, w == c.width, "%s %q: width %v, want %v",
|
||||
std_font_name(c.font), c.glyph, w, c.width)
|
||||
}
|
||||
}
|
||||
|
||||
// Courier is fixed pitch, so the width question has one answer for every name,
|
||||
// including names the font cannot draw: a viewer still moves the pen one cell.
|
||||
@(test)
|
||||
test_std_font_width_courier_is_fixed_pitch :: proc(t: ^testing.T) {
|
||||
for f in ([?]int{STD_COURIER, STD_COURIER_BOLD, STD_COURIER_OBLIQUE, STD_COURIER_BOLD_OBLIQUE}) {
|
||||
for name in ([?]string{"A", "m", "space", "Aacute", "a1", "uni4E00", "notaglyph"}) {
|
||||
w, ok := std_font_width(f, name)
|
||||
testing.expectf(t, ok && w == 600, "%s %q: %v %v", std_font_name(f), name, w, ok)
|
||||
}
|
||||
w, ok := std_font_width(f, "")
|
||||
testing.expect(t, !ok && w == 0, "an empty glyph name has no width even in Courier")
|
||||
}
|
||||
}
|
||||
|
||||
// A name the font does not have must report ok = false rather than 0, because
|
||||
// the caller has to fall back to /MissingWidth and cannot tell those apart if
|
||||
// the width alone is returned.
|
||||
@(test)
|
||||
test_std_font_width_absent_and_out_of_range :: proc(t: ^testing.T) {
|
||||
for f in ([?]int{STD_HELVETICA, STD_TIMES_BOLD_ITALIC, STD_SYMBOL, STD_ZAPF_DINGBATS}) {
|
||||
for name in ([?]string{"", "notaglyph", "Asmall", "zzzzzz", "\x00"}) {
|
||||
w, ok := std_font_width(f, name)
|
||||
testing.expectf(t, !ok && w == 0, "%s %q: %v %v", std_font_name(f), name, w, ok)
|
||||
}
|
||||
}
|
||||
// Symbol and ZapfDingbats share no glyph names with the text fonts.
|
||||
_, ok := std_font_width(STD_HELVETICA, "alpha")
|
||||
testing.expect(t, !ok, "Helvetica has no alpha")
|
||||
_, ok = std_font_width(STD_SYMBOL, "A")
|
||||
testing.expect(t, !ok, "Symbol has no A -- code 0x41 is Alpha")
|
||||
_, ok = std_font_width(STD_ZAPF_DINGBATS, "A")
|
||||
testing.expect(t, !ok, "ZapfDingbats has no A")
|
||||
|
||||
for f in ([?]int{-1, -1000, STD_FONT_COUNT, 99} ) {
|
||||
w, iok := std_font_width(f, "A")
|
||||
testing.expectf(t, !iok && w == 0, "index %d must not resolve", f)
|
||||
testing.expect(t, std_font_name(f) == "", "no name for a bad index")
|
||||
testing.expect(t, !std_font_is_symbolic(f), "a bad index is not symbolic")
|
||||
testing.expect(t, std_font_builtin_encoding(f) == nil, "no encoding for a bad index")
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_std_font_lookup_canonical_names :: proc(t: ^testing.T) {
|
||||
for i in 0 ..< STD_FONT_COUNT {
|
||||
idx, ok := std_font_lookup(std_font_name(i))
|
||||
testing.expectf(t, ok, "%q did not resolve", std_font_name(i))
|
||||
testing.expectf(t, idx == i, "%q resolved to %d (%s), want %d",
|
||||
std_font_name(i), idx, std_font_name(idx), i)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_std_font_lookup_aliases :: proc(t: ^testing.T) {
|
||||
cases := [?]struct {
|
||||
name: string,
|
||||
want: int,
|
||||
}{
|
||||
// The Monotype spellings Word and Acrobat emit.
|
||||
{"Arial", STD_HELVETICA},
|
||||
{"ArialMT", STD_HELVETICA},
|
||||
{"Arial-BoldMT", STD_HELVETICA_BOLD},
|
||||
{"Arial-ItalicMT", STD_HELVETICA_OBLIQUE},
|
||||
{"Arial-BoldItalicMT", STD_HELVETICA_BOLD_OBLIQUE},
|
||||
{"Arial,Bold", STD_HELVETICA_BOLD},
|
||||
{"Arial,BoldItalic", STD_HELVETICA_BOLD_OBLIQUE},
|
||||
{"Arial-Black", STD_HELVETICA_BOLD},
|
||||
{"ArialNarrow", STD_HELVETICA},
|
||||
{"Helvetica-Italic", STD_HELVETICA_OBLIQUE},
|
||||
{"Helvetica Bold Oblique", STD_HELVETICA_BOLD_OBLIQUE},
|
||||
{"NimbusSanL-Regu", STD_HELVETICA},
|
||||
{"NimbusSans-BoldItalic", STD_HELVETICA_BOLD_OBLIQUE},
|
||||
|
||||
{"TimesNewRoman", STD_TIMES_ROMAN},
|
||||
{"TimesNewRomanPSMT", STD_TIMES_ROMAN},
|
||||
{"TimesNewRomanPS-BoldMT", STD_TIMES_BOLD},
|
||||
{"TimesNewRomanPS-ItalicMT", STD_TIMES_ITALIC},
|
||||
{"TimesNewRomanPS-BoldItalicMT", STD_TIMES_BOLD_ITALIC},
|
||||
{"Times", STD_TIMES_ROMAN},
|
||||
{"Times New Roman,Bold", STD_TIMES_BOLD},
|
||||
{"NimbusRomNo9L-Medi", STD_TIMES_ROMAN},
|
||||
|
||||
{"CourierNew", STD_COURIER},
|
||||
{"CourierNewPSMT", STD_COURIER},
|
||||
{"CourierNewPS-BoldMT", STD_COURIER_BOLD},
|
||||
{"CourierNew,BoldItalic", STD_COURIER_BOLD_OBLIQUE},
|
||||
{"CourierStd-Oblique", STD_COURIER_OBLIQUE},
|
||||
{"NimbusMonL-Regu", STD_COURIER},
|
||||
|
||||
// The two symbolic fonts have no styles; a style in the name is noise.
|
||||
{"Symbol", STD_SYMBOL},
|
||||
{"SymbolMT", STD_SYMBOL},
|
||||
{"Symbol,Bold", STD_SYMBOL},
|
||||
{"ZapfDingbats", STD_ZAPF_DINGBATS},
|
||||
{"Dingbats", STD_ZAPF_DINGBATS},
|
||||
{"ZapfDingbatsITC", STD_ZAPF_DINGBATS},
|
||||
}
|
||||
for c in cases {
|
||||
idx, ok := std_font_lookup(c.name)
|
||||
testing.expectf(t, ok, "%q did not resolve", c.name)
|
||||
testing.expectf(t, idx == c.want, "%q resolved to %d (%s), want %d (%s)",
|
||||
c.name, idx, std_font_name(idx), c.want, std_font_name(c.want))
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_std_font_lookup_subset_prefix :: proc(t: ^testing.T) {
|
||||
cases := [?]struct {
|
||||
name: string,
|
||||
want: int,
|
||||
}{
|
||||
{"ABCDEF+Helvetica", STD_HELVETICA},
|
||||
{"AAAAAA+Arial-BoldMT", STD_HELVETICA_BOLD},
|
||||
{"XYZabc+Times-Italic", STD_TIMES_ITALIC},
|
||||
{"ABCD+Courier", STD_COURIER},
|
||||
{"ABCDEF+ZapfDingbats", STD_ZAPF_DINGBATS},
|
||||
}
|
||||
for c in cases {
|
||||
idx, ok := std_font_lookup(c.name)
|
||||
testing.expectf(t, ok && idx == c.want, "%q -> %d %v", c.name, idx, ok)
|
||||
}
|
||||
// The tag really is removed, not just ignored: a tag that spells a family
|
||||
// must not decide the family of the font behind it.
|
||||
idx, ok := std_font_lookup("TIMESX+Helvetica")
|
||||
testing.expect(t, ok && idx == STD_HELVETICA, "the subset tag must not be matched against")
|
||||
idx, ok = std_font_lookup("BOLDXX+Helvetica")
|
||||
testing.expect(t, ok && idx == STD_HELVETICA, "a tag spelling a style must not set the style")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_std_font_lookup_rejects_other_fonts :: proc(t: ^testing.T) {
|
||||
for name in ([?]string{
|
||||
"", "+", "ABCDEF+", "Garamond", "Wingdings", "Wingdings-Regular",
|
||||
"MS-Mincho", "Calibri", "Cambria-Bold", "DejaVuSans", "Verdana",
|
||||
"Georgia", "TrebuchetMS", "Webdings", "\x00\x01\x02",
|
||||
}) {
|
||||
idx, ok := std_font_lookup(name)
|
||||
testing.expectf(t, !ok && idx == 0, "%q must not resolve to a standard font", name)
|
||||
}
|
||||
// A name longer than the normalising buffer must be handled, not crash.
|
||||
long := make([]u8, 4096)
|
||||
defer delete(long)
|
||||
for i in 0 ..< len(long) {
|
||||
long[i] = 'x'
|
||||
}
|
||||
_, ok := std_font_lookup(string(long))
|
||||
testing.expect(t, !ok, "a 4 KB name of nothing is not a standard font")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_std_font_is_symbolic :: proc(t: ^testing.T) {
|
||||
for i in 0 ..< STD_FONT_COUNT {
|
||||
want := i == STD_SYMBOL || i == STD_ZAPF_DINGBATS
|
||||
testing.expectf(t, std_font_is_symbolic(i) == want, "%s", std_font_name(i))
|
||||
}
|
||||
testing.expect(t, std_font_builtin_encoding(STD_SYMBOL) == encoding_symbol(), "")
|
||||
testing.expect(t, std_font_builtin_encoding(STD_ZAPF_DINGBATS) == encoding_zapf(), "")
|
||||
testing.expect(t, std_font_builtin_encoding(STD_HELVETICA) == encoding_standard(), "")
|
||||
testing.expect(t, std_font_builtin_encoding(STD_TIMES_ROMAN) == encoding_standard(), "")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Encodings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// The classic wrong-apostrophe bug: code 0x27 is a right single quote in
|
||||
// StandardEncoding and a straight typewriter apostrophe in WinAnsi, and the
|
||||
// straight one lives at 0xA9 in Standard while the curly one lives at 0x92 in
|
||||
// WinAnsi. Reading a WinAnsi document with the Standard table turns every
|
||||
// apostrophe into a curly quote and every 0x92 into nothing.
|
||||
@(test)
|
||||
test_encoding_quote_positions :: proc(t: ^testing.T) {
|
||||
std := encoding_standard()
|
||||
win := encoding_winansi()
|
||||
|
||||
testing.expect_value(t, std[0x27], "quoteright")
|
||||
testing.expect_value(t, win[0x27], "quotesingle")
|
||||
testing.expect_value(t, std[0xA9], "quotesingle")
|
||||
testing.expect_value(t, win[0x92], "quoteright")
|
||||
|
||||
testing.expect_value(t, std[0x60], "quoteleft")
|
||||
testing.expect_value(t, win[0x60], "grave")
|
||||
testing.expect_value(t, win[0x91], "quoteleft")
|
||||
|
||||
// Standard has no code at all where WinAnsi keeps the Latin-1 letters.
|
||||
testing.expect_value(t, std[0xFC], "")
|
||||
testing.expect_value(t, win[0xFC], "udieresis")
|
||||
|
||||
testing.expect_value(t, glyph_name_to_rune(std[0x27]), rune(0x2019))
|
||||
testing.expect_value(t, glyph_name_to_rune(win[0x27]), rune(0x0027))
|
||||
}
|
||||
|
||||
// 0x80..0x9F is where WinAnsi stops being Latin-1: CP1252 puts the typographic
|
||||
// characters in the C1 control area, and the annex fills every code CP1252
|
||||
// leaves undefined with bullet (ISO 32000-2, Annex D.2, note 3).
|
||||
@(test)
|
||||
test_encoding_winansi_c1_range :: proc(t: ^testing.T) {
|
||||
win := encoding_winansi()
|
||||
cases := [?]struct {
|
||||
code: int,
|
||||
name: string,
|
||||
}{
|
||||
{0x80, "Euro"}, {0x81, "bullet"}, {0x82, "quotesinglbase"}, {0x83, "florin"},
|
||||
{0x84, "quotedblbase"}, {0x85, "ellipsis"}, {0x86, "dagger"}, {0x87, "daggerdbl"},
|
||||
{0x88, "circumflex"}, {0x89, "perthousand"}, {0x8A, "Scaron"}, {0x8B, "guilsinglleft"},
|
||||
{0x8C, "OE"}, {0x8D, "bullet"}, {0x8E, "Zcaron"}, {0x8F, "bullet"},
|
||||
{0x90, "bullet"}, {0x91, "quoteleft"}, {0x92, "quoteright"}, {0x93, "quotedblleft"},
|
||||
{0x94, "quotedblright"}, {0x95, "bullet"}, {0x96, "endash"}, {0x97, "emdash"},
|
||||
{0x98, "tilde"}, {0x99, "trademark"}, {0x9A, "scaron"}, {0x9B, "guilsinglright"},
|
||||
{0x9C, "oe"}, {0x9D, "bullet"}, {0x9E, "zcaron"}, {0x9F, "Ydieresis"},
|
||||
}
|
||||
for c in cases {
|
||||
testing.expectf(t, win[c.code] == c.name, "WinAnsi 0x%02X: %q, want %q",
|
||||
c.code, win[c.code], c.name)
|
||||
}
|
||||
// Latin-1 would have left all of these undefined.
|
||||
std := encoding_standard()
|
||||
for c in cases {
|
||||
testing.expectf(t, std[c.code] == "", "StandardEncoding 0x%02X is undefined", c.code)
|
||||
}
|
||||
}
|
||||
|
||||
// Above 0xA0 WinAnsi is Latin-1 exactly, with two codes that carry a character
|
||||
// PDF draws as its plain form: a no-break space and a soft hyphen.
|
||||
@(test)
|
||||
test_encoding_winansi_is_latin1_above_a0 :: proc(t: ^testing.T) {
|
||||
win := encoding_winansi()
|
||||
for code in 0xA0 ..= 0xFF {
|
||||
name := win[code]
|
||||
testing.expectf(t, name != "", "WinAnsi 0x%02X is defined", code)
|
||||
want := rune(code)
|
||||
switch code {
|
||||
case 0xA0:
|
||||
want = 0x0020 // no-break space, drawn as a space
|
||||
case 0xAD:
|
||||
want = 0x002D // soft hyphen, drawn as a hyphen
|
||||
}
|
||||
testing.expectf(t, glyph_name_to_rune(name) == want,
|
||||
"WinAnsi 0x%02X (%s) -> U+%04X, want U+%04X", code, name,
|
||||
glyph_name_to_rune(name), want)
|
||||
}
|
||||
// ASCII is shared by both Latin encodings apart from the two quote codes.
|
||||
std := encoding_standard()
|
||||
for code in 0x20 ..= 0x7E {
|
||||
if code == 0x27 || code == 0x60 {
|
||||
continue
|
||||
}
|
||||
testing.expectf(t, std[code] == win[code], "0x%02X: %q vs %q", code, std[code], win[code])
|
||||
testing.expectf(t, glyph_name_to_rune(win[code]) == rune(code),
|
||||
"WinAnsi 0x%02X (%s)", code, win[code])
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_encoding_macroman_high_range :: proc(t: ^testing.T) {
|
||||
mac := encoding_macroman()
|
||||
cases := [?]struct {
|
||||
code: int,
|
||||
name: string,
|
||||
}{
|
||||
{0x80, "Adieresis"}, {0x81, "Aring"}, {0x82, "Ccedilla"}, {0x83, "Eacute"},
|
||||
{0x84, "Ntilde"}, {0x85, "Odieresis"}, {0x86, "Udieresis"}, {0x87, "aacute"},
|
||||
{0x88, "agrave"}, {0x89, "acircumflex"}, {0x8A, "adieresis"}, {0x8B, "atilde"},
|
||||
{0x8C, "aring"}, {0x8D, "ccedilla"}, {0x8E, "eacute"}, {0x8F, "egrave"},
|
||||
{0x90, "ecircumflex"}, {0x91, "edieresis"}, {0x92, "iacute"}, {0x93, "igrave"},
|
||||
{0x94, "icircumflex"}, {0x95, "idieresis"}, {0x96, "ntilde"}, {0x97, "oacute"},
|
||||
{0x98, "ograve"}, {0x99, "ocircumflex"}, {0x9A, "odieresis"}, {0x9B, "otilde"},
|
||||
{0x9C, "uacute"}, {0x9D, "ugrave"}, {0x9E, "ucircumflex"}, {0x9F, "udieresis"},
|
||||
{0xA0, "dagger"}, {0xA1, "degree"}, {0xA2, "cent"}, {0xA3, "sterling"},
|
||||
{0xA4, "section"}, {0xA5, "bullet"}, {0xA6, "paragraph"}, {0xA7, "germandbls"},
|
||||
{0xA8, "registered"}, {0xA9, "copyright"}, {0xAA, "trademark"}, {0xAB, "acute"},
|
||||
{0xAC, "dieresis"}, {0xAD, "notequal"}, {0xAE, "AE"}, {0xAF, "Oslash"},
|
||||
{0xB0, "infinity"}, {0xB1, "plusminus"}, {0xB2, "lessequal"}, {0xB3, "greaterequal"},
|
||||
{0xB4, "yen"}, {0xB5, "mu"}, {0xB6, "partialdiff"}, {0xB7, "summation"},
|
||||
{0xB8, "product"}, {0xB9, "pi"}, {0xBA, "integral"}, {0xBB, "ordfeminine"},
|
||||
{0xBC, "ordmasculine"}, {0xBD, "Omega"}, {0xBE, "ae"}, {0xBF, "oslash"},
|
||||
{0xC0, "questiondown"}, {0xC1, "exclamdown"}, {0xC2, "logicalnot"}, {0xC3, "radical"},
|
||||
{0xC4, "florin"}, {0xC5, "approxequal"}, {0xC6, "Delta"}, {0xC7, "guillemotleft"},
|
||||
{0xC8, "guillemotright"}, {0xC9, "ellipsis"}, {0xCA, "space"}, {0xCB, "Agrave"},
|
||||
{0xCC, "Atilde"}, {0xCD, "Otilde"}, {0xCE, "OE"}, {0xCF, "oe"},
|
||||
{0xD0, "endash"}, {0xD1, "emdash"}, {0xD2, "quotedblleft"}, {0xD3, "quotedblright"},
|
||||
{0xD4, "quoteleft"}, {0xD5, "quoteright"}, {0xD6, "divide"}, {0xD7, "lozenge"},
|
||||
{0xD8, "ydieresis"}, {0xD9, "Ydieresis"}, {0xDA, "fraction"}, {0xDB, "currency"},
|
||||
{0xDC, "guilsinglleft"}, {0xDD, "guilsinglright"}, {0xDE, "fi"}, {0xDF, "fl"},
|
||||
{0xE0, "daggerdbl"}, {0xE1, "periodcentered"}, {0xE2, "quotesinglbase"}, {0xE3, "quotedblbase"},
|
||||
{0xE4, "perthousand"}, {0xE5, "Acircumflex"}, {0xE6, "Ecircumflex"}, {0xE7, "Aacute"},
|
||||
{0xE8, "Edieresis"}, {0xE9, "Egrave"}, {0xEA, "Iacute"}, {0xEB, "Icircumflex"},
|
||||
{0xEC, "Idieresis"}, {0xED, "Igrave"}, {0xEE, "Oacute"}, {0xEF, "Ocircumflex"},
|
||||
{0xF0, "apple"}, {0xF1, "Ograve"}, {0xF2, "Uacute"}, {0xF3, "Ucircumflex"},
|
||||
{0xF4, "Ugrave"}, {0xF5, "dotlessi"}, {0xF6, "circumflex"}, {0xF7, "tilde"},
|
||||
{0xF8, "macron"}, {0xF9, "breve"}, {0xFA, "dotaccent"}, {0xFB, "ring"},
|
||||
{0xFC, "cedilla"}, {0xFD, "hungarumlaut"}, {0xFE, "ogonek"}, {0xFF, "caron"},
|
||||
}
|
||||
for c in cases {
|
||||
testing.expectf(t, mac[c.code] == c.name, "MacRoman 0x%02X: %q, want %q",
|
||||
c.code, mac[c.code], c.name)
|
||||
}
|
||||
// 0xCA is Mac OS Roman's no-break space; PDF draws it as an ordinary space.
|
||||
testing.expect_value(t, mac[0xCA], "space")
|
||||
// MacRoman is not Latin-1: 0x80 is A-dieresis, not a control code, and the
|
||||
// accented letters are in a completely different order.
|
||||
win := encoding_winansi()
|
||||
differing := 0
|
||||
for code in 0x80 ..= 0xFF {
|
||||
if mac[code] != win[code] {
|
||||
differing += 1
|
||||
}
|
||||
}
|
||||
testing.expectf(t, differing > 100, "MacRoman and WinAnsi differ widely above 0x80, got %d", differing)
|
||||
}
|
||||
|
||||
// ISO 32000-2 Annex D.2 leaves these fifteen positions blank, but Mac OS Roman
|
||||
// defines them and producers emitted them; every other reader fills them in.
|
||||
@(test)
|
||||
test_encoding_macroman_mac_os_extras :: proc(t: ^testing.T) {
|
||||
mac := encoding_macroman()
|
||||
cases := [?]struct {
|
||||
code: int,
|
||||
name: string,
|
||||
cp: rune,
|
||||
}{
|
||||
{0xAD, "notequal", 0x2260}, {0xB0, "infinity", 0x221E},
|
||||
{0xB2, "lessequal", 0x2264}, {0xB3, "greaterequal", 0x2265},
|
||||
{0xB6, "partialdiff", 0x2202}, {0xB7, "summation", 0x2211},
|
||||
{0xB8, "product", 0x220F}, {0xB9, "pi", 0x03C0},
|
||||
{0xBA, "integral", 0x222B}, {0xBD, "Omega", 0x2126},
|
||||
{0xC3, "radical", 0x221A}, {0xC5, "approxequal", 0x2248},
|
||||
{0xC6, "Delta", 0x2206}, {0xD7, "lozenge", 0x25CA},
|
||||
{0xF0, "apple", 0xF8FF},
|
||||
}
|
||||
for c in cases {
|
||||
testing.expectf(t, mac[c.code] == c.name, "MacRoman 0x%02X: %q", c.code, mac[c.code])
|
||||
testing.expectf(t, glyph_name_to_rune(c.name) == c.cp,
|
||||
"%s -> U+%04X, want U+%04X", c.name, glyph_name_to_rune(c.name), c.cp)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_encoding_standard_high_range :: proc(t: ^testing.T) {
|
||||
std := encoding_standard()
|
||||
cases := [?]struct {
|
||||
code: int,
|
||||
name: string,
|
||||
}{
|
||||
{0xA1, "exclamdown"}, {0xA2, "cent"}, {0xA3, "sterling"}, {0xA4, "fraction"},
|
||||
{0xA5, "yen"}, {0xA6, "florin"}, {0xA7, "section"}, {0xA8, "currency"},
|
||||
{0xA9, "quotesingle"}, {0xAA, "quotedblleft"}, {0xAB, "guillemotleft"}, {0xAC, "guilsinglleft"},
|
||||
{0xAD, "guilsinglright"}, {0xAE, "fi"}, {0xAF, "fl"}, {0xB1, "endash"},
|
||||
{0xB2, "dagger"}, {0xB3, "daggerdbl"}, {0xB4, "periodcentered"}, {0xB6, "paragraph"},
|
||||
{0xB7, "bullet"}, {0xB8, "quotesinglbase"}, {0xB9, "quotedblbase"}, {0xBA, "quotedblright"},
|
||||
{0xBB, "guillemotright"}, {0xBC, "ellipsis"}, {0xBD, "perthousand"}, {0xBF, "questiondown"},
|
||||
{0xC1, "grave"}, {0xC2, "acute"}, {0xC3, "circumflex"}, {0xC4, "tilde"},
|
||||
{0xC5, "macron"}, {0xC6, "breve"}, {0xC7, "dotaccent"}, {0xC8, "dieresis"},
|
||||
{0xCA, "ring"}, {0xCB, "cedilla"}, {0xCD, "hungarumlaut"}, {0xCE, "ogonek"},
|
||||
{0xCF, "caron"}, {0xD0, "emdash"}, {0xE1, "AE"}, {0xE3, "ordfeminine"},
|
||||
{0xE8, "Lslash"}, {0xE9, "Oslash"}, {0xEA, "OE"}, {0xEB, "ordmasculine"},
|
||||
{0xF1, "ae"}, {0xF5, "dotlessi"}, {0xF8, "lslash"}, {0xF9, "oslash"},
|
||||
{0xFA, "oe"}, {0xFB, "germandbls"},
|
||||
}
|
||||
seen: [256]bool
|
||||
for c in cases {
|
||||
testing.expectf(t, std[c.code] == c.name, "Standard 0x%02X: %q, want %q",
|
||||
c.code, std[c.code], c.name)
|
||||
seen[c.code] = true
|
||||
}
|
||||
// Everything above 0x7F that the table above does not name is undefined.
|
||||
for code in 0x80 ..= 0xFF {
|
||||
if !seen[code] {
|
||||
testing.expectf(t, std[code] == "", "Standard 0x%02X should be undefined, got %q",
|
||||
code, std[code])
|
||||
}
|
||||
}
|
||||
// StandardEncoding has no accented letters at all; that is why a document
|
||||
// in French needs WinAnsi, MacRoman or a /Differences array.
|
||||
for name in ([?]string{"eacute", "ccedilla", "udieresis", "Adieresis"}) {
|
||||
found := false
|
||||
for code in 0 ..< 256 {
|
||||
if std[code] == name {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
testing.expectf(t, !found, "StandardEncoding must not contain %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_encoding_symbol_and_zapf :: proc(t: ^testing.T) {
|
||||
sym := encoding_symbol()
|
||||
testing.expect_value(t, sym[0x20], "space")
|
||||
testing.expect_value(t, sym[0x41], "Alpha")
|
||||
testing.expect_value(t, sym[0x61], "alpha")
|
||||
testing.expect_value(t, sym[0x62], "beta")
|
||||
testing.expect_value(t, sym[0xD6], "radical")
|
||||
testing.expect_value(t, sym[0xE5], "summation")
|
||||
testing.expect_value(t, sym[0xF2], "integral")
|
||||
testing.expect_value(t, sym[0xA0], "Euro")
|
||||
// Symbol's 0x41 is not "A": reading it with a Latin table gives Greek text
|
||||
// spelled in Latin letters, the usual symptom of a missing built-in encoding.
|
||||
testing.expect(t, sym[0x41] != encoding_standard()[0x41], "")
|
||||
|
||||
zapf := encoding_zapf()
|
||||
testing.expect_value(t, zapf[0x20], "space")
|
||||
testing.expect_value(t, zapf[0x21], "a1")
|
||||
testing.expect_value(t, zapf[0x41], "a10")
|
||||
testing.expect_value(t, zapf[0x61], "a60")
|
||||
testing.expect_value(t, zapf[0x7E], "a100")
|
||||
testing.expect_value(t, zapf[0x80], "a89")
|
||||
testing.expect_value(t, zapf[0xA1], "a101")
|
||||
testing.expect_value(t, zapf[0xFE], "a191")
|
||||
testing.expect_value(t, zapf[0xFF], "")
|
||||
|
||||
exp := encoding_macexpert()
|
||||
testing.expect_value(t, exp[0x20], "space")
|
||||
testing.expect_value(t, exp[0x21], "exclamsmall")
|
||||
testing.expect_value(t, exp[0x30], "zerooldstyle")
|
||||
testing.expect_value(t, exp[0x61], "Asmall")
|
||||
testing.expect_value(t, exp[0xF6], "nsuperior")
|
||||
testing.expect_value(t, exp[0x40], "")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_encoding_by_name :: proc(t: ^testing.T) {
|
||||
testing.expect(t, encoding_by_name("WinAnsiEncoding") == encoding_winansi(), "")
|
||||
testing.expect(t, encoding_by_name("MacRomanEncoding") == encoding_macroman(), "")
|
||||
testing.expect(t, encoding_by_name("MacExpertEncoding") == encoding_macexpert(), "")
|
||||
testing.expect(t, encoding_by_name("StandardEncoding") == encoding_standard(), "")
|
||||
testing.expect(t, encoding_by_name("WinAnsi") == encoding_winansi(), "")
|
||||
testing.expect(t, encoding_by_name("MacRoman") == encoding_macroman(), "")
|
||||
// An unknown name is nil, which the caller must read as "leave the font's
|
||||
// built-in encoding alone" rather than "reset to Standard".
|
||||
for name in ([?]string{"", "Encoding", "winansiencoding", "WinAnsiEncodings",
|
||||
"PDFDocEncoding", "Identity-H", "\x00"}) {
|
||||
testing.expectf(t, encoding_by_name(name) == nil, "%q must not resolve", name)
|
||||
}
|
||||
// The four tables are genuinely different objects.
|
||||
testing.expect(t, encoding_standard() != encoding_winansi(), "")
|
||||
testing.expect(t, encoding_winansi() != encoding_macroman(), "")
|
||||
testing.expect(t, encoding_macroman() != encoding_macexpert(), "")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Glyph names
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@(test)
|
||||
test_glyph_name_to_rune_agl_algorithm :: proc(t: ^testing.T) {
|
||||
cases := [?]struct {
|
||||
name: string,
|
||||
want: rune,
|
||||
}{
|
||||
// Ordinary AGL names.
|
||||
{"A", 0x0041},
|
||||
{"space", 0x0020},
|
||||
{"quoteright", 0x2019},
|
||||
{"eacute", 0x00E9},
|
||||
{"germandbls", 0x00DF},
|
||||
{"fi", 0xFB01},
|
||||
{"Alpha", 0x0391},
|
||||
{"alpha", 0x03B1},
|
||||
{"afii10017", 0x0410}, // Cyrillic capital A
|
||||
{"Acyrillic", 0x0410},
|
||||
{"summation", 0x2211},
|
||||
{"a1", 0x2701}, // ZapfDingbats' own list
|
||||
{"Euro", 0x20AC},
|
||||
|
||||
// uniXXXX, one code point per four digits; the first one wins.
|
||||
{"uni0041", 0x0041},
|
||||
{"uni00e9", 0x00E9}, // lowercase hex is not AGL-legal but common
|
||||
{"uni20AC", 0x20AC},
|
||||
{"uni00410042", 0x0041},
|
||||
|
||||
// uXXXX through uXXXXXX.
|
||||
{"u0041", 0x0041},
|
||||
{"u1F600", 0x1F600},
|
||||
{"u10FFFF", 0x10FFFF},
|
||||
|
||||
// A suffix after a period is a stylistic variant of the same character.
|
||||
{"a.sc", 0x0061},
|
||||
{"one.oldstyle", 0x0031},
|
||||
{"A.alt01", 0x0041},
|
||||
{"uni0041.swash", 0x0041},
|
||||
|
||||
// An underscore joins components; a ligature is named by its parts and
|
||||
// only the first can be represented as one rune.
|
||||
{"f_i", 0x0066},
|
||||
{"f_f_l", 0x0066},
|
||||
{"uni0066_uni0069", 0x0066},
|
||||
|
||||
// The AGL is consulted before the uni/u forms, or a real name that
|
||||
// begins with those letters would be parsed as hex.
|
||||
{"union", 0x222A},
|
||||
{"universal", 0x2200},
|
||||
{"uacute", 0x00FA},
|
||||
{"u", 0x0075},
|
||||
{"g", 0x0067},
|
||||
|
||||
// Nothing at all.
|
||||
{"", -1},
|
||||
{".notdef", -1},
|
||||
{".null", -1},
|
||||
{"_", -1},
|
||||
{"g3", -1}, // a glyph index, not a character
|
||||
{"g127", -1},
|
||||
{"cid42", -1},
|
||||
{"index7", -1},
|
||||
{"glyph12", -1},
|
||||
{"uni", -1},
|
||||
{"uni41", -1}, // not a multiple of four digits
|
||||
{"uni00411", -1},
|
||||
{"uniZZZZ", -1},
|
||||
{"uniD800", -1}, // a lone surrogate is not a character
|
||||
{"uniDFFF", -1},
|
||||
{"uni0041D800", -1}, // and not in a later group either
|
||||
{"uni004100", -1}, // a trailing part-group is not a name
|
||||
{"u110000", -1}, // past the end of the code space
|
||||
{"uD800", -1},
|
||||
{"u041", -1}, // fewer than four digits
|
||||
{"u00000041", -1}, // more than six
|
||||
{"notaglyphname", -1},
|
||||
{"\x00\x01", -1},
|
||||
}
|
||||
for c in cases {
|
||||
got := glyph_name_to_rune(c.name)
|
||||
testing.expectf(t, got == c.want, "%q -> U+%04X (%d), want U+%04X (%d)",
|
||||
c.name, got, got, c.want, c.want)
|
||||
}
|
||||
}
|
||||
|
||||
// The ASCII range must survive a trip through both directions, because that is
|
||||
// the path every /Differences array and every text extraction takes.
|
||||
@(test)
|
||||
test_rune_to_glyph_name_ascii_round_trip :: proc(t: ^testing.T) {
|
||||
for r in rune(0x20) ..= rune(0x7E) {
|
||||
name := rune_to_glyph_name(r)
|
||||
testing.expectf(t, name != "", "U+%04X has no glyph name", r)
|
||||
back := glyph_name_to_rune(name)
|
||||
testing.expectf(t, back == r, "U+%04X -> %q -> U+%04X", r, name, back)
|
||||
}
|
||||
// The names are the plain ones, not an obscure synonym.
|
||||
testing.expect_value(t, rune_to_glyph_name(' '), "space")
|
||||
testing.expect_value(t, rune_to_glyph_name('A'), "A")
|
||||
testing.expect_value(t, rune_to_glyph_name('z'), "z")
|
||||
testing.expect_value(t, rune_to_glyph_name('0'), "zero")
|
||||
testing.expect_value(t, rune_to_glyph_name('.'), "period")
|
||||
testing.expect_value(t, rune_to_glyph_name('-'), "hyphen")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_rune_to_glyph_name_beyond_ascii :: proc(t: ^testing.T) {
|
||||
cases := [?]struct {
|
||||
r: rune,
|
||||
name: string,
|
||||
}{
|
||||
{0x00E9, "eacute"},
|
||||
{0x00DF, "germandbls"},
|
||||
{0x2019, "quoteright"},
|
||||
{0x2014, "emdash"},
|
||||
{0x20AC, "Euro"},
|
||||
{0x0391, "Alpha"},
|
||||
{0x03C0, "pi"},
|
||||
{0xFB01, "fi"},
|
||||
{0x2701, "a1"},
|
||||
}
|
||||
for c in cases {
|
||||
testing.expectf(t, rune_to_glyph_name(c.r) == c.name, "U+%04X -> %q, want %q",
|
||||
c.r, rune_to_glyph_name(c.r), c.name)
|
||||
testing.expectf(t, glyph_name_to_rune(c.name) == c.r, "%q -> U+%04X",
|
||||
c.name, glyph_name_to_rune(c.name))
|
||||
}
|
||||
// Code points the list does not name come back empty rather than as a
|
||||
// fabricated "uniXXXX", which a font would not find either.
|
||||
for r in ([?]rune{-1, 0, 0x0378, 0x4E00, 0x1F600, 0x10FFFF, 0x110000}) {
|
||||
testing.expectf(t, rune_to_glyph_name(r) == "", "U+%04X must have no name", r)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Table invariants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Every lookup here is a binary search, so a table that is out of order fails
|
||||
// silently for some of its entries and not others. Walking each table proves
|
||||
// both the ordering and that the search finds everything in it.
|
||||
@(test)
|
||||
test_tables_are_sorted_and_complete :: proc(t: ^testing.T) {
|
||||
for i in 1 ..< len(STD_TEXT_GLYPHS) {
|
||||
testing.expectf(t, STD_TEXT_GLYPHS[i-1] < STD_TEXT_GLYPHS[i],
|
||||
"STD_TEXT_GLYPHS out of order at %d: %q then %q", i, STD_TEXT_GLYPHS[i-1], STD_TEXT_GLYPHS[i])
|
||||
}
|
||||
for i in 1 ..< len(STD_SYMBOL_GLYPHS) {
|
||||
testing.expectf(t, STD_SYMBOL_GLYPHS[i-1] < STD_SYMBOL_GLYPHS[i], "STD_SYMBOL_GLYPHS at %d", i)
|
||||
}
|
||||
for i in 1 ..< len(STD_ZAPF_GLYPHS) {
|
||||
testing.expectf(t, STD_ZAPF_GLYPHS[i-1] < STD_ZAPF_GLYPHS[i], "STD_ZAPF_GLYPHS at %d", i)
|
||||
}
|
||||
for i in 1 ..< len(AGL_TABLE) {
|
||||
testing.expectf(t, AGL_TABLE[i-1].name < AGL_TABLE[i].name,
|
||||
"AGL_TABLE out of order at %d: %q then %q", i, AGL_TABLE[i-1].name, AGL_TABLE[i].name)
|
||||
}
|
||||
for i in 1 ..< len(AGL_BY_RUNE) {
|
||||
testing.expectf(t, AGL_TABLE[AGL_BY_RUNE[i-1]].cp < AGL_TABLE[AGL_BY_RUNE[i]].cp,
|
||||
"AGL_BY_RUNE out of order at %d", i)
|
||||
}
|
||||
|
||||
// Every name is findable, and every proportional width is plausible: the
|
||||
// narrowest glyph in any of the fourteen is 138 units and the widest 1042.
|
||||
for name, i in STD_TEXT_GLYPHS {
|
||||
testing.expectf(t, len(name) > 0, "empty name at %d", i)
|
||||
for f in STD_HELVETICA ..= STD_TIMES_BOLD_ITALIC {
|
||||
w, ok := std_font_width(f, name)
|
||||
testing.expectf(t, ok, "%s has no %q", std_font_name(f), name)
|
||||
testing.expectf(t, w >= 138 && w <= 1042, "%s %q: %v", std_font_name(f), name, w)
|
||||
}
|
||||
}
|
||||
for name in STD_SYMBOL_GLYPHS {
|
||||
w, ok := std_font_width(STD_SYMBOL, name)
|
||||
testing.expectf(t, ok && w >= 138 && w <= 1042, "Symbol %q: %v %v", name, w, ok)
|
||||
}
|
||||
for name in STD_ZAPF_GLYPHS {
|
||||
w, ok := std_font_width(STD_ZAPF_DINGBATS, name)
|
||||
testing.expectf(t, ok && w >= 138 && w <= 1042, "ZapfDingbats %q: %v %v", name, w, ok)
|
||||
}
|
||||
// agl_is_index_name is only equivalent to the fallthrough while the table
|
||||
// holds no name shaped like a glyph index; that is the invariant it rests on.
|
||||
for e in AGL_TABLE {
|
||||
for p in ([?]string{"g", "cid", "index", "glyph"}) {
|
||||
if len(e.name) <= len(p) || e.name[:len(p)] != p {
|
||||
continue
|
||||
}
|
||||
digits := true
|
||||
for j in len(p) ..< len(e.name) {
|
||||
if e.name[j] < '0' || e.name[j] > '9' {
|
||||
digits = false
|
||||
}
|
||||
}
|
||||
testing.expectf(t, !digits, "AGL name %q is shaped like a glyph index", e.name)
|
||||
}
|
||||
}
|
||||
for e, i in AGL_TABLE {
|
||||
testing.expectf(t, len(e.name) > 0, "empty AGL name at %d", i)
|
||||
testing.expectf(t, glyph_name_to_rune(e.name) == e.cp,
|
||||
"AGL %q -> U+%04X, want U+%04X", e.name, glyph_name_to_rune(e.name), e.cp)
|
||||
}
|
||||
}
|
||||
|
||||
// A width table that is missing a glyph the encoding can name is the same bug as
|
||||
// a wrong width: the caller falls back to /MissingWidth, which a standard-14
|
||||
// font dictionary usually does not carry, and the line collapses.
|
||||
@(test)
|
||||
test_every_encoded_glyph_has_a_width :: proc(t: ^testing.T) {
|
||||
// Seven glyphs are named by Mac OS Roman but are genuinely not in Adobe's
|
||||
// base 14 -- Helvetica has no infinity and no apple. A viewer draws .notdef
|
||||
// for them, so no width is the honest answer; they are listed rather than
|
||||
// skipped quietly so the rest of the sweep stays strict, and each one is
|
||||
// checked below to still be absent.
|
||||
absent := [?]string{"Omega", "apple", "approxequal", "infinity", "integral", "pi", "product"}
|
||||
is_absent :: proc(list: []string, name: string) -> bool {
|
||||
for x in list {
|
||||
if x == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
for enc in ([?]^[256]string{encoding_standard(), encoding_winansi(), encoding_macroman()}) {
|
||||
for name, code in enc {
|
||||
if name == "" || is_absent(absent[:], name) {
|
||||
continue
|
||||
}
|
||||
for f in 0 ..< STD_SYMBOL {
|
||||
_, ok := std_font_width(f, name)
|
||||
testing.expectf(t, ok, "%s has no width for %q (code 0x%02X)",
|
||||
std_font_name(f), name, code)
|
||||
}
|
||||
testing.expectf(t, glyph_name_to_rune(name) >= 0, "%q has no Unicode", name)
|
||||
}
|
||||
}
|
||||
for name in absent {
|
||||
_, ok := std_font_width(STD_HELVETICA, name)
|
||||
testing.expectf(t, !ok, "%q is in Helvetica after all; drop it from the list", name)
|
||||
_, ok = std_font_width(STD_TIMES_ROMAN, name)
|
||||
testing.expectf(t, !ok, "%q is in Times-Roman after all; drop it from the list", name)
|
||||
testing.expectf(t, glyph_name_to_rune(name) >= 0,
|
||||
"%q must still extract as text even with no width", name)
|
||||
}
|
||||
// The eight that MacRoman names and the fonts do carry must not be in that
|
||||
// list by accident.
|
||||
for name in ([?]string{"notequal", "lessequal", "greaterequal", "partialdiff",
|
||||
"summation", "radical", "Delta", "lozenge"}) {
|
||||
_, ok := std_font_width(STD_HELVETICA, name)
|
||||
testing.expectf(t, ok, "Helvetica does have %q", name)
|
||||
}
|
||||
for name in encoding_symbol() {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
_, ok := std_font_width(STD_SYMBOL, name)
|
||||
testing.expectf(t, ok, "Symbol has no width for %q", name)
|
||||
testing.expectf(t, glyph_name_to_rune(name) >= 0, "%q has no Unicode", name)
|
||||
}
|
||||
for name in encoding_zapf() {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
_, ok := std_font_width(STD_ZAPF_DINGBATS, name)
|
||||
testing.expectf(t, ok, "ZapfDingbats has no width for %q", name)
|
||||
testing.expectf(t, glyph_name_to_rune(name) >= 0, "%q has no Unicode", name)
|
||||
}
|
||||
// Every glyph the fonts do have must extract as text, or a page set in one
|
||||
// of them cannot be searched.
|
||||
for name in STD_TEXT_GLYPHS {
|
||||
testing.expectf(t, glyph_name_to_rune(name) >= 0, "%q has no Unicode", name)
|
||||
}
|
||||
for name in STD_SYMBOL_GLYPHS {
|
||||
testing.expectf(t, glyph_name_to_rune(name) >= 0, "%q has no Unicode", name)
|
||||
}
|
||||
for name in STD_ZAPF_GLYPHS {
|
||||
testing.expectf(t, glyph_name_to_rune(name) >= 0, "%q has no Unicode", name)
|
||||
}
|
||||
}
|
||||
|
||||
// The helpers underneath the public four, checked directly: they are where a
|
||||
// name that came out of a file is taken apart, so they carry the bounds.
|
||||
@(test)
|
||||
test_std_name_helpers :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, std_strip_subset("ABCDEF+Helvetica"), "Helvetica")
|
||||
testing.expect_value(t, std_strip_subset("abcdef+Helvetica"), "Helvetica")
|
||||
testing.expect_value(t, std_strip_subset("AB+Helvetica"), "Helvetica")
|
||||
testing.expect_value(t, std_strip_subset("Helvetica"), "Helvetica")
|
||||
testing.expect_value(t, std_strip_subset(""), "")
|
||||
testing.expect_value(t, std_strip_subset("+Helvetica"), "+Helvetica")
|
||||
testing.expect_value(t, std_strip_subset("ABCDEF+"), "")
|
||||
// The plus must be within the tag, and only letters may precede it.
|
||||
testing.expect_value(t, std_strip_subset("Helvetica+Bold"), "Helvetica+Bold")
|
||||
testing.expect_value(t, std_strip_subset("AB1DEF+Helvetica"), "AB1DEF+Helvetica")
|
||||
|
||||
buf: [8]u8
|
||||
testing.expect_value(t, std_normalize("Arial-BoldMT", buf[:]), "arialbol")
|
||||
testing.expect_value(t, std_normalize("", buf[:]), "")
|
||||
big: [64]u8
|
||||
testing.expect_value(t, std_normalize("Times New Roman,Bold", big[:]), "timesnewromanbold")
|
||||
testing.expect_value(t, std_normalize("\xff\x00-_.", big[:]), "")
|
||||
|
||||
testing.expect(t, std_contains("helvetica", "vet"), "")
|
||||
testing.expect(t, std_contains("helvetica", "helvetica"), "")
|
||||
testing.expect(t, std_contains("helvetica", ""), "the empty needle is everywhere")
|
||||
testing.expect(t, !std_contains("helvetica", "helveticax"), "")
|
||||
testing.expect(t, !std_contains("", "a"), "")
|
||||
testing.expect(t, std_contains("aaab", "ab"), "")
|
||||
}
|
||||
Reference in New Issue
Block a user