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