package harfbuzz

import (
	
	

	ot 
	
	ucd 
	
)

// ported from harfbuzz/src/hb-ot-shape-complex-indic.cc, .hh Copyright © 2011,2012  Google, Inc.  Behdad Esfahbod

// Keep in sync with the code generator.
const (
	posStart = iota

	posRaToBecomeReph
	posPreM
	posPreC

	posBaseC
	posAfterMain

	posAboveC

	posBeforeSub
	posBelowC
	posAfterSub

	posBeforePost
	posPostC
	posAfterPost

	posSmvd

	posEnd
)

var _ otComplexShaper = (*complexShaperIndic)(nil)

// Indic shaper.
type complexShaperIndic struct {
	complexShaperNil

	plan indicShapePlan
}

/* Note:
 *
 * We treat Vowels and placeholders as if they were consonants.  This is safe because Vowels
 * cannot happen in a consonant syllable.  The plus side however is, we can call the
 * consonant syllable logic from the vowel syllable function and get it all right! */
const (
	consonantFlags = 1<<indSM_ex_C | 1<<indSM_ex_CS |
		1<<indSM_ex_Ra | 1<<indSM_ex_CM | 1<<indSM_ex_V |
		1<<indSM_ex_PLACEHOLDER | 1<<indSM_ex_DOTTEDCIRCLE
	joinerFlags = 1<<indSM_ex_ZWJ | 1<<indSM_ex_ZWNJ
)

func isOneOf( *GlyphInfo,  uint32) bool {
	/* If it ligated, all bets are off. */
	if .ligated() {
		return false
	}
	return 1<<.complexCategory& != 0
}

func isJoiner( *GlyphInfo) bool {
	return isOneOf(, joinerFlags)
}

func isConsonant( *GlyphInfo) bool {
	return isOneOf(, consonantFlags)
}

func isHalant( *GlyphInfo) bool {
	return isOneOf(, 1<<indSM_ex_H)
}

func ( *GlyphInfo) () {
	 := .codepoint
	 := indicGetCategories()
	.complexCategory, .complexAux = uint8(&0xFF), uint8(>>8)
}

type indicWouldSubstituteFeature struct {
	lookups []lookupMap
	//   count int
	zeroContext bool
}

func newIndicWouldSubstituteFeature( *otMap,  tables.Tag,  bool) indicWouldSubstituteFeature {
	var  indicWouldSubstituteFeature
	.zeroContext = 
	.lookups = .getStageLookups(0 /*GSUB*/, .getFeatureStage(0 /*GSUB*/, ))
	return 
}

func ( indicWouldSubstituteFeature) ( []GID,  *Font) bool {
	for ,  := range .lookups {
		if otLayoutLookupWouldSubstitute(, .index, , .zeroContext) {
			return true
		}
	}
	return false
}

/*
 * Indic configurations.  Note that we do not want to keep every single script-specific
 * behavior in these tables necessarily.  This should mainly be used for per-script
 * properties that are cheaper keeping here, than in the code.  Ie. if, say, one and
 * only one script has an exception, that one script can be if'ed directly in the code,
 * instead of adding a new flag in these structs.
 */

// reph_position_t
const (
	rephPosAfterMain  = posAfterMain
	rephPosBeforeSub  = posBeforeSub
	rephPosAfterSub   = posAfterSub
	rephPosBeforePost = posBeforePost
	rephPosAfterPost  = posAfterPost
)

// reph_mode_t
const (
	rephModeImplicit = iota /* Reph formed out of initial Ra,H sequence. */
	rephModeExplicit        /* Reph formed out of initial Ra,H,ZWJ sequence. */
	rephModeLogRepha        /* Encoded Repha character, needs reordering. */
)

// blwf_mode_t
const (
	blwfModePreAndPost = iota /* Below-forms feature applied to pre-base and post-base. */
	blwfModePostOnly          /* Below-forms feature applied to post-base only. */
)

type indicConfig struct {
	script     language.Script
	hasOldSpec bool
	virama     rune
	rephPos    uint8
	rephMode   uint8
	blwfMode   uint8
}

var indicConfigs = [...]indicConfig{
	/* Default.  Should be first. */
	{0, false, 0, rephPosBeforePost, rephModeImplicit, blwfModePreAndPost},
	{language.Devanagari, true, 0x094D, rephPosBeforePost, rephModeImplicit, blwfModePreAndPost},
	{language.Bengali, true, 0x09CD, rephPosAfterSub, rephModeImplicit, blwfModePreAndPost},
	{language.Gurmukhi, true, 0x0A4D, rephPosBeforeSub, rephModeImplicit, blwfModePreAndPost},
	{language.Gujarati, true, 0x0ACD, rephPosBeforePost, rephModeImplicit, blwfModePreAndPost},
	{language.Oriya, true, 0x0B4D, rephPosAfterMain, rephModeImplicit, blwfModePreAndPost},
	{language.Tamil, true, 0x0BCD, rephPosAfterPost, rephModeImplicit, blwfModePreAndPost},
	{language.Telugu, true, 0x0C4D, rephPosAfterPost, rephModeExplicit, blwfModePostOnly},
	{language.Kannada, true, 0x0CCD, rephPosAfterPost, rephModeImplicit, blwfModePostOnly},
	{language.Malayalam, true, 0x0D4D, rephPosAfterMain, rephModeLogRepha, blwfModePreAndPost},
}

