package harfbuzz

import (
	
	

	
	ot 
	
	ucd 
	
)

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

var _ otComplexShaper = (*complexShaperArabic)(nil)

const flagArabicHasStch = bsfShaper0

/* See:
 * https://github.com/harfbuzz/harfbuzz/commit/6e6f82b6f3dde0fc6c3c7d991d9ec6cfff57823d#commitcomment-14248516 */
func isWord( generalCategory) bool {
	const  = 1<<ucd.Unassigned |
		1<<ucd.Co |
		/*1 <<  LowercaseLetter |*/
		1<<ucd.Lm |
		1<<ucd.Lo |
		/*1 <<  TitlecaseLetter |*/
		/*1 <<  UppercaseLetter |*/
		1<<ucd.Mc |
		1<<ucd.Me |
		1<<ucd.Mn |
		1<<ucd.Nd |
		1<<ucd.Nl |
		1<<ucd.No |
		1<<ucd.Sc |
		1<<ucd.Sk |
		1<<ucd.Sm |
		1<<ucd.So
	return (1<<)& != 0
}

/*
 * Joining types:
 */

// arabicJoining is a property used to shape Arabic runes.
// See the table arabicJoinings.
type arabicJoining byte

const (
	ajU          arabicJoining = 'U' // Un-joining, e.g. Full Stop
	ajR          arabicJoining = 'R' // Right-joining, e.g. Arabic Letter Dal
	ajAlaph      arabicJoining = 'a' // Alaph group (included in kind R)
	ajDalathRish arabicJoining = 'd' // Dalat Rish group (included in kind R)
	ajD          arabicJoining = 'D' // Dual-joining, e.g. Arabic Letter Ain
	ajC          arabicJoining = 'C' // Join-Causing, e.g. Tatweel, ZWJ
	ajL          arabicJoining = 'L' // Left-joining, i.e. fictional
	ajT          arabicJoining = 'T' // Transparent, e.g. Arabic Fatha
	ajG          arabicJoining = 'G' // Ignored, e.g. LRE, RLE, ZWNBSP
)

// index into arabicStateTable
const (
	joiningTypeU = iota
	joiningTypeL
	joiningTypeR
	joiningTypeD
	joiningGroupAlaph
	joiningGroupDalathRish
	numStateMachineCols
	joiningTypeT
	joiningTypeC = joiningTypeD
)

func getJoiningType( rune,  generalCategory) uint8 {
	if ,  := arabicJoinings[];  {
		switch  {
		case ajU:
			return joiningTypeU
		case ajL:
			return joiningTypeL
		case ajR:
			return joiningTypeR
		case ajD:
			return joiningTypeD
		case ajAlaph:
			return joiningGroupAlaph
		case ajDalathRish:
			return joiningGroupDalathRish
		case ajT:
			return joiningTypeT
		case ajC:
			return joiningTypeC
		}
	}

	const  = 1<<ucd.Mn | 1<<ucd.Me | 1<<ucd.Cf
	if 1<<& != 0 {
		return joiningTypeT
	}
	return joiningTypeU
}

func featureIsSyriac( ot.Tag) bool {
	return '2' <= byte() && byte() <= '3'
}

var arabicFeatures = [...]ot.Tag{
	ot.NewTag('i', 's', 'o', 'l'),
	ot.NewTag('f', 'i', 'n', 'a'),
	ot.NewTag('f', 'i', 'n', '2'),
	ot.NewTag('f', 'i', 'n', '3'),
	ot.NewTag('m', 'e', 'd', 'i'),
	ot.NewTag('m', 'e', 'd', '2'),
	ot.NewTag('i', 'n', 'i', 't'),
}

/* Same order as the feature array */
const (
	arabIsol = iota
	arabFina
	arabFin2
	araFin3
	arabMedi
	arabMed2
	arabInit

	arabNone

	/* We abuse the same byte for other things... */
	arabStchFixed
	arabStchRepeating
)

