// 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 (
	
	

	
)

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( *Font,  []Point) []Point {
	if  := int(.maxTwilightPoints) + 4;  <= cap() {
		 = [:]
		for  := range  {
			[] = Point{}
		}
	} else {
		 = make([]Point, )
	}
	return 
}

func ( *hinter) ( *Font,  fixed.Int26_6) error {
	.points[twilightZone][0] = resetTwilightPoints(, .points[twilightZone][0])
	.points[twilightZone][1] = resetTwilightPoints(, .points[twilightZone][1])
	.points[twilightZone][2] = resetTwilightPoints(, .points[twilightZone][2])

	 := .scale != 
	if .font !=  {
		.font,  = , true
		if .functions == nil {
			.functions = make(map[int32][]byte)
		} else {
			for  := range .functions {
				delete(.functions, )
			}
		}

		if  := int(.maxStackElements);  > len(.stack) {
			 += 255
			 &^= 255
			.stack = make([]int32, )
		}
		if  := int(.maxStorage);  > len(.store) {
			 += 15
			 &^= 15
			.store = make([]int32, )
		}
		if len(.fpgm) != 0 {
			if  := .run(.fpgm, nil, nil, nil, nil);  != nil {
				return 
			}
		}
	}

	if  {
		.scale = 
		.scaledCVTInitialized = false

		.defaultGS = globalDefaultGS

		if len(.prep) != 0 {
			if  := .run(.prep, nil, nil, nil, nil);  != nil {
				return 
			}
			.defaultGS = .gs
			// The MS rasterizer doesn't allow the following graphics state
			// variables to be modified by the CVT program.
			.defaultGS.pv = globalDefaultGS.pv
			.defaultGS.fv = globalDefaultGS.fv
			.defaultGS.dv = globalDefaultGS.dv
			.defaultGS.rp = globalDefaultGS.rp
			.defaultGS.zp = globalDefaultGS.zp
			.defaultGS.loop = globalDefaultGS.loop
		}
	}
	return nil
}

