// Package freezelib provides a Go library for generating beautiful code screenshots // from source code and terminal output. // // This library is based on the freeze CLI tool by Charm and provides a programmatic // interface for creating code screenshots with syntax highlighting, themes, and // various styling options. package freezelib import ( "fmt" "io" "os" "sort" "github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/styles" ) // Freeze is the main interface for generating code screenshots type Freeze struct { generator *Generator config *Config } // New creates a new Freeze instance with default configuration func New() *Freeze { config := DefaultConfig() return &Freeze{ generator: NewGenerator(config), config: config, } } // NewWithConfig creates a new Freeze instance with the provided configuration func NewWithConfig(config *Config) *Freeze { if config == nil { config = DefaultConfig() } return &Freeze{ generator: NewGenerator(config), config: config, } } // NewWithPreset creates a new Freeze instance with a preset configuration func NewWithPreset(presetName string) *Freeze { config := GetPreset(presetName) return &Freeze{ generator: NewGenerator(config), config: config, } } // Config returns the current configuration func (f *Freeze) Config() *Config { return f.config } // SetConfig updates the configuration and recreates the generator func (f *Freeze) SetConfig(config *Config) *Freeze { f.config = config f.generator = NewGenerator(config) return f } // UpdateConfig allows modifying the current configuration func (f *Freeze) UpdateConfig(fn func(*Config)) *Freeze { fn(f.config) f.generator = NewGenerator(f.config) return f } // GenerateFromCode generates an SVG screenshot from source code func (f *Freeze) GenerateFromCode(code, language string) ([]byte, error) { return f.generator.GenerateFromCode(code, language) } // GenerateFromCodeAuto generates an SVG screenshot from source code with automatic language detection func (f *Freeze) GenerateFromCodeAuto(code string) ([]byte, error) { return f.generator.GenerateFromCode(code, "") } // GenerateFromFile generates an SVG screenshot from a source code file func (f *Freeze) GenerateFromFile(filename string) ([]byte, error) { return f.generator.GenerateFromFile(filename) } // GenerateFromReader generates an SVG screenshot from a reader containing source code func (f *Freeze) GenerateFromReader(reader io.Reader, language string) ([]byte, error) { content, err := io.ReadAll(reader) if err != nil { return nil, fmt.Errorf("failed to read from reader: %w", err) } return f.generator.GenerateFromCode(string(content), language) } // GenerateFromANSI generates an SVG screenshot from ANSI terminal output func (f *Freeze) GenerateFromANSI(ansiOutput string) ([]byte, error) { return f.generator.GenerateFromANSI(ansiOutput) } // GeneratePNGFromCode generates a PNG screenshot from source code func (f *Freeze) GeneratePNGFromCode(code, language string) ([]byte, error) { svgData, err := f.generator.GenerateFromCode(code, language) if err != nil { return nil, err } // Calculate dimensions for PNG (use 4x scale for better quality) width := f.config.Width height := f.config.Height if width == 0 || height == 0 { // Use default dimensions with 4x scale width = 800 * 4 height = 600 * 4 } else { width *= 4 height *= 4 } return f.generator.ConvertToPNG(svgData, width, height) } // GeneratePNGFromCodeAuto generates a PNG screenshot from source code with automatic language detection func (f *Freeze) GeneratePNGFromCodeAuto(code string) ([]byte, error) { svgData, err := f.generator.GenerateFromCode(code, "") if err != nil { return nil, err } // Calculate dimensions for PNG (use 4x scale for better quality) width := f.config.Width height := f.config.Height if width == 0 || height == 0 { // Use default dimensions with 4x scale width = 800 * 4 height = 600 * 4 } else { width *= 4 height *= 4 } return f.generator.ConvertToPNG(svgData, width, height) } // GeneratePNGFromFile generates a PNG screenshot from a source code file func (f *Freeze) GeneratePNGFromFile(filename string) ([]byte, error) { svgData, err := f.generator.GenerateFromFile(filename) if err != nil { return nil, err } // Calculate dimensions for PNG width := f.config.Width height := f.config.Height if width == 0 || height == 0 { width = 800 * 4 height = 600 * 4 } else { width *= 4 height *= 4 } return f.generator.ConvertToPNG(svgData, width, height) } // GeneratePNGFromANSI generates a PNG screenshot from ANSI terminal output func (f *Freeze) GeneratePNGFromANSI(ansiOutput string) ([]byte, error) { svgData, err := f.generator.GenerateFromANSI(ansiOutput) if err != nil { return nil, err } // Calculate dimensions for PNG width := f.config.Width height := f.config.Height if width == 0 || height == 0 { width = 800 * 4 height = 600 * 4 } else { width *= 4 height *= 4 } return f.generator.ConvertToPNG(svgData, width, height) } // SaveToFile saves the generated SVG to a file func (f *Freeze) SaveToFile(data []byte, filename string) error { return os.WriteFile(filename, data, 0644) } // SaveCodeToFile generates and saves a code screenshot to a file func (f *Freeze) SaveCodeToFile(code, language, filename string) error { var data []byte var err error if isPNGFile(filename) { data, err = f.GeneratePNGFromCode(code, language) } else { data, err = f.GenerateFromCode(code, language) } if err != nil { return err } return f.SaveToFile(data, filename) } // SaveCodeToFileAuto generates and saves a code screenshot to a file with automatic language detection func (f *Freeze) SaveCodeToFileAuto(code, filename string) error { var data []byte var err error if isPNGFile(filename) { data, err = f.GeneratePNGFromCodeAuto(code) } else { data, err = f.GenerateFromCodeAuto(code) } if err != nil { return err } return f.SaveToFile(data, filename) } // SaveFileToFile generates and saves a file screenshot to a file func (f *Freeze) SaveFileToFile(inputFile, outputFile string) error { var data []byte var err error if isPNGFile(outputFile) { data, err = f.GeneratePNGFromFile(inputFile) } else { data, err = f.GenerateFromFile(inputFile) } if err != nil { return err } return f.SaveToFile(data, outputFile) } // SaveANSIToFile generates and saves an ANSI screenshot to a file func (f *Freeze) SaveANSIToFile(ansiOutput, filename string) error { var data []byte var err error if isPNGFile(filename) { data, err = f.GeneratePNGFromANSI(ansiOutput) } else { data, err = f.GenerateFromANSI(ansiOutput) } if err != nil { return err } return f.SaveToFile(data, filename) } // Clone creates a copy of the Freeze instance with the same configuration func (f *Freeze) Clone() *Freeze { return NewWithConfig(f.config.Clone()) } // WithTheme creates a new Freeze instance with the specified theme func (f *Freeze) WithTheme(theme string) *Freeze { clone := f.Clone() clone.config.SetTheme(theme) clone.generator = NewGenerator(clone.config) return clone } // WithFont creates a new Freeze instance with the specified font func (f *Freeze) WithFont(family string, size float64) *Freeze { clone := f.Clone() clone.config.SetFont(family, size) clone.generator = NewGenerator(clone.config) return clone } // WithBackground creates a new Freeze instance with the specified background color func (f *Freeze) WithBackground(color string) *Freeze { clone := f.Clone() clone.config.SetBackground(color) clone.generator = NewGenerator(clone.config) return clone } // WithWindow creates a new Freeze instance with window controls enabled/disabled func (f *Freeze) WithWindow(enabled bool) *Freeze { clone := f.Clone() clone.config.SetWindow(enabled) clone.generator = NewGenerator(clone.config) return clone } // WithLineNumbers creates a new Freeze instance with line numbers enabled/disabled func (f *Freeze) WithLineNumbers(enabled bool) *Freeze { clone := f.Clone() clone.config.SetLineNumbers(enabled) clone.generator = NewGenerator(clone.config) return clone } // WithShadow creates a new Freeze instance with shadow settings func (f *Freeze) WithShadow(blur, x, y float64) *Freeze { clone := f.Clone() clone.config.SetShadow(blur, x, y) clone.generator = NewGenerator(clone.config) return clone } // WithBorder creates a new Freeze instance with border settings func (f *Freeze) WithBorder(width, radius float64, color string) *Freeze { clone := f.Clone() clone.config.SetBorder(width, radius, color) clone.generator = NewGenerator(clone.config) return clone } // WithPadding creates a new Freeze instance with padding settings func (f *Freeze) WithPadding(values ...float64) *Freeze { clone := f.Clone() clone.config.SetPadding(values...) clone.generator = NewGenerator(clone.config) return clone } // WithMargin creates a new Freeze instance with margin settings func (f *Freeze) WithMargin(values ...float64) *Freeze { clone := f.Clone() clone.config.SetMargin(values...) clone.generator = NewGenerator(clone.config) return clone } // WithDimensions creates a new Freeze instance with specific dimensions func (f *Freeze) WithDimensions(width, height float64) *Freeze { clone := f.Clone() clone.config.SetDimensions(width, height) clone.generator = NewGenerator(clone.config) return clone } // DetectLanguage detects the programming language from code content func (f *Freeze) DetectLanguage(code string) string { return f.generator.DetectLanguage(code) } // DetectLanguageFromFilename detects the programming language from filename func (f *Freeze) DetectLanguageFromFilename(filename string) string { return f.generator.DetectLanguageFromFilename(filename) } // DetectLanguageFromFile detects language from both filename and content func (f *Freeze) DetectLanguageFromFile(filename, content string) string { return f.generator.DetectLanguageFromFile(filename, content) } // GetSupportedLanguages returns a sorted list of all supported programming languages func (f *Freeze) GetSupportedLanguages() []string { languages := lexers.Names(false) sort.Strings(languages) return languages } // IsLanguageSupported checks if a language is supported func (f *Freeze) IsLanguageSupported(language string) bool { return f.generator.IsLanguageSupported(language) } // SetLanguageDetector sets a custom language detector func (f *Freeze) SetLanguageDetector(detector *LanguageDetector) *Freeze { f.generator.SetLanguageDetector(detector) return f } // GetLanguageDetector returns the current language detector func (f *Freeze) GetLanguageDetector() *LanguageDetector { return f.generator.GetLanguageDetector() } // GetSupportedThemes returns a sorted list of all supported themes func (f *Freeze) GetSupportedThemes() []string { var themes []string for name := range styles.Registry { themes = append(themes, name) } sort.Strings(themes) return themes } // IsThemeSupported checks if a theme is supported func (f *Freeze) IsThemeSupported(theme string) bool { return f.generator.IsThemeSupported(theme) } // GetAvailablePresets returns a sorted list of all available presets func (f *Freeze) GetAvailablePresets() []string { presets := ListPresets() sort.Strings(presets) return presets } // isPNGFile checks if the filename has a PNG extension func isPNGFile(filename string) bool { return len(filename) > 4 && filename[len(filename)-4:] == ".png" } // Global convenience functions for accessing supported options // GetSupportedLanguages returns a sorted list of all supported programming languages func GetSupportedLanguages() []string { languages := lexers.Names(false) sort.Strings(languages) return languages } // GetSupportedThemes returns a sorted list of all supported themes func GetSupportedThemes() []string { var themes []string for name := range styles.Registry { themes = append(themes, name) } sort.Strings(themes) return themes } // GetAvailablePresets returns a sorted list of all available presets func GetAvailablePresets() []string { presets := ListPresets() sort.Strings(presets) return presets } // Version information const ( Version = "1.0.0" Author = "Charm" )