var arabicStateTable = [...][numStateMachineCols]struct {
	prevAction uint8
	currAction uint8
	nextState  uint16
}{
	/*   jt_U,          jt_L,          jt_R,          jt_D,          jg_ALAPH,      jg_DALATH_RISH */

	/* State 0: prev was U, not willing to join. */
	{{arabNone, arabNone, 0}, {arabNone, arabIsol, 2}, {arabNone, arabIsol, 1}, {arabNone, arabIsol, 2}, {arabNone, arabIsol, 1}, {arabNone, arabIsol, 6}},

	/* State 1: prev was R or ISOL/ALAPH, not willing to join. */
	{{arabNone, arabNone, 0}, {arabNone, arabIsol, 2}, {arabNone, arabIsol, 1}, {arabNone, arabIsol, 2}, {arabNone, arabFin2, 5}, {arabNone, arabIsol, 6}},

	/* State 2: prev was D/L in ISOL form, willing to join. */
	{{arabNone, arabNone, 0}, {arabNone, arabIsol, 2}, {arabInit, arabFina, 1}, {arabInit, arabFina, 3}, {arabInit, arabFina, 4}, {arabInit, arabFina, 6}},

	/* State 3: prev was D in FINA form, willing to join. */
	{{arabNone, arabNone, 0}, {arabNone, arabIsol, 2}, {arabMedi, arabFina, 1}, {arabMedi, arabFina, 3}, {arabMedi, arabFina, 4}, {arabMedi, arabFina, 6}},

	/* State 4: prev was FINA ALAPH, not willing to join. */
	{{arabNone, arabNone, 0}, {arabNone, arabIsol, 2}, {arabMed2, arabIsol, 1}, {arabMed2, arabIsol, 2}, {arabMed2, arabFin2, 5}, {arabMed2, arabIsol, 6}},

	/* State 5: prev was FIN2/FIN3 ALAPH, not willing to join. */
	{{arabNone, arabNone, 0}, {arabNone, arabIsol, 2}, {arabIsol, arabIsol, 1}, {arabIsol, arabIsol, 2}, {arabIsol, arabFin2, 5}, {arabIsol, arabIsol, 6}},

	/* State 6: prev was DALATH/RISH, not willing to join. */
	{{arabNone, arabNone, 0}, {arabNone, arabIsol, 2}, {arabNone, arabIsol, 1}, {arabNone, arabIsol, 2}, {arabNone, araFin3, 5}, {arabNone, arabIsol, 6}},
}

type complexShaperArabic struct {
	complexShaperNil

	plan arabicShapePlan
}

func (complexShaperArabic) () (zeroWidthMarks, bool) {
	return zeroWidthMarksByGdefLate, true
}

func (complexShaperArabic) () normalizationMode {
	return nmDefault
}

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

	/* We apply features according to the Arabic spec, with pauses
	* in between most.
	*
	* The pause between init/medi/... and rlig is required.  See eg:
	* https://bugzilla.mozilla.org/show_bug.cgi?id=644184
	*
	* The pauses between init/medi/... themselves are not necessarily
	* needed as only one of those features is applied to any character.
	* The only difference it makes is when fonts have contextual
	* substitutions.  We now follow the order of the spec, which makes
	* for better experience if that's what Uniscribe is doing.
	*
	* At least for Arabic, looks like Uniscribe has a pause between
	* rlig and calt.  Otherwise the IranNastaliq's ALLAH ligature won't
	* work.  However, testing shows that rlig and calt are applied
	* together for Mongolian in Uniscribe.  As such, we only add a
	* pause for Arabic, not other scripts.
	 */

	.enableFeature(ot.NewTag('s', 't', 'c', 'h'))
	.addGSUBPause(recordStch)

	.enableFeatureExt(ot.NewTag('c', 'c', 'm', 'p'), ffManualZWJ, 1)
	.enableFeatureExt(ot.NewTag('l', 'o', 'c', 'l'), ffManualZWJ, 1)

	.addGSUBPause(nil)

	for ,  := range arabicFeatures {
		 := .props.Script == language.Arabic && !featureIsSyriac()
		 := ffNone
		if  {
			 = ffHasFallback
		}
		.addFeatureExt(, ffManualZWJ|, 1)
		.addGSUBPause(nil)
	}

	/* Unicode says a ZWNJ means "don't ligate". In Arabic script
	* however, it says a ZWJ should also mean "don't ligate". So we run
	* the main ligating features as MANUAL_ZWJ. */

	.enableFeatureExt(ot.NewTag('r', 'l', 'i', 'g'), ffManualZWJ|ffHasFallback, 1)

	if .props.Script == language.Arabic {
		.addGSUBPause(arabicFallbackShape)
	}
	.enableFeatureExt(ot.NewTag('c', 'a', 'l', 't'), ffManualZWJ, 1)
	/* https://github.com/harfbuzz/harfbuzz/issues/1573 */
	if !.hasFeature(ot.NewTag('r', 'c', 'l', 't')) {
		.addGSUBPause(nil)
	}

	.enableFeatureExt(ot.NewTag('l', 'i', 'g', 'a'), ffManualZWJ, 1)
	.enableFeatureExt(ot.NewTag('c', 'l', 'i', 'g'), ffManualZWJ, 1)

	/* The spec includes 'cswh'.  Earlier versions of Windows
	* used to enable this by default, but testing suggests
	* that Windows 8 and later do not enable it by default,
	* and spec now says 'Off by default'.
	* We disabled this in ae23c24c32.
	* Note that IranNastaliq uses this feature extensively
	* to fixup broken glyph sequences.  Oh well...
	* Test case: U+0643,U+0640,U+0631. */
	//map_.enable_feature (newTag('c','s','w','h'));
	.enableFeatureExt(ot.NewTag('m', 's', 'e', 't'), ffManualZWJ, 1)
}

