package harfbuzz
import (
"fmt"
"github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/font/opentype/tables"
)
var _ layoutLookup = lookupGPOS {}
type lookupGPOS font .GPOSLookup
func (l lookupGPOS ) Props () uint32 { return l .LookupOptions .Props () }
func (l lookupGPOS ) collectCoverage (dst *setDigest ) {
for _ , table := range l .Subtables {
dst .collectCoverage (table .Cov ())
}
}
func (l lookupGPOS ) dispatchSubtables (ctx *getSubtablesContext ) {
for _ , table := range l .Subtables {
*ctx = append (*ctx , newGPOSApplicable (table ))
}
}
func (l lookupGPOS ) dispatchApply (ctx *otApplyContext ) bool {
for _ , table := range l .Subtables {
if ctx .applyGPOS (table ) {
return true
}
}
return false
}
func (lookupGPOS ) isReverse () bool { return false }
const (
attachTypeNone = 0x00
attachTypeMark = 0x01
attachTypeCursive = 0x02
)
func positionStartGPOS(buffer *Buffer ) {
for i := range buffer .Pos {
buffer .Pos [i ].attachChain = 0
buffer .Pos [i ].attachType = 0
}
}
func propagateAttachmentOffsets(pos []GlyphPosition , i int , direction Direction ) {
chain , type_ := pos [i ].attachChain , pos [i ].attachType
pos [i ].attachChain = 0
j := i + int (chain )
if j >= len (pos ) {
return
}
if pos [j ].attachChain != 0 {
propagateAttachmentOffsets (pos , j , direction )
}
if (type_ & attachTypeCursive ) != 0 {
if direction .isHorizontal () {
pos [i ].YOffset += pos [j ].YOffset
} else {
pos [i ].XOffset += pos [j ].XOffset
}
} else {
pos [i ].XOffset += pos [j ].XOffset
pos [i ].YOffset += pos [j ].YOffset
if j < i {
if direction .isForward () {
for _ , p := range pos [j :i ] {
pos [i ].XOffset -= p .XAdvance
pos [i ].YOffset -= p .YAdvance
}
} else {
for _ , p := range pos [j +1 : i +1 ] {
pos [i ].XOffset += p .XAdvance
pos [i ].YOffset += p .YAdvance
}
}
} else {
if direction .isForward () {
for k := i ; k < j ; k ++ {
pos [i ].XOffset += pos [k ].XAdvance
pos [i ].YOffset += pos [k ].YAdvance
}
} else {
for k := i + 1 ; k < j +1 ; k ++ {
pos [i ].XOffset -= pos [k ].XAdvance
pos [i ].YOffset -= pos [k ].YAdvance
}
}
}
}
}
func positionFinishOffsetsGPOS(font *Font , buffer *Buffer ) {
pos := buffer .Pos
direction := buffer .Props .Direction
if buffer .scratchFlags &bsfHasGPOSAttachment != 0 {
if debugMode {
fmt .Println ("POSITION - handling attachments" )
}
if direction .isForward () {
for i := range pos {
if pos [i ].attachChain != 0 {
propagateAttachmentOffsets (pos , i , direction )
}
}
} else {
for i := len (pos ) - 1 ; i >= 0 ; i -- {
if pos [i ].attachChain != 0 {
propagateAttachmentOffsets (pos , i , direction )
}
}
}
}
if font .slant != 0 && direction .isHorizontal () {
slantXY := font .slant * float32 (font .XScale ) / float32 (font .YScale )
for i , pos := range buffer .Pos {
if pos .YOffset != 0 {
buffer .Pos [i ].XOffset += roundf (slantXY * float32 (pos .YOffset ))
}
}
}
}
func applyRecurseGPOS(c *otApplyContext , lookupIndex uint16 ) bool {
gpos := c .font .face .GPOS
l := lookupGPOS (gpos .Lookups [lookupIndex ])
return c .applyRecurseLookup (lookupIndex , l )
}
func (c *otApplyContext ) applyGPOS (table tables .GPOSLookup ) bool {
buffer := c .buffer
glyphID := buffer .cur (0 ).Glyph
glyphPos := buffer .curPos (0 )
index , ok := table .Cov ().Index (gID (glyphID ))
if !ok {
return false
}
if debugMode {
fmt .Printf ("\t\tAPPLY - type %T at index %d\n" , table , c .buffer .idx )
}
switch data := table .(type ) {
case tables .SinglePos :
switch inner := data .Data .(type ) {
case tables .SinglePosData1 :
c .applyGPOSValueRecord (inner .ValueFormat , inner .ValueRecord , glyphPos )
case tables .SinglePosData2 :
c .applyGPOSValueRecord (inner .ValueFormat , inner .ValueRecords [index ], glyphPos )
}
buffer .idx ++
case tables .PairPos :
skippyIter := &c .iterInput
skippyIter .resetFast (buffer .idx )
if ok , unsafeTo := skippyIter .next (); !ok {
buffer .unsafeToConcat (buffer .idx , unsafeTo )
return false
}
switch inner := data .Data .(type ) {
case tables .PairPosData1 :
return c .applyGPOSPair1 (inner , index )
case tables .PairPosData2 :
return c .applyGPOSPair2 (inner )
}
case tables .CursivePos :
return c .applyGPOSCursive (data , index )
case tables .MarkBasePos :
return c .applyGPOSMarkToBase (data , index )
case tables .MarkLigPos :
return c .applyGPOSMarkToLigature (data , index )
case tables .MarkMarkPos :
return c .applyGPOSMarkToMark (data , index )
case tables .ContextualPos :
switch inner := data .Data .(type ) {
case tables .ContextualPos1 :
return c .applyLookupContext1 (tables .SequenceContextFormat1 (inner ), index )
case tables .ContextualPos2 :
return c .applyLookupContext2 (tables .SequenceContextFormat2 (inner ), index , glyphID )
case tables .ContextualPos3 :
return c .applyLookupContext3 (tables .SequenceContextFormat3 (inner ), index )
}
case tables .ChainedContextualPos :
switch inner := data .Data .(type ) {
case tables .ChainedContextualPos1 :
return c .applyLookupChainedContext1 (tables .ChainedSequenceContextFormat1 (inner ), index )
case tables .ChainedContextualPos2 :
return c .applyLookupChainedContext2 (tables .ChainedSequenceContextFormat2 (inner ), index , glyphID )
case tables .ChainedContextualPos3 :
return c .applyLookupChainedContext3 (tables .ChainedSequenceContextFormat3 (inner ), index )
}
}
return true
}
func (c *otApplyContext ) applyGPOSValueRecord (format tables .ValueFormat , v tables .ValueRecord , glyphPos *GlyphPosition ) bool {
var ret bool
if format == 0 {
return ret
}
font := c .font
horizontal := c .direction .isHorizontal ()
if format &tables .XPlacement != 0 {
glyphPos .XOffset += font .emScaleX (v .XPlacement )
ret = ret || v .XPlacement != 0
}
if format &tables .YPlacement != 0 {
glyphPos .YOffset += font .emScaleY (v .YPlacement )
ret = ret || v .YPlacement != 0
}
if format &tables .XAdvance != 0 {
if horizontal {
glyphPos .XAdvance += font .emScaleX (v .XAdvance )
ret = ret || v .XAdvance != 0
}
}
if format &tables .YAdvance != 0 {
if !horizontal {
glyphPos .YAdvance -= font .emScaleY (v .YAdvance )
ret = ret || v .YAdvance != 0
}
}
if format &tables .Devices == 0 {
return ret
}
xp , yp := font .face .Ppem ()
useXDevice := xp != 0 || len (font .varCoords ()) != 0
useYDevice := yp != 0 || len (font .varCoords ()) != 0
if !useXDevice && !useYDevice {
return ret
}
if format &tables .XPlaDevice != 0 && useXDevice {
glyphPos .XOffset += font .getXDelta (c .varStore , v .XPlaDevice )
ret = ret || v .XPlaDevice != nil
}
if format &tables .YPlaDevice != 0 && useYDevice {
glyphPos .YOffset += font .getYDelta (c .varStore , v .YPlaDevice )
ret = ret || v .YPlaDevice != nil
}
if format &tables .XAdvDevice != 0 && horizontal && useXDevice {
glyphPos .XAdvance += font .getXDelta (c .varStore , v .XAdvDevice )
ret = ret || v .XAdvDevice != nil
}
if format &tables .YAdvDevice != 0 && !horizontal && useYDevice {
glyphPos .YAdvance -= font .getYDelta (c .varStore , v .YAdvDevice )
ret = ret || v .YAdvDevice != nil
}
return ret
}
func reverseCursiveMinorOffset(pos []GlyphPosition , i int , direction Direction , newParent int ) {
chain , type_ := pos [i ].attachChain , pos [i ].attachType
if chain == 0 || type_ &attachTypeCursive == 0 {
return
}
pos [i ].attachChain = 0
j := i + int (chain )
if j == newParent {
return
}
reverseCursiveMinorOffset (pos , j , direction , newParent )
if direction .isHorizontal () {
pos [j ].YOffset = -pos [i ].YOffset
} else {
pos [j ].XOffset = -pos [i ].XOffset
}
pos [j ].attachChain = -chain
pos [j ].attachType = type_
}
func (c *otApplyContext ) applyGPOSPair1 (inner tables .PairPosData1 , index int ) bool {
set := inner .PairSets [index ]
pos := c .iterInput .idx
buffer := c .buffer
record , ok := set .FindGlyph (gID (buffer .Info [pos ].Glyph ))
if !ok {
buffer .unsafeToConcat (buffer .idx , pos +1 )
return false
}
ap1 := c .applyGPOSValueRecord (inner .ValueFormat1 , record .ValueRecord1 , buffer .curPos (0 ))
ap2 := c .applyGPOSValueRecord (inner .ValueFormat2 , record .ValueRecord2 , &buffer .Pos [pos ])
if ap1 || ap2 {
buffer .unsafeToBreak (buffer .idx , pos +1 )
}
if inner .ValueFormat2 != 0 {
pos ++
buffer .unsafeToBreak (buffer .idx , pos +1 )
}
buffer .idx = pos
return true
}
func (c *otApplyContext ) applyGPOSPair2 (inner tables .PairPosData2 ) bool {
buffer := c .buffer
skippyIter := &c .iterInput
glyphID := buffer .cur (0 ).Glyph
class2 , ok2 := inner .ClassDef2 .Class (gID (buffer .Info [skippyIter .idx ].Glyph ))
if !ok2 {
buffer .unsafeToConcat (buffer .idx , skippyIter .idx +1 )
return false
}
class1 , _ := inner .ClassDef1 .Class (gID (glyphID ))
vals := inner .Record (class1 , class2 )
ap1 := c .applyGPOSValueRecord (inner .ValueFormat1 , vals .ValueRecord1 , buffer .curPos (0 ))
ap2 := c .applyGPOSValueRecord (inner .ValueFormat2 , vals .ValueRecord2 , &buffer .Pos [skippyIter .idx ])
if ap1 || ap2 {
buffer .unsafeToBreak (buffer .idx , skippyIter .idx +1 )
} else {
buffer .unsafeToConcat (buffer .idx , skippyIter .idx +1 )
}
if inner .ValueFormat2 != 0 {
skippyIter .idx ++
buffer .unsafeToBreak (buffer .idx , skippyIter .idx +1 )
}
buffer .idx = skippyIter .idx
return true
}
func (c *otApplyContext ) applyGPOSCursive (data tables .CursivePos , covIndex int ) bool {
buffer := c .buffer
thisRecord := data .EntryExits [covIndex ]
if thisRecord .EntryAnchor == nil {
return false
}
skippyIter := &c .iterInput
skippyIter .resetFast (buffer .idx )
if ok , unsafeFrom := skippyIter .prev (); !ok {
buffer .unsafeToConcatFromOutbuffer (unsafeFrom , buffer .idx +1 )
return false
}
prevIndex , ok := data .Cov ().Index (gID (buffer .Info [skippyIter .idx ].Glyph ))
if !ok {
buffer .unsafeToConcatFromOutbuffer (skippyIter .idx , buffer .idx +1 )
return false
}
prevRecord := data .EntryExits [prevIndex ]
if prevRecord .ExitAnchor == nil {
buffer .unsafeToConcatFromOutbuffer (skippyIter .idx , buffer .idx +1 )
return false
}
i := skippyIter .idx
j := buffer .idx
buffer .unsafeToBreak (i , j +1 )
exitX , exitY := c .getAnchor (prevRecord .ExitAnchor , buffer .Info [i ].Glyph )
entryX , entryY := c .getAnchor (thisRecord .EntryAnchor , buffer .Info [j ].Glyph )
pos := buffer .Pos
var d Position
switch c .direction {
case LeftToRight :
pos [i ].XAdvance = roundf (exitX ) + pos [i ].XOffset
d = roundf (entryX ) + pos [j ].XOffset
pos [j ].XAdvance -= d
pos [j ].XOffset -= d
case RightToLeft :
d = roundf (exitX ) + pos [i ].XOffset
pos [i ].XAdvance -= d
pos [i ].XOffset -= d
pos [j ].XAdvance = roundf (entryX ) + pos [j ].XOffset
case TopToBottom :
pos [i ].YAdvance = roundf (exitY ) + pos [i ].YOffset
d = roundf (entryY ) + pos [j ].YOffset
pos [j ].YAdvance -= d
pos [j ].YOffset -= d
case BottomToTop :
d = roundf (exitY ) + pos [i ].YOffset
pos [i ].YAdvance -= d
pos [i ].YOffset -= d
pos [j ].YAdvance = roundf (entryY )
}
child := i
parent := j
xOffset := roundf (entryX - exitX )
yOffset := roundf (entryY - exitY )
if uint16 (c .lookupProps )&otRightToLeft == 0 {
k := child
child = parent
parent = k
xOffset = -xOffset
yOffset = -yOffset
}
reverseCursiveMinorOffset (pos , child , c .direction , parent )
chain := parent - child
if int (int16 (chain )) != parent -child {
pos [child ].attachChain = 0
buffer .idx ++
return true
}
pos [child ].attachType = attachTypeCursive
pos [child ].attachChain = int16 (chain )
buffer .scratchFlags |= bsfHasGPOSAttachment
if c .direction .isHorizontal () {
pos [child ].YOffset = yOffset
} else {
pos [child ].XOffset = xOffset
}
if pos [parent ].attachChain == -pos [child ].attachChain {
pos [parent ].attachChain = 0
if c .direction .isHorizontal () {
pos [parent ].YOffset = 0
} else {
pos [parent ].XOffset = 0
}
}
buffer .idx ++
return true
}
func (c *otApplyContext ) getAnchor (anchor tables .Anchor , glyph GID ) (x , y float32 ) {
font := c .font
switch anchor := anchor .(type ) {
case tables .AnchorFormat1 :
return font .emFscaleX (anchor .XCoordinate ), font .emFscaleY (anchor .YCoordinate )
case tables .AnchorFormat2 :
xPpem , yPpem := font .face .Ppem ()
var cx , cy Position
ret := xPpem != 0 || yPpem != 0
if ret {
cx , cy , ret = font .getGlyphContourPointForOrigin (glyph , anchor .AnchorPoint , LeftToRight )
}
if ret && xPpem != 0 {
x = float32 (cx )
} else {
x = font .emFscaleX (anchor .XCoordinate )
}
if ret && yPpem != 0 {
y = float32 (cy )
} else {
y = font .emFscaleY (anchor .YCoordinate )
}
return x , y
case tables .AnchorFormat3 :
xPpem , yPpem := font .face .Ppem ()
x , y = font .emFscaleX (anchor .XCoordinate ), font .emFscaleY (anchor .YCoordinate )
if xPpem != 0 || len (font .varCoords ()) != 0 {
x += float32 (font .getXDelta (c .varStore , anchor .XDevice ))
}
if yPpem != 0 || len (font .varCoords ()) != 0 {
y += float32 (font .getYDelta (c .varStore , anchor .YDevice ))
}
return x , y
default :
panic ("exhaustive switch" )
}
}
func (c *otApplyContext ) applyGPOSMarks (marks tables .MarkArray , markIndex , glyphIndex int , anchors tables .AnchorMatrix , glyphPos int ) bool {
buffer := c .buffer
markClass := marks .MarkRecords [markIndex ].MarkClass
markAnchor := marks .MarkAnchors [markIndex ]
glyphAnchor := anchors .Anchor (glyphIndex , int (markClass ))
if glyphAnchor == nil {
return false
}
buffer .unsafeToBreak (glyphPos , buffer .idx +1 )
markX , markY := c .getAnchor (markAnchor , buffer .cur (0 ).Glyph )
baseX , baseY := c .getAnchor (glyphAnchor , buffer .Info [glyphPos ].Glyph )
o := buffer .curPos (0 )
o .XOffset = roundf (baseX - markX )
o .YOffset = roundf (baseY - markY )
chain := glyphPos - buffer .idx
if int (int16 (chain )) != chain {
o .attachChain = 0
buffer .idx ++
return true
}
o .attachType = attachTypeMark
o .attachChain = int16 (chain )
buffer .scratchFlags |= bsfHasGPOSAttachment
buffer .idx ++
return true
}
func (c *otApplyContext ) applyGPOSMarkToBase (data tables .MarkBasePos , markIndex int ) bool {
buffer := c .buffer
skippyIter := &c .iterInput
skippyIter .matcher .lookupProps = uint32 (otIgnoreMarks )
if c .lastBaseUntil > buffer .idx {
c .lastBaseUntil = 0
c .lastBase = -1
}
for j := buffer .idx ; j > c .lastBaseUntil ; j -- {
ma := skippyIter .match (&buffer .Info [j -1 ])
if ma == match {
idx := j - 1
accept := !buffer .Info [idx ].multiplied () || buffer .Info [idx ].getLigComp () == 0 ||
idx == 0 || buffer .Info [idx -1 ].isMark () ||
buffer .Info [idx ].getLigID () != buffer .Info [idx -1 ].getLigID () ||
buffer .Info [idx ].getLigComp () != buffer .Info [idx -1 ].getLigComp ()+1
_ , covered := data .BaseCoverage .Index (gID (buffer .Info [idx ].Glyph ))
if !accept && !covered {
ma = skip
}
}
if ma == match {
c .lastBase = j - 1
break
}
}
c .lastBaseUntil = buffer .idx
if c .lastBase == -1 {
buffer .unsafeToConcatFromOutbuffer (0 , buffer .idx +1 )
return false
}
idx := c .lastBase
baseIndex , ok := data .BaseCoverage .Index (gID (buffer .Info [idx ].Glyph ))
if !ok {
buffer .unsafeToConcatFromOutbuffer (idx , buffer .idx +1 )
return false
}
return c .applyGPOSMarks (data .MarkArray , markIndex , baseIndex , data .BaseArray .Anchors (), idx )
}
func (c *otApplyContext ) applyGPOSMarkToLigature (data tables .MarkLigPos , markIndex int ) bool {
buffer := c .buffer
skippyIter := &c .iterInput
skippyIter .matcher .lookupProps = uint32 (otIgnoreMarks )
if c .lastBaseUntil > buffer .idx {
c .lastBaseUntil = 0
c .lastBase = -1
}
for j := buffer .idx ; j > c .lastBaseUntil ; j -- {
ma := skippyIter .match (&buffer .Info [j -1 ])
if ma == match {
c .lastBase = j - 1
break
}
}
c .lastBaseUntil = buffer .idx
if c .lastBase == -1 {
c .buffer .unsafeToConcatFromOutbuffer (0 , buffer .idx +1 )
return false
}
idx := c .lastBase
ligIndex , ok := data .LigatureCoverage .Index (gID (buffer .Info [idx ].Glyph ))
if !ok {
c .buffer .unsafeToConcatFromOutbuffer (idx , c .buffer .idx +1 )
return false
}
ligAttach := data .LigatureArray .LigatureAttachs [ligIndex ].Anchors ()
compCount := ligAttach .Len ()
if compCount == 0 {
return false
}
ligID := buffer .Info [idx ].getLigID ()
markID := buffer .cur (0 ).getLigID ()
markComp := buffer .cur (0 ).getLigComp ()
compIndex := compCount - 1
if ligID != 0 && ligID == markID && markComp > 0 {
compIndex = min (compCount , int (buffer .cur (0 ).getLigComp ())) - 1
}
return c .applyGPOSMarks (data .MarkArray , markIndex , compIndex , ligAttach , idx )
}
func (c *otApplyContext ) applyGPOSMarkToMark (data tables .MarkMarkPos , mark1Index int ) bool {
buffer := c .buffer
skippyIter := &c .iterInput
skippyIter .resetFast (buffer .idx )
skippyIter .matcher .lookupProps = c .lookupProps &^ uint32 (ignoreFlags )
if ok , _ := skippyIter .prev (); !ok {
return false
}
if !buffer .Info [skippyIter .idx ].isMark () {
return false
}
j := skippyIter .idx
id1 := buffer .cur (0 ).getLigID ()
id2 := buffer .Info [j ].getLigID ()
comp1 := buffer .cur (0 ).getLigComp ()
comp2 := buffer .Info [j ].getLigComp ()
if id1 == id2 {
if id1 == 0 {
goto good
} else if comp1 == comp2 {
goto good
}
} else {
if (id1 > 0 && comp1 == 0 ) || (id2 > 0 && comp2 == 0 ) {
goto good
}
}
return false
good :
mark2Index , ok := data .Mark2Coverage .Index (gID (buffer .Info [j ].Glyph ))
if !ok {
return false
}
return c .applyGPOSMarks (data .Mark1Array , mark1Index , mark2Index , data .Mark2Array .Anchors (), j )
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .