From e3b4bc4c1f4c4d94465d02ea5dd440965c223737 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Tue, 30 Jul 2013 19:20:31 +1000 Subject: [PATCH] freetype/truetype: function call opcodes. R=bsiegert CC=golang-dev https://codereview.appspot.com/11983043 --- freetype/truetype/glyph.go | 3 + freetype/truetype/hint.go | 183 +++++++++++++++++++++++++++------ freetype/truetype/hint_test.go | 68 ++++++++++-- freetype/truetype/opcodes.go | 10 +- freetype/truetype/truetype.go | 11 +- 5 files changed, 228 insertions(+), 47 deletions(-) diff --git a/freetype/truetype/glyph.go b/freetype/truetype/glyph.go index 7ddf87e..c6c9300 100644 --- a/freetype/truetype/glyph.go +++ b/freetype/truetype/glyph.go @@ -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 diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go index 9e5ee5d..cf5489b 100644 --- a/freetype/truetype/hint.go +++ b/freetype/truetype/hint.go @@ -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 diff --git a/freetype/truetype/hint_test.go b/freetype/truetype/hint_test.go index d29001c..81182d2 100644 --- a/freetype/truetype/hint_test.go +++ b/freetype/truetype/hint_test.go @@ -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 } } diff --git a/freetype/truetype/opcodes.go b/freetype/truetype/opcodes.go index 8a3b38f..e963793 100644 --- a/freetype/truetype/opcodes.go +++ b/freetype/truetype/opcodes.go @@ -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 diff --git a/freetype/truetype/truetype.go b/freetype/truetype/truetype.go index 57bc9eb..a7cf4e6 100644 --- a/freetype/truetype/truetype.go +++ b/freetype/truetype/truetype.go @@ -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