type arabicShapePlan struct {
	fallbackPlan *arabicFallbackPlan
	/* The "+ 1" in the next array is to accommodate for the "NONE" command,
	* which is not an OpenType feature, but this simplifies the code by not
	* having to do a "if (... < NONE) ..." and just rely on the fact that
	* maskArray[NONE] == 0. */
	maskArray  [len(arabicFeatures) + 1]GlyphMask
	doFallback bool
	hasStch    bool
}

func newArabicPlan( *otShapePlan) arabicShapePlan {
	var  arabicShapePlan

	.doFallback = .props.Script == language.Arabic
	.hasStch = .otMap.getMask1(ot.NewTag('s', 't', 'c', 'h')) != 0
	for ,  := range arabicFeatures {
		.maskArray[] = .otMap.getMask1()
		.doFallback = .doFallback &&
			(featureIsSyriac() || .otMap.needsFallback())
	}
	return 
}

func ( *complexShaperArabic) ( *otShapePlan) {
	.plan = newArabicPlan()
}

func applyArabicJoining( *Buffer) {
	 := .Info
	,  := -1, uint16(0)

	// check pre-context
	for ,  := range .context[0] {
		 := getJoiningType(, ucd.LookupGeneralCategory())

		if  == joiningTypeT {
			continue
		}

		 := &arabicStateTable[][]
		 = .nextState
		break
	}

	for  := 0;  < len(); ++ {
		 := getJoiningType([].codepoint, [].unicode.generalCategory())

		if  == joiningTypeT {
			[].complexAux = arabNone
			continue
		}

		 := &arabicStateTable[][]

		if .prevAction != arabNone &&  != -1 {
			[].complexAux = .prevAction
			.safeToInsertTatweel(, +1)
		} else {
			if  == -1 {
				if  >= joiningTypeR {
					.unsafeToConcatFromOutbuffer(0, +1)
				}
			} else {
				if  >= joiningTypeR ||
					(2 <=  &&  <= 5) /* States that have a possible prevAction. */ {
					.unsafeToConcat(, +1)
				}
			}
		}

		[].complexAux = .currAction

		 = 
		 = .nextState
	}

	for ,  := range .context[1] {
		 := getJoiningType(, ucd.LookupGeneralCategory())

		if  == joiningTypeT {
			continue
		}

		 := &arabicStateTable[][]
		if .prevAction != arabNone &&  != -1 {
			[].complexAux = .prevAction
			.safeToInsertTatweel(, len(.Info))
		} else if 2 <=  &&  <= 5 /* States that have a possible prevAction. */ {
			.unsafeToConcat(, len(.Info))
		}
		break
	}
}

