675 lines
13 KiB
Go
675 lines
13 KiB
Go
// Copyright 2012 The Freetype-Go Authors. All rights reserved.
|
|
// Use of this source code is governed by your choice of either the
|
|
// FreeType License or the GNU General Public License version 2 (or
|
|
// any later version), both of which can be found in the LICENSE file.
|
|
|
|
package truetype
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
func TestBytecode(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
prog []byte
|
|
want []int32
|
|
errStr string
|
|
}{
|
|
{
|
|
"underflow",
|
|
[]byte{
|
|
opDUP,
|
|
},
|
|
nil,
|
|
"underflow",
|
|
},
|
|
{
|
|
"infinite loop",
|
|
[]byte{
|
|
opPUSHW000, // [-1]
|
|
0xff,
|
|
0xff,
|
|
opDUP, // [-1, -1]
|
|
opJMPR, // [-1]
|
|
},
|
|
nil,
|
|
"too many steps",
|
|
},
|
|
{
|
|
"unbalanced if/else",
|
|
[]byte{
|
|
opPUSHB000, // [0]
|
|
0,
|
|
opIF,
|
|
},
|
|
nil,
|
|
"unbalanced",
|
|
},
|
|
{
|
|
"vector set/gets",
|
|
[]byte{
|
|
opSVTCA1, // []
|
|
opGPV, // [0x4000, 0]
|
|
opSVTCA0, // [0x4000, 0]
|
|
opGFV, // [0x4000, 0, 0, 0x4000]
|
|
opNEG, // [0x4000, 0, 0, -0x4000]
|
|
opSPVFS, // [0x4000, 0]
|
|
opSFVTPV, // [0x4000, 0]
|
|
opPUSHB000, // [0x4000, 0, 1]
|
|
1,
|
|
opGFV, // [0x4000, 0, 1, 0, -0x4000]
|
|
opPUSHB000, // [0x4000, 0, 1, 0, -0x4000, 2]
|
|
2,
|
|
},
|
|
[]int32{0x4000, 0, 1, 0, -0x4000, 2},
|
|
"",
|
|
},
|
|
{
|
|
"jumps",
|
|
[]byte{
|
|
opPUSHB001, // [10, 2]
|
|
10,
|
|
2,
|
|
opJMPR, // [10]
|
|
opDUP, // not executed
|
|
opDUP, // [10, 10]
|
|
opPUSHB010, // [10, 10, 20, 2, 1]
|
|
20,
|
|
2,
|
|
1,
|
|
opJROT, // [10, 10, 20]
|
|
opDUP, // not executed
|
|
opDUP, // [10, 10, 20, 20]
|
|
opPUSHB010, // [10, 10, 20, 20, 30, 2, 1]
|
|
30,
|
|
2,
|
|
1,
|
|
opJROF, // [10, 10, 20, 20, 30]
|
|
opDUP, // [10, 10, 20, 20, 30, 30]
|
|
opDUP, // [10, 10, 20, 20, 30, 30, 30]
|
|
},
|
|
[]int32{10, 10, 20, 20, 30, 30, 30},
|
|
"",
|
|
},
|
|
{
|
|
"stack ops",
|
|
[]byte{
|
|
opPUSHB010, // [10, 20, 30]
|
|
10,
|
|
20,
|
|
30,
|
|
opCLEAR, // []
|
|
opPUSHB010, // [40, 50, 60]
|
|
40,
|
|
50,
|
|
60,
|
|
opSWAP, // [40, 60, 50]
|
|
opDUP, // [40, 60, 50, 50]
|
|
opDUP, // [40, 60, 50, 50, 50]
|
|
opPOP, // [40, 60, 50, 50]
|
|
opDEPTH, // [40, 60, 50, 50, 4]
|
|
opCINDEX, // [40, 60, 50, 50, 40]
|
|
opPUSHB000, // [40, 60, 50, 50, 40, 4]
|
|
4,
|
|
opMINDEX, // [40, 50, 50, 40, 60]
|
|
},
|
|
[]int32{40, 50, 50, 40, 60},
|
|
"",
|
|
},
|
|
{
|
|
"push ops",
|
|
[]byte{
|
|
opPUSHB000, // [255]
|
|
255,
|
|
opPUSHW001, // [255, -2, 253]
|
|
255,
|
|
254,
|
|
0,
|
|
253,
|
|
opNPUSHB, // [1, -2, 253, 1, 2]
|
|
2,
|
|
1,
|
|
2,
|
|
opNPUSHW, // [1, -2, 253, 1, 2, 0x0405, 0x0607, 0x0809]
|
|
3,
|
|
4,
|
|
5,
|
|
6,
|
|
7,
|
|
8,
|
|
9,
|
|
},
|
|
[]int32{255, -2, 253, 1, 2, 0x0405, 0x0607, 0x0809},
|
|
"",
|
|
},
|
|
{
|
|
"store ops",
|
|
[]byte{
|
|
opPUSHB011, // [1, 22, 3, 44]
|
|
1,
|
|
22,
|
|
3,
|
|
44,
|
|
opWS, // [1, 22]
|
|
opWS, // []
|
|
opPUSHB000, // [3]
|
|
3,
|
|
opRS, // [44]
|
|
},
|
|
[]int32{44},
|
|
"",
|
|
},
|
|
{
|
|
"comparison ops",
|
|
[]byte{
|
|
opPUSHB001, // [10, 20]
|
|
10,
|
|
20,
|
|
opLT, // [1]
|
|
opPUSHB001, // [1, 10, 20]
|
|
10,
|
|
20,
|
|
opLTEQ, // [1, 1]
|
|
opPUSHB001, // [1, 1, 10, 20]
|
|
10,
|
|
20,
|
|
opGT, // [1, 1, 0]
|
|
opPUSHB001, // [1, 1, 0, 10, 20]
|
|
10,
|
|
20,
|
|
opGTEQ, // [1, 1, 0, 0]
|
|
opEQ, // [1, 1, 1]
|
|
opNEQ, // [1, 0]
|
|
},
|
|
[]int32{1, 0},
|
|
"",
|
|
},
|
|
{
|
|
"odd/even",
|
|
// Calculate odd(2+31/64), odd(2+32/64), even(2), even(1).
|
|
[]byte{
|
|
opPUSHB000, // [159]
|
|
159,
|
|
opODD, // [0]
|
|
opPUSHB000, // [0, 160]
|
|
160,
|
|
opODD, // [0, 1]
|
|
opPUSHB000, // [0, 1, 128]
|
|
128,
|
|
opEVEN, // [0, 1, 1]
|
|
opPUSHB000, // [0, 1, 1, 64]
|
|
64,
|
|
opEVEN, // [0, 1, 1, 0]
|
|
},
|
|
[]int32{0, 1, 1, 0},
|
|
"",
|
|
},
|
|
{
|
|
"if true",
|
|
[]byte{
|
|
opPUSHB001, // [255, 1]
|
|
255,
|
|
1,
|
|
opIF,
|
|
opPUSHB000, // [255, 2]
|
|
2,
|
|
opEIF,
|
|
opPUSHB000, // [255, 2, 254]
|
|
254,
|
|
},
|
|
[]int32{255, 2, 254},
|
|
"",
|
|
},
|
|
{
|
|
"if false",
|
|
[]byte{
|
|
opPUSHB001, // [255, 0]
|
|
255,
|
|
0,
|
|
opIF,
|
|
opPUSHB000, // [255]
|
|
2,
|
|
opEIF,
|
|
opPUSHB000, // [255, 254]
|
|
254,
|
|
},
|
|
[]int32{255, 254},
|
|
"",
|
|
},
|
|
{
|
|
"if/else true",
|
|
[]byte{
|
|
opPUSHB000, // [1]
|
|
1,
|
|
opIF,
|
|
opPUSHB000, // [2]
|
|
2,
|
|
opELSE,
|
|
opPUSHB000, // not executed
|
|
3,
|
|
opEIF,
|
|
},
|
|
[]int32{2},
|
|
"",
|
|
},
|
|
{
|
|
"if/else false",
|
|
[]byte{
|
|
opPUSHB000, // [0]
|
|
0,
|
|
opIF,
|
|
opPUSHB000, // not executed
|
|
2,
|
|
opELSE,
|
|
opPUSHB000, // [3]
|
|
3,
|
|
opEIF,
|
|
},
|
|
[]int32{3},
|
|
"",
|
|
},
|
|
{
|
|
"if/else true if/else false",
|
|
// 0x58 is the opcode for opIF. The literal 0x58s below are pushed data.
|
|
[]byte{
|
|
opPUSHB010, // [255, 0, 1]
|
|
255,
|
|
0,
|
|
1,
|
|
opIF,
|
|
opIF,
|
|
opPUSHB001, // not executed
|
|
0x58,
|
|
0x58,
|
|
opELSE,
|
|
opPUSHW000, // [255, 0x5858]
|
|
0x58,
|
|
0x58,
|
|
opEIF,
|
|
opELSE,
|
|
opIF,
|
|
opNPUSHB, // not executed
|
|
3,
|
|
0x58,
|
|
0x58,
|
|
0x58,
|
|
opELSE,
|
|
opNPUSHW, // not executed
|
|
2,
|
|
0x58,
|
|
0x58,
|
|
0x58,
|
|
0x58,
|
|
opEIF,
|
|
opEIF,
|
|
opPUSHB000, // [255, 0x5858, 254]
|
|
254,
|
|
},
|
|
[]int32{255, 0x5858, 254},
|
|
"",
|
|
},
|
|
{
|
|
"if/else false if/else true",
|
|
// 0x58 is the opcode for opIF. The literal 0x58s below are pushed data.
|
|
[]byte{
|
|
opPUSHB010, // [255, 1, 0]
|
|
255,
|
|
1,
|
|
0,
|
|
opIF,
|
|
opIF,
|
|
opPUSHB001, // not executed
|
|
0x58,
|
|
0x58,
|
|
opELSE,
|
|
opPUSHW000, // not executed
|
|
0x58,
|
|
0x58,
|
|
opEIF,
|
|
opELSE,
|
|
opIF,
|
|
opNPUSHB, // [255, 0x58, 0x58, 0x58]
|
|
3,
|
|
0x58,
|
|
0x58,
|
|
0x58,
|
|
opELSE,
|
|
opNPUSHW, // not executed
|
|
2,
|
|
0x58,
|
|
0x58,
|
|
0x58,
|
|
0x58,
|
|
opEIF,
|
|
opEIF,
|
|
opPUSHB000, // [255, 0x58, 0x58, 0x58, 254]
|
|
254,
|
|
},
|
|
[]int32{255, 0x58, 0x58, 0x58, 254},
|
|
"",
|
|
},
|
|
{
|
|
"logical ops",
|
|
[]byte{
|
|
opPUSHB010, // [0, 10, 20]
|
|
0,
|
|
10,
|
|
20,
|
|
opAND, // [0, 1]
|
|
opOR, // [1]
|
|
opNOT, // [0]
|
|
},
|
|
[]int32{0},
|
|
"",
|
|
},
|
|
{
|
|
"arithmetic ops",
|
|
// Calculate abs((-(1 - (2*3)))/2 + 1/64).
|
|
// The answer is 5/2 + 1/64 in ideal numbers, or 161 in 26.6 fixed point math.
|
|
[]byte{
|
|
opPUSHB010, // [64, 128, 192]
|
|
1 << 6,
|
|
2 << 6,
|
|
3 << 6,
|
|
opMUL, // [64, 384]
|
|
opSUB, // [-320]
|
|
opNEG, // [320]
|
|
opPUSHB000, // [320, 128]
|
|
2 << 6,
|
|
opDIV, // [160]
|
|
opPUSHB000, // [160, 1]
|
|
1,
|
|
opADD, // [161]
|
|
opABS, // [161]
|
|
},
|
|
[]int32{161},
|
|
"",
|
|
},
|
|
{
|
|
"floor, ceiling",
|
|
[]byte{
|
|
opPUSHB000, // [96]
|
|
96,
|
|
opFLOOR, // [64]
|
|
opPUSHB000, // [64, 96]
|
|
96,
|
|
opCEILING, // [64, 128]
|
|
},
|
|
[]int32{64, 128},
|
|
"",
|
|
},
|
|
{
|
|
"rounding",
|
|
// Round 1.40625 (which is 90/64) under various rounding policies.
|
|
// See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
|
|
[]byte{
|
|
opROFF, // []
|
|
opPUSHB000, // [90]
|
|
90,
|
|
opROUND00, // [90]
|
|
opRTG, // [90]
|
|
opPUSHB000, // [90, 90]
|
|
90,
|
|
opROUND00, // [90, 64]
|
|
opRTHG, // [90, 64]
|
|
opPUSHB000, // [90, 64, 90]
|
|
90,
|
|
opROUND00, // [90, 64, 96]
|
|
opRDTG, // [90, 64, 96]
|
|
opPUSHB000, // [90, 64, 96, 90]
|
|
90,
|
|
opROUND00, // [90, 64, 96, 64]
|
|
opRUTG, // [90, 64, 96, 64]
|
|
opPUSHB000, // [90, 64, 96, 64, 90]
|
|
90,
|
|
opROUND00, // [90, 64, 96, 64, 128]
|
|
opRTDG, // [90, 64, 96, 64, 128]
|
|
opPUSHB000, // [90, 64, 96, 64, 128, 90]
|
|
90,
|
|
opROUND00, // [90, 64, 96, 64, 128, 96]
|
|
},
|
|
[]int32{90, 64, 96, 64, 128, 96},
|
|
"",
|
|
},
|
|
{
|
|
"super-rounding",
|
|
// See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
|
|
// and the sign preservation steps of the "Order of rounding operations" section.
|
|
[]byte{
|
|
opPUSHB000, // [0x58]
|
|
0x58,
|
|
opSROUND, // []
|
|
opPUSHW000, // [-81]
|
|
0xff,
|
|
0xaf,
|
|
opROUND00, // [-80]
|
|
opPUSHW000, // [-80, -80]
|
|
0xff,
|
|
0xb0,
|
|
opROUND00, // [-80, -80]
|
|
opPUSHW000, // [-80, -80, -17]
|
|
0xff,
|
|
0xef,
|
|
opROUND00, // [-80, -80, -16]
|
|
opPUSHW000, // [-80, -80, -16, -16]
|
|
0xff,
|
|
0xf0,
|
|
opROUND00, // [-80, -80, -16, -16]
|
|
opPUSHB000, // [-80, -80, -16, -16, 0]
|
|
0,
|
|
opROUND00, // [-80, -80, -16, -16, 16]
|
|
opPUSHB000, // [-80, -80, -16, -16, 16, 16]
|
|
16,
|
|
opROUND00, // [-80, -80, -16, -16, 16, 16]
|
|
opPUSHB000, // [-80, -80, -16, -16, 16, 16, 47]
|
|
47,
|
|
opROUND00, // [-80, -80, -16, -16, 16, 16, 16]
|
|
opPUSHB000, // [-80, -80, -16, -16, 16, 16, 16, 48]
|
|
48,
|
|
opROUND00, // [-80, -80, -16, -16, 16, 16, 16, 80]
|
|
},
|
|
[]int32{-80, -80, -16, -16, 16, 16, 16, 80},
|
|
"",
|
|
},
|
|
{
|
|
"roll",
|
|
[]byte{
|
|
opPUSHB010, // [1, 2, 3]
|
|
1,
|
|
2,
|
|
3,
|
|
opROLL, // [2, 3, 1]
|
|
},
|
|
[]int32{2, 3, 1},
|
|
"",
|
|
},
|
|
{
|
|
"max/min",
|
|
[]byte{
|
|
opPUSHW001, // [-2, -3]
|
|
0xff,
|
|
0xfe,
|
|
0xff,
|
|
0xfd,
|
|
opMAX, // [-2]
|
|
opPUSHW001, // [-2, -4, -5]
|
|
0xff,
|
|
0xfc,
|
|
0xff,
|
|
0xfb,
|
|
opMIN, // [-2, -5]
|
|
},
|
|
[]int32{-2, -5},
|
|
"",
|
|
},
|
|
{
|
|
"functions",
|
|
[]byte{
|
|
opPUSHB011, // [3, 7, 0, 3]
|
|
3,
|
|
7,
|
|
0,
|
|
3,
|
|
|
|
opFDEF, // Function #3 (not called)
|
|
opPUSHB000,
|
|
98,
|
|
opENDF,
|
|
|
|
opFDEF, // Function #0
|
|
opDUP,
|
|
opADD,
|
|
opENDF,
|
|
|
|
opFDEF, // Function #7
|
|
opPUSHB001,
|
|
10,
|
|
0,
|
|
opCALL,
|
|
opDUP,
|
|
opENDF,
|
|
|
|
opFDEF, // Function #3 (again)
|
|
opPUSHB000,
|
|
99,
|
|
opENDF,
|
|
|
|
opPUSHB001, // [2, 0]
|
|
2,
|
|
0,
|
|
opCALL, // [4]
|
|
opPUSHB000, // [4, 3]
|
|
3,
|
|
opLOOPCALL, // [99, 99, 99, 99]
|
|
opPUSHB000, // [99, 99, 99, 99, 7]
|
|
7,
|
|
opCALL, // [99, 99, 99, 99, 20, 20]
|
|
},
|
|
[]int32{99, 99, 99, 99, 20, 20},
|
|
"",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
h := &hinter{}
|
|
h.init(&Font{
|
|
maxStorage: 32,
|
|
maxStackElements: 100,
|
|
}, 768)
|
|
err, errStr := h.run(tc.prog, nil, nil, nil, nil), ""
|
|
if err != nil {
|
|
errStr = err.Error()
|
|
}
|
|
if tc.errStr != "" {
|
|
if errStr == "" {
|
|
t.Errorf("%s: got no error, want %q", tc.desc, tc.errStr)
|
|
} else if !strings.Contains(errStr, tc.errStr) {
|
|
t.Errorf("%s: got error %q, want one containing %q", tc.desc, errStr, tc.errStr)
|
|
}
|
|
continue
|
|
}
|
|
if errStr != "" {
|
|
t.Errorf("%s: got error %q, want none", tc.desc, errStr)
|
|
continue
|
|
}
|
|
got := h.stack[:len(tc.want)]
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMove tests that the hinter.move method matches the output of the C
|
|
// Freetype implementation.
|
|
func TestMove(t *testing.T) {
|
|
h, p := hinter{}, Point{}
|
|
testCases := []struct {
|
|
pvX, pvY, fvX, fvY f2dot14
|
|
wantX, wantY fixed.Int26_6
|
|
}{
|
|
{+0x4000, +0x0000, +0x4000, +0x0000, +1000, +0},
|
|
{+0x4000, +0x0000, -0x4000, +0x0000, +1000, +0},
|
|
{-0x4000, +0x0000, +0x4000, +0x0000, -1000, +0},
|
|
{-0x4000, +0x0000, -0x4000, +0x0000, -1000, +0},
|
|
{+0x0000, +0x4000, +0x0000, +0x4000, +0, +1000},
|
|
{+0x0000, +0x4000, +0x0000, -0x4000, +0, +1000},
|
|
{+0x4000, +0x0000, +0x2d41, +0x2d41, +1000, +1000},
|
|
{+0x4000, +0x0000, -0x2d41, +0x2d41, +1000, -1000},
|
|
{+0x4000, +0x0000, +0x2d41, -0x2d41, +1000, -1000},
|
|
{+0x4000, +0x0000, -0x2d41, -0x2d41, +1000, +1000},
|
|
{-0x4000, +0x0000, +0x2d41, +0x2d41, -1000, -1000},
|
|
{-0x4000, +0x0000, -0x2d41, +0x2d41, -1000, +1000},
|
|
{-0x4000, +0x0000, +0x2d41, -0x2d41, -1000, +1000},
|
|
{-0x4000, +0x0000, -0x2d41, -0x2d41, -1000, -1000},
|
|
{+0x376d, +0x2000, +0x2d41, +0x2d41, +732, +732},
|
|
{-0x376d, +0x2000, +0x2d41, +0x2d41, -2732, -2732},
|
|
{+0x376d, +0x2000, +0x2d41, -0x2d41, +2732, -2732},
|
|
{-0x376d, +0x2000, +0x2d41, -0x2d41, -732, +732},
|
|
{-0x376d, -0x2000, +0x2d41, +0x2d41, -732, -732},
|
|
{+0x376d, +0x2000, +0x4000, +0x0000, +1155, +0},
|
|
{+0x376d, +0x2000, +0x0000, +0x4000, +0, +2000},
|
|
}
|
|
for _, tc := range testCases {
|
|
p = Point{}
|
|
h.gs.pv = [2]f2dot14{tc.pvX, tc.pvY}
|
|
h.gs.fv = [2]f2dot14{tc.fvX, tc.fvY}
|
|
h.move(&p, 1000, true)
|
|
tx := p.Flags&flagTouchedX != 0
|
|
ty := p.Flags&flagTouchedY != 0
|
|
wantTX := tc.fvX != 0
|
|
wantTY := tc.fvY != 0
|
|
if p.X != tc.wantX || p.Y != tc.wantY || tx != wantTX || ty != wantTY {
|
|
t.Errorf("pv=%v, fv=%v\ngot %d, %d, %t, %t\nwant %d, %d, %t, %t",
|
|
h.gs.pv, h.gs.fv, p.X, p.Y, tx, ty, tc.wantX, tc.wantY, wantTX, wantTY)
|
|
continue
|
|
}
|
|
|
|
// Check that p is aligned with the freedom vector.
|
|
a := int64(p.X) * int64(tc.fvY)
|
|
b := int64(p.Y) * int64(tc.fvX)
|
|
if a != b {
|
|
t.Errorf("pv=%v, fv=%v, p=%v not aligned with fv", h.gs.pv, h.gs.fv, p)
|
|
continue
|
|
}
|
|
|
|
// Check that the projected p is 1000 away from the origin.
|
|
dotProd := (int64(p.X)*int64(tc.pvX) + int64(p.Y)*int64(tc.pvY) + 1<<13) >> 14
|
|
if dotProd != 1000 {
|
|
t.Errorf("pv=%v, fv=%v, p=%v not 1000 from origin", h.gs.pv, h.gs.fv, p)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestNormalize tests that the normalize function matches the output of the C
|
|
// Freetype implementation.
|
|
func TestNormalize(t *testing.T) {
|
|
testCases := [][2]f2dot14{
|
|
{-15895, 3974},
|
|
{-15543, 5181},
|
|
{-14654, 7327},
|
|
{-11585, 11585},
|
|
{0, 16384},
|
|
{11585, 11585},
|
|
{14654, 7327},
|
|
{15543, 5181},
|
|
{15895, 3974},
|
|
{16066, 3213},
|
|
{16161, 2694},
|
|
{16219, 2317},
|
|
{16257, 2032},
|
|
{16284, 1809},
|
|
}
|
|
for i, want := range testCases {
|
|
got := normalize(f2dot14(i)-4, 1)
|
|
if got != want {
|
|
t.Errorf("i=%d: got %v, want %v", i, got, want)
|
|
}
|
|
}
|
|
}
|