From 43c4b0b00d20b2a8f1141f0a3ec418ceef460e52 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Fri, 2 Aug 2013 19:13:26 +1000 Subject: [PATCH] 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 --- freetype/truetype/glyph.go | 5 +- freetype/truetype/hint.go | 247 ++++++++++++++++++++++++----------- freetype/truetype/opcodes.go | 46 +++---- 3 files changed, 198 insertions(+), 100 deletions(-) diff --git a/freetype/truetype/glyph.go b/freetype/truetype/glyph.go index c6c9300..392a125 100644 --- a/freetype/truetype/glyph.go +++ b/freetype/truetype/glyph.go @@ -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{} diff --git a/freetype/truetype/hint.go b/freetype/truetype/hint.go index cf5489b..1eb7d46 100644 --- a/freetype/truetype/hint.go +++ b/freetype/truetype/hint.go @@ -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 } diff --git a/freetype/truetype/opcodes.go b/freetype/truetype/opcodes.go index e963793..bb300b3 100644 --- a/freetype/truetype/opcodes.go +++ b/freetype/truetype/opcodes.go @@ -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