freetype/truetype: function call opcodes.

R=bsiegert
CC=golang-dev
https://codereview.appspot.com/11983043
This commit is contained in:
Nigel Tao 2013-07-30 19:20:31 +10:00
parent 28cc5fbc5d
commit e3b4bc4c1f
5 changed files with 228 additions and 47 deletions

View file

@ -124,6 +124,9 @@ func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error {
g.Point[i].Y = f.scale(scale * g.Point[i].Y)
}
if h != nil {
if err := h.init(f, scale); err != nil {
return err
}
// TODO: invoke h.
}
return nil

View file

@ -12,12 +12,28 @@ import (
"errors"
)
// callStackEntry is a bytecode call stack entry.
type callStackEntry struct {
program []byte
pc int
loopCount int32
}
// Hinter implements bytecode hinting. Pass a Hinter to GlyphBuf.Load to hint
// the resulting glyph. A Hinter can be re-used to hint a series of glyphs from
// a Font.
type Hinter struct {
stack, store []int32
// functions is a map from function number to bytecode.
functions map[int32][]byte
// font and scale are the font and scale last used for this Hinter.
// Changing the font will require running the new font's fpgm bytecode.
// Changing either will require running the font's prep bytecode.
font *Font
scale int32
// The fields below constitue the graphics state, which is described at
// https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
@ -31,17 +47,44 @@ type Hinter struct {
roundPeriod, roundPhase, roundThreshold f26dot6
}
func (h *Hinter) init(f *Font) {
if x := int(f.maxStackElements); x > len(h.stack) {
x += 255
x &^= 255
h.stack = make([]int32, x)
func (h *Hinter) init(f *Font, scale int32) error {
rescale := h.scale != scale
if h.font != f {
h.font, rescale = f, true
if h.functions == nil {
h.functions = make(map[int32][]byte)
} else {
for k := range h.functions {
delete(h.functions, k)
}
}
if x := int(f.maxStackElements); x > len(h.stack) {
x += 255
x &^= 255
h.stack = make([]int32, x)
}
if x := int(f.maxStorage); x > len(h.store) {
x += 15
x &^= 15
h.store = make([]int32, x)
}
if len(f.fpgm) != 0 {
if err := h.run(f.fpgm); err != nil {
return err
}
}
}
if x := int(f.maxStorage); x > len(h.store) {
x += 15
x &^= 15
h.store = make([]int32, x)
if rescale {
h.scale = scale
if len(f.prep) != 0 {
if err := h.run(f.prep); err != nil {
return err
}
}
}
return nil
}
func (h *Hinter) run(program []byte) error {
@ -64,7 +107,11 @@ func (h *Hinter) run(program []byte) error {
var (
steps, pc, top int
opcode uint8
callStack [32]callStackEntry
callStackTop int
)
for 0 <= pc && pc < len(program) {
steps++
if steps == 100000 {
@ -199,6 +246,65 @@ func (h *Hinter) run(program []byte) error {
top--
}
case opLOOPCALL, opCALL:
if callStackTop >= len(callStack) {
return errors.New("truetype: hinting: call stack overflow")
}
top--
f, ok := h.functions[h.stack[top]]
if !ok {
return errors.New("truetype: hinting: undefined function")
}
callStack[callStackTop] = callStackEntry{program, pc, 1}
if opcode == opLOOPCALL {
top--
if h.stack[top] == 0 {
break
}
callStack[callStackTop].loopCount = h.stack[top]
}
callStackTop++
program, pc = f, 0
continue
case opFDEF:
// Save all bytecode up until the next ENDF.
startPC := pc + 1
fdefloop:
for {
pc++
if pc >= len(program) {
return errors.New("truetype: hinting: unbalanced FDEF")
}
switch program[pc] {
case opFDEF:
return errors.New("truetype: hinting: nested FDEF")
case opENDF:
top--
h.functions[h.stack[top]] = program[startPC : pc+1]
break fdefloop
default:
var ok bool
pc, ok = skipInstructionPayload(program, pc)
if !ok {
return errors.New("truetype: hinting: unbalanced FDEF")
}
}
}
case opENDF:
if callStackTop == 0 {
return errors.New("truetype: hinting: call stack underflow")
}
callStackTop--
callStack[callStackTop].loopCount--
if callStack[callStackTop].loopCount != 0 {
callStackTop++
pc = 0
continue
}
program, pc = callStack[callStackTop].program, callStack[callStackTop].pc
case opRTDG:
h.roundPeriod = 1 << 5
h.roundPhase = 0
@ -386,7 +492,8 @@ func (h *Hinter) run(program []byte) error {
return errors.New("truetype: hinting: unsupported IDEF instruction")
case opROLL:
h.stack[top-1], h.stack[top-3], h.stack[top-2] = h.stack[top-3], h.stack[top-2], h.stack[top-1]
h.stack[top-1], h.stack[top-3], h.stack[top-2] =
h.stack[top-3], h.stack[top-2], h.stack[top-1]
case opMAX:
top--
@ -400,11 +507,15 @@ func (h *Hinter) run(program []byte) error {
h.stack[top-1] = h.stack[top]
}
case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111:
case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011,
opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111:
opcode -= opPUSHB000 - 1
goto push
case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111:
case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011,
opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111:
opcode -= opPUSHW000 - 1
opcode += 0x80
goto push
@ -438,24 +549,12 @@ func (h *Hinter) run(program []byte) error {
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.
var ok bool
pc, ok = skipInstructionPayload(program, pc)
if !ok {
return errors.New("truetype: hinting: unbalanced IF or ELSE")
}
}
}
pc++
@ -502,6 +601,32 @@ func (h *Hinter) run(program []byte) error {
return nil
}
// skipInstructionPayload increments pc by the extra data that follows a
// variable length PUSHB or PUSHW instruction.
func skipInstructionPayload(program []byte, pc int) (newPC int, ok bool) {
switch program[pc] {
case opNPUSHB:
pc++
if pc >= len(program) {
return 0, false
}
pc += int(program[pc])
case opNPUSHW:
pc++
if pc >= len(program) {
return 0, false
}
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))
}
return pc, true
}
// f2dot14 is a 2.14 fixed point number.
type f2dot14 int16

View file

@ -52,16 +52,16 @@ func TestBytecode(t *testing.T) {
"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 ]
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 ]
opGFV, // [0x4000, 0, 1, 0, -0x4000]
opPUSHB000, // [0x4000, 0, 1, 0, -0x4000, 2]
2,
},
[]int32{0x4000, 0, 1, 0, -0x4000, 2},
@ -505,6 +505,52 @@ func TestBytecode(t *testing.T) {
[]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 {
@ -512,7 +558,7 @@ func TestBytecode(t *testing.T) {
h.init(&Font{
maxStorage: 32,
maxStackElements: 100,
})
}, 768)
err, errStr := h.run(tc.prog), ""
if err != nil {
errStr = err.Error()
@ -531,7 +577,7 @@ func TestBytecode(t *testing.T) {
}
got := h.stack[:len(tc.want)]
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("got %v, want %v", got, tc.want)
t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want)
continue
}
}

View file

@ -52,10 +52,10 @@ const (
opALIGNPTS = 0x27
op_0x28 = 0x28
opUTP = 0x29
opLOOPCALL = 0x2a
opCALL = 0x2b
opFDEF = 0x2c
opENDF = 0x2d
opLOOPCALL = 0x2a // LOOP and CALL function
opCALL = 0x2b // CALL function
opFDEF = 0x2c // Function DEFinition
opENDF = 0x2d // END Function definition
opMDAP0 = 0x2e
opMDAP1 = 0x2f
opIUP0 = 0x30
@ -273,7 +273,7 @@ var popCount = [256]uint8{
// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
0, 0, 0, 0, 0, 0, q, q, q, q, 2, 2, 0, 0, 0, q, // 0x00 - 0x0f
q, q, q, q, q, q, q, 1, 0, 0, 1, 0, 1, q, q, q, // 0x10 - 0x1f
1, 1, 0, 2, 0, 1, 1, q, q, q, q, q, q, q, q, q, // 0x20 - 0x2f
1, 1, 0, 2, 0, 1, 1, q, q, q, 2, 1, 1, 0, q, q, // 0x20 - 0x2f
q, q, q, q, q, q, q, q, q, q, q, q, q, 0, q, q, // 0x30 - 0x3f
0, 0, 2, 1, q, q, q, q, q, q, q, q, q, q, q, 0, // 0x40 - 0x4f
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, q, q, q, // 0x50 - 0x5f

View file

@ -94,8 +94,9 @@ type cm struct {
type Font struct {
// Tables sliced from the TTF data. The different tables are documented
// at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
cmap, glyf, head, hhea, hmtx, kern, loca, maxp []byte
cmapIndexes []byte
cmap, cvt, fpgm, glyf, head, hhea, hmtx, kern, loca, maxp, prep []byte
cmapIndexes []byte
// Cached values derived from the raw ttf data.
cm []cm
@ -411,6 +412,10 @@ func parse(ttf []byte, offset int) (font *Font, err error) {
switch string(ttf[x : x+4]) {
case "cmap":
f.cmap, err = readTable(ttf, ttf[x+8:x+16])
case "cvt ":
f.cvt, err = readTable(ttf, ttf[x+8:x+16])
case "fpgm":
f.fpgm, err = readTable(ttf, ttf[x+8:x+16])
case "glyf":
f.glyf, err = readTable(ttf, ttf[x+8:x+16])
case "head":
@ -425,6 +430,8 @@ func parse(ttf []byte, offset int) (font *Font, err error) {
f.loca, err = readTable(ttf, ttf[x+8:x+16])
case "maxp":
f.maxp, err = readTable(ttf, ttf[x+8:x+16])
case "prep":
f.prep, err = readTable(ttf, ttf[x+8:x+16])
}
if err != nil {
return