package harfbuzz

import (
	
	

	
	
	ucd 
)

// ported from harfbuzz/src/hb-ot-layout-gsubgpos.hh Copyright © 2007,2008,2009,2010  Red Hat, Inc. 2010,2012  Google, Inc.  Behdad Esfahbod

// GSUB or GPOS lookup
type layoutLookup interface {
	// accumulate the subtables coverage into the diggest
	collectCoverage(*setDigest)
	// walk the subtables to add them to the context
	dispatchSubtables(*getSubtablesContext)

	// walk the subtables and apply the sub/pos
	dispatchApply(ctx *otApplyContext) bool

	Props() uint32
	isReverse() bool
}

/*
 * GSUB/GPOS Common
 */

const ignoreFlags = otIgnoreBaseGlyphs | otIgnoreLigatures | otIgnoreMarks

// use a digest to speedup match
type otLayoutLookupAccelerator struct {
	lookup    layoutLookup
	subtables getSubtablesContext
	digest    setDigest
}

func ( *otLayoutLookupAccelerator) ( layoutLookup) {
	.lookup = 
	.digest = setDigest{}
	.collectCoverage(&.digest)
	.subtables = nil
	.dispatchSubtables(&.subtables)
}

// apply the subtables and stops at the first success.
func ( *otLayoutLookupAccelerator) ( *otApplyContext) bool {
	for ,  := range .subtables {
		if .apply() {
			return true
		}
	}
	return false
}

// represents one layout subtable, with its own coverage
type applicable struct {
	objApply func(c *otApplyContext) bool

	digest setDigest
}

func newGSUBApplicable( tables.GSUBLookup) applicable {
	 := applicable{objApply: func( *otApplyContext) bool { return .applyGSUB() }}
	.digest.collectCoverage(.Cov())
	return 
}

func newGPOSApplicable( tables.GPOSLookup) applicable {
	 := applicable{objApply: func( *otApplyContext) bool { return .applyGPOS() }}
	.digest.collectCoverage(.Cov())
	return 
}

func ( applicable) ( *otApplyContext) bool {
	return .digest.mayHave(gID(.buffer.cur(0).Glyph)) && .objApply()
}

type getSubtablesContext []applicable

// one for GSUB, one for GPOS (known at compile time)
type otProxyMeta struct {
	recurseFunc recurseFunc
	tableIndex  uint8 // 0 for GSUB, 1 for GPOS
	inplace     bool
}

var (
	proxyGSUB = otProxyMeta{tableIndex: 0, inplace: false, recurseFunc: applyRecurseGSUB}
	proxyGPOS = otProxyMeta{tableIndex: 1, inplace: true, recurseFunc: applyRecurseGPOS}
)

type otProxy struct {
	otProxyMeta
	accels []otLayoutLookupAccelerator
}

type wouldApplyContext struct {
	glyphs      []GID
	indices     []uint16 // see get1N
	zeroContext bool
}

// `value` interpretation is dictated by the context
type matcherFunc = func(gid gID, value uint16) bool

// interprets `value` as a Glyph
func matchGlyph( gID,  uint16) bool { return  == gID() }

// interprets `value` as a Class
func matchClass( tables.ClassDef) matcherFunc {
	return func( gID,  uint16) bool {
		,  := .Class()
		return uint16() == 
	}
}

// interprets `value` as an index in coverage array
func matchCoverage( []tables.Coverage) matcherFunc {
	return func( gID,  uint16) bool {
		,  := [].Index()
		return 
	}
}

const (
	no uint8 = iota
	yes
	maybe
)

type otApplyContextMatcher struct {
	matchFunc    matcherFunc
	lookupProps  uint32
	mask         GlyphMask
	ignoreZWNJ   bool
	ignoreZWJ    bool
	ignoreHidden bool
	perSyllable  bool
	syllable     uint8
}