func ( *hinter) ( []byte, , ,  []Point,  []int) error {
	.gs = .defaultGS
	.points[glyphZone][current] = 
	.points[glyphZone][unhinted] = 
	.points[glyphZone][inFontUnits] = 
	.ends = 

	if len() > 50000 {
		return errors.New("truetype: hinting: too many instructions")
	}
	var (
		, ,  int
		         uint8

		    [32]callStackEntry
		 int
	)

	for 0 <=  &&  < len() {
		++
		if  == 100000 {
			return errors.New("truetype: hinting: too many steps")
		}
		 = []
		if  < int(popCount[]) {
			return errors.New("truetype: hinting: stack underflow")
		}
		switch  {

		case opSVTCA0:
			.gs.pv = [2]f2dot14{0, 0x4000}
			.gs.fv = [2]f2dot14{0, 0x4000}
			.gs.dv = [2]f2dot14{0, 0x4000}

		case opSVTCA1:
			.gs.pv = [2]f2dot14{0x4000, 0}
			.gs.fv = [2]f2dot14{0x4000, 0}
			.gs.dv = [2]f2dot14{0x4000, 0}

		case opSPVTCA0:
			.gs.pv = [2]f2dot14{0, 0x4000}
			.gs.dv = [2]f2dot14{0, 0x4000}

		case opSPVTCA1:
			.gs.pv = [2]f2dot14{0x4000, 0}
			.gs.dv = [2]f2dot14{0x4000, 0}

		case opSFVTCA0:
			.gs.fv = [2]f2dot14{0, 0x4000}

		case opSFVTCA1:
			.gs.fv = [2]f2dot14{0x4000, 0}

		case opSPVTL0, opSPVTL1, opSFVTL0, opSFVTL1:
			 -= 2
			 := .point(0, current, .stack[+0])
			 := .point(0, current, .stack[+1])
			if  == nil ||  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			 := f2dot14(.X - .X)
			 := f2dot14(.Y - .Y)
			if  == 0 &&  == 0 {
				 = 0x4000
			} else if &1 != 0 {
				// Counter-clockwise rotation.
				,  = -, 
			}
			 := normalize(, )
			if  < opSFVTL0 {
				.gs.pv = 
				.gs.dv = 
			} else {
				.gs.fv = 
			}

		case opSPVFS:
			 -= 2
			.gs.pv = normalize(f2dot14(.stack[]), f2dot14(.stack[+1]))
			.gs.dv = .gs.pv

		case opSFVFS:
			 -= 2
			.gs.fv = normalize(f2dot14(.stack[]), f2dot14(.stack[+1]))

		case opGPV:
			if +1 >= len(.stack) {
				return errors.New("truetype: hinting: stack overflow")
			}
			.stack[+0] = int32(.gs.pv[0])
			.stack[+1] = int32(.gs.pv[1])
			 += 2

		case opGFV:
			if +1 >= len(.stack) {
				return errors.New("truetype: hinting: stack overflow")
			}
			.stack[+0] = int32(.gs.fv[0])
			.stack[+1] = int32(.gs.fv[1])
			 += 2

		case opSFVTPV:
			.gs.fv = .gs.pv

		case opISECT:
			 -= 5
			 := .point(2, current, .stack[+0])
			 := .point(1, current, .stack[+1])
			 := .point(1, current, .stack[+2])
			 := .point(0, current, .stack[+3])
			 := .point(0, current, .stack[+4])
			if  == nil ||  == nil ||  == nil ||  == nil ||  == nil {
				return errors.New("truetype: hinting: point out of range")
			}

			 := .X - .X
			 := .Y - .Y
			 := .X - .X
			 := .Y - .Y
			 := .X - .X
			 := .Y - .Y
			 := mulDiv(int64(), int64(-), 0x40) +
				mulDiv(int64(), int64(), 0x40)
			 := mulDiv(int64(), int64(), 0x40) +
				mulDiv(int64(), int64(), 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.
			,  := , 
			if  < 0 {
				 = -
			}
			if  < 0 {
				 = -
			}
			if 19* >  {
				 := mulDiv(int64(), int64(-), 0x40) +
					mulDiv(int64(), int64(), 0x40)
				 := mulDiv(, int64(), )
				 := mulDiv(, int64(), )
				.X = .X + fixed.Int26_6()
				.Y = .Y + fixed.Int26_6()
			} else {
				.X = (.X + .X + .X + .X) / 4
				.Y = (.Y + .Y + .Y + .Y) / 4
			}
			.Flags |= flagTouchedX | flagTouchedY

		case opSRP0, opSRP1, opSRP2:
			--
			.gs.rp[-opSRP0] = .stack[]

		case opSZP0, opSZP1, opSZP2:
			--
			.gs.zp[-opSZP0] = .stack[]

		case opSZPS:
			--
			.gs.zp[0] = .stack[]
			.gs.zp[1] = .stack[]
			.gs.zp[2] = .stack[]

		case opSLOOP:
			--
			// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html#SLOOP
			// says that "Setting the loop variable to zero is an error". In
			// theory, the inequality on the next line should be "<=" instead
			// of "<". In practice, some font files' bytecode, such as the '2'
			// glyph in the DejaVuSansMono.ttf that comes with Ubuntu 14.04,
			// issue SLOOP with a zero on top of the stack. Just like the C
			// Freetype code, we allow the zero.
			if .stack[] < 0 {
				return errors.New("truetype: hinting: invalid data")
			}
			.gs.loop = .stack[]

		case opRTG:
			.gs.roundPeriod = 1 << 6
			.gs.roundPhase = 0
			.gs.roundThreshold = 1 << 5
			.gs.roundSuper45 = false

		case opRTHG:
			.gs.roundPeriod = 1 << 6
			.gs.roundPhase = 1 << 5
			.gs.roundThreshold = 1 << 5
			.gs.roundSuper45 = false

		case opSMD:
			--
			.gs.minDist = fixed.Int26_6(.stack[])

		case opELSE:
			 = 1
			goto 

		case opJMPR:
			--
			 += int(.stack[])
			continue

		case opSCVTCI:
			--
			.gs.controlValueCutIn = fixed.Int26_6(.stack[])

		case opSSWCI:
			--
			.gs.singleWidthCutIn = fixed.Int26_6(.stack[])

		case opSSW:
			--
			.gs.singleWidth = .font.scale(.scale * fixed.Int26_6(.stack[]))

		case opDUP:
			if  >= len(.stack) {
				return errors.New("truetype: hinting: stack overflow")
			}
			.stack[] = .stack[-1]
			++

		case opPOP:
			--

		case opCLEAR:
			 = 0

		case opSWAP:
			.stack[-1], .stack[-2] = .stack[-2], .stack[-1]

		case opDEPTH:
			if  >= len(.stack) {
				return errors.New("truetype: hinting: stack overflow")
			}
			.stack[] = int32()
			++

		case opCINDEX, opMINDEX:
			 := int(.stack[-1])
			if  <= 0 ||  >=  {
				return errors.New("truetype: hinting: invalid data")
			}
			.stack[-1] = .stack[-1-]
			if  == opMINDEX {
				copy(.stack[-1-:-1], .stack[-:])
				--
			}

		case opALIGNPTS:
			 -= 2
			 := .point(1, current, .stack[])
			 := .point(0, current, .stack[+1])
			if  == nil ||  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			 := dotProduct(fixed.Int26_6(.X-.X), fixed.Int26_6(.Y-.Y), .gs.pv) / 2
			.move(, +, true)
			.move(, -, true)

		case opUTP:
			--
			 := .point(0, current, .stack[])
			if  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			.Flags &^= flagTouchedX | flagTouchedY

		case opLOOPCALL, opCALL:
			if  >= len() {
				return errors.New("truetype: hinting: call stack overflow")
			}
			--
			,  := .functions[.stack[]]
			if ! {
				return errors.New("truetype: hinting: undefined function")
			}
			[] = callStackEntry{, , 1}
			if  == opLOOPCALL {
				--
				if .stack[] == 0 {
					break
				}
				[].loopCount = .stack[]
			}
			++
			,  = , 0
			continue

		case opFDEF:
			// Save all bytecode up until the next ENDF.
			 :=  + 1
		:
			for {
				++
				if  >= len() {
					return errors.New("truetype: hinting: unbalanced FDEF")
				}
				switch [] {
				case opFDEF:
					return errors.New("truetype: hinting: nested FDEF")
				case opENDF:
					--
					.functions[.stack[]] = [ : +1]
					break 
				default:
					var  bool
					,  = skipInstructionPayload(, )
					if ! {
						return errors.New("truetype: hinting: unbalanced FDEF")
					}
				}
			}

		case opENDF:
			if  == 0 {
				return errors.New("truetype: hinting: call stack underflow")
			}
			--
			[].loopCount--
			if [].loopCount != 0 {
				++
				 = 0
				continue
			}
			,  = [].program, [].pc

		case opMDAP0, opMDAP1:
			--
			 := .stack[]
			 := .point(0, current, )
			if  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			 := fixed.Int26_6(0)
			if  == opMDAP1 {
				 = dotProduct(.X, .Y, .gs.pv)
				// TODO: metrics compensation.
				 = .round() - 
			}
			.move(, , true)
			.gs.rp[0] = 
			.gs.rp[1] = 

		case opIUP0, opIUP1:
			,  :=  == opIUP0, uint32(flagTouchedX)
			if  {
				 = flagTouchedY
			}
			 := 0
			for ,  := range .ends {
				for  := ;  < ; ++ {
					for  <  && .points[glyphZone][current][].Flags& == 0 {
						++
					}
					if  ==  {
						break
					}
					,  := , 
					++
					for ;  < ; ++ {
						if .points[glyphZone][current][].Flags& != 0 {
							.iupInterp(, +1, -1, , )
							 = 
						}
					}
					if  ==  {
						.iupShift(, , , )
					} else {
						.iupInterp(, +1, -1, , )
						if  > 0 {
							.iupInterp(, , -1, , )
						}
					}
				}
				 = 
			}

		case opSHP0, opSHP1:
			if  < int(.gs.loop) {
				return errors.New("truetype: hinting: stack underflow")
			}
			, , ,  := .displacement(&1 == 0)
			if ! {
				return errors.New("truetype: hinting: point out of range")
			}
			for ; .gs.loop != 0; .gs.loop-- {
				--
				 := .point(2, current, .stack[])
				if  == nil {
					return errors.New("truetype: hinting: point out of range")
				}
				.move(, , true)
			}
			.gs.loop = 1

		case opSHC0, opSHC1:
			--
			, , ,  := .displacement(&1 == 0)
			if ! {
				return errors.New("truetype: hinting: point out of range")
			}
			if .gs.zp[2] == 0 {
				// TODO: implement this when we have a glyph that does this.
				return errors.New("hinting: unimplemented SHC instruction")
			}
			 := .stack[]
			if  < 0 || len() <= int() {
				return errors.New("truetype: hinting: contour out of range")
			}
			,  := int32(0), int32(.ends[])
			if  > 0 {
				 = int32(.ends[-1])
			}
			 := .gs.zp[] != .gs.zp[2]
			for  := ;  < ; ++ {
				if  ||  !=  {
					.move(.point(2, current, ), , true)
				}
			}

		case opSHZ0, opSHZ1:
			--
			, , ,  := .displacement(&1 == 0)
			if ! {
				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.
			 := int32(len(.points[.gs.zp[2]][current]))
			if .gs.zp[2] == glyphZone {
				 -= 4
			}
			for  := int32(0);  < ; ++ {
				if  !=  || .gs.zp[] != .gs.zp[2] {
					.move(.point(2, current, ), , false)
				}
			}

		case opSHPIX:
			--
			 := fixed.Int26_6(.stack[])
			if  < int(.gs.loop) {
				return errors.New("truetype: hinting: stack underflow")
			}
			for ; .gs.loop != 0; .gs.loop-- {
				--
				 := .point(2, current, .stack[])
				if  == nil {
					return errors.New("truetype: hinting: point out of range")
				}
				.move(, , true)
			}
			.gs.loop = 1

		case opIP:
			if  < int(.gs.loop) {
				return errors.New("truetype: hinting: stack underflow")
			}
			 := inFontUnits
			 := .gs.zp[0] == 0 || .gs.zp[1] == 0 || .gs.zp[2] == 0
			if  {
				 = unhinted
			}
			 := .point(1, , .gs.rp[2])
			 := .point(0, , .gs.rp[1])
			 := dotProduct(.X-.X, .Y-.Y, .gs.dv)

			 = .point(1, current, .gs.rp[2])
			 := .point(0, current, .gs.rp[1])
			 := dotProduct(.X-.X, .Y-.Y, .gs.pv)
			for ; .gs.loop != 0; .gs.loop-- {
				--
				 := .stack[]
				 = .point(2, , )
				 := dotProduct(.X-.X, .Y-.Y, .gs.dv)
				 = .point(2, current, )
				 := dotProduct(.X-.X, .Y-.Y, .gs.pv)
				 := fixed.Int26_6(0)
				if  != 0 {
					if  != 0 {
						 = fixed.Int26_6(mulDiv(int64(), int64(), int64()))
					} else {
						 = -
					}
				}
				.move(, -, true)
			}
			.gs.loop = 1

		case opMSIRP0, opMSIRP1:
			 -= 2
			 := .stack[]
			 := fixed.Int26_6(.stack[+1])

			// TODO: special case h.gs.zp[1] == 0 in C Freetype.
			 := .point(0, current, .gs.rp[0])
			 := .point(1, current, )
			if  == nil ||  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			 := dotProduct(.X-.X, .Y-.Y, .gs.pv)

			// Set-RP0 bit.
			if  == opMSIRP1 {
				.gs.rp[0] = 
			}
			.gs.rp[1] = .gs.rp[0]
			.gs.rp[2] = 

			// Move the point.
			.move(, -, true)

		case opALIGNRP:
			if  < int(.gs.loop) {
				return errors.New("truetype: hinting: stack underflow")
			}
			 := .point(0, current, .gs.rp[0])
			if  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			for ; .gs.loop != 0; .gs.loop-- {
				--
				 := .point(1, current, .stack[])
				if  == nil {
					return errors.New("truetype: hinting: point out of range")
				}
				.move(, -dotProduct(.X-.X, .Y-.Y, .gs.pv), true)
			}
			.gs.loop = 1

		case opRTDG:
			.gs.roundPeriod = 1 << 5
			.gs.roundPhase = 0
			.gs.roundThreshold = 1 << 4
			.gs.roundSuper45 = false

		case opMIAP0, opMIAP1:
			 -= 2
			 := .stack[]
			 := .getScaledCVT(.stack[+1])
			if .gs.zp[0] == 0 {
				 := .point(0, unhinted, )
				 := .point(0, current, )
				.X = fixed.Int26_6((int64() * int64(.gs.fv[0])) >> 14)
				.Y = fixed.Int26_6((int64() * int64(.gs.fv[1])) >> 14)
				* = *
			}
			 := .point(0, current, )
			 := dotProduct(.X, .Y, .gs.pv)
			if  == opMIAP1 {
				if fabs(-) > .gs.controlValueCutIn {
					 = 
				}
				// TODO: metrics compensation.
				 = .round()
			}
			.move(, -, true)
			.gs.rp[0] = 
			.gs.rp[1] = 

		case opNPUSHB:
			 = 0
			goto 

		case opNPUSHW:
			 = 0x80
			goto 

		case opWS:
			 -= 2
			 := int(.stack[])
			if  < 0 || len(.store) <=  {
				return errors.New("truetype: hinting: invalid data")
			}
			.store[] = .stack[+1]

		case opRS:
			 := int(.stack[-1])
			if  < 0 || len(.store) <=  {
				return errors.New("truetype: hinting: invalid data")
			}
			.stack[-1] = .store[]

		case opWCVTP:
			 -= 2
			.setScaledCVT(.stack[], fixed.Int26_6(.stack[+1]))

		case opRCVT:
			.stack[-1] = int32(.getScaledCVT(.stack[-1]))

		case opGC0, opGC1:
			 := .stack[-1]
			if  == opGC0 {
				 := .point(2, current, )
				.stack[-1] = int32(dotProduct(.X, .Y, .gs.pv))
			} else {
				 := .point(2, unhinted, )
				// Using dv as per C Freetype.
				.stack[-1] = int32(dotProduct(.X, .Y, .gs.dv))
			}

		case opSCFS:
			 -= 2
			 := .stack[]
			 := .point(2, current, )
			if  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			 := dotProduct(.X, .Y, .gs.pv)
			.move(, fixed.Int26_6(.stack[+1])-, true)
			if .gs.zp[2] != 0 {
				break
			}
			 := .point(2, unhinted, )
			if  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			.X = .X
			.Y = .Y

		case opMD0, opMD1:
			--
			, ,  := pointType(0), [2]f2dot14{}, false
			if  == opMD0 {
				 = current
				 = .gs.pv
			} else if .gs.zp[0] == 0 || .gs.zp[1] == 0 {
				 = unhinted
				 = .gs.dv
			} else {
				 = inFontUnits
				 = .gs.dv
				 = true
			}
			 := .point(0, , .stack[-1])
			 := .point(1, , .stack[])
			if  == nil ||  == nil {
				return errors.New("truetype: hinting: point out of range")
			}
			 := int32(dotProduct(.X-.X, .Y-.Y, ))
			if  {
				 = int32(int64(*int32(.scale)) / int64(.font.fUnitsPerEm))
			}
			.stack[-1] = 

		case opMPPEM, opMPS:
			if  >= len(.stack) {
				return errors.New("truetype: hinting: stack overflow")
			}
			// For MPS, point size should be irrelevant; we return the PPEM.
			.stack[] = int32(.scale) >> 6
			++

		case opFLIPON, opFLIPOFF:
			.gs.autoFlip =  == opFLIPON

		case opDEBUG:
			// No-op.

		case opLT:
			--
			.stack[-1] = bool2int32(.stack[-1] < .stack[])

		case opLTEQ:
			--
			.stack[-1] = bool2int32(.stack[-1] <= .stack[])

		case opGT:
			--
			.stack[-1] = bool2int32(.stack[-1] > .stack[])

		case opGTEQ:
			--
			.stack[-1] = bool2int32(.stack[-1] >= .stack[])

		case opEQ:
			--
			.stack[-1] = bool2int32(.stack[-1] == .stack[])

		case opNEQ:
			--
			.stack[-1] = bool2int32(.stack[-1] != .stack[])

		case opODD, opEVEN:
			 := .round(fixed.Int26_6(.stack[-1])) >> 6
			.stack[-1] = int32(&1) ^ int32(-opODD)

		case opIF:
			--
			if .stack[] == 0 {
				 = 0
				goto 
			}

		case opEIF:
			// No-op.

		case opAND:
			--
			.stack[-1] = bool2int32(.stack[-1] != 0 && .stack[] != 0)

		case opOR:
			--
			.stack[-1] = bool2int32(.stack[-1]|.stack[] != 0)

		case opNOT:
			.stack[-1] = bool2int32(.stack[-1] == 0)

		case opDELTAP1:
			goto 

		case opSDB:
			--
			.gs.deltaBase = .stack[]

		case opSDS:
			--
			.gs.deltaShift = .stack[]

		case opADD:
			--
			.stack[-1] += .stack[]

		case opSUB:
			--
			.stack[-1] -= .stack[]

		case opDIV:
			--
			if .stack[] == 0 {
				return errors.New("truetype: hinting: division by zero")
			}
			.stack[-1] = int32(fdiv(fixed.Int26_6(.stack[-1]), fixed.Int26_6(.stack[])))

		case opMUL:
			--
			.stack[-1] = int32(fmul(fixed.Int26_6(.stack[-1]), fixed.Int26_6(.stack[])))

		case opABS:
			if .stack[-1] < 0 {
				.stack[-1] = -.stack[-1]
			}

		case opNEG:
			.stack[-1] = -.stack[-1]

		case opFLOOR:
			.stack[-1] &^= 63

		case opCEILING:
			.stack[-1] += 63
			.stack[-1] &^= 63

		case opROUND00, opROUND01, opROUND10, opROUND11:
			// The four flavors of opROUND are equivalent. See the comment below on
			// opNROUND for the rationale.
			.stack[-1] = int32(.round(fixed.Int26_6(.stack[-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:
			 -= 2
			.setScaledCVT(.stack[], .font.scale(.scale*fixed.Int26_6(.stack[+1])))

		case opDELTAP2, opDELTAP3, opDELTAC1, opDELTAC2, opDELTAC3:
			goto 

		case opSROUND, opS45ROUND:
			--
			switch (.stack[] >> 6) & 0x03 {
			case 0:
				.gs.roundPeriod = 1 << 5
			case 1, 3:
				.gs.roundPeriod = 1 << 6
			case 2:
				.gs.roundPeriod = 1 << 7
			}
			.gs.roundSuper45 =  == opS45ROUND
			if .gs.roundSuper45 {
				// The spec says to multiply by √2, but the C Freetype code says 1/√2.
				// We go with 1/√2.
				.gs.roundPeriod *= 46341
				.gs.roundPeriod /= 65536
			}
			.gs.roundPhase = .gs.roundPeriod * fixed.Int26_6((.stack[]>>4)&0x03) / 4
			if  := .stack[] & 0x0f;  != 0 {
				.gs.roundThreshold = .gs.roundPeriod * fixed.Int26_6(-4) / 8
			} else {
				.gs.roundThreshold = .gs.roundPeriod - 1
			}

		case opJROT:
			 -= 2
			if .stack[+1] != 0 {
				 += int(.stack[])
				continue
			}

		case opJROF:
			 -= 2
			if .stack[+1] == 0 {
				 += int(.stack[])
				continue
			}

		case opROFF:
			.gs.roundPeriod = 0
			.gs.roundPhase = 0
			.gs.roundThreshold = 0
			.gs.roundSuper45 = false

		case opRUTG:
			.gs.roundPeriod = 1 << 6
			.gs.roundPhase = 0
			.gs.roundThreshold = 1<<6 - 1
			.gs.roundSuper45 = false

		case opRDTG:
			.gs.roundPeriod = 1 << 6
			.gs.roundPhase = 0
			.gs.roundThreshold = 0
			.gs.roundSuper45 = false

		case opSANGW, opAA:
			// These ops are "anachronistic" and no longer used.
			--

		case opFLIPPT:
			if  < int(.gs.loop) {
				return errors.New("truetype: hinting: stack underflow")
			}
			 := .points[glyphZone][current]
			for ; .gs.loop != 0; .gs.loop-- {
				--
				 := .stack[]
				if  < 0 || len() <= int() {
					return errors.New("truetype: hinting: point out of range")
				}
				[].Flags ^= flagOnCurve
			}
			.gs.loop = 1

		case opFLIPRGON, opFLIPRGOFF:
			 -= 2
			, ,  := .stack[], .stack[+1], .points[glyphZone][current]
			if  < 0 || len() <= int() ||  < 0 || len() <= int() {
				return errors.New("truetype: hinting: point out of range")
			}
			for ;  <= ; ++ {
				if  == opFLIPRGON {
					[].Flags |= flagOnCurve
				} else {
					[].Flags &^= flagOnCurve
				}
			}

		case opSCANCTRL:
			// We do not support dropout control, as we always rasterize grayscale glyphs.
			--

		case opSDPVTL0, opSDPVTL1:
			 -= 2
			for  := 0;  < 2; ++ {
				 := unhinted
				if  != 0 {
					 = current
				}
				 := .point(1, , .stack[])
				 := .point(2, , .stack[+1])
				if  == nil ||  == nil {
					return errors.New("truetype: hinting: point out of range")
				}
				 := f2dot14(.X - .X)
				 := f2dot14(.Y - .Y)
				if  == 0 &&  == 0 {
					 = 0x4000
				} else if &1 != 0 {
					// Counter-clockwise rotation.
					,  = -, 
				}
				if  == 0 {
					.gs.dv = normalize(, )
				} else {
					.gs.pv = normalize(, )
				}
			}

		case opGETINFO:
			 := int32(0)
			if .stack[-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".
				 |= 35
			}
			if .stack[-1]&(1<<5) != 0 {
				// Set that we support grayscale.
				 |= 1 << 12
			}
			// We set no other bits, as we do not support rotated or stretched glyphs.
			.stack[-1] = 

		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:
			.stack[-1], .stack[-3], .stack[-2] =
				.stack[-3], .stack[-2], .stack[-1]

		case opMAX:
			--
			if .stack[-1] < .stack[] {
				.stack[-1] = .stack[]
			}

		case opMIN:
			--
			if .stack[-1] > .stack[] {
				.stack[-1] = .stack[]
			}

		case opSCANTYPE:
			// We do not support dropout control, as we always rasterize grayscale glyphs.
			--

		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.
			 -= 2

		default:
			if  < opPUSHB000 {
				return errors.New("truetype: hinting: unrecognized instruction")
			}

			if  < opMDRP00000 {
				// PUSHxxxx opcode.

				if  < opPUSHW000 {
					 -= opPUSHB000 - 1
				} else {
					 -= opPUSHW000 - 1 - 0x80
				}
				goto 
			}

			if  < opMIRP00000 {
				// MDRPxxxxx opcode.

				--
				 := .stack[]
				 := .point(0, current, .gs.rp[0])
				 := .point(1, current, )
				if  == nil ||  == nil {
					return errors.New("truetype: hinting: point out of range")
				}

				 := fixed.Int26_6(0)
				if .gs.zp[0] == 0 || .gs.zp[1] == 0 {
					 := .point(1, unhinted, )
					 := .point(0, unhinted, .gs.rp[0])
					 = dotProduct(.X-.X, .Y-.Y, .gs.dv)
				} else {
					 := .point(1, inFontUnits, )
					 := .point(0, inFontUnits, .gs.rp[0])
					 = dotProduct(.X-.X, .Y-.Y, .gs.dv)
					 = .font.scale(.scale * )
				}

				// Single-width cut-in test.
				if  := fabs( - .gs.singleWidth);  < .gs.singleWidthCutIn {
					if  >= 0 {
						 = +.gs.singleWidth
					} else {
						 = -.gs.singleWidth
					}
				}

				// Rounding bit.
				// TODO: metrics compensation.
				 := 
				if &0x04 != 0 {
					 = .round()
				}

				// Minimum distance bit.
				if &0x08 != 0 {
					if  >= 0 {
						if  < .gs.minDist {
							 = .gs.minDist
						}
					} else {
						if  > -.gs.minDist {
							 = -.gs.minDist
						}
					}
				}

				// Set-RP0 bit.
				.gs.rp[1] = .gs.rp[0]
				.gs.rp[2] = 
				if &0x10 != 0 {
					.gs.rp[0] = 
				}

				// Move the point.
				 = dotProduct(.X-.X, .Y-.Y, .gs.pv)
				.move(, -, true)

			} else {
				// MIRPxxxxx opcode.

				 -= 2
				 := .stack[]
				 := .getScaledCVT(.stack[+1])
				if fabs(-.gs.singleWidth) < .gs.singleWidthCutIn {
					if  >= 0 {
						 = +.gs.singleWidth
					} else {
						 = -.gs.singleWidth
					}
				}

				if .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")
				}

				 := .point(0, unhinted, .gs.rp[0])
				 := .point(1, unhinted, )
				if  == nil ||  == nil {
					return errors.New("truetype: hinting: point out of range")
				}
				 := dotProduct(.X-.X, .Y-.Y, .gs.dv)

				 = .point(0, current, .gs.rp[0])
				 = .point(1, current, )
				if  == nil ||  == nil {
					return errors.New("truetype: hinting: point out of range")
				}
				 := dotProduct(.X-.X, .Y-.Y, .gs.pv)

				if .gs.autoFlip && ^ < 0 {
					 = -
				}

				// Rounding bit.
				// TODO: metrics compensation.
				 := 
				if &0x04 != 0 {
					// The CVT value is only used if close enough to oldDist.
					if (.gs.zp[0] == .gs.zp[1]) &&
						(fabs(-) > .gs.controlValueCutIn) {

						 = 
					}
					 = .round()
				}

				// Minimum distance bit.
				if &0x08 != 0 {
					if  >= 0 {
						if  < .gs.minDist {
							 = .gs.minDist
						}
					} else {
						if  > -.gs.minDist {
							 = -.gs.minDist
						}
					}
				}

				// Set-RP0 bit.
				.gs.rp[1] = .gs.rp[0]
				.gs.rp[2] = 
				if &0x10 != 0 {
					.gs.rp[0] = 
				}

				// Move the point.
				.move(, -, true)
			}
		}
		++
		continue

	:
		// 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.
		{
		:
			for  := 0; ; {
				++
				if  >= len() {
					return errors.New("truetype: hinting: unbalanced IF or ELSE")
				}
				switch [] {
				case opIF:
					++
				case opELSE:
					if  == 0 &&  == 0 {
						break 
					}
				case opEIF:
					--
					if  < 0 {
						break 
					}
				default:
					var  bool
					,  = skipInstructionPayload(, )
					if ! {
						return errors.New("truetype: hinting: unbalanced IF or ELSE")
					}
				}
			}
			++
			continue
		}

	:
		// 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.
		{
			 := 1
			if &0x80 != 0 {
				 &^= 0x80
				 = 2
			}
			if  == 0 {
				++
				if  >= len() {
					return errors.New("truetype: hinting: insufficient data")
				}
				 = []
			}
			++
			if +int() > len(.stack) {
				return errors.New("truetype: hinting: stack overflow")
			}
			if +*int() > len() {
				return errors.New("truetype: hinting: insufficient data")
			}
			for ;  > 0; -- {
				if  == 1 {
					.stack[] = int32([])
				} else {
					.stack[] = int32(int8([]))<<8 | int32([+1])
				}
				++
				 += 
			}
			continue
		}

	:
		{
			if  >= opDELTAC1 && !.scaledCVTInitialized {
				.initializeScaledCVT()
			}
			--
			 := .stack[]
			if int32() < 2* {
				return errors.New("truetype: hinting: stack underflow")
			}
			for ;  > 0; -- {
				 -= 2
				 := .stack[]
				 := ( & 0xf0) >> 4
				switch  {
				case opDELTAP2, opDELTAC2:
					 += 16
				case opDELTAP3, opDELTAC3:
					 += 32
				}
				 += .gs.deltaBase
				if  := (int32(.scale) + 1<<5) >> 6;  !=  {
					continue
				}
				 = ( & 0x0f) - 8
				if  >= 0 {
					++
				}
				 =  * 64 / (1 << uint32(.gs.deltaShift))
				if  >= opDELTAC1 {
					 := .stack[+1]
					if  < 0 || len(.scaledCVT) <= int() {
						return errors.New("truetype: hinting: index out of range")
					}
					.scaledCVT[] += fixed.Int26_6()
				} else {
					 := .point(0, current, .stack[+1])
					if  == nil {
						return errors.New("truetype: hinting: point out of range")
					}
					.move(, fixed.Int26_6(), true)
				}
			}
			++
			continue
		}
	}
	return nil
}

func ( *hinter) () {
	.scaledCVTInitialized = true
	if  := len(.font.cvt) / 2;  <= cap(.scaledCVT) {
		.scaledCVT = .scaledCVT[:]
	} else {
		if  < 32 {
			 = 32
		}
		.scaledCVT = make([]fixed.Int26_6, len(.font.cvt)/2, )
	}
	for  := range .scaledCVT {
		 := uint16(.font.cvt[2*])<<8 | uint16(.font.cvt[2*+1])
		.scaledCVT[] = .font.scale(.scale * fixed.Int26_6(int16()))
	}
}

// getScaledCVT returns the scaled value from the font's Control Value Table.
func ( *hinter) ( int32) fixed.Int26_6 {
	if !.scaledCVTInitialized {
		.initializeScaledCVT()
	}
	if  < 0 || len(.scaledCVT) <= int() {
		return 0
	}
	return .scaledCVT[]
}

// setScaledCVT overrides the scaled value from the font's Control Value Table.
func ( *hinter) ( int32,  fixed.Int26_6) {
	if !.scaledCVTInitialized {
		.initializeScaledCVT()
	}
	if  < 0 || len(.scaledCVT) <= int() {
		return
	}
	.scaledCVT[] = 
}

func ( *hinter) ( uint32,  pointType,  int32) *Point {
	 := .points[.gs.zp[]][]
	if  < 0 || len() <= int() {
		return nil
	}
	return &[]
}

func ( *hinter) ( *Point,  fixed.Int26_6,  bool) {
	 := int64(.gs.fv[0])
	 := int64(.gs.pv[0])
	if  == 0x4000 &&  == 0x4000 {
		.X += fixed.Int26_6()
		if  {
			.Flags |= flagTouchedX
		}
		return
	}

	 := int64(.gs.fv[1])
	 := int64(.gs.pv[1])
	if  == 0x4000 &&  == 0x4000 {
		.Y += fixed.Int26_6()
		if  {
			.Flags |= flagTouchedY
		}
		return
	}

	 := (* + *) >> 14

	if  != 0 {
		.X += fixed.Int26_6(mulDiv(, int64(), ))
		if  {
			.Flags |= flagTouchedX
		}
	}

	if  != 0 {
		.Y += fixed.Int26_6(mulDiv(, int64(), ))
		if  {
			.Flags |= flagTouchedY
		}
	}
}

func ( *hinter) ( bool, , , ,  int) {
	if  >  {
		return
	}
	if  >= len(.points[glyphZone][current]) ||
		 >= len(.points[glyphZone][current]) {
		return
	}

	var ,  fixed.Int26_6
	if  {
		 = .points[glyphZone][inFontUnits][].Y
		 = .points[glyphZone][inFontUnits][].Y
	} else {
		 = .points[glyphZone][inFontUnits][].X
		 = .points[glyphZone][inFontUnits][].X
	}
	if  >  {
		,  = , 
		,  = , 
	}

	var , , ,  fixed.Int26_6
	if  {
		 = .points[glyphZone][unhinted][].Y
		 = .points[glyphZone][unhinted][].Y
		 = .points[glyphZone][current][].Y - 
		 = .points[glyphZone][current][].Y - 
	} else {
		 = .points[glyphZone][unhinted][].X
		 = .points[glyphZone][unhinted][].X
		 = .points[glyphZone][current][].X - 
		 = .points[glyphZone][current][].X - 
	}

	var ,  fixed.Int26_6
	if  ==  {
		for  := ;  <= ; ++ {
			if  {
				 = .points[glyphZone][unhinted][].Y
			} else {
				 = .points[glyphZone][unhinted][].X
			}

			if  <=  {
				 += 
			} else {
				 += 
			}

			if  {
				.points[glyphZone][current][].Y = 
			} else {
				.points[glyphZone][current][].X = 
			}
		}
		return
	}

	,  := int64(0), false
	for  := ;  <= ; ++ {
		if  {
			 = .points[glyphZone][unhinted][].Y
			 = .points[glyphZone][inFontUnits][].Y
		} else {
			 = .points[glyphZone][unhinted][].X
			 = .points[glyphZone][inFontUnits][].X
		}

		if  <=  {
			 += 
		} else if  >=  {
			 += 
		} else {
			if ! {
				 = true
				 = mulDiv(int64(+--), 0x10000, int64(-))
			}
			 := int64(-) * 
			if  >= 0 {
				 += 0x8000
			} else {
				 -= 0x8000
			}
			 =  +  + fixed.Int26_6(/0x10000)
		}

		if  {
			.points[glyphZone][current][].Y = 
		} else {
			.points[glyphZone][current][].X = 
		}
	}
}

func ( *hinter) ( bool, , ,  int) {
	var  fixed.Int26_6
	if  {
		 = .points[glyphZone][current][].Y - .points[glyphZone][unhinted][].Y
	} else {
		 = .points[glyphZone][current][].X - .points[glyphZone][unhinted][].X
	}
	if  == 0 {
		return
	}
	for  := ;  < ; ++ {
		if  ==  {
			continue
		}
		if  {
			.points[glyphZone][current][].Y += 
		} else {
			.points[glyphZone][current][].X += 
		}
	}
}

func ( *hinter) ( bool) ( uint32,  int32,  fixed.Int26_6,  bool) {
	,  = uint32(0), .gs.rp[1]
	if  {
		,  = 1, .gs.rp[2]
	}
	 := .point(, current, )
	 := .point(, unhinted, )
	if  == nil ||  == nil {
		return 0, 0, 0, false
	}
	 = dotProduct(.X-.X, .Y-.Y, .gs.pv)
	return , , , true
}

// skipInstructionPayload increments pc by the extra data that follows a
// variable length PUSHB or PUSHW instruction.
func skipInstructionPayload( []byte,  int) ( int,  bool) {
	switch [] {
	case opNPUSHB:
		++
		if  >= len() {
			return 0, false
		}
		 += int([])
	case opNPUSHW:
		++
		if  >= len() {
			return 0, false
		}
		 += 2 * int([])
	case opPUSHB000, opPUSHB001, opPUSHB010, opPUSHB011,
		opPUSHB100, opPUSHB101, opPUSHB110, opPUSHB111:
		 += int([] - (opPUSHB000 - 1))
	case opPUSHW000, opPUSHW001, opPUSHW010, opPUSHW011,
		opPUSHW100, opPUSHW101, opPUSHW110, opPUSHW111:
		 += 2 * int([]-(opPUSHW000-1))
	}
	return , true
}

// f2dot14 is a 2.14 fixed point number.
type f2dot14 int16

func normalize(,  f2dot14) [2]f2dot14 {
	,  := float64(), float64()
	 := 0x4000 / math.Hypot(, )
	 *= 
	if  >= 0 {
		 += 0.5
	} else {
		 -= 0.5
	}
	 *= 
	if  >= 0 {
		 += 0.5
	} else {
		 -= 0.5
	}
	return [2]f2dot14{f2dot14(), f2dot14()}
}

// fabs returns abs(x) in 26.6 fixed point arithmetic.
func fabs( fixed.Int26_6) fixed.Int26_6 {
	if  < 0 {
		return -
	}
	return 
}

// fdiv returns x/y in 26.6 fixed point arithmetic.
func fdiv(,  fixed.Int26_6) fixed.Int26_6 {
	return fixed.Int26_6((int64() << 6) / int64())
}

// fmul returns x*y in 26.6 fixed point arithmetic.
func fmul(,  fixed.Int26_6) fixed.Int26_6 {
	return fixed.Int26_6((int64()*int64() + 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(,  fixed.Int26_6,  [2]f2dot14) fixed.Int26_6 {
	// Compute x*q[0] as 64-bit value.
	 := uint32((int32() & 0xFFFF) * int32([0]))
	 := (int32() >> 16) * int32([0])

	 :=  + (uint32() << 16)
	 := ( >> 16) + (int32() >> 31) + bool2int32( < )

	// Compute y*q[1] as 64-bit value.
	 = uint32((int32() & 0xFFFF) * int32([1]))
	 = (int32() >> 16) * int32([1])

	 :=  + (uint32() << 16)
	 := ( >> 16) + (int32() >> 31) + bool2int32( < )

	// Add them.
	 :=  + 
	 :=  +  + bool2int32( < )

	// Divide the result by 2^14 with rounding.
	 :=  >> 31
	 =  + uint32()
	 +=  + bool2int32( < )
	 = 

	 =  + 0x2000
	 += bool2int32( < )

	return fixed.Int26_6((uint32() << 18) | ( >> 14))
}

// mulDiv returns x*y/z, rounded to the nearest integer.
func mulDiv(, ,  int64) int64 {
	 :=  * 
	if  < 0 {
		,  = -, -
	}
	if  >= 0 {
		 +=  / 2
	} else {
		 -=  / 2
	}
	return  / 
}

// round rounds the given number. The rounding algorithm is described at
// https://developer.apple.com/fonts/TTRefMan/RM02/Chap2.html#rounding
func ( *hinter) ( fixed.Int26_6) fixed.Int26_6 {
	if .gs.roundPeriod == 0 {
		// Rounding is off.
		return 
	}
	if  >= 0 {
		 :=  - .gs.roundPhase + .gs.roundThreshold
		if .gs.roundSuper45 {
			 /= .gs.roundPeriod
			 *= .gs.roundPeriod
		} else {
			 &= -.gs.roundPeriod
		}
		if  != 0 &&  < 0 {
			 = 0
		}
		return  + .gs.roundPhase
	}
	 := - - .gs.roundPhase + .gs.roundThreshold
	if .gs.roundSuper45 {
		 /= .gs.roundPeriod
		 *= .gs.roundPeriod
	} else {
		 &= -.gs.roundPeriod
	}
	if  < 0 {
		 = 0
	}
	return - - .gs.roundPhase
}

func bool2int32( bool) int32 {
	if  {
		return 1
	}
	return 0
}