diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go index 587935d..6b128dd 100644 --- a/freetype/truetype/hint.go +++ b/freetype/truetype/hint.go @@ -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 } diff --git a/freetype/truetype/hint_test.go b/freetype/truetype/hint_test.go index 94a7914..3c66be3 100644 --- a/freetype/truetype/hint_test.go +++ b/freetype/truetype/hint_test.go @@ -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 { diff --git a/freetype/truetype/opcodes.go b/freetype/truetype/opcodes.go index ae49474..c20cc70 100644 --- a/freetype/truetype/opcodes.go +++ b/freetype/truetype/opcodes.go @@ -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