package harfbuzz

import (
	

	
	ot 
	
	ucd 
)

// Support functions for OpenType shaping related queries.
// ported from src/hb-ot-shape.cc Copyright © 2009,2010  Red Hat, Inc. 2010,2011,2012  Google, Inc. Behdad Esfahbod

/*
 * GSUB/GPOS feature query and enumeration interface
 */

const (
	// Special value for script index indicating unsupported script.
	NoScriptIndex = 0xFFFF
	// Special value for feature index indicating unsupported feature.
	NoFeatureIndex = 0xFFFF
	// Special value for language index indicating default or unsupported language.
	DefaultLanguageIndex = 0xFFFF
	// Special value for variations index indicating unsupported variation.
	noVariationsIndex = -1
)

type otShapePlanner struct {
	shaper                 otComplexShaper
	props                  SegmentProperties
	tables                 *font.Font // also used by the map builders
	otMap                  otMapBuilder
	aatMap                 aatMapBuilder
	applyMorx              bool
	scriptZeroMarks        bool
	scriptFallbackPosition bool
}

func newOtShapePlanner( *font.Font,  SegmentProperties) *otShapePlanner {
	var  otShapePlanner
	.props = 
	.tables = 
	.otMap = newOtMapBuilder(, )
	.aatMap = newAatMapBuilder(, )
	/* https://github.com/harfbuzz/harfbuzz/issues/2124 */
	.applyMorx = len(.Morx) != 0 && (.Direction.isHorizontal() || len(.GSUB.Lookups) == 0)

	.shaper = categorizeComplex(.Script, .Direction, .otMap.chosenScript[0])

	,  := .shaper.marksBehavior()
	.scriptZeroMarks =  != zeroWidthMarksNone
	.scriptFallbackPosition = 

	/* https://github.com/harfbuzz/harfbuzz/issues/1528 */
	if ,  := .shaper.(complexShaperDefault); .applyMorx && ! {
		.shaper = complexShaperDefault{dumb: true}
	}
	return &
}

func ( *otShapePlanner) ( *otShapePlan,  otShapePlanKey) {
	.props = .props
	.shaper = .shaper
	.otMap.compile(&.otMap, )
	if .applyMorx {
		.aatMap.compile(&.aatMap)
	}

	.fracMask = .otMap.getMask1(ot.NewTag('f', 'r', 'a', 'c'))
	.numrMask = .otMap.getMask1(ot.NewTag('n', 'u', 'm', 'r'))
	.dnomMask = .otMap.getMask1(ot.NewTag('d', 'n', 'o', 'm'))
	.hasFrac = .fracMask != 0 || (.numrMask != 0 && .dnomMask != 0)

	.rtlmMask = .otMap.getMask1(ot.NewTag('r', 't', 'l', 'm'))
	.hasVert = .otMap.getMask1(ot.NewTag('v', 'e', 'r', 't')) != 0

	 := ot.NewTag('v', 'k', 'r', 'n')
	if .props.Direction.isHorizontal() {
		 = ot.NewTag('k', 'e', 'r', 'n')
	}

	.kernMask, _ = .otMap.getMask()
	.requestedKerning = .kernMask != 0

	 := .otMap.getFeatureIndex(1, ) != NoFeatureIndex
	 := .shaper.gposTag() != 0 && .shaper.gposTag() != .otMap.chosenScript[1]

	// Decide who provides glyph classes. GDEF or Unicode.
	if .tables.GDEF.GlyphClassDef == nil {
		.fallbackGlyphClasses = true
	}

	// Decide who does substitutions. GSUB, morx, or fallback.
	.applyMorx = .applyMorx

	//  Decide who does positioning. GPOS, kerx, kern, or fallback.
	 := .tables.Kerx != nil
	 := !.applyMorx && .tables.GSUB.Lookups != nil
	 := ! && .tables.GPOS.Lookups != nil

	if  && !( && ) {
		.applyKerx = true
	} else if  {
		.applyGpos = true
	}

	if !.applyKerx && (! || !.applyGpos) {
		// apparently Apple applies kerx if GPOS kern was not applied.
		if  {
			.applyKerx = true
		} else if .tables.Kern != nil {
			.applyKern = .scriptFallbackPosition // Not all shapers apply legacy `kern`
		}
	}

	.applyFallbackKern = .scriptFallbackPosition && !(.applyGpos || .applyKerx || .applyKern)

	.zeroMarks = .scriptZeroMarks && !.applyKerx &&
		(!.applyKern || !hasMachineKerning(.tables.Kern))
	.hasGposMark = .otMap.getMask1(ot.NewTag('m', 'a', 'r', 'k')) != 0

	.adjustMarkPositioningWhenZeroing = !.applyGpos && !.applyKerx &&
		(!.applyKern || !hasCrossKerning(.tables.Kern))

	.fallbackMarkPositioning = .adjustMarkPositioningWhenZeroing && .scriptFallbackPosition

	// If we're using morx shaping, we cancel mark position adjustment because
	// Apple Color Emoji assumes this will NOT be done when forming emoji sequences;
	// https://github.com/harfbuzz/harfbuzz/issues/2967.
	if .applyMorx {
		.adjustMarkPositioningWhenZeroing = false
	}

	// According to Ned, trak is applied by default for "modern fonts", as detected by presence of STAT table.
	.applyTrak = !.tables.Trak.IsEmpty() && .tables.STAT != nil
}

