text/msdf/contour.go

Functions Structs

Functions

func AddContour

AddContour appends a contour to the shape.

func (s *Shape) AddContour(c *Contour) {
	s.Contours = append(s.Contours, c)
}

func AddEdge

AddEdge appends an edge to the contour.

func (c *Contour) AddEdge(e Edge) {
	c.Edges = append(c.Edges, e)
}

func AssignColors

AssignColors assigns edge colors to preserve corners.

This is the key MSDF innovation - corners get different colors

so that the median operation can preserve them.

func AssignColors(shape *Shape, angleThreshold float64) {
	for _, contour := range shape.Contours {
		if len(contour.Edges) == 0 {
			continue
		}

		assignContourColors(contour, angleThreshold)
	}
}

func Bounds

Bounds returns the bounding box of all edges in the contour.

func (c *Contour) Bounds() Rect {
	if len(c.Edges) == 0 {
		return Rect{}
	}

	bounds := c.Edges[0].Bounds()
	for i := 1; i < len(c.Edges); i++ {
		bounds = bounds.Union(c.Edges[i].Bounds())
	}
	return bounds
}

func CalculateBounds

CalculateBounds computes and stores the overall bounding box.

func (s *Shape) CalculateBounds() {
	if len(s.Contours) == 0 {
		s.Bounds = Rect{}
		return
	}

	s.Bounds = s.Contours[0].Bounds()
	for i := 1; i < len(s.Contours); i++ {
		s.Bounds = s.Bounds.Union(s.Contours[i].Bounds())
	}
}

func CalculateWinding

CalculateWinding calculates and stores the winding direction.

Positive = CCW (outer contour), Negative = CW (inner/hole).

func (c *Contour) CalculateWinding() {
	// Calculate the signed area using the shoelace formula.
	// Sum up the cross products of consecutive edge endpoints.
	var area float64
	for i := range c.Edges {
		p0 := c.Edges[i].StartPoint()
		p1 := c.Edges[i].EndPoint()
		area += p0.Cross(p1)
	}
	c.Winding = area / 2
}

func Clone

Clone creates a deep copy of the contour.

func (c *Contour) Clone() *Contour {
	clone := &Contour{
		Edges:		make([]Edge, len(c.Edges)),
		Winding:	c.Winding,
	}
	for i := range c.Edges {
		clone.Edges[i] = c.Edges[i].Clone()
	}
	return clone
}

func EdgeCount

EdgeCount returns the total number of edges across all contours.

func (s *Shape) EdgeCount() int {
	count := 0
	for _, c := range s.Contours {
		count += len(c.Edges)
	}
	return count
}

func FromOutline

FromOutline converts a GlyphOutline to a Shape with colored edges.

This is the main entry point for MSDF generation.

func FromOutline(outline *text.GlyphOutline) *Shape {
	if outline == nil || len(outline.Segments) == 0 {
		return NewShape()
	}

	shape := NewShape()
	var currentContour *Contour
	var currentPos Point

	for _, seg := range outline.Segments {
		switch seg.Op {
		case text.OutlineOpMoveTo:
			// Start a new contour
			if currentContour != nil && len(currentContour.Edges) > 0 {
				currentContour.CalculateWinding()
				shape.AddContour(currentContour)
			}
			currentContour = NewContour()
			currentPos = Point{
				X:	float64(seg.Points[0].X),
				Y:	float64(seg.Points[0].Y),
			}

		case text.OutlineOpLineTo:
			if currentContour == nil {
				currentContour = NewContour()
			}
			endPoint := Point{
				X:	float64(seg.Points[0].X),
				Y:	float64(seg.Points[0].Y),
			}
			// Skip degenerate lines
			if endPoint.Sub(currentPos).LengthSquared() > 1e-12 {
				edge := NewLinearEdge(currentPos, endPoint)
				currentContour.AddEdge(edge)
			}
			currentPos = endPoint

		case text.OutlineOpQuadTo:
			if currentContour == nil {
				currentContour = NewContour()
			}
			controlPoint := Point{
				X:	float64(seg.Points[0].X),
				Y:	float64(seg.Points[0].Y),
			}
			endPoint := Point{
				X:	float64(seg.Points[1].X),
				Y:	float64(seg.Points[1].Y),
			}
			edge := NewQuadraticEdge(currentPos, controlPoint, endPoint)
			currentContour.AddEdge(edge)
			currentPos = endPoint

		case text.OutlineOpCubicTo:
			if currentContour == nil {
				currentContour = NewContour()
			}
			control1 := Point{
				X:	float64(seg.Points[0].X),
				Y:	float64(seg.Points[0].Y),
			}
			control2 := Point{
				X:	float64(seg.Points[1].X),
				Y:	float64(seg.Points[1].Y),
			}
			endPoint := Point{
				X:	float64(seg.Points[2].X),
				Y:	float64(seg.Points[2].Y),
			}
			edge := NewCubicEdge(currentPos, control1, control2, endPoint)
			currentContour.AddEdge(edge)
			currentPos = endPoint
		}
	}

	// Add the last contour
	if currentContour != nil && len(currentContour.Edges) > 0 {
		currentContour.CalculateWinding()
		shape.AddContour(currentContour)
	}

	shape.CalculateBounds()
	return shape
}

func IsClockwise

IsClockwise returns true if the contour winds clockwise.

func (c *Contour) IsClockwise() bool {
	return c.Winding < 0
}

func NewContour

NewContour creates an empty contour.

func NewContour() *Contour {
	return &Contour{
		Edges: make([]Edge, 0),
	}
}

func NewShape

NewShape creates an empty shape.

func NewShape() *Shape {
	return &Shape{
		Contours: make([]*Contour, 0),
	}
}

func SelectBlue

SelectBlue returns true if the color includes blue.

func SelectBlue(color EdgeColor) bool {
	return color.HasBlue()
}

func SelectGreen

SelectGreen returns true if the color includes green.

func SelectGreen(color EdgeColor) bool {
	return color.HasGreen()
}

func SelectRed

SelectRed returns true if the color includes red.

func SelectRed(color EdgeColor) bool {
	return color.HasRed()
}

func SwitchColor

SwitchColor returns the next color in the cycle.

Used for edge coloring algorithm.

func SwitchColor(current EdgeColor, seed int) EdgeColor {
	colors := []EdgeColor{ColorCyan, ColorMagenta, ColorYellow}
	for i, c := range colors {
		if c == current {
			return colors[(i+1+seed)%len(colors)]
		}
	}
	return colors[seed%len(colors)]
}

func Validate

Validate checks that the shape is properly closed.

func (s *Shape) Validate() bool {
	for _, contour := range s.Contours {
		if len(contour.Edges) == 0 {
			continue
		}

		// Check that contour is closed (last endpoint == first startpoint)
		first := contour.Edges[0].StartPoint()
		last := contour.Edges[len(contour.Edges)-1].EndPoint()

		dx := math.Abs(first.X - last.X)
		dy := math.Abs(first.Y - last.Y)
		if dx > 1e-6 || dy > 1e-6 {
			return false
		}
	}
	return true
}

Structs

type Contour struct

Contour represents a closed contour of edges.

A glyph typically consists of one or more contours.

type Contour struct {
	// Edges is the list of edges that form this contour.
	Edges	[]Edge

	// Winding is the winding direction of the contour.
	// Positive = counter-clockwise (filled), Negative = clockwise (hole).
	Winding	float64
}

type Shape struct

Shape represents a complete glyph shape consisting of contours.

type Shape struct {
	// Contours are the closed paths that make up the shape.
	Contours	[]*Contour

	// Bounds is the overall bounding box.
	Bounds	Rect
}