text/glyph_run.go

Functions Structs

Functions

func AddGlyph

AddGlyph adds a glyph to the batch.

func (b *GlyphRunBuilder) AddGlyph(fontID uint64, glyphID GlyphID, pos Point, size float32) {
	b.instances = append(b.instances, GlyphInstance{
		FontID:		fontID,
		GlyphID:	glyphID,
		Position:	pos,
		Size:		size,
	})
}

func AddShapedGlyph

AddShapedGlyph adds a ShapedGlyph to the batch.

func (b *GlyphRunBuilder) AddShapedGlyph(fontID uint64, glyph *ShapedGlyph, size float32) {
	if glyph == nil {
		return
	}
	b.instances = append(b.instances, GlyphInstance{
		FontID:		fontID,
		GlyphID:	glyph.GID,
		Position:	Point{X: float32(glyph.X), Y: float32(glyph.Y)},
		Size:		size,
	})
}

func AddShapedGlyphs

AddShapedGlyphs adds multiple shaped glyphs from a slice.

All glyphs are assumed to be from the same font.

func (b *GlyphRunBuilder) AddShapedGlyphs(fontID uint64, glyphs []ShapedGlyph, origin Point, size float32) {
	for i := range glyphs {
		glyph := &glyphs[i]
		pos := Point{
			X:	origin.X + float32(glyph.X),
			Y:	origin.Y + float32(glyph.Y),
		}
		b.instances = append(b.instances, GlyphInstance{
			FontID:		fontID,
			GlyphID:	glyph.GID,
			Position:	pos,
			Size:		size,
		})
	}
}

func AddShapedRun

AddShapedRun adds all glyphs from a ShapedRun.

The origin parameter specifies the starting position for the run.

func (b *GlyphRunBuilder) AddShapedRun(run *ShapedRun, origin Point) {
	if run == nil || len(run.Glyphs) == 0 || run.Face == nil {
		return
	}

	font := run.Face.Source().Parsed()
	if font == nil {
		return
	}

	fontID := computeFontID(font)
	size := float32(run.Size)

	for i := range run.Glyphs {
		glyph := &run.Glyphs[i]
		pos := Point{
			X:	origin.X + float32(glyph.X),
			Y:	origin.Y + float32(glyph.Y),
		}
		b.instances = append(b.instances, GlyphInstance{
			FontID:		fontID,
			GlyphID:	glyph.GID,
			Position:	pos,
			Size:		size,
		})
	}
}

func Build

Build retrieves all glyph outlines from cache and returns draw commands.

The createGlyph function is called to create outlines for cache misses.

If createGlyph is nil, cache misses result in nil outlines being skipped.

 

Returns a slice of DrawCommands ready for rendering.

func (b *GlyphRunBuilder) Build(createGlyph func(fontID uint64, glyphID GlyphID, size float32) *GlyphOutline) []DrawCommand {
	if len(b.instances) == 0 {
		return nil
	}

	commands := make([]DrawCommand, 0, len(b.instances))

	for i := range b.instances {
		inst := &b.instances[i]

		// Build cache key
		key := OutlineCacheKey{
			FontID:		inst.FontID,
			GID:		inst.GlyphID,
			Size:		sizeToInt16(float64(inst.Size)),
			Hinting:	HintingNone,
		}

		// Get or create outline
		var outline *GlyphOutline
		if createGlyph != nil {
			outline = b.cache.GetOrCreate(key, func() *GlyphOutline {
				return createGlyph(inst.FontID, inst.GlyphID, inst.Size)
			})
		} else {
			outline = b.cache.Get(key)
		}

		// Skip glyphs without outlines (e.g., spaces)
		if outline == nil || outline.IsEmpty() {
			continue
		}

		// Create transform for glyph positioning
		// Y-flip is applied because fonts have Y-up, screen has Y-down
		transform := &AffineTransform{
			A:	1,
			B:	0,
			C:	0,
			D:	-1,	// Y-flip
			Tx:	inst.Position.X,
			Ty:	inst.Position.Y,
		}

		commands = append(commands, DrawCommand{
			Outline:	outline,
			Transform:	transform,
			Instance:	*inst,
		})
	}

	return commands
}

func BuildTransformed

BuildTransformed is like Build but applies an additional transformation to all commands.

