freetype/truetype: rounding opcodes.
R=bsiegert CC=golang-dev http://codereview.appspot.com/6348079
This commit is contained in:
parent
e843d5cf7c
commit
9e927de79b
3 changed files with 204 additions and 23 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue