package harfbuzz

import (
	

	
	
)

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

var _ layoutLookup = lookupGPOS{}

// implements layoutLookup
type lookupGPOS font.GPOSLookup

func ( lookupGPOS) () uint32 { return .LookupOptions.Props() }

func ( lookupGPOS) ( *setDigest) {
	for ,  := range .Subtables {
		.collectCoverage(.Cov())
	}
}

func ( lookupGPOS) ( *getSubtablesContext) {
	for ,  := range .Subtables {
		* = append(*, newGPOSApplicable())
	}
}

func ( lookupGPOS) ( *otApplyContext) bool {
	for ,  := range .Subtables {
		if .applyGPOS() {
			return true
		}
	}
	return false
}

func (lookupGPOS) () bool { return false }

// attach_type_t
const (
	attachTypeNone = 0x00

	/* Each attachment should be either a mark or a cursive; can't be both. */
	attachTypeMark    = 0x01
	attachTypeCursive = 0x02
)

func positionStartGPOS( *Buffer) {
	for  := range .Pos {
		.Pos[].attachChain = 0
		.Pos[].attachType = 0
	}
}

func propagateAttachmentOffsets( []GlyphPosition,  int,  Direction) {
	/* Adjusts offsets of attached glyphs (both cursive and mark) to accumulate
	 * offset of glyph they are attached to. */
	,  := [].attachChain, [].attachType

	[].attachChain = 0

	 :=  + int()

	if  >= len() {
		return
	}

	if [].attachChain != 0 {
		(, , )
	}

	//   assert (!!(type_ & attachTypeMark) ^ !!(type_ & attachTypeCursive));

	if ( & attachTypeCursive) != 0 {
		if .isHorizontal() {
			[].YOffset += [].YOffset
		} else {
			[].XOffset += [].XOffset
		}
	} else /*if (type_ & attachTypeMark)*/ {
		[].XOffset += [].XOffset
		[].YOffset += [].YOffset

		// i is the position of the mark; j is the base.
		if  <  {
			/* This is the common case: mark follows base.
			 * And currently the only way in OpenType. */
			if .isForward() {
				for ,  := range [:] {
					[].XOffset -= .XAdvance
					[].YOffset -= .YAdvance
				}
			} else {
				for ,  := range [+1 : +1] {
					[].XOffset += .XAdvance
					[].YOffset += .YAdvance
				}
			}
		} else { // j > i
			/* This can happen with `kerx`: a mark attaching
			 * to a base after it in the logical order. */
			if .isForward() {
				for  := ;  < ; ++ {
					[].XOffset += [].XAdvance
					[].YOffset += [].YAdvance
				}
			} else {
				for  :=  + 1;  < +1; ++ {
					[].XOffset -= [].XAdvance
					[].YOffset -= [].YAdvance
				}
			}
		}
	}
}

func positionFinishOffsetsGPOS( *Font,  *Buffer) {
	 := .Pos
	 := .Props.Direction

	/* Handle attachments */
	if .scratchFlags&bsfHasGPOSAttachment != 0 {
		if debugMode {
			fmt.Println("POSITION - handling attachments")
		}

		// https://github.com/harfbuzz/harfbuzz/issues/5514
		if .isForward() {
			for  := range  {
				if [].attachChain != 0 {
					propagateAttachmentOffsets(, , )
				}
			}
		} else {
			for  := len() - 1;  >= 0; -- {
				if [].attachChain != 0 {
					propagateAttachmentOffsets(, , )
				}
			}
		}
	}

	if .slant != 0 && .isHorizontal() {
		 := .slant * float32(.XScale) / float32(.YScale)

		/* Slanting shaping results is only supported for horizontal text,
		 * as it gets weird otherwise. */
		for ,  := range .Pos {
			if .YOffset != 0 {
				.Pos[].XOffset += roundf( * float32(.YOffset))
			}
		}
	}
}

func applyRecurseGPOS( *otApplyContext,  uint16) bool {
	 := .font.face.GPOS
	 := lookupGPOS(.Lookups[])
	return .applyRecurseLookup(, )
}

