freetype/truetype: add explicit graphics state.

These new opcodes aren't unit-tested per se, but they will be
exercised by an end-to-end hinting test of the Luxi fonts.

R=bsiegert
CC=golang-dev
https://codereview.appspot.com/12100043
This commit is contained in:
Nigel Tao 2013-08-02 19:13:26 +10:00
parent e3b4bc4c1f
commit 43c4b0b00d
3 changed files with 198 additions and 100 deletions

View File

@ -105,8 +105,9 @@ func (g *GlyphBuf) decodeCoords(d []byte, offset int, np0 int) int {
}
// Load loads a glyph's contours from a Font, overwriting any previously
// loaded contours for this GlyphBuf. The Hinter is optional; if non-nil, then
// the resulting glyph will be hinted by the Font's bytecode instructions.
// loaded contours for this GlyphBuf. scale is the number of 26.6 fixed point
// units in 1 em. The Hinter is optional; if non-nil, then the resulting glyph
// will be hinted by the Font's bytecode instructions.
func (g *GlyphBuf) Load(f *Font, scale int32, i Index, h *Hinter) error {
// Reset the GlyphBuf.
g.B = Bounds{}

View File

@ -34,17 +34,45 @@ type Hinter struct {
font *Font
scale int32
// The fields below constitue the graphics state, which is described at
// https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
// gs and defaultGS are the current and default graphics state. The
// default graphics state is the global default graphics state after
// the font's fpgm and prep programs have been run.
gs, defaultGS graphicsState
}
// graphicsState is described at https://developer.apple.com/fonts/TTRefMan/RM04/Chap4.html
type graphicsState struct {
// Projection vector, freedom vector and dual projection vector.
pv, fv, dv [2]f2dot14
// Reference points and zone pointers.
rp, zp [3]int32
// Control Value / Single Width Cut-In.
controlValueCutIn, singleWidthCutIn, singleWidth f26dot6
// Delta base / shift.
deltaBase, deltaShift int32
// Minimum distance.
minDist f26dot6
// Loop count.
loop int32
// Rounding policy.
roundPeriod, roundPhase, roundThreshold f26dot6
// Auto-flip.
autoFlip bool
}
var globalDefaultGS = graphicsState{
pv: [2]f2dot14{0x4000, 0}, // Unit vector along the X axis.
fv: [2]f2dot14{0x4000, 0},
dv: [2]f2dot14{0x4000, 0},
zp: [3]int32{1, 1, 1},
controlValueCutIn: (17 << 6) / 16, // 17/16 as an f26dot6.
deltaBase: 9,
deltaShift: 3,
minDist: 1 << 6, // 1 as an f26dot6.
loop: 1,
roundPeriod: 1 << 6, // 1 as an f26dot6.
roundThreshold: 1 << 5, // 1/2 as an f26dot6.
autoFlip: true,
}
func (h *Hinter) init(f *Font, scale int32) error {
@ -78,28 +106,29 @@ func (h *Hinter) init(f *Font, scale int32) error {
if rescale {
h.scale = scale
h.defaultGS = globalDefaultGS
if len(f.prep) != 0 {
if err := h.run(f.prep); err != nil {
return err
}
h.defaultGS = h.gs
// The MS rasterizer doesn't allow the following graphics state
// variables to be modified by the CVT program.
h.defaultGS.pv = globalDefaultGS.pv
h.defaultGS.fv = globalDefaultGS.fv
h.defaultGS.dv = globalDefaultGS.dv
h.defaultGS.rp = globalDefaultGS.rp
h.defaultGS.zp = globalDefaultGS.zp
h.defaultGS.loop = globalDefaultGS.loop
}
}
return nil
}
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
h.roundThreshold = 1 << 5
h.gs = h.defaultGS
if len(program) > 50000 {
return errors.New("truetype: hinting: too many instructions")
@ -127,81 +156,95 @@ 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 ??
h.gs.pv = [2]f2dot14{0, 0x4000}
h.gs.fv = [2]f2dot14{0, 0x4000}
// TODO: h.gs.dv = h.gs.pv ??
case opSVTCA1:
h.pv = [2]f2dot14{0x4000, 0}
h.fv = [2]f2dot14{0x4000, 0}
// TODO: h.dv = h.pv ??
h.gs.pv = [2]f2dot14{0x4000, 0}
h.gs.fv = [2]f2dot14{0x4000, 0}
// TODO: h.gs.dv = h.gs.pv ??
case opSPVTCA0:
h.pv = [2]f2dot14{0, 0x4000}
// TODO: h.dv = h.pv ??
h.gs.pv = [2]f2dot14{0, 0x4000}
// TODO: h.gs.dv = h.gs.pv ??
case opSPVTCA1:
h.pv = [2]f2dot14{0x4000, 0}
// TODO: h.dv = h.pv ??
h.gs.pv = [2]f2dot14{0x4000, 0}
// TODO: h.gs.dv = h.gs.pv ??
case opSFVTCA0:
h.fv = [2]f2dot14{0, 0x4000}
h.gs.fv = [2]f2dot14{0, 0x4000}
case opSFVTCA1:
h.fv = [2]f2dot14{0x4000, 0}
h.gs.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 ??
h.gs.pv[0] = f2dot14(h.stack[top+0])
h.gs.pv[1] = f2dot14(h.stack[top+1])
// TODO: normalize h.gs.pv ??
// TODO: h.gs.dv = h.gs.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 ??
h.gs.fv[0] = f2dot14(h.stack[top+0])
h.gs.fv[1] = f2dot14(h.stack[top+1])
// TODO: normalize h.gs.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])
h.stack[top+0] = int32(h.gs.pv[0])
h.stack[top+1] = int32(h.gs.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])
h.stack[top+0] = int32(h.gs.fv[0])
h.stack[top+1] = int32(h.gs.fv[1])
top += 2
case opSFVTPV:
h.fv = h.pv
h.gs.fv = h.gs.pv
case opSRP0, opSRP1, opSRP2:
top--
h.gs.rp[opcode-opSRP0] = h.stack[top]
case opSZP0, opSZP1, opSZP2:
top--
h.gs.zp[opcode-opSZP0] = h.stack[top]
case opSZPS:
top--
h.gs.zp[0] = h.stack[top]
h.gs.zp[1] = h.stack[top]
h.gs.zp[2] = h.stack[top]
case opSLOOP:
top--
if h.stack[top] <= 0 {
return errors.New("truetype: hinting: invalid data")
}
h.loop = h.stack[top]
h.gs.loop = h.stack[top]
case opRTG:
h.roundPeriod = 1 << 6
h.roundPhase = 0
h.roundThreshold = 1 << 5
h.gs.roundPeriod = 1 << 6
h.gs.roundPhase = 0
h.gs.roundThreshold = 1 << 5
case opRTHG:
h.roundPeriod = 1 << 6
h.roundPhase = 1 << 5
h.roundThreshold = 1 << 5
h.gs.roundPeriod = 1 << 6
h.gs.roundPhase = 1 << 5
h.gs.roundThreshold = 1 << 5
case opSMD:
top--
h.minDist = f26dot6(h.stack[top])
h.gs.minDist = f26dot6(h.stack[top])
case opELSE:
opcode = 1
@ -212,6 +255,18 @@ func (h *Hinter) run(program []byte) error {
pc += int(h.stack[top])
continue
case opSCVTCI:
top--
h.gs.controlValueCutIn = f26dot6(h.stack[top])
case opSSWCI:
top--
h.gs.singleWidthCutIn = f26dot6(h.stack[top])
case opSSW:
top--
h.gs.singleWidth = f26dot6(h.stack[top])
case opDUP:
if top >= len(h.stack) {
return errors.New("truetype: hinting: stack overflow")
@ -306,9 +361,9 @@ func (h *Hinter) run(program []byte) error {
program, pc = callStack[callStackTop].program, callStack[callStackTop].pc
case opRTDG:
h.roundPeriod = 1 << 5
h.roundPhase = 0
h.roundThreshold = 1 << 4
h.gs.roundPeriod = 1 << 5
h.gs.roundPhase = 0
h.gs.roundThreshold = 1 << 4
case opNPUSHB:
opcode = 0
@ -333,6 +388,17 @@ func (h *Hinter) run(program []byte) error {
}
h.stack[top-1] = h.store[i]
case opMPPEM, opMPS:
if top >= len(h.stack) {
return errors.New("truetype: hinting: stack overflow")
}
// For MPS, point size should be irrelevant; we return the PPEM.
h.stack[top] = h.scale >> 6
top++
case opFLIPON, opFLIPOFF:
h.gs.autoFlip = opcode == opFLIPON
case opDEBUG:
// No-op.
@ -385,6 +451,14 @@ func (h *Hinter) run(program []byte) error {
case opNOT:
h.stack[top-1] = bool2int32(h.stack[top-1] == 0)
case opSDB:
top--
h.gs.deltaBase = h.stack[top]
case opSDS:
top--
h.gs.deltaShift = h.stack[top]
case opADD:
top--
h.stack[top-1] += h.stack[top]
@ -435,23 +509,23 @@ func (h *Hinter) run(program []byte) error {
top--
switch (h.stack[top] >> 6) & 0x03 {
case 0:
h.roundPeriod = 1 << 5
h.gs.roundPeriod = 1 << 5
case 1, 3:
h.roundPeriod = 1 << 6
h.gs.roundPeriod = 1 << 6
case 2:
h.roundPeriod = 1 << 7
h.gs.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.gs.roundPeriod *= 46341
h.gs.roundPeriod /= 65536
}
h.roundPhase = h.roundPeriod * f26dot6((h.stack[top]>>4)&0x03) / 4
h.gs.roundPhase = h.gs.roundPeriod * f26dot6((h.stack[top]>>4)&0x03) / 4
if x := h.stack[top] & 0x0f; x != 0 {
h.roundThreshold = h.roundPeriod * f26dot6(x-4) / 8
h.gs.roundThreshold = h.gs.roundPeriod * f26dot6(x-4) / 8
} else {
h.roundThreshold = h.roundPeriod - 1
h.gs.roundThreshold = h.gs.roundPeriod - 1
}
case opJROT:
@ -469,24 +543,43 @@ func (h *Hinter) run(program []byte) error {
}
case opROFF:
h.roundPeriod = 0
h.roundPhase = 0
h.roundThreshold = 0
h.gs.roundPeriod = 0
h.gs.roundPhase = 0
h.gs.roundThreshold = 0
case opRUTG:
h.roundPeriod = 1 << 6
h.roundPhase = 0
h.roundThreshold = 1<<6 - 1
h.gs.roundPeriod = 1 << 6
h.gs.roundPhase = 0
h.gs.roundThreshold = 1<<6 - 1
case opRDTG:
h.roundPeriod = 1 << 6
h.roundPhase = 0
h.roundThreshold = 0
h.gs.roundPeriod = 1 << 6
h.gs.roundPhase = 0
h.gs.roundThreshold = 0
case opSANGW, opAA:
// These ops are "anachronistic" and no longer used.
top--
case opSCANCTRL:
// We do not support dropout control, as we always rasterize grayscale glyphs.
top--
case opGETINFO:
res := int32(0)
if h.stack[top-1]&(1<<0) != 0 {
// Set the engine version. We hard-code this to 35, the same as
// the C freetype code, which says that "Version~35 corresponds
// to MS rasterizer v.1.7 as used e.g. in Windows~98".
res |= 35
}
if h.stack[top-1]&(1<<5) != 0 {
// Set that we support grayscale.
res |= 1 << 12
}
// We set no other bits, as we do not support rotated or stretched glyphs.
h.stack[top-1] = res
case opIDEF:
// IDEF is for ancient versions of the bytecode interpreter, and is no longer used.
return errors.New("truetype: hinting: unsupported IDEF instruction")
@ -507,6 +600,10 @@ func (h *Hinter) run(program []byte) error {
h.stack[top-1] = h.stack[top]
}
case opSCANTYPE:
// We do not support dropout control, as we always rasterize grayscale glyphs.
top--
case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011,
opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111:
@ -646,26 +743,26 @@ func (x f26dot6) mul(y f26dot6) f26dot6 {
// 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 f26dot6) f26dot6 {
if h.roundPeriod == 0 {
if h.gs.roundPeriod == 0 {
return x
}
neg := x < 0
x -= h.roundPhase
x += h.roundThreshold
x -= h.gs.roundPhase
x += h.gs.roundThreshold
if x >= 0 {
x = (x / h.roundPeriod) * h.roundPeriod
x = (x / h.gs.roundPeriod) * h.gs.roundPeriod
} else {
x -= h.roundPeriod
x -= h.gs.roundPeriod
x += 1
x = (x / h.roundPeriod) * h.roundPeriod
x = (x / h.gs.roundPeriod) * h.gs.roundPeriod
}
x += h.roundPhase
x += h.gs.roundPhase
if neg {
if x >= 0 {
x = h.roundPhase - h.roundPeriod
x = h.gs.roundPhase - h.gs.roundPeriod
}
} else if x < 0 {
x = h.roundPhase
x = h.gs.roundPhase
}
return x
}

View File

@ -26,22 +26,22 @@ const (
opGFV = 0x0d // Get Freedom Vector
opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector
opISECT = 0x0f
opSRP0 = 0x10
opSRP1 = 0x11
opSRP2 = 0x12
opSZP0 = 0x13
opSZP1 = 0x14
opSZP2 = 0x15
opSZPS = 0x16
opSRP0 = 0x10 // Set Reference Point 0
opSRP1 = 0x11 // Set Reference Point 1
opSRP2 = 0x12 // Set Reference Point 2
opSZP0 = 0x13 // Set Zone Pointer 0
opSZP1 = 0x14 // Set Zone Pointer 1
opSZP2 = 0x15 // Set Zone Pointer 2
opSZPS = 0x16 // Set Zone PointerS
opSLOOP = 0x17 // Set LOOP variable
opRTG = 0x18 // Round To Grid
opRTHG = 0x19 // Round To Half Grid
opSMD = 0x1a // Set Minimum Distance
opELSE = 0x1b // ELSE clause
opJMPR = 0x1c // JuMP Relative
opSCVTCI = 0x1d
opSSWCI = 0x1e
opSSW = 0x1f
opSCVTCI = 0x1d // Set Control Value Table Cut-In
opSSWCI = 0x1e // Set Single Width Cut-In
opSSW = 0x1f // Set Single Width
opDUP = 0x20 // DUPlicate top stack element
opPOP = 0x21 // POP top stack element
opCLEAR = 0x22 // CLEAR the stack
@ -85,10 +85,10 @@ const (
opSCFS = 0x48
opMD0 = 0x49
opMD1 = 0x4a
opMPPEM = 0x4b
opMPS = 0x4c
opFLIPON = 0x4d
opFLIPOFF = 0x4e
opMPPEM = 0x4b // Measure Pixels Per EM
opMPS = 0x4c // Measure Point Size
opFLIPON = 0x4d // set the auto FLIP Boolean to ON
opFLIPOFF = 0x4e // set the auto FLIP Boolean to OFF
opDEBUG = 0x4f // DEBUG call
opLT = 0x50 // Less Than
opLTEQ = 0x51 // Less Than or EQual
@ -104,8 +104,8 @@ const (
opOR = 0x5b // logical OR
opNOT = 0x5c // logical NOT
opDELTAP1 = 0x5d
opSDB = 0x5e
opSDS = 0x5f
opSDB = 0x5e // Set Delta Base in the graphics state
opSDS = 0x5f // Set Delta Shift in the graphics state
opADD = 0x60 // ADD
opSUB = 0x61 // SUBtract
opDIV = 0x62 // DIVide
@ -143,15 +143,15 @@ const (
opFLIPRGOFF = 0x82
op_0x83 = 0x83
op_0x84 = 0x84
opSCANCTRL = 0x85
opSCANCTRL = 0x85 // SCAN conversion ConTRoL
opSDPVTL0 = 0x86
opSDPVTL1 = 0x87
opGETINFO = 0x88
opGETINFO = 0x88 // GET INFOrmation
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
opSCANTYPE = 0x8d // SCANTYPE
opINSTCTRL = 0x8e
op_0x8f = 0x8f
op_0x90 = 0x90
@ -272,14 +272,14 @@ const (
var popCount = [256]uint8{
// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
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, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f
1, 1, 0, 2, 0, 1, 1, q, q, q, 2, 1, 1, 0, q, q, // 0x20 - 0x2f
q, q, q, q, q, q, q, q, q, q, q, q, q, 0, q, q, // 0x30 - 0x3f
0, 0, 2, 1, q, q, q, q, q, q, q, q, q, q, q, 0, // 0x40 - 0x4f
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, q, q, q, // 0x50 - 0x5f
0, 0, 2, 1, q, q, q, q, q, q, q, 0, 0, 0, 0, 0, // 0x40 - 0x4f
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, q, 1, 1, // 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, 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, 1, q, q, 1, 1, 3, 2, 2, 1, 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