type otShapePlan struct {
	shaper otComplexShaper
	props  SegmentProperties

	otMap  otMap
	aatMap aatMap

	fracMask GlyphMask
	numrMask GlyphMask
	dnomMask GlyphMask
	rtlmMask GlyphMask
	kernMask GlyphMask

	hasFrac                          bool
	requestedKerning                 bool
	hasVert                          bool
	hasGposMark                      bool
	zeroMarks                        bool
	fallbackGlyphClasses             bool
	fallbackMarkPositioning          bool
	adjustMarkPositioningWhenZeroing bool

	applyGpos         bool
	applyFallbackKern bool
	applyKern         bool
	applyKerx         bool
	applyMorx         bool
	applyTrak         bool
}

func ( *otShapePlan) ( *font.Font,  SegmentProperties,  []Feature,  otShapePlanKey) {
	 := newOtShapePlanner(, )

	.collectFeatures()

	.compile(, )

	.shaper.dataCreate()
}

func ( *otShapePlan) ( *Font,  *Buffer) {
	.otMap.substitute(, , )
}

func ( *otShapePlan) ( *Font,  *Buffer) {
	if .applyGpos {
		.otMap.position(, , )
	} else if .applyKerx {
		.aatLayoutPosition(, )
	}

	if .applyKern {
		.otLayoutKern(, )
	} else if .applyFallbackKern {
		.otApplyFallbackKern(, )
	}

	if .applyTrak {
		.aatLayoutTrack(, )
	}
}

var (
	commonFeatures = [...]otMapFeature{
		{ot.NewTag('a', 'b', 'v', 'm'), ffGLOBAL},
		{ot.NewTag('b', 'l', 'w', 'm'), ffGLOBAL},
		{ot.NewTag('c', 'c', 'm', 'p'), ffGLOBAL},
		{ot.NewTag('l', 'o', 'c', 'l'), ffGLOBAL},
		{ot.NewTag('m', 'a', 'r', 'k'), ffGlobalManualJoiners},
		{ot.NewTag('m', 'k', 'm', 'k'), ffGlobalManualJoiners},
		{ot.NewTag('r', 'l', 'i', 'g'), ffGLOBAL},
	}

	horizontalFeatures = [...]otMapFeature{
		{ot.NewTag('c', 'a', 'l', 't'), ffGLOBAL},
		{ot.NewTag('c', 'l', 'i', 'g'), ffGLOBAL},
		{ot.NewTag('c', 'u', 'r', 's'), ffGLOBAL},
		{ot.NewTag('d', 'i', 's', 't'), ffGLOBAL},
		{ot.NewTag('k', 'e', 'r', 'n'), ffGlobalHasFallback},
		{ot.NewTag('l', 'i', 'g', 'a'), ffGLOBAL},
		{ot.NewTag('r', 'c', 'l', 't'), ffGLOBAL},
	}
)