func mongolianVariationSelectors( *Buffer) {
	// copy complexAux from base to Mongolian variation selectors.
	 := .Info
	for  := 1;  < len(); ++ {
		if  := [].codepoint; 0x180B <=  &&  <= 0x180D ||  == 0x180F {
			[].complexAux = [-1].complexAux
		}
	}
}

func ( arabicShapePlan) ( *Buffer,  language.Script) {
	applyArabicJoining()
	if  == language.Mongolian {
		mongolianVariationSelectors()
	}

	 := .Info
	for  := range  {
		[].Mask |= .maskArray[[].complexAux]
	}
}

func ( *complexShaperArabic) ( *otShapePlan,  *Buffer,  *Font) {
	.plan.setupMasks(, .props.Script)
}

func arabicFallbackShape( *otShapePlan,  *Font,  *Buffer) bool {
	 := .shaper.(*complexShaperArabic).plan

	if !.doFallback {
		return false
	}

	 := .fallbackPlan
	if  == nil {
		// this sucks. We need a font to build the fallback plan...
		 = newArabicFallbackPlan(, )
	}

	.shape(, )
	return true
}

//
//  Stretch feature: "stch".
//  See example here:
//  https://docs.microsoft.com/en-us/typography/script-development/syriac
//  We implement this in a generic way, such that the Arabic subtending
//  marks can use it as well.
//

func recordStch( *otShapePlan,  *Font,  *Buffer) bool {
	 := .shaper.(*complexShaperArabic).plan
	if !.hasStch {
		return false
	}

	/* 'stch' feature was just applied.  Look for anything that multiplied,
	* and record it for stch treatment later.  Note that rtlm, frac, etc
	* are applied before stch, but we assume that they didn't result in
	* anything multiplying into 5 pieces, so it's safe-ish... */

	 := .Info
	for  := range  {
		if [].multiplied() {
			 := [].getLigComp()
			if %2 != 0 {
				[].complexAux = arabStchRepeating
			} else {
				[].complexAux = arabStchFixed
			}
			.scratchFlags |= flagArabicHasStch
		}
	}

	return false
}

func inRange( uint8) bool {
	return arabStchFixed <=  &&  <= arabStchRepeating
}

func ( *complexShaperArabic) ( *otShapePlan,  *Buffer,  *Font) {
	if .scratchFlags&flagArabicHasStch == 0 {
		return
	}

	 := .Props.Direction == RightToLeft
	if ! {
		.Reverse()
	}

	/* We do a two pass implementation:
	* First pass calculates the exact number of extra glyphs we need,
	* We then enlarge buffer to have that much room,
	* Second pass applies the stretch, copying things to the end of buffer. */

	 := Position(+1)
	if .XScale < 0 {
		 = -1
	}
	const (
		 = iota
		
	)
	var (
		       = len(.Info) // before enlarging
		 = 0                // Set during MEASURE, used during CUT
	)
	for  := ;  <= ; ++ {
		 := .Info
		 := .Pos
		 := len() // enlarged after MEASURE
		for  := ;  != 0; -- {
			if  := [-1].complexAux; !inRange() {
				if  ==  {
					--
					[] = [-1]
					[] = [-1]
				}
				continue
			}

			/* Yay, justification! */
			var (
				     Position // Total to be filled
				     Position // Sum of fixed tiles
				 Position // Sum of repeating tiles
				     = 0
				 = 0
			)
			 := 
			for  != 0 && inRange([-1].complexAux) {
				--
				 := .GlyphHAdvance([].Glyph)
				if [].complexAux == arabStchFixed {
					 += 
					++
				} else {
					 += 
					++
				}
			}
			 := 
			 := 
			for  != 0 && !inRange([-1].complexAux) &&
				((&[-1]).isDefaultIgnorable() ||
					isWord((&[-1]).unicode.generalCategory())) {
				--
				 += [].XAdvance
			}
			++ // Don't touch i again.

			if debugMode {
				fmt.Printf("ARABIC - step %d: stretch at (%d,%d,%d)\n", +1, , , )
				fmt.Printf("ARABIC - rest of word:    count=%d width %d\n", -, )
				fmt.Printf("ARABIC - fixed tiles:     count=%d width=%d\n", , )
				fmt.Printf("ARABIC - repeating tiles: count=%d width=%d\n", , )
			}

			// number of additional times to repeat each repeating tile.
			var  int

			 :=  - 
			if * > * && * > 0 {
				 = int((*)/(*) - 1)
			}

			// see if we can improve the fit by adding an extra repeat and squeezing them together a bit.
			var  Position
			 := * - **(Position()+1)
			if  > 0 &&  > 0 {
				++
				 := (Position()+1)** - *
				if  > 0 {
					 =  / Position(*)
					 = 0
				}
			}

			if  ==  {
				 +=  * 
				if debugMode {
					fmt.Printf("ARABIC - will add extra %d copies of repeating tiles\n", )
				}
			} else {
				.unsafeToBreak(, )
				 :=  / 2
				for  := ;  > ; -- {
					 := .GlyphHAdvance([-1].Glyph)

					 := 1
					if [-1].complexAux == arabStchRepeating {
						 += 
					}

					if debugMode {
						fmt.Printf("ARABIC - appending %d copies of glyph %d; j=%d\n", , [-1].codepoint, )
					}

					[-1].XAdvance = 0
					for  := 0;  < ; ++ {
						if  {
							 -= 
							if  > 0 {
								 += 
							}
						}
						[-1].XOffset = 
						// append copy.
						--
						[] = [-1]
						[] = [-1]

						if ! {
							 += 
							if  > 0 {
								 -= 
							}
						}
					}
				}
			}
		}

		if  ==  { // enlarge
			.Info = append(.Info, make([]GlyphInfo, )...)
			.Pos = append(.Pos, make([]GlyphPosition, )...)
		}
	}

	if ! {
		.Reverse()
	}
}

