// 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. 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 roundSuper45 bool // 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. roundSuper45: false, 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 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 h.gs.roundSuper45 = false case opRTHG: h.gs.roundPeriod = 1 << 6 h.gs.roundPhase = 1 << 5 h.gs.roundThreshold = 1 << 5 h.gs.roundSuper45 = false 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 opALIGNPTS: top -= 2 p := h.point(1, current, h.stack[top]) q := h.point(0, current, h.stack[top+1]) if p == nil || q == nil { return errors.New("truetype: hinting: point out of range") } d := dotProduct(f26dot6(q.X-p.X), f26dot6(q.Y-p.Y), h.gs.pv) / 2 h.move(p, +d, true) h.move(q, -d, true) case opUTP: top-- p := h.point(0, current, h.stack[top]) if p == nil { return errors.New("truetype: hinting: point out of range") } p.Flags &^= flagTouchedX | flagTouchedY 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, true) } } 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.pv) // 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 h.gs.roundSuper45 = false 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 opSCFS: top -= 2 i := h.stack[top] p := h.point(2, current, i) if p == nil { return errors.New("truetype: hinting: point out of range") } c := dotProduct(f26dot6(p.X), f26dot6(p.Y), h.gs.pv) h.move(p, f26dot6(h.stack[top+1])-c, true) if h.gs.zp[2] != 0 { break } q := h.point(2, unhinted, i) if q == nil { return errors.New("truetype: hinting: point out of range") } q.X = p.X q.Y = p.Y case opMD0, opMD1: top-- pt, v, scale := pointType(0), [2]f2dot14{}, false if opcode == opMD0 { pt = current v = h.gs.pv } else if h.gs.zp[0] == 0 || h.gs.zp[1] == 0 { pt = unhinted v = h.gs.dv } else { pt = inFontUnits v = h.gs.dv scale = true } p := h.point(0, pt, h.stack[top-1]) q := h.point(1, pt, h.stack[top]) if p == nil || q == nil { return errors.New("truetype: hinting: point out of range") } d := int32(dotProduct(f26dot6(p.X-q.X), f26dot6(p.Y-q.Y), v)) if scale { d = int32(int64(d*h.scale) / int64(h.font.fUnitsPerEm)) } h.stack[top-1] = d 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 delta 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, opDELTAC1, opDELTAC2, opDELTAC3: goto delta 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 } h.gs.roundSuper45 = opcode == opS45ROUND if h.gs.roundSuper45 { // 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 h.gs.roundSuper45 = false case opRUTG: h.gs.roundPeriod = 1 << 6 h.gs.roundPhase = 0 h.gs.roundThreshold = 1<<6 - 1 h.gs.roundSuper45 = false case opRDTG: h.gs.roundPeriod = 1 << 6 h.gs.roundPhase = 0 h.gs.roundThreshold = 0 h.gs.roundSuper45 = false case opSANGW, opAA: // These ops are "anachronistic" and no longer used. top-- case opFLIPPT: if top < int(h.gs.loop) { return errors.New("truetype: hinting: stack underflow") } points := h.points[glyphZone][current] for ; h.gs.loop != 0; h.gs.loop-- { top-- i := h.stack[top] if i < 0 || len(points) <= int(i) { return errors.New("truetype: hinting: point out of range") } points[i].Flags ^= flagOnCurve } h.gs.loop = 1 case opFLIPRGON, opFLIPRGOFF: top -= 2 i, j, points := h.stack[top], h.stack[top+1], h.points[glyphZone][current] if i < 0 || len(points) <= int(i) || j < 0 || len(points) <= int(j) { return errors.New("truetype: hinting: point out of range") } for ; i <= j; i++ { if opcode == opFLIPRGON { points[i].Flags |= flagOnCurve } else { points[i].Flags &^= flagOnCurve } } 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 opINSTCTRL: // TODO: support instruction execution control? It seems rare, and even when // nominally used (e.g. Source Sans Pro), it seems conditional on extreme or // unusual rasterization conditions. For example, the code snippet at // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html#INSTCTRL // uses INSTCTRL when grid-fitting a rotated or stretched glyph, but // freetype-go does not support rotated or stretched glyphs. top -= 2 default: if opcode < opPUSHB000 { return errors.New("truetype: hinting: unrecognized instruction") } if opcode < opMDRP00000 { // PUSHxxxx opcode. if opcode < opPUSHW000 { opcode -= opPUSHB000 - 1 } else { opcode -= opPUSHW000 - 1 - 0x80 } goto push } if opcode < opMIRP00000 { // MDRPxxxxx opcode. 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) } else { // MIRPxxxxx opcode. 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) } } 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 } delta: { if opcode >= opDELTAC1 && !h.scaledCVTInitialized { h.initializeScaledCVT() } top-- n := h.stack[top] if int32(top) < 2*n { return errors.New("truetype: hinting: stack underflow") } for ; n > 0; n-- { top -= 2 b := h.stack[top] c := (b & 0xf0) >> 4 switch opcode { case opDELTAP2, opDELTAC2: c += 16 case opDELTAP3, 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)) if opcode >= opDELTAC1 { 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) } else { p := h.point(0, current, h.stack[top+1]) if p == nil { return errors.New("truetype: hinting: point out of range") } h.move(p, f26dot6(b), true) } } 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) + 1<<5) >> 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 if h.gs.roundSuper45 { ret /= h.gs.roundPeriod ret *= h.gs.roundPeriod } else { ret &= -h.gs.roundPeriod } if x != 0 && ret < 0 { ret = 0 } return ret + h.gs.roundPhase } ret := -x - h.gs.roundPhase + h.gs.roundThreshold if h.gs.roundSuper45 { ret /= h.gs.roundPeriod ret *= h.gs.roundPeriod } else { ret &= -h.gs.roundPeriod } if ret < 0 { ret = 0 } return -ret - h.gs.roundPhase } func bool2int32(b bool) int32 { if b { return 1 } return 0 }