func (b *GlyphRunBuilder) BuildTransformed(
	createGlyph func(fontID uint64, glyphID GlyphID, size float32) *GlyphOutline,
	userTransform *AffineTransform,
) []DrawCommand {
	if len(b.instances) == 0 {
		return nil
	}

	commands := make([]DrawCommand, 0, len(b.instances))

	for i := range b.instances {
		inst := &b.instances[i]

		// Build cache key
		key := OutlineCacheKey{
			FontID:		inst.FontID,
			GID:		inst.GlyphID,
			Size:		sizeToInt16(float64(inst.Size)),
			Hinting:	HintingNone,
		}

		// Get or create outline
		var outline *GlyphOutline
		if createGlyph != nil {
			outline = b.cache.GetOrCreate(key, func() *GlyphOutline {
				return createGlyph(inst.FontID, inst.GlyphID, inst.Size)
			})
		} else {
			outline = b.cache.Get(key)
		}

		// Skip glyphs without outlines
		if outline == nil || outline.IsEmpty() {
			continue
		}

		// Create glyph positioning transform
		glyphTransform := &AffineTransform{
			A:	1,
			B:	0,
			C:	0,
			D:	-1,	// Y-flip
			Tx:	inst.Position.X,
			Ty:	inst.Position.Y,
		}

		// Combine with user transform
		var finalTransform *AffineTransform
		if userTransform != nil {
			finalTransform = userTransform.Multiply(glyphTransform)
		} else {
			finalTransform = glyphTransform
		}

		commands = append(commands, DrawCommand{
			Outline:	outline,
			Transform:	finalTransform,
			Instance:	*inst,
		})
	}

	return commands
}

func Cache

Cache returns the glyph cache used by this builder.

func (b *GlyphRunBuilder) Cache() *GlyphCache {
	return b.cache
}

func Clear

Clear resets the builder for reuse.

The cache is not cleared.

func (b *GlyphRunBuilder) Clear() {
	b.instances = b.instances[:0]
}

func Get

Get retrieves a GlyphRunBuilder from the pool.

func (p *GlyphRunBuilderPool) Get() *GlyphRunBuilder {
	builder := p.pool.Get().(*GlyphRunBuilder)
	builder.cache = p.cache
	return builder
}

func Instances

Instances returns a copy of the current glyph instances.

func (b *GlyphRunBuilder) Instances() []GlyphInstance {
	if len(b.instances) == 0 {
		return nil
	}
	result := make([]GlyphInstance, len(b.instances))
	copy(result, b.instances)
	return result
}

func Len

Len returns the number of glyph instances currently buffered.

func (b *GlyphRunBuilder) Len() int {
	return len(b.instances)
}

func NewGlyphRunBuilder

NewGlyphRunBuilder creates a builder that uses the given cache.

If cache is nil, the global glyph cache is used.

func NewGlyphRunBuilder(cache *GlyphCache) *GlyphRunBuilder {
	if cache == nil {
		cache = GetGlobalGlyphCache()
	}
	return &GlyphRunBuilder{
		cache:		cache,
		instances:	make([]GlyphInstance, 0, 64),
	}
}

func NewGlyphRunBuilderPool

NewGlyphRunBuilderPool creates a new pool with the given cache.

If cache is nil, the global glyph cache is used.

func NewGlyphRunBuilderPool(cache *GlyphCache) *GlyphRunBuilderPool {
	if cache == nil {
		cache = GetGlobalGlyphCache()
	}
	return &GlyphRunBuilderPool{
		pool: sync.Pool{
			New: func() any {
				return NewGlyphRunBuilder(nil)
			},
		},
		cache:	cache,
	}
}

func Put

Put returns a GlyphRunBuilder to the pool.

The builder is cleared before being returned.

func (p *GlyphRunBuilderPool) Put(builder *GlyphRunBuilder) {
	if builder != nil {
		builder.Clear()
		p.pool.Put(builder)
	}
}

func SetCache

SetCache sets the glyph cache used by this builder.

If cache is nil, the global glyph cache is used.

func (b *GlyphRunBuilder) SetCache(cache *GlyphCache) {
	if cache == nil {
		cache = GetGlobalGlyphCache()
	}
	b.cache = cache
}

Structs

type GlyphInstance struct

GlyphInstance represents a single glyph to be rendered at a specific position.

It contains all information needed to look up the glyph outline and position it.

type GlyphInstance struct {
	// FontID uniquely identifies the font.
	FontID	uint64

	// GlyphID is the glyph index within the font.
	GlyphID	GlyphID

	// Position is where the glyph should be drawn.
	Position	Point

	// Size is the font size in pixels (ppem).
	Size	float32
}

type Point struct

Point represents a 2D point for glyph positioning.

type Point struct {
	X, Y float32
}

type DrawCommand struct

DrawCommand represents a single glyph draw command.

Contains the outline path and the transformation to apply.

type DrawCommand struct {
	// Outline is the glyph outline path.
	Outline	*GlyphOutline

	// Transform is the transformation to apply to the outline.
	Transform	*AffineTransform

	// Instance is the original glyph instance.
	Instance	GlyphInstance
}

type GlyphRunBuilder struct

GlyphRunBuilder batches glyphs for efficient rendering.

It accumulates glyphs from shaped text and creates draw commands

by looking up glyph outlines from the cache.

 

GlyphRunBuilder is NOT safe for concurrent use.

Each goroutine should have its own builder.

type GlyphRunBuilder struct {
	cache		*GlyphCache
	instances	[]GlyphInstance
}

type GlyphRunBuilderPool struct

GlyphRunBuilderPool provides pooled GlyphRunBuilders for high-concurrency scenarios.

type GlyphRunBuilderPool struct {
	pool	sync.Pool
	cache	*GlyphCache
}