freetype/truetype: function call opcodes.
R=bsiegert CC=golang-dev https://codereview.appspot.com/11983043
This commit is contained in:
parent
28cc5fbc5d
commit
e3b4bc4c1f
5 changed files with 228 additions and 47 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue