package harfbuzz
import (
"fmt"
"math/bits"
"github.com/go-text/typesetting/font"
"github.com/go-text/typesetting/font/opentype/tables"
)
const maxContextLength = 64
var _ layoutLookup = lookupGSUB {}
type lookupGSUB font .GSUBLookup
func (l lookupGSUB ) Props () uint32 { return l .LookupOptions .Props () }
func (l lookupGSUB ) collectCoverage (dst *setDigest ) {
for _ , table := range l .Subtables {
dst .collectCoverage (table .Cov ())
}
}
func (l lookupGSUB ) dispatchSubtables (ctx *getSubtablesContext ) {
for _ , table := range l .Subtables {
*ctx = append (*ctx , newGSUBApplicable (table ))
}
}
func (l lookupGSUB ) dispatchApply (ctx *otApplyContext ) bool {
for _ , table := range l .Subtables {
if ctx .applyGSUB (table ) {
return true
}
}
return false
}
func (l lookupGSUB ) wouldApply (ctx *wouldApplyContext , accel *otLayoutLookupAccelerator ) bool {
if len (ctx .glyphs ) == 0 {
return false
}
if !accel .digest .mayHave (gID (ctx .glyphs [0 ])) {
return false
}
for _ , table := range l .Subtables {
if ctx .wouldApplyGSUB (table ) {
return true
}
}
return false
}
func (l lookupGSUB ) isReverse () bool {
if len (l .Subtables ) == 0 {
return false
}
_ , is := l .Subtables [0 ].(tables .ReverseChainSingleSubs )
return is
}
func applyRecurseGSUB(c *otApplyContext , lookupIndex uint16 ) bool {
gsub := c .font .face .GSUB
l := lookupGSUB (gsub .Lookups [lookupIndex ])
return c .applyRecurseLookup (lookupIndex , l )
}
func matchesLigature(l tables .Ligature , glyphsFromSecond []GID ) bool {
if len (glyphsFromSecond ) != len (l .ComponentGlyphIDs ) {
return false
}
for i , g := range glyphsFromSecond {
if g != GID (l .ComponentGlyphIDs [i ]) {
return false
}
}
return true
}
func (c *wouldApplyContext ) wouldApplyGSUB (table tables .GSUBLookup ) bool {
index , ok := table .Cov ().Index (gID (c .glyphs [0 ]))
switch data := table .(type ) {
case tables .SingleSubs , tables .MultipleSubs , tables .AlternateSubs , tables .ReverseChainSingleSubs :
return len (c .glyphs ) == 1 && ok
case tables .LigatureSubs :
if !ok {
return false
}
ligatureSet := data .LigatureSets [index ].Ligatures
glyphsFromSecond := c .glyphs [1 :]
for _ , ligature := range ligatureSet {
if matchesLigature (ligature , glyphsFromSecond ) {
return true
}
}
return false
case tables .ContextualSubs :
switch inner := data .Data .(type ) {
case tables .ContextualSubs1 :
return c .wouldApplyLookupContext1 (tables .SequenceContextFormat1 (inner ), index )
case tables .ContextualSubs2 :
return c .wouldApplyLookupContext2 (tables .SequenceContextFormat2 (inner ), index , c .glyphs [0 ])
case tables .ContextualSubs3 :
return c .wouldApplyLookupContext3 (tables .SequenceContextFormat3 (inner ), index )
}
case tables .ChainedContextualSubs :
switch inner := data .Data .(type ) {
case tables .ChainedContextualSubs1 :
return c .wouldApplyLookupChainedContext1 (tables .ChainedSequenceContextFormat1 (inner ), index )
case tables .ChainedContextualSubs2 :
return c .wouldApplyLookupChainedContext2 (tables .ChainedSequenceContextFormat2 (inner ), index , c .glyphs [0 ])
case tables .ChainedContextualSubs3 :
return c .wouldApplyLookupChainedContext3 (tables .ChainedSequenceContextFormat3 (inner ), index )
}
}
return false
}
func (c *otApplyContext ) applyGSUB (table tables .GSUBLookup ) bool {
glyph := c .buffer .cur (0 )
glyphID := glyph .Glyph
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 .SingleSubs :
switch inner := data .Data .(type ) {
case tables .SingleSubstData1 :
glyphID = GID (uint16 (int (glyphID ) + int (inner .DeltaGlyphID )))
c .replaceGlyph (glyphID )
case tables .SingleSubstData2 :
if index >= len (inner .SubstituteGlyphIDs ) {
return false
}
c .replaceGlyph (GID (inner .SubstituteGlyphIDs [index ]))
}
case tables .MultipleSubs :
c .applySubsSequence (data .Sequences [index ].SubstituteGlyphIDs )
case tables .AlternateSubs :
alternates := data .AlternateSets [index ].AlternateGlyphIDs
return c .applySubsAlternate (alternates )
case tables .LigatureSubs :
ligatureSet := data .LigatureSets [index ].Ligatures
return c .applySubsLigature (ligatureSet )
case tables .ContextualSubs :
switch inner := data .Data .(type ) {
case tables .ContextualSubs1 :
return c .applyLookupContext1 (tables .SequenceContextFormat1 (inner ), index )
case tables .ContextualSubs2 :
return c .applyLookupContext2 (tables .SequenceContextFormat2 (inner ), index , glyphID )
case tables .ContextualSubs3 :
return c .applyLookupContext3 (tables .SequenceContextFormat3 (inner ), index )
}
case tables .ChainedContextualSubs :
switch inner := data .Data .(type ) {
case tables .ChainedContextualSubs1 :
return c .applyLookupChainedContext1 (tables .ChainedSequenceContextFormat1 (inner ), index )
case tables .ChainedContextualSubs2 :
return c .applyLookupChainedContext2 (tables .ChainedSequenceContextFormat2 (inner ), index , glyphID )
case tables .ChainedContextualSubs3 :
return c .applyLookupChainedContext3 (tables .ChainedSequenceContextFormat3 (inner ), index )
}
case tables .ReverseChainSingleSubs :
if c .nestingLevelLeft != maxNestingLevel {
return false
}
lB , lL := len (data .BacktrackCoverages ), len (data .LookaheadCoverages )
hasMatch , startIndex := c .matchBacktrack (get1N (&c .indices , 0 , lB ), matchCoverage (data .BacktrackCoverages ))
if !hasMatch {
return false
}
hasMatch , endIndex := c .matchLookahead (get1N (&c .indices , 0 , lL ), matchCoverage (data .LookaheadCoverages ), c .buffer .idx +1 )
if !hasMatch {
return false
}
c .buffer .unsafeToBreakFromOutbuffer (startIndex , endIndex )
c .setGlyphClass (GID (data .SubstituteGlyphIDs [index ]))
c .buffer .cur (0 ).Glyph = GID (data .SubstituteGlyphIDs [index ])
}
return true
}
func (c *otApplyContext ) applySubsSequence (seq []gID ) {
switch len (seq ) {
case 1 :
c .replaceGlyph (GID (seq [0 ]))
case 0 :
c .buffer .deleteGlyph ()
default :
var klass uint16
if c .buffer .cur (0 ).isLigature () {
klass = tables .GPBaseGlyph
}
ligID := c .buffer .cur (0 ).getLigID ()
for i , g := range seq {
if ligID == 0 {
c .buffer .cur (0 ).setLigPropsForMark (0 , uint8 (i ))
}
c .setGlyphClassExt (GID (g ), klass , false , true )
c .buffer .outputGlyphIndex (GID (g ))
}
c .buffer .skipGlyph ()
}
}
func (c *otApplyContext ) applySubsAlternate (alternates []gID ) bool {
count := uint32 (len (alternates ))
if count == 0 {
return false
}
glyphMask := c .buffer .cur (0 ).Mask
lookupMask := c .lookupMask
shift := bits .TrailingZeros32 (lookupMask )
altIndex := (lookupMask & glyphMask ) >> shift
if altIndex == otMapMaxValue && c .random {
c .buffer .unsafeToBreak (0 , len (c .buffer .Info ))
altIndex = c .randomNumber ()%count + 1
}
if altIndex > count || altIndex == 0 {
return false
}
c .replaceGlyph (GID (alternates [altIndex -1 ]))
return true
}
func (c *otApplyContext ) applyOneLigature (lig tables .Ligature ) bool {
count := len (lig .ComponentGlyphIDs ) + 1
if count == 1 {
c .replaceGlyph (GID (lig .LigatureGlyph ))
return true
}
if count > maxContextLength {
return false
}
ok , matchEnd , totalComponentCount := c .matchInput (lig .ComponentGlyphIDs , matchGlyph )
if !ok {
c .buffer .unsafeToConcat (c .buffer .idx , matchEnd )
return false
}
c .ligateInput (count , matchEnd , lig .LigatureGlyph , totalComponentCount )
return true
}
func (c *otApplyContext ) applySubsLigature (ligatureSet []tables .Ligature ) bool {
for _ , lig := range ligatureSet {
if c .applyOneLigature (lig ) {
return true
}
}
return false
}
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 .