freetype/truetype: vector set/gets, store ops, roll/max/min ops.

R=bsiegert
CC=golang-dev
http://codereview.appspot.com/6354080
This commit is contained in:
Nigel Tao 2012-07-09 23:18:25 +10:00
parent 9e927de79b
commit 2bf22ccf6b
3 changed files with 242 additions and 42 deletions

View file

@ -13,12 +13,34 @@ import (
)
type hinter struct {
// TODO: variable sized stack and store slices based on the maxp section?
// Should the arrays for the stack and store be combined? For now, fixed
// maximum sizes seem to work in practice.
stack [800]int32
// The graphics state is described at https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
roundPeriod, roundPhase, roundThreshold int32
store [128]int32
// The fields below constitue the graphics state, which is described at
// https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
// Projection vector, freedom vector and dual projection vector.
pv, fv, dv [2]f2dot14
// Minimum distance.
minDist f26dot6
// Loop count.
loop int32
// Rounding policy.
roundPeriod, roundPhase, roundThreshold f26dot6
}
func (h *hinter) run(program []byte) error {
// The default vectors are along the X axis.
h.pv = [2]f2dot14{0x4000, 0}
h.fv = [2]f2dot14{0x4000, 0}
h.dv = [2]f2dot14{0x4000, 0}
// The default minimum distance is 1.
h.minDist = 1 << 6
// The default loop count is 1.
h.loop = 1
// The default rounding policy is round to grid.
h.roundPeriod = 1 << 6
h.roundPhase = 0
@ -31,7 +53,7 @@ func (h *hinter) run(program []byte) error {
steps, pc, top int
opcode uint8
)
for 0 <= pc && int(pc) < len(program) {
for 0 <= pc && pc < len(program) {
steps++
if steps == 100000 {
return errors.New("truetype: hinting: too many steps")
@ -45,6 +67,69 @@ func (h *hinter) run(program []byte) error {
}
switch opcode {
case opSVTCA0:
h.pv = [2]f2dot14{0, 0x4000}
h.fv = [2]f2dot14{0, 0x4000}
// TODO: h.dv = h.pv ??
case opSVTCA1:
h.pv = [2]f2dot14{0x4000, 0}
h.fv = [2]f2dot14{0x4000, 0}
// TODO: h.dv = h.pv ??
case opSPVTCA0:
h.pv = [2]f2dot14{0, 0x4000}
// TODO: h.dv = h.pv ??
case opSPVTCA1:
h.pv = [2]f2dot14{0x4000, 0}
// TODO: h.dv = h.pv ??
case opSFVTCA0:
h.fv = [2]f2dot14{0, 0x4000}
case opSFVTCA1:
h.fv = [2]f2dot14{0x4000, 0}
case opSPVFS:
top -= 2
h.pv[0] = f2dot14(h.stack[top+0])
h.pv[1] = f2dot14(h.stack[top+1])
// TODO: normalize h.pv ??
// TODO: h.dv = h.pv ??
case opSFVFS:
top -= 2
h.fv[0] = f2dot14(h.stack[top+0])
h.fv[1] = f2dot14(h.stack[top+1])
// TODO: normalize h.fv ??
case opGPV:
if top+1 >= len(h.stack) {
return errors.New("truetype: hinting: stack overflow")
}
h.stack[top+0] = int32(h.pv[0])
h.stack[top+1] = int32(h.pv[1])
top += 2
case opGFV:
if top+1 >= len(h.stack) {
return errors.New("truetype: hinting: stack overflow")
}
h.stack[top+0] = int32(h.fv[0])
h.stack[top+1] = int32(h.fv[1])
top += 2
case opSFVTPV:
h.fv = h.pv
case opSLOOP:
top--
if h.stack[top] <= 0 {
return errors.New("truetype: hinting: invalid data")
}
h.loop = h.stack[top]
case opRTG:
h.roundPeriod = 1 << 6
h.roundPhase = 0
@ -55,6 +140,10 @@ func (h *hinter) run(program []byte) error {
h.roundPhase = 1 << 5
h.roundThreshold = 1 << 5
case opSMD:
top--
h.minDist = f26dot6(h.stack[top])
case opELSE:
opcode = 1
goto ifelse
@ -65,7 +154,7 @@ func (h *hinter) run(program []byte) error {
continue
case opDUP:
if int(top) >= len(h.stack) {
if top >= len(h.stack) {
return errors.New("truetype: hinting: stack overflow")
}
h.stack[top] = h.stack[top-1]
@ -81,7 +170,7 @@ func (h *hinter) run(program []byte) error {
h.stack[top-1], h.stack[top-2] = h.stack[top-2], h.stack[top-1]
case opDEPTH:
if int(top) >= len(h.stack) {
if top >= len(h.stack) {
return errors.New("truetype: hinting: stack overflow")
}
h.stack[top] = int32(top)
@ -111,6 +200,21 @@ func (h *hinter) run(program []byte) error {
opcode = 0x80
goto push
case opWS:
top -= 2
i := int(h.stack[top])
if i < 0 || len(h.store) <= i {
return errors.New("truetype: hinting: invalid data")
}
h.store[i] = h.stack[top+1]
case opRS:
i := int(h.stack[top-1])
if i < 0 || len(h.store) <= i {
return errors.New("truetype: hinting: invalid data")
}
h.stack[top-1] = h.store[i]
case opDEBUG:
// No-op.
@ -172,11 +276,11 @@ func (h *hinter) run(program []byte) error {
if h.stack[top] == 0 {
return errors.New("truetype: hinting: division by zero")
}
h.stack[top-1] = div(h.stack[top-1], h.stack[top])
h.stack[top-1] = int32(f26dot6(h.stack[top-1]).div(f26dot6(h.stack[top])))
case opMUL:
top--
h.stack[top-1] = mul(h.stack[top-1], h.stack[top])
h.stack[top-1] = int32(f26dot6(h.stack[top-1]).mul(f26dot6(h.stack[top])))
case opABS:
if h.stack[top-1] < 0 {
@ -196,7 +300,7 @@ func (h *hinter) run(program []byte) error {
case opROUND00, opROUND01, opROUND10, opROUND11:
// The four flavors of opROUND are equivalent. See the comment below on
// opNROUND for the rationale.
h.stack[top-1] = h.round(h.stack[top-1])
h.stack[top-1] = int32(h.round(f26dot6(h.stack[top-1])))
case opNROUND00, opNROUND01, opNROUND10, opNROUND11:
// No-op. The spec says to add one of four "compensations for the engine
@ -221,9 +325,9 @@ func (h *hinter) run(program []byte) error {
h.roundPeriod *= 46341
h.roundPeriod /= 65536
}
h.roundPhase = h.roundPeriod * ((h.stack[top] >> 4) & 0x03) / 4
h.roundPhase = h.roundPeriod * f26dot6((h.stack[top]>>4)&0x03) / 4
if x := h.stack[top] & 0x0f; x != 0 {
h.roundThreshold = h.roundPeriod * (x - 4) / 8
h.roundThreshold = h.roundPeriod * f26dot6(x-4) / 8
} else {
h.roundThreshold = h.roundPeriod - 1
}
@ -257,6 +361,29 @@ func (h *hinter) run(program []byte) error {
h.roundPhase = 0
h.roundThreshold = 0
case opSANGW, opAA:
// These ops are "anachronistic" and no longer used.
top--
case opIDEF:
// IDEF is for ancient versions of the bytecode interpreter, and is no longer used.
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]
case opMAX:
top--
if h.stack[top-1] < h.stack[top] {
h.stack[top-1] = h.stack[top]
}
case opMIN:
top--
if h.stack[top-1] > h.stack[top] {
h.stack[top-1] = h.stack[top]
}
case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111:
opcode -= opPUSHB000 - 1
goto push
@ -332,7 +459,7 @@ func (h *hinter) run(program []byte) error {
}
if opcode == 0 {
pc++
if int(pc) >= len(program) {
if pc >= len(program) {
return errors.New("truetype: hinting: insufficient data")
}
opcode = program[pc]
@ -359,19 +486,25 @@ func (h *hinter) run(program []byte) error {
return nil
}
// f2dot14 is a 2.14 fixed point number.
type f2dot14 int16
// f26dot6 is a 26.6 fixed point number.
type f26dot6 int32
// div returns x/y in 26.6 fixed point arithmetic.
func div(x, y int32) int32 {
return int32((int64(x) << 6) / int64(y))
func (x f26dot6) div(y f26dot6) f26dot6 {
return f26dot6((int64(x) << 6) / int64(y))
}
// mul returns x*y in 26.6 fixed point arithmetic.
func mul(x, y int32) int32 {
return int32(int64(x) * int64(y) >> 6)
func (x f26dot6) mul(y f26dot6) f26dot6 {
return f26dot6(int64(x) * int64(y) >> 6)
}
// round rounds the given number. The rounding algorithm is described at
// https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
func (h *hinter) round(x int32) int32 {
func (h *hinter) round(x f26dot6) f26dot6 {
if h.roundPeriod == 0 {
return x
}

View file

@ -48,6 +48,25 @@ func TestBytecode(t *testing.T) {
nil,
"unbalanced",
},
{
"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 ]
1,
opGFV, // [ 0x4000, 0, 1, 0, -0x4000 ]
opPUSHB000, // [ 0x4000, 0, 1, 0, -0x4000, 2 ]
2,
},
[]int32{0x4000, 0, 1, 0, -0x4000, 2},
"",
},
{
"jumps",
[]byte{
@ -126,6 +145,23 @@ func TestBytecode(t *testing.T) {
[]int32{255, -2, 253, 1, 2, 0x0405, 0x0607, 0x0809},
"",
},
{
"store ops",
[]byte{
opPUSHB011, // [1, 22, 3, 44]
1,
22,
3,
44,
opWS, // [1, 22]
opWS, // []
opPUSHB000, // [3]
3,
opRS, // [44]
},
[]int32{44},
"",
},
{
"comparison ops",
[]byte{
@ -418,6 +454,37 @@ func TestBytecode(t *testing.T) {
[]int32{-112, -48, -48, -48, 16, 16, 16, 80},
"",
},
{
"roll",
[]byte{
opPUSHB010, // [1, 2, 3]
1,
2,
3,
opROLL, // [2, 3, 1]
},
[]int32{2, 3, 1},
"",
},
{
"max/min",
[]byte{
opPUSHW001, // [-2, -3]
0xff,
0xfe,
0xff,
0xfd,
opMAX, // [-2]
opPUSHW001, // [-2, -4, -5]
0xff,
0xfc,
0xff,
0xfb,
opMIN, // [-2, -5]
},
[]int32{-2, -5},
"",
},
}
for _, tc := range testCases {

View file

@ -10,21 +10,21 @@ package truetype
// TODO: the opXXX constants without end-of-line comments are not yet implemented.
const (
opSVTCA0 = 0x00
opSVTCA1 = 0x01
opSPVTCA0 = 0x02
opSPVTCA1 = 0x03
opSFVTCA0 = 0x04
opSFVTCA1 = 0x05
opSVTCA0 = 0x00 // Set freedom and projection Vectors To Coordinate Axis
opSVTCA1 = 0x01 // .
opSPVTCA0 = 0x02 // Set Projection Vector To Coordinate Axis
opSPVTCA1 = 0x03 // .
opSFVTCA0 = 0x04 // Set Freedom Vector to Coordinate Axis
opSFVTCA1 = 0x05 // .
opSPVTL0 = 0x06
opSPVTL1 = 0x07
opSFVTL0 = 0x08
opSFVTL1 = 0x09
opSFVFS = 0x0b
opSPVFS = 0x0a
opGPV = 0x0c
opGFV = 0x0d
opSFVTPV = 0x0e
opSPVFS = 0x0a // Set Projection Vector From Stack
opSFVFS = 0x0b // Set Freedom Vector From Stack
opGPV = 0x0c // Get Projection Vector
opGFV = 0x0d // Get Freedom Vector
opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector
opISECT = 0x0f
opSRP0 = 0x10
opSRP1 = 0x11
@ -33,10 +33,10 @@ const (
opSZP1 = 0x14
opSZP2 = 0x15
opSZPS = 0x16
opSLOOP = 0x17
opSLOOP = 0x17 // Set LOOP variable
opRTG = 0x18 // Round To Grid
opRTHG = 0x19 // Round To Half Grid
opSMD = 0x1a
opSMD = 0x1a // Set Minimum Distance
opELSE = 0x1b // ELSE clause
opJMPR = 0x1c // JuMP Relative
opSCVTCI = 0x1d
@ -76,8 +76,8 @@ const (
opMIAP1 = 0x3f
opNPUSHB = 0x40 // PUSH N Bytes
opNPUSHW = 0x41 // PUSH N Words
opWS = 0x42
opRS = 0x43
opWS = 0x42 // Write Store
opRS = 0x43 // Read Store
opWCVTP = 0x44
opRCVT = 0x45
opGC0 = 0x46
@ -136,8 +136,8 @@ const (
op_0x7b = 0x7b
opRUTG = 0x7c // Round Up To Grid
opRDTG = 0x7d // Round Down To Grid
opSANGW = 0x7e
opAA = 0x7f
opSANGW = 0x7e // Set ANGle Weight
opAA = 0x7f // Adjust Angle
opFLIPPT = 0x80
opFLIPRGON = 0x81
opFLIPRGOFF = 0x82
@ -147,10 +147,10 @@ const (
opSDPVTL0 = 0x86
opSDPVTL1 = 0x87
opGETINFO = 0x88
opIFDEF = 0x89
opROLL = 0x8a
opMAX = 0x8b
opMIN = 0x8c
opIDEF = 0x89 // Instruction DEFinition
opROLL = 0x8a // ROLL the top three stack elements
opMAX = 0x8b // MAXimum of top two stack elements
opMIN = 0x8c // MINimum of top two stack elements
opSCANTYPE = 0x8d
opINSTCTRL = 0x8e
op_0x8f = 0x8f
@ -271,15 +271,15 @@ const (
// popCount is the number of stack elements that each opcode pops.
var popCount = [256]uint8{
// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x00 - 0x0f
q, q, q, q, q, q, q, q, 0, 0, q, 0, 1, q, q, q, // 0x10 - 0x1f
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
q, q, q, q, q, q, q, q, q, q, q, q, q, 0, q, q, // 0x30 - 0x3f
0, 0, q, q, 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, q, q, 1, 0, 2, 2, 1, q, q, q, // 0x50 - 0x5f
2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f
q, q, q, q, q, q, 1, 1, 2, 2, 0, q, 0, 0, q, q, // 0x70 - 0x7f
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x80 - 0x8f
q, q, q, q, q, q, 1, 1, 2, 2, 0, q, 0, 0, 1, 1, // 0x70 - 0x7f
q, q, q, q, q, q, q, q, q, 1, 3, 2, 2, q, q, q, // 0x80 - 0x8f
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0x90 - 0x9f
q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, q, // 0xa0 - 0xaf
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf