1
2
3
4
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
26
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
50
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
125
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
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
244
245
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
285
286
287
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