func ( *otApplyContextMatcher) ( *otApplyContext,  bool) {
	.matchFunc = nil
	.lookupProps = .lookupProps
	/* Ignore ZWNJ if we are matching GPOS, or matching GSUB context and asked to. */
	.ignoreZWNJ = .tableIndex == 1 || ( && .autoZWNJ)
	/* Ignore ZWJ if we are matching context, or asked to. */
	.ignoreZWJ =  || .autoZWJ
	/* Ignore hidden glyphs (like CGJ) during GPOS. */
	.ignoreHidden = .tableIndex == 1
	if  {
		.mask = math.MaxUint32
	} else {
		.mask = .lookupMask
	}
	// Per syllable matching is only for GSUB.
	.perSyllable = .tableIndex == 0 && .perSyllable
	.syllable = 0
}

func ( otApplyContextMatcher) ( *GlyphInfo,  []uint16) uint8 {
	if .Mask&.mask == 0 || (.perSyllable && .syllable != 0 && .syllable != .syllable) {
		return no
	}

	if .matchFunc != nil {
		if .matchFunc(gID(.Glyph), [0]) {
			return yes
		}
		return no
	}

	return maybe
}

func ( otApplyContextMatcher) ( *otApplyContext,  *GlyphInfo) uint8 {
	if !.checkGlyphProperty(, .lookupProps) {
		return yes
	}

	if .isDefaultIgnorable() &&
		(.ignoreZWNJ || !.isZwnj()) &&
		(.ignoreZWJ || !.isZwj()) &&
		(.ignoreHidden || !.isHidden()) {
		return maybe
	}

	return no
}

type skippingIterator struct {
	c       *otApplyContext
	matcher otApplyContextMatcher

	matchGlyphDataArray []uint16
	matchGlyphDataStart int // start as index in matchGlyphDataArray

	idx int
	end int
}

func ( *skippingIterator) ( *otApplyContext,  bool) {
	.c = 
	.end = len(.buffer.Info)
	.matchGlyphDataArray = .matchGlyphDataArray[:0]
	.matchGlyphDataStart = 0
	.matcher.init(, )
}

func ( *skippingIterator) ( matcherFunc,  []uint16) {
	.matcher.matchFunc = 
	.matchGlyphDataArray = 
	.matchGlyphDataStart = 0
}

func ( *skippingIterator) ( int) {
	// For GSUB forward iterator
	.idx = 
	.end = len(.c.buffer.Info)
	.matcher.syllable = .c.buffer.cur(0).syllable
}

func ( *skippingIterator) ( int) {
	// For GSUB backward iterator
	.idx = 
	.matcher.syllable = .c.buffer.cur(0).syllable
}

func ( *skippingIterator) ( int) {
	// Doesn't set end or syllable. Used by GPOS which doesn't care / change.
	.idx = 
}

func ( *skippingIterator) ( *GlyphInfo) uint8 { return .matcher.maySkip(.c, ) }

type matchRes uint8

const (
	match matchRes = iota
	notMatch
	skip
)

func ( *skippingIterator) ( *GlyphInfo) matchRes {
	 := .matcher.maySkip(.c, )
	if  == yes {
		return skip
	}

	 := .matcher.mayMatch(, .matchGlyphDataArray[.matchGlyphDataStart:])
	if  == yes || ( == maybe &&  == no) {
		return match
	}

	if  == no {
		return notMatch
	}

	return skip
}

func ( *skippingIterator) () ( bool,  int) {
	 := .end - 1
	for .idx <  {
		.idx++
		 := &.c.buffer.Info[.idx]
		switch .match() {
		case match:
			if len(.matchGlyphDataArray) != 0 {
				.matchGlyphDataStart++
			}
			return true, 0
		case notMatch:
			return false, .idx + 1
		case skip:
			continue
		}
	}
	return false, .end
}

func ( *skippingIterator) () ( bool,  int) {
	 := 0
	 := len(.c.buffer.outInfo)
	for .idx >  {
		.idx--
		var  *GlyphInfo
		if .idx <  {
			 = &.c.buffer.outInfo[.idx]
		} else {
			// we are in "position mode" : outInfo is not used anymore
			// in the C implementation, outInfo and info now are sharing the same storage
			 = &.c.buffer.Info[.idx]
		}

		switch .match() {
		case match:
			if len(.matchGlyphDataArray) != 0 {
				.matchGlyphDataStart++
			}
			return true, 0
		case notMatch:
			return false, max(1, .idx) - 1
		case skip:
			continue
		}
	}
	return false, 0
}

