scene/text.go

Functions Structs

Functions

func Bounds

Bounds returns the bounding rectangle of the text.

func (ts *TextShape) Bounds() Rect {
	if ts.path == nil {
		return EmptyRect()
	}
	return ts.path.Bounds()
}

func Config

Config returns the current configuration.

func (r *TextRenderer) Config() TextRendererConfig {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.config
}

func DefaultTextRendererConfig

DefaultTextRendererConfig returns the default configuration.

func DefaultTextRendererConfig() TextRendererConfig {
	return TextRendererConfig{
		FlipY:			false,
		SubpixelPositioning:	true,
		HintingEnabled:		false,
	}
}

func DrawGlyphs

DrawGlyphs draws pre-shaped glyphs to the scene as a TagText glyph run.

Like DrawText but skips shaping — use when glyphs are already shaped.

The original text string is required for GPU renderer's DrawString path.

func (s *Scene) DrawGlyphs(str string, glyphs []text.ShapedGlyph, face text.Face, x, y float32, brush Brush) error {
	if len(glyphs) == 0 {
		return nil
	}
	if face == nil {
		return &text.FontError{Reason: "face is nil"}
	}

	return s.encodeTextRun(str, glyphs, face, x, y, brush)
}

func DrawText

DrawText shapes text and records it as a TagText glyph run in the scene.

Shaping (HarfBuzz) is performed once at recording time. Resolution to pixels

(atlas, outline, text op) is deferred to render/playback time via the renderer's

text resolution capability. See ADR-022.

 

Breaking change from v0.45: previously converted glyphs to vector paths

(one Fill per glyph, no hinting). Now stores compact glyph references

that renderers resolve at playback time with full hinting and atlas batching.

func (s *Scene) DrawText(str string, face text.Face, x, y float32, brush Brush) error {
	if str == "" {
		return nil
	}
	if face == nil {
		return &text.FontError{Reason: "face is nil"}
	}

	shaped := text.Shape(str, face)
	if len(shaped) == 0 {
		return nil
	}

	return s.encodeTextRun(str, shaped, face, x, y, brush)
}

func Get

Get retrieves a renderer from the pool.

func (p *TextRendererPool) Get() *TextRenderer {
	return p.pool.Get().(*TextRenderer)
}

func NewTextRenderer

NewTextRenderer creates a new text renderer with default configuration.

func NewTextRenderer() *TextRenderer {
	return NewTextRendererWithConfig(DefaultTextRendererConfig())
}

func NewTextRendererPool

NewTextRendererPool creates a new pool of text renderers.

func NewTextRendererPool() *TextRendererPool {
	return &TextRendererPool{
		pool: sync.Pool{
			New: func() any {
				return NewTextRenderer()
			},
		},
	}
}

func NewTextRendererWithConfig

NewTextRendererWithConfig creates a new text renderer with the given configuration.

func NewTextRendererWithConfig(config TextRendererConfig) *TextRenderer {
	return &TextRenderer{
		extractor:	text.NewOutlineExtractor(),
		pathPool:	NewPathPool(),
		config:		config,
	}
}

func NewTextShape

NewTextShape creates a TextShape from text.

The text is shaped and converted to a path at the specified position.

func NewTextShape(str string, face text.Face, x, y float32) (*TextShape, error) {
	if str == "" {
		return &TextShape{path: NewPath()}, nil
	}

	renderer := NewTextRenderer()
	rendered, err := renderer.RenderText(str, face)
	if err != nil {
		return nil, err
	}

	// Combine all glyph paths with offset
	composite := NewPath()
	for _, rg := range rendered {
		if rg.Path != nil && !rg.Path.IsEmpty() {
			// Transform path to final position
			transformed := rg.Path.Transform(TranslateAffine(x, y))
			appendPath(composite, transformed)
		}
	}

	return &TextShape{path: composite}, nil
}

func Put

Put returns a renderer to the pool.

func (p *TextRendererPool) Put(r *TextRenderer) {
	if r != nil {
		p.pool.Put(r)
	}
}

func RenderGlyph

RenderGlyph converts a single shaped glyph to a renderable path.

The path is positioned at the glyph's location and scaled appropriately.