func ( *otShapePlanner) ( []Feature) {
	 := &.otMap

	.enableFeature(ot.NewTag('r', 'v', 'r', 'n'))
	.addGSUBPause(nil)

	switch .props.Direction {
	case LeftToRight:
		.enableFeature(ot.NewTag('l', 't', 'r', 'a'))
		.enableFeature(ot.NewTag('l', 't', 'r', 'm'))
	case RightToLeft:
		.enableFeature(ot.NewTag('r', 't', 'l', 'a'))
		.addFeature(ot.NewTag('r', 't', 'l', 'm'))
	}

	/* Automatic fractions. */
	.addFeature(ot.NewTag('f', 'r', 'a', 'c'))
	.addFeature(ot.NewTag('n', 'u', 'm', 'r'))
	.addFeature(ot.NewTag('d', 'n', 'o', 'm'))

	/* Random! */
	.enableFeatureExt(ot.NewTag('r', 'a', 'n', 'd'), ffRandom, otMapMaxValue)

	.enableFeature(ot.NewTag('H', 'a', 'r', 'f')) /* Considered required. */
	.enableFeature(ot.NewTag('H', 'A', 'R', 'F')) /* Considered discretionary. */

	.shaper.collectFeatures()

	.enableFeature(ot.NewTag('B', 'u', 'z', 'z')) /* Considered required. */
	.enableFeature(ot.NewTag('B', 'U', 'Z', 'Z')) /* Considered discretionary. */

	for ,  := range commonFeatures {
		.addFeatureExt(.tag, .flags, 1)
	}

	if .props.Direction.isHorizontal() {
		for ,  := range horizontalFeatures {
			.addFeatureExt(.tag, .flags, 1)
		}
	} else {
		/* We really want to find a 'vert' feature if there's any in the font, no
		 * matter which script/langsys it is listed (or not) under.
		 * See various bugs referenced from:
		 * https://github.com/harfbuzz/harfbuzz/issues/63 */
		.enableFeatureExt(ot.NewTag('v', 'e', 'r', 't'), ffGlobalSearch, 1)
	}

	for ,  := range  {
		 := ffNone
		if .Start == FeatureGlobalStart && .End == FeatureGlobalEnd {
			 = ffGLOBAL
		}
		.addFeatureExt(.Tag, , .Value)
	}

	.shaper.overrideFeatures()
}

/*
 * shaper
 */

type otContext struct {
	plan         *otShapePlan
	font         *Font
	buffer       *Buffer
	userFeatures []Feature

	// transient stuff
	targetDirection Direction
}

/* Main shaper */

/*
 * Substitute
 */