type recurseFunc = func(c *otApplyContext, lookupIndex uint16) bool

type otApplyContext struct {
	font   *Font
	buffer *Buffer

	recurseFunc recurseFunc
	gdef        tables.GDEF
	varStore    tables.ItemVarStore
	indices     []uint16 // see get1N()

	iterContext skippingIterator
	iterInput   skippingIterator

	nestingLevelLeft int
	tableIndex       uint8 // 0 for GSUB, 1 for GPOS
	lookupMask       GlyphMask
	lookupProps      uint32
	randomState      uint32
	lookupIndex      uint16
	direction        Direction

	hasGlyphClasses bool
	autoZWNJ        bool
	autoZWJ         bool
	perSyllable     bool
	newSyllables    uint8 // 0xFF for undefined
	random          bool

	lastBase      int // GPOS uses
	lastBaseUntil int // GPOS uses

	matchPositions []int
}

func ( *otApplyContext) ( uint8,  *Font,  *Buffer) {
	.font = 
	.buffer = 

	.recurseFunc = nil
	.gdef = .face.GDEF
	.varStore = .gdef.ItemVarStore
	.indices = .indices[:0]

	.nestingLevelLeft = maxNestingLevel
	.tableIndex = 
	.lookupMask = 1
	.lookupProps = 0
	.randomState = 1
	.lookupIndex = 0
	.direction = .Props.Direction

	.hasGlyphClasses = .gdef.GlyphClassDef != nil
	.autoZWNJ = true
	.autoZWJ = true
	.perSyllable = false
	.newSyllables = 0xFF
	.random = false

	.lastBase = -1
	.lastBaseUntil = 0

	ensureLen(&.matchPositions, 8)

	// iterContext
	// iterInput
	.initIters()
}

func ( *otApplyContext) () {
	.iterInput.init(, false)
	.iterContext.init(, true)
}

func ( *otApplyContext) ( GlyphMask) {
	.lookupMask = 
	.initIters()
}

func ( *otApplyContext) ( uint32) {
	.lookupProps = 
	.initIters()
}

func ( *otApplyContext) ( uint16,  layoutLookup) bool {
	 := .lookupProps
	 := .lookupIndex

	.lookupIndex = 
	.setLookupProps(.Props())

	 := .matchPositions
	var  [8]int
	.matchPositions = [:]

	 := .dispatchApply()

	.lookupIndex = 
	.setLookupProps()

	.matchPositions = 

	return 
}

func ( *otApplyContext) ( *otLayoutLookupAccelerator) {
	.applyString(proxyGSUB, )
}

func ( *otApplyContext) ( *GlyphInfo,  uint32) bool {
	 := .glyphProps

	/* Not covered, if, for example, glyph class is ligature and
	 * matchProps includes LookupFlags::IgnoreLigatures */
	if ( & uint16() & ignoreFlags) != 0 {
		return false
	}

	if &tables.GPMark != 0 {
		return .matchPropertiesMark(, , )
	}

	return true
}

func ( *otApplyContext) ( *GlyphInfo,  uint16,  uint32) bool {
	/* If using mark filtering sets, the high uint16 of
	 * matchProps has the set index. */
	if uint16()&font.UseMarkFilteringSet != 0 {
		,  := .gdef.MarkGlyphSetsDef.Coverages[>>16].Index(gID(.Glyph))
		return 
	}

	/* The second byte of matchProps has the meaning
	 * "ignore marks of attachment type different than
	 * the attachment type specified." */
	if uint16()&otMarkAttachmentType != 0 {
		return uint16()&otMarkAttachmentType == ( & otMarkAttachmentType)
	}

	return true
}

func ( *otApplyContext) ( GID) {
	.setGlyphClassExt(, 0, false, false)
}