// https://www.unicode.org/reports/tr53/
var modifierCombiningMarks = [...]rune{
	0x0654, /* ARABIC HAMZA ABOVE */
	0x0655, /* ARABIC HAMZA BELOW */
	0x0658, /* ARABIC MARK NOON GHUNNA */
	0x06DC, /* ARABIC SMALL HIGH SEEN */
	0x06E3, /* ARABIC SMALL LOW SEEN */
	0x06E7, /* ARABIC SMALL HIGH YEH */
	0x06E8, /* ARABIC SMALL HIGH NOON */
	0x08CA, /* ARABIC SMALL HIGH FARSI YEH */
	0x08CB, /* ARABIC SMALL HIGH YEH BARREE WITH TWO DOTS BELOW */
	0x08CD, /* ARABIC SMALL HIGH ZAH */
	0x08CE, /* ARABIC LARGE ROUND DOT ABOVE */
	0x08CF, /* ARABIC LARGE ROUND DOT BELOW */
	0x08D3, /* ARABIC SMALL LOW WAW */
	0x08F3, /* ARABIC SMALL HIGH WAW */
}

func infoIsMcm( *GlyphInfo) bool {
	 := .codepoint
	for  := 0;  < len(modifierCombiningMarks); ++ {
		if  == modifierCombiningMarks[] {
			return true
		}
	}
	return false
}

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

	if debugMode {
		fmt.Printf("ARABIC - Reordering marks from %d to %d\n", , )
	}

	 := 
	for  := uint8(220);  <= 230;  += 10 {
		if debugMode {
			fmt.Printf("ARABIC - Looking for combining class %d's starting at %d\n", , )
		}
		for  <  && [].getModifiedCombiningClass() <  {
			++
		}
		if debugMode {
			fmt.Printf("ARABIC - Looking for %d's stopped at %d\n", , )
		}

		if  ==  {
			break
		}

		if [].getModifiedCombiningClass() >  {
			continue
		}

		 := 
		for  <  && [].getModifiedCombiningClass() ==  && infoIsMcm(&[]) {
			++
		}

		if  ==  {
			continue
		}

		if debugMode {
			fmt.Printf("ARABIC - Found %d's from %d to %d", , , )
			// shift it!
			fmt.Printf("ARABIC - Shifting %d's: %d %d", , , )
		}

		var  [shapeComplexMaxCombiningMarks]GlyphInfo
		//  assert (j - i <= len (temp));
		.mergeClusters(, )
		copy([:-], [:])
		copy([+-:], [:])
		copy([:], [:-])

		/* Renumber CC such that the reordered sequence is still sorted.
		 * 22 and 26 are chosen because they are smaller than all Arabic categories,
		 * and are folded back to 220/230 respectively during fallback mark positioning.
		 *
		 * We do this because the CGJ-handling logic in the normalizer relies on
		 * mark sequences having an increasing order even after this reordering.
		 * https://github.com/harfbuzz/harfbuzz/issues/554
		 * This, however, does break some obscure sequences, where the normalizer
		 * might compose a sequence that it should not.  For example, in the seequence
		 * ALEF, HAMZAH, MADDAH, we should NOT try to compose ALEF+MADDAH, but with this
		 * renumbering, we will. */
		 :=  +  - 
		 := mcc26
		if  == 220 {
			 = mcc26
		}
		for  <  {
			[].setModifiedCombiningClass()
			++
		}

		 = 
	}
}

