text/msdf/generator.go

Functions Structs

Functions

func Config

Config returns the generator's configuration.

func (g *Generator) Config() Config {
	return g.config
}

func DefaultGenerator

DefaultGenerator creates a new MSDF generator with default configuration.

func DefaultGenerator() *Generator {
	return NewGenerator(DefaultConfig())
}

func ErrorCorrection

ErrorCorrection applies error correction to fix artifacts.

This handles cases where the median produces incorrect results.

func ErrorCorrection(msdf *MSDF, threshold float64) {
	if msdf == nil {
		return
	}

	w, h := msdf.Width, msdf.Height

	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			r, g, b := msdf.GetPixel(x, y)

			// Get the median
			med := median3Byte(r, g, b)

			// Calculate error for each channel
			rErr := math.Abs(float64(r) - float64(med))
			gErr := math.Abs(float64(g) - float64(med))
			bErr := math.Abs(float64(b) - float64(med))

			maxErr := max(rErr, gErr, bErr)
			thresholdVal := threshold * 255

			// If error is too high, correct by moving toward median
			if maxErr > thresholdVal {
				if rErr > thresholdVal {
					r = correctChannel(r, med, thresholdVal)
				}
				if gErr > thresholdVal {
					g = correctChannel(g, med, thresholdVal)
				}
				if bErr > thresholdVal {
					b = correctChannel(b, med, thresholdVal)
				}
				msdf.SetPixel(x, y, r, g, b)
			}
		}
	}
}

func Generate

Generate creates an MSDF texture from a glyph outline.

Returns nil if the outline is empty.

func (g *Generator) Generate(outline *text.GlyphOutline) (*MSDF, error) {
	if err := g.config.Validate(); err != nil {
		return nil, err
	}

	if outline == nil || outline.IsEmpty() {
		// Return a simple transparent MSDF for empty glyphs
		return g.generateEmpty(), nil
	}

	// Convert outline to shape with edges
	shape := FromOutline(outline)
	if shape.EdgeCount() == 0 {
		return g.generateEmpty(), nil
	}

	// Assign colors to edges based on corners
	AssignColors(shape, g.config.AngleThreshold)

	// Calculate scaling and translation
	shapeBounds := shape.Bounds
	if shapeBounds.IsEmpty() {
		return g.generateEmpty(), nil
	}

	// Add padding for the distance range
	padding := g.config.Range
	bounds := shapeBounds.Expand(padding)

	// Calculate scale to fit in texture size
	scale := calculateScale(bounds, g.config.Size, padding)

	// Center the expanded bounds within the MSDF cell.
	// With uniform scaling (min of scaleX, scaleY), the non-limiting axis
	// doesn't fill the available space. Centering ensures the glyph content
	// is at the center of the cell, which allows symmetric padding in the
	// screen quad computation (gpu_text.go).
	occupiedW := bounds.Width() * scale
	occupiedH := bounds.Height() * scale
	translateX := (float64(g.config.Size) - occupiedW) / 2
	translateY := (float64(g.config.Size) - occupiedH) / 2

	// Create the MSDF texture
	msdf := &MSDF{
		Data:		make([]byte, g.config.Size*g.config.Size*3),
		Width:		g.config.Size,
		Height:		g.config.Size,
		Bounds:		bounds,
		Scale:		scale,
		TranslateX:	translateX,
		TranslateY:	translateY,
	}

	// Generate the distance field
	g.generateDistanceField(msdf, shape)

	return msdf, nil
}

func Generate

Generate generates an MSDF using a pooled generator.

func (p *GeneratorPool) Generate(outline *text.GlyphOutline) (*MSDF, error) {
	gen := p.Get()
	defer p.Put(gen)
	return gen.Generate(outline)
}

func GenerateBatch

GenerateBatch generates MSDF textures for multiple outlines.

This is more efficient than generating them one by one.