func ( *otApplyContext) ( GID,  uint16, ,  bool) {
	 := gID()

	.buffer.digest.add()

	if .newSyllables != 0xFF {
		.buffer.cur(0).syllable = .newSyllables
	}

	 := .buffer.cur(0).glyphProps | substituted
	if  {
		 |= ligated
		// In the only place that the MULTIPLIED bit is used, Uniscribe
		// seems to only care about the "last" transformation between
		// Ligature and Multiple substitutions.  Ie. if you ligate, expand,
		// and ligate again, it forgives the multiplication and acts as
		// if only ligation happened.  As such, clear MULTIPLIED bit.
		 &= ^multiplied
	}
	if  {
		 |= multiplied
	}
	if .hasGlyphClasses {
		 &= preserve
		.buffer.cur(0).glyphProps =  | .gdef.GlyphProps()
	} else if  != 0 {
		 &= preserve
		.buffer.cur(0).glyphProps =  | 
	} else {
		.buffer.cur(0).glyphProps = 
	}
}

func ( *otApplyContext) ( GID) {
	.setGlyphClass()
	.buffer.replaceGlyphIndex()
}

func ( *otApplyContext) () uint32 {
	/* http://www.cplusplus.com/reference/random/minstd_rand/ */
	.randomState = .randomState * 48271 % 2147483647
	return .randomState
}

func ( *otApplyContext) ( tables.SequenceRuleSet,  matcherFunc) bool {
	for ,  := range .SeqRule {
		// the first which match is applied
		 := .contextApplyLookup(.InputSequence, .SeqLookupRecords, )
		if  {
			return true
		}
	}
	return false
}

func ( *otApplyContext) ( tables.ChainedClassSequenceRuleSet,  [3]matcherFunc) bool {
	for ,  := range .ChainedSeqRules {

		if debugMode {
			fmt.Println("APPLY - chain rule number", )
		}

		 := .chainContextApplyLookup(.BacktrackSequence, .InputSequence, .LookaheadSequence, .SeqLookupRecords, )
		if  { // stop at the first application
			return true
		}
	}
	return false
}

// `input` starts with second glyph (`inputCount` = len(input)+1)
func ( *otApplyContext) ( []uint16,  []tables.SequenceLookupRecord,  matcherFunc) bool {
	if len()+1 > maxContextLength {
		return false
	}
	, ,  := .matchInput(, )
	if  {
		.buffer.unsafeToBreak(.buffer.idx, )
		.applyLookup(len()+1, , )
		return true
	} else {
		.buffer.unsafeToConcat(.buffer.idx, )
		return false
	}
}

//	`input` starts with second glyph (`inputCount` = len(input)+1)
//
// lookupsContexts : backtrack, input, lookahead
func ( *otApplyContext) (, ,  []uint16,
	 []tables.SequenceLookupRecord,  [3]matcherFunc,
) bool {
	if len()+1 > maxContextLength {
		return false
	}

	, ,  := .matchInput(, [1])
	 := 
	if !( &&  != 0) {
		.buffer.unsafeToConcat(.buffer.idx, )
		return false
	}

	,  = .matchLookahead(, [2], )
	if ! {
		.buffer.unsafeToConcat(.buffer.idx, )
		return false
	}

	,  := .matchBacktrack(, [0])
	if ! {
		.buffer.unsafeToConcatFromOutbuffer(, )
		return false
	}

	.buffer.unsafeToBreakFromOutbuffer(, )
	.applyLookup(len()+1, , )
	return true
}

func ( *wouldApplyContext) ( tables.SequenceContextFormat1,  int) bool {
	if  >= len(.SeqRuleSet) { // index is not sanitized in tt.Parse
		return false
	}
	 := .SeqRuleSet[]
	return .wouldApplyRuleSet(, matchGlyph)
}

func ( *wouldApplyContext) ( tables.SequenceContextFormat2,  int,  GID) bool {
	,  := .ClassDef.Class(gID())
	 := .ClassSeqRuleSet[]
	return .wouldApplyRuleSet(, matchClass(.ClassDef))
}

func ( *wouldApplyContext) ( tables.SequenceContextFormat3,  int) bool {
	 := get1N(&.indices, 1, len(.Coverages))
	return .wouldMatchInput(, matchCoverage(.Coverages))
}