// return `true` is the positionning found a match and was applied
func ( *otApplyContext) ( tables.GPOSLookup) bool {
	 := .buffer
	 := .cur(0).Glyph
	 := .curPos(0)
	,  := .Cov().Index(gID())
	if ! {
		return false
	}

	if debugMode {
		fmt.Printf("\t\tAPPLY - type %T at index %d\n", , .buffer.idx)
	}

	switch data := .(type) {
	case tables.SinglePos:
		switch inner := .Data.(type) {
		case tables.SinglePosData1:
			.applyGPOSValueRecord(.ValueFormat, .ValueRecord, )
		case tables.SinglePosData2:
			.applyGPOSValueRecord(.ValueFormat, .ValueRecords[], )
		}
		.idx++
	case tables.PairPos:
		 := &.iterInput
		.resetFast(.idx)
		if ,  := .next(); ! {
			.unsafeToConcat(.idx, )
			return false
		}
		switch inner := .Data.(type) {
		case tables.PairPosData1:
			return .applyGPOSPair1(, )
		case tables.PairPosData2:
			return .applyGPOSPair2()
		}

	case tables.CursivePos:
		return .applyGPOSCursive(, )
	case tables.MarkBasePos:
		return .applyGPOSMarkToBase(, )
	case tables.MarkLigPos:
		return .applyGPOSMarkToLigature(, )
	case tables.MarkMarkPos:
		return .applyGPOSMarkToMark(, )

	case tables.ContextualPos:
		switch inner := .Data.(type) {
		case tables.ContextualPos1:
			return .applyLookupContext1(tables.SequenceContextFormat1(), )
		case tables.ContextualPos2:
			return .applyLookupContext2(tables.SequenceContextFormat2(), , )
		case tables.ContextualPos3:
			return .applyLookupContext3(tables.SequenceContextFormat3(), )
		}

	case tables.ChainedContextualPos:
		switch inner := .Data.(type) {
		case tables.ChainedContextualPos1:
			return .applyLookupChainedContext1(tables.ChainedSequenceContextFormat1(), )
		case tables.ChainedContextualPos2:
			return .applyLookupChainedContext2(tables.ChainedSequenceContextFormat2(), , )
		case tables.ChainedContextualPos3:
			return .applyLookupChainedContext3(tables.ChainedSequenceContextFormat3(), )
		}
	}
	return true
}

func ( *otApplyContext) ( tables.ValueFormat,  tables.ValueRecord,  *GlyphPosition) bool {
	var  bool
	if  == 0 {
		return 
	}

	 := .font
	 := .direction.isHorizontal()

	if &tables.XPlacement != 0 {
		.XOffset += .emScaleX(.XPlacement)
		 =  || .XPlacement != 0
	}
	if &tables.YPlacement != 0 {
		.YOffset += .emScaleY(.YPlacement)
		 =  || .YPlacement != 0
	}
	if &tables.XAdvance != 0 {
		if  {
			.XAdvance += .emScaleX(.XAdvance)
			 =  || .XAdvance != 0
		}
	}
	/* YAdvance values grow downward but font-space grows upward, hence negation */
	if &tables.YAdvance != 0 {
		if ! {
			.YAdvance -= .emScaleY(.YAdvance)
			 =  || .YAdvance != 0
		}
	}

	if &tables.Devices == 0 {
		return 
	}

	,  := .face.Ppem()
	 :=  != 0 || len(.varCoords()) != 0
	 :=  != 0 || len(.varCoords()) != 0

	if ! && ! {
		return 
	}

	if &tables.XPlaDevice != 0 &&  {
		.XOffset += .getXDelta(.varStore, .XPlaDevice)
		 =  || .XPlaDevice != nil
	}
	if &tables.YPlaDevice != 0 &&  {
		.YOffset += .getYDelta(.varStore, .YPlaDevice)
		 =  || .YPlaDevice != nil
	}
	if &tables.XAdvDevice != 0 &&  &&  {
		.XAdvance += .getXDelta(.varStore, .XAdvDevice)
		 =  || .XAdvDevice != nil
	}
	if &tables.YAdvDevice != 0 && ! &&  {
		/* YAdvance values grow downward but font-space grows upward, hence negation */
		.YAdvance -= .getYDelta(.varStore, .YAdvDevice)
		 =  || .YAdvDevice != nil
	}
	return 
}

