freetype/truetype/hint_test.go

676 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)
}
}
}