text/shaper_gotext.go
Functions
func ClearCache
func (s *GoTextShaper) ClearCache() {
s.mu.Lock()
defer s.mu.Unlock()
s.fontCache = make(map[*FontSource]*font.Font)
}
func NewGoTextShaper
NewGoTextShaper creates a new GoTextShaper backed by go-text/typesetting's
HarfBuzz implementation.
func NewGoTextShaper() *GoTextShaper {
return &GoTextShaper{
shaperPool: sync.Pool{
New: func() any {
return &shaping.HarfbuzzShaper{}
},
},
fontCache: make(map[*FontSource]*font.Font),
}
}
func RemoveSource
RemoveSource removes the cached parsed font for a specific FontSource.
This is useful when a FontSource is closed.
func (s *GoTextShaper) RemoveSource(source *FontSource) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.fontCache, source)
}
func Shape
Shape implements the Shaper interface.
It converts text into positioned glyphs using HarfBuzz shaping via go-text/typesetting.
The font size is obtained from face.Size().
This produces higher-quality output than BuiltinShaper for text that benefits
from kerning, ligatures, or complex script shaping.
func (s *GoTextShaper) Shape(text string, face Face) []ShapedGlyph {
if text == "" || face == nil {
return nil
}
source := face.Source()
if source == nil {
return nil
}
// Get or create the cached go-text Font for this source.
goTextFont, err := s.getOrCreateFont(source)
if err != nil {
// Fall back: return nil on font parsing error.
// In production, users should validate their fonts upfront.
return nil
}
// Create a lightweight font.Face for this shaping call.
// font.Face is NOT safe for concurrent use, so each Shape() call
// gets its own instance. font.NewFace is cheap — it wraps the
// thread-safe *Font and initializes glyph caches.
goTextFace := font.NewFace(goTextFont)
size := face.Size()
runes := []rune(text)
// Convert our Direction to go-text's di.Direction.
dir := mapDirection(face.Direction())
// Detect script from the first non-space rune.
script := detectScript(runes)
// Build shaping input.
input := shaping.Input{
Text: runes,
RunStart: 0,
RunEnd: len(runes),
Direction: dir,
Face: goTextFace,
Size: floatToFixed(size),
Script: script,
Language: language.NewLanguage("en"),
}
// Get a HarfbuzzShaper from the pool (not concurrent-safe, so each
// goroutine needs its own instance).
hbShaper := s.shaperPool.Get().(*shaping.HarfbuzzShaper)
output := hbShaper.Shape(input)
s.shaperPool.Put(hbShaper)
// Convert go-text glyphs to our ShapedGlyph format.
result := convertGlyphs(output.Glyphs, dir, text)
// Post-process: fix tab characters that HarfBuzz mapped to notdef (GID=0).
// Replace with space GID and proper tab-stop advance.
fixTabGlyphs(result, runes, face)
return result
}
Structs
type GoTextShaper struct
GoTextShaper provides HarfBuzz-level text shaping using go-text/typesetting.
It supports advanced OpenType features including:
- Ligature substitution (fi, fl, ffi, etc.)
- Kerning pairs (AV, To, etc.)
- Contextual alternates
- Right-to-left text (Arabic, Hebrew)
- Complex scripts (Devanagari, Thai, etc.)
GoTextShaper is an opt-in replacement for BuiltinShaper. To use it:
shaper := text.NewGoTextShaper()
text.SetShaper(shaper)
defer text.SetShaper(nil) // Reset to default BuiltinShaper
GoTextShaper is safe for concurrent use. It caches parsed font.Font objects
(which are thread-safe) and creates lightweight font.Face instances per
Shape() call (font.Face is NOT safe for concurrent use). The HarfbuzzShaper
instances are pooled via sync.Pool since they also are not concurrent-safe.
type GoTextShaper struct {
// shaperPool pools HarfbuzzShaper instances for concurrent use.
// HarfbuzzShaper has internal mutable state (buffer) and is NOT safe
// for concurrent use, but reusing across sequential calls is efficient.
shaperPool sync.Pool
// mu protects the font cache.
mu sync.RWMutex
// fontCache maps FontSource pointers to parsed go-text Font objects.
// font.Font is read-only and safe for concurrent use, unlike font.Face.
// This avoids re-parsing the font data on every Shape() call.
fontCache map[*FontSource]*font.Font
}
ClearCache removes all cached parsed fonts.
Call this if you no longer need previously loaded fonts and want to free memory.