func ( *wouldApplyContext) ( tables.SequenceRuleSet,  matcherFunc) bool {
	for ,  := range .SeqRule {
		if .wouldMatchInput(.InputSequence, ) {
			return true
		}
	}
	return false
}

func ( *wouldApplyContext) ( tables.ChainedSequenceRuleSet,  matcherFunc) bool {
	for ,  := range .ChainedSeqRules {
		if .wouldApplyChainLookup(.BacktrackSequence, .InputSequence, .LookaheadSequence, ) {
			return true
		}
	}
	return false
}

func ( *wouldApplyContext) ( tables.ChainedSequenceContextFormat1,  int) bool {
	if  >= len(.ChainedSeqRuleSet) { // index is not sanitized in tt.Parse
		return false
	}
	 := .ChainedSeqRuleSet[]
	return .wouldApplyChainRuleSet(, matchGlyph)
}

func ( *wouldApplyContext) ( tables.ChainedSequenceContextFormat2,  int,  GID) bool {
	,  := .InputClassDef.Class(gID())
	 := .ChainedClassSeqRuleSet[]
	return .wouldApplyChainRuleSet(, matchClass(.InputClassDef))
}

func ( *wouldApplyContext) ( tables.ChainedSequenceContextFormat3,  int) bool {
	, ,  := len(.BacktrackCoverages), len(.InputCoverages), len(.LookaheadCoverages)
	return .wouldApplyChainLookup(get1N(&.indices, 0, ), get1N(&.indices, 1, ), get1N(&.indices, 0, ),
		matchCoverage(.InputCoverages))
}

// `input` starts with second glyph (`inputCount` = len(input)+1)
// only the input lookupsContext is needed
func ( *wouldApplyContext) (, ,  []uint16,  matcherFunc) bool {
	 := true
	if .zeroContext {
		 = len() == 0 && len() == 0
	}
	return  && .wouldMatchInput(, )
}

// `input` starts with second glyph (`count` = len(input)+1)
func ( *wouldApplyContext) ( []uint16,  matcherFunc) bool {
	if len(.glyphs) != len()+1 {
		return false
	}

	for ,  := range  {
		if !(gID(.glyphs[+1]), ) {
			return false
		}
	}

	return true
}

// `input` starts with second glyph (`inputCount` = len(input)+1)
func ( *otApplyContext) ( []uint16,  matcherFunc) ( bool,  int,  uint8) {
	 := .buffer
	 := len() + 1
	if  == 1 {
		 = .idx + 1
		.matchPositions[0] = .idx
		 = .cur(0).getLigNumComps()
		return true, , 
	}

	if  > maxContextLength {
		return false, 0, 0
	}
	 := &.iterInput
	.reset(.idx)
	.setMatchFunc(, )

	/*
	* This is perhaps the trickiest part of OpenType...  Remarks:
	*
	* - If all components of the ligature were marks, we call this a mark ligature.
	*
	* - If there is no GDEF, and the ligature is NOT a mark ligature, we categorize
	*   it as a ligature glyph.
	*
	* - Ligatures cannot be formed across glyphs attached to different components
	*   of previous ligatures.  Eg. the sequence is LAM,SHADDA,LAM,FATHA,HEH, and
	*   LAM,LAM,HEH form a ligature, leaving SHADDA,FATHA next to eachother.
	*   However, it would be wrong to ligate that SHADDA,FATHA sequence.
	*   There are a couple of exceptions to this:
	*
	*   o If a ligature tries ligating with marks that belong to it itself, go ahead,
	*     assuming that the font designer knows what they are doing (otherwise it can
	*     break Indic stuff when a matra wants to ligate with a conjunct,
	*
	*   o If two marks want to ligate and they belong to different components of the
	*     same ligature glyph, and said ligature glyph is to be ignored according to
	*     mark-filtering rules, then allow.
	*     https://github.com/harfbuzz/harfbuzz/issues/545
	 */

	 := .cur(0).getLigID()
	 := .cur(0).getLigComp()

	const (
		 = iota
		
		
	)
	 := 
	for  := 1;  < ; ++ {
		if ,  := .next(); ! {
			return false, , 0
		}

		ensureLen(&.matchPositions, +1)

		.matchPositions[] = .idx

		 := .Info[.idx].getLigID()
		 := .Info[.idx].getLigComp()
		if  != 0 &&  != 0 {
			/* If first component was attached to a previous ligature component,
			* all subsequent components should be attached to the same ligature
			* component, otherwise we shouldn't ligate them... */
			if  !=  ||  !=  {
				/* ...unless, we are attached to a base ligature and that base
				 * ligature is ignorable. */
				if  ==  {
					 := false
					 := .outInfo
					 := len()
					for  != 0 && [-1].getLigID() ==  {
						if [-1].getLigComp() == 0 {
							--
							 = true
							break
						}
						--
					}

					if  && .maySkip(&[]) == yes {
						 = 
					} else {
						 = 
					}
				}

				if  ==  {
					return false, 0, 0
				}
			}
		} else {
			/* If first component was NOT attached to a previous ligature component,
			* all subsequent components should also NOT be attached to any ligature
			* component, unless they are attached to the first component itself! */
			if  != 0 &&  != 0 && ( != ) {
				return false, 0, 0
			}
		}

		 += .Info[.idx].getLigNumComps()
	}

	 = .idx + 1
	 += .cur(0).getLigNumComps()
	.matchPositions[0] = .idx

	return true, , 
}