func vertCharFor( rune) rune {
	switch  >> 8 {
	case 0x20:
		switch  {
		case 0x2013:
			return 0xfe32 // EN DASH
		case 0x2014:
			return 0xfe31 // EM DASH
		case 0x2025:
			return 0xfe30 // TWO DOT LEADER
		case 0x2026:
			return 0xfe19 // HORIZONTAL ELLIPSIS
		}
	case 0x30:
		switch  {
		case 0x3001:
			return 0xfe11 // IDEOGRAPHIC COMMA
		case 0x3002:
			return 0xfe12 // IDEOGRAPHIC FULL STOP
		case 0x3008:
			return 0xfe3f // LEFT ANGLE BRACKET
		case 0x3009:
			return 0xfe40 // RIGHT ANGLE BRACKET
		case 0x300a:
			return 0xfe3d // LEFT DOUBLE ANGLE BRACKET
		case 0x300b:
			return 0xfe3e // RIGHT DOUBLE ANGLE BRACKET
		case 0x300c:
			return 0xfe41 // LEFT CORNER BRACKET
		case 0x300d:
			return 0xfe42 // RIGHT CORNER BRACKET
		case 0x300e:
			return 0xfe43 // LEFT WHITE CORNER BRACKET
		case 0x300f:
			return 0xfe44 // RIGHT WHITE CORNER BRACKET
		case 0x3010:
			return 0xfe3b // LEFT BLACK LENTICULAR BRACKET
		case 0x3011:
			return 0xfe3c // RIGHT BLACK LENTICULAR BRACKET
		case 0x3014:
			return 0xfe39 // LEFT TORTOISE SHELL BRACKET
		case 0x3015:
			return 0xfe3a // RIGHT TORTOISE SHELL BRACKET
		case 0x3016:
			return 0xfe17 // LEFT WHITE LENTICULAR BRACKET
		case 0x3017:
			return 0xfe18 // RIGHT WHITE LENTICULAR BRACKET
		}
	case 0xfe:
		switch  {
		case 0xfe4f:
			return 0xfe34 // WAVY LOW LINE
		}
	case 0xff:
		switch  {
		case 0xff01:
			return 0xfe15 // FULLWIDTH EXCLAMATION MARK
		case 0xff08:
			return 0xfe35 // FULLWIDTH LEFT PARENTHESIS
		case 0xff09:
			return 0xfe36 // FULLWIDTH RIGHT PARENTHESIS
		case 0xff0c:
			return 0xfe10 // FULLWIDTH COMMA
		case 0xff1a:
			return 0xfe13 // FULLWIDTH COLON
		case 0xff1b:
			return 0xfe14 // FULLWIDTH SEMICOLON
		case 0xff1f:
			return 0xfe16 // FULLWIDTH QUESTION MARK
		case 0xff3b:
			return 0xfe47 // FULLWIDTH LEFT SQUARE BRACKET
		case 0xff3d:
			return 0xfe48 // FULLWIDTH RIGHT SQUARE BRACKET
		case 0xff3f:
			return 0xfe33 // FULLWIDTH LOW LINE
		case 0xff5b:
			return 0xfe37 // FULLWIDTH LEFT CURLY BRACKET
		case 0xff5d:
			return 0xfe38 // FULLWIDTH RIGHT CURLY BRACKET
		}
	}

	return 
}

func ( *otContext) () {
	 := .buffer.Info

	if .targetDirection.isBackward() {
		 := .plan.rtlmMask

		for  := range  {
			 := ucd.LookupMirrorChar([].codepoint)
			if  != [].codepoint && .font.hasGlyph() {
				[].codepoint = 
			} else {
				[].Mask |= 
			}
		}
	}

	if .targetDirection.isVertical() && !.plan.hasVert {
		for  := range  {
			 := vertCharFor([].codepoint)
			if  != [].codepoint && .font.hasGlyph() {
				[].codepoint = 
			}
		}
	}
}

func ( *otContext) () {
	if .buffer.scratchFlags&bsfHasFractionSlash == 0 || !.plan.hasFrac {
		return
	}

	 := .buffer

	var ,  GlyphMask
	if .Props.Direction.isForward() {
		 = .plan.numrMask | .plan.fracMask
		 = .plan.fracMask | .plan.dnomMask
	} else {
		 = .plan.fracMask | .plan.dnomMask
		 = .plan.numrMask | .plan.fracMask
	}

	 := len(.Info)
	 := .Info
	for  := 0;  < ; ++ {
		if [].codepoint == 0x2044 /* FRACTION SLASH */ {
			,  := , +1
			for  != 0 && [-1].unicode.generalCategory() == ucd.Nd {
				--
			}
			for  <  && [].unicode.generalCategory() == ucd.Nd {
				++
			}

			if  ==  ||  == +1 {
				if  ==  {
					.unsafeToConcat(, +1)
				}
				if  == +1 {
					.unsafeToConcat(-1, )
				}
				continue
			}

			.unsafeToBreak(, )

			for  := ;  < ; ++ {
				[].Mask |= 
			}
			[].Mask |= .plan.fracMask
			for  :=  + 1;  < ; ++ {
				[].Mask |= 
			}

			 =  - 1
		}
	}
}