func reverseCursiveMinorOffset( []GlyphPosition,  int,  Direction,  int) {
	,  := [].attachChain, [].attachType
	if  == 0 || &attachTypeCursive == 0 {
		return
	}

	[].attachChain = 0

	 :=  + int()

	// stop if we see new parent in the chain
	if  ==  {
		return
	}
	(, , , )

	if .isHorizontal() {
		[].YOffset = -[].YOffset
	} else {
		[].XOffset = -[].XOffset
	}

	[].attachChain = -
	[].attachType = 
}

func ( *otApplyContext) ( tables.PairPosData1,  int) bool {
	 := .PairSets[]
	 := .iterInput.idx

	 := .buffer
	,  := .FindGlyph(gID(.Info[].Glyph))
	if ! {
		.unsafeToConcat(.idx, +1)
		return false
	}

	 := .applyGPOSValueRecord(.ValueFormat1, .ValueRecord1, .curPos(0))
	 := .applyGPOSValueRecord(.ValueFormat2, .ValueRecord2, &.Pos[])

	if  ||  {
		.unsafeToBreak(.idx, +1)
	}

	if .ValueFormat2 != 0 {
		// https://github.com/harfbuzz/harfbuzz/issues/3824
		// https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
		++
		.unsafeToBreak(.idx, +1)
	}
	.idx = 
	return true
}

func ( *otApplyContext) ( tables.PairPosData2) bool {
	 := .buffer
	 := &.iterInput

	 := .cur(0).Glyph
	,  := .ClassDef2.Class(gID(.Info[.idx].Glyph))
	if ! {
		.unsafeToConcat(.idx, .idx+1)
		return false
	}

	,  := .ClassDef1.Class(gID())
	 := .Record(, )

	 := .applyGPOSValueRecord(.ValueFormat1, .ValueRecord1, .curPos(0))
	 := .applyGPOSValueRecord(.ValueFormat2, .ValueRecord2, &.Pos[.idx])

	if  ||  {
		.unsafeToBreak(.idx, .idx+1)
	} else {
		.unsafeToConcat(.idx, .idx+1)
	}

	if .ValueFormat2 != 0 {
		// https://github.com/harfbuzz/harfbuzz/issues/3824
		// https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
		.idx++
		.unsafeToBreak(.idx, .idx+1)
	}
	.idx = .idx
	return true
}

func ( *otApplyContext) ( tables.CursivePos,  int) bool {
	 := .buffer

	 := .EntryExits[]
	if .EntryAnchor == nil {
		return false
	}

	 := &.iterInput
	.resetFast(.idx)
	if ,  := .prev(); ! {
		.unsafeToConcatFromOutbuffer(, .idx+1)
		return false
	}

	,  := .Cov().Index(gID(.Info[.idx].Glyph))
	if ! {
		.unsafeToConcatFromOutbuffer(.idx, .idx+1)
		return false
	}
	 := .EntryExits[]
	if .ExitAnchor == nil {
		.unsafeToConcatFromOutbuffer(.idx, .idx+1)
		return false
	}

	 := .idx
	 := .idx

	.unsafeToBreak(, +1)
	,  := .getAnchor(.ExitAnchor, .Info[].Glyph)
	,  := .getAnchor(.EntryAnchor, .Info[].Glyph)

	 := .Pos

	var  Position
	/* Main-direction adjustment */
	switch .direction {
	case LeftToRight:
		[].XAdvance = roundf() + [].XOffset

		 = roundf() + [].XOffset
		[].XAdvance -= 
		[].XOffset -= 
	case RightToLeft:
		 = roundf() + [].XOffset
		[].XAdvance -= 
		[].XOffset -= 

		[].XAdvance = roundf() + [].XOffset
	case TopToBottom:
		[].YAdvance = roundf() + [].YOffset

		 = roundf() + [].YOffset
		[].YAdvance -= 
		[].YOffset -= 
	case BottomToTop:
		 = roundf() + [].YOffset
		[].YAdvance -= 
		[].YOffset -= 

		[].YAdvance = roundf()
	}

	/* Cross-direction adjustment */

	/* We attach child to parent (think graph theory and rooted trees whereas
	 * the root stays on baseline and each node aligns itself against its
	 * parent.
	 *
	 * Optimize things for the case of RightToLeft, as that's most common in
	 * Arabic. */
	 := 
	 := 
	 := roundf( - )
	 := roundf( - )
	if uint16(.lookupProps)&otRightToLeft == 0 {
		 := 
		 = 
		 = 
		 = -
		 = -
	}

	/* If child was already connected to someone else, walk through its old
	 * chain and reverse the link direction, such that the whole tree of its
	 * previous connection now attaches to new parent.  Watch out for case
	 * where new parent is on the path from old chain...
	 */
	reverseCursiveMinorOffset(, , .direction, )

	 :=  - 
	if int(int16()) != - { // handle overflow
		[].attachChain = 0
		.idx++
		return true
	}
	[].attachType = attachTypeCursive
	[].attachChain = int16()
	.scratchFlags |= bsfHasGPOSAttachment
	if .direction.isHorizontal() {
		[].YOffset = 
	} else {
		[].XOffset = 
	}

	/* If parent was attached to child, separate them.
	 * https://github.com/harfbuzz/harfbuzz/issues/2469 */
	if [].attachChain == -[].attachChain {
		[].attachChain = 0
		if .direction.isHorizontal() {
			[].YOffset = 0
		} else {
			[].XOffset = 0
		}
	}

	.idx++
	return true
}

