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