2012-07-03 12:46:16 +02:00
|
|
|
// 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 {
|
2012-07-04 13:28:44 +02:00
|
|
|
if len(program) > 50000 {
|
|
|
|
return errors.New("truetype: hinting: too many instructions")
|
|
|
|
}
|
2012-07-03 12:46:16 +02:00
|
|
|
var (
|
|
|
|
steps, pc, top int
|
|
|
|
opcode uint8
|
|
|
|
)
|
2012-07-05 12:29:35 +02:00
|
|
|
for 0 <= pc && int(pc) < len(program) {
|
2012-07-03 12:46:16 +02:00
|
|
|
steps++
|
|
|
|
if steps == 100000 {
|
2012-07-04 13:28:44 +02:00
|
|
|
return errors.New("truetype: hinting: too many steps")
|
2012-07-03 12:46:16 +02:00
|
|
|
}
|
|
|
|
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 {
|
|
|
|
|
2012-07-04 13:28:44 +02:00
|
|
|
case opELSE:
|
|
|
|
opcode = 1
|
|
|
|
goto ifelse
|
|
|
|
|
2012-07-05 12:29:35 +02:00
|
|
|
case opJMPR:
|
|
|
|
top--
|
|
|
|
pc += int(h.stack[top])
|
|
|
|
continue
|
|
|
|
|
2012-07-03 12:46:16 +02:00
|
|
|
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
|
|
|
|
|
2012-07-04 13:28:44 +02:00
|
|
|
case opDEBUG:
|
|
|
|
// No-op.
|
|
|
|
|
2012-07-03 12:46:16 +02:00
|
|
|
case opLT:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] < h.stack[top])
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opLTEQ:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] <= h.stack[top])
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opGT:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] > h.stack[top])
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opGTEQ:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] >= h.stack[top])
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opEQ:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] == h.stack[top])
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opNEQ:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] != h.stack[top])
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opAND:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] != 0 && h.stack[top] != 0)
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opOR:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1]|h.stack[top] != 0)
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opNOT:
|
|
|
|
h.stack[top-1] = bool2int32(h.stack[top-1] == 0)
|
|
|
|
|
2012-07-04 13:28:44 +02:00
|
|
|
case opIF:
|
|
|
|
top--
|
|
|
|
if h.stack[top] == 0 {
|
|
|
|
opcode = 0
|
|
|
|
goto ifelse
|
|
|
|
}
|
|
|
|
|
|
|
|
case opEIF:
|
|
|
|
// No-op.
|
|
|
|
|
2012-07-03 12:46:16 +02:00
|
|
|
case opADD:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] += h.stack[top]
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opSUB:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] -= h.stack[top]
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opDIV:
|
2012-07-05 12:29:35 +02:00
|
|
|
top--
|
|
|
|
if h.stack[top] == 0 {
|
2012-07-03 12:46:16 +02:00
|
|
|
return errors.New("truetype: hinting: division by zero")
|
|
|
|
}
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = int32((int64(h.stack[top-1]) << 6) / int64(h.stack[top]))
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
case opMUL:
|
|
|
|
top--
|
2012-07-05 12:29:35 +02:00
|
|
|
h.stack[top-1] = int32((int64(h.stack[top-1]) * int64(h.stack[top])) >> 6)
|
2012-07-03 12:46:16 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2012-07-05 12:29:35 +02:00
|
|
|
case opJROT:
|
|
|
|
top -= 2
|
|
|
|
if h.stack[top+1] != 0 {
|
|
|
|
pc += int(h.stack[top])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
case opJROF:
|
|
|
|
top -= 2
|
|
|
|
if h.stack[top+1] == 0 {
|
|
|
|
pc += int(h.stack[top])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2012-07-03 12:46:16 +02:00
|
|
|
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
|
|
|
|
|
2012-07-04 13:28:44 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2012-07-03 12:46:16 +02:00
|
|
|
push:
|
2012-07-04 13:28:44 +02:00
|
|
|
// Push n elements from the program to the stack, where n is the low 7 bits of
|
2012-07-03 12:46:16 +02:00
|
|
|
// 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
|
|
|
|
}
|