func ( *otContext) () {
	.buffer.resetMasks(.plan.otMap.globalMask)
}

func ( *otContext) () {
	 := &.plan.otMap
	 := .buffer

	.setupMasksFraction()

	.plan.shaper.setupMasks(.plan, , .font)

	for ,  := range .userFeatures {
		if !(.Start == FeatureGlobalStart && .End == FeatureGlobalEnd) {
			,  := .getMask(.Tag)
			.setMasks(.Value<<, , .Start, .End)
		}
	}
}

func zeroWidthDefaultIgnorables( *Buffer) {
	if .scratchFlags&bsfHasDefaultIgnorables == 0 ||
		.Flags&PreserveDefaultIgnorables != 0 ||
		.Flags&RemoveDefaultIgnorables != 0 {
		return
	}

	 := .Pos
	for ,  := range .Info {
		if .isDefaultIgnorable() {
			[].XAdvance, [].YAdvance = 0, 0
			if .Props.Direction.isHorizontal() {
				[].XOffset = 0
			} else {
				[].YOffset = 0
			}
		}
	}
}

func dealWithVariationSelectors( *Buffer) {
	if (.scratchFlags&bsfHasVariationSelectorFallback == 0) ||
		.notFoundVariationSelector == 0xFFFFFFFF {
		return
	}

	 := .Info
	 := .Pos
	for  := range  {
		if [].isVariationSelector() {
			[].Glyph = .notFoundVariationSelector
			[].XAdvance, [].YAdvance, [].XOffset, [].YOffset = 0, 0, 0, 0
			[].setVariationSelector(false)
		}
	}
}

func hideDefaultIgnorables( *Buffer,  *Font) {
	if .scratchFlags&bsfHasDefaultIgnorables == 0 ||
		.Flags&PreserveDefaultIgnorables != 0 {
		return
	}

	 := .Info

	var (
		 = .Invisible
		        bool
	)
	if  == 0 {
		,  = .face.NominalGlyph(' ')
	}
	if .Flags&RemoveDefaultIgnorables == 0 &&  {
		// replace default-ignorables with a zero-advance invisible glyph.
		for  := range  {
			if [].isDefaultIgnorable() {
				[].Glyph = 
			}
		}
	} else {
		otLayoutDeleteGlyphsInplace(, (*GlyphInfo).isDefaultIgnorable)
	}
}

// use unicodeProp to assign a class
func synthesizeGlyphClasses( *Buffer) {
	 := .Info
	for  := range  {
		/* Never mark default-ignorables as marks.
		 * They won't get in the way of lookups anyway,
		 * but having them as mark will cause them to be skipped
		 * over if the lookup-flag says so, but at least for the
		 * Mongolian variation selectors, looks like Uniscribe
		 * marks them as non-mark.  Some Mongolian fonts without
		 * GDEF rely on this.  Another notable character that
		 * this applies to is COMBINING GRAPHEME JOINER. */
		 := tables.GPMark
		if [].unicode.generalCategory() != ucd.Mn || [].isDefaultIgnorable() {
			 = tables.GPBaseGlyph
		}

		[].glyphProps = 
	}
}

func ( *otContext) () {
	 := .buffer

	// substituteDefault : normalize and sets Glyph
	.otRotateChars()

	otShapeNormalize(.plan, , .font)

	.setupMasks()

	// this is unfortunate to go here, but necessary...
	if .plan.fallbackMarkPositioning {
		fallbackMarkPositionRecategorizeMarks()
	}

	if debugMode {
		fmt.Println("BEFORE SUBSTITUTE:", .buffer.Info)
	}

	// otSubstitutePlan : glyph fields are now set up ...
	// ... apply complex substitution from font

	layoutSubstituteStart(.font, )

	if .plan.fallbackGlyphClasses {
		synthesizeGlyphClasses(.buffer)
	}

	if .plan.applyMorx {
		.plan.aatLayoutSubstitute(.font, .buffer, .userFeatures)
		.buffer.updateDigest()
	} else {
		.buffer.updateDigest()
		.plan.substitute(.font, )
	}

	//
	if .plan.applyMorx && .plan.applyGpos {
		aatLayoutRemoveDeletedGlyphs()
	}
}

