text/msdf/shelf.go

Functions Structs

Functions

func Allocate

Allocate finds space for a rectangle of the given size.

Returns x, y position and true if space was found, or -1, -1, false if not.

 

The algorithm:

1. Try to fit on an existing shelf with enough height

2. If no shelf fits, create a new shelf

3. If no space for new shelf, allocation fails

func (a *ShelfAllocator) Allocate(w, h int) (x, y int, ok bool) {
	// Add padding to requested size
	paddedW := w + a.padding
	paddedH := h + a.padding

	// Try to find an existing shelf with enough space and height
	for i := range a.shelves {
		shelf := &a.shelves[i]

		// Check if item fits horizontally
		if shelf.x+paddedW > a.width {
			continue
		}

		// Check if item fits vertically in this shelf
		if h > shelf.height {
			// Item is taller than shelf - check if we can extend the shelf
			// Only possible if this is the last shelf and there's room below
			if i == len(a.shelves)-1 {
				newBottom := shelf.y + paddedH
				if newBottom <= a.height {
					// Extend shelf height
					shelf.height = h
					x, y = shelf.x, shelf.y
					shelf.x += paddedW
					a.usedArea += w * h
					return x, y, true
				}
			}
			continue
		}

		// Item fits on this shelf
		x, y = shelf.x, shelf.y
		shelf.x += paddedW
		a.usedArea += w * h
		return x, y, true
	}

	// No existing shelf works - try to create a new one
	newY := 0
	if len(a.shelves) > 0 {
		last := a.shelves[len(a.shelves)-1]
		newY = last.y + last.height + a.padding
	}

	// Check if new shelf fits
	if newY+paddedH > a.height {
		return -1, -1, false
	}

	// Create new shelf
	newShelf := shelf{
		y:	newY,
		height:	h,
		x:	paddedW,
	}
	a.shelves = append(a.shelves, newShelf)
	a.usedArea += w * h

	return 0, newY, true
}

func Allocate

Allocate returns the position of the next available cell.

Returns -1, -1, false if the grid is full.

func (g *GridAllocator) Allocate() (x, y int, ok bool) {
	if g.next >= g.cols*g.rows {
		return -1, -1, false
	}

	col := g.next % g.cols
	row := g.next / g.cols

	cellWithPad := g.cellSize + g.padding
	x = col * cellWithPad
	y = row * cellWithPad

	g.next++
	return x, y, true
}

func AllocateFixed

AllocateFixed allocates a fixed-size cell, optimized for uniform glyph sizes.

This is more efficient when all cells are the same size.

func (a *ShelfAllocator) AllocateFixed(cellSize int) (x, y int, ok bool) {
	return a.Allocate(cellSize, cellSize)
}

func Allocated

Allocated returns the number of cells currently allocated.

func (g *GridAllocator) Allocated() int {
	return g.next
}

func CanFit

CanFit returns true if an item of the given size could possibly fit.

This is a quick check without actually allocating.

func (a *ShelfAllocator) CanFit(w, h int) bool {
	paddedW := w + a.padding
	paddedH := h + a.padding

	// Items wider than the allocator can never fit
	if paddedW > a.width {
		return false
	}

	// Items taller than the allocator can never fit
	if paddedH > a.height {
		return false
	}

	// Check existing shelves
	for i := range a.shelves {
		shelf := &a.shelves[i]

		// Check if item fits horizontally
		if shelf.x+paddedW > a.width {
			continue
		}

		// Check if item fits in shelf height
		if h <= shelf.height {
			return true
		}

		// Check if we can extend last shelf
		if i == len(a.shelves)-1 {
			if shelf.y+paddedH <= a.height {
				return true
			}
		}
	}

	// Check if we can create a new shelf
	newY := 0
	if len(a.shelves) > 0 {
		last := a.shelves[len(a.shelves)-1]
		newY = last.y + last.height + a.padding
	}

	return newY+paddedH <= a.height
}

func Capacity

Capacity returns the maximum number of cells that can be allocated.

func (g *GridAllocator) Capacity() int {
	return g.cols * g.rows
}

func CellSize

CellSize returns the size of each cell.

func (g *GridAllocator) CellSize() int {
	return g.cellSize
}

func CurrentShelfRemainingWidth

CurrentShelfRemainingWidth returns the remaining width on the current (last) shelf.

