Skip to content

Library Usage Fonts

Chris Watson edited this page Nov 21, 2024 · 3 revisions

Font Management

Goshot provides comprehensive font management capabilities through the fonts package. This guide covers how to use and customize fonts in your code screenshots.

Basic Usage

import (
    "github.com/watzon/goshot/pkg/fonts"
    "github.com/watzon/goshot/pkg/chrome"
)

// Get a font by name (uses system fonts)
font, err := fonts.GetFont("Arial", nil)
if err != nil {
    // Falls back to Monaspace Argon (sans) or Neon (mono) if the font is not found
    font, err = fonts.GetFallback(fonts.FallbackSans)
    if err != nil {
        log.Fatal(err)
    }
}

// Use the font in chrome configuration
theme := chrome.Theme{
    TitleFont: font.Name,
    // ... other theme settings
}

Font Styles

Goshot supports various font styles and weights:

style := &fonts.FontStyle{
    Weight:    fonts.WeightBold,
    Italic:    true,
    Condensed: false,
    Mono:      false,
}

// Get a specific font variant
boldItalicFont, err := fonts.GetFont("Roboto", style)

Available Font Weights

const (
    WeightThin       FontWeight = iota + 1 // Thinnest
    WeightExtraLight                       // Extra Light
    WeightLight                            // Light
    WeightRegular                          // Regular (Normal)
    WeightMedium                           // Medium
    WeightSemiBold                         // Semi-bold
    WeightBold                             // Bold
    WeightExtraBold                        // Extra Bold
    WeightBlack                            // Black
    WeightHeavy                            // Heaviest
)

Variable Font Axes

Note

Support for variable fonts will be coming in the future, but requires a new font library that's capable of parsing OpenType font tables.

Goshot supports variable fonts with the following common axes:

const (
    AxisWeight      = "wght" // Weight axis
    AxisWidth       = "wdth" // Width axis
    AxisSlant       = "slnt" // Slant axis
    AxisItalic      = "ital" // Italic axis
    AxisOpticalSize = "opsz" // Optical size axis
    AxisTexture     = "TXTR" // Texture healing
    AxisLigatures   = "liga" // Ligatures
)

For Monaspace fonts specifically, these ranges are supported:

const (
    MonaspaceWeightMin = 200
    MonaspaceWeightMax = 800
    MonaspaceWidthMin  = 100
    MonaspaceWidthMax  = 125
    MonaspaceSlantMin  = -11
    MonaspaceSlantMax  = 1
)

System Fonts

Goshot can use fonts installed on your system:

// List all available system fonts
availableFonts := fonts.ListFonts()
for _, fontName := range availableFonts {
    fmt.Printf("Found font: %s\n", fontName)
}

System Font Locations

Goshot searches for fonts in these standard locations:

  • Linux:
    • ~/.fonts
    • ~/.local/share/fonts
    • /usr/share/fonts
    • /usr/local/share/fonts
  • macOS:
    • /System/Library/Fonts
    • /Library/Fonts
    • ~/Library/Fonts
  • Windows:
    • C:\Windows\Fonts

Font Variants

You can work with different variants of the same font family:

// Get all variants of a font
variants, err := fonts.GetFontVariants("Roboto")
if err != nil {
    log.Fatal(err)
}

for _, font := range variants {
    fmt.Printf("Variant: Weight=%d, Italic=%v, Condensed=%v\n",
        font.Style.Weight, font.Style.Italic, font.Style.Condensed)
}

Font Face Creation

Create font faces for rendering:

// Create a font face with specific size
face, err := font.GetFontFace(14.0) // 14pt size
if err != nil {
    log.Fatal(err)
}

// Use the face for rendering
// The face implements font.Face interface

Bundled Fonts

Goshot includes Monaspace fonts (Argon and Neon) as fallback fonts:

// Get the fallback font (sans-serif variant)
fallback, err := fonts.GetFallback(fonts.FallbackSans)
if err != nil {
    log.Fatal(err)
}

// Get the monospace fallback variant
monoFallback, err := fonts.GetFallback(fonts.FallbackMono)
if err != nil {
    log.Fatal(err)
}

Advanced Usage

Font Cache Management

// Clear the font cache if needed
fonts.ClearCache()

Custom Font Loading

type FontLoader struct {
    cache map[string]*fonts.Font
}

func (l *FontLoader) LoadFont(name string, style *fonts.FontStyle) (*fonts.Font, error) {
    // Try cache first
    cacheKey := fmt.Sprintf("%s-%v", name, style)
    if cached, ok := l.cache[cacheKey]; ok {
        return cached, nil
    }

    // Try to load from system
    font, err := fonts.GetFont(name, style)
    if err != nil {
        // Try fallback
        font, err = fonts.GetFallback(fonts.FallbackSans)
        if err != nil {
            return nil, err
        }
    }

    // Cache the result
    l.cache[cacheKey] = font
    return font, nil
}

Best Practices

  1. Font Selection

    • Use system fonts when available for consistent appearance
    • Provide fallbacks for missing fonts
    • Consider font licensing when bundling fonts
  2. Performance

    // Cache fonts for repeated use
    type FontManager struct {
        fonts map[string]*fonts.Font
        mu    sync.RWMutex
    }
    
    func (m *FontManager) GetFont(name string) (*fonts.Font, error) {
        m.mu.RLock()
        if font, ok := m.fonts[name]; ok {
            m.mu.RUnlock()
            return font, nil
        }
        m.mu.RUnlock()
    
        font, err := fonts.GetFont(name, nil)
        if err != nil {
            return nil, err
        }
    
        m.mu.Lock()
        m.fonts[name] = font
        m.mu.Unlock()
    
        return font, nil
    }
  3. Error Handling

    func getFontWithFallback(name string, style *fonts.FontStyle) (*fonts.Font, error) {
        font, err := fonts.GetFont(name, style)
        if err != nil {
            // Log the error but don't fail
            log.Printf("Failed to load font %s: %v", name, err)
            
            // Try without style
            font, err = fonts.GetFont(name, nil)
            if err != nil {
                // Try fallback
                return fonts.GetFallback(fonts.FallbackSans)
            }
        }
        return font, nil
    }

Common Patterns

Font Style Matching

func findClosestStyle(available []*fonts.Font, desired fonts.FontStyle) *fonts.Font {
    var best *fonts.Font
    var bestScore int

    for _, font := range available {
        score := 0
        
        // Weight match
        weightDiff := abs(int(font.Style.Weight) - int(desired.Weight))
        score += 100 - (weightDiff * 10)
        
        // Style matches
        if font.Style.Italic == desired.Italic {
            score += 50
        }
        if font.Style.Condensed == desired.Condensed {
            score += 50
        }
        
        if score > bestScore {
            best = font
            bestScore = score
        }
    }
    
    return best
}