// panic if anchor is nil
func ( *otApplyContext) ( tables.Anchor,  GID) (,  float32) {
	 := .font
	switch anchor := .(type) {
	case tables.AnchorFormat1:
		return .emFscaleX(.XCoordinate), .emFscaleY(.YCoordinate)
	case tables.AnchorFormat2:
		,  := .face.Ppem()
		var ,  Position
		 :=  != 0 ||  != 0
		if  {
			, ,  = .getGlyphContourPointForOrigin(, .AnchorPoint, LeftToRight)
		}
		if  &&  != 0 {
			 = float32()
		} else {
			 = .emFscaleX(.XCoordinate)
		}
		if  &&  != 0 {
			 = float32()
		} else {
			 = .emFscaleY(.YCoordinate)
		}
		return , 
	case tables.AnchorFormat3:
		,  := .face.Ppem()
		,  = .emFscaleX(.XCoordinate), .emFscaleY(.YCoordinate)
		if  != 0 || len(.varCoords()) != 0 {
			 += float32(.getXDelta(.varStore, .XDevice))
		}
		if  != 0 || len(.varCoords()) != 0 {
			 += float32(.getYDelta(.varStore, .YDevice))
		}
		return , 
	default:
		panic("exhaustive switch")
	}
}

func ( *otApplyContext) ( tables.MarkArray, ,  int,  tables.AnchorMatrix,  int) bool {
	 := .buffer
	 := .MarkRecords[].MarkClass
	 := .MarkAnchors[]

	 := .Anchor(, int())
	// If this subtable doesn't have an anchor for this base and this class,
	// return false such that the subsequent subtables have a chance at it.
	if  == nil {
		return false
	}

	.unsafeToBreak(, .idx+1)
	,  := .getAnchor(, .cur(0).Glyph)
	,  := .getAnchor(, .Info[].Glyph)

	 := .curPos(0)
	.XOffset = roundf( - )
	.YOffset = roundf( - )
	 :=  - .idx
	if int(int16()) !=  { // overflow
		.attachChain = 0
		.idx++
		return true
	}
	.attachType = attachTypeMark
	.attachChain = int16()
	.scratchFlags |= bsfHasGPOSAttachment

	.idx++
	return true
}