// `count` and `c.matchPositions` include the first glyph
func ( *otApplyContext) (,  int,  gID,  uint8) {
	 := .buffer

	.mergeClusters(.idx, )

	/* - If a base and one or more marks ligate, consider that as a base, NOT
	*   ligature, such that all following marks can still attach to it.
	*   https://github.com/harfbuzz/harfbuzz/issues/1109
	*
	* - If all components of the ligature were marks, we call this a mark ligature.
	*   If it *is* a mark ligature, we don't allocate a new ligature id, and leave
	*   the ligature to keep its old ligature id.  This will allow it to attach to
	*   a base ligature in GPOS.  Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
	*   and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a
	*   ligature id and component value of 2.  Then if SHADDA,FATHA form a ligature
	*   later, we don't want them to lose their ligature id/component, otherwise
	*   GPOS will fail to correctly position the mark ligature on top of the
	*   LAM,LAM,HEH ligature.  See:
	*     https://bugzilla.gnome.org/show_bug.cgi?id=676343
	*
	* - If a ligature is formed of components that some of which are also ligatures
	*   themselves, and those ligature components had marks attached to *their*
	*   components, we have to attach the marks to the new ligature component
	*   positions!  Now *that*'s tricky!  And these marks may be following the
	*   last component of the whole sequence, so we should loop forward looking
	*   for them and update them.
	*
	*   Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
	*   'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
	*   id and component == 1.  Now, during 'liga', the LAM and the LAM-HEH ligature
	*   form a LAM-LAM-HEH ligature.  We need to reassign the SHADDA and FATHA to
	*   the new ligature with a component value of 2.
	*
	*   This in fact happened to a font...  See:
	*   https://bugzilla.gnome.org/show_bug.cgi?id=437633
	 */

	 := .Info[.matchPositions[0]].isBaseGlyph()
	 := .Info[.matchPositions[0]].isMark()
	for  := 1;  < ; ++ {
		if !.Info[.matchPositions[]].isMark() {
			 = false
			 = false
			break
		}
	}
	 := ! && !

	,  := uint16(0), uint8(0)
	if  {
		 = tables.GPLigature
		 = .allocateLigID()
	}
	 := .cur(0).getLigID()
	 := .cur(0).getLigNumComps()
	 := 

	if  {
		.cur(0).setLigPropsForLigature(, )
		if .cur(0).unicode.generalCategory() == ucd.Mn {
			.cur(0).setGeneralCategory(ucd.Lo)
		}
	}

	// ReplaceGlyph_with_ligature
	.setGlyphClassExt(GID(), , true, false)
	.replaceGlyphIndex(GID())

	for  := 1;  < ; ++ {
		for .idx < .matchPositions[] {
			if  {
				 := .cur(0).getLigComp()
				if  == 0 {
					 = 
				}
				 :=  -  +
					min8(, )
				.cur(0).setLigPropsForMark(, )
			}
			.nextGlyph()
		}

		 = .cur(0).getLigID()
		 = .cur(0).getLigNumComps()
		 += 

		/* Skip the base glyph */
		.skipGlyph()
	}

	if ! &&  != 0 {
		/* Re-adjust components for any marks following. */
		for  := .idx;  < len(.Info); ++ {
			if  != .Info[].getLigID() {
				break
			}

			 := .Info[].getLigComp()
			if  == 0 {
				break
			}

			 :=  -  +
				min8(, )
			.Info[].setLigPropsForMark(, )
		}
	}
}

