From 8ed9e9345e418be839baa5cde1bb5a356e3ba895 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Tue, 3 Jul 2012 20:46:16 +1000 Subject: [PATCH] freetype/truetype: first cut of a bytecode interpreter. R=gri, bsiegert CC=golang-dev http://codereview.appspot.com/6347057 --- freetype/truetype/hint.go | 210 +++++++++++++++++++++++ freetype/truetype/hint_test.go | 181 ++++++++++++++++++++ freetype/truetype/opcodes.go | 293 +++++++++++++++++++++++++++++++++ freetype/truetype/truetype.go | 2 +- 4 files changed, 685 insertions(+), 1 deletion(-) create mode 100644 freetype/truetype/hint.go create mode 100644 freetype/truetype/hint_test.go create mode 100644 freetype/truetype/opcodes.go diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go new file mode 100644 index 0000000..340f61d --- /dev/null +++ b/freetype/truetype/hint.go @@ -0,0 +1,210 @@ +// 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 + +// This file implements a Truetype bytecode interpreter. +// The opcodes are described at https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html + +import ( + "errors" +) + +type hinter struct { + stack [800]int32 + // TODO: add more state, as per https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html +} + +func (h *hinter) run(program []byte) error { + var ( + steps, pc, top int + opcode uint8 + ) + for int(pc) < len(program) { + steps++ + if steps == 100000 { + return errors.New("truetype: hinting: too many instructions") + } + opcode = program[pc] + if popCount[opcode] == q { + return errors.New("truetype: hinting: unimplemented instruction") + } + if top < int(popCount[opcode]) { + return errors.New("truetype: hinting: stack underflow") + } + switch opcode { + + case opDUP: + if int(top) >= len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + h.stack[top] = h.stack[top-1] + top++ + + case opPOP: + top-- + + case opCLEAR: + top = 0 + + case opSWAP: + h.stack[top-1], h.stack[top-2] = h.stack[top-2], h.stack[top-1] + + case opDEPTH: + if int(top) >= len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + h.stack[top] = int32(top) + top++ + + case opCINDEX, opMINDEX: + x := int(h.stack[top-1]) + if x <= 0 || x >= top { + return errors.New("truetype: hinting: invalid data") + } + h.stack[top-1] = h.stack[top-1-x] + if opcode == opMINDEX { + copy(h.stack[top-1-x:top-1], h.stack[top-x:top]) + top-- + } + + case opNPUSHB: + opcode = 0 + goto push + + case opNPUSHW: + opcode = 0x80 + goto push + + case opLT: + h.stack[top-2] = bool2int32(h.stack[top-2] < h.stack[top-1]) + top-- + + case opLTEQ: + h.stack[top-2] = bool2int32(h.stack[top-2] <= h.stack[top-1]) + top-- + + case opGT: + h.stack[top-2] = bool2int32(h.stack[top-2] > h.stack[top-1]) + top-- + + case opGTEQ: + h.stack[top-2] = bool2int32(h.stack[top-2] >= h.stack[top-1]) + top-- + + case opEQ: + h.stack[top-2] = bool2int32(h.stack[top-2] == h.stack[top-1]) + top-- + + case opNEQ: + h.stack[top-2] = bool2int32(h.stack[top-2] != h.stack[top-1]) + top-- + + case opAND: + h.stack[top-2] = bool2int32(h.stack[top-2] != 0 && h.stack[top-1] != 0) + top-- + + case opOR: + h.stack[top-2] = bool2int32(h.stack[top-2]|h.stack[top-1] != 0) + top-- + + case opNOT: + h.stack[top-1] = bool2int32(h.stack[top-1] == 0) + + case opADD: + h.stack[top-2] += h.stack[top-1] + top-- + + case opSUB: + h.stack[top-2] -= h.stack[top-1] + top-- + + case opDIV: + if h.stack[top-1] == 0 { + return errors.New("truetype: hinting: division by zero") + } + h.stack[top-2] = int32((int64(h.stack[top-2]) << 6) / int64(h.stack[top-1])) + top-- + + case opMUL: + h.stack[top-2] = int32((int64(h.stack[top-2]) * int64(h.stack[top-1])) >> 6) + top-- + + case opABS: + if h.stack[top-1] < 0 { + h.stack[top-1] = -h.stack[top-1] + } + + case opNEG: + h.stack[top-1] = -h.stack[top-1] + + case opFLOOR: + h.stack[top-1] &^= 63 + + case opCEILING: + h.stack[top-1] += 63 + h.stack[top-1] &^= 63 + + case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111: + opcode -= opPUSHB000 - 1 + goto push + + case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111: + opcode -= opPUSHW000 - 1 + opcode += 0x80 + goto push + + default: + return errors.New("truetype: hinting: unrecognized instruction") + } + pc++ + continue + + push: + // push n elements from the program to the stack, where n is the low 7 bits of + // opcode. If the low 7 bits are zero, then n is the next byte from the program. + // The high bit being 0 means that the elements are zero-extended bytes. + // The high bit being 1 means that the elements are sign-extended words. + { + width := 1 + if opcode&0x80 != 0 { + opcode &^= 0x80 + width = 2 + } + if opcode == 0 { + pc++ + if int(pc) >= len(program) { + return errors.New("truetype: hinting: insufficient data") + } + opcode = program[pc] + } + pc++ + if top+int(opcode) > len(h.stack) { + return errors.New("truetype: hinting: stack overflow") + } + if pc+width*int(opcode) > len(program) { + return errors.New("truetype: hinting: insufficient data") + } + for ; opcode > 0; opcode-- { + if width == 1 { + h.stack[top] = int32(program[pc]) + } else { + h.stack[top] = int32(int8(program[pc]))<<8 | int32(program[pc+1]) + } + top++ + pc += width + } + continue + } + } + return nil +} + +func bool2int32(b bool) int32 { + if b { + return 1 + } + return 0 +} diff --git a/freetype/truetype/hint_test.go b/freetype/truetype/hint_test.go new file mode 100644 index 0000000..68c0621 --- /dev/null +++ b/freetype/truetype/hint_test.go @@ -0,0 +1,181 @@ +// 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" +) + +func TestBytecode(t *testing.T) { + testCases := []struct { + desc string + prog []byte + want []int32 + errStr string + }{ + { + "underflow", + []byte{ + opDUP, + }, + nil, + "underflow", + }, + { + "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}, + "", + }, + { + "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}, + "", + }, + { + "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}, + "", + }, + } + + for _, tc := range testCases { + h := &hinter{} + err, errStr := h.run(tc.prog), "" + 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("got %v, want %v", got, tc.want) + continue + } + } +} diff --git a/freetype/truetype/opcodes.go b/freetype/truetype/opcodes.go new file mode 100644 index 0000000..eb50056 --- /dev/null +++ b/freetype/truetype/opcodes.go @@ -0,0 +1,293 @@ +// 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 + +// The Truetype opcodes are summarized at https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html + +// TODO: the opXXX constants without end-of-line comments are not yet implemented. + +const ( + opSVTCA0 = 0x00 + opSVTCA1 = 0x01 + opSPVTCA0 = 0x02 + opSPVTCA1 = 0x03 + opSFVTCA0 = 0x04 + opSFVTCA1 = 0x05 + opSPVTL0 = 0x06 + opSPVTL1 = 0x07 + opSFVTL0 = 0x08 + opSFVTL1 = 0x09 + opSFVFS = 0x0b + opSPVFS = 0x0a + opGPV = 0x0c + opGFV = 0x0d + opSFVTPV = 0x0e + opISECT = 0x0f + opSRP0 = 0x10 + opSRP1 = 0x11 + opSRP2 = 0x12 + opSZP0 = 0x13 + opSZP1 = 0x14 + opSZP2 = 0x15 + opSZPS = 0x16 + opSLOOP = 0x17 + opRTG = 0x18 + opRTHG = 0x19 + opSMD = 0x1a + opELSE = 0x1b + opJMPR = 0x1c + opSCVTCI = 0x1d + opSSWCI = 0x1e + opSSW = 0x1f + opDUP = 0x20 // DUPlicate top stack element + opPOP = 0x21 // POP top stack element + opCLEAR = 0x22 // CLEAR the stack + opSWAP = 0x23 // SWAP the top two elements on the stack + opDEPTH = 0x24 // DEPTH of the stack + opCINDEX = 0x25 // Copy the INDEXed element to the top of the stack + opMINDEX = 0x26 // Move the INDEXed element to the top of the stack + opALIGNPTS = 0x27 + op_0x28 = 0x28 + opUTP = 0x29 + opLOOPCALL = 0x2a + opCALL = 0x2b + opFDEF = 0x2c + opENDF = 0x2d + opMDAP0 = 0x2e + opMDAP1 = 0x2f + opIUP0 = 0x30 + opIUP1 = 0x31 + opSHP0 = 0x32 + opSHP1 = 0x33 + opSHC0 = 0x34 + opSHC1 = 0x35 + opSHZ0 = 0x36 + opSHZ1 = 0x37 + opSHPIX = 0x38 + opIP = 0x39 + opMSIRP0 = 0x3a + opMSIRP1 = 0x3b + opALIGNRP = 0x3c + opRTGD = 0x3d + opMIAP0 = 0x3e + opMIAP1 = 0x3f + opNPUSHB = 0x40 // PUSH N Bytes + opNPUSHW = 0x41 // PUSH N Words + opWS = 0x42 + opRS = 0x43 + opWCVTP = 0x44 + opRCVT = 0x45 + opGC0 = 0x46 + opGC1 = 0x47 + opSCFS = 0x48 + opMD0 = 0x49 + opMD1 = 0x4a + opMPPEM = 0x4b + opMPS = 0x4c + opFLIPON = 0x4d + opFLIPOFF = 0x4e + opDEBUG = 0x4f + opLT = 0x50 // Less Than + opLTEQ = 0x51 // Less Than or EQual + opGT = 0x52 // Greater Than + opGTEQ = 0x53 // Greater Than or EQual + opEQ = 0x54 // EQual + opNEQ = 0x55 // Not EQual + opODD = 0x56 + opEVEN = 0x57 + opIF = 0x58 + opEIF = 0x59 + opAND = 0x5a // logical AND + opOR = 0x5b // logical OR + opNOT = 0x5c // logical NOT + opDELTAP1 = 0x5d + opSDB = 0x5e + opSDS = 0x5f + opADD = 0x60 // ADD + opSUB = 0x61 // SUBtract + opDIV = 0x62 // DIVide + opMUL = 0x63 // MULtiply + opABS = 0x64 // ABSolute value + opNEG = 0x65 // NEGate + opFLOOR = 0x66 // FLOOR + opCEILING = 0x67 // CEILING + opROUND00 = 0x68 + opROUND01 = 0x69 + opROUND10 = 0x6a + opROUND11 = 0x6b + opNROUND00 = 0x6c + opNROUND01 = 0x6d + opNROUND10 = 0x6e + opNROUND11 = 0x6f + opWCVTF = 0x70 + opDELTAP2 = 0x71 + opDELTAP3 = 0x72 + opDELTAC1 = 0x73 + opDELTAC2 = 0x74 + opDELTAC3 = 0x75 + opSROUND = 0x76 + opS45ROUND = 0x77 + opJROT = 0x78 + opJROF = 0x79 + opROFF = 0x7a + op_0x7b = 0x7b + opRUTG = 0x7c + opRDTG = 0x7d + opSANGW = 0x7e + opAA = 0x7f + opFLIPPT = 0x80 + opFLIPRGON = 0x81 + opFLIPRGOFF = 0x82 + op_0x83 = 0x83 + op_0x84 = 0x84 + opSCANCTRL = 0x85 + opSDPVTL0 = 0x86 + opSDPVTL1 = 0x87 + opGETINFO = 0x88 + opIFDEF = 0x89 + opROLL = 0x8a + opMAX = 0x8b + opMIN = 0x8c + opSCANTYPE = 0x8d + opINSTCTRL = 0x8e + op_0x8f = 0x8f + op_0x90 = 0x90 + op_0x91 = 0x91 + op_0x92 = 0x92 + op_0x93 = 0x93 + op_0x94 = 0x94 + op_0x95 = 0x95 + op_0x96 = 0x96 + op_0x97 = 0x97 + op_0x98 = 0x98 + op_0x99 = 0x99 + op_0x9a = 0x9a + op_0x9b = 0x9b + op_0x9c = 0x9c + op_0x9d = 0x9d + op_0x9e = 0x9e + op_0x9f = 0x9f + op_0xa0 = 0xa0 + op_0xa1 = 0xa1 + op_0xa2 = 0xa2 + op_0xa3 = 0xa3 + op_0xa4 = 0xa4 + op_0xa5 = 0xa5 + op_0xa6 = 0xa6 + op_0xa7 = 0xa7 + op_0xa8 = 0xa8 + op_0xa9 = 0xa9 + op_0xaa = 0xaa + op_0xab = 0xab + op_0xac = 0xac + op_0xad = 0xad + op_0xae = 0xae + op_0xaf = 0xaf + opPUSHB000 = 0xb0 // PUSH Bytes + opPUSHB001 = 0xb1 // . + opPUSHB010 = 0xb2 // . + opPUSHB011 = 0xb3 // . + opPUSHB100 = 0xb4 // . + opPUSHB101 = 0xb5 // . + opPUSHB110 = 0xb6 // . + opPUSHB111 = 0xb7 // . + opPUSHW000 = 0xb8 // PUSH Words + opPUSHW001 = 0xb9 // . + opPUSHW010 = 0xba // . + opPUSHW011 = 0xbb // . + opPUSHW100 = 0xbc // . + opPUSHW101 = 0xbd // . + opPUSHW110 = 0xbe // . + opPUSHW111 = 0xbf // . + opMDRP00000 = 0xc0 + opMDRP00001 = 0xc1 + opMDRP00010 = 0xc2 + opMDRP00011 = 0xc3 + opMDRP00100 = 0xc4 + opMDRP00101 = 0xc5 + opMDRP00110 = 0xc6 + opMDRP00111 = 0xc7 + opMDRP01000 = 0xc8 + opMDRP01001 = 0xc9 + opMDRP01010 = 0xca + opMDRP01011 = 0xcb + opMDRP01100 = 0xcc + opMDRP01101 = 0xcd + opMDRP01110 = 0xce + opMDRP01111 = 0xcf + opMDRP10000 = 0xd0 + opMDRP10001 = 0xd1 + opMDRP10010 = 0xd2 + opMDRP10011 = 0xd3 + opMDRP10100 = 0xd4 + opMDRP10101 = 0xd5 + opMDRP10110 = 0xd6 + opMDRP10111 = 0xd7 + opMDRP11000 = 0xd8 + opMDRP11001 = 0xd9 + opMDRP11010 = 0xda + opMDRP11011 = 0xdb + opMDRP11100 = 0xdd + opMDRP11101 = 0xdc + opMDRP11110 = 0xde + opMDRP11111 = 0xdf + opMIRP00000 = 0xe0 + opMIRP00001 = 0xe1 + opMIRP00010 = 0xe2 + opMIRP00011 = 0xe3 + opMIRP00100 = 0xe4 + opMIRP00101 = 0xe5 + opMIRP00110 = 0xe6 + opMIRP00111 = 0xe7 + opMIRP01000 = 0xe8 + opMIRP01001 = 0xe9 + opMIRP01010 = 0xea + opMIRP01011 = 0xeb + opMIRP01100 = 0xec + opMIRP01101 = 0xed + opMIRP01110 = 0xee + opMIRP01111 = 0xef + opMIRP10000 = 0xf0 + opMIRP10001 = 0xf1 + opMIRP10010 = 0xf2 + opMIRP10011 = 0xf3 + opMIRP10100 = 0xf4 + opMIRP10101 = 0xf5 + opMIRP10110 = 0xf6 + opMIRP10111 = 0xf7 + opMIRP11000 = 0xf8 + opMIRP11001 = 0xf9 + opMIRP11010 = 0xfa + opMIRP11011 = 0xfb + opMIRP11100 = 0xfd + opMIRP11101 = 0xfc + opMIRP11110 = 0xfe + opMIRP11111 = 0xff +) + +// popCount is the number of stack elements that each opcode pops. +var popCount = [256]uint8{ + // 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x00 - 0x0f + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x10 - 0x1f + 1, 1, 0, 2, 0, 1, 1, q, q, q, q, q, q, q, q, q, // 0x20 - 0x2f + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x30 - 0x3f + 0, 0, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x40 - 0x4f + 2, 2, 2, 2, 2, 2, q, q, q, q, 2, 2, 1, q, q, q, // 0x50 - 0x5f + 2, 2, 2, 2, 1, 1, 1, 1, q, q, q, q, q, q, q, q, // 0x60 - 0x6f + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x70 - 0x7f + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x80 - 0x8f + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x90 - 0x9f + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xa0 - 0xaf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xc0 - 0xcf + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xd0 - 0xdf + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xe0 - 0xef + q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xf0 - 0xff +} + +// popCount[opcode] == q means that that opcode is not yet implemented. +const q = 255 diff --git a/freetype/truetype/truetype.go b/freetype/truetype/truetype.go index 5204a3e..7e2e564 100644 --- a/freetype/truetype/truetype.go +++ b/freetype/truetype/truetype.go @@ -3,7 +3,7 @@ // FreeType License or the GNU General Public License version 2 (or // any later version), both of which can be found in the LICENSE file. -// The truetype package provides a parser for the TTF and TTC file formats. +// Package truetype provides a parser for the TTF and TTC file formats. // Those formats are documented at http://developer.apple.com/fonts/TTRefMan/ // and http://www.microsoft.com/typography/otspec/ //