// Features ordered the same as the entries in [arabicShaping] rows,
// followed by rlig.  Don't change.
// We currently support one subtable per lookup, and one lookup
// per feature.  But we allow duplicate features, so we use that!
var arabicFallbackFeatures = [...]ot.Tag{
	ot.NewTag('i', 'n', 'i', 't'),
	ot.NewTag('m', 'e', 'd', 'i'),
	ot.NewTag('f', 'i', 'n', 'a'),
	ot.NewTag('i', 's', 'o', 'l'),
	ot.NewTag('r', 'l', 'i', 'g'),
	ot.NewTag('r', 'l', 'i', 'g'),
	ot.NewTag('r', 'l', 'i', 'g'),
}

// used to sort both array at the same time
type jointGlyphs struct {
	glyphs, substitutes []gID
}

func ( jointGlyphs) () int { return len(.glyphs) }
func ( jointGlyphs) (,  int) {
	.glyphs[], .glyphs[] = .glyphs[], .glyphs[]
	.substitutes[], .substitutes[] = .substitutes[], .substitutes[]
}
func ( jointGlyphs) (,  int) bool { return .glyphs[] < .glyphs[] }

func arabicFallbackSynthesizeLookupSingle( *Font,  int) *lookupGSUB {
	var ,  []gID

	// populate arrays
	for  := rune(firstArabicShape);  <= lastArabicShape; ++ {
		 := rune(arabicShaping[-firstArabicShape][])
		,  := .face.NominalGlyph()
		,  := .face.NominalGlyph()

		if  == 0 || ! || ! ||  ==  ||  > 0xFFFF ||  > 0xFFFF {
			continue
		}

		 = append(, gID())
		 = append(, gID())
	}

	if len() == 0 {
		return nil
	}

	sort.Stable(jointGlyphs{glyphs: , substitutes: })

	return &lookupGSUB{
		LookupOptions: font.LookupOptions{Flag: otIgnoreMarks},
		Subtables: []tables.GSUBLookup{
			tables.SingleSubs{Data: tables.SingleSubstData2{
				Coverage:           tables.Coverage1{Glyphs: },
				SubstituteGlyphIDs: ,
			}},
		},
	}
}

// used to sort both array at the same time
type glyphsIndirections struct {
	glyphs       []gID
	indirections []int
}

func ( glyphsIndirections) () int { return len(.glyphs) }
func ( glyphsIndirections) (,  int) {
	.glyphs[], .glyphs[] = .glyphs[], .glyphs[]
	.indirections[], .indirections[] = .indirections[], .indirections[]
}
func ( glyphsIndirections) (,  int) bool { return .glyphs[] < .glyphs[] }

