diff options
author | =Corey Hulen <corey@hulen.com> | 2015-09-17 09:21:56 -0700 |
---|---|---|
committer | =Corey Hulen <corey@hulen.com> | 2015-09-17 09:21:56 -0700 |
commit | 9b5198c69c01ff11b0977282b61cf2222ef2f2b1 (patch) | |
tree | a5179226ff013f082a6629dbf888454c53611383 /Godeps/_workspace/src/github.com/golang/freetype/truetype/hint.go | |
parent | 51c3445e694e68fdecd809f17fbaa64751e5931c (diff) | |
parent | 3edd8dbdf65e0b7be422a46328cb85482aa78f69 (diff) | |
download | chat-9b5198c69c01ff11b0977282b61cf2222ef2f2b1.tar.gz chat-9b5198c69c01ff11b0977282b61cf2222ef2f2b1.tar.bz2 chat-9b5198c69c01ff11b0977282b61cf2222ef2f2b1.zip |
Merge branch 'master' into PLT-92
Diffstat (limited to 'Godeps/_workspace/src/github.com/golang/freetype/truetype/hint.go')
-rw-r--r-- | Godeps/_workspace/src/github.com/golang/freetype/truetype/hint.go | 1763 |
1 files changed, 1763 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/golang/freetype/truetype/hint.go b/Godeps/_workspace/src/github.com/golang/freetype/truetype/hint.go new file mode 100644 index 000000000..0315de511 --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/freetype/truetype/hint.go @@ -0,0 +1,1763 @@ +// 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" + + "golang.org/x/image/math/fixed" +) + +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 fixed.Int26_6 + + // 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 []fixed.Int26_6 +} + +// 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 fixed.Int26_6 + // Delta base / shift. + deltaBase, deltaShift int32 + // Minimum distance. + minDist fixed.Int26_6 + // Loop count. + loop int32 + // Rounding policy. + roundPeriod, roundPhase, roundThreshold fixed.Int26_6 + 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 a fixed.Int26_6. + deltaBase: 9, + deltaShift: 3, + minDist: 1 << 6, // 1 as a fixed.Int26_6. + loop: 1, + roundPeriod: 1 << 6, // 1 as a fixed.Int26_6. + roundThreshold: 1 << 5, // 1/2 as a fixed.Int26_6. + 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 fixed.Int26_6) 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 + fixed.Int26_6(rx) + p.Y = a0.Y + fixed.Int26_6(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 = fixed.Int26_6(h.stack[top]) + + case opELSE: + opcode = 1 + goto ifelse + + case opJMPR: + top-- + pc += int(h.stack[top]) + continue + + case opSCVTCI: + top-- + h.gs.controlValueCutIn = fixed.Int26_6(h.stack[top]) + + case opSSWCI: + top-- + h.gs.singleWidthCutIn = fixed.Int26_6(h.stack[top]) + + case opSSW: + top-- + h.gs.singleWidth = h.font.scale(h.scale * fixed.Int26_6(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(fixed.Int26_6(q.X-p.X), fixed.Int26_6(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 := fixed.Int26_6(0) + if opcode == opMDAP1 { + distance = dotProduct(p.X, 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 := fixed.Int26_6(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(p.X-oldP.X, 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(p.X-curP.X, 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(p.X-oldP.X, p.Y-oldP.Y, h.gs.dv) + p = h.point(2, current, i) + curDist := dotProduct(p.X-curP.X, p.Y-curP.Y, h.gs.pv) + newDist := fixed.Int26_6(0) + if oldDist != 0 { + if oldRange != 0 { + newDist = fixed.Int26_6(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 := fixed.Int26_6(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(p.X-ref.X, 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(p.X-ref.X, 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 = fixed.Int26_6((int64(distance) * int64(h.gs.fv[0])) >> 14) + p.Y = fixed.Int26_6((int64(distance) * int64(h.gs.fv[1])) >> 14) + *q = *p + } + p := h.point(0, current, i) + oldDist := dotProduct(p.X, p.Y, h.gs.pv) + if opcode == opMIAP1 { + if fabs(distance-oldDist) > 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], fixed.Int26_6(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(p.X, p.Y, h.gs.pv)) + } else { + p := h.point(2, unhinted, i) + // Using dv as per C Freetype. + h.stack[top-1] = int32(dotProduct(p.X, 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(p.X, p.Y, h.gs.pv) + h.move(p, fixed.Int26_6(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(p.X-q.X, p.Y-q.Y, v)) + if scale { + d = int32(int64(d*int32(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] = int32(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(fixed.Int26_6(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(fdiv(fixed.Int26_6(h.stack[top-1]), fixed.Int26_6(h.stack[top]))) + + case opMUL: + top-- + h.stack[top-1] = int32(fmul(fixed.Int26_6(h.stack[top-1]), fixed.Int26_6(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(fixed.Int26_6(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], h.font.scale(h.scale*fixed.Int26_6(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 * fixed.Int26_6((h.stack[top]>>4)&0x03) / 4 + if x := h.stack[top] & 0x0f; x != 0 { + h.gs.roundThreshold = h.gs.roundPeriod * fixed.Int26_6(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 := fixed.Int26_6(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(p0.X-p1.X, 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(p0.X-p1.X, p0.Y-p1.Y, h.gs.dv) + oldDist = h.font.scale(h.scale * oldDist) + } + + // Single-width cut-in test. + if x := fabs(oldDist - h.gs.singleWidth); 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(p.X-ref.X, 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 fabs(cvtDist-h.gs.singleWidth) < 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(p.X-ref.X, 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(p.X-ref.X, 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]) && + (fabs(cvtDist-oldDist) > 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 := (int32(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] += fixed.Int26_6(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, fixed.Int26_6(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([]fixed.Int26_6, 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] = h.font.scale(h.scale * fixed.Int26_6(int16(unscaled))) + } +} + +// getScaledCVT returns the scaled value from the font's Control Value Table. +func (h *hinter) getScaledCVT(i int32) fixed.Int26_6 { + 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 fixed.Int26_6) { + 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 fixed.Int26_6, touch bool) { + fvx := int64(h.gs.fv[0]) + pvx := int64(h.gs.pv[0]) + if fvx == 0x4000 && pvx == 0x4000 { + p.X += fixed.Int26_6(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 += fixed.Int26_6(distance) + if touch { + p.Flags |= flagTouchedY + } + return + } + + fvDotPv := (fvx*pvx + fvy*pvy) >> 14 + + if fvx != 0 { + p.X += fixed.Int26_6(mulDiv(fvx, int64(distance), fvDotPv)) + if touch { + p.Flags |= flagTouchedX + } + } + + if fvy != 0 { + p.Y += fixed.Int26_6(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 fixed.Int26_6 + 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 fixed.Int26_6 + 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 fixed.Int26_6 + 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 + fixed.Int26_6(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 fixed.Int26_6 + 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 fixed.Int26_6, 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(p.X-q.X, 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)} +} + +// fabs returns abs(x) in 26.6 fixed point arithmetic. +func fabs(x fixed.Int26_6) fixed.Int26_6 { + if x < 0 { + return -x + } + return x +} + +// fdiv returns x/y in 26.6 fixed point arithmetic. +func fdiv(x, y fixed.Int26_6) fixed.Int26_6 { + return fixed.Int26_6((int64(x) << 6) / int64(y)) +} + +// fmul returns x*y in 26.6 fixed point arithmetic. +func fmul(x, y fixed.Int26_6) fixed.Int26_6 { + return fixed.Int26_6((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 fixed.Int26_6((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 fixed.Int26_6, q [2]f2dot14) fixed.Int26_6 { + // 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 fixed.Int26_6((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 fixed.Int26_6) fixed.Int26_6 { + 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 +} |