func (a *ShelfAllocator) CurrentShelfRemainingWidth() int {
	if len(a.shelves) == 0 {
		return a.width
	}
	last := a.shelves[len(a.shelves)-1]
	if last.x >= a.width {
		return 0
	}
	return a.width - last.x
}

func GridDimensions

GridDimensions returns the number of columns and rows.

func (g *GridAllocator) GridDimensions() (cols, rows int) {
	return g.cols, g.rows
}

func IsFull

IsFull returns true if no more cells can be allocated.

func (g *GridAllocator) IsFull() bool {
	return g.next >= g.cols*g.rows
}

func NewGridAllocator

NewGridAllocator creates a grid allocator for uniform cells.

func NewGridAllocator(width, height, cellSize, padding int) *GridAllocator {
	cellWithPad := cellSize + padding
	cols := width / cellWithPad
	rows := height / cellWithPad

	if cols <= 0 {
		cols = 1
	}
	if rows <= 0 {
		rows = 1
	}

	return &GridAllocator{
		width:		width,
		height:		height,
		cellSize:	cellSize,
		padding:	padding,
		cols:		cols,
		rows:		rows,
		next:		0,
	}
}

func NewShelfAllocator

NewShelfAllocator creates a new allocator for the given dimensions.

func NewShelfAllocator(width, height, padding int) *ShelfAllocator {
	return &ShelfAllocator{
		width:		width,
		height:		height,
		padding:	padding,
		shelves:	make([]shelf, 0, 16),	// Preallocate for typical use
	}
}

func Remaining

Remaining returns the number of cells still available.

func (g *GridAllocator) Remaining() int {
	return g.Capacity() - g.next
}

func RemainingHeight

RemainingHeight returns the vertical space remaining for new shelves.

func (a *ShelfAllocator) RemainingHeight() int {
	if len(a.shelves) == 0 {
		return a.height
	}

	last := a.shelves[len(a.shelves)-1]
	used := last.y + last.height + a.padding
	if used >= a.height {
		return 0
	}
	return a.height - used
}

func Reset

Reset clears all allocations.

func (g *GridAllocator) Reset() {
	g.next = 0
}

func Reset

Reset clears all allocations, allowing the allocator to be reused.

func (a *ShelfAllocator) Reset() {
	a.shelves = a.shelves[:0]	// Keep capacity
	a.usedArea = 0
}

func ShelfCount

ShelfCount returns the number of shelves currently in use.

func (a *ShelfAllocator) ShelfCount() int {
	return len(a.shelves)
}

func TotalArea

TotalArea returns the total area of the atlas.

func (a *ShelfAllocator) TotalArea() int {
	return a.width * a.height
}

func UsedArea

UsedArea returns the total area used by allocations.

func (a *ShelfAllocator) UsedArea() int {
	return a.usedArea
}

func Utilization

Utilization returns the percentage of atlas space used (0.0 to 1.0).

func (a *ShelfAllocator) Utilization() float64 {
	if a.width <= 0 || a.height <= 0 {
		return 0
	}
	totalArea := a.width * a.height
	return float64(a.usedArea) / float64(totalArea)
}

func Utilization

Utilization returns the percentage of cells used (0.0 to 1.0).

func (g *GridAllocator) Utilization() float64 {
	capacity := g.Capacity()
	if capacity <= 0 {
		return 0
	}
	return float64(g.next) / float64(capacity)
}

Structs

type ShelfAllocator struct

ShelfAllocator implements shelf-based rectangle packing.

Simple and fast algorithm suitable for uniform-sized glyphs.

 

The algorithm organizes rectangles in horizontal "shelves".

Each shelf has a fixed height (determined by the tallest item placed so far).

New items are placed left-to-right on the current shelf until no space remains,

then a new shelf is started below.

type ShelfAllocator struct {
	width	int	// Total width of the atlas
	height	int	// Total height of the atlas
	padding	int	// Padding between glyphs
	shelves	[]shelf	// List of shelves

	// Tracking for utilization
	usedArea	int
}

type GridAllocator struct

GridAllocator is a specialized allocator for uniform grid-based layouts.

More efficient than ShelfAllocator when all cells are exactly the same size.

type GridAllocator struct {
	width		int	// Atlas width
	height		int	// Atlas height
	cellSize	int	// Size of each cell (square)
	padding		int	// Padding between cells
	cols		int	// Number of columns
	rows		int	// Number of rows
	next		int	// Next cell index
}