freetype/truetype: rounding opcodes.

R=bsiegert
CC=golang-dev
http://codereview.appspot.com/6348079
This commit is contained in:
Nigel Tao 2012-07-06 17:34:39 +10:00
parent e843d5cf7c
commit 9e927de79b
3 changed files with 204 additions and 23 deletions

View File

@ -14,10 +14,16 @@ import (
type hinter struct {
stack [800]int32
// TODO: add more state, as per https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
// The graphics state is described at https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
roundPeriod, roundPhase, roundThreshold int32
}
func (h *hinter) run(program []byte) error {
// The default rounding policy is round to grid.
h.roundPeriod = 1 << 6
h.roundPhase = 0
h.roundThreshold = 1 << 5
if len(program) > 50000 {
return errors.New("truetype: hinting: too many instructions")
}
@ -39,6 +45,16 @@ func (h *hinter) run(program []byte) error {
}
switch opcode {
case opRTG:
h.roundPeriod = 1 << 6
h.roundPhase = 0
h.roundThreshold = 1 << 5
case opRTHG:
h.roundPeriod = 1 << 6
h.roundPhase = 1 << 5
h.roundThreshold = 1 << 5
case opELSE:
opcode = 1
goto ifelse
@ -82,6 +98,11 @@ func (h *hinter) run(program []byte) error {
top--
}
case opRTDG:
h.roundPeriod = 1 << 5
h.roundPhase = 0
h.roundThreshold = 1 << 4
case opNPUSHB:
opcode = 0
goto push
@ -151,11 +172,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] = int32((int64(h.stack[top-1]) << 6) / int64(h.stack[top]))
h.stack[top-1] = div(h.stack[top-1], h.stack[top])
case opMUL:
top--
h.stack[top-1] = int32((int64(h.stack[top-1]) * int64(h.stack[top])) >> 6)
h.stack[top-1] = mul(h.stack[top-1], h.stack[top])
case opABS:
if h.stack[top-1] < 0 {
@ -172,6 +193,41 @@ func (h *hinter) run(program []byte) error {
h.stack[top-1] += 63
h.stack[top-1] &^= 63
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])
case opNROUND00, opNROUND01, opNROUND10, opNROUND11:
// No-op. The spec says to add one of four "compensations for the engine
// characteristics", to cater for things like "different dot-size printers".
// https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#engine_compensation
// This code does not implement engine compensation, as we don't expect to
// be used to output on dot-matrix printers.
case opSROUND, opS45ROUND:
top--
switch (h.stack[top] >> 6) & 0x03 {
case 0:
h.roundPeriod = 1 << 5
case 1, 3:
h.roundPeriod = 1 << 6
case 2:
h.roundPeriod = 1 << 7
}
if opcode == opS45ROUND {
// The spec says to multiply by √2, but the C Freetype code says 1/√2.
// We go with 1/√2.
h.roundPeriod *= 46341
h.roundPeriod /= 65536
}
h.roundPhase = h.roundPeriod * ((h.stack[top] >> 4) & 0x03) / 4
if x := h.stack[top] & 0x0f; x != 0 {
h.roundThreshold = h.roundPeriod * (x - 4) / 8
} else {
h.roundThreshold = h.roundPeriod - 1
}
case opJROT:
top -= 2
if h.stack[top+1] != 0 {
@ -186,6 +242,21 @@ func (h *hinter) run(program []byte) error {
continue
}
case opROFF:
h.roundPeriod = 0
h.roundPhase = 0
h.roundThreshold = 0
case opRUTG:
h.roundPeriod = 1 << 6
h.roundPhase = 0
h.roundThreshold = 1<<6 - 1
case opRDTG:
h.roundPeriod = 1 << 6
h.roundPhase = 0
h.roundThreshold = 0
case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111:
opcode -= opPUSHB000 - 1
goto push
@ -288,6 +359,43 @@ func (h *hinter) run(program []byte) error {
return nil
}
// div returns x/y in 26.6 fixed point arithmetic.
func div(x, y int32) int32 {
return int32((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)
}
// 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 {
if h.roundPeriod == 0 {
return x
}
neg := x < 0
x -= h.roundPhase
x += h.roundThreshold
if x >= 0 {
x = (x / h.roundPeriod) * h.roundPeriod
} else {
x -= h.roundPeriod
x += 1
x = (x / h.roundPeriod) * h.roundPeriod
}
x += h.roundPhase
if neg {
if x >= 0 {
x = h.roundPhase - h.roundPeriod
}
} else if x < 0 {
x = h.roundPhase
}
return x
}
func bool2int32(b bool) int32 {
if b {
return 1

View File

@ -345,6 +345,79 @@ func TestBytecode(t *testing.T) {
[]int32{64, 128},
"",
},
{
"rounding",
// Round 1.40625 (which is 90/64) under various rounding policies.
// See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
[]byte{
opROFF, // []
opPUSHB000, // [90]
90,
opROUND00, // [90]
opRTG, // [90]
opPUSHB000, // [90, 90]
90,
opROUND00, // [90, 64]
opRTHG, // [90, 64]
opPUSHB000, // [90, 64, 90]
90,
opROUND00, // [90, 64, 96]
opRDTG, // [90, 64, 96]
opPUSHB000, // [90, 64, 96, 90]
90,
opROUND00, // [90, 64, 96, 64]
opRUTG, // [90, 64, 96, 64]
opPUSHB000, // [90, 64, 96, 64, 90]
90,
opROUND00, // [90, 64, 96, 64, 128]
opRTDG, // [90, 64, 96, 64, 128]
opPUSHB000, // [90, 64, 96, 64, 128, 90]
90,
opROUND00, // [90, 64, 96, 64, 128, 96]
},
[]int32{90, 64, 96, 64, 128, 96},
"",
},
{
"super-rounding",
// See figure 20 of https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
// and the sign preservation steps of the "Order of rounding operations" section.
[]byte{
opPUSHB000, // [0x58]
0x58,
opSROUND, // []
opPUSHW000, // [-81]
0xff,
0xaf,
opROUND00, // [-112]
opPUSHW000, // [-112, -80]
0xff,
0xb0,
opROUND00, // [-112, -48]
opPUSHW000, // [-112, -48, -17]
0xff,
0xef,
opROUND00, // [-112, -48, -48]
opPUSHW000, // [-112, -48, -48, -16]
0xff,
0xf0,
opROUND00, // [-112, -48, -48, -48]
opPUSHB000, // [-112, -48, -48, -48, 0]
0,
opROUND00, // [-112, -48, -48, -48, 16]
opPUSHB000, // [-112, -48, -48, -48, 16, 16]
16,
opROUND00, // [-112, -48, -48, -48, 16, 16]
opPUSHB000, // [-112, -48, -48, -48, 16, 16, 47]
47,
opROUND00, // [-112, -48, -48, -48, 16, 16, 16]
opPUSHB000, // [-112, -48, -48, -48, 16, 16, 16, 48]
48,
opROUND00, // [-112, -48, -48, -48, 16, 16, 16, 80]
},
[]int32{-112, -48, -48, -48, 16, 16, 16, 80},
"",
},
}
for _, tc := range testCases {

View File

@ -34,8 +34,8 @@ const (
opSZP2 = 0x15
opSZPS = 0x16
opSLOOP = 0x17
opRTG = 0x18
opRTHG = 0x19
opRTG = 0x18 // Round To Grid
opRTHG = 0x19 // Round To Half Grid
opSMD = 0x1a
opELSE = 0x1b // ELSE clause
opJMPR = 0x1c // JuMP Relative
@ -71,7 +71,7 @@ const (
opMSIRP0 = 0x3a
opMSIRP1 = 0x3b
opALIGNRP = 0x3c
opRTGD = 0x3d
opRTDG = 0x3d // Round To Double Grid
opMIAP0 = 0x3e
opMIAP1 = 0x3f
opNPUSHB = 0x40 // PUSH N Bytes
@ -114,28 +114,28 @@ const (
opNEG = 0x65 // NEGate
opFLOOR = 0x66 // FLOOR
opCEILING = 0x67 // CEILING
opROUND00 = 0x68
opROUND01 = 0x69
opROUND10 = 0x6a
opROUND11 = 0x6b
opNROUND00 = 0x6c
opNROUND01 = 0x6d
opNROUND10 = 0x6e
opNROUND11 = 0x6f
opROUND00 = 0x68 // ROUND value
opROUND01 = 0x69 // .
opROUND10 = 0x6a // .
opROUND11 = 0x6b // .
opNROUND00 = 0x6c // No ROUNDing of value
opNROUND01 = 0x6d // .
opNROUND10 = 0x6e // .
opNROUND11 = 0x6f // .
opWCVTF = 0x70
opDELTAP2 = 0x71
opDELTAP3 = 0x72
opDELTAC1 = 0x73
opDELTAC2 = 0x74
opDELTAC3 = 0x75
opSROUND = 0x76
opS45ROUND = 0x77
opSROUND = 0x76 // Super ROUND
opS45ROUND = 0x77 // Super ROUND 45 degrees
opJROT = 0x78 // Jump Relative On True
opJROF = 0x79 // Jump Relative On False
opROFF = 0x7a
opROFF = 0x7a // Round OFF
op_0x7b = 0x7b
opRUTG = 0x7c
opRDTG = 0x7d
opRUTG = 0x7c // Round Up To Grid
opRDTG = 0x7d // Round Down To Grid
opSANGW = 0x7e
opAA = 0x7f
opFLIPPT = 0x80
@ -272,13 +272,13 @@ const (
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, q, q, q, 0, 1, q, q, q, // 0x10 - 0x1f
q, q, q, q, q, q, q, q, 0, 0, q, 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, q, q, q, // 0x30 - 0x3f
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
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, q, q, q, q, q, q, q, q, // 0x60 - 0x6f
q, q, q, q, q, q, q, q, 2, 2, q, q, q, q, q, q, // 0x70 - 0x7f
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, 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