func ( *otApplyContext) ( uint16) bool {
	if .nestingLevelLeft == 0 || .recurseFunc == nil || .buffer.maxOps <= 0 {
		if .buffer.maxOps <= 0 {
			.buffer.maxOps--
			return false
		}
		.buffer.maxOps--
	}

	.nestingLevelLeft--
	 := .recurseFunc(, )
	.nestingLevelLeft++
	return 
}

func ( *otApplyContext) ( []uint16,  matcherFunc) ( bool,  int) {
	if len() == 0 {
		return true, .buffer.backtrackLen()
	}

	 := &.iterContext
	.resetBack(.buffer.backtrackLen())
	.setMatchFunc(, )

	for  := 0;  < len(); ++ {
		if ,  := .prev(); ! {
			return false, 
		}
	}

	return true, .idx
}

func ( *otApplyContext) ( []uint16,  matcherFunc,  int) ( bool,  int) {
	if len() == 0 {
		return true, 
	}

	 := &.iterContext
	.reset( - 1)
	.setMatchFunc(, )

	for  := 0;  < len(); ++ {
		if ,  := .next(); ! {
			return false, 
		}
	}

	return true, .idx + 1
}

// `count` and `matchPositions` include the first glyph
// `lookupRecord` is in design order
func ( *otApplyContext) ( int,
	 []tables.SequenceLookupRecord,  int,
) {
	 := .buffer
	var  int

	/* All positions are distance from beginning of *output* buffer.
	* Adjust. */
	{
		 := .backtrackLen()
		 =  +  - .idx

		 :=  - .idx
		/* Convert positions to new indexing. */
		for  := 0;  < ; ++ {
			.matchPositions[] += 
		}
	}

	for ,  := range  {
		 := int(.SequenceIndex)
		if  >=  { // invalid, ignored
			continue
		}

		 := .backtrackLen() + .lookaheadLen()

		// This can happen if earlier recursed lookups deleted many entries.
		if .matchPositions[] >=  {
			continue
		}

		.moveTo(.matchPositions[])

		if .maxOps <= 0 {
			break
		}

		if debugMode {
			fmt.Printf("\t\tAPPLY nested lookup %d\n", .LookupListIndex)
		}

		if !.recurse(.LookupListIndex) {
			continue
		}

		 := .backtrackLen() + .lookaheadLen()
		 :=  - 

		if  == 0 {
			continue
		}

		// Recursed lookup changed buffer len. Adjust.
		//
		// TODO:
		//
		// Right now, if buffer length increased by n, we assume n new glyphs
		// were added right after the current position, and if buffer length
		// was decreased by n, we assume n match positions after the current
		// one where removed.  The former (buffer length increased) case is
		// fine, but the decrease case can be improved in at least two ways,
		// both of which are significant:
		//
		//   - If recursed-to lookup is MultipleSubst and buffer length
		//     decreased, then it's current match position that was deleted,
		//     NOT the one after it.
		//
		//   - If buffer length was decreased by n, it does not necessarily
		//     mean that n match positions where removed, as there recursed-to
		//     lookup might had a different LookupFlag.  Here's a constructed
		//     case of that:
		//     https://github.com/harfbuzz/harfbuzz/discussions/3538
		//
		// It should be possible to construct tests for both of these cases.
		//
		 += 
		if  < int(.matchPositions[]) {
			// End might end up being smaller than match_positions[idx] if the recursed
			// lookup ended up removing many items.
			// Just never rewind end beyond start of current position, since that is
			// not possible in the recursed lookup.  Also adjust delta as such.
			//
			// https://bugs.chromium.org/p/chromium/issues/detail?id=659496
			// https://github.com/harfbuzz/harfbuzz/issues/1611
			//
			 += .matchPositions[] - 
			 = .matchPositions[]
		}

		 :=  + 1 // next now is the position after the recursed lookup.

		if  > 0 {
			if + > maxContextLength {
				break
			}
			ensureLen(&.matchPositions, +)
		} else {
			/* NOTE: delta is negative. */
			 = max(, int()-int())
			 -= 
		}

		/* Shift! */
		copy(.matchPositions[+:], .matchPositions[:])
		 += 
		 += 

		/* Fill in new entries. */
		for  :=  + 1;  < ; ++ {
			.matchPositions[] = .matchPositions[-1] + 1
		}

		/* And fixup the rest. */
		for ;  < ; ++ {
			.matchPositions[] += 
		}

	}

	.moveTo()
}