// port of hb_ot_substitute_post
func ( *otContext) () {
	if .plan.applyMorx && !.plan.applyGpos {
		aatLayoutRemoveDeletedGlyphs(.buffer)
	}

	dealWithVariationSelectors(.buffer)
	hideDefaultIgnorables(.buffer, .font)

	if debugMode {
		fmt.Println("POSTPROCESS glyphs start")
	}
	.plan.shaper.postprocessGlyphs(.plan, .buffer, .font)
	if debugMode {
		fmt.Println("POSTPROCESS glyphs end ")
	}
}

/*
 * Position
 */

func zeroMarkWidthsByGdef( *Buffer,  bool) {
	for ,  := range .Info {
		if .isMark() {
			 := &.Pos[]
			if  { // adjustMarkOffsets
				.XOffset -= .XAdvance
				.YOffset -= .YAdvance
			}
			// zeroMarkWidth
			.XAdvance = 0
			.YAdvance = 0
		}
	}
}

// override Pos array with default values
func ( *otContext) () {
	 := .buffer.Props.Direction
	 := .buffer.Info
	 := .buffer.Pos

	if .isHorizontal() {
		for ,  := range  {
			[].XAdvance, [].YAdvance = .font.GlyphHAdvance(.Glyph), 0
			[].XOffset, [].YOffset = .font.subtractGlyphHOrigin(.Glyph, 0, 0)
		}
	} else {
		for ,  := range  {
			[].XAdvance, [].YAdvance = 0, .font.getGlyphVAdvance(.Glyph)
			// v_origin defaults to non-zero; apply even if only fallback is there.
			[].XOffset, [].YOffset = .font.subtractGlyphVOrigin(.Glyph, 0, 0)
		}
	}
	if .buffer.scratchFlags&bsfHasSpaceFallback != 0 {
		fallbackSpaces(.font, .buffer)
	}
}

// hb_ot_position_plan
func ( *otContext) () {
	 := .buffer.Info
	 := .buffer.Pos

	/* If the font has no GPOS and direction is forward, then when
	* zeroing mark widths, we shift the mark with it, such that the
	* mark is positioned hanging over the previous glyph.  When
	* direction is backward we don't shift and it will end up
	* hanging over the next glyph after the final reordering.
	*
	* Note: If fallback positioning happens, we don't care about
	* this as it will be overridden. */
	 := .plan.adjustMarkPositioningWhenZeroing && .buffer.Props.Direction.isForward()

	// we change glyph origin to what GPOS expects (horizontal), apply GPOS, change it back.

	for ,  := range  {
		[].XOffset, [].YOffset = .font.addGlyphHOrigin(.Glyph, [].XOffset, [].YOffset)
	}

	otLayoutPositionStart(.font, .buffer)
	,  := .plan.shaper.marksBehavior()

	if .plan.zeroMarks {
		if  == zeroWidthMarksByGdefEarly {
			zeroMarkWidthsByGdef(.buffer, )
		}
	}

	.plan.position(.font, .buffer) // apply GPOS, AAT

	if .plan.zeroMarks {
		if  == zeroWidthMarksByGdefLate {
			zeroMarkWidthsByGdef(.buffer, )
		}
	}

	// finish off. Has to follow a certain order.
	zeroWidthDefaultIgnorables(.buffer)
	otLayoutPositionFinishOffsets(.font, .buffer)

	for ,  := range  {
		[].XOffset, [].YOffset = .font.subtractGlyphHOrigin(.Glyph, [].XOffset, [].YOffset)
	}

	if .plan.fallbackMarkPositioning {
		fallbackMarkPosition(.plan, .font, .buffer, )
	}
}

func ( *otContext) () {
	.buffer.clearPositions()

	.positionDefault()

	if debugMode {
		fmt.Println("AFTER DEFAULT POSITION", .buffer.Pos)
	}

	.positionComplex()

	if .buffer.Props.Direction.isBackward() {
		.buffer.Reverse()
	}
}