func (r *TextRenderer) RenderGlyph(glyph text.ShapedGlyph, face text.Face) (*RenderedGlyph, error) {
	r.mu.RLock()
	config := r.config
	r.mu.RUnlock()

	// Get the font source and size
	source := face.Source()
	if source == nil {
		return nil, &text.FontError{Reason: "face has no font source"}
	}
	size := face.Size()
	parsed := source.Parsed()

	cache := r.ensureCache()
	fontID := computeSceneTextFontID(source)
	sizeKey := computeSizeKey(size)

	cacheKey := text.OutlineCacheKey{
		FontID:		fontID,
		GID:		glyph.GID,
		Size:		sizeKey,
		Hinting:	text.HintingNone,
	}
	outline := cache.GetOrCreate(cacheKey, func() *text.GlyphOutline {
		o, err := r.extractor.ExtractOutline(parsed, glyph.GID, size)
		if err != nil || o == nil || o.IsEmpty() {
			return nil
		}
		return o
	})

	// Create the rendered glyph
	rendered := &RenderedGlyph{
		Advance:	float32(glyph.XAdvance),
		X:		float32(glyph.X),
		Y:		float32(glyph.Y),
		GID:		glyph.GID,
		Type:		text.GlyphTypeOutline,
		Cluster:	glyph.Cluster,
	}

	// Handle empty outlines (like space)
	if outline == nil {
		rendered.Path = nil
		rendered.Bounds = EmptyRect()
		return rendered, nil
	}

	// Convert outline to path with positioning
	path := r.outlineToPath(outline, float32(glyph.X), float32(glyph.Y), config)
	rendered.Path = path
	rendered.Bounds = path.Bounds()

	return rendered, nil
}

func RenderGlyphs

RenderGlyphs converts multiple shaped glyphs to renderable paths.

Returns a slice of rendered glyphs in the same order as input.

func (r *TextRenderer) RenderGlyphs(glyphs []text.ShapedGlyph, face text.Face) ([]*RenderedGlyph, error) {
	if len(glyphs) == 0 {
		return nil, nil
	}

	r.mu.RLock()
	config := r.config
	r.mu.RUnlock()

	// Get the font source and size
	source := face.Source()
	if source == nil {
		return nil, &text.FontError{Reason: "face has no font source"}
	}
	size := face.Size()
	parsed := source.Parsed()

	cache := r.ensureCache()
	fontID := computeSceneTextFontID(source)
	sizeKey := computeSizeKey(size)

	// Render all glyphs
	rendered := make([]*RenderedGlyph, len(glyphs))
	for i, glyph := range glyphs {
		rg := &RenderedGlyph{
			Advance:	float32(glyph.XAdvance),
			X:		float32(glyph.X),
			Y:		float32(glyph.Y),
			GID:		glyph.GID,
			Type:		text.GlyphTypeOutline,
			Cluster:	glyph.Cluster,
		}

		cacheKey := text.OutlineCacheKey{
			FontID:		fontID,
			GID:		glyph.GID,
			Size:		sizeKey,
			Hinting:	text.HintingNone,
		}
		outline := cache.GetOrCreate(cacheKey, func() *text.GlyphOutline {
			o, err := r.extractor.ExtractOutline(parsed, glyph.GID, size)
			if err != nil || o == nil || o.IsEmpty() {
				return nil
			}
			return o
		})

		if outline == nil {
			rg.Path = nil
			rg.Bounds = EmptyRect()
			rendered[i] = rg
			continue
		}

		// Convert to path
		path := r.outlineToPath(outline, float32(glyph.X), float32(glyph.Y), config)
		rg.Path = path
		rg.Bounds = path.Bounds()
		rendered[i] = rg
	}

	return rendered, nil
}

func RenderRun

RenderRun converts a shaped run to renderable glyphs.

func (r *TextRenderer) RenderRun(run *text.ShapedRun) ([]*RenderedGlyph, error) {
	if run == nil || len(run.Glyphs) == 0 {
		return nil, nil
	}
	return r.RenderGlyphs(run.Glyphs, run.Face)
}

func RenderText

RenderText shapes and renders text in one operation.

