diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go index 340f61d..ea90692 100644 --- a/freetype/truetype/hint.go +++ b/freetype/truetype/hint.go @@ -18,6 +18,9 @@ type hinter struct { } func (h *hinter) run(program []byte) error { + if len(program) > 50000 { + return errors.New("truetype: hinting: too many instructions") + } var ( steps, pc, top int opcode uint8 @@ -25,7 +28,7 @@ func (h *hinter) run(program []byte) error { for int(pc) < len(program) { steps++ if steps == 100000 { - return errors.New("truetype: hinting: too many instructions") + return errors.New("truetype: hinting: too many steps") } opcode = program[pc] if popCount[opcode] == q { @@ -36,6 +39,10 @@ func (h *hinter) run(program []byte) error { } switch opcode { + case opELSE: + opcode = 1 + goto ifelse + case opDUP: if int(top) >= len(h.stack) { return errors.New("truetype: hinting: stack overflow") @@ -78,6 +85,9 @@ func (h *hinter) run(program []byte) error { opcode = 0x80 goto push + case opDEBUG: + // No-op. + case opLT: h.stack[top-2] = bool2int32(h.stack[top-2] < h.stack[top-1]) top-- @@ -113,6 +123,16 @@ func (h *hinter) run(program []byte) error { case opNOT: h.stack[top-1] = bool2int32(h.stack[top-1] == 0) + case opIF: + top-- + if h.stack[top] == 0 { + opcode = 0 + goto ifelse + } + + case opEIF: + // No-op. + case opADD: h.stack[top-2] += h.stack[top-1] top-- @@ -162,8 +182,55 @@ func (h *hinter) run(program []byte) error { pc++ continue + ifelse: + // Skip past bytecode until the next ELSE (if opcode == 0) or the + // next EIF (for all opcodes). Opcode == 0 means that we have come + // from an IF. Opcode == 1 means that we have come from an ELSE. + { + ifelseloop: + for depth := 0; ; { + pc++ + if pc >= len(program) { + return errors.New("truetype: hinting: unbalanced IF or ELSE") + } + switch program[pc] { + case opIF: + depth++ + case opELSE: + if depth == 0 && opcode == 0 { + break ifelseloop + } + case opEIF: + depth-- + if depth < 0 { + break ifelseloop + } + case opNPUSHB: + pc++ + if pc >= len(program) { + return errors.New("truetype: hinting: unbalanced IF or ELSE") + } + pc += int(program[pc]) + case opNPUSHW: + pc++ + if pc >= len(program) { + return errors.New("truetype: hinting: unbalanced IF or ELSE") + } + pc += 2 * int(program[pc]) + case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111: + pc += int(program[pc] - (opPUSHB000 - 1)) + case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111: + pc += 2 * int(program[pc]-(opPUSHW000-1)) + default: + // No-op. + } + } + pc++ + continue + } + push: - // push n elements from the program to the stack, where n is the low 7 bits of + // 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. diff --git a/freetype/truetype/hint_test.go b/freetype/truetype/hint_test.go index 68c0621..61f4300 100644 --- a/freetype/truetype/hint_test.go +++ b/freetype/truetype/hint_test.go @@ -26,6 +26,16 @@ func TestBytecode(t *testing.T) { nil, "underflow", }, + { + "unbalanced if/else", + []byte{ + opPUSHB000, // [0] + 0, + opIF, + }, + nil, + "unbalanced", + }, { "stack ops", []byte{ @@ -102,6 +112,150 @@ func TestBytecode(t *testing.T) { []int32{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{ diff --git a/freetype/truetype/opcodes.go b/freetype/truetype/opcodes.go index eb50056..e8956fd 100644 --- a/freetype/truetype/opcodes.go +++ b/freetype/truetype/opcodes.go @@ -37,7 +37,7 @@ const ( opRTG = 0x18 opRTHG = 0x19 opSMD = 0x1a - opELSE = 0x1b + opELSE = 0x1b // ELSE clause opJMPR = 0x1c opSCVTCI = 0x1d opSSWCI = 0x1e @@ -89,7 +89,7 @@ const ( opMPS = 0x4c opFLIPON = 0x4d opFLIPOFF = 0x4e - opDEBUG = 0x4f + opDEBUG = 0x4f // DEBUG call opLT = 0x50 // Less Than opLTEQ = 0x51 // Less Than or EQual opGT = 0x52 // Greater Than @@ -98,8 +98,8 @@ const ( opNEQ = 0x55 // Not EQual opODD = 0x56 opEVEN = 0x57 - opIF = 0x58 - opEIF = 0x59 + opIF = 0x58 // IF test + opEIF = 0x59 // End IF opAND = 0x5a // logical AND opOR = 0x5b // logical OR opNOT = 0x5c // logical NOT @@ -272,11 +272,11 @@ const ( 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 + q, q, q, q, q, q, q, q, q, q, q, 0, 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 + 0, 0, q, q, q, q, q, q, q, q, q, q, q, q, q, 0, // 0x40 - 0x4f + 2, 2, 2, 2, 2, 2, q, q, 1, 0, 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