package harfbuzz
import (
"github.com/go-text/typesetting/font/opentype/tables"
"github.com/go-text/typesetting/language"
)
const (
substituted tables .GlyphProps = 1 << (iota + 4 )
ligated
multiplied
preserve = substituted | ligated | multiplied
)
type bufferScratchFlags uint32
const (
bsfHasFractionSlash bufferScratchFlags = 1 << iota
bsfHasDefaultIgnorables
bsfHasSpaceFallback
bsfHasGPOSAttachment
bsfHasCGJ
bsfHasBrokenSyllable
bsfHasVariationSelectorFallback
bsfHasContinuations
bsfDefault bufferScratchFlags = 0x00000000
bsfShaper0 bufferScratchFlags = 0x01000000
bsfShaper1 bufferScratchFlags = 0x02000000
bsfShaper2 bufferScratchFlags = 0x04000000
bsfShaper3 bufferScratchFlags = 0x08000000
)
const contextLength = 5
const (
maxOpsDefault = 0x1FFFFFFF
maxLenDefault = 0x3FFFFFFF
)
type Buffer struct {
Info []GlyphInfo
Pos []GlyphPosition
context [2 ][]rune
outInfo []GlyphInfo
Props SegmentProperties
Invisible GID
NotFound GID
notFoundVariationSelector GID
Flags ShappingOptions
ClusterLevel ClusterLevel
maxOps int
maxLen int
serial uint
idx int
scratchFlags bufferScratchFlags
haveOutput bool
planCache map [Face ][]*shapePlan
digest setDigest
}
func NewBuffer () *Buffer {
return &Buffer {
ClusterLevel : MonotoneGraphemes ,
maxOps : maxOpsDefault ,
planCache : map [Face ][]*shapePlan {},
notFoundVariationSelector : 0xFFFFFFFF ,
}
}
func (b *Buffer ) AddRune (codepoint rune , cluster int ) {
b .append (codepoint , cluster )
b .clearContext (1 )
}
func (b *Buffer ) append (codepoint rune , cluster int ) {
b .Info = append (b .Info , GlyphInfo {codepoint : codepoint , Cluster : cluster })
b .Pos = append (b .Pos , GlyphPosition {})
}
func (b *Buffer ) AddRunes (text []rune , itemOffset , itemLength int ) {
if len (b .Info ) == 0 && itemOffset > 0 {
b .clearContext (0 )
for prev := itemOffset - 1 ; prev >= 0 && len (b .context [0 ]) < contextLength ; prev -- {
b .context [0 ] = append (b .context [0 ], text [prev ])
}
}
if itemLength < 0 {
itemLength = len (text ) - itemOffset
}
for i , u := range text [itemOffset : itemOffset +itemLength ] {
b .append (u , itemOffset +i )
}
s := itemOffset + itemLength + contextLength
if s > len (text ) {
s = len (text )
}
b .context [1 ] = text [itemOffset +itemLength : s ]
}
func (b *Buffer ) GuessSegmentProperties () {
if b .Props .Script == 0 {
for _ , info := range b .Info {
script := language .LookupScript (info .codepoint )
if script .Strong () && script != language .Unknown {
b .Props .Script = script
break
}
}
}
if b .Props .Direction == 0 {
b .Props .Direction = getHorizontalDirection (b .Props .Script )
if b .Props .Direction == 0 {
b .Props .Direction = LeftToRight
}
}
if b .Props .Language == "" {
b .Props .Language = language .DefaultLanguage ()
}
}
func (b *Buffer ) Clear () {
b .ClusterLevel = 0
b .Flags = 0
b .Invisible = 0
b .NotFound = 0
b .notFoundVariationSelector = 0xFFFFFFFF
b .Props = SegmentProperties {}
b .scratchFlags = 0
b .haveOutput = false
b .idx = 0
b .Info = b .Info [:0 ]
b .outInfo = b .outInfo [:0 ]
b .Pos = b .Pos [:0 ]
b .clearContext (0 )
b .clearContext (1 )
b .serial = 0
}
func (b *Buffer ) cur (i int ) *GlyphInfo { return &b .Info [b .idx +i ] }
func (b *Buffer ) curPos (i int ) *GlyphPosition { return &b .Pos [b .idx +i ] }
func (b *Buffer ) prev () *GlyphInfo {
return &b .outInfo [len (b .outInfo )-1 ]
}
func (b *Buffer ) updateDigest () {
b .digest = setDigest {}
for _ , glyph := range b .Info {
b .digest .add (gID (glyph .Glyph ))
}
}
func (b *Buffer ) backtrackLen () int {
if b .haveOutput {
return len (b .outInfo )
}
return b .idx
}
func (b *Buffer ) lookaheadLen () int { return len (b .Info ) - b .idx }
func (b *Buffer ) replaceGlyph (u rune ) {
b .replaceGlyphs (1 , []rune {u }, nil )
}
func (b *Buffer ) replaceGlyphIndex (g GID ) {
b .outInfo = append (b .outInfo , b .Info [b .idx ])
b .outInfo [len (b .outInfo )-1 ].Glyph = g
b .idx ++
}
func (b *Buffer ) replaceGlyphs (numIn int , codepoints []rune , glyphs []GID ) {
b .mergeClusters (b .idx , b .idx +numIn )
var origInfo *GlyphInfo
if b .idx < len (b .Info ) {
origInfo = b .cur (0 )
} else {
origInfo = b .prev ()
}
replaceCodepoints := codepoints != nil
replaceGlyphs := glyphs != nil
L := len (b .outInfo )
Lplus := max (len (codepoints ), len (glyphs ))
b .outInfo = append (b .outInfo , make ([]GlyphInfo , Lplus )...)
for i := 0 ; i < Lplus ; i ++ {
b .outInfo [L +i ] = *origInfo
if replaceCodepoints {
b .outInfo [L +i ].codepoint = codepoints [i ]
}
if replaceGlyphs {
b .outInfo [L +i ].Glyph = glyphs [i ]
}
}
b .idx += numIn
}
func (b *Buffer ) outputRune (r rune ) {
b .replaceGlyphs (0 , []rune {r }, nil )
}
func (b *Buffer ) outputGlyphIndex (g GID ) {
b .replaceGlyphs (0 , nil , []GID {g })
}
func (b *Buffer ) copyGlyph () {
b .outInfo = append (b .outInfo , b .Info [b .idx ])
}
func (b *Buffer ) nextGlyph () {
if b .haveOutput {
b .outInfo = append (b .outInfo , b .Info [b .idx ])
}
b .idx ++
}
func (b *Buffer ) nextGlyphs (n int ) {
if b .haveOutput {
b .outInfo = append (b .outInfo , b .Info [b .idx :b .idx +n ]...)
}
b .idx += n
}
func (b *Buffer ) skipGlyph () { b .idx ++ }
func (b *Buffer ) resetMasks (mask GlyphMask ) {
for j := range b .Info {
b .Info [j ].Mask = mask
}
}
func (b *Buffer ) setGlyphFlags (mask GlyphMask , start , end int , interior , fromOutBuffer bool ) {
if end != maxInt && end -start > 255 {
return
}
info := b .Info
end = min (end , len (info ))
if interior && !fromOutBuffer && end -start < 2 {
return
}
if !fromOutBuffer || !b .haveOutput {
if !interior {
for i := start ; i < end ; i ++ {
info [i ].Mask |= mask
}
} else {
cluster := b .findMinCluster (info , start , end , maxInt )
b .infosSetGlyphFlags (info , start , end , cluster , mask )
}
} else {
outInfo := b .outInfo
if !interior {
for i := start ; i < len (outInfo ); i ++ {
outInfo [i ].Mask |= mask
}
for i := b .idx ; i < end ; i ++ {
info [i ].Mask |= mask
}
} else {
cluster := b .findMinCluster (info , b .idx , end , maxInt )
cluster = b .findMinCluster (outInfo , start , len (outInfo ), cluster )
b .infosSetGlyphFlags (outInfo , start , len (outInfo ), cluster , mask )
b .infosSetGlyphFlags (info , b .idx , end , cluster , mask )
}
}
}
func (b *Buffer ) setMasks (value , mask GlyphMask , clusterStart , clusterEnd int ) {
if mask == 0 {
return
}
notMask := ^mask
value &= mask
b .maxOps -= len (b .Info )
if clusterStart == 0 && clusterEnd == -1 {
for i , info := range b .Info {
b .Info [i ].Mask = (info .Mask & notMask ) | value
}
return
}
for i , info := range b .Info {
if clusterStart <= info .Cluster && info .Cluster < clusterEnd {
b .Info [i ].Mask = (info .Mask & notMask ) | value
}
}
}
func (b *Buffer ) mergeClusters (start , end int ) {
if end -start < 2 {
return
}
if b .ClusterLevel == Characters {
b .unsafeToBreak (start , end )
return
}
b .maxOps -= end - start
cluster := b .Info [start ].Cluster
for i := start + 1 ; i < end ; i ++ {
cluster = min (cluster , b .Info [i ].Cluster )
}
if cluster != b .Info [end -1 ].Cluster {
for end < len (b .Info ) && b .Info [end -1 ].Cluster == b .Info [end ].Cluster {
end ++
}
}
if cluster != b .Info [start ].Cluster {
for b .idx < start && b .Info [start -1 ].Cluster == b .Info [start ].Cluster {
start --
}
}
if b .idx == start && b .Info [start ].Cluster != cluster {
startC := b .Info [start ].Cluster
for i := len (b .outInfo ); i != 0 && b .outInfo [i -1 ].Cluster == startC ; i -- {
b .outInfo [i -1 ].setCluster (cluster , 0 )
}
}
for i := start ; i < end ; i ++ {
b .Info [i ].setCluster (cluster , 0 )
}
}
func (b *Buffer ) deleteGlyph () {
cluster := b .Info [b .idx ].Cluster
if L := len (b .outInfo ); b .idx +1 < len (b .Info ) && cluster == b .Info [b .idx +1 ].Cluster ||
L != 0 && cluster == b .outInfo [L -1 ].Cluster {
goto done
}
if L := len (b .outInfo ); L != 0 {
if cluster < b .outInfo [L -1 ].Cluster {
mask := b .Info [b .idx ].Mask
oldCluster := b .outInfo [L -1 ].Cluster
for i := L ; i != 0 && b .outInfo [i -1 ].Cluster == oldCluster ; i -- {
b .outInfo [i -1 ].setCluster (cluster , mask )
}
}
goto done
}
if b .idx +1 < len (b .Info ) {
b .mergeClusters (b .idx , b .idx +2 )
goto done
}
done :
b .skipGlyph ()
}
func (b *Buffer ) deleteGlyphsInplace (filter func (*GlyphInfo ) bool ) {
var (
j int
info = b .Info
pos = b .Pos
)
for i := range info {
if filter (&info [i ]) {
cluster := info [i ].Cluster
if i +1 < len (b .Info ) && cluster == info [i +1 ].Cluster {
continue
}
if j != 0 {
if cluster < info [j -1 ].Cluster {
mask := info [i ].Mask
oldCluster := info [j -1 ].Cluster
for k := j ; k != 0 && info [k -1 ].Cluster == oldCluster ; k -- {
info [k -1 ].setCluster (cluster , mask )
}
}
continue
}
if i +1 < len (b .Info ) {
b .mergeClusters (i , i +2 )
}
continue
}
if j != i {
info [j ] = info [i ]
pos [j ] = pos [i ]
}
j ++
}
b .Info = b .Info [:j ]
b .Pos = b .Pos [:j ]
}
func (b *Buffer ) unsafeToBreak (start , end int ) {
b .setGlyphFlags (GlyphUnsafeToBreak |GlyphUnsafeToConcat , start , end , true , false )
}
func (b *Buffer ) safeToInsertTatweel (start , end int ) {
if (b .Flags & ProduceSafeToInsertTatweel ) == 0 {
b .unsafeToBreak (start , end )
return
}
b .setGlyphFlags (GlyphSafeToInsertTatweel , start , end , true , false )
}
func (b *Buffer ) unsafeToConcat (start , end int ) {
if (b .Flags & ProduceUnsafeToConcat ) == 0 {
return
}
b .setGlyphFlags (GlyphUnsafeToConcat , start , end , true , false )
}
func (b *Buffer ) unsafeToBreakFromOutbuffer (start , end int ) {
b .setGlyphFlags (GlyphUnsafeToBreak |GlyphUnsafeToConcat , start , end , true , true )
}
func (b *Buffer ) unsafeToConcatFromOutbuffer (start , end int ) {
if (b .Flags & ProduceUnsafeToConcat ) == 0 {
return
}
b .setGlyphFlags (GlyphUnsafeToConcat , start , end , false , true )
}
func (b *Buffer ) findMinCluster (infos []GlyphInfo , start , end , cluster int ) int {
if start == end {
return cluster
}
if b .ClusterLevel == Characters {
for i := start ; i < end ; i ++ {
cluster = min (cluster , infos [i ].Cluster )
}
return cluster
}
return min (cluster , min (infos [start ].Cluster , infos [end -1 ].Cluster ))
}
func (b *Buffer ) infosSetGlyphFlags (infos []GlyphInfo , start , end , cluster int , mask GlyphMask ) {
if start == end {
return
}
b .maxOps -= end - start
clusterFirst := infos [start ].Cluster
clusterLast := infos [end -1 ].Cluster
if b .ClusterLevel == Characters || (cluster != clusterFirst && cluster != clusterLast ) {
for i := start ; i < end ; i ++ {
if cluster != infos [i ].Cluster {
infos [i ].Mask |= mask
}
}
return
}
if cluster == clusterFirst {
for i := end ; start < i && infos [i -1 ].Cluster != clusterFirst ; i -- {
infos [i -1 ].Mask |= mask
}
} else {
for i := start ; i < end && infos [i ].Cluster != clusterLast ; i ++ {
infos [i ].Mask |= mask
}
}
}
func (b *Buffer ) clearPositions () {
b .haveOutput = false
b .outInfo = b .outInfo [:0 ]
L := len (b .Info )
if cap (b .Pos ) >= L {
b .Pos = b .Pos [:L ]
} else {
b .Pos = make ([]GlyphPosition , L )
}
}
func (b *Buffer ) removeOutput (setOutput bool ) {
b .haveOutput = setOutput
b .outInfo = b .outInfo [:0 ]
}
func (b *Buffer ) clearOutput () {
b .removeOutput (true )
b .idx = 0
}
func (b *Buffer ) clearContext (side uint ) { b .context [side ] = b .context [side ][:0 ] }
func (b *Buffer ) reverseRange (start , end int ) {
if end -start < 2 {
return
}
info := b .Info [start :end ]
pos := b .Pos [start :end ]
L := len (info )
_ = pos [L -1 ]
for i := L /2 - 1 ; i >= 0 ; i -- {
opp := L - 1 - i
info [i ], info [opp ] = info [opp ], info [i ]
pos [i ], pos [opp ] = pos [opp ], pos [i ]
}
}
func (b *Buffer ) Reverse () { b .reverseRange (0 , len (b .Info )) }
func (b *Buffer ) reverseClusters () {
b .reverseGroups (func (gi1 , gi2 *GlyphInfo ) bool {
return gi1 .Cluster == gi2 .Cluster
}, false )
}
func (b *Buffer ) reverseGroups (groupFunc func (*GlyphInfo , *GlyphInfo ) bool , mergeClusters bool ) {
if len (b .Info ) == 0 {
return
}
count := len (b .Info )
start := 0
var i int
for i = 1 ; i < count ; i ++ {
if !groupFunc (&b .Info [i -1 ], &b .Info [i ]) {
if mergeClusters {
b .mergeClusters (start , i )
}
b .reverseRange (start , i )
start = i
}
}
if mergeClusters {
b .mergeClusters (start , i )
}
b .reverseRange (start , i )
b .Reverse ()
}
func (b *Buffer ) swapBuffers () {
b .nextGlyphs (len (b .Info ) - b .idx )
b .haveOutput = false
b .Info , b .outInfo = b .outInfo , b .Info
b .idx = 0
}
func (b *Buffer ) allocateLigID () uint8 {
ligID := uint8 (b .serial & 0x07 )
b .serial ++
if ligID == 0 {
ligID = b .allocateLigID ()
}
return ligID
}
func (b *Buffer ) shiftForward (count int ) bool {
L := len (b .Info )
b .maxOps -= L - b .idx
if b .maxOps < 0 {
return false
}
b .Info = append (b .Info , make ([]GlyphInfo , count )...)
copy (b .Info [b .idx +count :], b .Info [b .idx :L ])
b .idx += count
return true
}
func (b *Buffer ) moveTo (i int ) {
if !b .haveOutput {
b .idx = i
return
}
outL := len (b .outInfo )
if outL < i {
count := i - outL
b .outInfo = append (b .outInfo , b .Info [b .idx :count +b .idx ]...)
b .idx += count
} else if outL > i {
count := outL - i
if b .idx < count {
ok := b .shiftForward (count - b .idx )
if !ok {
return
}
}
b .idx -= count
copy (b .Info [b .idx :], b .outInfo [outL -count :outL ])
b .outInfo = b .outInfo [:outL -count ]
}
}
func (b *Buffer ) collectGlyphs (out *intSet ) {
out .Clear ()
for _ , info := range b .Info {
out .addGlyph (info .Glyph )
}
}
type graphemesIterator struct {
buffer *Buffer
start int
}
func (g *graphemesIterator ) next () (start , end int ) {
info := g .buffer .Info
count := len (info )
start = g .start
if start >= count {
return
}
for end = g .start + 1 ; end < count && info [end ].isContinuation (); end ++ {
}
g .start = end
return start , end
}
func (b *Buffer ) graphemesIterator () (*graphemesIterator , int ) {
return &graphemesIterator {buffer : b }, len (b .Info )
}
type clusterIterator struct {
buffer *Buffer
start int
}
func (c *clusterIterator ) next () (start , end int ) {
info := c .buffer .Info
count := len (info )
start = c .start
if start >= count {
return
}
cluster := info [start ].Cluster
for end = start + 1 ; end < count && cluster == info [end ].Cluster ; end ++ {
}
c .start = end
return start , end
}
func (b *Buffer ) clusterIterator () (*clusterIterator , int ) {
return &clusterIterator {buffer : b }, len (b .Info )
}
type syllableIterator struct {
buffer *Buffer
start int
}
func (c *syllableIterator ) next () (start , end int ) {
info := c .buffer .Info
count := len (c .buffer .Info )
start = c .start
if start >= count {
return
}
syllable := info [start ].syllable
for end = start + 1 ; end < count && syllable == info [end ].syllable ; end ++ {
}
c .start = end
return start , end
}
func (b *Buffer ) syllableIterator () (*syllableIterator , int ) {
return &syllableIterator {buffer : b }, len (b .Info )
}
func (b *Buffer ) sort (start , end int , compar func (a , b *GlyphInfo ) int ) {
for i := start + 1 ; i < end ; i ++ {
j := i
for j > start && compar (&b .Info [j -1 ], &b .Info [i ]) > 0 {
j --
}
if i == j {
continue
}
b .mergeClusters (j , i +1 )
t := b .Info [i ]
copy (b .Info [j +1 :], b .Info [j :i ])
b .Info [j ] = t
}
}
func (b *Buffer ) mergeOutClusters (start , end int ) {
if b .ClusterLevel == Characters {
return
}
if end -start < 2 {
return
}
cluster := b .outInfo [start ].Cluster
for i := start + 1 ; i < end ; i ++ {
cluster = min (cluster , b .outInfo [i ].Cluster )
}
for start != 0 && b .outInfo [start -1 ].Cluster == b .outInfo [start ].Cluster {
start --
}
for end < len (b .outInfo ) && b .outInfo [end -1 ].Cluster == b .outInfo [end ].Cluster {
end ++
}
if end == len (b .outInfo ) {
endC := b .outInfo [end -1 ].Cluster
for i := b .idx ; i < len (b .Info ) && b .Info [i ].Cluster == endC ; i ++ {
b .Info [i ].setCluster (cluster , 0 )
}
}
for i := start ; i < end ; i ++ {
b .outInfo [i ].setCluster (cluster , 0 )
}
}
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 .