Uses the global shaper for text shaping.

func (r *TextRenderer) RenderText(str string, face text.Face) ([]*RenderedGlyph, error) {
	if str == "" {
		return nil, nil
	}

	// Shape the text
	shaped := text.Shape(str, face)
	if len(shaped) == 0 {
		return nil, nil
	}

	return r.RenderGlyphs(shaped, face)
}

func SetConfig

SetConfig updates the configuration.

func (r *TextRenderer) SetConfig(config TextRendererConfig) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.config = config
}

func TextAdvance

TextAdvance returns the total advance width of rendered glyphs.

func TextAdvance(glyphs []*RenderedGlyph) float32 {
	if len(glyphs) == 0 {
		return 0
	}

	// The advance is the position of the last glyph plus its advance
	last := glyphs[len(glyphs)-1]
	return last.X + last.Advance
}

func TextBounds

TextBounds computes the bounding box for rendered text.

func TextBounds(glyphs []*RenderedGlyph) Rect {
	if len(glyphs) == 0 {
		return EmptyRect()
	}

	bounds := EmptyRect()
	for _, rg := range glyphs {
		if rg.Path != nil {
			bounds = bounds.Union(rg.Bounds)
		}
	}

	return bounds
}

func ToCompositePath

ToCompositePath combines multiple rendered glyphs into a single path.

This is useful for text that will be rendered as a single unit.

func (r *TextRenderer) ToCompositePath(glyphs []*RenderedGlyph) *Path {
	if len(glyphs) == 0 {
		return nil
	}

	composite := NewPath()
	for _, rg := range glyphs {
		if rg.Path != nil && !rg.Path.IsEmpty() {
			appendPath(composite, rg.Path)
		}
	}

	return composite
}

func ToPath

ToPath returns the path representation of the text.

func (ts *TextShape) ToPath() *Path {
	return ts.path
}

Structs

type TextRenderer struct

TextRenderer converts shaped text to scene paths for GPU rendering.

It supports glyph outlines and handles positioning, transforms, and caching.

 

TextRenderer is safe for concurrent use.

type TextRenderer struct {
	mu	sync.RWMutex

	// extractor extracts glyph outlines from fonts
	extractor	*text.OutlineExtractor

	// pathPool reuses Path objects to reduce allocations
	pathPool	*PathPool

	// config holds renderer configuration
	config	TextRendererConfig

	// cache holds cached glyph outlines (lazy-initialized from global)
	cache	*text.GlyphCache
}

type TextRendererConfig struct

TextRendererConfig holds configuration for TextRenderer.

type TextRendererConfig struct {
	// FlipY inverts the Y-axis for glyph outlines (default: false).
	// Our OutlineExtractor preserves sfnt's Y-down convention
	// (Y=0 at baseline, Y<0 above, Y>0 below), which matches
	// screen coordinates directly. Set to true only if your
	// coordinate system has Y increasing upward.
	FlipY	bool

	// SubpixelPositioning enables fractional glyph positioning.
	// When false, glyphs are snapped to integer pixel positions.
	SubpixelPositioning	bool

	// HintingEnabled enables font hinting for sharper rendering at small sizes.
	HintingEnabled	bool
}

type RenderedGlyph struct

RenderedGlyph represents a glyph that has been converted to a path.

type RenderedGlyph struct {
	// Path is the vector path representing the glyph outline.
	// May be nil for non-outline glyphs (bitmap, empty).
	Path	*Path

	// Bounds is the bounding box of the rendered glyph.
	Bounds	Rect

	// Advance is the distance to the next glyph position.
	Advance	float32

	// Position is the glyph position relative to the text origin.
	X, Y	float32

	// GID is the glyph ID.
	GID	text.GlyphID

	// Type indicates the glyph type.
	Type	text.GlyphType

	// Cluster is the source character index.
	Cluster	int
}

type TextRendererPool struct

TextRendererPool manages a pool of TextRenderers for concurrent use.

type TextRendererPool struct {
	pool sync.Pool
}

type TextShape struct

TextShape represents a shaped text string as a scene Shape.

This allows text to be used with fill/stroke operations.

type TextShape struct {
	path *Path
}