package shaping
import (
"github.com/go-text/typesetting/di"
"github.com/go-text/typesetting/harfbuzz"
"golang.org/x/image/math/fixed"
)
type HarfbuzzShaper struct {
buf *harfbuzz .Buffer
fonts fontLRU
features []harfbuzz .Feature
}
func (h *HarfbuzzShaper ) SetFontCacheSize (size int ) {
h .fonts .maxSizeOffset = size - defaultFontCacheSize
}
var _ Shaper = (*HarfbuzzShaper )(nil )
type Shaper interface {
Shape (Input ) Output
}
const (
scaleShift = 6
)
func clamp(val , low , high int ) int {
if val < low {
return low
}
if val > high {
return high
}
return val
}
func (t *HarfbuzzShaper ) Shape (input Input ) Output {
if t .buf == nil {
t .buf = harfbuzz .NewBuffer ()
} else {
t .buf .Clear ()
}
runes , start , end := input .Text , input .RunStart , input .RunEnd
if end < start {
end , start = start , end
}
start = clamp (start , 0 , len (runes ))
end = clamp (end , 0 , len (runes ))
t .buf .AddRunes (runes , start , end -start )
isSideways := false
if input .Direction .IsSideways () {
input .Direction = input .Direction .SwitchAxis ()
isSideways = true
}
t .buf .Props .Direction = input .Direction .Harfbuzz ()
t .buf .Props .Language = input .Language
t .buf .Props .Script = input .Script
font , ok := t .fonts .Get (input .Face .Font )
if !ok {
font = harfbuzz .NewFont (input .Face )
t .fonts .Put (input .Face .Font , font )
}
font .XScale = int32 (input .Size .Ceil ()) << scaleShift
font .YScale = font .XScale
if L := len (input .FontFeatures ); cap (t .features ) < L {
t .features = make ([]harfbuzz .Feature , L )
} else {
t .features = t .features [0 :L ]
}
for i , f := range input .FontFeatures {
t .features [i ] = harfbuzz .Feature {
Tag : f .Tag ,
Value : f .Value ,
Start : harfbuzz .FeatureGlobalStart ,
End : harfbuzz .FeatureGlobalEnd ,
}
}
t .buf .Shape (font , t .features )
isVertical := input .Direction .IsVertical ()
glyphs := make ([]Glyph , len (t .buf .Info ))
for i := range glyphs {
g := t .buf .Info [i ].Glyph
glyphs [i ] = Glyph {
ClusterIndex : t .buf .Info [i ].Cluster ,
GlyphID : g ,
Mask : t .buf .Info [i ].Mask ,
}
extents , ok := font .GlyphExtents (g )
if !ok {
continue
}
glyphs [i ].Width = fixed .I (int (extents .Width )) >> scaleShift
glyphs [i ].Height = fixed .I (int (extents .Height )) >> scaleShift
glyphs [i ].XBearing = fixed .I (int (extents .XBearing )) >> scaleShift
glyphs [i ].YBearing = fixed .I (int (extents .YBearing )) >> scaleShift
if isVertical {
glyphs [i ].YAdvance = fixed .I (int (t .buf .Pos [i ].YAdvance )) >> scaleShift
glyphs [i ].Advance = glyphs [i ].YAdvance
} else {
glyphs [i ].XAdvance = fixed .I (int (t .buf .Pos [i ].XAdvance )) >> scaleShift
glyphs [i ].Advance = glyphs [i ].XAdvance
}
glyphs [i ].XOffset = fixed .I (int (t .buf .Pos [i ].XOffset )) >> scaleShift
glyphs [i ].YOffset = fixed .I (int (t .buf .Pos [i ].YOffset )) >> scaleShift
}
countClusters (glyphs , input .RunEnd , input .Direction .Progression ())
out := Output {
Glyphs : glyphs ,
Direction : input .Direction ,
Face : input .Face ,
Size : input .Size ,
}
out .Runes .Offset = input .RunStart
out .Runes .Count = input .RunEnd - input .RunStart
if isSideways {
out .sideways ()
}
fontExtents := font .ExtentsForDirection (out .Direction .Harfbuzz ())
out .LineBounds = Bounds {
Ascent : fixed .I (int (fontExtents .Ascender )) >> scaleShift ,
Descent : fixed .I (int (fontExtents .Descender )) >> scaleShift ,
Gap : fixed .I (int (fontExtents .LineGap )) >> scaleShift ,
}
out .RecalculateAll ()
return out
}
func countClusters(glyphs []Glyph , textLen int , dir di .Progression ) {
currentCluster := -1
runesInCluster := 0
glyphsInCluster := 0
previousCluster := textLen
for i := range glyphs {
g := glyphs [i ].ClusterIndex
if g != currentCluster {
runesInCluster = 0
glyphsInCluster = 1
currentCluster = g
nextCluster := -1
glyphCountLoop :
for k := i + 1 ; k < len (glyphs ); k ++ {
if glyphs [k ].ClusterIndex == g {
glyphsInCluster ++
} else {
nextCluster = glyphs [k ].ClusterIndex
break glyphCountLoop
}
}
if nextCluster == -1 {
nextCluster = textLen
}
switch dir {
case di .FromTopLeft :
runesInCluster = nextCluster - currentCluster
case di .TowardTopLeft :
runesInCluster = previousCluster - currentCluster
}
previousCluster = g
}
glyphs [i ].GlyphCount = glyphsInCluster
glyphs [i ].RuneCount = runesInCluster
}
}
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 .