func ( *otApplyContext) ( tables.MarkBasePos,  int) bool {
	 := .buffer

	// Now we search backwards for a non-mark glyph.
	// We don't use skippy_iter.prev() to avoid O(n^2) behavior.

	 := &.iterInput
	.matcher.lookupProps = uint32(otIgnoreMarks)

	if .lastBaseUntil > .idx {
		.lastBaseUntil = 0
		.lastBase = -1
	}

	for  := .idx;  > .lastBaseUntil; -- {
		 := .match(&.Info[-1])
		if  == match {
			// https://github.com/harfbuzz/harfbuzz/issues/4124

			// We only want to attach to the first of a MultipleSubst sequence.
			// https://github.com/harfbuzz/harfbuzz/issues/740
			// Reject others...
			// ...but stop if we find a mark in the MultipleSubst sequence:
			// https://github.com/harfbuzz/harfbuzz/issues/1020
			 :=  - 1
			 := !.Info[].multiplied() || .Info[].getLigComp() == 0 ||
				 == 0 || .Info[-1].isMark() ||
				.Info[].getLigID() != .Info[-1].getLigID() ||
				.Info[].getLigComp() != .Info[-1].getLigComp()+1

			,  := .BaseCoverage.Index(gID(.Info[].Glyph))
			if ! && ! {
				 = skip
			}
		}
		if  == match {
			.lastBase =  - 1
			break
		}
	}

	.lastBaseUntil = .idx
	if .lastBase == -1 {
		.unsafeToConcatFromOutbuffer(0, .idx+1)
		return false
	}

	 := .lastBase
	,  := .BaseCoverage.Index(gID(.Info[].Glyph))
	if ! {
		.unsafeToConcatFromOutbuffer(, .idx+1)
		return false
	}

	return .applyGPOSMarks(.MarkArray, , , .BaseArray.Anchors(), )
}

func ( *otApplyContext) ( tables.MarkLigPos,  int) bool {
	 := .buffer

	// now we search backwards for a non-mark glyph
	 := &.iterInput
	.matcher.lookupProps = uint32(otIgnoreMarks)
	if .lastBaseUntil > .idx {
		.lastBaseUntil = 0
		.lastBase = -1
	}

	for  := .idx;  > .lastBaseUntil; -- {
		 := .match(&.Info[-1])
		if  == match {
			.lastBase =  - 1
			break
		}
	}
	.lastBaseUntil = .idx
	if .lastBase == -1 {
		.buffer.unsafeToConcatFromOutbuffer(0, .idx+1)
		return false
	}

	 := .lastBase
	,  := .LigatureCoverage.Index(gID(.Info[].Glyph))
	if ! {
		.buffer.unsafeToConcatFromOutbuffer(, .buffer.idx+1)
		return false
	}

	 := .LigatureArray.LigatureAttachs[].Anchors()

	// Find component to attach to
	 := .Len()
	if  == 0 {
		return false
	}

	// We must now check whether the ligature ID of the current mark glyph
	// is identical to the ligature ID of the found ligature.  If yes, we
	// can directly use the component index.  If not, we attach the mark
	// glyph to the last component of the ligature.
	 := .Info[].getLigID()
	 := .cur(0).getLigID()
	 := .cur(0).getLigComp()
	 :=  - 1
	if  != 0 &&  ==  &&  > 0 {
		 = min(, int(.cur(0).getLigComp())) - 1
	}

	return .applyGPOSMarks(.MarkArray, , , , )
}

func ( *otApplyContext) ( tables.MarkMarkPos,  int) bool {
	 := .buffer

	// now we search backwards for a suitable mark glyph until a non-mark glyph
	 := &.iterInput
	.resetFast(.idx)
	.matcher.lookupProps = .lookupProps &^ uint32(ignoreFlags)
	if ,  := .prev(); ! {
		return false
	}

	if !.Info[.idx].isMark() {
		return false
	}

	 := .idx

	 := .cur(0).getLigID()
	 := .Info[].getLigID()
	 := .cur(0).getLigComp()
	 := .Info[].getLigComp()

	if  ==  {
		if  == 0 { /* Marks belonging to the same base. */
			goto 
		} else if  ==  { /* Marks belonging to the same ligature component. */
			goto 
		}
	} else {
		/* If ligature ids don't match, it may be the case that one of the marks
		* itself is a ligature.  In which case match. */
		if ( > 0 &&  == 0) || ( > 0 &&  == 0) {
			goto 
		}
	}

	/* Didn't match. */
	return false

:
	,  := .Mark2Coverage.Index(gID(.Info[].Glyph))
	if ! {
		return false
	}

	return .applyGPOSMarks(.Mark1Array, , , .Mark2Array.Anchors(), )
}