// Copyright 2012 The Freetype-Go Authors. All rights reserved. // Use of this source code is governed by your choice of either the // FreeType License or the GNU General Public License version 2 (or // any later version), both of which can be found in the LICENSE file. package truetype // This file implements a Truetype bytecode interpreter. // The opcodes are described at https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html import ( "errors" "math" ) const ( twilightZone = 0 glyphZone = 1 numZone = 2 ) type pointType uint32 const ( current pointType = 0 unhinted pointType = 1 inFontUnits pointType = 2 numPointType = 3 ) // callStackEntry is a bytecode call stack entry. type callStackEntry struct { program []byte pc int loopCount int32 } // Hinter implements bytecode hinting. Pass a Hinter to GlyphBuf.Load to hint // the resulting glyph. A Hinter can be re-used to hint a series of glyphs from // a Font. type Hinter struct { stack, store []int32 // functions is a map from function number to bytecode. functions map[int32][]byte // font and scale are the font and scale last used for this Hinter. // Changing the font will require running the new font's fpgm bytecode. // Changing either will require running the font's prep bytecode. font *Font scale int32 // 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 // points and ends are the twilight zone's points, glyph's points // and glyph's contour boundaries. points [numZone][numPointType][]Point ends []int // scaledCVT is the lazily initialized scaled Control Value Table. scaledCVTInitialized bool scaledCVT []f26dot6 } // 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 resetTwilightPoints(f *Font, p []Point) []Point { if n := int(f.maxTwilightPoints) + 4; n <= cap(p) { p = p[:n] for i := range p { p[i] = Point{} } } else { p = make([]Point, n) } return p } func (h *Hinter) init(f *Font, scale int32) error { h.points[twilightZone][0] = resetTwilightPoints(f, h.points[twilightZone][0]) h.points[twilightZone][1] = resetTwilightPoints(f, h.points[twilightZone][1]) h.points[twilightZone][2] = resetTwilightPoints(f, h.points[twilightZone][2]) rescale := h.scale != scale if h.font != f { h.font, rescale = f, true if h.functions == nil { h.functions = make(map[int32][]byte) } else { for k := range h.functions { delete(h.functions, k) } } if x := int(f.maxStackElements); x > len(h.stack) { x += 255 x &^= 255 h.stack = make([]int32, x) } if x := int(f.maxStorage); x > len(h.store) { x += 15 x &^= 15 h.store = make([]int32, x) } if len(f.fpgm) != 0 { if err := h.run(f.fpgm, nil, nil, nil, nil); err != nil { return err } } } if rescale { h.scale = scale h.scaledCVTInitialized = false h.defaultGS = globalDefaultGS if len(f.prep) != 0 { if err := h.run(f.prep, nil, nil, nil, nil); 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, pCurrent, pUnhinted, pInFontUnits []Point, ends []int) error { h.gs = h.defaultGS h.points[glyphZone][current] = pCurrent h.points[glyphZone][unhinted] = pUnhinted h.points[glyphZone][inFontUnits] = pInFontUnits h.ends = ends if len(program) > 50000 { return errors.New("truetype: hinting: too many instructions") } var ( steps, pc, top int opcode uint8 callStack [32]callStackEntry callStackTop int ) for 0 <= pc && pc < len(program) { steps++ if steps == 100000 { return errors.New("truetype: hinting: too many steps") } opcode = program[pc] if popCount[opcode] == q { return errors.New("truetype: hinting: unimplemented instruction") } if top < int(popCount[opcode]) { return errors.New("truetype: hinting: stack underflow") } switch opcode { case opSVTCA0: h.gs.pv = [2]f2dot14{0, 0x4000} h.gs.fv = [2]f2dot14{0, 0x4000} h.gs.dv = [2]f2dot14{0, 0x4000} case opSVTCA1: h.gs.pv = [2]f2dot14{0x4000, 0} h.gs.fv = [2]f2dot14{0x4000, 0} h.gs.dv = [2]f2dot14{0x4000, 0} case opSPVTCA0: h.gs.pv = [2]f2dot14{0, 0x4000} h.gs.dv = [2]f2dot14{0, 0x4000} case opSPVTCA1: h.gs.pv = [2]f2dot14{0x4000, 0} h.gs.dv = [2]f2dot14{0x4000, 0} case opSFVTCA0: h.gs.fv = [2]f2dot14{0, 0x4000} case opSFVTCA1: h.gs.fv = [2]f2dot14{0x4000, 0} case opSPVTL0, opSPVTL1, opSFVTL0, opSFVTL1: top -= 2 p1 := h.point(0, current, h.stack[top+0]) p2 := h.point(0, current, h.stack[top+1]) if p1 == nil || p2 == nil { return errors.New("truetype: hinting: point out of range") } dx := f2dot14(p1.X - p2.X) dy := f2dot14(p1.Y - p2.Y) if dx == 0 && dy == 0 { dx = 0x4000 } else if opcode&1 != 0 { // Counter-clockwise rotation. dx, dy = -dy, dx } v := normalize(dx, dy) if opcode < opSFVTL0 { h.gs.pv = v h.gs.dv = v } else { h.gs.fv = v } case opSPVFS: top -= 2 h.gs.pv = normalize(f2dot14(h.stack[top]), f2dot14(h.stack[top+1])) h.gs.dv = h.gs.pv case opSFVFS: top -= 2 h.gs.fv = normalize(f2dot14(h.stack[top]), f2dot14(h.stack[top+1])) case opGPV: if top+1 >= len(h.stack) { return errors.New("truetype: hinting: stack overflow") } 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.gs.fv[0]) h.stack[top+1] = int32(h.gs.fv[1]) top += 2 case opSFVTPV: h.gs.fv = h.gs.pv case opISECT: top -= 5 p := h.point(2, current, h.stack[top+0]) a0 := h.point(1, current, h.stack[top+1]) a1 := h.point(1, current, h.stack[top+2]) b0 := h.point(0, current, h.stack[top+3]) b1 := h.point(0, current, h.stack[top+4]) if p == nil || a0 == nil || a1 == nil || b0 == nil || b1 == nil { return errors.New("truetype: hinting: point out of range") } dbx := b1.X - b0.X dby := b1.Y - b0.Y dax := a1.X - a0.X day := a1.Y - a0.Y dx := b0.X - a0.X dy := b0.Y - a0.Y discriminant := mulDiv(int64(dax), int64(-dby), 0x40) + mulDiv(int64(day), int64(dbx), 0x40) dotProduct := mulDiv(int64(dax), int64(dbx), 0x40) + mulDiv(int64(day), int64(dby), 0x40) // The discriminant above is actually a cross product of vectors // da and db. Together with the dot product, they can be used as // surrogates for sine and cosine of the angle between the vectors. // Indeed, // dotproduct = |da||db|cos(angle) // discriminant = |da||db|sin(angle) // We use these equations to reject grazing intersections by // thresholding abs(tan(angle)) at 1/19, corresponding to 3 degrees. absDisc, absDotP := discriminant, dotProduct if absDisc < 0 { absDisc = -absDisc } if absDotP < 0 { absDotP = -absDotP } if 19*absDisc > absDotP { val := mulDiv(int64(dx), int64(-dby), 0x40) + mulDiv(int64(dy), int64(dbx), 0x40) rx := mulDiv(val, int64(dax), discriminant) ry := mulDiv(val, int64(day), discriminant) p.X = a0.X + int32(rx) p.Y = a0.Y + int32(ry) } else { p.X = (a0.X + a1.X + b0.X + b1.X) / 4 p.Y = (a0.Y + a1.Y + b0.Y + b1.Y) / 4 } p.Flags |= flagTouchedX | flagTouchedY 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.gs.loop = h.stack[top] case opRTG: h.gs.roundPeriod = 1 << 6 h.gs.roundPhase = 0 h.gs.roundThreshold = 1 << 5 case opRTHG: h.gs.roundPeriod = 1 << 6 h.gs.roundPhase = 1 << 5 h.gs.roundThreshold = 1 << 5 case opSMD: top-- h.gs.minDist = f26dot6(h.stack[top]) case opELSE: opcode = 1 goto ifelse case opJMPR: top-- 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.font.scale(h.scale * h.stack[top])) case opDUP: if top >= len(h.stack) { return errors.New("truetype: hinting: stack overflow") } h.stack[top] = h.stack[top-1] top++ case opPOP: top-- case opCLEAR: top = 0 case opSWAP: h.stack[top-1], h.stack[top-2] = h.stack[top-2], h.stack[top-1] case opDEPTH: if top >= len(h.stack) { return errors.New("truetype: hinting: stack overflow") } h.stack[top] = int32(top) top++ case opCINDEX, opMINDEX: x := int(h.stack[top-1]) if x <= 0 || x >= top { return errors.New("truetype: hinting: invalid data") } h.stack[top-1] = h.stack[top-1-x] if opcode == opMINDEX { copy(h.stack[top-1-x:top-1], h.stack[top-x:top]) top-- } case opLOOPCALL, opCALL: if callStackTop >= len(callStack) { return errors.New("truetype: hinting: call stack overflow") } top-- f, ok := h.functions[h.stack[top]] if !ok { return errors.New("truetype: hinting: undefined function") } callStack[callStackTop] = callStackEntry{program, pc, 1} if opcode == opLOOPCALL { top-- if h.stack[top] == 0 { break } callStack[callStackTop].loopCount = h.stack[top] } callStackTop++ program, pc = f, 0 continue case opFDEF: // Save all bytecode up until the next ENDF. startPC := pc + 1 fdefloop: for { pc++ if pc >= len(program) { return errors.New("truetype: hinting: unbalanced FDEF") } switch program[pc] { case opFDEF: return errors.New("truetype: hinting: nested FDEF") case opENDF: top-- h.functions[h.stack[top]] = program[startPC : pc+1] break fdefloop default: var ok bool pc, ok = skipInstructionPayload(program, pc) if !ok { return errors.New("truetype: hinting: unbalanced FDEF") } } } case opENDF: if callStackTop == 0 { return errors.New("truetype: hinting: call stack underflow") } callStackTop-- callStack[callStackTop].loopCount-- if callStack[callStackTop].loopCount != 0 { callStackTop++ pc = 0 continue } program, pc = callStack[callStackTop].program, callStack[callStackTop].pc case opMDAP0, opMDAP1: top-- i := h.stack[top] p := h.point(0, current, i) if p == nil { return errors.New("truetype: hinting: point out of range") } distance := f26dot6(0) if opcode == opMDAP1 { distance = dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv) // TODO: metrics compensation. distance = h.round(distance) - distance } h.move(p, distance, true) h.gs.rp[0] = i h.gs.rp[1] = i case opIUP0, opIUP1: iupY, mask := opcode == opIUP0, uint32(flagTouchedX) if iupY { mask = flagTouchedY } prevEnd := 0 for _, end := range h.ends { for i := prevEnd; i < end; i++ { for i < end && h.points[glyphZone][current][i].Flags&mask == 0 { i++ } if i == end { break } firstTouched, curTouched := i, i i++ for ; i < end; i++ { if h.points[glyphZone][current][i].Flags&mask != 0 { h.iupInterp(iupY, curTouched+1, i-1, curTouched, i) curTouched = i } } if curTouched == firstTouched { h.iupShift(iupY, prevEnd, end, curTouched) } else { h.iupInterp(iupY, curTouched+1, end-1, curTouched, firstTouched) if firstTouched > 0 { h.iupInterp(iupY, prevEnd, firstTouched-1, curTouched, firstTouched) } } } prevEnd = end } case opSHP0, opSHP1: if top < int(h.gs.loop) { return errors.New("truetype: hinting: stack underflow") } _, _, d, ok := h.displacement(opcode&1 == 0) if !ok { return errors.New("truetype: hinting: point out of range") } for ; h.gs.loop != 0; h.gs.loop-- { top-- p := h.point(2, current, h.stack[top]) if p == nil { return errors.New("truetype: hinting: point out of range") } h.move(p, d, true) } h.gs.loop = 1 case opSHC0, opSHC1: top-- zonePointer, i, d, ok := h.displacement(opcode&1 == 0) if !ok { return errors.New("truetype: hinting: point out of range") } if h.gs.zp[2] == 0 { // TODO: implement this when we have a glyph that does this. return errors.New("hinting: unimplemented SHC instruction") } contour := h.stack[top] if contour < 0 || len(ends) <= int(contour) { return errors.New("truetype: hinting: contour out of range") } j0, j1 := int32(0), int32(h.ends[contour]) if contour > 0 { j0 = int32(h.ends[contour-1]) } move := h.gs.zp[zonePointer] != h.gs.zp[2] for j := j0; j < j1; j++ { if move || j != i { h.move(h.point(2, current, j), d, false) } } case opSHZ0, opSHZ1: top-- zonePointer, i, d, ok := h.displacement(opcode&1 == 0) if !ok { return errors.New("truetype: hinting: point out of range") } // As per C Freetype, SHZ doesn't move the phantom points, or mark // the points as touched. limit := int32(len(h.points[h.gs.zp[2]][current])) if h.gs.zp[2] == glyphZone { limit -= 4 } for j := int32(0); j < limit; j++ { if i != j || h.gs.zp[zonePointer] != h.gs.zp[2] { h.move(h.point(2, current, j), d, false) } } case opSHPIX: top-- d := f26dot6(h.stack[top]) if top < int(h.gs.loop) { return errors.New("truetype: hinting: stack underflow") } for ; h.gs.loop != 0; h.gs.loop-- { top-- p := h.point(2, current, h.stack[top]) if p == nil { return errors.New("truetype: hinting: point out of range") } h.move(p, d, true) } h.gs.loop = 1 case opIP: if top < int(h.gs.loop) { return errors.New("truetype: hinting: stack underflow") } pointType := inFontUnits twilight := h.gs.zp[0] == 0 || h.gs.zp[1] == 0 || h.gs.zp[2] == 0 if twilight { pointType = unhinted } p := h.point(1, pointType, h.gs.rp[2]) oldP := h.point(0, pointType, h.gs.rp[1]) oldRange := dotProduct(f26dot6(p.X-oldP.X), f26dot6(p.Y-oldP.Y), h.gs.dv) p = h.point(1, current, h.gs.rp[2]) curP := h.point(0, current, h.gs.rp[1]) curRange := dotProduct(f26dot6(p.X-curP.X), f26dot6(p.Y-curP.Y), h.gs.pv) for ; h.gs.loop != 0; h.gs.loop-- { top-- i := h.stack[top] p = h.point(2, pointType, i) oldDist := dotProduct(f26dot6(p.X-oldP.X), f26dot6(p.Y-oldP.Y), h.gs.dv) p = h.point(2, current, i) curDist := dotProduct(f26dot6(p.X-curP.X), f26dot6(p.Y-curP.Y), h.gs.pv) newDist := f26dot6(0) if oldDist != 0 { if oldRange != 0 { newDist = f26dot6(mulDiv(int64(oldDist), int64(curRange), int64(oldRange))) } else { newDist = -oldDist } } h.move(p, newDist-curDist, true) } h.gs.loop = 1 case opMSIRP0, opMSIRP1: top -= 2 i := h.stack[top] distance := f26dot6(h.stack[top+1]) // TODO: special case h.gs.zp[1] == 0 in C Freetype. ref := h.point(0, current, h.gs.rp[0]) p := h.point(1, current, i) if ref == nil || p == nil { return errors.New("truetype: hinting: point out of range") } curDist := dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.dv) // Set-RP0 bit. if opcode == opMSIRP1 { h.gs.rp[0] = i } h.gs.rp[1] = h.gs.rp[0] h.gs.rp[2] = i // Move the point. h.move(p, distance-curDist, true) case opALIGNRP: if top < int(h.gs.loop) { return errors.New("truetype: hinting: stack underflow") } ref := h.point(0, current, h.gs.rp[0]) if ref == nil { return errors.New("truetype: hinting: point out of range") } for ; h.gs.loop != 0; h.gs.loop-- { top-- p := h.point(1, current, h.stack[top]) if p == nil { return errors.New("truetype: hinting: point out of range") } h.move(p, -dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv), true) } h.gs.loop = 1 case opRTDG: h.gs.roundPeriod = 1 << 5 h.gs.roundPhase = 0 h.gs.roundThreshold = 1 << 4 case opMIAP0, opMIAP1: top -= 2 i := h.stack[top] distance := h.getScaledCVT(h.stack[top+1]) if h.gs.zp[0] == 0 { p := h.point(0, unhinted, i) q := h.point(0, current, i) p.X = int32((int64(distance) * int64(h.gs.fv[0])) >> 14) p.Y = int32((int64(distance) * int64(h.gs.fv[1])) >> 14) *q = *p } p := h.point(0, current, i) oldDist := dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv) if opcode == opMIAP1 { if (distance - oldDist).abs() > h.gs.controlValueCutIn { distance = oldDist } // TODO: metrics compensation. distance = h.round(distance) } h.move(p, distance-oldDist, true) h.gs.rp[0] = i h.gs.rp[1] = i case opNPUSHB: opcode = 0 goto push case opNPUSHW: 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 opWCVTP: top -= 2 h.setScaledCVT(h.stack[top], f26dot6(h.stack[top+1])) case opRCVT: h.stack[top-1] = int32(h.getScaledCVT(h.stack[top-1])) case opGC0, opGC1: i := h.stack[top-1] if opcode == opGC0 { p := h.point(2, current, i) h.stack[top-1] = int32(dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv)) } else { p := h.point(2, unhinted, i) // Using dv as per C Freetype. h.stack[top-1] = int32(dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.dv)) } case opMD0, opMD1: top-- i, j := h.stack[top-1], h.stack[top] if opcode == opMD1 { p := h.point(0, current, i) q := h.point(1, current, j) h.stack[top-1] = int32(dotProduct(f26dot6(p.X-q.X), f26dot6(p.Y-q.Y), h.gs.pv)) } else { // TODO: do we need to check (h.gs.zp[0] == 0 || h.gs.zp[1] == 0) // as C Freetype does, similar to the MDRP instructions? p := h.point(0, unhinted, i) q := h.point(1, unhinted, j) // Use dv for MD0 as in C Freetype. h.stack[top-1] = int32(dotProduct(f26dot6(p.X-q.X), f26dot6(p.Y-q.Y), h.gs.dv)) } 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. case opLT: top-- h.stack[top-1] = bool2int32(h.stack[top-1] < h.stack[top]) case opLTEQ: top-- h.stack[top-1] = bool2int32(h.stack[top-1] <= h.stack[top]) case opGT: top-- h.stack[top-1] = bool2int32(h.stack[top-1] > h.stack[top]) case opGTEQ: top-- h.stack[top-1] = bool2int32(h.stack[top-1] >= h.stack[top]) case opEQ: top-- h.stack[top-1] = bool2int32(h.stack[top-1] == h.stack[top]) case opNEQ: top-- h.stack[top-1] = bool2int32(h.stack[top-1] != h.stack[top]) case opODD, opEVEN: i := h.round(f26dot6(h.stack[top-1])) >> 6 h.stack[top-1] = int32(i&1) ^ int32(opcode-opODD) case opIF: top-- if h.stack[top] == 0 { opcode = 0 goto ifelse } case opEIF: // No-op. case opAND: top-- h.stack[top-1] = bool2int32(h.stack[top-1] != 0 && h.stack[top] != 0) case opOR: top-- h.stack[top-1] = bool2int32(h.stack[top-1]|h.stack[top] != 0) case opNOT: h.stack[top-1] = bool2int32(h.stack[top-1] == 0) case opDELTAP1: goto deltap 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] case opSUB: top-- h.stack[top-1] -= h.stack[top] case opDIV: top-- if h.stack[top] == 0 { return errors.New("truetype: hinting: division by zero") } h.stack[top-1] = int32(f26dot6(h.stack[top-1]).div(f26dot6(h.stack[top]))) case opMUL: top-- h.stack[top-1] = int32(f26dot6(h.stack[top-1]).mul(f26dot6(h.stack[top]))) case opABS: if h.stack[top-1] < 0 { h.stack[top-1] = -h.stack[top-1] } case opNEG: h.stack[top-1] = -h.stack[top-1] case opFLOOR: h.stack[top-1] &^= 63 case opCEILING: 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] = 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 // 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 opWCVTF: top -= 2 h.setScaledCVT(h.stack[top], f26dot6(h.font.scale(h.scale*h.stack[top+1]))) case opDELTAP2, opDELTAP3: goto deltap case opDELTAC1, opDELTAC2, opDELTAC3: goto deltac case opSROUND, opS45ROUND: top-- switch (h.stack[top] >> 6) & 0x03 { case 0: h.gs.roundPeriod = 1 << 5 case 1, 3: h.gs.roundPeriod = 1 << 6 case 2: 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.gs.roundPeriod *= 46341 h.gs.roundPeriod /= 65536 } h.gs.roundPhase = h.gs.roundPeriod * f26dot6((h.stack[top]>>4)&0x03) / 4 if x := h.stack[top] & 0x0f; x != 0 { h.gs.roundThreshold = h.gs.roundPeriod * f26dot6(x-4) / 8 } else { h.gs.roundThreshold = h.gs.roundPeriod - 1 } case opJROT: top -= 2 if h.stack[top+1] != 0 { pc += int(h.stack[top]) continue } case opJROF: top -= 2 if h.stack[top+1] == 0 { pc += int(h.stack[top]) continue } case opROFF: h.gs.roundPeriod = 0 h.gs.roundPhase = 0 h.gs.roundThreshold = 0 case opRUTG: h.gs.roundPeriod = 1 << 6 h.gs.roundPhase = 0 h.gs.roundThreshold = 1<<6 - 1 case opRDTG: 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 opSDPVTL0, opSDPVTL1: top -= 2 for i := 0; i < 2; i++ { pt := unhinted if i != 0 { pt = current } p := h.point(1, pt, h.stack[top]) q := h.point(2, pt, h.stack[top+1]) if p == nil || q == nil { return errors.New("truetype: hinting: point out of range") } dx := f2dot14(p.X - q.X) dy := f2dot14(p.Y - q.Y) if dx == 0 && dy == 0 { dx = 0x4000 } else if opcode&1 != 0 { // Counter-clockwise rotation. dx, dy = -dy, dx } if i == 0 { h.gs.dv = normalize(dx, dy) } else { h.gs.pv = normalize(dx, dy) } } 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") 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 opSCANTYPE: // We do not support dropout control, as we always rasterize grayscale glyphs. top-- case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111: opcode -= opPUSHB000 - 1 goto push case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111: opcode -= opPUSHW000 - 1 opcode += 0x80 goto push case opMDRP00000, opMDRP00001, opMDRP00010, opMDRP00011, opMDRP00100, opMDRP00101, opMDRP00110, opMDRP00111, opMDRP01000, opMDRP01001, opMDRP01010, opMDRP01011, opMDRP01100, opMDRP01101, opMDRP01110, opMDRP01111, opMDRP10000, opMDRP10001, opMDRP10010, opMDRP10011, opMDRP10100, opMDRP10101, opMDRP10110, opMDRP10111, opMDRP11000, opMDRP11001, opMDRP11010, opMDRP11011, opMDRP11100, opMDRP11101, opMDRP11110, opMDRP11111: top-- i := h.stack[top] ref := h.point(0, current, h.gs.rp[0]) p := h.point(1, current, i) if ref == nil || p == nil { return errors.New("truetype: hinting: point out of range") } oldDist := f26dot6(0) if h.gs.zp[0] == 0 || h.gs.zp[1] == 0 { p0 := h.point(1, unhinted, i) p1 := h.point(0, unhinted, h.gs.rp[0]) oldDist = dotProduct(f26dot6(p0.X-p1.X), f26dot6(p0.Y-p1.Y), h.gs.dv) } else { p0 := h.point(1, inFontUnits, i) p1 := h.point(0, inFontUnits, h.gs.rp[0]) oldDist = dotProduct(f26dot6(p0.X-p1.X), f26dot6(p0.Y-p1.Y), h.gs.dv) oldDist = f26dot6(h.font.scale(h.scale * int32(oldDist))) } // Single-width cut-in test. if x := (oldDist - h.gs.singleWidth).abs(); x < h.gs.singleWidthCutIn { if oldDist >= 0 { oldDist = +h.gs.singleWidth } else { oldDist = -h.gs.singleWidth } } // Rounding bit. // TODO: metrics compensation. distance := oldDist if opcode&0x04 != 0 { distance = h.round(oldDist) } // Minimum distance bit. if opcode&0x08 != 0 { if oldDist >= 0 { if distance < h.gs.minDist { distance = h.gs.minDist } } else { if distance > -h.gs.minDist { distance = -h.gs.minDist } } } // Set-RP0 bit. h.gs.rp[1] = h.gs.rp[0] h.gs.rp[2] = i if opcode&0x10 != 0 { h.gs.rp[0] = i } // Move the point. oldDist = dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv) h.move(p, distance-oldDist, true) case opMIRP00000, opMIRP00001, opMIRP00010, opMIRP00011, opMIRP00100, opMIRP00101, opMIRP00110, opMIRP00111, opMIRP01000, opMIRP01001, opMIRP01010, opMIRP01011, opMIRP01100, opMIRP01101, opMIRP01110, opMIRP01111, opMIRP10000, opMIRP10001, opMIRP10010, opMIRP10011, opMIRP10100, opMIRP10101, opMIRP10110, opMIRP10111, opMIRP11000, opMIRP11001, opMIRP11010, opMIRP11011, opMIRP11100, opMIRP11101, opMIRP11110, opMIRP11111: top -= 2 i := h.stack[top] cvtDist := h.getScaledCVT(h.stack[top+1]) if (cvtDist - h.gs.singleWidth).abs() < h.gs.singleWidthCutIn { if cvtDist >= 0 { cvtDist = +h.gs.singleWidth } else { cvtDist = -h.gs.singleWidth } } if h.gs.zp[1] == 0 { // TODO: implement once we have a .ttf file that triggers // this, so that we can step through C's freetype. return errors.New("truetype: hinting: unimplemented twilight point adjustment") } ref := h.point(0, unhinted, h.gs.rp[0]) p := h.point(1, unhinted, i) if ref == nil || p == nil { return errors.New("truetype: hinting: point out of range") } oldDist := dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.dv) ref = h.point(0, current, h.gs.rp[0]) p = h.point(1, current, i) if ref == nil || p == nil { return errors.New("truetype: hinting: point out of range") } curDist := dotProduct(f26dot6(p.X-ref.X), f26dot6(p.Y-ref.Y), h.gs.pv) if h.gs.autoFlip && oldDist^cvtDist < 0 { cvtDist = -cvtDist } // Rounding bit. // TODO: metrics compensation. distance := cvtDist if opcode&0x04 != 0 { // The CVT value is only used if close enough to oldDist. if (h.gs.zp[0] == h.gs.zp[1]) && ((cvtDist - oldDist).abs() > h.gs.controlValueCutIn) { distance = oldDist } distance = h.round(distance) } // Minimum distance bit. if opcode&0x08 != 0 { if oldDist >= 0 { if distance < h.gs.minDist { distance = h.gs.minDist } } else { if distance > -h.gs.minDist { distance = -h.gs.minDist } } } // Set-RP0 bit. h.gs.rp[1] = h.gs.rp[0] h.gs.rp[2] = i if opcode&0x10 != 0 { h.gs.rp[0] = i } // Move the point. h.move(p, distance-curDist, true) default: return errors.New("truetype: hinting: unrecognized instruction") } pc++ continue ifelse: // Skip past bytecode until the next ELSE (if opcode == 0) or the // next EIF (for all opcodes). Opcode == 0 means that we have come // from an IF. Opcode == 1 means that we have come from an ELSE. { ifelseloop: for depth := 0; ; { pc++ if pc >= len(program) { return errors.New("truetype: hinting: unbalanced IF or ELSE") } switch program[pc] { case opIF: depth++ case opELSE: if depth == 0 && opcode == 0 { break ifelseloop } case opEIF: depth-- if depth < 0 { break ifelseloop } default: var ok bool pc, ok = skipInstructionPayload(program, pc) if !ok { return errors.New("truetype: hinting: unbalanced IF or ELSE") } } } pc++ continue } push: // Push n elements from the program to the stack, where n is the low 7 bits of // opcode. If the low 7 bits are zero, then n is the next byte from the program. // The high bit being 0 means that the elements are zero-extended bytes. // The high bit being 1 means that the elements are sign-extended words. { width := 1 if opcode&0x80 != 0 { opcode &^= 0x80 width = 2 } if opcode == 0 { pc++ if pc >= len(program) { return errors.New("truetype: hinting: insufficient data") } opcode = program[pc] } pc++ if top+int(opcode) > len(h.stack) { return errors.New("truetype: hinting: stack overflow") } if pc+width*int(opcode) > len(program) { return errors.New("truetype: hinting: insufficient data") } for ; opcode > 0; opcode-- { if width == 1 { h.stack[top] = int32(program[pc]) } else { h.stack[top] = int32(int8(program[pc]))<<8 | int32(program[pc+1]) } top++ pc += width } continue } // TODO: merge the deltap and deltac code, when we have enough test cases // (at least full coverage of Arial) to have confidence in re-factoring. deltap: { top-- n := f26dot6(h.stack[top]) if top < 2*int(h.gs.loop) { return errors.New("truetype: hinting: stack underflow") } for ; n > 0; n-- { top -= 2 p := h.point(0, current, h.stack[top+1]) if p == nil { return errors.New("truetype: hinting: point out of range") } b := h.stack[top] c := (b & 0xf0) >> 4 switch opcode { case opDELTAP2: c += 16 case opDELTAP3: c += 32 } c += h.gs.deltaBase if ppem := (h.scale + 1<<5) >> 6; ppem != c { continue } b = (b & 0x0f) - 8 if b >= 0 { b++ } b = b * 64 / (1 << uint32(h.gs.deltaShift)) h.move(p, f26dot6(b), true) } pc++ continue } deltac: { if !h.scaledCVTInitialized { h.initializeScaledCVT() } top-- n := f26dot6(h.stack[top]) if top < 2*int(h.gs.loop) { return errors.New("truetype: hinting: stack underflow") } for ; n > 0; n-- { top -= 2 b := h.stack[top] c := (b & 0xf0) >> 4 switch opcode { case opDELTAC2: c += 16 case opDELTAC3: c += 32 } c += h.gs.deltaBase if ppem := (h.scale + 1<<5) >> 6; ppem != c { continue } b = (b & 0x0f) - 8 if b >= 0 { b++ } b = b * 64 / (1 << uint32(h.gs.deltaShift)) a := h.stack[top+1] if a < 0 || len(h.scaledCVT) <= int(a) { return errors.New("truetype: hinting: index out of range") } h.scaledCVT[a] += f26dot6(b) } pc++ continue } } return nil } func (h *Hinter) initializeScaledCVT() { h.scaledCVTInitialized = true if n := len(h.font.cvt) / 2; n <= cap(h.scaledCVT) { h.scaledCVT = h.scaledCVT[:n] } else { if n < 32 { n = 32 } h.scaledCVT = make([]f26dot6, len(h.font.cvt)/2, n) } for i := range h.scaledCVT { unscaled := uint16(h.font.cvt[2*i])<<8 | uint16(h.font.cvt[2*i+1]) h.scaledCVT[i] = f26dot6(h.font.scale(h.scale * int32(int16(unscaled)))) } } // getScaledCVT returns the scaled value from the font's Control Value Table. func (h *Hinter) getScaledCVT(i int32) f26dot6 { if !h.scaledCVTInitialized { h.initializeScaledCVT() } if i < 0 || len(h.scaledCVT) <= int(i) { return 0 } return h.scaledCVT[i] } // setScaledCVT overrides the scaled value from the font's Control Value Table. func (h *Hinter) setScaledCVT(i int32, v f26dot6) { if !h.scaledCVTInitialized { h.initializeScaledCVT() } if i < 0 || len(h.scaledCVT) <= int(i) { return } h.scaledCVT[i] = v } func (h *Hinter) point(zonePointer uint32, pt pointType, i int32) *Point { points := h.points[h.gs.zp[zonePointer]][pt] if i < 0 || len(points) <= int(i) { return nil } return &points[i] } func (h *Hinter) move(p *Point, distance f26dot6, touch bool) { fvx := int64(h.gs.fv[0]) pvx := int64(h.gs.pv[0]) if fvx == 0x4000 && pvx == 0x4000 { p.X += int32(distance) if touch { p.Flags |= flagTouchedX } return } fvy := int64(h.gs.fv[1]) pvy := int64(h.gs.pv[1]) if fvy == 0x4000 && pvy == 0x4000 { p.Y += int32(distance) if touch { p.Flags |= flagTouchedY } return } fvDotPv := (fvx*pvx + fvy*pvy) >> 14 if fvx != 0 { p.X += int32(mulDiv(fvx, int64(distance), fvDotPv)) if touch { p.Flags |= flagTouchedX } } if fvy != 0 { p.Y += int32(mulDiv(fvy, int64(distance), fvDotPv)) if touch { p.Flags |= flagTouchedY } } } func (h *Hinter) iupInterp(interpY bool, p1, p2, ref1, ref2 int) { if p1 > p2 { return } if ref1 >= len(h.points[glyphZone][current]) || ref2 >= len(h.points[glyphZone][current]) { return } var ifu1, ifu2 int32 if interpY { ifu1 = h.points[glyphZone][inFontUnits][ref1].Y ifu2 = h.points[glyphZone][inFontUnits][ref2].Y } else { ifu1 = h.points[glyphZone][inFontUnits][ref1].X ifu2 = h.points[glyphZone][inFontUnits][ref2].X } if ifu1 > ifu2 { ifu1, ifu2 = ifu2, ifu1 ref1, ref2 = ref2, ref1 } var unh1, unh2, delta1, delta2 int32 if interpY { unh1 = h.points[glyphZone][unhinted][ref1].Y unh2 = h.points[glyphZone][unhinted][ref2].Y delta1 = h.points[glyphZone][current][ref1].Y - unh1 delta2 = h.points[glyphZone][current][ref2].Y - unh2 } else { unh1 = h.points[glyphZone][unhinted][ref1].X unh2 = h.points[glyphZone][unhinted][ref2].X delta1 = h.points[glyphZone][current][ref1].X - unh1 delta2 = h.points[glyphZone][current][ref2].X - unh2 } var xy, ifuXY int32 if ifu1 == ifu2 { for i := p1; i <= p2; i++ { if interpY { xy = h.points[glyphZone][unhinted][i].Y } else { xy = h.points[glyphZone][unhinted][i].X } if xy <= unh1 { xy += delta1 } else { xy += delta2 } if interpY { h.points[glyphZone][current][i].Y = xy } else { h.points[glyphZone][current][i].X = xy } } return } scale, scaleOK := int64(0), false for i := p1; i <= p2; i++ { if interpY { xy = h.points[glyphZone][unhinted][i].Y ifuXY = h.points[glyphZone][inFontUnits][i].Y } else { xy = h.points[glyphZone][unhinted][i].X ifuXY = h.points[glyphZone][inFontUnits][i].X } if xy <= unh1 { xy += delta1 } else if xy >= unh2 { xy += delta2 } else { if !scaleOK { scaleOK = true scale = mulDiv(int64(unh2+delta2-unh1-delta1), 0x10000, int64(ifu2-ifu1)) } numer := int64(ifuXY-ifu1) * scale if numer >= 0 { numer += 0x8000 } else { numer -= 0x8000 } xy = unh1 + delta1 + int32(numer/0x10000) } if interpY { h.points[glyphZone][current][i].Y = xy } else { h.points[glyphZone][current][i].X = xy } } } func (h *Hinter) iupShift(interpY bool, p1, p2, p int) { var delta int32 if interpY { delta = h.points[glyphZone][current][p].Y - h.points[glyphZone][unhinted][p].Y } else { delta = h.points[glyphZone][current][p].X - h.points[glyphZone][unhinted][p].X } if delta == 0 { return } for i := p1; i < p2; i++ { if i == p { continue } if interpY { h.points[glyphZone][current][i].Y += delta } else { h.points[glyphZone][current][i].X += delta } } } func (h *Hinter) displacement(useZP1 bool) (zonePointer uint32, i int32, d f26dot6, ok bool) { zonePointer, i = uint32(0), h.gs.rp[1] if useZP1 { zonePointer, i = 1, h.gs.rp[2] } p := h.point(zonePointer, current, i) q := h.point(zonePointer, unhinted, i) if p == nil || q == nil { return 0, 0, 0, false } d = dotProduct(f26dot6(p.X-q.X), f26dot6(p.Y-q.Y), h.gs.pv) return zonePointer, i, d, true } // skipInstructionPayload increments pc by the extra data that follows a // variable length PUSHB or PUSHW instruction. func skipInstructionPayload(program []byte, pc int) (newPC int, ok bool) { switch program[pc] { case opNPUSHB: pc++ if pc >= len(program) { return 0, false } pc += int(program[pc]) case opNPUSHW: pc++ if pc >= len(program) { return 0, false } pc += 2 * int(program[pc]) case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011, opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111: pc += int(program[pc] - (opPUSHB000 - 1)) case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011, opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111: pc += 2 * int(program[pc]-(opPUSHW000-1)) } return pc, true } // f2dot14 is a 2.14 fixed point number. type f2dot14 int16 func normalize(x, y f2dot14) [2]f2dot14 { fx, fy := float64(x), float64(y) l := 0x4000 / math.Hypot(fx, fy) fx *= l if fx >= 0 { fx += 0.5 } else { fx -= 0.5 } fy *= l if fy >= 0 { fy += 0.5 } else { fy -= 0.5 } return [2]f2dot14{f2dot14(fx), f2dot14(fy)} } // f26dot6 is a 26.6 fixed point number. type f26dot6 int32 // abs returns abs(x) in 26.6 fixed point arithmetic. func (x f26dot6) abs() f26dot6 { if x < 0 { return -x } return x } // div returns x/y in 26.6 fixed point arithmetic. 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 (x f26dot6) mul(y f26dot6) f26dot6 { return f26dot6(int64(x) * int64(y) >> 6) } // dotProduct returns the dot product of [x, y] and q. It is almost the same as // px := int64(x) // py := int64(y) // qx := int64(q[0]) // qy := int64(q[1]) // return f26dot6((px*qx + py*qy + 1<<13) >> 14) // except that the computation is done with 32-bit integers to produce exactly // the same rounding behavior as C Freetype. func dotProduct(x, y f26dot6, q [2]f2dot14) f26dot6 { // Compute x*q[0] as 64-bit value. l := uint32((int32(x) & 0xFFFF) * int32(q[0])) m := (int32(x) >> 16) * int32(q[0]) lo1 := l + (uint32(m) << 16) hi1 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo1 < l) // Compute y*q[1] as 64-bit value. l = uint32((int32(y) & 0xFFFF) * int32(q[1])) m = (int32(y) >> 16) * int32(q[1]) lo2 := l + (uint32(m) << 16) hi2 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo2 < l) // Add them. lo := lo1 + lo2 hi := hi1 + hi2 + bool2int32(lo < lo1) // Divide the result by 2^14 with rounding. s := hi >> 31 l = lo + uint32(s) hi += s + bool2int32(l < lo) lo = l l = lo + 0x2000 hi += bool2int32(l < lo) return f26dot6((uint32(hi) << 18) | (l >> 14)) } // mulDiv returns x*y/z, rounded to the nearest integer. func mulDiv(x, y, z int64) int64 { xy := x * y if z < 0 { xy, z = -xy, -z } if xy >= 0 { xy += z / 2 } else { xy -= z / 2 } return xy / z } // 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.gs.roundPeriod == 0 { // Rounding is off. return x } if x >= 0 { ret := (x - h.gs.roundPhase + h.gs.roundThreshold) & -h.gs.roundPeriod if x != 0 && ret < 0 { ret = 0 } return ret + h.gs.roundPhase } ret := -((-x - h.gs.roundPhase + h.gs.roundThreshold) & -h.gs.roundPeriod) if ret > 0 { ret = 0 } return ret - h.gs.roundPhase } func bool2int32(b bool) int32 { if b { return 1 } return 0 }