text/subpixel.go
Functions
func CacheMultiplier
func (c SubpixelConfig) CacheMultiplier() int {
if !c.Mode.IsEnabled() {
return 1
}
mult := 1
if c.Horizontal {
mult *= c.Mode.Divisions()
}
if c.Vertical {
mult *= c.Mode.Divisions()
}
return mult
}
func Clear
Clear removes all entries from the cache.
func (c *SubpixelCache) Clear() {
c.cache.Clear()
}
func Config
Config returns the subpixel configuration.
func (c *SubpixelCache) Config() SubpixelConfig {
c.mu.RLock()
defer c.mu.RUnlock()
return c.config
}
func DefaultSubpixelConfig
DefaultSubpixelConfig returns default configuration.
Uses 4 horizontal subpixel positions.
func DefaultSubpixelConfig() SubpixelConfig {
return SubpixelConfig{
Mode: Subpixel4,
Horizontal: true,
Vertical: false,
}
}
func Delete
Delete removes an entry from the cache.
func (c *SubpixelCache) Delete(key SubpixelKey) {
c.cache.Delete(c.toOutlineKey(key))
}
func Divisions
Divisions returns the number of subpixel divisions.
Returns 1 for SubpixelNone (no divisions).
func (m SubpixelMode) Divisions() int {
if m <= 0 {
return 1
}
return int(m)
}
func Get
Get retrieves a glyph at the specified subpixel position.
Returns nil if not found.
func (c *SubpixelCache) Get(key SubpixelKey) *GlyphOutline {
outline := c.cache.Get(c.toOutlineKey(key))
if outline != nil {
c.mu.Lock()
c.stats.Hits++
c.stats.SubpixelHits++
c.mu.Unlock()
} else {
c.mu.Lock()
c.stats.Misses++
c.mu.Unlock()
}
return outline
}
func GetGlobalSubpixelCache
GetGlobalSubpixelCache returns the global shared subpixel cache.
func GetGlobalSubpixelCache() *SubpixelCache {
return globalSubpixelCache
}
func GetOrCreate
GetOrCreate retrieves or creates a glyph at subpixel position.
The create function receives the subpixel offsets to apply during rasterization.
func (c *SubpixelCache) GetOrCreate(
key SubpixelKey,
create func(offsetX, offsetY float64) *GlyphOutline,
) *GlyphOutline {
// Try fast path first
if outline := c.Get(key); outline != nil {
return outline
}
if create == nil {
return nil
}
// Calculate subpixel offsets for rendering
c.mu.RLock()
config := c.config
c.mu.RUnlock()
offsetX, offsetY := SubpixelOffsets(key.SubX, key.SubY, config)
// Create the outline with subpixel offset applied
outline := create(offsetX, offsetY)
if outline != nil {
c.Set(key, outline)
c.mu.Lock()
c.stats.SubpixelCreates++
c.mu.Unlock()
}
return outline
}
func HighQualitySubpixelConfig
HighQualitySubpixelConfig returns a configuration with maximum subpixel quality.
Uses 10 horizontal subpixel positions.
func HighQualitySubpixelConfig() SubpixelConfig {
return SubpixelConfig{
Mode: Subpixel10,
Horizontal: true,
Vertical: false,
}
}
func HitRate
HitRate returns the cache hit rate as a percentage.
func (c *SubpixelCache) HitRate() float64 {
c.mu.RLock()
defer c.mu.RUnlock()
total := c.stats.Hits + c.stats.Misses
if total == 0 {
return 0
}
return float64(c.stats.Hits) / float64(total) * 100
}
func IsEnabled
IsEnabled returns true if any subpixel positioning is enabled.
func (c SubpixelConfig) IsEnabled() bool {
return c.Mode.IsEnabled() && (c.Horizontal || c.Vertical)
}
func IsEnabled
IsEnabled returns true if subpixel positioning is enabled.
func (m SubpixelMode) IsEnabled() bool {
return m > 0
}
func Len
Len returns the total number of cached entries.
func (c *SubpixelCache) Len() int {
return c.cache.Len()
}
func Maintain
Maintain performs periodic maintenance on the cache.
func (c *SubpixelCache) Maintain() {
c.cache.Maintain()
}
func MakeSubpixelKey
MakeSubpixelKey creates a SubpixelKey from glyph position and base cache key.
func MakeSubpixelKey(baseKey OutlineCacheKey, x, y float64, config SubpixelConfig) SubpixelKey {
_, _, subX, subY := QuantizePoint(x, y, config)
return SubpixelKey{
OutlineCacheKey: baseKey,
SubX: subX,
SubY: subY,
}
}
func NewSubpixelCache
NewSubpixelCache creates a cache with subpixel support.
func NewSubpixelCache(config SubpixelConfig) *SubpixelCache {
return &SubpixelCache{
cache: NewGlyphCache(),
config: config,
}
}
func NewSubpixelCacheWithConfig
NewSubpixelCacheWithConfig creates a cache with custom glyph cache config.
func NewSubpixelCacheWithConfig(config SubpixelConfig, glyphConfig GlyphCacheConfig) *SubpixelCache {
// Adjust cache size to account for subpixel multiplier
// This maintains similar memory usage per unique glyph
adjustedConfig := glyphConfig
mult := config.CacheMultiplier()
if mult > 1 {
adjustedConfig.MaxEntries = glyphConfig.MaxEntries * mult
}
return &SubpixelCache{
cache: NewGlyphCacheWithConfig(adjustedConfig),
config: config,
}
}
func NoSubpixelConfig
NoSubpixelConfig returns a configuration with subpixel positioning disabled.
func NoSubpixelConfig() SubpixelConfig {
return SubpixelConfig{
Mode: SubpixelNone,
Horizontal: false,
Vertical: false,
}
}
func Quantize
Quantize converts a fractional position to quantized subpixel offset.
Returns the integer position and subpixel key component.
For example, with Subpixel4 mode:
- pos=10.0 returns (10, 0)
- pos=10.25 returns (10, 1)
- pos=10.5 returns (10, 2)
- pos=10.75 returns (10, 3)
- pos=10.99 returns (10, 3) // quantized to nearest
func Quantize(pos float64, mode SubpixelMode) (intPos int, subPos uint8) {
if !mode.IsEnabled() {
// No subpixel positioning - round to nearest integer
return int(pos + 0.5), 0
}
// Compute floor (integer part that is <= pos)
intPart := int(pos)
if pos < 0 && pos != float64(intPart) {
intPart--
}
// Get the fractional part [0, 1)
frac := pos - float64(intPart)
// Quantize to subpixel position
divisions := float64(mode.Divisions())
subPosFloat := frac * divisions
subPosInt := int(subPosFloat)
// Clamp to valid range [0, mode-1]
if subPosInt >= mode.Divisions() {
subPosInt = mode.Divisions() - 1
}
if subPosInt < 0 {
subPosInt = 0
}
// Safe conversion since subPosInt is in range [0, mode-1]
// and mode is at most 10
return intPart, uint8(subPosInt) //nolint:gosec // subPosInt is bounded [0, mode-1]
}
func QuantizePoint
QuantizePoint quantizes both X and Y positions.
Returns integer positions and subpixel key components.
func QuantizePoint(x, y float64, config SubpixelConfig) (intX, intY int, subX, subY uint8) {
if config.Horizontal {
intX, subX = Quantize(x, config.Mode)
} else {
intX, subX = int(x+0.5), 0
}
if config.Vertical {
intY, subY = Quantize(y, config.Mode)
} else {
intY, subY = int(y+0.5), 0
}
return intX, intY, subX, subY
}
func ResetStats
ResetStats resets the cache statistics.
func (c *SubpixelCache) ResetStats() {
c.mu.Lock()
defer c.mu.Unlock()
c.stats = SubpixelCacheStats{}
c.cache.ResetStats()
}
func Set
Set stores a glyph outline at the specified subpixel position.
func (c *SubpixelCache) Set(key SubpixelKey, outline *GlyphOutline) {
c.cache.Set(c.toOutlineKey(key), outline)
}
func SetConfig
SetConfig updates the subpixel configuration.
Note: This clears the cache as existing entries may be invalid.
func (c *SubpixelCache) SetConfig(config SubpixelConfig) {
c.mu.Lock()
defer c.mu.Unlock()
c.config = config
c.cache.Clear()
}
func SetGlobalSubpixelCache
SetGlobalSubpixelCache replaces the global subpixel cache.
The old cache is returned for cleanup if needed.
func SetGlobalSubpixelCache(cache *SubpixelCache) *SubpixelCache {
if cache == nil {
cache = NewSubpixelCache(DefaultSubpixelConfig())
}
old := globalSubpixelCache
globalSubpixelCache = cache
return old
}
func Stats
Stats returns subpixel cache statistics.
func (c *SubpixelCache) Stats() SubpixelCacheStats {
c.mu.RLock()
defer c.mu.RUnlock()
return c.stats
}
func String
String returns the string representation of the subpixel mode.
func (m SubpixelMode) String() string {
switch m {
case SubpixelNone:
return noneStr
case Subpixel4:
return "Subpixel4"
case Subpixel10:
return "Subpixel10"
default:
return unknownStr
}
}
func SubpixelOffset
SubpixelOffset returns the rendering offset for a subpixel position.
For Subpixel4 mode: 0 -> 0.0, 1 -> 0.25, 2 -> 0.5, 3 -> 0.75
For Subpixel10 mode: 0 -> 0.0, 1 -> 0.1, ..., 9 -> 0.9
func SubpixelOffset(subPos uint8, mode SubpixelMode) float64 {
if !mode.IsEnabled() {
return 0
}
return float64(subPos) / float64(mode.Divisions())
}
func SubpixelOffsets
SubpixelOffsets returns both X and Y rendering offsets.
func SubpixelOffsets(subX, subY uint8, config SubpixelConfig) (offsetX, offsetY float64) {
if config.Horizontal {
offsetX = SubpixelOffset(subX, config.Mode)
}
if config.Vertical {
offsetY = SubpixelOffset(subY, config.Mode)
}
return offsetX, offsetY
}
Structs
type SubpixelConfig struct
SubpixelConfig holds subpixel positioning configuration.
type SubpixelConfig struct {
// Mode determines the number of subpixel positions.
Mode SubpixelMode
// Horizontal enables subpixel positioning on X axis.
Horizontal bool
// Vertical enables subpixel positioning on Y axis (rarely needed).
Vertical bool
}
type SubpixelKey struct
SubpixelKey extends OutlineCacheKey with subpixel offset.
This allows caching separate rasterized glyphs for each subpixel position.
type SubpixelKey struct {
OutlineCacheKey
// SubX is the quantized horizontal subpixel position (0 to Mode-1).
SubX uint8
// SubY is the quantized vertical subpixel position (0 to Mode-1).
SubY uint8
}
type SubpixelCache struct
SubpixelCache wraps GlyphCache with subpixel awareness.
It provides the same interface as GlyphCache but uses SubpixelKey
to differentiate between the same glyph at different subpixel offsets.
SubpixelCache is safe for concurrent use.
type SubpixelCache struct {
// cache is the underlying glyph cache
cache *GlyphCache
// config holds the subpixel configuration
config SubpixelConfig
// stats holds cache statistics
stats SubpixelCacheStats
mu sync.RWMutex
}
type SubpixelCacheStats struct
SubpixelCacheStats holds subpixel cache statistics.
type SubpixelCacheStats struct {
// Hits is the number of cache hits
Hits uint64
// Misses is the number of cache misses
Misses uint64
// SubpixelHits is hits where subpixel position matched
SubpixelHits uint64
// SubpixelCreates is glyphs created for subpixel positions
SubpixelCreates uint64
}
CacheMultiplier returns the factor by which cache size increases.
For Subpixel4 with horizontal only: 4x
For Subpixel10 with both: 100x