package harfbuzz
import (
"fmt"
"math"
"math/bits"
"sort"
"github.com/go-text/typesetting/font"
ot "github.com/go-text/typesetting/font/opentype"
"github.com/go-text/typesetting/font/opentype/tables"
)
type otMapFeatureFlags uint8
const (
ffGLOBAL otMapFeatureFlags = 1 << iota
ffHasFallback
ffManualZWNJ
ffManualZWJ
ffGlobalSearch
ffRandom
ffPerSyllable
ffNone otMapFeatureFlags = 0
ffManualJoiners = ffManualZWNJ | ffManualZWJ
ffGlobalManualJoiners = ffGLOBAL | ffManualJoiners
ffGlobalHasFallback = ffGLOBAL | ffHasFallback
)
const (
otMapMaxBits = 8
otMapMaxValue = (1 << otMapMaxBits ) - 1
)
type otMapFeature struct {
tag tables .Tag
flags otMapFeatureFlags
}
type featureInfo struct {
Tag tables .Tag
maxValue uint32
flags otMapFeatureFlags
defaultValue uint32
stage [2 ]int
}
type stageInfo struct {
pauseFunc pauseFunc
index int
}
type otMapBuilder struct {
tables *font .Font
props SegmentProperties
stages [2 ][]stageInfo
featureInfos []featureInfo
scriptIndex [2 ]int
languageIndex [2 ]int
currentStage [2 ]int
chosenScript [2 ]tables .Tag
foundScript [2 ]bool
}
func newOtMapBuilder(tables *font .Font , props SegmentProperties ) otMapBuilder {
var out otMapBuilder
out .tables = tables
out .props = props
scriptTags , languageTags := newOTTagsFromScriptAndLanguage (props .Script , props .Language )
out .scriptIndex [0 ], out .chosenScript [0 ], out .foundScript [0 ] = selectScript (&tables .GSUB .Layout , scriptTags )
out .languageIndex [0 ], _ = selectLanguage (&tables .GSUB .Layout , out .scriptIndex [0 ], languageTags )
out .scriptIndex [1 ], out .chosenScript [1 ], out .foundScript [1 ] = selectScript (&tables .GPOS .Layout , scriptTags )
out .languageIndex [1 ], _ = selectLanguage (&tables .GPOS .Layout , out .scriptIndex [1 ], languageTags )
return out
}
func (mb *otMapBuilder ) addFeatureExt (tag tables .Tag , flags otMapFeatureFlags , value uint32 ) {
var info featureInfo
info .Tag = tag
info .maxValue = value
info .flags = flags
if (flags & ffGLOBAL ) != 0 {
info .defaultValue = value
}
info .stage = mb .currentStage
mb .featureInfos = append (mb .featureInfos , info )
}
type pauseFunc func (plan *otShapePlan , font *Font , buffer *Buffer ) bool
func (mb *otMapBuilder ) addPause (tableIndex int , fn pauseFunc ) {
s := stageInfo {
index : mb .currentStage [tableIndex ],
pauseFunc : fn ,
}
mb .stages [tableIndex ] = append (mb .stages [tableIndex ], s )
mb .currentStage [tableIndex ]++
}
func (mb *otMapBuilder ) addGSUBPause (fn pauseFunc ) { mb .addPause (0 , fn ) }
func (mb *otMapBuilder ) addGPOSPause (fn pauseFunc ) { mb .addPause (1 , fn ) }
func (mb *otMapBuilder ) enableFeatureExt (tag tables .Tag , flags otMapFeatureFlags , value uint32 ) {
mb .addFeatureExt (tag , ffGLOBAL |flags , value )
}
func (mb *otMapBuilder ) enableFeature (tag tables .Tag ) { mb .enableFeatureExt (tag , ffNone , 1 ) }
func (mb *otMapBuilder ) addFeature (tag tables .Tag ) { mb .addFeatureExt (tag , ffNone , 1 ) }
func (mb *otMapBuilder ) disableFeature (tag tables .Tag ) { mb .addFeatureExt (tag , ffGLOBAL , 0 ) }
func (mb *otMapBuilder ) compile (m *otMap , key otShapePlanKey ) {
const globalBitShift = 8 *4 - 1
const globalBitMask = 1 << globalBitShift
m .globalMask = globalBitMask
var (
requiredFeatureIndex [2 ]uint16
requiredFeatureTag [2 ]tables .Tag
requiredFeatureStage [2 ]int
)
gsub , gpos := mb .tables .GSUB , mb .tables .GPOS
tables := [2 ]*font .Layout {&gsub .Layout , &gpos .Layout }
m .chosenScript = mb .chosenScript
m .foundScript = mb .foundScript
requiredFeatureIndex [0 ], requiredFeatureTag [0 ] = getRequiredFeature (tables [0 ], mb .scriptIndex [0 ], mb .languageIndex [0 ])
requiredFeatureIndex [1 ], requiredFeatureTag [1 ] = getRequiredFeature (tables [1 ], mb .scriptIndex [1 ], mb .languageIndex [1 ])
if len (mb .featureInfos ) != 0 {
sort .SliceStable (mb .featureInfos , func (i , j int ) bool {
return mb .featureInfos [i ].Tag < mb .featureInfos [j ].Tag
})
j := 0
for i , feat := range mb .featureInfos {
if i == 0 {
continue
}
if feat .Tag != mb .featureInfos [j ].Tag {
j ++
mb .featureInfos [j ] = feat
continue
}
if feat .flags &ffGLOBAL != 0 {
mb .featureInfos [j ].flags |= ffGLOBAL
mb .featureInfos [j ].maxValue = feat .maxValue
mb .featureInfos [j ].defaultValue = feat .defaultValue
} else {
if mb .featureInfos [j ].flags &ffGLOBAL != 0 {
mb .featureInfos [j ].flags ^= ffGLOBAL
}
mb .featureInfos [j ].maxValue = max32 (mb .featureInfos [j ].maxValue , feat .maxValue )
}
mb .featureInfos [j ].flags |= (feat .flags & ffHasFallback )
mb .featureInfos [j ].stage [0 ] = min (mb .featureInfos [j ].stage [0 ], feat .stage [0 ])
mb .featureInfos [j ].stage [1 ] = min (mb .featureInfos [j ].stage [1 ], feat .stage [1 ])
}
mb .featureInfos = mb .featureInfos [0 : j +1 ]
}
nextBit := bits .OnesCount32 (glyphFlagDefined ) + 1
for _ , info := range mb .featureInfos {
bitsNeeded := 0
if (info .flags &ffGLOBAL ) != 0 && info .maxValue == 1 {
bitsNeeded = 0
} else {
bitsNeeded = min (otMapMaxBits , bitStorage (info .maxValue ))
}
if info .maxValue == 0 || nextBit +bitsNeeded >= globalBitShift {
continue
}
var (
found = false
featureIndex [2 ]uint16
)
for tableIndex , table := range tables {
if requiredFeatureTag [tableIndex ] == info .Tag {
requiredFeatureStage [tableIndex ] = info .stage [tableIndex ]
}
featureIndex [tableIndex ] = findFeatureForLang (table , mb .scriptIndex [tableIndex ], mb .languageIndex [tableIndex ], info .Tag )
found = found || featureIndex [tableIndex ] != NoFeatureIndex
}
if !found && (info .flags &ffGlobalSearch ) != 0 {
for tableIndex , table := range tables {
featureIndex [tableIndex ] = findFeature (table , info .Tag )
found = found || featureIndex [tableIndex ] != NoFeatureIndex
}
}
if !found && info .flags &ffHasFallback == 0 {
continue
}
var map_ featureMap
map_ .tag = info .Tag
map_ .index = featureIndex
map_ .stage = info .stage
map_ .autoZWNJ = info .flags &ffManualZWNJ == 0
map_ .autoZWJ = info .flags &ffManualZWJ == 0
map_ .random = info .flags &ffRandom != 0
map_ .perSyllable = info .flags &ffPerSyllable != 0
if (info .flags &ffGLOBAL ) != 0 && info .maxValue == 1 {
map_ .shift = globalBitShift
map_ .mask = globalBitMask
} else {
map_ .shift = nextBit
map_ .mask = (1 << (nextBit + bitsNeeded )) - (1 << nextBit )
nextBit += bitsNeeded
m .globalMask |= (info .defaultValue << map_ .shift ) & map_ .mask
}
map_ .mask1 = (1 << map_ .shift ) & map_ .mask
map_ .needsFallback = !found
if debugMode {
fmt .Printf ("\tMAP - adding feature %s (%d) for stage %v\n" , info .Tag , info .Tag , info .stage )
}
m .features = append (m .features , map_ )
}
mb .featureInfos = mb .featureInfos [:0 ]
mb .addGSUBPause (nil )
mb .addGPOSPause (nil )
for tableIndex , table := range tables {
stageIndex := 0
lastNumLookups := 0
for stage := 0 ; stage < mb .currentStage [tableIndex ]; stage ++ {
if requiredFeatureIndex [tableIndex ] != NoFeatureIndex &&
requiredFeatureStage [tableIndex ] == stage {
const emptyTag = 0x20202020
m .addLookups (table , tableIndex , requiredFeatureIndex [tableIndex ],
key [tableIndex ], globalBitMask , true , true , false , false , emptyTag )
}
for _ , feat := range m .features {
if feat .stage [tableIndex ] == stage {
m .addLookups (table , tableIndex ,
feat .index [tableIndex ],
key [tableIndex ],
feat .mask ,
feat .autoZWNJ ,
feat .autoZWJ ,
feat .random ,
feat .perSyllable ,
feat .tag ,
)
}
}
if ls := m .lookups [tableIndex ]; lastNumLookups < len (ls ) {
view := ls [lastNumLookups :]
sort .Slice (view , func (i , j int ) bool { return view [i ].index < view [j ].index })
j := lastNumLookups
for i := j + 1 ; i < len (ls ); i ++ {
if ls [i ].index != ls [j ].index {
j ++
ls [j ] = ls [i ]
} else {
ls [j ].mask |= ls [i ].mask
ls [j ].autoZWNJ = ls [j ].autoZWNJ && ls [i ].autoZWNJ
ls [j ].autoZWJ = ls [j ].autoZWJ && ls [i ].autoZWJ
}
}
m .lookups [tableIndex ] = ls [:j +1 ]
}
lastNumLookups = len (m .lookups [tableIndex ])
if stageIndex < len (mb .stages [tableIndex ]) && mb .stages [tableIndex ][stageIndex ].index == stage {
sm := stageMap {
lastLookup : lastNumLookups ,
pauseFunc : mb .stages [tableIndex ][stageIndex ].pauseFunc ,
}
m .stages [tableIndex ] = append (m .stages [tableIndex ], sm )
stageIndex ++
}
}
}
}
func (mb *otMapBuilder ) hasFeature (tag ot .Tag ) bool {
tables := [2 ]*font .Layout {&mb .tables .GSUB .Layout , &mb .tables .GPOS .Layout }
for tableIndex , table := range tables {
if findFeatureForLang (table , mb .scriptIndex [tableIndex ], mb .languageIndex [tableIndex ], tag ) != NoFeatureIndex {
return true
}
}
return false
}
type featureMap struct {
tag tables .Tag
index [2 ]uint16
stage [2 ]int
shift int
mask GlyphMask
mask1 GlyphMask
needsFallback bool
autoZWNJ bool
autoZWJ bool
random bool
perSyllable bool
}
func bsearchFeature(features []featureMap , tag tables .Tag ) *featureMap {
low , high := 0 , len (features )
for low < high {
mid := low + (high -low )/2
p := features [mid ].tag
if tag < p {
high = mid
} else if tag > p {
low = mid + 1
} else {
return &features [mid ]
}
}
return nil
}
type lookupMap struct {
index uint16
autoZWNJ bool
autoZWJ bool
random bool
perSyllable bool
featureTag ot .Tag
mask GlyphMask
}
type stageMap struct {
pauseFunc pauseFunc
lastLookup int
}
type otMap struct {
lookups [2 ][]lookupMap
stages [2 ][]stageMap
features []featureMap
chosenScript [2 ]tables .Tag
globalMask GlyphMask
foundScript [2 ]bool
applyContext otApplyContext
}
func (m *otMap ) needsFallback (featureTag tables .Tag ) bool {
if ma := bsearchFeature (m .features , featureTag ); ma != nil {
return ma .needsFallback
}
return false
}
func (m *otMap ) getMask (featureTag tables .Tag ) (GlyphMask , int ) {
if ma := bsearchFeature (m .features , featureTag ); ma != nil {
return ma .mask , ma .shift
}
return 0 , 0
}
func (m *otMap ) getMask1 (featureTag tables .Tag ) GlyphMask {
if ma := bsearchFeature (m .features , featureTag ); ma != nil {
return ma .mask1
}
return 0
}
func (m *otMap ) getFeatureIndex (tableIndex int , featureTag tables .Tag ) uint16 {
if ma := bsearchFeature (m .features , featureTag ); ma != nil {
return ma .index [tableIndex ]
}
return NoFeatureIndex
}
func (m *otMap ) getFeatureStage (tableIndex int , featureTag tables .Tag ) int {
if ma := bsearchFeature (m .features , featureTag ); ma != nil {
return ma .stage [tableIndex ]
}
return math .MaxInt32
}
func (m *otMap ) getStageLookups (tableIndex , stage int ) []lookupMap {
if stage > len (m .stages [tableIndex ]) {
return nil
}
start , end := 0 , len (m .lookups [tableIndex ])
if stage != 0 {
start = m .stages [tableIndex ][stage -1 ].lastLookup
}
if stage < len (m .stages [tableIndex ]) {
end = m .stages [tableIndex ][stage ].lastLookup
}
return m .lookups [tableIndex ][start :end ]
}
func (m *otMap ) addLookups (table *font .Layout , tableIndex int , featureIndex uint16 , variationsIndex int ,
mask GlyphMask , autoZwnj , autoZwj , random , perSyllable bool , featureTag ot .Tag ,
) {
lookupIndices := getFeatureLookupsWithVar (table , featureIndex , variationsIndex )
for _ , lookupInd := range lookupIndices {
lookup := lookupMap {
mask : mask ,
index : lookupInd ,
autoZWNJ : autoZwnj ,
autoZWJ : autoZwj ,
random : random ,
perSyllable : perSyllable ,
featureTag : featureTag ,
}
m .lookups [tableIndex ] = append (m .lookups [tableIndex ], lookup )
}
}
func (m *otMap ) substitute (plan *otShapePlan , font *Font , buffer *Buffer ) {
if debugMode {
fmt .Println ("SUBSTITUTE - start table GSUB" )
}
proxy := otProxy {otProxyMeta : proxyGSUB , accels : font .gsubAccels }
m .apply (proxy , plan , font , buffer )
if debugMode {
fmt .Println ("SUBSTITUTE - end table GSUB" )
}
}
func (m *otMap ) position (plan *otShapePlan , font *Font , buffer *Buffer ) {
if debugMode {
fmt .Println ("POSITION - start table GPOS" )
}
proxy := otProxy {otProxyMeta : proxyGPOS , accels : font .gposAccels }
m .apply (proxy , plan , font , buffer )
if debugMode {
fmt .Println ("POSITION - end table GPOS" )
}
}
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 .