func (g *Generator) GenerateBatch(outlines []*text.GlyphOutline) ([]*MSDF, error) {
	if err := g.config.Validate(); err != nil {
		return nil, err
	}

	results := make([]*MSDF, len(outlines))
	var wg sync.WaitGroup
	var firstError error
	var errMu sync.Mutex

	for i, outline := range outlines {
		wg.Add(1)
		go func(idx int, o *text.GlyphOutline) {
			defer wg.Done()

			msdf, err := g.Generate(o)
			if err != nil {
				errMu.Lock()
				if firstError == nil {
					firstError = err
				}
				errMu.Unlock()
				return
			}
			results[idx] = msdf
		}(i, outline)
	}

	wg.Wait()

	if firstError != nil {
		return nil, firstError
	}
	return results, nil
}

func GenerateWithMetrics

GenerateWithMetrics generates an MSDF and returns metrics.

func (g *Generator) GenerateWithMetrics(outline *text.GlyphOutline) (*MSDF, *Metrics, error) {
	if err := g.config.Validate(); err != nil {
		return nil, nil, err
	}

	if outline == nil || outline.IsEmpty() {
		msdf := g.generateEmpty()
		metrics := &Metrics{
			Width:		msdf.Width,
			Height:		msdf.Height,
			Scale:		msdf.Scale,
			Bounds:		msdf.Bounds,
			NumContours:	0,
			NumEdges:	0,
		}
		return msdf, metrics, nil
	}

	// Convert outline to shape
	shape := FromOutline(outline)

	// Collect metrics
	metrics := &Metrics{
		Width:		g.config.Size,
		Height:		g.config.Size,
		NumContours:	len(shape.Contours),
		NumEdges:	shape.EdgeCount(),
		Bounds:		shape.Bounds,
	}

	// Generate the MSDF
	msdf, err := g.Generate(outline)
	if err != nil {
		return nil, nil, err
	}

	metrics.Scale = msdf.Scale

	return msdf, metrics, nil
}

func Get

Get retrieves a generator from the pool.

func (p *GeneratorPool) Get() *Generator {
	return p.pool.Get().(*Generator)
}

func MedianFilter

MedianFilter applies a median filter to clean up noise.

This is a post-processing step that can improve quality.

func MedianFilter(msdf *MSDF) *MSDF {
	if msdf == nil {
		return nil
	}

	result := &MSDF{
		Data:		make([]byte, len(msdf.Data)),
		Width:		msdf.Width,
		Height:		msdf.Height,
		Bounds:		msdf.Bounds,
		Scale:		msdf.Scale,
		TranslateX:	msdf.TranslateX,
		TranslateY:	msdf.TranslateY,
	}

	w, h := msdf.Width, msdf.Height

	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			rVals, gVals, bVals := collectNeighborhood(msdf, x, y, w, h)
			rMed := median9(rVals)
			gMed := median9(gVals)
			bMed := median9(bVals)

			result.SetPixel(x, y, rMed, gMed, bMed)
		}
	}

	return result
}

func NewGenerator

NewGenerator creates a new MSDF generator with the given configuration.

func NewGenerator(config Config) *Generator {
	return &Generator{
		config: config,
	}
}

func NewGeneratorPool

NewGeneratorPool creates a new generator pool with the given configuration.

func NewGeneratorPool(config Config) *GeneratorPool {
	return &GeneratorPool{
		config:	config,
		pool: sync.Pool{
			New: func() interface{} {
				return NewGenerator(config)
			},
		},
	}
}

func Put

Put returns a generator to the pool.

func (p *GeneratorPool) Put(g *Generator) {
	// Reset config in case it was modified
	g.config = p.config
	p.pool.Put(g)
}

func SetConfig

SetConfig updates the generator's configuration.

func (g *Generator) SetConfig(config Config) {
	g.config = config
}

Structs

type Generator struct

Generator creates MSDF textures from glyph outlines.

type Generator struct {
	config Config
}

type Metrics struct

Metrics returns statistics about the generated MSDF.

type Metrics struct {
	// Width and Height of the texture.
	Width, Height	int

	// Scale factor used.
	Scale	float64

	// Bounds in outline space.
	Bounds	Rect

	// NumContours is the number of contours in the source shape.
	NumContours	int

	// NumEdges is the total number of edges.
	NumEdges	int
}

type GeneratorPool struct

GeneratorPool manages a pool of generators for concurrent use.

type GeneratorPool struct {
	pool	sync.Pool
	config	Config
}