freetype/truetype: function call opcodes.
R=bsiegert CC=golang-dev https://codereview.appspot.com/11983043
This commit is contained in:
parent
28cc5fbc5d
commit
e3b4bc4c1f
|
@ -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)
|
g.Point[i].Y = f.scale(scale * g.Point[i].Y)
|
||||||
}
|
}
|
||||||
if h != nil {
|
if h != nil {
|
||||||
|
if err := h.init(f, scale); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// TODO: invoke h.
|
// TODO: invoke h.
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -12,12 +12,28 @@ import (
|
||||||
"errors"
|
"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
|
// 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
|
// the resulting glyph. A Hinter can be re-used to hint a series of glyphs from
|
||||||
// a Font.
|
// a Font.
|
||||||
type Hinter struct {
|
type Hinter struct {
|
||||||
stack, store []int32
|
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
|
// The fields below constitue the graphics state, which is described at
|
||||||
// https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
|
// https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
|
||||||
|
|
||||||
|
@ -31,17 +47,44 @@ type Hinter struct {
|
||||||
roundPeriod, roundPhase, roundThreshold f26dot6
|
roundPeriod, roundPhase, roundThreshold f26dot6
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hinter) init(f *Font) {
|
func (h *Hinter) init(f *Font, scale int32) error {
|
||||||
if x := int(f.maxStackElements); x > len(h.stack) {
|
rescale := h.scale != scale
|
||||||
x += 255
|
if h.font != f {
|
||||||
x &^= 255
|
h.font, rescale = f, true
|
||||||
h.stack = make([]int32, x)
|
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
|
if rescale {
|
||||||
x &^= 15
|
h.scale = scale
|
||||||
h.store = make([]int32, x)
|
if len(f.prep) != 0 {
|
||||||
|
if err := h.run(f.prep); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hinter) run(program []byte) error {
|
func (h *Hinter) run(program []byte) error {
|
||||||
|
@ -64,7 +107,11 @@ func (h *Hinter) run(program []byte) error {
|
||||||
var (
|
var (
|
||||||
steps, pc, top int
|
steps, pc, top int
|
||||||
opcode uint8
|
opcode uint8
|
||||||
|
|
||||||
|
callStack [32]callStackEntry
|
||||||
|
callStackTop int
|
||||||
)
|
)
|
||||||
|
|
||||||
for 0 <= pc && pc < len(program) {
|
for 0 <= pc && pc < len(program) {
|
||||||
steps++
|
steps++
|
||||||
if steps == 100000 {
|
if steps == 100000 {
|
||||||
|
@ -199,6 +246,65 @@ func (h *Hinter) run(program []byte) error {
|
||||||
top--
|
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:
|
case opRTDG:
|
||||||
h.roundPeriod = 1 << 5
|
h.roundPeriod = 1 << 5
|
||||||
h.roundPhase = 0
|
h.roundPhase = 0
|
||||||
|
@ -386,7 +492,8 @@ func (h *Hinter) run(program []byte) error {
|
||||||
return errors.New("truetype: hinting: unsupported IDEF instruction")
|
return errors.New("truetype: hinting: unsupported IDEF instruction")
|
||||||
|
|
||||||
case opROLL:
|
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:
|
case opMAX:
|
||||||
top--
|
top--
|
||||||
|
@ -400,11 +507,15 @@ func (h *Hinter) run(program []byte) error {
|
||||||
h.stack[top-1] = h.stack[top]
|
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
|
opcode -= opPUSHB000 - 1
|
||||||
goto push
|
goto push
|
||||||
|
|
||||||
case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111:
|
case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011,
|
||||||
|
opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111:
|
||||||
|
|
||||||
opcode -= opPUSHW000 - 1
|
opcode -= opPUSHW000 - 1
|
||||||
opcode += 0x80
|
opcode += 0x80
|
||||||
goto push
|
goto push
|
||||||
|
@ -438,24 +549,12 @@ func (h *Hinter) run(program []byte) error {
|
||||||
if depth < 0 {
|
if depth < 0 {
|
||||||
break ifelseloop
|
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:
|
default:
|
||||||
// No-op.
|
var ok bool
|
||||||
|
pc, ok = skipInstructionPayload(program, pc)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("truetype: hinting: unbalanced IF or ELSE")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pc++
|
pc++
|
||||||
|
@ -502,6 +601,32 @@ func (h *Hinter) run(program []byte) error {
|
||||||
return nil
|
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.
|
// f2dot14 is a 2.14 fixed point number.
|
||||||
type f2dot14 int16
|
type f2dot14 int16
|
||||||
|
|
||||||
|
|
|
@ -52,16 +52,16 @@ func TestBytecode(t *testing.T) {
|
||||||
"vector set/gets",
|
"vector set/gets",
|
||||||
[]byte{
|
[]byte{
|
||||||
opSVTCA1, // []
|
opSVTCA1, // []
|
||||||
opGPV, // [ 0x4000, 0 ]
|
opGPV, // [0x4000, 0]
|
||||||
opSVTCA0, // [ 0x4000, 0 ]
|
opSVTCA0, // [0x4000, 0]
|
||||||
opGFV, // [ 0x4000, 0, 0, 0x4000 ]
|
opGFV, // [0x4000, 0, 0, 0x4000]
|
||||||
opNEG, // [ 0x4000, 0, 0, -0x4000 ]
|
opNEG, // [0x4000, 0, 0, -0x4000]
|
||||||
opSPVFS, // [ 0x4000, 0 ]
|
opSPVFS, // [0x4000, 0]
|
||||||
opSFVTPV, // [ 0x4000, 0 ]
|
opSFVTPV, // [0x4000, 0]
|
||||||
opPUSHB000, // [ 0x4000, 0, 1 ]
|
opPUSHB000, // [0x4000, 0, 1]
|
||||||
1,
|
1,
|
||||||
opGFV, // [ 0x4000, 0, 1, 0, -0x4000 ]
|
opGFV, // [0x4000, 0, 1, 0, -0x4000]
|
||||||
opPUSHB000, // [ 0x4000, 0, 1, 0, -0x4000, 2 ]
|
opPUSHB000, // [0x4000, 0, 1, 0, -0x4000, 2]
|
||||||
2,
|
2,
|
||||||
},
|
},
|
||||||
[]int32{0x4000, 0, 1, 0, -0x4000, 2},
|
[]int32{0x4000, 0, 1, 0, -0x4000, 2},
|
||||||
|
@ -505,6 +505,52 @@ func TestBytecode(t *testing.T) {
|
||||||
[]int32{-2, -5},
|
[]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 {
|
for _, tc := range testCases {
|
||||||
|
@ -512,7 +558,7 @@ func TestBytecode(t *testing.T) {
|
||||||
h.init(&Font{
|
h.init(&Font{
|
||||||
maxStorage: 32,
|
maxStorage: 32,
|
||||||
maxStackElements: 100,
|
maxStackElements: 100,
|
||||||
})
|
}, 768)
|
||||||
err, errStr := h.run(tc.prog), ""
|
err, errStr := h.run(tc.prog), ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStr = err.Error()
|
errStr = err.Error()
|
||||||
|
@ -531,7 +577,7 @@ func TestBytecode(t *testing.T) {
|
||||||
}
|
}
|
||||||
got := h.stack[:len(tc.want)]
|
got := h.stack[:len(tc.want)]
|
||||||
if !reflect.DeepEqual(got, 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
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,10 +52,10 @@ const (
|
||||||
opALIGNPTS = 0x27
|
opALIGNPTS = 0x27
|
||||||
op_0x28 = 0x28
|
op_0x28 = 0x28
|
||||||
opUTP = 0x29
|
opUTP = 0x29
|
||||||
opLOOPCALL = 0x2a
|
opLOOPCALL = 0x2a // LOOP and CALL function
|
||||||
opCALL = 0x2b
|
opCALL = 0x2b // CALL function
|
||||||
opFDEF = 0x2c
|
opFDEF = 0x2c // Function DEFinition
|
||||||
opENDF = 0x2d
|
opENDF = 0x2d // END Function definition
|
||||||
opMDAP0 = 0x2e
|
opMDAP0 = 0x2e
|
||||||
opMDAP1 = 0x2f
|
opMDAP1 = 0x2f
|
||||||
opIUP0 = 0x30
|
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
|
// 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
|
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
|
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
|
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
|
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
|
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 {
|
type Font struct {
|
||||||
// Tables sliced from the TTF data. The different tables are documented
|
// Tables sliced from the TTF data. The different tables are documented
|
||||||
// at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
|
// at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
|
||||||
cmap, glyf, head, hhea, hmtx, kern, loca, maxp []byte
|
cmap, cvt, fpgm, glyf, head, hhea, hmtx, kern, loca, maxp, prep []byte
|
||||||
cmapIndexes []byte
|
|
||||||
|
cmapIndexes []byte
|
||||||
|
|
||||||
// Cached values derived from the raw ttf data.
|
// Cached values derived from the raw ttf data.
|
||||||
cm []cm
|
cm []cm
|
||||||
|
@ -411,6 +412,10 @@ func parse(ttf []byte, offset int) (font *Font, err error) {
|
||||||
switch string(ttf[x : x+4]) {
|
switch string(ttf[x : x+4]) {
|
||||||
case "cmap":
|
case "cmap":
|
||||||
f.cmap, err = readTable(ttf, ttf[x+8:x+16])
|
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":
|
case "glyf":
|
||||||
f.glyf, err = readTable(ttf, ttf[x+8:x+16])
|
f.glyf, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
case "head":
|
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])
|
f.loca, err = readTable(ttf, ttf[x+8:x+16])
|
||||||
case "maxp":
|
case "maxp":
|
||||||
f.maxp, err = readTable(ttf, ttf[x+8:x+16])
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue