text/shaper_gotext.go

Functions Structs

Functions

func ClearCache

ClearCache removes all cached parsed fonts.

Call this if you no longer need previously loaded fonts and want to free memory.

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
}