/* Propagate cluster-level glyph flags to be the same on all cluster glyphs.
 * Simplifies using them. */
func propagateFlags( *Buffer) {
	 := glyphFlagDefined
	if .Flags&ProduceUnsafeToConcat == 0 {
		 &= ^GlyphUnsafeToConcat
	}

	 := .Info

	if .Flags&ProduceSafeToInsertTatweel == 0 {
		,  := .clusterIterator()
		for ,  := .next();  < ; ,  = .next() {
			if - == 1 {
				[].Mask &= 
				continue
			}

			var  GlyphMask
			for  := ;  < ; ++ {
				 |= [].Mask
			}

			 &= 

			for  := ;  < ; ++ {
				[].Mask = 
			}
		}
		return
	}

	// If we are producing SAFE_TO_INSERT_TATWEEL, then do two things:
	//
	// - If the places that the Arabic shaper marked as SAFE_TO_INSERT_TATWEEL,
	//   are UNSAFE_TO_BREAK, then clear the SAFE_TO_INSERT_TATWEEL,
	// - Any place that is SAFE_TO_INSERT_TATWEEL, is also now UNSAFE_TO_BREAK.
	//
	// We couldn't make this interaction earlier. It has to be done this way.

	,  := .clusterIterator()
	for ,  := .next();  < ; ,  = .next() {
		var  GlyphMask
		for  := ;  < ; ++ {
			 |= [].Mask
		}

		if &GlyphUnsafeToBreak != 0 {
			 &= ^GlyphSafeToInsertTatweel
		}
		if &GlyphSafeToInsertTatweel != 0 {
			 |= GlyphUnsafeToBreak | GlyphUnsafeToConcat
		}

		 &= 

		for  := ;  < ; ++ {
			[].Mask = 
		}
	}
}

// shaperOpentype is the main shaper of this library.
// It handles complex language and Opentype layout features found in fonts.
type shaperOpentype struct {
	tables *font.Font
	plan   otShapePlan
	key    otShapePlanKey
}

type otShapePlanKey = [2]int // -1 for not found

func ( *shaperOpentype) ( *font.Font,  []tables.Coord) {
	.plan = otShapePlan{}
	.key = otShapePlanKey{
		0: .GSUB.FindVariationIndex(),
		1: .GPOS.FindVariationIndex(),
	}
	.tables = 
}

func ( *shaperOpentype) ( SegmentProperties,  []Feature) {
	.plan.init0(.tables, , , .key)
}

// pull it all together!
func ( *shaperOpentype) ( *Font,  *Buffer,  []Feature) {
	 := otContext{plan: &.plan, font: , buffer: , userFeatures: }
	.buffer.scratchFlags = bsfDefault

	const  = 64
	const  = 16384
	const  = 1024
	const  = 16384
	.buffer.maxOps = max(len(.buffer.Info)*, )
	.buffer.maxLen = max(len(.buffer.Info)*, )

	// save the original direction, we use it later.
	.targetDirection = .buffer.Props.Direction

	.initializeMasks()
	.buffer.setUnicodeProps()
	.buffer.insertDottedCircle(.font)

	.buffer.formClusters()

	if debugMode {
		fmt.Println("FORMING CLUSTER :", .buffer.Info)
	}

	.buffer.ensureNativeDirection()

	if debugMode {
		fmt.Printf("PREPROCESS text start\n")
	}
	.plan.shaper.preprocessText(.plan, .buffer, .font)
	if debugMode {
		fmt.Println("PREPROCESS text end:", .buffer.Info)
	}

	.substituteBeforePosition() // apply GSUB

	if debugMode {
		fmt.Println("AFTER SUBSTITUTE", .buffer.Info)
	}

	.position()

	if debugMode {
		fmt.Println("AFTER POSITION", .buffer.Pos)
	}

	.substituteAfterPosition()

	propagateFlags(.buffer)

	.buffer.Props.Direction = .targetDirection

	.buffer.maxOps = maxOpsDefault
}