var indicFeatures = [...]otMapFeature{
	/*
	* Basic features.
	* These features are applied in order, one at a time, after initial_reordering.
	 */
	{ot.NewTag('n', 'u', 'k', 't'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('a', 'k', 'h', 'n'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('r', 'p', 'h', 'f'), ffManualJoiners | ffPerSyllable},
	{ot.NewTag('r', 'k', 'r', 'f'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('p', 'r', 'e', 'f'), ffManualJoiners | ffPerSyllable},
	{ot.NewTag('b', 'l', 'w', 'f'), ffManualJoiners | ffPerSyllable},
	{ot.NewTag('a', 'b', 'v', 'f'), ffManualJoiners | ffPerSyllable},
	{ot.NewTag('h', 'a', 'l', 'f'), ffManualJoiners | ffPerSyllable},
	{ot.NewTag('p', 's', 't', 'f'), ffManualJoiners | ffPerSyllable},
	{ot.NewTag('v', 'a', 't', 'u'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('c', 'j', 'c', 't'), ffGlobalManualJoiners | ffPerSyllable},
	/*
	* Other features.
	* These features are applied all at once, after final_reordering
	* but before clearing syllables.
	* Default Bengali font in Windows for example has intermixed
	* lookups for init,pres,abvs,blws features.
	 */
	{ot.NewTag('i', 'n', 'i', 't'), ffManualJoiners | ffPerSyllable},
	{ot.NewTag('p', 'r', 'e', 's'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('a', 'b', 'v', 's'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('b', 'l', 'w', 's'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('p', 's', 't', 's'), ffGlobalManualJoiners | ffPerSyllable},
	{ot.NewTag('h', 'a', 'l', 'n'), ffGlobalManualJoiners | ffPerSyllable},
}

// in the same order as the indicFeatures array
const (
	indicNukt = iota
	indicAkhn
	indicRphf
	indicRkrf
	indicPref
	indicBlwf
	indicAbvf
	indicHalf
	indicPstf
	indicVatu
	indicCjct

	indicInit
	indicPres
	indicAbvs
	indicBlws
	indicPsts
	indicHaln

	indicNumFeatures
	indicBasicFeatures = indicInit /* Don't forget to update this! */
)

func ( *complexShaperIndic) ( *otShapePlanner) {
	 := &.otMap

	/* Do this before any lookups have been applied. */
	.addGSUBPause(setupSyllablesIndic)

	.enableFeatureExt(ot.NewTag('l', 'o', 'c', 'l'), ffPerSyllable, 1)
	/* The Indic specs do not require ccmp, but we apply it here since if
	* there is a use of it, it's typically at the beginning. */
	.enableFeatureExt(ot.NewTag('c', 'c', 'm', 'p'), ffPerSyllable, 1)

	 := 0
	.addGSUBPause(.initialReorderingIndic)

	for ;  < indicBasicFeatures; ++ {
		.addFeatureExt(indicFeatures[].tag, indicFeatures[].flags, 1)
		.addGSUBPause(nil)
	}

	.addGSUBPause(.plan.finalReorderingIndic)

	for ;  < indicNumFeatures; ++ {
		.addFeatureExt(indicFeatures[].tag, indicFeatures[].flags, 1)
	}
}

func (complexShaperIndic) ( *otShapePlanner) {
	.otMap.disableFeature(ot.NewTag('l', 'i', 'g', 'a'))
	.otMap.addGSUBPause(nil)
}

type indicShapePlan struct {
	blwf indicWouldSubstituteFeature
	pstf indicWouldSubstituteFeature
	vatu indicWouldSubstituteFeature
	rphf indicWouldSubstituteFeature
	pref indicWouldSubstituteFeature

	maskArray   [indicNumFeatures]GlyphMask
	config      indicConfig
	viramaGlyph GID // cached value

	isOldSpec bool
}

func ( *indicShapePlan) ( *Font) GID {
	if .viramaGlyph == ^GID(0) {
		,  := .face.NominalGlyph(.config.virama)
		if .config.virama == 0 || ! {
			 = 0
		}
		/* Technically speaking, the spec says we should apply 'locl' to virama too.
		* Maybe one day... */

		/* Our get_nominal_glyph() function needs a font, so we can't get the virama glyph
		* during shape planning...  Instead, overwrite it here. */
		.viramaGlyph = 
	}

	return .viramaGlyph
}

func ( *complexShaperIndic) ( *otShapePlan) {
	var  indicShapePlan

	.config = indicConfigs[0]
	for  := 1;  < len(indicConfigs); ++ {
		if .props.Script == indicConfigs[].script {
			.config = indicConfigs[]
			break
		}
	}

	.isOldSpec = .config.hasOldSpec && ((.otMap.chosenScript[0] & 0x000000FF) != '2')
	.viramaGlyph = ^GID(0)

	/* Use zero-context wouldSubstitute() matching for new-spec of the main
	* Indic scripts, and scripts with one spec only, but not for old-specs.
	* The new-spec for all dual-spec scripts says zero-context matching happens.
	*
	* However, testing with Malayalam shows that old and new spec both allow
	* context.  Testing with Bengali new-spec however shows that it doesn't.
	* So, the heuristic here is the way it is.  It should *only* be changed,
	* as we discover more cases of what Windows does.  DON'T TOUCH OTHERWISE. */
	 := !.isOldSpec && .props.Script != language.Malayalam
	.rphf = newIndicWouldSubstituteFeature(&.otMap, ot.NewTag('r', 'p', 'h', 'f'), )
	.pref = newIndicWouldSubstituteFeature(&.otMap, ot.NewTag('p', 'r', 'e', 'f'), )
	.blwf = newIndicWouldSubstituteFeature(&.otMap, ot.NewTag('b', 'l', 'w', 'f'), )
	.pstf = newIndicWouldSubstituteFeature(&.otMap, ot.NewTag('p', 's', 't', 'f'), )
	.vatu = newIndicWouldSubstituteFeature(&.otMap, ot.NewTag('v', 'a', 't', 'u'), )

	for  := range .maskArray {
		if indicFeatures[].flags&ffGLOBAL != 0 {
			.maskArray[] = 0
		} else {
			.maskArray[] = .otMap.getMask1(indicFeatures[].tag)
		}
	}

	.plan = 
}

func ( *indicShapePlan) (,  GID,  *Font) uint8 {
	/* For old-spec, the order of glyphs is Consonant,Virama,
	* whereas for new-spec, it's Virama,Consonant.  However,
	* some broken fonts (like Free Sans) simply copied lookups
	* from old-spec to new-spec without modification.
	* And oddly enough, Uniscribe seems to respect those lookups.
	* Eg. in the sequence U+0924,U+094D,U+0930, Uniscribe finds
	* base at 0.  The font however, only has lookups matching
	* 930,94D in 'blwf', not the expected 94D,930 (with new-spec
	* table).  As such, we simply match both sequences.  Seems
	* to work.
	*
	* Vatu is done as well, for:
	* https://github.com/harfbuzz/harfbuzz/issues/1587
	 */
	 := [3]GID{, , }
	if .blwf.wouldSubstitute([0:2], ) ||
		.blwf.wouldSubstitute([1:3], ) ||
		.vatu.wouldSubstitute([0:2], ) ||
		.vatu.wouldSubstitute([1:3], ) {
		return posBelowC
	}
	if .pstf.wouldSubstitute([0:2], ) ||
		.pstf.wouldSubstitute([1:3], ) {
		return posPostC
	}
	if .pref.wouldSubstitute([0:2], ) ||
		.pref.wouldSubstitute([1:3], ) {
		return posPostC
	}
	return posBaseC
}

func ( *complexShaperIndic) ( *otShapePlan,  *Buffer,  *Font) {
	/* We cannot setup masks here.  We save information about characters
	* and setup masks later on in a pause-callback. */

	 := .Info
	for  := range  {
		[].setIndicProperties()
	}
}

func setupSyllablesIndic( *otShapePlan,  *Font,  *Buffer) bool {
	findSyllablesIndic()
	,  := .syllableIterator()
	for ,  := .next();  < ; ,  = .next() {
		.unsafeToBreak(, )
	}
	return false
}

func foundSyllableIndic( uint8, ,  int,  []GlyphInfo,  *uint8) {
	for  := ;  < ; ++ {
		[].syllable = (* << 4) | 
	}
	*++
	if * == 16 {
		* = 1
	}
}

func ( *indicShapePlan) ( *Font,  *Buffer) {
	 := .loadViramaGlyph()
	if  != 0 {
		 := .Info
		for  := range  {
			if [].complexAux == posBaseC {
				 := [].Glyph
				[].complexAux = .consonantPositionFromFace(, , )
			}
		}
	}
}

/* Rules from:
 * https://docs.microsqoft.com/en-us/typography/script-development/devanagari */
func ( *indicShapePlan) ( *Font,  *Buffer, ,  int) {
	 := .Info

	/* https://github.com/harfbuzz/harfbuzz/issues/435#issuecomment-335560167
	* For compatibility with legacy usage in Kannada,
	* Ra+h+ZWJ must behave like Ra+ZWJ+h... */
	if .Props.Script == language.Kannada &&
		+3 <=  &&
		isOneOf(&[], 1<<indSM_ex_Ra) &&
		isOneOf(&[+1], 1<<indSM_ex_H) &&
		isOneOf(&[+2], 1<<indSM_ex_ZWJ) {
		.mergeClusters(+1, +3)
		[+1], [+2] = [+2], [+1]
	}

	/* 1. Find base consonant:
	*
	* The shaping engine finds the base consonant of the syllable, using the
	* following algorithm: starting from the end of the syllable, move backwards
	* until a consonant is found that does not have a below-base or post-base
	* form (post-base forms have to follow below-base forms), or that is not a
	* pre-base-reordering Ra, or arrive at the first consonant. The consonant
	* stopped at will be the base.
	*
	*   o If the syllable starts with Ra + Halant (in a script that has Reph)
	*     and has more than one consonant, Ra is excluded from candidates for
	*     base consonants.
	 */

	 := 
	 := false

	{
		/* . If the syllable starts with Ra + Halant (in a script that has Reph)
		 *    and has more than one consonant, Ra is excluded from candidates for
		 *    base consonants. */
		 := 
		if .maskArray[indicRphf] != 0 && +3 <=  &&
			((.config.rephMode == rephModeImplicit && !isJoiner(&[+2])) ||
				(.config.rephMode == rephModeExplicit && [+2].complexCategory == indSM_ex_ZWJ)) {
			/* See if it matches the 'rphf' feature. */
			 := [3]GID{[].Glyph, [+1].Glyph, 0}
			if .config.rephMode == rephModeExplicit {
				[2] = [+2].Glyph
			}
			if .rphf.wouldSubstitute([:2], ) ||
				(.config.rephMode == rephModeExplicit &&
					.rphf.wouldSubstitute([:3], )) {
				 += 2
				for  <  && isJoiner(&[]) {
					++
				}
				 = 
				 = true
			}
		} else if .config.rephMode == rephModeLogRepha && [].complexCategory == indSM_ex_Repha {
			 += 1
			for  <  && isJoiner(&[]) {
				++
			}
			 = 
			 = true
		}

		{
			/* . starting from the end of the syllable, move backwards */
			 := 
			 := false
			for  := true; ;  =  >  {
				--
				/* . until a consonant is found */
				if isConsonant(&[]) {
					/* . that does not have a below-base or post-base form
					 * (post-base forms have to follow below-base forms), */
					if [].complexAux != posBelowC &&
						([].complexAux != posPostC || ) {
						 = 
						break
					}
					if [].complexAux == posBelowC {
						 = true
					}

					/* . or that is not a pre-base-reordering Ra,
					 *
					 * IMPLEMENTATION NOTES:
					 *
					 * Our pre-base-reordering Ra's are marked posPostC, so will be skipped
					 * by the logic above already.
					 */

					/* . or arrive at the first consonant. The consonant stopped at will
					 * be the base. */
					 = 
				} else {
					/* A ZWJ after a Halant stops the base search, and requests an explicit
					 * half form.
					 * A ZWJ before a Halant, requests a subjoined form instead, and hence
					 * search continues.  This is particularly important for Bengali
					 * sequence Ra,H,Ya that should form Ya-Phalaa by subjoining Ya. */
					if  <  &&
						[].complexCategory == indSM_ex_ZWJ &&
						[-1].complexCategory == indSM_ex_H {
						break
					}
				}
			}
		}

		/* . If the syllable starts with Ra + Halant (in a script that has Reph)
		 *    and has more than one consonant, Ra is excluded from candidates for
		 *    base consonants.
		 *
		 *  Only do this for unforced Reph. (ie. not for Ra,H,ZWJ. */
		if  &&  ==  && - <= 2 {
			/* Have no other consonant, so Reph is not formed and Ra becomes base. */
			 = false
		}
	}

	/* 2. Decompose and reorder Matras:
	*
	* Each matra and any syllable modifier sign in the syllable are moved to the
	* appropriate position relative to the consonant(s) in the syllable. The
	* shaping engine decomposes two- or three-part matras into their constituent
	* parts before any repositioning. Matra characters are classified by which
	* consonant in a conjunct they have affinity for and are reordered to the
	* following positions:
	*
	*   o Before first half form in the syllable
	*   o After subjoined consonants
	*   o After post-form consonant
	*   o After main consonant (for above marks)
	*
	* IMPLEMENTATION NOTES:
	*
	* The normalize() routine has already decomposed matras for us, so we don't
	* need to worry about that.
	 */

	/* 3.  Reorder marks to canonical order:
	*
	* Adjacent nukta and halant or nukta and vedic sign are always repositioned
	* if necessary, so that the nukta is first.
	*
	* IMPLEMENTATION NOTES:
	*
	* We don't need to do this: the normalize() routine already did this for us.
	 */

	/* Reorder characters */

	for  := ;  < ; ++ {
		[].complexAux = min8(posPreC, [].complexAux)
	}

	if  <  {
		[].complexAux = posBaseC
	}

	/* Handle beginning Ra */
	if  {
		[].complexAux = posRaToBecomeReph
	}

	/* For old-style Indic script tags, move the first post-base Halant after
	* last consonant.
	*
	* Reports suggest that in some scripts Uniscribe does this only if there
	* is *not* a Halant after last consonant already.  We know that is the
	* case for Kannada, while it reorders unconditionally in other scripts,
	* eg. Malayalam, Bengali, and Devanagari.  We don't currently know about
	* other scripts, so we block Kannada.
	*
	* Kannada test case:
	* U+0C9A,U+0CCD,U+0C9A,U+0CCD
	* With some versions of Lohit Kannada.
	* https://bugs.freedesktop.org/show_bug.cgi?id=59118
	*
	* Malayalam test case:
	* U+0D38,U+0D4D,U+0D31,U+0D4D,U+0D31,U+0D4D
	* With lohit-ttf-20121122/Lohit-Malayalam.ttf
	*
	* Bengali test case:
	* U+0998,U+09CD,U+09AF,U+09CD
	* With Windows XP vrinda.ttf
	* https://github.com/harfbuzz/harfbuzz/issues/1073
	*
	* Devanagari test case:
	* U+091F,U+094D,U+0930,U+094D
	* With chandas.ttf
	* https://github.com/harfbuzz/harfbuzz/issues/1071
	 */
	if .isOldSpec {
		 := .Props.Script == language.Kannada
		for  :=  + 1;  < ; ++ {
			if [].complexCategory == indSM_ex_H {
				var  int
				for  =  - 1;  > ; -- {
					if isConsonant(&[]) ||
						( && [].complexCategory == indSM_ex_H) {
						break
					}
				}
				if [].complexCategory != indSM_ex_H &&  >  {
					/* Move Halant to after last consonant. */
					if debugMode {
						fmt.Printf("INDIC - halant: switching glyph %d to %d (and shifting between)", , )
					}
					 := []
					copy([:], [+1:])
					[] = 
				}
				break
			}
		}
	}

	/* Attach misc marks to previous char to move with them. */
	{
		var  uint8 = posStart
		for  := ;  < ; ++ {
			if 1<<[].complexCategory&(joinerFlags|1<<indSM_ex_N|1<<indSM_ex_RS|1<<indSM_ex_CM|1<<indSM_ex_H) != 0 {
				[].complexAux = 
				if [].complexCategory == indSM_ex_H && [].complexAux == posPreM {
					/*
					* Uniscribe doesn't move the Halant with Left Matra.
					* TEST: U+092B,U+093F,U+094D
					* We follow.
					 */
					for  := ;  > ; -- {
						if [-1].complexAux != posPreM {
							[].complexAux = [-1].complexAux
							break
						}
					}
				}
			} else if [].complexAux != posSmvd {
				if [].complexCategory == indSM_ex_MPst &&
					 >  && [-1].complexCategory == indSM_ex_SM {
					[-1].complexAux = [].complexAux
				}

				 = [].complexAux
			}
		}
	}

	/* For post-base consonants let them own anything before them
	* since the last consonant or matra. */
	{
		 := 
		for  :=  + 1;  < ; ++ {
			if isConsonant(&[]) {
				for  :=  + 1;  < ; ++ {
					if [].complexAux < posSmvd {
						[].complexAux = [].complexAux
					}
				}
				 = 
			} else if  := [].complexCategory;  == indSM_ex_M ||  == indSM_ex_MPst {
				 = 
			}
		}
	}

	{
		/* Use Syllable for sort accounting temporarily. */
		 := [].syllable
		for  := ;  < ; ++ {
			[].syllable = uint8( - )
		}

		/* Sit tight, rock 'n roll! */

		if debugMode {
			fmt.Printf("INDIC - post-base: sorting between glyph %d and %d\n", , )
		}

		 := [:]
		sort.SliceStable(, func(,  int) bool { return [].complexAux < [].complexAux })

		// Find base again; also flip left-matra sequence.
		 := 
		 := 
		 = 
		for  := ;  < ; ++ {
			if [].complexAux == posBaseC {
				 = 
				break
			} else if [].complexAux == posPreM {
				if  ==  {
					 = 
				}
				 = 
			}
		}

		/* https://github.com/harfbuzz/harfbuzz/issues/3863 */
		if  <  {
			/* No need to merge clusters, handled later. */
			.reverseRange(, +1)
			/* Reverse back nuktas, etc. */
			 := 
			for  := ;  <= ; ++ {
				if  := [].complexCategory;  == indSM_ex_M ||  == indSM_ex_MPst {
					.reverseRange(, +1)
					 =  + 1
				}
			}
		}

		// Things are out-of-control for post base positions, they may shuffle
		// around like crazy.  In old-spec mode, we move halants around, so in
		// that case merge all clusters after base.  Otherwise, check the sort
		// order and merge as needed.
		// For pre-base stuff, we handle cluster issues in final reordering.
		//
		// We could use buffer.sort() for this, if there was no special
		// reordering of pre-base stuff happening later...
		// We don't want to mergeClusters all of that, which buffer.sort()
		// would.  Here's a concrete example:
		//
		// Assume there's a pre-base consonant and explicit Halant before base,
		// followed by a prebase-reordering (left) Matra:
		//
		//   C,H,ZWNJ,B,M
		//
		// At this point in reordering we would have:
		//
		//   M,C,H,ZWNJ,B
		//
		// whereas in final reordering we will bring the Matra closer to Base:
		//
		//   C,H,ZWNJ,M,B
		//
		// That's why we don't want to merge-clusters anything before the Base
		// at this point.  But if something moved from after Base to before it,
		// we should merge clusters from base to them.  In final-reordering, we
		// only move things around before base, and merge-clusters up to base.
		// These two merge-clusters from the two sides of base will interlock
		// to merge things correctly. See:
		// https://github.com/harfbuzz/harfbuzz/issues/2272
		if .isOldSpec || - > 127 {
			.mergeClusters(, )
		} else {
			/* Note!  Syllable is a one-byte field. */
			for  := ;  < ; ++ {
				if [].syllable != 255 {
					,  := , 
					 :=  + int([].syllable)
					for  !=  {
						 = min(, )
						 = max(, )
						 :=  + int([].syllable)
						[].syllable = 255 /* So we don't process j later again. */
						 = 
					}
					.mergeClusters(max(, ), +1)
				}
			}
		}

		/* Put syllable back in. */
		for  := ;  < ; ++ {
			[].syllable = 
		}
	}

	/* Setup masks now */

	{
		var  GlyphMask

		/* Reph */
		for  := ;  <  && [].complexAux == posRaToBecomeReph; ++ {
			[].Mask |= .maskArray[indicRphf]
		}

		/* Pre-base */
		 = .maskArray[indicHalf]
		if !.isOldSpec &&
			.config.blwfMode == blwfModePreAndPost {
			 |= .maskArray[indicBlwf]
		}
		for  := ;  < ; ++ {
			[].Mask |= 
		}
		/* Base */
		 = 0
		if  <  {
			[].Mask |= 
		}
		/* Post-base */
		 = .maskArray[indicBlwf] |
			.maskArray[indicAbvf] |
			.maskArray[indicPstf]
		for  :=  + 1;  < ; ++ {
			[].Mask |= 
		}
	}

	if .isOldSpec &&
		.Props.Script == language.Devanagari {
		/* Old-spec eye-lash Ra needs special handling.  From the
		 * spec:
		 *
		 * "The feature 'below-base form' is applied to consonants
		 * having below-base forms and following the base consonant.
		 * The exception is vattu, which may appear below half forms
		 * as well as below the base glyph. The feature 'below-base
		 * form' will be applied to all such occurrences of Ra as well."
		 *
		 * Test case: U+0924,U+094D,U+0930,U+094d,U+0915
		 * with Sanskrit 2003 font.
		 *
		 * However, note that Ra,Halant,ZWJ is the correct way to
		 * request eyelash form of Ra, so we wouldbn't inhibit it
		 * in that sequence.
		 *
		 * Test case: U+0924,U+094D,U+0930,U+094d,U+200D,U+0915
		 */
		for  := ; +1 < ; ++ {
			if [].complexCategory == indSM_ex_Ra &&
				[+1].complexCategory == indSM_ex_H &&
				(+2 ==  ||
					[+2].complexCategory != indSM_ex_ZWJ) {
				[].Mask |= .maskArray[indicBlwf]
				[+1].Mask |= .maskArray[indicBlwf]
			}
		}
	}

	 := 2
	if .maskArray[indicPref] != 0 && + <  {
		/* Find a Halant,Ra sequence and mark it for pre-base-reordering processing. */
		for  :=  + 1; +-1 < ; ++ {
			var  [2]GID
			for  := 0;  < ; ++ {
				[] = [+].Glyph
			}
			if .pref.wouldSubstitute([:], ) {
				for  := 0;  < ; ++ {
					[].Mask |= .maskArray[indicPref]
					++
				}
				break
			}
		}
	}

	/* Apply ZWJ/ZWNJ effects */
	for  :=  + 1;  < ; ++ {
		if isJoiner(&[]) {
			 := [].complexCategory == indSM_ex_ZWNJ
			 := 

			for  := true; ;  = ( >  && !isConsonant(&[])) {
				--

				/* ZWJ/ZWNJ should disable CJCT.  They do that by simply
				 * being there, since we don't skip them for the CJCT
				 * feature (ie. F_MANUAL_ZWJ) */

				/* A ZWNJ disables HALF. */
				if  {
					[].Mask &= ^.maskArray[indicHalf]
				}

			}
		}
	}
}

func ( *indicShapePlan) ( *Font,  *Buffer, ,  int) {
	/* We treat placeholder/dotted-circle as if they are consonants, so we
	 * should just chain... */
	.initialReorderingConsonantSyllable(, , , )
}

func ( *indicShapePlan) ( *Font,  *Buffer, ,  int) {
	 := (.Info[].syllable & 0x0F)
	switch  {
	case indicVowelSyllable, indicConsonantSyllable: /* We made the vowels look like consonants.  So let's call the consonant logic! */
		.initialReorderingConsonantSyllable(, , , )
	case indicBrokenCluster, indicStandaloneCluster: /* We already inserted dotted-circles, so just call the standalone_cluster. */
		.initialReorderingStandaloneCluster(, , , )
	}
}

func ( *complexShaperIndic) ( *otShapePlan,  *Font,  *Buffer) bool {
	if debugMode {
		fmt.Println("INDIC - start reordering indic initial")
	}

	.plan.updateConsonantPositionsIndic(, )
	 := syllabicInsertDottedCircles(, , indicBrokenCluster,
		indSM_ex_DOTTEDCIRCLE, indSM_ex_Repha, posEnd)

	,  := .syllableIterator()
	for ,  := .next();  < ; ,  = .next() {
		.plan.initialReorderingSyllableIndic(, , , )
	}

	if debugMode {
		fmt.Println("INDIC - end reordering indic initial")
	}

	return 
}

func ( *indicShapePlan) ( *Buffer, ,  int) {
	 := .Info

	/* This function relies heavily on halant glyphs.  Lots of ligation
	* and possibly multiple substitutions happened prior to this
	* phase, and that might have messed up our properties.  Recover
	* from a particular case of that where we're fairly sure that a
	* class of otH is desired but has been lost. */
	/* We don't call loadViramaGlyph(), since we know it's already
	* loaded. */
	 := .viramaGlyph
	if  != 0 {
		for  := ;  < ; ++ {
			if [].Glyph ==  &&
				[].ligated() && [].multiplied() {
				/* This will make sure that this glyph passes isHalant() test. */
				[].complexCategory = indSM_ex_H
				[].clearLigatedAndMultiplied()
			}
		}
	}

	/* 4. Final reordering:
	*
	* After the localized forms and basic shaping forms GSUB features have been
	* applied (see below), the shaping engine performs some final glyph
	* reordering before applying all the remaining font features to the entire
	* syllable.
	 */

	 := .maskArray[indicPref] != 0

	/* Find base again */
	var  int
	for  = ;  < ; ++ {
		if [].complexAux >= posBaseC {
			if  && +1 <  {
				for  :=  + 1;  < ; ++ {
					if ([].Mask & .maskArray[indicPref]) != 0 {
						if !([].substituted() && [].ligatedAndDidntMultiply()) {
							/* Ok, this was a 'pref' candidate but didn't form any.
							* Base is around here... */
							 = 
							for  <  && isHalant(&[]) {
								++
							}
							if  <  {
								[].complexAux = posBaseC
							}

							 = false
						}
						break
					}
					if  ==  {
						break
					}
				}
			}
			/* For Malayalam, skip over unformed below- (but NOT post-) forms. */
			if .Props.Script == language.Malayalam {
				for  :=  + 1;  < ; ++ {
					for  <  && isJoiner(&[]) {
						++
					}
					if  ==  || !isHalant(&[]) {
						break
					}
					++ /* Skip halant. */
					for  <  && isJoiner(&[]) {
						++
					}
					if  <  && isConsonant(&[]) && [].complexAux == posBelowC {
						 = 
						[].complexAux = posBaseC
					}
				}
			}

			if  <  && [].complexAux > posBaseC {
				--
			}
			break
		}
	}
	if  ==  &&  <  && isOneOf(&[-1], 1<<indSM_ex_ZWJ) {
		--
	}
	if  <  {
		for  <  && isOneOf(&[], 1<<indSM_ex_N|1<<indSM_ex_H) {
			--
		}
	}

	/*   o Reorder matras:
	*
	*     If a pre-base matra character had been reordered before applying basic
	*     features, the glyph can be moved closer to the main consonant based on
	*     whether half-forms had been formed. Actual position for the matra is
	*     defined as “after last standalone halant glyph, after initial matra
	*     position and before the main consonant”. If ZWJ or ZWNJ follow this
	*     halant, position is moved after it.
	*
	* IMPLEMENTATION NOTES:
	*
	* It looks like the last sentence is wrong.  Testing, with Windows 7 Uniscribe
	* and Devanagari shows that the behavior is best described as:
	*
	* "If ZWJ follows this halant, matra is NOT repositioned after this halant.
	*  If ZWNJ follows this halant, position is moved after it."
	*
	* Test case, with Adobe Devanagari or Nirmala UI:
	*
	*   U+091F,U+094D,U+200C,U+092F,U+093F
	*   (Matra moves to the middle, after ZWNJ.)
	*
	*   U+091F,U+094D,U+200D,U+092F,U+093F
	*   (Matra does NOT move, stays to the left.)
	*
	* https://github.com/harfbuzz/harfbuzz/issues/1070
	 */

	if +1 <  &&  <  /* Otherwise there can't be any pre-base matra characters. */ {
		/* If we lost track of base, alas, position before last thingy. */
		 :=  - 1
		if  ==  {
			 =  - 2
		}

		/* Malayalam / Tamil do not have "half" forms or explicit virama forms.
		 * The glyphs formed by 'half' are Chillus or ligated explicit viramas.
		 * We want to position matra after them.
		 */
		if .Props.Script != language.Malayalam && .Props.Script != language.Tamil {
		:
			for  >  && !isOneOf(&[], 1<<indSM_ex_M|1<<indSM_ex_MPst|1<<indSM_ex_H) {
				--
			}
			/* If we found no Halant we are done.
			* Otherwise only proceed if the Halant does
			* not belong to the Matra itself! */
			if isHalant(&[]) && [].complexAux != posPreM {
				if +1 <  {
					/* . If ZWJ follows this halant, matra is NOT repositioned after this halant. */
					if [+1].complexCategory == indSM_ex_ZWJ {
						/* Keep searching. */
						if  >  {
							--
							goto 
						}
					}
					/* . If ZWNJ follows this halant, position is moved after it.
					*
					* IMPLEMENTATION NOTES:
					*
					* This is taken care of by the state-machine. A Halant,ZWNJ is a terminating
					* sequence for a consonant syllable; any pre-base matras occurring after it
					* will belong to the subsequent syllable.
					 */
				}
			} else {
				 =  /* No move. */
			}
		}

		if  <  && [].complexAux != posPreM {
			/* Now go see if there's actually any matras... */
			for  := ;  > ; -- {
				if [-1].complexAux == posPreM {
					 :=  - 1
					if  <  &&  <=  { /* Shouldn't actually happen. */
						--
					}

					if debugMode {
						fmt.Printf("INDIC - matras: switching glyph %d to %d (and shifting between)", , )
					}

					 := []
					copy([:], [+1:])
					[] = 

					/* Note: this mergeClusters() is intentionally *after* the reordering.
					* Indic matra reordering is special and tricky... */
					.mergeClusters(, min(, +1))

					--
				}
			}
		} else {
			for  := ;  < ; ++ {
				if [].complexAux == posPreM {
					.mergeClusters(, min(, +1))
					break
				}
			}
		}
	}

	/*   o Reorder reph:
	*
	*     Reph’s original position is always at the beginning of the syllable,
	*     (i.e. it is not reordered at the character reordering stage). However,
	*     it will be reordered according to the basic-forms shaping results.
	*     Possible positions for reph, depending on the script, are; after main,
	*     before post-base consonant forms, and after post-base consonant forms.
	 */

	/* Two cases:
	*
	* - If repha is encoded as a sequence of characters (Ra,H or Ra,H,ZWJ), then
	*   we should only move it if the sequence ligated to the repha form.
	*
	* - If repha is encoded separately and in the logical position, we should only
	*   move it if it did NOT ligate.  If it ligated, it's probably the font trying
	*   to make it work without the reordering.
	 */
	if +1 <  && [].complexAux == posRaToBecomeReph &&
		([].complexCategory == indSM_ex_Repha) != [].ligatedAndDidntMultiply() {
		var  int
		 := .config.rephPos

		/*       1. If reph should be positioned after post-base consonant forms,
		 *          proceed to step 5.
		 */
		if  == rephPosAfterPost {
			goto 
		}

		/*       2. If the reph repositioning class is not after post-base: target
		 *          position is after the first explicit halant glyph between the
		 *          first post-reph consonant and last main consonant. If ZWJ or ZWNJ
		 *          are following this halant, position is moved after it. If such
		 *          position is found, this is the target position. Otherwise,
		 *          proceed to the next step.
		 *
		 *          Note: in old-implementation fonts, where classifications were
		 *          fixed in shaping engine, there was no case where reph position
		 *          will be found on this step.
		 */
		{
			 =  + 1
			for  <  && !isHalant(&[]) {
				++
			}

			if  <  && isHalant(&[]) {
				/* .If ZWJ or ZWNJ are following this halant, position is moved after it. */
				if +1 <  && isJoiner(&[+1]) {
					++
				}
				goto 
			}
		}

		/*       3. If reph should be repositioned after the main consonant: find the
		 *          first consonant not ligated with main, or find the first
		 *          consonant that is not a potential pre-base-reordering Ra.
		 */
		if  == rephPosAfterMain {
			 = 
			for +1 <  && [+1].complexAux <= posAfterMain {
				++
			}
			if  <  {
				goto 
			}
		}

		/*       4. If reph should be positioned before post-base consonant, find
		 *          first post-base classified consonant not ligated with main. If no
		 *          consonant is found, the target position should be before the
		 *          first matra, syllable modifier sign or vedic sign.
		 */
		/* This is our take on what step 4 is trying to say (and failing, BADLY). */
		if  == rephPosAfterSub {
			 = 
			for +1 <  &&
				(1<<[+1].complexAux)&(1<<posPostC|1<<posAfterPost|1<<posSmvd) == 0 {
				++
			}
			if  <  {
				goto 
			}
		}

		/*       5. If no consonant is found in steps 3 or 4, move reph to a position
		 *          immediately before the first post-base matra, syllable modifier
		 *          sign or vedic sign that has a reordering class after the intended
		 *          reph position. For example, if the reordering position for reph
		 *          is post-main, it will skip above-base matras that also have a
		 *          post-main position.
		 */
	:
		{
			/* Copied from step 2. */
			 =  + 1
			for  <  && !isHalant(&[]) {
				++
			}

			if  <  && isHalant(&[]) {
				/* .If ZWJ or ZWNJ are following this halant, position is moved after it. */
				if +1 <  && isJoiner(&[+1]) {
					++
				}
				goto 
			}
		}
		/* See https://github.com/harfbuzz/harfbuzz/issues/2298#issuecomment-615318654 */

		/*       6. Otherwise, reorder reph to the end of the syllable.
		 */
		{
			 =  - 1
			for  >  && [].complexAux == posSmvd {
				--
			}

			/*
			* If the Reph is to be ending up after a Matra,Halant sequence,
			* position it before that Halant so it can interact with the Matra.
			* However, if it's a plain Consonant,Halant we shouldn't do that.
			* Uniscribe doesn't do this.
			* TEST: U+0930,U+094D,U+0915,U+094B,U+094D
			 */
			if isHalant(&[]) {
				for  :=  + 1;  < ; ++ {
					if  := [].complexCategory;  == indSM_ex_M ||  == indSM_ex_MPst {
						/* Ok, got it. */
						--
					}
				}
			}

			goto 
		}

	:
		{

			if debugMode {
				fmt.Printf("INDIC - reph: switching glyph %d to %d (and shifting between)", , )
			}

			/* Move */
			.mergeClusters(, +1)
			 := []
			copy([:], [+1:])
			[] = 

			if  <  &&  <=  {
				--
			}
		}
	}

	/*   o Reorder pre-base-reordering consonants:
	*
	*     If a pre-base-reordering consonant is found, reorder it according to
	*     the following rules:
	 */

	if  && +1 <  /* Otherwise there can't be any pre-base-reordering Ra. */ {
		for  :=  + 1;  < ; ++ {
			if ([].Mask & .maskArray[indicPref]) != 0 {
				/*       1. Only reorder a glyph produced by substitution during application
				 *          of the <pref> feature. (Note that a font may shape a Ra consonant with
				 *          the feature generally but block it in certain contexts.)
				 */
				/* Note: We just check that something got substituted.  We don't check that
				 * the <pref> feature actually did it...
				 *
				 * Reorder pref only if it ligated. */
				if [].ligatedAndDidntMultiply() {
					/*
					*       2. Try to find a target position the same way as for pre-base matra.
					*          If it is found, reorder pre-base consonant glyph.
					*
					*       3. If position is not found, reorder immediately before main
					*          consonant.
					 */

					 := 
					/* Malayalam / Tamil do not have "half" forms or explicit virama forms.
					* The glyphs formed by 'half' are Chillus or ligated explicit viramas.
					* We want to position matra after them.
					 */
					if .Props.Script != language.Malayalam && .Props.Script != language.Tamil {
						for  >  && !isOneOf(&[-1], 1<<indSM_ex_M|1<<indSM_ex_MPst|1<<indSM_ex_H) {
							--
						}
					}

					if  >  && isHalant(&[-1]) {
						/* . If ZWJ or ZWNJ follow this halant, position is moved after it. */
						if  <  && isJoiner(&[]) {
							++
						}
					}

					{

						 := 
						.mergeClusters(, +1)

						if debugMode {
							fmt.Printf("INDIC - pre-base: switching glyph %d to %d (and shifting between)", , )
						}

						 := []
						copy([+1:], [:])
						[] = 

						if  <=  &&  <  {
							++
						}
					}
				}

				break
			}
		}
	}

	/* Apply 'init' to the Left Matra if it's a word start. */
	if [].complexAux == posPreM {
		const  = 1<<ucd.Unassigned |
			1<<ucd.Co |
			1<<ucd.Cs |
			1<<ucd.Ll |
			1<<ucd.Lm |
			1<<ucd.Lo |
			1<<ucd.Lt |
			1<<ucd.Lu |
			1<<ucd.Mc |
			1<<ucd.Me |
			1<<ucd.Mn
		if  == 0 || 1<<[-1].unicode.generalCategory()& == 0 {
			[].Mask |= .maskArray[indicInit]
		} else {
			.unsafeToBreak(-1, +1)
		}
	}
}

func ( *indicShapePlan) ( *otShapePlan,  *Font,  *Buffer) bool {
	if len(.Info) == 0 {
		return false
	}

	if debugMode {
		fmt.Println("INDIC - start reordering indic final")
	}

	,  := .syllableIterator()
	for ,  := .next();  < ; ,  = .next() {
		.finalReorderingSyllableIndic(, , )
	}

	if debugMode {
		fmt.Println("INDIC - end reordering indic final")
	}

	return false
}

func ( complexShaperIndic) ( *otShapePlan,  *Buffer,  *Font) {
	preprocessTextVowelConstraints()
}

func ( *complexShaperIndic) ( *otNormalizeContext,  rune) (rune, rune, bool) {
	switch  {
	/* Don't decompose these. */
	case 0x0931, /* DEVANAGARI LETTER RRA */
		// https://github.com/harfbuzz/harfbuzz/issues/779
		0x09DC, /* BENGALI LETTER RRA */
		0x09DD, /* BENGALI LETTER RHA */
		0x0B94:
		return 0, 0, false /* TAMIL LETTER AU */

		/*
		 * Decompose split matras that don't have Unicode decompositions.
		 */
	}

	return ucd.Decompose()
}

func ( *complexShaperIndic) ( *otNormalizeContext, ,  rune) (rune, bool) {
	/* Avoid recomposing split matras. */
	if ucd.LookupGeneralCategory().IsMark() {
		return 0, false
	}

	/* Composition-exclusion exceptions that we want to recompose. */
	if  == 0x09AF &&  == 0x09BC {
		return 0x09DF, true
	}

	return ucd.Compose(, )
}

func (complexShaperIndic) () (zeroWidthMarks, bool) {
	return zeroWidthMarksNone, false
}

func (complexShaperIndic) () normalizationMode {
	return nmComposedDiacriticsNoShortCircuit
}