...

Source file src/github.com/golang/freetype/truetype/truetype_test.go

Documentation: github.com/golang/freetype/truetype

     1  // Copyright 2012 The Freetype-Go Authors. All rights reserved.
     2  // Use of this source code is governed by your choice of either the
     3  // FreeType License or the GNU General Public License version 2 (or
     4  // any later version), both of which can be found in the LICENSE file.
     5  
     6  package truetype
     7  
     8  import (
     9  	"bufio"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"strconv"
    15  	"strings"
    16  	"testing"
    17  
    18  	"golang.org/x/image/font"
    19  	"golang.org/x/image/math/fixed"
    20  )
    21  
    22  func parseTestdataFont(name string) (f *Font, testdataIsOptional bool, err error) {
    23  	b, err := ioutil.ReadFile(fmt.Sprintf("../testdata/%s.ttf", name))
    24  	if err != nil {
    25  		// The "x-foo" fonts are optional tests, as they are not checked
    26  		// in for copyright or file size reasons.
    27  		return nil, strings.HasPrefix(name, "x-"), fmt.Errorf("%s: ReadFile: %v", name, err)
    28  	}
    29  	f, err = Parse(b)
    30  	if err != nil {
    31  		return nil, true, fmt.Errorf("%s: Parse: %v", name, err)
    32  	}
    33  	return f, false, nil
    34  }
    35  
    36  func mkBounds(minX, minY, maxX, maxY fixed.Int26_6) fixed.Rectangle26_6 {
    37  	return fixed.Rectangle26_6{
    38  		Min: fixed.Point26_6{
    39  			X: minX,
    40  			Y: minY,
    41  		},
    42  		Max: fixed.Point26_6{
    43  			X: maxX,
    44  			Y: maxY,
    45  		},
    46  	}
    47  }
    48  
    49  // TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly.
    50  // The numerical values can be manually verified by examining luxisr.ttx.
    51  func TestParse(t *testing.T) {
    52  	f, _, err := parseTestdataFont("luxisr")
    53  	if err != nil {
    54  		t.Fatal(err)
    55  	}
    56  	if got, want := f.FUnitsPerEm(), int32(2048); got != want {
    57  		t.Errorf("FUnitsPerEm: got %v, want %v", got, want)
    58  	}
    59  	fupe := fixed.Int26_6(f.FUnitsPerEm())
    60  	if got, want := f.Bounds(fupe), mkBounds(-441, -432, 2024, 2033); got != want {
    61  		t.Errorf("Bounds: got %v, want %v", got, want)
    62  	}
    63  
    64  	i0 := f.Index('A')
    65  	i1 := f.Index('V')
    66  	if i0 != 36 || i1 != 57 {
    67  		t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1)
    68  	}
    69  	if got, want := f.HMetric(fupe, i0), (HMetric{1366, 19}); got != want {
    70  		t.Errorf("HMetric: got %v, want %v", got, want)
    71  	}
    72  	if got, want := f.VMetric(fupe, i0), (VMetric{2465, 553}); got != want {
    73  		t.Errorf("VMetric: got %v, want %v", got, want)
    74  	}
    75  	if got, want := f.Kern(fupe, i0, i1), fixed.Int26_6(-144); got != want {
    76  		t.Errorf("Kern: got %v, want %v", got, want)
    77  	}
    78  
    79  	g := &GlyphBuf{}
    80  	err = g.Load(f, fupe, i0, font.HintingNone)
    81  	if err != nil {
    82  		t.Fatalf("Load: %v", err)
    83  	}
    84  	g0 := &GlyphBuf{
    85  		Bounds: g.Bounds,
    86  		Points: g.Points,
    87  		Ends:   g.Ends,
    88  	}
    89  	g1 := &GlyphBuf{
    90  		Bounds: mkBounds(19, 0, 1342, 1480),
    91  		Points: []Point{
    92  			{19, 0, 51},
    93  			{581, 1480, 1},
    94  			{789, 1480, 51},
    95  			{1342, 0, 1},
    96  			{1116, 0, 35},
    97  			{962, 410, 3},
    98  			{368, 410, 33},
    99  			{214, 0, 3},
   100  			{428, 566, 19},
   101  			{904, 566, 33},
   102  			{667, 1200, 3},
   103  		},
   104  		Ends: []int{8, 11},
   105  	}
   106  	if got, want := fmt.Sprint(g0), fmt.Sprint(g1); got != want {
   107  		t.Errorf("GlyphBuf:\ngot  %v\nwant %v", got, want)
   108  	}
   109  }
   110  
   111  func TestIndex(t *testing.T) {
   112  	testCases := map[string]map[rune]Index{
   113  		"luxisr": {
   114  			' ':      3,
   115  			'!':      4,
   116  			'A':      36,
   117  			'V':      57,
   118  			'É':      101,
   119  			'fl':      193,
   120  			'\u22c5': 385,
   121  			'中':      0,
   122  		},
   123  
   124  		// The x-etc test cases use those versions of the .ttf files provided
   125  		// by Ubuntu 14.04. See testdata/make-other-hinting-txts.sh for details.
   126  
   127  		"x-arial-bold": {
   128  			' ':      3,
   129  			'+':      14,
   130  			'0':      19,
   131  			'_':      66,
   132  			'w':      90,
   133  			'~':      97,
   134  			'Ä':      98,
   135  			'fl':      192,
   136  			'½':      242,
   137  			'σ':      305,
   138  			'λ':      540,
   139  			'ỹ':      1275,
   140  			'\u04e9': 1319,
   141  			'中':      0,
   142  		},
   143  		"x-deja-vu-sans-oblique": {
   144  			' ':      3,
   145  			'*':      13,
   146  			'Œ':      276,
   147  			'ω':      861,
   148  			'‡':      2571,
   149  			'⊕':      3110,
   150  			'fl':      4728,
   151  			'\ufb03': 4729,
   152  			'\ufffd': 4813,
   153  			// TODO: '\U0001f640': ???,
   154  			'中': 0,
   155  		},
   156  		"x-droid-sans-japanese": {
   157  			' ':      0,
   158  			'\u3000': 3,
   159  			'\u3041': 25,
   160  			'\u30fe': 201,
   161  			'\uff61': 202,
   162  			'\uff67': 208,
   163  			'\uff9e': 263,
   164  			'\uff9f': 264,
   165  			'\u4e00': 265,
   166  			'\u557e': 1000,
   167  			'\u61b6': 2024,
   168  			'\u6ede': 3177,
   169  			'\u7505': 3555,
   170  			'\u81e3': 4602,
   171  			'\u81e5': 4603,
   172  			'\u81e7': 4604,
   173  			'\u81e8': 4605,
   174  			'\u81ea': 4606,
   175  			'\u81ed': 4607,
   176  			'\u81f3': 4608,
   177  			'\u81f4': 4609,
   178  			'\u91c7': 5796,
   179  			'\u9fa0': 6620,
   180  			'\u203e': 12584,
   181  		},
   182  		"x-times-new-roman": {
   183  			' ':      3,
   184  			':':      29,
   185  			'fl':      192,
   186  			'Ŀ':      273,
   187  			'♠':      388,
   188  			'Ŗ':      451,
   189  			'Σ':      520,
   190  			'\u200D': 745,
   191  			'Ẽ':      1216,
   192  			'\u04e9': 1319,
   193  			'中':      0,
   194  		},
   195  	}
   196  	for name, wants := range testCases {
   197  		f, testdataIsOptional, err := parseTestdataFont(name)
   198  		if err != nil {
   199  			if testdataIsOptional {
   200  				t.Log(err)
   201  			} else {
   202  				t.Fatal(err)
   203  			}
   204  			continue
   205  		}
   206  		for r, want := range wants {
   207  			if got := f.Index(r); got != want {
   208  				t.Errorf("%s: Index of %q, aka %U: got %d, want %d", name, r, r, got, want)
   209  			}
   210  		}
   211  	}
   212  }
   213  
   214  func TestName(t *testing.T) {
   215  	testCases := map[string]string{
   216  		"luximr": "Luxi Mono",
   217  		"luxirr": "Luxi Serif",
   218  		"luxisr": "Luxi Sans",
   219  	}
   220  
   221  	for name, want := range testCases {
   222  		f, testdataIsOptional, err := parseTestdataFont(name)
   223  		if err != nil {
   224  			if testdataIsOptional {
   225  				t.Log(err)
   226  			} else {
   227  				t.Fatal(err)
   228  			}
   229  			continue
   230  		}
   231  		if got := f.Name(NameIDFontFamily); got != want {
   232  			t.Errorf("%s: got %q, want %q", name, got, want)
   233  		}
   234  	}
   235  }
   236  
   237  type scalingTestData struct {
   238  	advanceWidth fixed.Int26_6
   239  	bounds       fixed.Rectangle26_6
   240  	points       []Point
   241  }
   242  
   243  // scalingTestParse parses a line of points like
   244  // 213 -22 -111 236 555;-22 -111 1, 178 555 1, 236 555 1, 36 -111 1
   245  // The line will not have a trailing "\n".
   246  func scalingTestParse(line string) (ret scalingTestData) {
   247  	next := func(s string) (string, fixed.Int26_6) {
   248  		t, i := "", strings.Index(s, " ")
   249  		if i != -1 {
   250  			s, t = s[:i], s[i+1:]
   251  		}
   252  		x, _ := strconv.Atoi(s)
   253  		return t, fixed.Int26_6(x)
   254  	}
   255  
   256  	i := strings.Index(line, ";")
   257  	prefix, line := line[:i], line[i+1:]
   258  
   259  	prefix, ret.advanceWidth = next(prefix)
   260  	prefix, ret.bounds.Min.X = next(prefix)
   261  	prefix, ret.bounds.Min.Y = next(prefix)
   262  	prefix, ret.bounds.Max.X = next(prefix)
   263  	prefix, ret.bounds.Max.Y = next(prefix)
   264  
   265  	ret.points = make([]Point, 0, 1+strings.Count(line, ","))
   266  	for len(line) > 0 {
   267  		s := line
   268  		if i := strings.Index(line, ","); i != -1 {
   269  			s, line = line[:i], line[i+1:]
   270  			for len(line) > 0 && line[0] == ' ' {
   271  				line = line[1:]
   272  			}
   273  		} else {
   274  			line = ""
   275  		}
   276  		s, x := next(s)
   277  		s, y := next(s)
   278  		s, f := next(s)
   279  		ret.points = append(ret.points, Point{X: x, Y: y, Flags: uint32(f)})
   280  	}
   281  	return ret
   282  }
   283  
   284  // scalingTestEquals is equivalent to, but faster than, calling
   285  // reflect.DeepEqual(a, b), and also returns the index of the first non-equal
   286  // element. It also treats a nil []Point and an empty non-nil []Point as equal.
   287  // a and b must have equal length.
   288  func scalingTestEquals(a, b []Point) (index int, equals bool) {
   289  	for i, p := range a {
   290  		if p != b[i] {
   291  			return i, false
   292  		}
   293  	}
   294  	return 0, true
   295  }
   296  
   297  var scalingTestCases = []struct {
   298  	name string
   299  	size int
   300  }{
   301  	{"luxisr", 12},
   302  	{"x-arial-bold", 11},
   303  	{"x-deja-vu-sans-oblique", 17},
   304  	{"x-droid-sans-japanese", 9},
   305  	{"x-times-new-roman", 13},
   306  }
   307  
   308  func testScaling(t *testing.T, h font.Hinting) {
   309  	for _, tc := range scalingTestCases {
   310  		f, testdataIsOptional, err := parseTestdataFont(tc.name)
   311  		if err != nil {
   312  			if testdataIsOptional {
   313  				t.Log(err)
   314  			} else {
   315  				t.Error(err)
   316  			}
   317  			continue
   318  		}
   319  		hintingStr := "sans"
   320  		if h != font.HintingNone {
   321  			hintingStr = "with"
   322  		}
   323  		testFile, err := os.Open(fmt.Sprintf(
   324  			"../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hintingStr))
   325  		if err != nil {
   326  			t.Errorf("%s: Open: %v", tc.name, err)
   327  			continue
   328  		}
   329  		defer testFile.Close()
   330  
   331  		wants := []scalingTestData{}
   332  		scanner := bufio.NewScanner(testFile)
   333  		if scanner.Scan() {
   334  			major, minor, patch := 0, 0, 0
   335  			_, err := fmt.Sscanf(scanner.Text(), "freetype version %d.%d.%d", &major, &minor, &patch)
   336  			if err != nil {
   337  				t.Errorf("%s: version information: %v", tc.name, err)
   338  			}
   339  			if (major < 2) || (major == 2 && minor < 5) || (major == 2 && minor == 5 && patch < 1) {
   340  				t.Errorf("%s: need freetype version >= 2.5.1.\n"+
   341  					"Try setting LD_LIBRARY_PATH=/path/to/freetype_built_from_src/objs/.libs/\n"+
   342  					"and re-running testdata/make-other-hinting-txts.sh",
   343  					tc.name)
   344  				continue
   345  			}
   346  		} else {
   347  			t.Errorf("%s: no version information", tc.name)
   348  			continue
   349  		}
   350  		for scanner.Scan() {
   351  			wants = append(wants, scalingTestParse(scanner.Text()))
   352  		}
   353  		if err := scanner.Err(); err != nil && err != io.EOF {
   354  			t.Errorf("%s: Scanner: %v", tc.name, err)
   355  			continue
   356  		}
   357  
   358  		glyphBuf := &GlyphBuf{}
   359  		for i, want := range wants {
   360  			if err = glyphBuf.Load(f, fixed.I(tc.size), Index(i), h); err != nil {
   361  				t.Errorf("%s: glyph #%d: Load: %v", tc.name, i, err)
   362  				continue
   363  			}
   364  			got := scalingTestData{
   365  				advanceWidth: glyphBuf.AdvanceWidth,
   366  				bounds:       glyphBuf.Bounds,
   367  				points:       glyphBuf.Points,
   368  			}
   369  
   370  			if got.advanceWidth != want.advanceWidth {
   371  				t.Errorf("%s: glyph #%d advance width:\ngot  %v\nwant %v",
   372  					tc.name, i, got.advanceWidth, want.advanceWidth)
   373  				continue
   374  			}
   375  
   376  			if got.bounds != want.bounds {
   377  				t.Errorf("%s: glyph #%d bounds:\ngot  %v\nwant %v",
   378  					tc.name, i, got.bounds, want.bounds)
   379  				continue
   380  			}
   381  
   382  			for i := range got.points {
   383  				got.points[i].Flags &= 0x01
   384  			}
   385  			if len(got.points) != len(want.points) {
   386  				t.Errorf("%s: glyph #%d:\ngot  %v\nwant %v\ndifferent slice lengths: %d versus %d",
   387  					tc.name, i, got.points, want.points, len(got.points), len(want.points))
   388  				continue
   389  			}
   390  			if j, equals := scalingTestEquals(got.points, want.points); !equals {
   391  				t.Errorf("%s: glyph #%d:\ngot  %v\nwant %v\nat index %d: %v versus %v",
   392  					tc.name, i, got.points, want.points, j, got.points[j], want.points[j])
   393  				continue
   394  			}
   395  		}
   396  	}
   397  }
   398  
   399  func TestScalingHintingNone(t *testing.T) { testScaling(t, font.HintingNone) }
   400  func TestScalingHintingFull(t *testing.T) { testScaling(t, font.HintingFull) }
   401  

View as plain text