...

Source file src/golang.org/x/image/font/sfnt/sfnt_test.go

Documentation: golang.org/x/image/font/sfnt

     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  }
   969  

View as plain text