1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package sfnt
6
7import (
8	"bytes"
9	"fmt"
10	"image"
11	"io/ioutil"
12	"path/filepath"
13	"testing"
14
15	"golang.org/x/image/font"
16	"golang.org/x/image/font/gofont/gobold"
17	"golang.org/x/image/font/gofont/gomono"
18	"golang.org/x/image/font/gofont/goregular"
19	"golang.org/x/image/math/fixed"
20)
21
22func pt(x, y fixed.Int26_6) fixed.Point26_6 {
23	return fixed.Point26_6{X: x, Y: y}
24}
25
26func moveTo(xa, ya fixed.Int26_6) Segment {
27	return Segment{
28		Op:   SegmentOpMoveTo,
29		Args: [3]fixed.Point26_6{pt(xa, ya)},
30	}
31}
32
33func lineTo(xa, ya fixed.Int26_6) Segment {
34	return Segment{
35		Op:   SegmentOpLineTo,
36		Args: [3]fixed.Point26_6{pt(xa, ya)},
37	}
38}
39
40func quadTo(xa, ya, xb, yb fixed.Int26_6) Segment {
41	return Segment{
42		Op:   SegmentOpQuadTo,
43		Args: [3]fixed.Point26_6{pt(xa, ya), pt(xb, yb)},
44	}
45}
46
47func cubeTo(xa, ya, xb, yb, xc, yc fixed.Int26_6) Segment {
48	return Segment{
49		Op:   SegmentOpCubeTo,
50		Args: [3]fixed.Point26_6{pt(xa, ya), pt(xb, yb), pt(xc, yc)},
51	}
52}
53
54func translate(dx, dy fixed.Int26_6, s Segment) Segment {
55	translateArgs(&s.Args, dx, dy)
56	return s
57}
58
59func transform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, s Segment) Segment {
60	transformArgs(&s.Args, txx, txy, tyx, tyy, dx, dy)
61	return s
62}
63
64func checkSegmentsEqual(got, want []Segment) error {
65	// Flip got's Y axis. The test cases' coordinates are given with the Y axis
66	// increasing up, as that is what the ttx tool gives, and is the model for
67	// the underlying font format. The Go API returns coordinates with the Y
68	// axis increasing down, the same as the standard graphics libraries.
69	for i := range got {
70		for j := range got[i].Args {
71			got[i].Args[j].Y *= -1
72		}
73	}
74
75	if len(got) != len(want) {
76		return fmt.Errorf("got %d elements, want %d\noverall:\ngot  %v\nwant %v",
77			len(got), len(want), got, want)
78	}
79	for i, g := range got {
80		if w := want[i]; g != w {
81			return fmt.Errorf("element %d:\ngot  %v\nwant %v\noverall:\ngot  %v\nwant %v",
82				i, g, w, got, want)
83		}
84	}
85
86	// Check that every contour is closed.
87	if len(got) == 0 {
88		return nil
89	}
90	if got[0].Op != SegmentOpMoveTo {
91		return fmt.Errorf("segments do not start with a moveTo")
92	}
93	var (
94		first, last fixed.Point26_6
95		firstI      int
96	)
97	checkClosed := func(lastI int) error {
98		if first != last {
99			return fmt.Errorf("segments[%d:%d] not closed:\nfirst %v\nlast  %v", firstI, lastI, first, last)
100		}
101		return nil
102	}
103	for i, g := range got {
104		switch g.Op {
105		case SegmentOpMoveTo:
106			if i != 0 {
107				if err := checkClosed(i); err != nil {
108					return err
109				}
110			}
111			firstI, first, last = i, g.Args[0], g.Args[0]
112		case SegmentOpLineTo:
113			last = g.Args[0]
114		case SegmentOpQuadTo:
115			last = g.Args[1]
116		case SegmentOpCubeTo:
117			last = g.Args[2]
118		}
119	}
120	return checkClosed(len(got))
121}
122
123func TestTrueTypeParse(t *testing.T) {
124	f, err := Parse(goregular.TTF)
125	if err != nil {
126		t.Fatalf("Parse: %v", err)
127	}
128	testTrueType(t, f, goregular.TTF)
129}
130
131func TestTrueTypeParseReaderAt(t *testing.T) {
132	f, err := ParseReaderAt(bytes.NewReader(goregular.TTF))
133	if err != nil {
134		t.Fatalf("ParseReaderAt: %v", err)
135	}
136	testTrueType(t, f, goregular.TTF)
137}
138
139func testTrueType(t *testing.T, f *Font, wantSrc []byte) {
140	if got, want := f.UnitsPerEm(), Units(2048); got != want {
141		t.Errorf("UnitsPerEm: got %d, want %d", got, want)
142	}
143	// The exact number of glyphs in goregular.TTF can vary, and future
144	// versions may add more glyphs, but https://blog.golang.org/go-fonts says
145	// that "The WGL4 character set... [has] more than 650 characters in all.
146	if got, want := f.NumGlyphs(), 650; got <= want {
147		t.Errorf("NumGlyphs: got %d, want > %d", got, want)
148	}
149	buf := &bytes.Buffer{}
150	if n, err := f.WriteSourceTo(nil, buf); err != nil {
151		t.Fatalf("WriteSourceTo: %v", err)
152	} else if n != int64(len(wantSrc)) {
153		t.Fatalf("WriteSourceTo: got %d, want %d", n, len(wantSrc))
154	} else if gotSrc := buf.Bytes(); !bytes.Equal(gotSrc, wantSrc) {
155		t.Fatalf("WriteSourceTo: contents differ")
156	}
157}
158
159func fontData(name string) []byte {
160	switch name {
161	case "gobold":
162		return gobold.TTF
163	case "gomono":
164		return gomono.TTF
165	case "goregular":
166		return goregular.TTF
167	}
168	panic("unreachable")
169}
170
171func TestBounds(t *testing.T) {
172	testCases := map[string]fixed.Rectangle26_6{
173		"gobold": {
174			Min: fixed.Point26_6{
175				X: -452,
176				Y: -2193,
177			},
178			Max: fixed.Point26_6{
179				X: 2190,
180				Y: 432,
181			},
182		},
183		"gomono": {
184			Min: fixed.Point26_6{
185				X: 0,
186				Y: -2227,
187			},
188			Max: fixed.Point26_6{
189				X: 1229,
190				Y: 432,
191			},
192		},
193		"goregular": {
194			Min: fixed.Point26_6{
195				X: -440,
196				Y: -2118,
197			},
198			Max: fixed.Point26_6{
199				X: 2160,
200				Y: 543,
201			},
202		},
203	}
204
205	var b Buffer
206	for name, want := range testCases {
207		f, err := Parse(fontData(name))
208		if err != nil {
209			t.Errorf("Parse(%q): %v", name, err)
210			continue
211		}
212		ppem := fixed.Int26_6(f.UnitsPerEm())
213
214		got, err := f.Bounds(&b, ppem, font.HintingNone)
215		if err != nil {
216			t.Errorf("name=%q: Bounds: %v", name, err)
217			continue
218		}
219		if got != want {
220			t.Errorf("name=%q: Bounds: got %v, want %v", name, got, want)
221			continue
222		}
223	}
224}
225
226func TestMetrics(t *testing.T) {
227	cmapFont, err := ioutil.ReadFile(filepath.FromSlash("../testdata/cmapTest.ttf"))
228	if err != nil {
229		t.Fatal(err)
230	}
231	testCases := map[string]struct {
232		font []byte
233		want font.Metrics
234	}{
235		"goregular": {goregular.TTF, font.Metrics{Height: 2367, Ascent: 1935, Descent: 432, XHeight: 1086, CapHeight: 1480,
236			CaretSlope: image.Point{X: 0, Y: 1}}},
237		// cmapTest.ttf has a non-zero lineGap.
238		"cmapTest": {cmapFont, font.Metrics{Height: 1549, Ascent: 1365, Descent: 0, XHeight: 800, CapHeight: 800,
239			CaretSlope: image.Point{X: 20, Y: 100}}},
240	}
241	var b Buffer
242	for name, tc := range testCases {
243		f, err := Parse(tc.font)
244		if err != nil {
245			t.Errorf("name=%q: Parse: %v", name, err)
246			continue
247		}
248		ppem := fixed.Int26_6(f.UnitsPerEm())
249
250		got, err := f.Metrics(&b, ppem, font.HintingNone)
251		if err != nil {
252			t.Errorf("name=%q: Metrics: %v", name, err)
253			continue
254		}
255		if got != tc.want {
256			t.Errorf("name=%q: Metrics: got %v, want %v", name, got, tc.want)
257			continue
258		}
259	}
260}
261
262func TestGlyphBounds(t *testing.T) {
263	f, err := Parse(goregular.TTF)
264	if err != nil {
265		t.Fatalf("Parse: %v", err)
266	}
267	ppem := fixed.Int26_6(f.UnitsPerEm())
268
269	testCases := []struct {
270		r          rune
271		wantBounds fixed.Rectangle26_6
272		wantAdv    fixed.Int26_6
273	}{{
274		r: ' ',
275		wantBounds: fixed.Rectangle26_6{
276			Min: fixed.Point26_6{X: 0, Y: 0},
277			Max: fixed.Point26_6{X: 0, Y: 0},
278		},
279		wantAdv: 569,
280	}, {
281		r: 'A',
282		wantBounds: fixed.Rectangle26_6{
283			Min: fixed.Point26_6{X: 19, Y: -1480},
284			Max: fixed.Point26_6{X: 1342, Y: 0},
285		},
286		wantAdv: 1366,
287	}, {
288		r: 'Á',
289		wantBounds: fixed.Rectangle26_6{
290			Min: fixed.Point26_6{X: 19, Y: -1935},
291			Max: fixed.Point26_6{X: 1342, Y: 0},
292		},
293		wantAdv: 1366,
294	}, {
295		r: 'Æ',
296		wantBounds: fixed.Rectangle26_6{
297			Min: fixed.Point26_6{X: 19, Y: -1480},
298			Max: fixed.Point26_6{X: 1990, Y: 0},
299		},
300		wantAdv: 2048,
301	}, {
302		r: 'i',
303		wantBounds: fixed.Rectangle26_6{
304			Min: fixed.Point26_6{X: 144, Y: -1500},
305			Max: fixed.Point26_6{X: 361, Y: 0}},
306		wantAdv: 505,
307	}, {
308		r: 'j',
309		wantBounds: fixed.Rectangle26_6{
310			Min: fixed.Point26_6{X: -84, Y: -1500},
311			Max: fixed.Point26_6{X: 387, Y: 419},
312		},
313		wantAdv: 519,
314	}, {
315		r: 'x',
316		wantBounds: fixed.Rectangle26_6{
317			Min: fixed.Point26_6{X: 28, Y: -1086},
318			Max: fixed.Point26_6{X: 993, Y: 0},
319		},
320		wantAdv: 1024,
321	}}
322
323	var b Buffer
324	for _, tc := range testCases {
325		gi, err := f.GlyphIndex(&b, tc.r)
326		if err != nil {
327			t.Errorf("r=%q: %v", tc.r, err)
328			continue
329		}
330
331		gotBounds, gotAdv, err := f.GlyphBounds(&b, gi, ppem, font.HintingNone)
332		if err != nil {
333			t.Errorf("r=%q: GlyphBounds: %v", tc.r, err)
334			continue
335		}
336		if gotBounds != tc.wantBounds {
337			t.Errorf("r=%q: Bounds: got %#v, want %#v", tc.r, gotBounds, tc.wantBounds)
338		}
339		if gotAdv != tc.wantAdv {
340			t.Errorf("r=%q: Adv: got %#v, want %#v", tc.r, gotAdv, tc.wantAdv)
341		}
342	}
343}
344
345func TestGlyphAdvance(t *testing.T) {
346	testCases := map[string][]struct {
347		r    rune
348		want fixed.Int26_6
349	}{
350		"gobold": {
351			{' ', 569},
352			{'A', 1479},
353			{'Á', 1479},
354			{'Æ', 2048},
355			{'i', 592},
356			{'x', 1139},
357		},
358		"gomono": {
359			{' ', 1229},
360			{'A', 1229},
361			{'Á', 1229},
362			{'Æ', 1229},
363			{'i', 1229},
364			{'x', 1229},
365		},
366		"goregular": {
367			{' ', 569},
368			{'A', 1366},
369			{'Á', 1366},
370			{'Æ', 2048},
371			{'i', 505},
372			{'x', 1024},
373		},
374	}
375
376	var b Buffer
377	for name, testCases1 := range testCases {
378		f, err := Parse(fontData(name))
379		if err != nil {
380			t.Errorf("Parse(%q): %v", name, err)
381			continue
382		}
383		ppem := fixed.Int26_6(f.UnitsPerEm())
384
385		for _, tc := range testCases1 {
386			x, err := f.GlyphIndex(&b, tc.r)
387			if err != nil {
388				t.Errorf("name=%q, r=%q: GlyphIndex: %v", name, tc.r, err)
389				continue
390			}
391			got, err := f.GlyphAdvance(&b, x, ppem, font.HintingNone)
392			if err != nil {
393				t.Errorf("name=%q, r=%q: GlyphAdvance: %v", name, tc.r, err)
394				continue
395			}
396			if got != tc.want {
397				t.Errorf("name=%q, r=%q: GlyphAdvance: got %d, want %d", name, tc.r, got, tc.want)
398				continue
399			}
400		}
401	}
402}
403
404func TestGoRegularGlyphIndex(t *testing.T) {
405	f, err := Parse(goregular.TTF)
406	if err != nil {
407		t.Fatalf("Parse: %v", err)
408	}
409
410	testCases := []struct {
411		r    rune
412		want GlyphIndex
413	}{
414		// Glyphs that aren't present in Go Regular.
415		{'\u001f', 0}, // U+001F <control>
416		{'\u0200', 0}, // U+0200 LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
417		{'\u2000', 0}, // U+2000 EN QUAD
418
419		// The want values below can be verified by running the ttx tool on
420		// Go-Regular.ttf.
421		//
422		// The actual values are ad hoc, and result from whatever tools the
423		// Bigelow & Holmes type foundry used and the order in which they
424		// crafted the glyphs. They may change over time as newer versions of
425		// the font are released.
426
427		{'\u0020', 3},  // U+0020 SPACE
428		{'\u0021', 4},  // U+0021 EXCLAMATION MARK
429		{'\u0022', 5},  // U+0022 QUOTATION MARK
430		{'\u0023', 6},  // U+0023 NUMBER SIGN
431		{'\u0024', 7},  // U+0024 DOLLAR SIGN
432		{'\u0025', 8},  // U+0025 PERCENT SIGN
433		{'\u0026', 9},  // U+0026 AMPERSAND
434		{'\u0027', 10}, // U+0027 APOSTROPHE
435
436		{'\u03bd', 396}, // U+03BD GREEK SMALL LETTER NU
437		{'\u03be', 397}, // U+03BE GREEK SMALL LETTER XI
438		{'\u03bf', 398}, // U+03BF GREEK SMALL LETTER OMICRON
439		{'\u03c0', 399}, // U+03C0 GREEK SMALL LETTER PI
440		{'\u03c1', 400}, // U+03C1 GREEK SMALL LETTER RHO
441		{'\u03c2', 401}, // U+03C2 GREEK SMALL LETTER FINAL SIGMA
442	}
443
444	var b Buffer
445	for _, tc := range testCases {
446		got, err := f.GlyphIndex(&b, tc.r)
447		if err != nil {
448			t.Errorf("r=%q: %v", tc.r, err)
449			continue
450		}
451		if got != tc.want {
452			t.Errorf("r=%q: got %d, want %d", tc.r, got, tc.want)
453			continue
454		}
455	}
456}
457
458func TestGlyphIndex(t *testing.T) {
459	data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/cmapTest.ttf"))
460	if err != nil {
461		t.Fatal(err)
462	}
463
464	for _, format := range []int{-1, 0, 4, 12} {
465		testGlyphIndex(t, data, format)
466	}
467}
468
469func testGlyphIndex(t *testing.T, data []byte, cmapFormat int) {
470	if cmapFormat >= 0 {
471		originalSupportedCmapFormat := supportedCmapFormat
472		defer func() {
473			supportedCmapFormat = originalSupportedCmapFormat
474		}()
475		supportedCmapFormat = func(format, pid, psid uint16) bool {
476			return int(format) == cmapFormat && originalSupportedCmapFormat(format, pid, psid)
477		}
478	}
479
480	f, err := Parse(data)
481	if err != nil {
482		t.Errorf("cmapFormat=%d: %v", cmapFormat, err)
483		return
484	}
485
486	testCases := []struct {
487		r    rune
488		want GlyphIndex
489	}{
490		// Glyphs that aren't present in cmapTest.ttf.
491		{'?', 0},
492		{'\ufffd', 0},
493		{'\U0001f4a9', 0},
494
495		// For a .TTF file, FontForge maps:
496		//	- ".notdef"          to glyph index 0.
497		//	- ".null"            to glyph index 1.
498		//	- "nonmarkingreturn" to glyph index 2.
499
500		{'/', 0},
501		{'0', 3},
502		{'1', 4},
503		{'2', 5},
504		{'3', 0},
505
506		{'@', 0},
507		{'A', 6},
508		{'B', 7},
509		{'C', 0},
510
511		{'`', 0},
512		{'a', 8},
513		{'b', 0},
514
515		// Of the remaining runes, only U+00FF LATIN SMALL LETTER Y WITH
516		// DIAERESIS is in both the Mac Roman encoding and the cmapTest.ttf
517		// font file.
518		{'\u00fe', 0},
519		{'\u00ff', 9},
520		{'\u0100', 10},
521		{'\u0101', 11},
522		{'\u0102', 0},
523
524		{'\u4e2c', 0},
525		{'\u4e2d', 12},
526		{'\u4e2e', 0},
527
528		{'\U0001f0a0', 0},
529		{'\U0001f0a1', 13},
530		{'\U0001f0a2', 0},
531
532		{'\U0001f0b0', 0},
533		{'\U0001f0b1', 14},
534		{'\U0001f0b2', 15},
535		{'\U0001f0b3', 0},
536	}
537
538	var b Buffer
539	for _, tc := range testCases {
540		want := tc.want
541		switch {
542		case cmapFormat == 0 && tc.r > '\u007f' && tc.r != '\u00ff':
543			// cmap format 0, with the Macintosh Roman encoding, can only
544			// represent a limited set of non-ASCII runes, e.g. U+00FF.
545			want = 0
546		case cmapFormat == 4 && tc.r > '\uffff':
547			// cmap format 4 only supports the Basic Multilingual Plane (BMP).
548			want = 0
549		}
550
551		got, err := f.GlyphIndex(&b, tc.r)
552		if err != nil {
553			t.Errorf("cmapFormat=%d, r=%q: %v", cmapFormat, tc.r, err)
554			continue
555		}
556		if got != want {
557			t.Errorf("cmapFormat=%d, r=%q: got %d, want %d", cmapFormat, tc.r, got, want)
558			continue
559		}
560	}
561}
562
563func TestPostScriptSegments(t *testing.T) {
564	// wants' vectors correspond 1-to-1 to what's in the CFFTest.sfd file,
565	// although OpenType/CFF and FontForge's SFD have reversed orders.
566	// https://fontforge.github.io/validation.html says that "All paths must be
567	// drawn in a consistent direction. Clockwise for external paths,
568	// anti-clockwise for internal paths. (Actually PostScript requires the
569	// exact opposite, but FontForge reverses PostScript contours when it loads
570	// them so that everything is consistant internally -- and reverses them
571	// again when it saves them, of course)."
572	//
573	// The .notdef glyph isn't explicitly in the SFD file, but for some unknown
574	// reason, FontForge generates it in the OpenType/CFF file.
575	wants := [][]Segment{{
576		// .notdef
577		// - contour #0
578		moveTo(50, 0),
579		lineTo(450, 0),
580		lineTo(450, 533),
581		lineTo(50, 533),
582		lineTo(50, 0),
583		// - contour #1
584		moveTo(100, 50),
585		lineTo(100, 483),
586		lineTo(400, 483),
587		lineTo(400, 50),
588		lineTo(100, 50),
589	}, {
590		// zero
591		// - contour #0
592		moveTo(300, 700),
593		cubeTo(380, 700, 420, 580, 420, 500),
594		cubeTo(420, 350, 390, 100, 300, 100),
595		cubeTo(220, 100, 180, 220, 180, 300),
596		cubeTo(180, 450, 210, 700, 300, 700),
597		// - contour #1
598		moveTo(300, 800),
599		cubeTo(200, 800, 100, 580, 100, 400),
600		cubeTo(100, 220, 200, 0, 300, 0),
601		cubeTo(400, 0, 500, 220, 500, 400),
602		cubeTo(500, 580, 400, 800, 300, 800),
603	}, {
604		// one
605		// - contour #0
606		moveTo(100, 0),
607		lineTo(300, 0),
608		lineTo(300, 800),
609		lineTo(100, 800),
610		lineTo(100, 0),
611	}, {
612		// Q
613		// - contour #0
614		moveTo(657, 237),
615		lineTo(289, 387),
616		lineTo(519, 615),
617		lineTo(657, 237),
618		// - contour #1
619		moveTo(792, 169),
620		cubeTo(867, 263, 926, 502, 791, 665),
621		cubeTo(645, 840, 380, 831, 228, 673),
622		cubeTo(71, 509, 110, 231, 242, 93),
623		cubeTo(369, -39, 641, 18, 722, 93),
624		lineTo(802, 3),
625		lineTo(864, 83),
626		lineTo(792, 169),
627	}, {
628		// uni4E2D
629		// - contour #0
630		moveTo(141, 520),
631		lineTo(137, 356),
632		lineTo(245, 400),
633		lineTo(331, 26),
634		lineTo(355, 414),
635		lineTo(463, 434),
636		lineTo(453, 620),
637		lineTo(341, 592),
638		lineTo(331, 758),
639		lineTo(243, 752),
640		lineTo(235, 562),
641		lineTo(141, 520),
642	}}
643
644	testSegments(t, "CFFTest.otf", wants)
645}
646
647func TestTrueTypeSegments(t *testing.T) {
648	// wants' vectors correspond 1-to-1 to what's in the glyfTest.sfd file,
649	// although FontForge's SFD format stores quadratic Bézier curves as cubics
650	// with duplicated off-curve points. quadTo(bx, by, cx, cy) is stored as
651	// "bx by bx by cx cy".
652	//
653	// The .notdef, .null and nonmarkingreturn glyphs aren't explicitly in the
654	// SFD file, but for some unknown reason, FontForge generates them in the
655	// TrueType file.
656	wants := [][]Segment{{
657		// .notdef
658		// - contour #0
659		moveTo(68, 0),
660		lineTo(68, 1365),
661		lineTo(612, 1365),
662		lineTo(612, 0),
663		lineTo(68, 0),
664		// - contour #1
665		moveTo(136, 68),
666		lineTo(544, 68),
667		lineTo(544, 1297),
668		lineTo(136, 1297),
669		lineTo(136, 68),
670	}, {
671		// .null
672		// Empty glyph.
673	}, {
674		// nonmarkingreturn
675		// Empty glyph.
676	}, {
677		// zero
678		// - contour #0
679		moveTo(614, 1434),
680		quadTo(369, 1434, 369, 614),
681		quadTo(369, 471, 435, 338),
682		quadTo(502, 205, 614, 205),
683		quadTo(860, 205, 860, 1024),
684		quadTo(860, 1167, 793, 1300),
685		quadTo(727, 1434, 614, 1434),
686		// - contour #1
687		moveTo(614, 1638),
688		quadTo(1024, 1638, 1024, 819),
689		quadTo(1024, 0, 614, 0),
690		quadTo(205, 0, 205, 819),
691		quadTo(205, 1638, 614, 1638),
692	}, {
693		// one
694		// - contour #0
695		moveTo(205, 0),
696		lineTo(205, 1638),
697		lineTo(614, 1638),
698		lineTo(614, 0),
699		lineTo(205, 0),
700	}, {
701		// five
702		// - contour #0
703		moveTo(0, 0),
704		lineTo(0, 100),
705		lineTo(400, 100),
706		lineTo(400, 0),
707		lineTo(0, 0),
708	}, {
709		// six
710		// - contour #0
711		moveTo(0, 0),
712		lineTo(0, 100),
713		lineTo(400, 100),
714		lineTo(400, 0),
715		lineTo(0, 0),
716		// - contour #1
717		translate(111, 234, moveTo(205, 0)),
718		translate(111, 234, lineTo(205, 1638)),
719		translate(111, 234, lineTo(614, 1638)),
720		translate(111, 234, lineTo(614, 0)),
721		translate(111, 234, lineTo(205, 0)),
722	}, {
723		// seven
724		// - contour #0
725		moveTo(0, 0),
726		lineTo(0, 100),
727		lineTo(400, 100),
728		lineTo(400, 0),
729		lineTo(0, 0),
730		// - contour #1
731		transform(1<<13, 0, 0, 1<<13, 56, 117, moveTo(205, 0)),
732		transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 1638)),
733		transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 1638)),
734		transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 0)),
735		transform(1<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 0)),
736	}, {
737		// eight
738		// - contour #0
739		moveTo(0, 0),
740		lineTo(0, 100),
741		lineTo(400, 100),
742		lineTo(400, 0),
743		lineTo(0, 0),
744		// - contour #1
745		transform(3<<13, 0, 0, 1<<13, 56, 117, moveTo(205, 0)),
746		transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 1638)),
747		transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 1638)),
748		transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(614, 0)),
749		transform(3<<13, 0, 0, 1<<13, 56, 117, lineTo(205, 0)),
750	}, {
751		// nine
752		// - contour #0
753		moveTo(0, 0),
754		lineTo(0, 100),
755		lineTo(400, 100),
756		lineTo(400, 0),
757		lineTo(0, 0),
758		// - contour #1
759		transform(22381, 8192, 5996, 14188, 237, 258, moveTo(205, 0)),
760		transform(22381, 8192, 5996, 14188, 237, 258, lineTo(205, 1638)),
761		transform(22381, 8192, 5996, 14188, 237, 258, lineTo(614, 1638)),
762		transform(22381, 8192, 5996, 14188, 237, 258, lineTo(614, 0)),
763		transform(22381, 8192, 5996, 14188, 237, 258, lineTo(205, 0)),
764	}}
765
766	testSegments(t, "glyfTest.ttf", wants)
767}
768
769func testSegments(t *testing.T, filename string, wants [][]Segment) {
770	data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/" + filename))
771	if err != nil {
772		t.Fatalf("ReadFile: %v", err)
773	}
774	f, err := Parse(data)
775	if err != nil {
776		t.Fatalf("Parse: %v", err)
777	}
778	ppem := fixed.Int26_6(f.UnitsPerEm())
779
780	if ng := f.NumGlyphs(); ng != len(wants) {
781		t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
782	}
783	var b Buffer
784	for i, want := range wants {
785		got, err := f.LoadGlyph(&b, GlyphIndex(i), ppem, nil)
786		if err != nil {
787			t.Errorf("i=%d: LoadGlyph: %v", i, err)
788			continue
789		}
790		if err := checkSegmentsEqual(got, want); err != nil {
791			t.Errorf("i=%d: %v", i, err)
792			continue
793		}
794	}
795	if _, err := f.LoadGlyph(nil, 0xffff, ppem, nil); err != ErrNotFound {
796		t.Errorf("LoadGlyph(..., 0xffff, ...):\ngot  %v\nwant %v", err, ErrNotFound)
797	}
798
799	name, err := f.Name(nil, NameIDFamily)
800	if err != nil {
801		t.Errorf("Name: %v", err)
802	} else if want := filename[:len(filename)-len(".ttf")]; name != want {
803		t.Errorf("Name:\ngot  %q\nwant %q", name, want)
804	}
805}
806
807func TestPPEM(t *testing.T) {
808	data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/glyfTest.ttf"))
809	if err != nil {
810		t.Fatalf("ReadFile: %v", err)
811	}
812	f, err := Parse(data)
813	if err != nil {
814		t.Fatalf("Parse: %v", err)
815	}
816	var b Buffer
817	x, err := f.GlyphIndex(&b, '1')
818	if err != nil {
819		t.Fatalf("GlyphIndex: %v", err)
820	}
821	if x == 0 {
822		t.Fatalf("GlyphIndex: no glyph index found for the rune '1'")
823	}
824
825	testCases := []struct {
826		ppem fixed.Int26_6
827		want []Segment
828	}{{
829		ppem: fixed.Int26_6(12 << 6),
830		want: []Segment{
831			moveTo(77, 0),
832			lineTo(77, 614),
833			lineTo(230, 614),
834			lineTo(230, 0),
835			lineTo(77, 0),
836		},
837	}, {
838		ppem: fixed.Int26_6(2048),
839		want: []Segment{
840			moveTo(205, 0),
841			lineTo(205, 1638),
842			lineTo(614, 1638),
843			lineTo(614, 0),
844			lineTo(205, 0),
845		},
846	}}
847
848	for i, tc := range testCases {
849		got, err := f.LoadGlyph(&b, x, tc.ppem, nil)
850		if err != nil {
851			t.Errorf("i=%d: LoadGlyph: %v", i, err)
852			continue
853		}
854		if err := checkSegmentsEqual(got, tc.want); err != nil {
855			t.Errorf("i=%d: %v", i, err)
856			continue
857		}
858	}
859}
860
861func TestPostInfo(t *testing.T) {
862	data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/glyfTest.ttf"))
863	if err != nil {
864		t.Fatalf("ReadFile: %v", err)
865	}
866	f, err := Parse(data)
867	if err != nil {
868		t.Fatalf("Parse: %v", err)
869	}
870	post := f.PostTable()
871	if post.ItalicAngle != -11.25 {
872		t.Error("ItalicAngle:", post.ItalicAngle)
873	}
874	if post.UnderlinePosition != -255 {
875		t.Error("UnderlinePosition:", post.UnderlinePosition)
876	}
877	if post.UnderlineThickness != 102 {
878		t.Error("UnderlineThickness:", post.UnderlineThickness)
879	}
880	if post.IsFixedPitch {
881		t.Error("IsFixedPitch:", post.IsFixedPitch)
882	}
883}
884
885func TestGlyphName(t *testing.T) {
886	f, err := Parse(goregular.TTF)
887	if err != nil {
888		t.Fatalf("Parse: %v", err)
889	}
890
891	testCases := []struct {
892		r    rune
893		want string
894	}{
895		{'\x00', "uni0000"},
896		{'!', "exclam"},
897		{'A', "A"},
898		{'{', "braceleft"},
899		{'\u00c4', "Adieresis"}, // U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS
900		{'\u2020', "dagger"},    // U+2020 DAGGER
901		{'\u2660', "spade"},     // U+2660 BLACK SPADE SUIT
902		{'\uf800', "gopher"},    // U+F800 <Private Use>
903		{'\ufffe', ".notdef"},   // Not in the Go Regular font, so GlyphIndex returns (0, nil).
904	}
905
906	var b Buffer
907	for _, tc := range testCases {
908		x, err := f.GlyphIndex(&b, tc.r)
909		if err != nil {
910			t.Errorf("r=%q: GlyphIndex: %v", tc.r, err)
911			continue
912		}
913		got, err := f.GlyphName(&b, x)
914		if err != nil {
915			t.Errorf("r=%q: GlyphName: %v", tc.r, err)
916			continue
917		}
918		if got != tc.want {
919			t.Errorf("r=%q: got %q, want %q", tc.r, got, tc.want)
920			continue
921		}
922	}
923}
924
925func TestBuiltInPostNames(t *testing.T) {
926	testCases := []struct {
927		x    GlyphIndex
928		want string
929	}{
930		{0, ".notdef"},
931		{1, ".null"},
932		{2, "nonmarkingreturn"},
933		{13, "asterisk"},
934		{36, "A"},
935		{93, "z"},
936		{123, "ocircumflex"},
937		{202, "Edieresis"},
938		{255, "Ccaron"},
939		{256, "ccaron"},
940		{257, "dcroat"},
941		{258, ""},
942		{999, ""},
943		{0xffff, ""},
944	}
945
946	for _, tc := range testCases {
947		if tc.x >= numBuiltInPostNames {
948			continue
949		}
950		i := builtInPostNamesOffsets[tc.x+0]
951		j := builtInPostNamesOffsets[tc.x+1]
952		got := builtInPostNamesData[i:j]
953		if got != tc.want {
954			t.Errorf("x=%d: got %q, want %q", tc.x, got, tc.want)
955		}
956	}
957}
958