func ( *otApplyContext) ( tables.SequenceContextFormat1,  int) bool {
	if  >= len(.SeqRuleSet) { // index is not sanitized in tt.Parse
		return false
	}
	 := .SeqRuleSet[]
	return .applyRuleSet(, matchGlyph)
}

func ( *otApplyContext) ( tables.SequenceContextFormat2,  int,  GID) bool {
	,  := .ClassDef.Class(gID())
	var  tables.SequenceRuleSet
	if int() < len(.ClassSeqRuleSet) {
		 = .ClassSeqRuleSet[]
	}
	return .applyRuleSet(, matchClass(.ClassDef))
}

// return a slice containing [start, start+1, ..., end-1],
// using `indices` as an internal buffer to avoid allocations
// these indices are used to refer to coverage
func get1N( *[]uint16, ,  int) []uint16 {
	if  > cap(*) {
		* = make([]uint16, )
		for  := range * {
			(*)[] = uint16()
		}
	}
	return (*)[:]
}

func ( *otApplyContext) ( tables.SequenceContextFormat3,  int) bool {
	 := get1N(&.indices, 1, len(.Coverages))
	return .contextApplyLookup(, .SeqLookupRecords, matchCoverage(.Coverages))
}

func ( *otApplyContext) ( tables.ChainedSequenceContextFormat1,  int) bool {
	if  >= len(.ChainedSeqRuleSet) { // index is not sanitized in tt.Parse
		return false
	}
	 := .ChainedSeqRuleSet[]
	return .applyChainRuleSet(, [3]matcherFunc{matchGlyph, matchGlyph, matchGlyph})
}

func ( *otApplyContext) ( tables.ChainedSequenceContextFormat2,  int,  GID) bool {
	,  := .InputClassDef.Class(gID())
	var  tables.ChainedClassSequenceRuleSet
	if int() < len(.ChainedClassSeqRuleSet) {
		 = .ChainedClassSeqRuleSet[]
	}
	return .applyChainRuleSet(, [3]matcherFunc{
		matchClass(.BacktrackClassDef), matchClass(.InputClassDef), matchClass(.LookaheadClassDef),
	})
}

func ( *otApplyContext) ( tables.ChainedSequenceContextFormat3,  int) bool {
	, ,  := len(.BacktrackCoverages), len(.InputCoverages), len(.LookaheadCoverages)
	return .chainContextApplyLookup(get1N(&.indices, 0, ), get1N(&.indices, 1, ), get1N(&.indices, 0, ),
		.SeqLookupRecords, [3]matcherFunc{
			matchCoverage(.BacktrackCoverages), matchCoverage(.InputCoverages), matchCoverage(.LookaheadCoverages),
		})
}

// grows the slice if required; and copy the current contents
func ensureLen( *[]int,  int) {
	if  > len(*) {
		if  <= cap(*) {
			* = (*)[:]
		} else {
			 := make([]int, )
			copy(, *)
			* = 
		}
	}
}