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:
parent
9e927de79b
commit
2bf22ccf6b
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user