func arabicFallbackSynthesizeLookupLigature( *Font,  []arabicTableEntry,  uint16) *lookupGSUB {
	var (
		            []gID
		 []int // original index into ArabicLigatures
	)

	// Populate arrays

	// sort out the first-glyphs
	for ,  := range  {
		,  := .face.NominalGlyph(.First)
		if ! {
			continue
		}
		 = append(, gID())
		 = append(, )
	}

	if len() == 0 {
		return nil
	}

	sort.Stable(glyphsIndirections{glyphs: , indirections: })

	var  tables.LigatureSubs
	.Coverage = tables.Coverage1{Glyphs: }

	// now that the first-glyphs are sorted, walk again, populate ligatures.
	for ,  := range  {
		 := [].Ligatures
		var  tables.LigatureSet
		for ,  := range  {
			 := .ligature
			,  := .face.NominalGlyph()
			if ! {
				continue
			}

			 := .components
			var  []gID
			for ,  := range  {
				,  := .face.NominalGlyph()
				if ! {
					break
				}
				 = append(, gID())
			}

			if len() != len() {
				continue
			}

			.Ligatures = append(.Ligatures, tables.Ligature{
				LigatureGlyph:     gID(),
				ComponentGlyphIDs: , // ligatures are 2-component
			})
		}
		.LigatureSets = append(.LigatureSets, )
	}

	return &lookupGSUB{
		LookupOptions: font.LookupOptions{Flag: },
		Subtables: []tables.GSUBLookup{
			,
		},
	}
}

func arabicFallbackSynthesizeLookup( *Font,  int) *lookupGSUB {
	switch  {
	case 0, 1, 2, 3:
		return arabicFallbackSynthesizeLookupSingle(, )
	case 4:
		return arabicFallbackSynthesizeLookupLigature(, arabicLigature3Table[:], otIgnoreMarks)
	case 5:
		return arabicFallbackSynthesizeLookupLigature(, arabicLigatureTable[:], otIgnoreMarks)
	case 6:
		return arabicFallbackSynthesizeLookupLigature(, arabicLigatureMarkTable[:], 0)
	default:
		panic("unexpected arabic fallback feature index")
	}
}

const arabicFallbackMaxLookups = len(arabicFallbackFeatures)

type arabicFallbackPlan struct {
	accelArray [arabicFallbackMaxLookups]otLayoutLookupAccelerator
	numLookups int
	maskArray  [arabicFallbackMaxLookups]GlyphMask
}

func ( *arabicFallbackPlan) ( *otShapePlan,  *Font) bool {
	// does this font look like it's Windows-1256-encoded?
	,  := .face.NominalGlyph(0x0627) /* ALEF */
	,  := .face.NominalGlyph(0x0644) /* LAM */
	,  := .face.NominalGlyph(0x0649) /* ALEF MAKSURA */
	,  := .face.NominalGlyph(0x064A) /* YEH */
	,  := .face.NominalGlyph(0x0652) /* SUKUN */
	if !( == 199 &&  == 225 &&  == 236 &&  == 237 &&  == 250) {
		return false
	}

	var  int
	for ,  := range arabicWin1256GsubLookups {
		.maskArray[] = .otMap.getMask1(.tag)
		if .maskArray[] != 0 {
			if .lookup != nil {
				.accelArray[].init(*.lookup)
				++
			}
		}
	}

	.numLookups = 

	return  > 0
}

func ( *arabicFallbackPlan) ( *otShapePlan,  *Font) bool {
	var  int
	for ,  := range arabicFallbackFeatures {
		.maskArray[] = .otMap.getMask1()
		if .maskArray[] != 0 {
			 := arabicFallbackSynthesizeLookup(, )
			if  != nil {
				.accelArray[].init(*)
				++
			}
		}
	}

	.numLookups = 

	return  > 0
}

func newArabicFallbackPlan( *otShapePlan,  *Font) *arabicFallbackPlan {
	var  arabicFallbackPlan

	/* Try synthesizing GSUB table using Unicode Arabic Presentation Forms,
	* in case the font has cmap entries for the presentation-forms characters. */
	if .initUnicode(, ) {
		return &
	}

	/* See if this looks like a Windows-1256-encoded font. If it does, use a
	* hand-coded GSUB table. */
	if .initWin1256(, ) {
		return &
	}

	return &arabicFallbackPlan{}
}

func ( *arabicFallbackPlan) ( *Font,  *Buffer) {
	var  otApplyContext
	.reset(0, , )
	for  := 0;  < .numLookups; ++ {
		if .accelArray[].lookup != nil {
			.setLookupMask(.maskArray[])
			.substituteLookup(&.accelArray[])
		}
	}
}