From d2630e4503d7b30b692d580b4fe59fdaefead398 Mon Sep 17 00:00:00 2001 From: landaiqing Date: Fri, 18 Jul 2025 19:02:23 +0800 Subject: [PATCH] :sparkles: initial commit --- .gitignore | 264 +++++++++ .idea/.gitignore | 8 + .idea/freezelib.iml | 9 + .idea/modules.xml | 8 + LICENSE | 18 + README.md | 287 ++++++++++ README_CN.md | 287 ++++++++++ USAGE.md | 264 +++++++++ USAGE_EN.md | 264 +++++++++ ansi.go | 299 ++++++++++ config.go | 242 ++++++++ examples/01-basic/main.go | 173 ++++++ examples/02-formats/main.go | 337 +++++++++++ examples/03-themes/main.go | 395 +++++++++++++ examples/04-languages/main.go | 896 ++++++++++++++++++++++++++++++ examples/05-terminal/main.go | 411 ++++++++++++++ examples/06-advanced/main.go | 587 +++++++++++++++++++ examples/07-batch/main.go | 680 +++++++++++++++++++++++ examples/README.md | 113 ++++ examples/README_CN.md | 114 ++++ font/JetBrainsMono-Regular.ttf | Bin 0 -> 274744 bytes font/JetBrainsMonoNL-Regular.ttf | Bin 0 -> 208576 bytes font/font.go | 165 ++++++ freeze.go | 310 +++++++++++ generator.go | 443 +++++++++++++++ go.mod | 29 + go.sum | 52 ++ presets.go | 194 +++++++ quickfreeze.go | 348 ++++++++++++ sample/basic_example.svg | 15 + sample/chained_example.svg | 24 + sample/custom_config_example.svg | 25 + sample/file_example.svg | 19 + sample/main.go | 329 +++++++++++ sample/preset_dark_example.svg | 17 + sample/preset_light_example.svg | 17 + sample/preset_minimal_example.svg | 17 + sample/preset_retro_example.svg | 17 + sample/quickfreeze_example.svg | 16 + sample/sample.rs | 12 + sample/simple_test.go | 81 +++ sample/terminal_example.svg | 8 + svg/svg.go | 220 ++++++++ 43 files changed, 8014 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/freezelib.iml create mode 100644 .idea/modules.xml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README_CN.md create mode 100644 USAGE.md create mode 100644 USAGE_EN.md create mode 100644 ansi.go create mode 100644 config.go create mode 100644 examples/01-basic/main.go create mode 100644 examples/02-formats/main.go create mode 100644 examples/03-themes/main.go create mode 100644 examples/04-languages/main.go create mode 100644 examples/05-terminal/main.go create mode 100644 examples/06-advanced/main.go create mode 100644 examples/07-batch/main.go create mode 100644 examples/README.md create mode 100644 examples/README_CN.md create mode 100644 font/JetBrainsMono-Regular.ttf create mode 100644 font/JetBrainsMonoNL-Regular.ttf create mode 100644 font/font.go create mode 100644 freeze.go create mode 100644 generator.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 presets.go create mode 100644 quickfreeze.go create mode 100644 sample/basic_example.svg create mode 100644 sample/chained_example.svg create mode 100644 sample/custom_config_example.svg create mode 100644 sample/file_example.svg create mode 100644 sample/main.go create mode 100644 sample/preset_dark_example.svg create mode 100644 sample/preset_light_example.svg create mode 100644 sample/preset_minimal_example.svg create mode 100644 sample/preset_retro_example.svg create mode 100644 sample/quickfreeze_example.svg create mode 100644 sample/sample.rs create mode 100644 sample/simple_test.go create mode 100644 sample/terminal_example.svg create mode 100644 svg/svg.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..815ad95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,264 @@ +### GoLand+all template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Go template +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +### GoLand template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### GoLand+iml template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/freezelib.iml b/.idea/freezelib.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/freezelib.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..6ca929f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cee7643 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) 2025 landaiqing + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a35c730 --- /dev/null +++ b/README.md @@ -0,0 +1,287 @@ +# FreezeLib - Go Library for Beautiful Code Screenshots + +**Language / 语言**: [English](README.md) | [中文](README_CN.md) + +**Documentation / 文档**: [Usage Guide (English)](USAGE_EN.md) | [使用指南 (中文)](USAGE.md) + +FreezeLib is a Go library for generating beautiful screenshots of code and terminal output. It's based on the popular [freeze](https://github.com/charmbracelet/freeze) CLI tool by Charm, but redesigned as a reusable library for Go applications. + +## Features + +- 🎨 **Syntax Highlighting**: Support for 100+ programming languages +- 🖼️ **Multiple Output Formats**: Generate SVG and PNG images +- 🎭 **Rich Themes**: Built-in themes including GitHub, Dracula, Monokai, and more +- 🪟 **Window Controls**: macOS-style window decorations +- 📏 **Line Numbers**: Optional line numbering +- 🌈 **ANSI Support**: Render colored terminal output +- ⚡ **Easy API**: Simple and chainable API design +- 🎯 **Presets**: Pre-configured styles for common use cases +- 🔧 **Highly Customizable**: Fine-tune every aspect of the output + +## Installation + +```bash +go get github.com/landaiqing/freezelib +``` + +## Quick Start + +### Basic Usage + +```go +package main + +import ( + "os" + "github.com/landaiqing/freezelib" +) + +func main() { + // Create a new freeze instance + freeze := freezelib.New() + + // Go code to screenshot + code := `package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +}` + + // Generate SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + panic(err) + } + + // Save to file + os.WriteFile("hello.svg", svgData, 0644) +} +``` + +### QuickFreeze API + +For a more fluent experience, use the QuickFreeze API: + +```go +qf := freezelib.NewQuickFreeze() + +svgData, err := qf.WithTheme("dracula"). + WithFont("Fira Code", 14). + WithWindow(). + WithShadow(). + WithLineNumbers(). + CodeToSVG(code) +``` + +## API Reference + +### Core Types + +#### Freeze + +The main interface for generating screenshots: + +```go +freeze := freezelib.New() // Default config +freeze := freezelib.NewWithConfig(config) // Custom config +freeze := freezelib.NewWithPreset("dark") // Preset config +``` + +#### QuickFreeze + +Simplified, chainable API: + +```go +qf := freezelib.NewQuickFreeze() +qf := freezelib.NewQuickFreezeWithPreset("terminal") +``` + +### Generation Methods + +#### From Code String +```go +svgData, err := freeze.GenerateFromCode(code, "python") +pngData, err := freeze.GeneratePNGFromCode(code, "python") +``` + +#### From File +```go +svgData, err := freeze.GenerateFromFile("main.go") +pngData, err := freeze.GeneratePNGFromFile("main.go") +``` + +#### From ANSI Terminal Output +```go +terminalOutput := "\033[32mSUCCESS\033[0m: Build completed" +svgData, err := freeze.GenerateFromANSI(terminalOutput) +pngData, err := freeze.GeneratePNGFromANSI(terminalOutput) +``` + +#### From Reader +```go +svgData, err := freeze.GenerateFromReader(reader, "javascript") +``` + +### Configuration + +#### Basic Configuration +```go +config := freezelib.DefaultConfig() +config.SetTheme("github-dark") +config.SetFont("JetBrains Mono", 14) +config.SetBackground("#1e1e1e") +config.SetWindow(true) +config.SetLineNumbers(true) +``` + +#### Advanced Configuration +```go +config.SetPadding(20) // All sides +config.SetPadding(20, 40) // Vertical, horizontal +config.SetPadding(20, 40, 20, 40) // Top, right, bottom, left + +config.SetShadow(20, 0, 10) // Blur, X offset, Y offset +config.SetBorder(1, 8, "#333") // Width, radius, color +config.SetDimensions(800, 600) // Width, height +config.SetLines(10, 20) // Line range (1-indexed) +``` + +### Presets + +FreezeLib comes with several built-in presets: + +```go +// Available presets +presets := []string{ + "base", // Simple, clean + "full", // macOS-style with window controls + "terminal", // Optimized for terminal output + "presentation", // High contrast for presentations + "minimal", // Minimal styling + "dark", // Dark theme + "light", // Light theme + "retro", // Retro terminal style + "neon", // Neon/cyberpunk style + "compact", // Compact for small snippets +} + +freeze := freezelib.NewWithPreset("dark") +``` + +### Chainable Methods + +Both `Freeze` and `QuickFreeze` support method chaining: + +```go +freeze := freezelib.New(). + WithTheme("monokai"). + WithFont("Cascadia Code", 15). + WithWindow(true). + WithShadow(20, 0, 10). + WithLineNumbers(true) + +svgData, err := freeze.GenerateFromCode(code, "rust") +``` + +## Examples + +### Terminal Output Screenshot + +```go +freeze := freezelib.NewWithPreset("terminal") + +ansiOutput := "\033[32m✓ Tests passed\033[0m\n" + + "\033[31m✗ Build failed\033[0m\n" + + "\033[33m⚠ Warning: deprecated API\033[0m" + +svgData, err := freeze.GenerateFromANSI(ansiOutput) +``` + +### Custom Styling + +```go +config := freezelib.DefaultConfig() +config.Theme = "github" +config.Background = "#f6f8fa" +config.Font.Family = "SF Mono" +config.Font.Size = 16 +config.SetPadding(30) +config.SetMargin(20) +config.Window = true +config.ShowLineNumbers = true +config.Border.Radius = 12 +config.Shadow.Blur = 25 + +freeze := freezelib.NewWithConfig(config) +``` + +### Batch Processing + +```go +files := []string{"main.go", "config.go", "utils.go"} + +for _, file := range files { + svgData, err := freeze.GenerateFromFile(file) + if err != nil { + continue + } + + outputFile := strings.TrimSuffix(file, ".go") + ".svg" + os.WriteFile(outputFile, svgData, 0644) +} +``` + +## Supported Languages + +FreezeLib supports syntax highlighting for 100+ programming languages including: + +- Go, Rust, Python, JavaScript, TypeScript +- C, C++, C#, Java, Kotlin, Swift +- HTML, CSS, SCSS, JSON, YAML, XML +- Shell, PowerShell, Dockerfile +- SQL, GraphQL, Markdown +- And many more... + +## Supported Themes + +Popular themes include: +- `github` / `github-dark` +- `dracula` +- `monokai` +- `solarized-dark` / `solarized-light` +- `nord` +- `one-dark` +- `material` +- `vim` +- And many more... + +## Error Handling + +```go +svgData, err := freeze.GenerateFromCode(code, "go") +if err != nil { + // Handle specific errors + switch { + case strings.Contains(err.Error(), "language"): + // Language detection failed + case strings.Contains(err.Error(), "config"): + // Configuration error + default: + // Other errors + } +} +``` + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +MIT License - see the [LICENSE](./LICENSE) file for details. + +## Acknowledgments + +This library is based on the excellent [freeze](https://github.com/charmbracelet/freeze) CLI tool by [Charm](https://charm.sh). Special thanks to the Charm team for creating such a beautiful tool. diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..6814c0f --- /dev/null +++ b/README_CN.md @@ -0,0 +1,287 @@ +# FreezeLib - 美观代码截图的 Go 库 + +**Language / 语言**: [English](README.md) | [中文](README_CN.md) + +**Documentation / 文档**: [Usage Guide (English)](USAGE_EN.md) | [使用指南 (中文)](USAGE.md) + +FreezeLib 是一个用于生成美观代码和终端输出截图的 Go 库。它基于 Charm 团队广受欢迎的 [freeze](https://github.com/charmbracelet/freeze) CLI 工具,但重新设计为可在 Go 应用程序中重复使用的库。 + +## 特性 + +- 🎨 **语法高亮**: 支持 100+ 种编程语言 +- 🖼️ **多种输出格式**: 生成 SVG 和 PNG 图像 +- 🎭 **丰富主题**: 内置主题包括 GitHub、Dracula、Monokai 等 +- 🪟 **窗口控件**: macOS 风格的窗口装饰 +- 📏 **行号**: 可选的行号显示 +- 🌈 **ANSI 支持**: 渲染彩色终端输出 +- ⚡ **简易 API**: 简单且可链式调用的 API 设计 +- 🎯 **预设配置**: 常见用例的预配置样式 +- 🔧 **高度可定制**: 精细调整输出的每个方面 + +## 安装 + +```bash +go get github.com/landaiqing/freezelib +``` + +## 快速开始 + +### 基本用法 + +```go +package main + +import ( + "os" + "github.com/landaiqing/freezelib" +) + +func main() { + // 创建新的 freeze 实例 + freeze := freezelib.New() + + // 要截图的 Go 代码 + code := `package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +}` + + // 生成 SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + panic(err) + } + + // 保存到文件 + os.WriteFile("hello.svg", svgData, 0644) +} +``` + +### QuickFreeze API + +为了更流畅的体验,使用 QuickFreeze API: + +```go +qf := freezelib.NewQuickFreeze() + +svgData, err := qf.WithTheme("dracula"). + WithFont("Fira Code", 14). + WithWindow(). + WithShadow(). + WithLineNumbers(). + CodeToSVG(code) +``` + +## API 参考 + +### 核心类型 + +#### Freeze + +生成截图的主要接口: + +```go +freeze := freezelib.New() // 默认配置 +freeze := freezelib.NewWithConfig(config) // 自定义配置 +freeze := freezelib.NewWithPreset("dark") // 预设配置 +``` + +#### QuickFreeze + +简化的链式 API: + +```go +qf := freezelib.NewQuickFreeze() +qf := freezelib.NewQuickFreezeWithPreset("terminal") +``` + +### 生成方法 + +#### 从代码字符串 +```go +svgData, err := freeze.GenerateFromCode(code, "python") +pngData, err := freeze.GeneratePNGFromCode(code, "python") +``` + +#### 从文件 +```go +svgData, err := freeze.GenerateFromFile("main.go") +pngData, err := freeze.GeneratePNGFromFile("main.go") +``` + +#### 从 ANSI 终端输出 +```go +terminalOutput := "\033[32mSUCCESS\033[0m: Build completed" +svgData, err := freeze.GenerateFromANSI(terminalOutput) +pngData, err := freeze.GeneratePNGFromANSI(terminalOutput) +``` + +#### 从 Reader +```go +svgData, err := freeze.GenerateFromReader(reader, "javascript") +``` + +### 配置 + +#### 基本配置 +```go +config := freezelib.DefaultConfig() +config.SetTheme("github-dark") +config.SetFont("JetBrains Mono", 14) +config.SetBackground("#1e1e1e") +config.SetWindow(true) +config.SetLineNumbers(true) +``` + +#### 高级配置 +```go +config.SetPadding(20) // 所有边 +config.SetPadding(20, 40) // 垂直,水平 +config.SetPadding(20, 40, 20, 40) // 上,右,下,左 + +config.SetShadow(20, 0, 10) // 模糊,X 偏移,Y 偏移 +config.SetBorder(1, 8, "#333") // 宽度,圆角,颜色 +config.SetDimensions(800, 600) // 宽度,高度 +config.SetLines(10, 20) // 行范围(1-indexed) +``` + +### 预设 + +FreezeLib 提供了几个内置预设: + +```go +// 可用预设 +presets := []string{ + "base", // 简洁干净 + "full", // macOS 风格窗口控件 + "terminal", // 终端输出优化 + "presentation", // 演示高对比度 + "minimal", // 极简样式 + "dark", // 深色主题 + "light", // 浅色主题 + "retro", // 复古终端风格 + "neon", // 霓虹/赛博朋克风格 + "compact", // 小代码片段紧凑型 +} + +freeze := freezelib.NewWithPreset("dark") +``` + +### 链式方法 + +`Freeze` 和 `QuickFreeze` 都支持方法链: + +```go +freeze := freezelib.New(). + WithTheme("monokai"). + WithFont("Cascadia Code", 15). + WithWindow(true). + WithShadow(20, 0, 10). + WithLineNumbers(true) + +svgData, err := freeze.GenerateFromCode(code, "rust") +``` + +## 示例 + +### 终端输出截图 + +```go +freeze := freezelib.NewWithPreset("terminal") + +ansiOutput := "\033[32m✓ Tests passed\033[0m\n" + + "\033[31m✗ Build failed\033[0m\n" + + "\033[33m⚠ Warning: deprecated API\033[0m" + +svgData, err := freeze.GenerateFromANSI(ansiOutput) +``` + +### 自定义样式 + +```go +config := freezelib.DefaultConfig() +config.Theme = "github" +config.Background = "#f6f8fa" +config.Font.Family = "SF Mono" +config.Font.Size = 16 +config.SetPadding(30) +config.SetMargin(20) +config.Window = true +config.ShowLineNumbers = true +config.Border.Radius = 12 +config.Shadow.Blur = 25 + +freeze := freezelib.NewWithConfig(config) +``` + +### 批量处理 + +```go +files := []string{"main.go", "config.go", "utils.go"} + +for _, file := range files { + svgData, err := freeze.GenerateFromFile(file) + if err != nil { + continue + } + + outputFile := strings.TrimSuffix(file, ".go") + ".svg" + os.WriteFile(outputFile, svgData, 0644) +} +``` + +## 支持的语言 + +FreezeLib 支持 100+ 种编程语言的语法高亮,包括: + +- Go, Rust, Python, JavaScript, TypeScript +- C, C++, C#, Java, Kotlin, Swift +- HTML, CSS, SCSS, JSON, YAML, XML +- Shell, PowerShell, Dockerfile +- SQL, GraphQL, Markdown +- 等等... + +## 支持的主题 + +流行主题包括: +- `github` / `github-dark` +- `dracula` +- `monokai` +- `solarized-dark` / `solarized-light` +- `nord` +- `one-dark` +- `material` +- `vim` +- 等等... + +## 错误处理 + +```go +svgData, err := freeze.GenerateFromCode(code, "go") +if err != nil { + // 处理特定错误 + switch { + case strings.Contains(err.Error(), "language"): + // 语言检测失败 + case strings.Contains(err.Error(), "config"): + // 配置错误 + default: + // 其他错误 + } +} +``` + +## 贡献 + +欢迎贡献!请随时提交 Pull Request。 + +## 许可证 + +MIT 许可证 - 详见 [LICENSE](./LICENSE) 文件。 + +## 致谢 + +本库基于 [Charm](https://charm.sh) 团队出色的 [freeze](https://github.com/charmbracelet/freeze) CLI 工具。特别感谢 Charm 团队创造了如此美观的工具。 diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..3ddf52a --- /dev/null +++ b/USAGE.md @@ -0,0 +1,264 @@ +# FreezeLib 使用指南 + +**Language / 语言**: [English](USAGE_EN.md) | [中文](USAGE.md) + +**Main Documentation / 主要文档**: [README (English)](README.md) | [README (中文)](README_CN.md) + +FreezeLib 是一个基于 Charm 的 freeze CLI 工具重构的 Go 公共库,用于生成美观的代码截图。 + +## 🚀 快速开始 + +### 基本用法 + +```go +package main + +import ( + "github.com/landaiqing/freezelib" + "os" +) + +func main() { + // 创建 freeze 实例 + freeze := freezelib.New() + + // 要截图的代码 + code := `package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +}` + + // 生成 SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + panic(err) + } + + // 保存到文件 + os.WriteFile("hello.svg", svgData, 0644) +} +``` + +### 链式调用 API + +```go +// 使用 QuickFreeze 进行链式调用 +qf := freezelib.NewQuickFreeze() + +svgData, err := qf.WithTheme("dracula"). + WithFont("Fira Code", 14). + WithWindow(). + WithShadow(). + WithLineNumbers(). + WithLanguage("javascript"). + CodeToSVG(code) +``` + +## 📋 主要功能 + +### 1. 多种输入方式 + +```go +// 从代码字符串生成 +svgData, err := freeze.GenerateFromCode(code, "python") + +// 从文件生成 +svgData, err := freeze.GenerateFromFile("main.go") + +// 从 ANSI 终端输出生成 +ansiOutput := "\033[32m✓ SUCCESS\033[0m: Build completed" +svgData, err := freeze.GenerateFromANSI(ansiOutput) + +// 从 Reader 生成 +svgData, err := freeze.GenerateFromReader(reader, "javascript") +``` + +### 2. 多种输出格式 + +```go +// 生成 SVG +svgData, err := freeze.GenerateFromCode(code, "go") + +// 生成 PNG +pngData, err := freeze.GeneratePNGFromCode(code, "go") + +// 直接保存到文件 +err := freeze.SaveCodeToFile(code, "go", "output.svg") +err := freeze.SaveCodeToFile(code, "go", "output.png") // 自动检测格式 +``` + +### 3. 预设配置 + +```go +// 使用预设配置 +freeze := freezelib.NewWithPreset("dark") // 深色主题 +freeze := freezelib.NewWithPreset("terminal") // 终端风格 +freeze := freezelib.NewWithPreset("presentation") // 演示风格 + +// 可用预设 +presets := []string{ + "base", // 基础样式 + "full", // macOS 风格 + "terminal", // 终端优化 + "presentation", // 演示优化 + "minimal", // 极简风格 + "dark", // 深色主题 + "light", // 浅色主题 + "retro", // 复古风格 + "neon", // 霓虹风格 + "compact", // 紧凑风格 +} +``` + +### 4. 自定义配置 + +```go +config := freezelib.DefaultConfig() + +// 基本设置 +config.SetTheme("github-dark") +config.SetFont("JetBrains Mono", 14) +config.SetBackground("#1e1e1e") +config.SetLanguage("python") + +// 布局设置 +config.SetPadding(20) // 所有边 +config.SetPadding(20, 40) // 垂直,水平 +config.SetPadding(20, 40, 20, 40) // 上,右,下,左 +config.SetMargin(15) +config.SetDimensions(800, 600) + +// 装饰效果 +config.SetWindow(true) // 窗口控件 +config.SetLineNumbers(true) // 行号 +config.SetShadow(20, 0, 10) // 阴影:模糊,X偏移,Y偏移 +config.SetBorder(1, 8, "#333") // 边框:宽度,圆角,颜色 + +// 行范围(1-indexed) +config.SetLines(10, 20) // 只截取第10-20行 + +freeze := freezelib.NewWithConfig(config) +``` + +## 🎨 支持的主题 + +- `github` / `github-dark` +- `dracula` +- `monokai` +- `solarized-dark` / `solarized-light` +- `nord` +- `one-dark` +- `material` +- `vim` +- 等等... + +## 💻 支持的语言 + +支持 100+ 种编程语言,包括: +- Go, Rust, Python, JavaScript, TypeScript +- C, C++, C#, Java, Kotlin, Swift +- HTML, CSS, SCSS, JSON, YAML, XML +- Shell, PowerShell, Dockerfile +- SQL, GraphQL, Markdown +- 等等... + +## 🔧 高级用法 + +### 批量处理 + +```go +files := []string{"main.go", "config.go", "utils.go"} + +for _, file := range files { + svgData, err := freeze.GenerateFromFile(file) + if err != nil { + continue + } + + outputFile := strings.TrimSuffix(file, ".go") + ".svg" + os.WriteFile(outputFile, svgData, 0644) +} +``` + +### 终端输出截图 + +```go +freeze := freezelib.NewWithPreset("terminal") + +ansiOutput := "\033[32m✓ Tests passed\033[0m\n" + + "\033[31m✗ Build failed\033[0m\n" + + "\033[33m⚠ Warning: deprecated API\033[0m" + +svgData, err := freeze.GenerateFromANSI(ansiOutput) +``` + +### 链式方法 + +```go +freeze := freezelib.New(). + WithTheme("monokai"). + WithFont("Cascadia Code", 15). + WithWindow(true). + WithShadow(20, 0, 10). + WithLineNumbers(true) + +svgData, err := freeze.GenerateFromCode(code, "rust") +``` + +## 📊 性能优化建议 + +1. **重用实例**:创建一个 `Freeze` 实例并重复使用 +2. **选择合适格式**:网页用 SVG,演示用 PNG +3. **设置具体尺寸**:指定尺寸可提高性能 +4. **批量操作**:在单个会话中处理多个文件 + +## 🐛 错误处理 + +```go +svgData, err := freeze.GenerateFromCode(code, "go") +if err != nil { + switch { + case strings.Contains(err.Error(), "language"): + // 语言检测失败 + case strings.Contains(err.Error(), "config"): + // 配置错误 + default: + // 其他错误 + } +} +``` + +## 📁 项目结构 + +``` +freezelib/ +├── freeze.go # 主要 API 接口 +├── config.go # 配置结构体 +├── generator.go # 核心生成逻辑 +├── quickfreeze.go # 简化 API +├── presets.go # 预设配置 +├── ansi.go # ANSI 处理 +├── svg/ # SVG 处理 +├── font/ # 字体处理 +├── example/ # 使用示例 +└── README.md # 详细文档 +``` + +## 🤝 与原版 freeze 的区别 + +| 特性 | 原版 freeze | FreezeLib | +|------|-------------|-----------| +| 使用方式 | CLI 工具 | Go 库 | +| 集成方式 | 命令行调用 | 直接导入 | +| 配置方式 | 命令行参数/配置文件 | Go 结构体 | +| 扩展性 | 有限 | 高度可扩展 | +| 性能 | 进程启动开销 | 内存中处理 | + +## 📝 示例代码 + +查看 `examples` 目录中的完整示例: + +这将生成多个示例 SVG 文件,展示库的各种功能。 diff --git a/USAGE_EN.md b/USAGE_EN.md new file mode 100644 index 0000000..b107aab --- /dev/null +++ b/USAGE_EN.md @@ -0,0 +1,264 @@ +# FreezeLib Usage Guide + +**Language / 语言**: [English](USAGE_EN.md) | [中文](USAGE.md) + +**Main Documentation / 主要文档**: [README (English)](README.md) | [README (中文)](README_CN.md) + +FreezeLib is a Go library refactored from Charm's freeze CLI tool for generating beautiful code screenshots. + +## 🚀 Quick Start + +### Basic Usage + +```go +package main + +import ( + "github.com/landaiqing/freezelib" + "os" +) + +func main() { + // Create freeze instance + freeze := freezelib.New() + + // Code to screenshot + code := `package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +}` + + // Generate SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + panic(err) + } + + // Save to file + os.WriteFile("hello.svg", svgData, 0644) +} +``` + +### Chainable API + +```go +// Use QuickFreeze for method chaining +qf := freezelib.NewQuickFreeze() + +svgData, err := qf.WithTheme("dracula"). + WithFont("Fira Code", 14). + WithWindow(). + WithShadow(). + WithLineNumbers(). + WithLanguage("javascript"). + CodeToSVG(code) +``` + +## 📋 Main Features + +### 1. Multiple Input Methods + +```go +// Generate from code string +svgData, err := freeze.GenerateFromCode(code, "python") + +// Generate from file +svgData, err := freeze.GenerateFromFile("main.go") + +// Generate from ANSI terminal output +ansiOutput := "\033[32m✓ SUCCESS\033[0m: Build completed" +svgData, err := freeze.GenerateFromANSI(ansiOutput) + +// Generate from Reader +svgData, err := freeze.GenerateFromReader(reader, "javascript") +``` + +### 2. Multiple Output Formats + +```go +// Generate SVG +svgData, err := freeze.GenerateFromCode(code, "go") + +// Generate PNG +pngData, err := freeze.GeneratePNGFromCode(code, "go") + +// Save directly to file +err := freeze.SaveCodeToFile(code, "go", "output.svg") +err := freeze.SaveCodeToFile(code, "go", "output.png") // Auto-detect format +``` + +### 3. Preset Configurations + +```go +// Use preset configurations +freeze := freezelib.NewWithPreset("dark") // Dark theme +freeze := freezelib.NewWithPreset("terminal") // Terminal style +freeze := freezelib.NewWithPreset("presentation") // Presentation style + +// Available presets +presets := []string{ + "base", // Basic style + "full", // macOS style + "terminal", // Terminal optimized + "presentation", // Presentation optimized + "minimal", // Minimal style + "dark", // Dark theme + "light", // Light theme + "retro", // Retro style + "neon", // Neon style + "compact", // Compact style +} +``` + +### 4. Custom Configuration + +```go +config := freezelib.DefaultConfig() + +// Basic settings +config.SetTheme("github-dark") +config.SetFont("JetBrains Mono", 14) +config.SetBackground("#1e1e1e") +config.SetLanguage("python") + +// Layout settings +config.SetPadding(20) // All sides +config.SetPadding(20, 40) // Vertical, horizontal +config.SetPadding(20, 40, 20, 40) // Top, right, bottom, left +config.SetMargin(15) +config.SetDimensions(800, 600) + +// Decorative effects +config.SetWindow(true) // Window controls +config.SetLineNumbers(true) // Line numbers +config.SetShadow(20, 0, 10) // Shadow: blur, X offset, Y offset +config.SetBorder(1, 8, "#333") // Border: width, radius, color + +// Line range (1-indexed) +config.SetLines(10, 20) // Only capture lines 10-20 + +freeze := freezelib.NewWithConfig(config) +``` + +## 🎨 Supported Themes + +- `github` / `github-dark` +- `dracula` +- `monokai` +- `solarized-dark` / `solarized-light` +- `nord` +- `one-dark` +- `material` +- `vim` +- And more... + +## 💻 Supported Languages + +Supports 100+ programming languages including: +- Go, Rust, Python, JavaScript, TypeScript +- C, C++, C#, Java, Kotlin, Swift +- HTML, CSS, SCSS, JSON, YAML, XML +- Shell, PowerShell, Dockerfile +- SQL, GraphQL, Markdown +- And more... + +## 🔧 Advanced Usage + +### Batch Processing + +```go +files := []string{"main.go", "config.go", "utils.go"} + +for _, file := range files { + svgData, err := freeze.GenerateFromFile(file) + if err != nil { + continue + } + + outputFile := strings.TrimSuffix(file, ".go") + ".svg" + os.WriteFile(outputFile, svgData, 0644) +} +``` + +### Terminal Output Screenshots + +```go +freeze := freezelib.NewWithPreset("terminal") + +ansiOutput := "\033[32m✓ Tests passed\033[0m\n" + + "\033[31m✗ Build failed\033[0m\n" + + "\033[33m⚠ Warning: deprecated API\033[0m" + +svgData, err := freeze.GenerateFromANSI(ansiOutput) +``` + +### Method Chaining + +```go +freeze := freezelib.New(). + WithTheme("monokai"). + WithFont("Cascadia Code", 15). + WithWindow(true). + WithShadow(20, 0, 10). + WithLineNumbers(true) + +svgData, err := freeze.GenerateFromCode(code, "rust") +``` + +## 📊 Performance Optimization Tips + +1. **Reuse instances**: Create one `Freeze` instance and reuse it +2. **Choose appropriate formats**: SVG for web, PNG for presentations +3. **Set specific dimensions**: Specifying dimensions improves performance +4. **Batch operations**: Process multiple files in a single session + +## 🐛 Error Handling + +```go +svgData, err := freeze.GenerateFromCode(code, "go") +if err != nil { + switch { + case strings.Contains(err.Error(), "language"): + // Language detection failed + case strings.Contains(err.Error(), "config"): + // Configuration error + default: + // Other errors + } +} +``` + +## 📁 Project Structure + +``` +freezelib/ +├── freeze.go # Main API interface +├── config.go # Configuration structs +├── generator.go # Core generation logic +├── quickfreeze.go # Simplified API +├── presets.go # Preset configurations +├── ansi.go # ANSI processing +├── svg/ # SVG processing +├── font/ # Font processing +├── example/ # Usage examples +└── README.md # Detailed documentation +``` + +## 🤝 Differences from Original freeze + +| Feature | Original freeze | FreezeLib | +|---------|----------------|-----------| +| Usage | CLI tool | Go library | +| Integration | Command line calls | Direct import | +| Configuration | CLI args/config files | Go structs | +| Extensibility | Limited | Highly extensible | +| Performance | Process startup overhead | In-memory processing | + +## 📝 Example Code + +Check the complete examples in the `examples` directory: + +This will generate multiple example SVG files showcasing various features of the library. diff --git a/ansi.go b/ansi.go new file mode 100644 index 0000000..9f24f26 --- /dev/null +++ b/ansi.go @@ -0,0 +1,299 @@ +package freezelib + +import ( + "fmt" + "strings" + + "github.com/beevik/etree" + "github.com/charmbracelet/x/ansi" + "github.com/mattn/go-runewidth" +) + +// dispatcher handles ANSI escape sequences and converts them to SVG +type dispatcher struct { + lines []*etree.Element + svg *etree.Element + config *Config + scale float64 + row int + col int + bg *etree.Element + bgWidth int +} + +// newDispatcher creates a new ANSI dispatcher +func newDispatcher(lines []*etree.Element, svg *etree.Element, config *Config, scale float64) *dispatcher { + return &dispatcher{ + lines: lines, + svg: svg, + config: config, + scale: scale, + row: 0, + col: 0, + } +} + +// Print handles printable characters +func (p *dispatcher) Print(r rune) { + p.row = clamp(p.row, 0, len(p.lines)-1) + // insert the rune in the last tspan + children := p.lines[p.row].ChildElements() + var lastChild *etree.Element + isFirstChild := len(children) == 0 + if isFirstChild { + lastChild = etree.NewElement("tspan") + lastChild.CreateAttr("xml:space", "preserve") + p.lines[p.row].AddChild(lastChild) + } else { + lastChild = children[len(children)-1] + } + + if runewidth.RuneWidth(r) > 1 { + newChild := lastChild.Copy() + newChild.SetText(string(r)) + newChild.CreateAttr("dx", fmt.Sprintf("%.2fpx", (p.config.Font.Size/5)*p.scale)) + p.lines[p.row].AddChild(newChild) + } else { + lastChild.SetText(lastChild.Text() + string(r)) + } + + p.col += runewidth.RuneWidth(r) + if p.bg != nil { + p.bgWidth += runewidth.RuneWidth(r) + } +} + +// Execute handles control characters +func (p *dispatcher) Execute(code byte) { + if code == '\t' { + for p.col%16 != 0 { + p.Print(' ') + } + } + if code == '\n' { + p.endBackground() + p.row++ + p.col = 0 + } +} + +// endBackground ends the current background span +func (p *dispatcher) endBackground() { + if p.bg == nil { + return + } + p.bg.CreateAttr("width", fmt.Sprintf("%.2fpx", float64(p.bgWidth)*(p.config.Font.Size/fontHeightToWidthRatio)*p.scale)) + p.bg = nil + p.bgWidth = 0 +} + +// CsiDispatch handles CSI (Control Sequence Introducer) sequences +func (p *dispatcher) CsiDispatch(cmd ansi.Cmd, params ansi.Params) { + if cmd != 'm' { + // ignore incomplete or non Style (SGR) sequences + return + } + + span := etree.NewElement("tspan") + span.CreateAttr("xml:space", "preserve") + reset := func() { + // reset ANSI, this is done by creating a new empty tspan, + // which would reset all the styles such that when text is appended to the last + // child of this line there is no styling applied. + if p.row < len(p.lines) { + p.lines[p.row].AddChild(span) + } + p.endBackground() + } + + if len(params) == 0 { + // zero params means reset + reset() + return + } + + var i int + for i < len(params) { + v := params[i].Param(0) + switch v { + case 0: + reset() + case 1: + // Bold - not implemented in SVG for now + p.lines[p.row].AddChild(span) + case 9: + span.CreateAttr("text-decoration", "line-through") + p.lines[p.row].AddChild(span) + case 3: + span.CreateAttr("font-style", "italic") + p.lines[p.row].AddChild(span) + case 4: + span.CreateAttr("text-decoration", "underline") + p.lines[p.row].AddChild(span) + case 30, 31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97: + span.CreateAttr("fill", ansiPalette[v]) + p.lines[p.row].AddChild(span) + case 38: + i++ + if i < len(params) { + switch params[i].Param(0) { + case 5: + if i+1 < len(params) { + n := params[i+1].Param(0) + i++ + fill := palette[n] + span.CreateAttr("fill", fill) + p.lines[p.row].AddChild(span) + } + case 2: + if i+3 < len(params) { + r := params[i+1].Param(0) + g := params[i+2].Param(0) + b := params[i+3].Param(0) + i += 3 + fill := fmt.Sprintf("rgb(%d,%d,%d)", r, g, b) + span.CreateAttr("fill", fill) + p.lines[p.row].AddChild(span) + } + } + } + case 40, 41, 42, 43, 44, 45, 46, 47, 100, 101, 102, 103, 104, 105, 106, 107: + // Background colors + p.endBackground() + p.bg = etree.NewElement("rect") + p.bg.CreateAttr("fill", ansiPalette[v-10]) + p.bg.CreateAttr("height", fmt.Sprintf("%.2fpx", p.config.Font.Size*p.config.LineHeight)) + p.bg.CreateAttr("x", fmt.Sprintf("%.2fpx", float64(p.col)*(p.config.Font.Size/fontHeightToWidthRatio)*p.scale)) + p.bg.CreateAttr("y", fmt.Sprintf("%.2fpx", float64(p.row)*p.config.Font.Size*p.config.LineHeight)) + p.svg.InsertChildAt(0, p.bg) + case 48: + i++ + if i < len(params) { + switch params[i].Param(0) { + case 5: + if i+1 < len(params) { + n := params[i+1].Param(0) + i++ + p.endBackground() + p.bg = etree.NewElement("rect") + p.bg.CreateAttr("fill", palette[n]) + p.bg.CreateAttr("height", fmt.Sprintf("%.2fpx", p.config.Font.Size*p.config.LineHeight)) + p.bg.CreateAttr("x", fmt.Sprintf("%.2fpx", float64(p.col)*(p.config.Font.Size/fontHeightToWidthRatio)*p.scale)) + p.bg.CreateAttr("y", fmt.Sprintf("%.2fpx", float64(p.row)*p.config.Font.Size*p.config.LineHeight)) + p.svg.InsertChildAt(0, p.bg) + } + case 2: + if i+3 < len(params) { + r := params[i+1].Param(0) + g := params[i+2].Param(0) + b := params[i+3].Param(0) + i += 3 + p.endBackground() + p.bg = etree.NewElement("rect") + p.bg.CreateAttr("fill", fmt.Sprintf("rgb(%d,%d,%d)", r, g, b)) + p.bg.CreateAttr("height", fmt.Sprintf("%.2fpx", p.config.Font.Size*p.config.LineHeight)) + p.bg.CreateAttr("x", fmt.Sprintf("%.2fpx", float64(p.col)*(p.config.Font.Size/fontHeightToWidthRatio)*p.scale)) + p.bg.CreateAttr("y", fmt.Sprintf("%.2fpx", float64(p.row)*p.config.Font.Size*p.config.LineHeight)) + p.svg.InsertChildAt(0, p.bg) + } + } + } + } + i++ + } +} + +// processANSI processes ANSI escape sequences in the input text +func processANSI(input string, lines []*etree.Element, svg *etree.Element, config *Config, scale float64) { + d := newDispatcher(lines, svg, config, scale) + parser := ansi.NewParser() + parser.SetHandler(ansi.Handler{ + Print: d.Print, + HandleCsi: d.CsiDispatch, + Execute: d.Execute, + }) + + for _, line := range strings.Split(input, "\n") { + parser.Parse([]byte(line)) + d.Execute(ansi.LF) // simulate a newline + } +} + +// stripANSI removes ANSI escape sequences from text +func stripANSI(input string) string { + return ansi.Strip(input) +} + +// isANSI checks if the input contains ANSI escape sequences +func isANSI(input string) bool { + return stripANSI(input) != input +} + +// clamp constrains a value between min and max +func clamp(value, min, max int) int { + if value < min { + return min + } + if value > max { + return max + } + return value +} + +const fontHeightToWidthRatio = 1.68 + +// ANSI color palette +var ansiPalette = map[int]string{ + 30: "#000000", // black + 31: "#FF0000", // red + 32: "#00FF00", // green + 33: "#FFFF00", // yellow + 34: "#0000FF", // blue + 35: "#FF00FF", // magenta + 36: "#00FFFF", // cyan + 37: "#FFFFFF", // white + 90: "#808080", // bright black (gray) + 91: "#FF8080", // bright red + 92: "#80FF80", // bright green + 93: "#FFFF80", // bright yellow + 94: "#8080FF", // bright blue + 95: "#FF80FF", // bright magenta + 96: "#80FFFF", // bright cyan + 97: "#FFFFFF", // bright white +} + +// 256-color palette +var palette = []string{ + "#000000", "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0", + "#808080", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff", + "#000000", "#00005f", "#000087", "#0000af", "#0000d7", "#0000ff", "#005f00", "#005f5f", + "#005f87", "#005faf", "#005fd7", "#005fff", "#008700", "#00875f", "#008787", "#0087af", + "#0087d7", "#0087ff", "#00af00", "#00af5f", "#00af87", "#00afaf", "#00afd7", "#00afff", + "#00d700", "#00d75f", "#00d787", "#00d7af", "#00d7d7", "#00d7ff", "#00ff00", "#00ff5f", + "#00ff87", "#00ffaf", "#00ffd7", "#00ffff", "#5f0000", "#5f005f", "#5f0087", "#5f00af", + "#5f00d7", "#5f00ff", "#5f5f00", "#5f5f5f", "#5f5f87", "#5f5faf", "#5f5fd7", "#5f5fff", + "#5f8700", "#5f875f", "#5f8787", "#5f87af", "#5f87d7", "#5f87ff", "#5faf00", "#5faf5f", + "#5faf87", "#5fafaf", "#5fafd7", "#5fafff", "#5fd700", "#5fd75f", "#5fd787", "#5fd7af", + "#5fd7d7", "#5fd7ff", "#5fff00", "#5fff5f", "#5fff87", "#5fffaf", "#5fffd7", "#5fffff", + "#870000", "#87005f", "#870087", "#8700af", "#8700d7", "#8700ff", "#875f00", "#875f5f", + "#875f87", "#875faf", "#875fd7", "#875fff", "#878700", "#87875f", "#878787", "#8787af", + "#8787d7", "#8787ff", "#87af00", "#87af5f", "#87af87", "#87afaf", "#87afd7", "#87afff", + "#87d700", "#87d75f", "#87d787", "#87d7af", "#87d7d7", "#87d7ff", "#87ff00", "#87ff5f", + "#87ff87", "#87ffaf", "#87ffd7", "#87ffff", "#af0000", "#af005f", "#af0087", "#af00af", + "#af00d7", "#af00ff", "#af5f00", "#af5f5f", "#af5f87", "#af5faf", "#af5fd7", "#af5fff", + "#af8700", "#af875f", "#af8787", "#af87af", "#af87d7", "#af87ff", "#afaf00", "#afaf5f", + "#afaf87", "#afafaf", "#afafd7", "#afafff", "#afd700", "#afd75f", "#afd787", "#afd7af", + "#afd7d7", "#afd7ff", "#afff00", "#afff5f", "#afff87", "#afffaf", "#afffd7", "#afffff", + "#d70000", "#d7005f", "#d70087", "#d700af", "#d700d7", "#d700ff", "#d75f00", "#d75f5f", + "#d75f87", "#d75faf", "#d75fd7", "#d75fff", "#d78700", "#d7875f", "#d78787", "#d787af", + "#d787d7", "#d787ff", "#d7af00", "#d7af5f", "#d7af87", "#d7afaf", "#d7afd7", "#d7afff", + "#d7d700", "#d7d75f", "#d7d787", "#d7d7af", "#d7d7d7", "#d7d7ff", "#d7ff00", "#d7ff5f", + "#d7ff87", "#d7ffaf", "#d7ffd7", "#d7ffff", "#ff0000", "#ff005f", "#ff0087", "#ff00af", + "#ff00d7", "#ff00ff", "#ff5f00", "#ff5f5f", "#ff5f87", "#ff5faf", "#ff5fd7", "#ff5fff", + "#ff8700", "#ff875f", "#ff8787", "#ff87af", "#ff87d7", "#ff87ff", "#ffaf00", "#ffaf5f", + "#ffaf87", "#ffafaf", "#ffafd7", "#ffafff", "#ffd700", "#ffd75f", "#ffd787", "#ffd7af", + "#ffd7d7", "#ffd7ff", "#ffff00", "#ffff5f", "#ffff87", "#ffffaf", "#ffffd7", "#ffffff", + "#080808", "#121212", "#1c1c1c", "#262626", "#303030", "#3a3a3a", "#444444", "#4e4e4e", + "#585858", "#626262", "#6c6c6c", "#767676", "#808080", "#8a8a8a", "#949494", "#9e9e9e", + "#a8a8a8", "#b2b2b2", "#bcbcbc", "#c6c6c6", "#d0d0d0", "#dadada", "#e4e4e4", "#eeeeee", +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..b710e2d --- /dev/null +++ b/config.go @@ -0,0 +1,242 @@ +package freezelib + +import ( + "fmt" + "strconv" + "strings" +) + +// Config represents the configuration for generating code screenshots +type Config struct { + // Window settings + Background string `json:"background"` + Margin []float64 `json:"margin"` + Padding []float64 `json:"padding"` + Window bool `json:"window"` + Width float64 `json:"width"` + Height float64 `json:"height"` + + // Language and theme + Language string `json:"language"` + Theme string `json:"theme"` + Wrap int `json:"wrap"` + + // Decoration + Border Border `json:"border"` + Shadow Shadow `json:"shadow"` + + // Font + Font Font `json:"font"` + + // Line settings + LineHeight float64 `json:"line_height"` + Lines []int `json:"lines"` + ShowLineNumbers bool `json:"show_line_numbers"` +} + +// Shadow configuration for drop shadow effects +type Shadow struct { + Blur float64 `json:"blur"` + X float64 `json:"x"` + Y float64 `json:"y"` +} + +// Border configuration for window borders +type Border struct { + Radius float64 `json:"radius"` + Width float64 `json:"width"` + Color string `json:"color"` +} + +// Font configuration +type Font struct { + Family string `json:"family"` + File string `json:"file"` + Size float64 `json:"size"` + Ligatures bool `json:"ligatures"` +} + +// DefaultConfig returns a default configuration +func DefaultConfig() *Config { + return &Config{ + Background: "#171717", + Margin: []float64{0}, + Padding: []float64{20}, + Window: false, + Width: 0, + Height: 0, + Language: "", + Theme: "charm", + Wrap: 0, + Border: Border{Radius: 0, Width: 0, Color: "#515151"}, + Shadow: Shadow{Blur: 0, X: 0, Y: 0}, + Font: Font{Family: "JetBrains Mono", Size: 14, Ligatures: true}, + LineHeight: 1.2, + Lines: []int{}, + ShowLineNumbers: false, + } +} + +// SetPadding sets padding for all sides or specific sides +// Accepts 1, 2, or 4 values like CSS padding +func (c *Config) SetPadding(values ...float64) *Config { + c.Padding = values + return c +} + +// SetMargin sets margin for all sides or specific sides +// Accepts 1, 2, or 4 values like CSS margin +func (c *Config) SetMargin(values ...float64) *Config { + c.Margin = values + return c +} + +// SetFont sets font family and size +func (c *Config) SetFont(family string, size float64) *Config { + c.Font.Family = family + c.Font.Size = size + return c +} + +// SetTheme sets the syntax highlighting theme +func (c *Config) SetTheme(theme string) *Config { + c.Theme = theme + return c +} + +// SetLanguage sets the programming language for syntax highlighting +func (c *Config) SetLanguage(language string) *Config { + c.Language = language + return c +} + +// SetBackground sets the background color +func (c *Config) SetBackground(color string) *Config { + c.Background = color + return c +} + +// SetWindow enables or disables window controls +func (c *Config) SetWindow(enabled bool) *Config { + c.Window = enabled + return c +} + +// SetLineNumbers enables or disables line numbers +func (c *Config) SetLineNumbers(enabled bool) *Config { + c.ShowLineNumbers = enabled + return c +} + +// SetShadow sets shadow properties +func (c *Config) SetShadow(blur, x, y float64) *Config { + c.Shadow = Shadow{Blur: blur, X: x, Y: y} + return c +} + +// SetBorder sets border properties +func (c *Config) SetBorder(width, radius float64, color string) *Config { + c.Border = Border{Width: width, Radius: radius, Color: color} + return c +} + +// SetDimensions sets the output dimensions +func (c *Config) SetDimensions(width, height float64) *Config { + c.Width = width + c.Height = height + return c +} + +// SetLines sets the line range to capture (1-indexed) +func (c *Config) SetLines(start, end int) *Config { + if start > 0 && end > 0 && start <= end { + c.Lines = []int{start - 1, end - 1} // Convert to 0-indexed + } + return c +} + +// expandPadding expands padding values according to CSS rules +func (c *Config) expandPadding(scale float64) []float64 { + p := c.Padding + switch len(p) { + case 1: + return []float64{p[0] * scale, p[0] * scale, p[0] * scale, p[0] * scale} + case 2: + return []float64{p[0] * scale, p[1] * scale, p[0] * scale, p[1] * scale} + case 4: + return []float64{p[0] * scale, p[1] * scale, p[2] * scale, p[3] * scale} + default: + return []float64{0, 0, 0, 0} + } +} + +// expandMargin expands margin values according to CSS rules +func (c *Config) expandMargin(scale float64) []float64 { + m := c.Margin + switch len(m) { + case 1: + return []float64{m[0] * scale, m[0] * scale, m[0] * scale, m[0] * scale} + case 2: + return []float64{m[0] * scale, m[1] * scale, m[0] * scale, m[1] * scale} + case 4: + return []float64{m[0] * scale, m[1] * scale, m[2] * scale, m[3] * scale} + default: + return []float64{0, 0, 0, 0} + } +} + +// Clone creates a deep copy of the configuration +func (c *Config) Clone() *Config { + clone := *c + clone.Margin = make([]float64, len(c.Margin)) + copy(clone.Margin, c.Margin) + clone.Padding = make([]float64, len(c.Padding)) + copy(clone.Padding, c.Padding) + clone.Lines = make([]int, len(c.Lines)) + copy(clone.Lines, c.Lines) + return &clone +} + +// Validate checks if the configuration is valid +func (c *Config) Validate() error { + if c.Font.Size <= 0 { + return fmt.Errorf("font size must be positive") + } + if c.LineHeight <= 0 { + return fmt.Errorf("line height must be positive") + } + if len(c.Lines) == 2 && c.Lines[0] > c.Lines[1] { + return fmt.Errorf("start line must be less than or equal to end line") + } + return nil +} + +// parseColor validates and normalizes color values +func parseColor(color string) string { + color = strings.TrimSpace(color) + if color == "" { + return "#000000" + } + if !strings.HasPrefix(color, "#") { + color = "#" + color + } + return color +} + +// dimensionToInt converts dimension strings to integers +func dimensionToInt(dimension string) int { + dimension = strings.TrimSuffix(dimension, "px") + val, err := strconv.Atoi(dimension) + if err != nil { + return 0 + } + return val +} + +// side constants for padding/margin indexing +const ( + top = 0 + right = 1 + bottom = 2 + left = 3 +) diff --git a/examples/01-basic/main.go b/examples/01-basic/main.go new file mode 100644 index 0000000..1203d0e --- /dev/null +++ b/examples/01-basic/main.go @@ -0,0 +1,173 @@ +package main + +import ( + "fmt" + "github.com/landaiqing/freezelib" + "os" +) + +func main() { + fmt.Println("🎯 Basic Usage Examples") + fmt.Println("=======================") + + // Create output directory + os.MkdirAll("./output", 0755) + + // Run basic examples + simpleExample() + helloWorldExample() + quickStartExample() + defaultConfigExample() + + fmt.Println("\n✅ Basic examples completed!") + fmt.Println("📁 Check the 'output' directory for generated files.") +} + +// Simple example - minimal code +func simpleExample() { + fmt.Println("\n📝 Simple Example") + fmt.Println("------------------") + + freeze := freezelib.New() + + code := `fmt.Println("Hello, FreezeLib!")` + + // Generate SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/simple.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/simple.svg") +} + +// Hello World example - classic first program +func helloWorldExample() { + fmt.Println("\n👋 Hello World Example") + fmt.Println("-----------------------") + + freeze := freezelib.New() + + code := `package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") + fmt.Println("Welcome to FreezeLib!") + + // This is a comment + for i := 1; i <= 3; i++ { + fmt.Printf("Count: %d\n", i) + } +}` + + // Generate both SVG and PNG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + fmt.Printf("❌ Error generating SVG: %v\n", err) + return + } + + pngData, err := freeze.GeneratePNGFromCode(code, "go") + if err != nil { + fmt.Printf("❌ Error generating PNG: %v\n", err) + return + } + + // Save files + os.WriteFile("output/hello_world.svg", svgData, 0644) + os.WriteFile("output/hello_world.png", pngData, 0644) + + fmt.Println("✅ Generated: output/hello_world.svg") + fmt.Println("✅ Generated: output/hello_world.png") +} + +// Quick start example - using QuickFreeze API +func quickStartExample() { + fmt.Println("\n⚡ Quick Start Example") + fmt.Println("----------------------") + + qf := freezelib.NewQuickFreeze() + + code := `function greet(name) { + return "Hello, " + name + "!"; +} + +const message = greet("FreezeLib"); +console.log(message); + +// Arrow function example +const multiply = (a, b) => a * b; +console.log("5 * 3 =", multiply(5, 3));` + + // Use QuickFreeze with basic styling + svgData, err := qf.WithTheme("github"). + WithFont("JetBrains Mono", 14). + WithLineNumbers(). + CodeToSVG(code) + + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/quick_start.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/quick_start.svg") +} + +// Default configuration example +func defaultConfigExample() { + fmt.Println("\n⚙️ Default Configuration Example") + fmt.Println("---------------------------------") + + // Show what default configuration looks like + config := freezelib.DefaultConfig() + freeze := freezelib.NewWithConfig(config) + + code := `# Python Example +def fibonacci(n): + """Calculate fibonacci number recursively.""" + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +# Generate first 10 fibonacci numbers +print("Fibonacci sequence:") +for i in range(10): + print(f"F({i}) = {fibonacci(i)}")` + + svgData, err := freeze.GenerateFromCode(code, "python") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/default_config.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/default_config.svg") + + // Print configuration details + fmt.Println("\n📋 Default Configuration:") + fmt.Printf(" Theme: %s\n", config.Theme) + fmt.Printf(" Font: %s, %dpt\n", config.Font.Family, config.Font.Size) + fmt.Printf(" Background: %s\n", config.Background) + fmt.Printf(" Window: %t\n", config.Window) + fmt.Printf(" Line Numbers: %t\n", config.ShowLineNumbers) +} diff --git a/examples/02-formats/main.go b/examples/02-formats/main.go new file mode 100644 index 0000000..020f9c7 --- /dev/null +++ b/examples/02-formats/main.go @@ -0,0 +1,337 @@ +package main + +import ( + "fmt" + "github.com/landaiqing/freezelib" + "os" +) + +func main() { + fmt.Println("📊 Output Format Examples") + fmt.Println("=========================") + + // Create output directory + os.MkdirAll("output", 0755) + + // Run format examples + svgVsPngExample() + qualityComparisonExample() + dimensionExamples() + formatOptimizationExample() + + fmt.Println("\n✅ Format examples completed!") + fmt.Println("📁 Check the 'output' directory for generated files.") + fmt.Println("📏 Compare file sizes and visual quality between formats.") +} + +// SVG vs PNG comparison +func svgVsPngExample() { + fmt.Println("\n🆚 SVG vs PNG Comparison") + fmt.Println("-------------------------") + + freeze := freezelib.New(). + WithTheme("github-dark"). + WithFont("JetBrains Mono", 14). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 8) + + code := `package main + +import ( + "fmt" + "net/http" + "log" +) + +func handler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) +} + +func main() { + http.HandleFunc("/", handler) + fmt.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +}` + + // Generate SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + fmt.Printf("❌ Error generating SVG: %v\n", err) + return + } + + // Generate PNG + pngData, err := freeze.GeneratePNGFromCode(code, "go") + if err != nil { + fmt.Printf("❌ Error generating PNG: %v\n", err) + return + } + + // Save files + svgPath := "output/comparison.svg" + pngPath := "output/comparison.png" + + os.WriteFile(svgPath, svgData, 0644) + os.WriteFile(pngPath, pngData, 0644) + + // Show file size comparison + svgInfo, _ := os.Stat(svgPath) + pngInfo, _ := os.Stat(pngPath) + + fmt.Printf("✅ Generated: %s (%d bytes)\n", svgPath, svgInfo.Size()) + fmt.Printf("✅ Generated: %s (%d bytes)\n", pngPath, pngInfo.Size()) + fmt.Printf("📊 Size ratio: PNG is %.1fx larger than SVG\n", + float64(pngInfo.Size())/float64(svgInfo.Size())) +} + +// Quality comparison with different settings +func qualityComparisonExample() { + fmt.Println("\n🎨 Quality Comparison") + fmt.Println("---------------------") + + baseCode := `def quicksort(arr): + if len(arr) <= 1: + return arr + + pivot = arr[len(arr) // 2] + left = [x for x in arr if x < pivot] + middle = [x for x in arr if x == pivot] + right = [x for x in arr if x > pivot] + + return quicksort(left) + middle + quicksort(right) + +# Example usage +numbers = [3, 6, 8, 10, 1, 2, 1] +sorted_numbers = quicksort(numbers) +print(f"Original: {numbers}") +print(f"Sorted: {sorted_numbers}")` + + // Different quality settings + configs := []struct { + name string + width float64 + height float64 + fontSize float64 + theme string + }{ + {"low_quality", 400, 300, 10, "github"}, + {"medium_quality", 800, 600, 14, "github-dark"}, + {"high_quality", 1200, 900, 16, "dracula"}, + {"ultra_quality", 1600, 1200, 18, "monokai"}, + } + + for _, config := range configs { + fmt.Printf("🔧 Generating %s...\n", config.name) + + freeze := freezelib.New(). + WithTheme(config.theme). + WithFont("JetBrains Mono", config.fontSize). + WithDimensions(config.width, config.height). + WithWindow(true). + WithLineNumbers(true). + WithShadow(10, 0, 5) + + // Generate PNG for quality comparison + pngData, err := freeze.GeneratePNGFromCode(baseCode, "python") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/quality_%s.png", config.name) + err = os.WriteFile(filename, pngData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + // Show file info + info, _ := os.Stat(filename) + fmt.Printf("✅ Generated: %s (%dx%d, %d bytes)\n", + filename, config.width, config.height, info.Size()) + } +} + +// Different dimension examples +func dimensionExamples() { + fmt.Println("\n📏 Dimension Examples") + fmt.Println("---------------------") + + code := `SELECT u.name, u.email, COUNT(o.id) as order_count +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE u.created_at >= '2024-01-01' +GROUP BY u.id, u.name, u.email +HAVING COUNT(o.id) > 0 +ORDER BY order_count DESC +LIMIT 10;` + + dimensions := []struct { + name string + width float64 + height float64 + desc string + }{ + {"square", 600, 600, "Square format"}, + {"wide", 1000, 400, "Wide format (presentations)"}, + {"tall", 400, 800, "Tall format (mobile)"}, + {"standard", 800, 600, "Standard 4:3 ratio"}, + {"widescreen", 1200, 675, "Widescreen 16:9 ratio"}, + } + + for _, dim := range dimensions { + fmt.Printf("📐 Creating %s format (%fx%f)...\n", dim.name, dim.width, dim.height) + + freeze := freezelib.New(). + WithTheme("nord"). + WithFont("Cascadia Code", 13). + WithDimensions(dim.width, dim.height). + WithWindow(true). + WithLineNumbers(true). + WithPadding(20) + + svgData, err := freeze.GenerateFromCode(code, "sql") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/dimension_%s.svg", dim.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + fmt.Printf("✅ Generated: %s - %s\n", filename, dim.desc) + } +} + +// Format optimization examples +func formatOptimizationExample() { + fmt.Println("\n⚡ Format Optimization") + fmt.Println("----------------------") + + code := `import React, { useState, useEffect } from 'react'; + +const TodoApp = () => { + const [todos, setTodos] = useState([]); + const [input, setInput] = useState(''); + + useEffect(() => { + // Load todos from localStorage + const saved = localStorage.getItem('todos'); + if (saved) { + setTodos(JSON.parse(saved)); + } + }, []); + + const addTodo = () => { + if (input.trim()) { + const newTodo = { + id: Date.now(), + text: input, + completed: false + }; + setTodos([...todos, newTodo]); + setInput(''); + } + }; + + return ( +
+

Todo List

+ setInput(e.target.value)} + placeholder="Add a todo..." + /> + +
+ ); +}; + +export default TodoApp;` + + // Optimized for different use cases + optimizations := []struct { + name string + format string + description string + config func() *freezelib.Freeze + }{ + { + "web_optimized", + "svg", + "Optimized for web (small, scalable)", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("system-ui", 12). + WithPadding(15). + WithWindow(false) // No window for smaller size + }, + }, + { + "print_optimized", + "png", + "Optimized for print (high DPI)", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("Times New Roman", 14). + WithDimensions(1200, 900). + WithWindow(true). + WithLineNumbers(true). + WithShadow(0, 0, 0) // No shadow for print + }, + }, + { + "social_optimized", + "png", + "Optimized for social media", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("dracula"). + WithFont("Fira Code", 16). + WithDimensions(1080, 1080). // Square for Instagram + WithWindow(true). + WithShadow(20, 0, 15). + WithPadding(30) + }, + }, + } + + for _, opt := range optimizations { + fmt.Printf("🎯 Creating %s (%s)...\n", opt.name, opt.description) + + freeze := opt.config() + + var data []byte + var err error + var filename string + + if opt.format == "svg" { + data, err = freeze.GenerateFromCode(code, "javascript") + filename = fmt.Sprintf("output/optimized_%s.svg", opt.name) + } else { + data, err = freeze.GeneratePNGFromCode(code, "javascript") + filename = fmt.Sprintf("output/optimized_%s.png", opt.name) + } + + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + err = os.WriteFile(filename, data, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + info, _ := os.Stat(filename) + fmt.Printf("✅ Generated: %s (%d bytes)\n", filename, info.Size()) + } +} diff --git a/examples/03-themes/main.go b/examples/03-themes/main.go new file mode 100644 index 0000000..1cf8150 --- /dev/null +++ b/examples/03-themes/main.go @@ -0,0 +1,395 @@ +package main + +import ( + "fmt" + "os" + + "github.com/landaiqing/freezelib" +) + +func main() { + fmt.Println("🎨 Theme Showcase Examples") + fmt.Println("===========================") + + // Create output directory + os.MkdirAll("output", 0755) + + // Run theme examples + popularThemesExample() + lightVsDarkExample() + themeComparisonExample() + customThemeExample() + + fmt.Println("\n✅ Theme examples completed!") + fmt.Println("📁 Check the 'output' directory for generated files.") + fmt.Println("🎨 Compare different themes and their visual styles.") +} + +// Popular themes showcase +func popularThemesExample() { + fmt.Println("\n🌟 Popular Themes Showcase") + fmt.Println("--------------------------") + + code := `class DataProcessor: + def __init__(self, data_source): + self.data_source = data_source + self.processed_data = [] + + def process(self): + """Process the data with validation and transformation.""" + try: + raw_data = self.load_data() + validated_data = self.validate(raw_data) + self.processed_data = self.transform(validated_data) + return True + except Exception as e: + print(f"Processing failed: {e}") + return False + + def validate(self, data): + # Remove null values and duplicates + clean_data = [item for item in data if item is not None] + return list(set(clean_data)) + + def transform(self, data): + # Apply business logic transformations + return [item.upper() if isinstance(item, str) else item + for item in data]` + + // Popular themes to showcase + themes := []struct { + name string + description string + }{ + {"github", "GitHub light theme - clean and professional"}, + {"github-dark", "GitHub dark theme - modern and sleek"}, + {"dracula", "Dracula theme - purple and pink accents"}, + {"monokai", "Monokai theme - classic dark with vibrant colors"}, + {"solarized-dark", "Solarized dark - easy on the eyes"}, + {"solarized-light", "Solarized light - warm and readable"}, + {"nord", "Nord theme - arctic, north-bluish color palette"}, + {"one-dark", "One Dark theme - Atom's signature theme"}, + {"material", "Material theme - Google's material design"}, + {"vim", "Vim theme - classic terminal colors"}, + } + + for _, theme := range themes { + fmt.Printf("🎨 Generating %s theme...\n", theme.name) + + freeze := freezelib.New(). + WithTheme(theme.name). + WithFont("JetBrains Mono", 14). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 8). + WithPadding(20) + + svgData, err := freeze.GenerateFromCode(code, "python") + if err != nil { + fmt.Printf("❌ Error with theme %s: %v\n", theme.name, err) + continue + } + + filename := fmt.Sprintf("output/theme_%s.svg", theme.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving %s: %v\n", filename, err) + continue + } + + fmt.Printf("✅ Generated: %s - %s\n", filename, theme.description) + } +} + +// Light vs Dark theme comparison +func lightVsDarkExample() { + fmt.Println("\n☀️🌙 Light vs Dark Comparison") + fmt.Println("------------------------------") + + code := ` + + + + + Modern Web App + + + +
+

Welcome to Our App

+
+

Features

+

This is a modern web application with responsive design.

+ +
+
+ +` + + // Light and dark theme pairs + themePairs := []struct { + light string + dark string + name string + }{ + {"github", "github-dark", "GitHub"}, + {"solarized-light", "solarized-dark", "Solarized"}, + {"material", "one-dark", "Material vs One Dark"}, + } + + for _, pair := range themePairs { + fmt.Printf("🔄 Comparing %s themes...\n", pair.name) + + // Light theme + lightFreeze := freezelib.New(). + WithTheme(pair.light). + WithFont("SF Mono", 13). + WithWindow(true). + WithLineNumbers(true). + WithShadow(10, 0, 5). + WithPadding(25) + + lightData, err := lightFreeze.GenerateFromCode(code, "html") + if err != nil { + fmt.Printf("❌ Error with light theme: %v\n", err) + continue + } + + // Dark theme + darkFreeze := freezelib.New(). + WithTheme(pair.dark). + WithFont("SF Mono", 13). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 10). + WithPadding(25) + + darkData, err := darkFreeze.GenerateFromCode(code, "html") + if err != nil { + fmt.Printf("❌ Error with dark theme: %v\n", err) + continue + } + + // Save files + lightFile := fmt.Sprintf("output/comparison_%s_light.svg", + sanitizeFilename(pair.name)) + darkFile := fmt.Sprintf("output/comparison_%s_dark.svg", + sanitizeFilename(pair.name)) + + os.WriteFile(lightFile, lightData, 0644) + os.WriteFile(darkFile, darkData, 0644) + + fmt.Printf("✅ Generated: %s (light)\n", lightFile) + fmt.Printf("✅ Generated: %s (dark)\n", darkFile) + } +} + +// Theme comparison grid +func themeComparisonExample() { + fmt.Println("\n📊 Theme Comparison Grid") + fmt.Println("------------------------") + + // Short code snippet for comparison + code := `fn main() { + let numbers = vec![1, 2, 3, 4, 5]; + + let doubled: Vec = numbers + .iter() + .map(|x| x * 2) + .collect(); + + println!("Original: {:?}", numbers); + println!("Doubled: {:?}", doubled); + + // Pattern matching + match doubled.len() { + 0 => println!("Empty vector"), + 1..=5 => println!("Small vector"), + _ => println!("Large vector"), + } +}` + + // Themes for comparison + comparisonThemes := []string{ + "github", "github-dark", "dracula", "monokai", + "nord", "one-dark", "material", "vim", + } + + for i, theme := range comparisonThemes { + fmt.Printf("🎯 Creating comparison sample %d: %s\n", i+1, theme) + + freeze := freezelib.New(). + WithTheme(theme). + WithFont("Fira Code", 12). + WithWindow(false). // No window for cleaner comparison + WithLineNumbers(false). + WithPadding(15). + WithDimensions(600, 400) // Consistent size + + svgData, err := freeze.GenerateFromCode(code, "rust") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/comparison_grid_%02d_%s.svg", i+1, theme) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + fmt.Printf("✅ Generated: %s\n", filename) + } +} + +// Custom theme example +func customThemeExample() { + fmt.Println("\n🎨 Custom Theme Example") + fmt.Println("------------------------") + + code := `package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +type Response struct { + Message string 'json:"message"' + Data map[string]interface{} 'json:"data"' + Status int 'json:"status"' +} + +func apiHandler(w http.ResponseWriter, r *http.Request) { + response := Response{ + Message: "API is working!", + Data: map[string]interface{}{ + "timestamp": "2024-01-15T10:30:00Z", + "version": "1.0.0", + "healthy": true, + }, + Status: 200, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +func main() { + http.HandleFunc("/api/status", apiHandler) + fmt.Println("API server starting on :8080") + http.ListenAndServe(":8080", nil) +}` + + // Create custom-styled versions + customStyles := []struct { + name string + config func() *freezelib.Freeze + desc string + }{ + { + "corporate", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("Arial", 14). + WithBackground("#f8f9fa"). + WithWindow(true). + WithLineNumbers(true). + WithShadow(5, 2, 3). + WithBorder(1, 4, "#dee2e6"). + WithPadding(30) + }, + "Corporate style - clean and professional", + }, + { + "cyberpunk", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("dracula"). + WithFont("Courier New", 13). + WithBackground("#0d1117"). + WithWindow(true). + WithLineNumbers(true). + WithShadow(20, 0, 15). + WithBorder(2, 0, "#ff79c6"). + WithPadding(25) + }, + "Cyberpunk style - neon and futuristic", + }, + { + "minimal", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("system-ui", 13). + WithBackground("#ffffff"). + WithWindow(false). + WithLineNumbers(false). + WithShadow(0, 0, 0). + WithPadding(20) + }, + "Minimal style - clean and distraction-free", + }, + } + + for _, style := range customStyles { + fmt.Printf("✨ Creating %s style...\n", style.name) + + freeze := style.config() + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/custom_%s.svg", style.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + fmt.Printf("✅ Generated: %s - %s\n", filename, style.desc) + } +} + +// Helper function to sanitize filenames +func sanitizeFilename(name string) string { + // Replace spaces and special characters with underscores + result := "" + for _, char := range name { + if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || + (char >= '0' && char <= '9') { + result += string(char) + } else { + result += "_" + } + } + return result +} diff --git a/examples/04-languages/main.go b/examples/04-languages/main.go new file mode 100644 index 0000000..de5a774 --- /dev/null +++ b/examples/04-languages/main.go @@ -0,0 +1,896 @@ +package main + +import ( + "fmt" + "os" + + "github.com/landaiqing/freezelib" +) + +func main() { + fmt.Println("💻 Programming Languages Examples") + fmt.Println("=================================") + + // Create output directory + os.MkdirAll("output", 0755) + + // Run language examples + popularLanguagesExample() + languageComparisonExample() + multiLanguageProjectExample() + languageSpecificFeaturesExample() + + fmt.Println("\n✅ Language examples completed!") + fmt.Println("📁 Check the 'output' directory for generated files.") +} + +// Popular programming languages showcase +func popularLanguagesExample() { + fmt.Println("\n🌟 Popular Languages Showcase") + fmt.Println("-----------------------------") + + // Language examples with sample code + languages := []struct { + name string + code string + lang string + }{ + { + "go", + `package main + +import ( + "fmt" + "time" +) + +func main() { + fmt.Println("Hello from Go!") + + // Goroutine example + go func() { + fmt.Println("This runs concurrently") + }() + + time.Sleep(time.Millisecond * 100) +}`, + "go", + }, + { + "python", + `import asyncio +from dataclasses import dataclass + +@dataclass +class User: + name: str + age: int + email: str + +async def fetch_user(user_id: int) -> User: + # Simulate API call + await asyncio.sleep(1) + return User(name="John Doe", age=30, email="john@example.com") + +async def main(): + user = await fetch_user(123) + print(f"User: {user.name}, {user.age}, {user.email}") + +if __name__ == "__main__": + asyncio.run(main())`, + "python", + }, + { + "javascript", + `// Modern JavaScript example +const fetchData = async () => { + try { + const response = await fetch('https://api.example.com/data'); + const data = await response.json(); + + // Destructuring and spread operator + const { items, meta } = data; + const allItems = [...items, { id: 'new', value: 42 }]; + + // Array methods + const filtered = allItems.filter(item => item.value > 10); + + return filtered; + } catch (error) { + console.error('Error fetching data:', error); + return []; + } +}; + +// Call the function +fetchData().then(result => console.log(result));`, + "javascript", + }, + { + "rust", + `use std::collections::HashMap; + +#[derive(Debug)] +struct User { + name: String, + age: u32, + active: bool, +} + +fn main() { + // Create a new User + let user = User { + name: String::from("Alice"), + age: 28, + active: true, + }; + + // Using a HashMap + let mut scores = HashMap::new(); + scores.insert(String::from("Blue"), 10); + scores.insert(String::from("Yellow"), 50); + + // Pattern matching + match user.age { + 0..=17 => println!("{} is underage", user.name), + 18..=64 => println!("{} is an adult", user.name), + _ => println!("{} is a senior", user.name), + } +}`, + "rust", + }, + { + "java", + `import java.util.List; +import java.util.stream.Collectors; + +public class StreamExample { + public static void main(String[] args) { + // Create a list of names + List names = List.of("Alice", "Bob", "Charlie", "David"); + + // Use streams to filter and transform + List filteredNames = names.stream() + .filter(name -> name.length() > 4) + .map(String::toUpperCase) + .sorted() + .collect(Collectors.toList()); + + // Print the results + System.out.println("Original names: " + names); + System.out.println("Filtered names: " + filteredNames); + } +}`, + "java", + }, + { + "csharp", + `using System; +using System.Collections.Generic; +using System.Linq; + +namespace LinqExample +{ + class Program + { + static void Main(string[] args) + { + // Create a list of products + var products = new List + { + new Product { Id = 1, Name = "Laptop", Price = 1200.00m, Category = "Electronics" }, + new Product { Id = 2, Name = "Desk Chair", Price = 250.50m, Category = "Furniture" }, + new Product { Id = 3, Name = "Coffee Maker", Price = 89.99m, Category = "Kitchen" }, + new Product { Id = 4, Name = "Tablet", Price = 400.00m, Category = "Electronics" } + }; + + // Use LINQ to query products + var expensiveElectronics = products + .Where(p => p.Category == "Electronics" && p.Price > 500) + .OrderBy(p => p.Price) + .Select(p => new { p.Name, p.Price }); + + Console.WriteLine("Expensive Electronics:"); + foreach (var item in expensiveElectronics) + { + Console.WriteLine($"{item.Name}: ${item.Price}"); + } + } + } + + class Product + { + public int Id { get; set; } + public string Name { get; set; } + public decimal Price { get; set; } + public string Category { get; set; } + } +}`, + "csharp", + }, + } + + // Create a consistent style for all languages + freeze := freezelib.New(). + WithTheme("github-dark"). + WithFont("JetBrains Mono", 14). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 8). + WithPadding(20) + + for _, lang := range languages { + fmt.Printf("📝 Generating %s example...\n", lang.name) + + svgData, err := freeze.GenerateFromCode(lang.code, lang.lang) + if err != nil { + fmt.Printf("❌ Error with %s: %v\n", lang.name, err) + continue + } + + filename := fmt.Sprintf("output/language_%s.svg", lang.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving %s: %v\n", filename, err) + continue + } + + fmt.Printf("✅ Generated: %s\n", filename) + } +} + +// Language comparison with the same algorithm +func languageComparisonExample() { + fmt.Println("\n🔄 Same Algorithm in Different Languages") + fmt.Println("---------------------------------------") + + // Fibonacci implementation in different languages + fibImplementations := []struct { + name string + code string + lang string + }{ + { + "go", + `package main + +import "fmt" + +// Recursive Fibonacci implementation +func fibonacci(n int) int { + if n <= 1 { + return n + } + return fibonacci(n-1) + fibonacci(n-2) +} + +// Iterative Fibonacci implementation +func fibonacciIterative(n int) int { + if n <= 1 { + return n + } + + a, b := 0, 1 + for i := 2; i <= n; i++ { + a, b = b, a+b + } + return b +} + +func main() { + n := 10 + fmt.Printf("Fibonacci(%d) = %d (recursive)\n", n, fibonacci(n)) + fmt.Printf("Fibonacci(%d) = %d (iterative)\n", n, fibonacciIterative(n)) +}`, + "go", + }, + { + "python", + `def fibonacci_recursive(n): + """Calculate Fibonacci number recursively.""" + if n <= 1: + return n + return fibonacci_recursive(n-1) + fibonacci_recursive(n-2) + +def fibonacci_iterative(n): + """Calculate Fibonacci number iteratively.""" + if n <= 1: + return n + + a, b = 0, 1 + for i in range(2, n+1): + a, b = b, a + b + return b + +def fibonacci_dynamic(n): + """Calculate Fibonacci number using dynamic programming.""" + memo = {0: 0, 1: 1} + + def fib(n): + if n not in memo: + memo[n] = fib(n-1) + fib(n-2) + return memo[n] + + return fib(n) + +# Test the functions +n = 10 +print(f"Fibonacci({n}) = {fibonacci_recursive(n)} (recursive)") +print(f"Fibonacci({n}) = {fibonacci_iterative(n)} (iterative)") +print(f"Fibonacci({n}) = {fibonacci_dynamic(n)} (dynamic)")`, + "python", + }, + { + "javascript", + `// Recursive Fibonacci implementation +function fibonacciRecursive(n) { + if (n <= 1) return n; + return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2); +} + +// Iterative Fibonacci implementation +function fibonacciIterative(n) { + if (n <= 1) return n; + + let a = 0, b = 1; + for (let i = 2; i <= n; i++) { + const temp = a + b; + a = b; + b = temp; + } + return b; +} + +// Fibonacci with memoization +function fibonacciMemoized(n, memo = {}) { + if (n in memo) return memo[n]; + if (n <= 1) return n; + + memo[n] = fibonacciMemoized(n - 1, memo) + fibonacciMemoized(n - 2, memo); + return memo[n]; +} + +// Test the functions +const n = 10; +console.log(Fibonacci(${n}) = ${fibonacciRecursive(n)} (recursive)); +console.log(Fibonacci(${n}) = ${fibonacciIterative(n)} (iterative)); +console.log(Fibonacci(${n}) = ${fibonacciMemoized(n)} (memoized));`, + "javascript", + }, + { + "rust", + `fn fibonacci_recursive(n: u32) -> u32 { + match n { + 0 => 0, + 1 => 1, + _ => fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2), + } +} + +fn fibonacci_iterative(n: u32) -> u32 { + match n { + 0 => 0, + 1 => 1, + _ => { + let mut a = 0; + let mut b = 1; + + for _ in 2..=n { + let temp = a + b; + a = b; + b = temp; + } + + b + } + } +} + +fn main() { + let n = 10; + println!("Fibonacci({}) = {} (recursive)", n, fibonacci_recursive(n)); + println!("Fibonacci({}) = {} (iterative)", n, fibonacci_iterative(n)); +}`, + "rust", + }, + } + + // Create a consistent style for comparison + freeze := freezelib.New(). + WithTheme("dracula"). + WithFont("Fira Code", 14). + WithWindow(true). + WithLineNumbers(true). + WithPadding(20) + + for _, impl := range fibImplementations { + fmt.Printf("🧮 Generating Fibonacci in %s...\n", impl.name) + + svgData, err := freeze.GenerateFromCode(impl.code, impl.lang) + if err != nil { + fmt.Printf("❌ Error with %s: %v\n", impl.name, err) + continue + } + + filename := fmt.Sprintf("output/fibonacci_%s.svg", impl.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving %s: %v\n", filename, err) + continue + } + + fmt.Printf("✅ Generated: %s\n", filename) + } +} + +// Multi-language project example +func multiLanguageProjectExample() { + fmt.Println("\n🌐 Multi-language Project") + fmt.Println("-------------------------") + + // Different files in a web project + projectFiles := []struct { + name string + code string + lang string + filename string + }{ + { + "HTML", + ` + + + + + Task Manager + + + +
+

Task Manager

+ +
+ + +
+ +
    + +
    +

    Total tasks: 0

    +

    Completed: 0

    +
    +
    + + + +`, + "html", + "index.html", + }, + { + "CSS", + `/* Task Manager Styles */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + line-height: 1.6; + background-color: #f5f5f5; + color: #333; +} + +.container { + max-width: 800px; + margin: 2rem auto; + padding: 2rem; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +h1 { + text-align: center; + margin-bottom: 2rem; + color: #2c3e50; +} + +.task-form { + display: flex; + margin-bottom: 1.5rem; +} + +#taskInput { + flex: 1; + padding: 0.75rem; + border: 1px solid #ddd; + border-radius: 4px 0 0 4px; + font-size: 1rem; +} + +#addTask { + padding: 0.75rem 1.5rem; + background-color: #3498db; + color: white; + border: none; + border-radius: 0 4px 4px 0; + cursor: pointer; + font-size: 1rem; +} + +.task-list { + list-style: none; + margin-bottom: 1.5rem; +} + +.task-list li { + padding: 1rem; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; +} + +.task-list li.completed { + text-decoration: line-through; + color: #7f8c8d; +} + +.stats { + display: flex; + justify-content: space-between; + color: #7f8c8d; + font-size: 0.9rem; +}`, + "css", + "styles.css", + }, + { + "JavaScript", + `// Task Manager App +document.addEventListener('DOMContentLoaded', () => { + // DOM Elements + const taskInput = document.getElementById('taskInput'); + const addTaskBtn = document.getElementById('addTask'); + const taskList = document.getElementById('taskList'); + const totalTasksEl = document.getElementById('totalTasks'); + const completedTasksEl = document.getElementById('completedTasks'); + + // Task array + let tasks = JSON.parse(localStorage.getItem('tasks')) || []; + + // Initial render + renderTasks(); + updateStats(); + + // Event Listeners + addTaskBtn.addEventListener('click', addTask); + taskInput.addEventListener('keypress', e => { + if (e.key === 'Enter') addTask(); + }); + + // Add a new task + function addTask() { + const taskText = taskInput.value.trim(); + if (taskText === '') return; + + tasks.push({ + id: Date.now(), + text: taskText, + completed: false + }); + + saveToLocalStorage(); + renderTasks(); + updateStats(); + + taskInput.value = ''; + taskInput.focus(); + } + + // Toggle task completion + function toggleTask(id) { + tasks = tasks.map(task => + task.id === id ? { ...task, completed: !task.completed } : task + ); + + saveToLocalStorage(); + renderTasks(); + updateStats(); + } + + // Delete a task + function deleteTask(id) { + tasks = tasks.filter(task => task.id !== id); + + saveToLocalStorage(); + renderTasks(); + updateStats(); + } + + // Render tasks to DOM + function renderTasks() { + taskList.innerHTML = ''; + + tasks.forEach(task => { + const li = document.createElement('li'); + li.className = task.completed ? 'completed' : ''; + + li.innerHTML = + ${task.text} + + ; + + taskList.appendChild(li); + }); + } + + // Update statistics + function updateStats() { + totalTasksEl.textContent = tasks.length; + completedTasksEl.textContent = tasks.filter(task => task.completed).length; + } + + // Save to localStorage + function saveToLocalStorage() { + localStorage.setItem('tasks', JSON.stringify(tasks)); + } + + // Expose functions to global scope for inline event handlers + window.toggleTask = toggleTask; + window.deleteTask = deleteTask; +});`, + "javascript", + "app.js", + }, + } + + // Create a consistent style for the project files + freeze := freezelib.New(). + WithTheme("github"). + WithFont("Cascadia Code", 13). + WithWindow(true). + WithLineNumbers(true). + WithShadow(10, 0, 5). + WithPadding(20) + + for _, file := range projectFiles { + fmt.Printf("📄 Generating %s file (%s)...\n", file.name, file.filename) + + svgData, err := freeze.GenerateFromCode(file.code, file.lang) + if err != nil { + fmt.Printf("❌ Error with %s: %v\n", file.name, err) + continue + } + + filename := fmt.Sprintf("output/project_%s.svg", file.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving %s: %v\n", filename, err) + continue + } + + fmt.Printf("✅ Generated: %s\n", filename) + } +} + +// Language-specific features example +func languageSpecificFeaturesExample() { + fmt.Println("\n✨ Language-Specific Features") + fmt.Println("----------------------------") + + // Language-specific code features + features := []struct { + name string + code string + lang string + description string + }{ + { + "go_concurrency", + `package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + + for j := range jobs { + fmt.Printf("Worker %d started job %d\n", id, j) + time.Sleep(time.Second) // Simulate work + fmt.Printf("Worker %d finished job %d\n", id, j) + results <- j * 2 + } +} + +func main() { + jobs := make(chan int, 5) + results := make(chan int, 5) + + // Start workers + var wg sync.WaitGroup + for w := 1; w <= 3; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + // Send jobs + for j := 1; j <= 5; j++ { + jobs <- j + } + close(jobs) + + // Wait for workers to finish + wg.Wait() + close(results) + + // Collect results + for r := range results { + fmt.Println("Result:", r) + } +}`, + "go", + "Go Concurrency with Goroutines and Channels", + }, + { + "python_decorators", + `import time +import functools +from typing import Callable, TypeVar, Any + +T = TypeVar('T') + +def timer(func: Callable[..., T]) -> Callable[..., T]: + """Decorator that prints the execution time of a function.""" + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> T: + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + print(f"{func.__name__} executed in {end_time - start_time:.4f} seconds") + return result + return wrapper + +def memoize(func: Callable[..., T]) -> Callable[..., T]: + """Decorator that caches function results.""" + cache = {} + + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any) -> T: + key = str(args) + str(kwargs) + if key not in cache: + cache[key] = func(*args, **kwargs) + return cache[key] + return wrapper + +@timer +@memoize +def fibonacci(n: int) -> int: + """Calculate the nth Fibonacci number.""" + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +# Test the decorated function +print(fibonacci(30)) # First call will be timed +print(fibonacci(30)) # Second call will use cached result`, + "python", + "Python Decorators and Type Annotations", + }, + { + "typescript_generics", + `// TypeScript Generics and Interfaces +interface Repository { + getById(id: string): Promise; + getAll(): Promise; + create(item: T): Promise; + update(id: string, item: T): Promise; + delete(id: string): Promise; +} + +interface User { + id?: string; + name: string; + email: string; + role: 'admin' | 'user' | 'guest'; + createdAt?: Date; +} + +class UserRepository implements Repository { + private users: Map = new Map(); + + async getById(id: string): Promise { + const user = this.users.get(id); + if (!user) { + throw new Error(User with id ${id} not found); + } + return user; + } + + async getAll(): Promise { + return Array.from(this.users.values()); + } + + async create(user: User): Promise { + const id = Math.random().toString(36).substring(2, 9); + const newUser = { + ...user, + id, + createdAt: new Date() + }; + this.users.set(id, newUser); + return newUser; + } + + async update(id: string, user: User): Promise { + if (!this.users.has(id)) { + throw new Error('User with id ${id} not found'); + } + const updatedUser = { ...user, id }; + this.users.set(id, updatedUser); + return updatedUser; + } + + async delete(id: string): Promise { + return this.users.delete(id); + } +} + +// Usage example +async function main() { + const userRepo = new UserRepository(); + + const newUser = await userRepo.create({ + name: 'John Doe', + email: 'john@example.com', + role: 'admin' + }); + + console.log('Created user:', newUser); + + const allUsers = await userRepo.getAll(); + console.log('All users:', allUsers); +} + +main().catch(console.error);`, + "typescript", + "TypeScript Generics, Interfaces and Type Safety", + }, + } + + // Create a consistent style for the feature examples + freeze := freezelib.New(). + WithTheme("one-dark"). + WithFont("JetBrains Mono", 13). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 8). + WithPadding(20) + + for _, feature := range features { + fmt.Printf("🔍 Generating %s example...\n", feature.name) + + svgData, err := freeze.GenerateFromCode(feature.code, feature.lang) + if err != nil { + fmt.Printf("❌ Error with %s: %v\n", feature.name, err) + continue + } + + filename := fmt.Sprintf("output/feature_%s.svg", feature.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving %s: %v\n", filename, err) + continue + } + + fmt.Printf("✅ Generated: %s - %s\n", filename, feature.description) + } +} diff --git a/examples/05-terminal/main.go b/examples/05-terminal/main.go new file mode 100644 index 0000000..2f5543f --- /dev/null +++ b/examples/05-terminal/main.go @@ -0,0 +1,411 @@ +package main + +import ( + "fmt" + "os" + + "github.com/landaiqing/freezelib" +) + +func main() { + fmt.Println("💻 Terminal Output Examples") + fmt.Println("============================") + + // Create output directory + os.MkdirAll("output", 0755) + + // Run terminal examples + basicAnsiExample() + buildOutputExample() + testResultsExample() + dockerOutputExample() + gitOutputExample() + systemLogsExample() + + fmt.Println("\n✅ Terminal examples completed!") + fmt.Println("📁 Check the 'output' directory for generated files.") +} + +// Basic ANSI color example +func basicAnsiExample() { + fmt.Println("\n🌈 Basic ANSI Colors") + fmt.Println("--------------------") + + // Terminal preset optimized for ANSI output + freeze := freezelib.NewWithPreset("terminal") + + ansiOutput := `\033[32m✓ SUCCESS\033[0m: Application started successfully +\033[33m⚠ WARNING\033[0m: Configuration file not found, using defaults +\033[31m✗ ERROR\033[0m: Failed to connect to database +\033[36mINFO\033[0m: Server listening on port 8080 +\033[35mDEBUG\033[0m: Loading user preferences +\033[37mTRACE\033[0m: Function call: getUserById(123) + +\033[1mBold text\033[0m and \033[4munderlined text\033[0m +\033[7mReversed text\033[0m and \033[9mstrikethrough text\033[0m + +Background colors: +\033[41mRed background\033[0m +\033[42mGreen background\033[0m +\033[43mYellow background\033[0m +\033[44mBlue background\033[0m` + + svgData, err := freeze.GenerateFromANSI(ansiOutput) + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/basic_ansi.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/basic_ansi.svg") +} + +// Build output example +func buildOutputExample() { + fmt.Println("\n🔨 Build Output") + fmt.Println("---------------") + + freeze := freezelib.New(). + WithTheme("github-dark"). + WithFont("Cascadia Code", 13). + WithWindow(true). + WithPadding(20). + WithBackground("#0d1117") + + buildOutput := `$ go build -v ./... +github.com/myproject/internal/config +github.com/myproject/internal/database +github.com/myproject/internal/handlers +github.com/myproject/cmd/server + +\033[32m✓ Build completed successfully\033[0m + +$ go test -v ./... +=== RUN TestUserService_CreateUser +--- PASS: TestUserService_CreateUser (0.01s) +=== RUN TestUserService_GetUser +--- PASS: TestUserService_GetUser (0.00s) +=== RUN TestUserService_UpdateUser +--- PASS: TestUserService_UpdateUser (0.01s) +=== RUN TestUserService_DeleteUser +--- PASS: TestUserService_DeleteUser (0.00s) +=== RUN TestDatabaseConnection +--- PASS: TestDatabaseConnection (0.05s) + +\033[32mPASS\033[0m +\033[32mok \033[0m github.com/myproject 0.123s + +\033[36mCoverage: 85.7% of statements\033[0m + +$ docker build -t myapp:latest . +Sending build context to Docker daemon 2.048kB +Step 1/8 : FROM golang:1.21-alpine AS builder + ---> 7642119cd161 +Step 2/8 : WORKDIR /app + ---> Using cache + ---> 8f3b8c9d4e5f +Step 3/8 : COPY go.mod go.sum ./ + ---> Using cache + ---> 1a2b3c4d5e6f +Step 4/8 : RUN go mod download + ---> Using cache + ---> 2b3c4d5e6f7g +Step 5/8 : COPY . . + ---> 3c4d5e6f7g8h +Step 6/8 : RUN go build -o main . + ---> Running in 4d5e6f7g8h9i + ---> 5e6f7g8h9i0j +Step 7/8 : FROM alpine:latest + ---> 6f7g8h9i0j1k +Step 8/8 : COPY --from=builder /app/main /main + ---> 7g8h9i0j1k2l +\033[32mSuccessfully built 7g8h9i0j1k2l\033[0m +\033[32mSuccessfully tagged myapp:latest\033[0m` + + svgData, err := freeze.GenerateFromANSI(buildOutput) + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/build_output.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/build_output.svg") +} + +// Test results example +func testResultsExample() { + fmt.Println("\n🧪 Test Results") + fmt.Println("---------------") + + freeze := freezelib.New(). + WithTheme("dracula"). + WithFont("JetBrains Mono", 14). + WithWindow(true). + WithLineNumbers(false). + WithShadow(15, 0, 10). + WithPadding(25) + + testOutput := `$ npm test + +> myapp@1.0.0 test +> jest --coverage + + PASS src/components/Button.test.js + Button Component + \033[32m✓\033[0m renders correctly (15ms) + \033[32m✓\033[0m handles click events (8ms) + \033[32m✓\033[0m applies custom className (3ms) + + PASS src/services/api.test.js + API Service + \033[32m✓\033[0m fetches user data (45ms) + \033[32m✓\033[0m handles network errors (12ms) + \033[32m✓\033[0m retries failed requests (23ms) + + FAIL src/utils/validation.test.js + Validation Utils + \033[32m✓\033[0m validates email addresses (5ms) + \033[31m✗\033[0m validates phone numbers (8ms) + \033[32m✓\033[0m validates passwords (3ms) + + ● Validation Utils › validates phone numbers + + expect(received).toBe(expected) + + Expected: true + Received: false + + 12 | test('validates phone numbers', () => { + 13 | const phoneNumber = '+1-555-123-4567'; + > 14 | expect(isValidPhoneNumber(phoneNumber)).toBe(true); + | ^ + 15 | }); + + at Object. (src/utils/validation.test.js:14:45) + +\033[33mTest Suites: 1 failed, 2 passed, 3 total\033[0m +\033[33mTests: 1 failed, 7 passed, 8 total\033[0m +\033[33mSnapshots: 0 total\033[0m +\033[33mTime: 2.847s\033[0m + +\033[36m----------------------|---------|----------|---------|---------|-------------------\033[0m +\033[36mFile | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s\033[0m +\033[36m----------------------|---------|----------|---------|---------|-------------------\033[0m +\033[36mAll files | 87.5 | 75.0 | 90.0 | 87.5 |\033[0m +\033[36m src/components | 95.0 | 85.0 | 100.0 | 95.0 |\033[0m +\033[36m Button.js | 95.0 | 85.0 | 100.0 | 95.0 | 23\033[0m +\033[36m src/services | 80.0 | 65.0 | 80.0 | 80.0 |\033[0m +\033[36m api.js | 80.0 | 65.0 | 80.0 | 80.0 | 45,67\033[0m +\033[36m src/utils | 87.5 | 75.0 | 90.0 | 87.5 |\033[0m +\033[36m validation.js | 87.5 | 75.0 | 90.0 | 87.5 | 34\033[0m +\033[36m----------------------|---------|----------|---------|---------|-------------------\033[0m` + + svgData, err := freeze.GenerateFromANSI(testOutput) + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/test_results.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/test_results.svg") +} + +// Docker output example +func dockerOutputExample() { + fmt.Println("\n🐳 Docker Output") + fmt.Println("----------------") + + freeze := freezelib.New(). + WithTheme("nord"). + WithFont("SF Mono", 13). + WithWindow(true). + WithPadding(20). + WithBackground("#2e3440") + + dockerOutput := `$ docker-compose up -d +Creating network "myapp_default" with the default driver +Creating volume "myapp_postgres_data" with default driver +Creating volume "myapp_redis_data" with default driver + +\033[33mPulling postgres (postgres:13)...\033[0m +13: Pulling from library/postgres +\033[36m7b1a6ab2e44d\033[0m: Pull complete +\033[36m5c9d4e5f6a7b\033[0m: Pull complete +\033[36m8c1d2e3f4a5b\033[0m: Pull complete +\033[36m9d2e3f4a5b6c\033[0m: Pull complete +\033[32mDigest: sha256:abc123def456...\033[0m +\033[32mStatus: Downloaded newer image for postgres:13\033[0m + +\033[33mPulling redis (redis:6-alpine)...\033[0m +6-alpine: Pulling from library/redis +\033[36m4c0d5e6f7a8b\033[0m: Pull complete +\033[36m5d1e6f7a8b9c\033[0m: Pull complete +\033[32mDigest: sha256:def456ghi789...\033[0m +\033[32mStatus: Downloaded newer image for redis:6-alpine\033[0m + +Creating myapp_postgres_1 ... \033[32mdone\033[0m +Creating myapp_redis_1 ... \033[32mdone\033[0m +Creating myapp_web_1 ... \033[32mdone\033[0m + +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +\033[36m1a2b3c4d5e6f\033[0m myapp:latest "go run main.go" 2 minutes ago Up 2 minutes \033[35m0.0.0.0:8080->8080/tcp\033[0m myapp_web_1 +\033[36m2b3c4d5e6f7g\033[0m postgres:13 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes \033[35m5432/tcp\033[0m myapp_postgres_1 +\033[36m3c4d5e6f7g8h\033[0m redis:6-alpine "docker-entrypoint.s…" 2 minutes ago Up 2 minutes \033[35m6379/tcp\033[0m myapp_redis_1 + +$ docker logs myapp_web_1 +\033[36m2024/01/15 10:30:00\033[0m \033[32mINFO\033[0m Starting server... +\033[36m2024/01/15 10:30:00\033[0m \033[32mINFO\033[0m Connected to database +\033[36m2024/01/15 10:30:00\033[0m \033[32mINFO\033[0m Connected to Redis +\033[36m2024/01/15 10:30:00\033[0m \033[32mINFO\033[0m Server listening on :8080 +\033[36m2024/01/15 10:30:15\033[0m \033[36mDEBUG\033[0m GET /api/health - 200 OK (2ms) +\033[36m2024/01/15 10:30:20\033[0m \033[36mDEBUG\033[0m POST /api/users - 201 Created (45ms)` + + svgData, err := freeze.GenerateFromANSI(dockerOutput) + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/docker_output.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/docker_output.svg") +} + +// Git output example +func gitOutputExample() { + fmt.Println("\n📚 Git Output") + fmt.Println("-------------") + + freeze := freezelib.New(). + WithTheme("github"). + WithFont("Menlo", 13). + WithWindow(true). + WithPadding(20). + WithBackground("#ffffff") + + gitOutput := `$ git status +On branch feature/user-authentication +Your branch is ahead of 'origin/main' by 3 commits. + (use "git push" to publish your local commits) + +Changes to be committed: + (use "git restore --staged ..." to unstage) + \033[32mnew file: src/auth/login.js\033[0m + \033[32mnew file: src/auth/register.js\033[0m + \033[32mmodified: src/app.js\033[0m + \033[32mmodified: package.json\033[0m + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git restore ..." to discard changes in working directory) + \033[31mmodified: README.md\033[0m + \033[31mmodified: src/components/Header.js\033[0m + +Untracked files: + (use "git add ..." to include in what will be committed) + \033[31msrc/auth/middleware.js\033[0m + \033[31mtests/auth.test.js\033[0m + +$ git log --oneline -5 +\033[33m7a8b9c0\033[0m \033[32m(HEAD -> feature/user-authentication)\033[0m Add user registration functionality +\033[33m6a7b8c9\033[0m Add login form validation +\033[33m5a6b7c8\033[0m Implement JWT authentication +\033[33m4a5b6c7\033[0m \033[36m(origin/main, main)\033[0m Update project dependencies +\033[33m3a4b5c6\033[0m Fix responsive design issues + +$ git diff --stat + README.md | 15 \033[32m+++++++++\033[0m\033[31m------\033[0m + package.json | 3 \033[32m+++\033[0m + src/app.js | 42 \033[32m++++++++++++++++++++++++++++++\033[0m\033[31m----------\033[0m + src/auth/login.js | 67 \033[32m+++++++++++++++++++++++++++++++++++++++++++++++++++++++\033[0m + src/auth/register.js | 89 \033[32m++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\033[0m + src/components/Header.js | 8 \033[32m+++++\033[0m\033[31m---\033[0m + 6 files changed, 201 insertions(+), 23 deletions(-)` + + svgData, err := freeze.GenerateFromANSI(gitOutput) + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/git_output.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/git_output.svg") +} + +// System logs example +func systemLogsExample() { + fmt.Println("\n📋 System Logs") + fmt.Println("--------------") + + freeze := freezelib.New(). + WithTheme("monokai"). + WithFont("Ubuntu Mono", 13). + WithWindow(true). + WithPadding(20). + WithBackground("#272822") + + systemLogs := `$ tail -f /var/log/application.log + +\033[90m2024-01-15 10:30:00.123\033[0m [\033[32mINFO \033[0m] \033[36mApplication\033[0m - Server starting up +\033[90m2024-01-15 10:30:00.456\033[0m [\033[32mINFO \033[0m] \033[36mDatabase \033[0m - Connection pool initialized (size: 10) +\033[90m2024-01-15 10:30:00.789\033[0m [\033[32mINFO \033[0m] \033[36mCache \033[0m - Redis connection established +\033[90m2024-01-15 10:30:01.012\033[0m [\033[32mINFO \033[0m] \033[36mSecurity \033[0m - JWT secret loaded from environment +\033[90m2024-01-15 10:30:01.234\033[0m [\033[32mINFO \033[0m] \033[36mHTTP \033[0m - Server listening on port 8080 + +\033[90m2024-01-15 10:30:15.567\033[0m [\033[34mDEBUG\033[0m] \033[36mAuth \033[0m - User login attempt: user@example.com +\033[90m2024-01-15 10:30:15.678\033[0m [\033[32mINFO \033[0m] \033[36mAuth \033[0m - User authenticated successfully: user@example.com +\033[90m2024-01-15 10:30:15.789\033[0m [\033[34mDEBUG\033[0m] \033[36mHTTP \033[0m - POST /api/auth/login - 200 OK (223ms) + +\033[90m2024-01-15 10:30:30.123\033[0m [\033[33mWARN \033[0m] \033[36mDatabase \033[0m - Slow query detected (1.2s): SELECT * FROM users WHERE... +\033[90m2024-01-15 10:30:30.234\033[0m [\033[34mDEBUG\033[0m] \033[36mHTTP \033[0m - GET /api/users - 200 OK (1234ms) + +\033[90m2024-01-15 10:30:45.456\033[0m [\033[31mERROR\033[0m] \033[36mPayment \033[0m - Payment processing failed: insufficient funds +\033[90m2024-01-15 10:30:45.567\033[0m [\033[31mERROR\033[0m] \033[36mPayment \033[0m - Stack trace: + at PaymentService.processPayment (payment.js:45:12) + at OrderController.createOrder (order.js:23:8) + at Router.handle (express.js:123:5) +\033[90m2024-01-15 10:30:45.678\033[0m [\033[34mDEBUG\033[0m] \033[36mHTTP \033[0m - POST /api/orders - 400 Bad Request (112ms) + +\033[90m2024-01-15 10:31:00.789\033[0m [\033[32mINFO \033[0m] \033[36mScheduler\033[0m - Running daily cleanup task +\033[90m2024-01-15 10:31:05.012\033[0m [\033[32mINFO \033[0m] \033[36mScheduler\033[0m - Cleanup completed: removed 1,234 expired sessions +\033[90m2024-01-15 10:31:05.123\033[0m [\033[32mINFO \033[0m] \033[36mScheduler\033[0m - Next cleanup scheduled for 2024-01-16 10:31:00` + + svgData, err := freeze.GenerateFromANSI(systemLogs) + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + err = os.WriteFile("output/system_logs.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Println("✅ Generated: output/system_logs.svg") +} diff --git a/examples/06-advanced/main.go b/examples/06-advanced/main.go new file mode 100644 index 0000000..450d3e7 --- /dev/null +++ b/examples/06-advanced/main.go @@ -0,0 +1,587 @@ +package main + +import ( + "fmt" + "os" + + "github.com/landaiqing/freezelib" +) + +func main() { + fmt.Println("🔧 Advanced Configuration Examples") + fmt.Println("===================================") + + // Create output directory + os.MkdirAll("output", 0755) + + // Run advanced examples + customFontExample() + advancedLayoutExample() + performanceOptimizationExample() + responsiveDesignExample() + brandingExample() + + fmt.Println("\n✅ Advanced examples completed!") + fmt.Println("📁 Check the 'output' directory for generated files.") +} + +// Custom font example +func customFontExample() { + fmt.Println("\n🔤 Custom Font Examples") + fmt.Println("-----------------------") + + code := `interface UserRepository { + findById(id: string): Promise; + findByEmail(email: string): Promise; + create(user: CreateUserDto): Promise; + update(id: string, updates: UpdateUserDto): Promise; + delete(id: string): Promise; +} + +class PostgresUserRepository implements UserRepository { + constructor(private db: Database) {} + + async findById(id: string): Promise { + const result = await this.db.query( + 'SELECT * FROM users WHERE id = $1', + [id] + ); + return result.rows[0] || null; + } + + async create(user: CreateUserDto): Promise { + const { name, email, role } = user; + const result = await this.db.query( + 'INSERT INTO users (name, email, role) VALUES ($1, $2, $3) RETURNING *', + [name, email, role] + ); + return result.rows[0]; + } +}` + + // Different font configurations + fontConfigs := []struct { + name string + family string + size float64 + desc string + }{ + {"monospace_small", "Courier New", 12, "Small monospace font"}, + {"monospace_large", "JetBrains Mono", 16, "Large modern monospace"}, + {"system_font", "system-ui", 14, "System default font"}, + {"serif_font", "Georgia", 14, "Serif font for readability"}, + {"condensed_font", "SF Mono", 13, "Condensed font for more content"}, + } + + for _, config := range fontConfigs { + fmt.Printf("🔤 Generating %s example...\n", config.name) + + freeze := freezelib.New(). + WithTheme("github-dark"). + WithFont(config.family, config.size). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 8). + WithPadding(25) + + svgData, err := freeze.GenerateFromCode(code, "typescript") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/font_%s.svg", config.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + fmt.Printf("✅ Generated: %s - %s\n", filename, config.desc) + } +} + +// Advanced layout example +func advancedLayoutExample() { + fmt.Println("\n📐 Advanced Layout Examples") + fmt.Println("---------------------------") + + code := `from dataclasses import dataclass +from typing import List, Optional, Dict, Any +from datetime import datetime +import asyncio +import aiohttp + +@dataclass +class APIResponse: + status_code: int + data: Dict[str, Any] + headers: Dict[str, str] + timestamp: datetime + +class AsyncAPIClient: + def __init__(self, base_url: str, timeout: int = 30): + self.base_url = base_url.rstrip('/') + self.timeout = aiohttp.ClientTimeout(total=timeout) + self.session: Optional[aiohttp.ClientSession] = None + + async def __aenter__(self): + self.session = aiohttp.ClientSession(timeout=self.timeout) + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self.session: + await self.session.close() + + async def get(self, endpoint: str, params: Optional[Dict] = None) -> APIResponse: + if not self.session: + raise RuntimeError("Client not initialized. Use async context manager.") + + url = f"{self.base_url}/{endpoint.lstrip('/')}" + + async with self.session.get(url, params=params) as response: + data = await response.json() + return APIResponse( + status_code=response.status, + data=data, + headers=dict(response.headers), + timestamp=datetime.now() + ) + +# Usage example +async def main(): + async with AsyncAPIClient("https://api.example.com") as client: + response = await client.get("/users", {"page": 1, "limit": 10}) + print(f"Status: {response.status_code}") + print(f"Data: {response.data}") + +if __name__ == "__main__": + asyncio.run(main())` + + // Different layout configurations + layouts := []struct { + name string + config func() *freezelib.Freeze + desc string + }{ + { + "compact", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("SF Mono", 11). + WithPadding(10). + WithMargin(5). + WithWindow(false). + WithLineNumbers(true). + WithDimensions(600, 800) + }, + "Compact layout for maximum content", + }, + { + "spacious", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github-dark"). + WithFont("JetBrains Mono", 16). + WithPadding(40). + WithMargin(30). + WithWindow(true). + WithLineNumbers(true). + WithShadow(25, 0, 15). + WithDimensions(1000, 1200) + }, + "Spacious layout for presentations", + }, + { + "mobile_friendly", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("dracula"). + WithFont("Menlo", 13). + WithPadding(15). + WithMargin(10). + WithWindow(false). + WithLineNumbers(false). + WithDimensions(400, 600) + }, + "Mobile-friendly narrow layout", + }, + { + "print_optimized", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("Times New Roman", 12). + WithPadding(20). + WithMargin(15). + WithWindow(false). + WithLineNumbers(true). + WithShadow(0, 0, 0). // No shadow for print + WithBackground("#ffffff"). + WithDimensions(800, 1000) + }, + "Print-optimized layout", + }, + } + + for _, layout := range layouts { + fmt.Printf("📐 Creating %s layout...\n", layout.name) + + freeze := layout.config() + svgData, err := freeze.GenerateFromCode(code, "python") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/layout_%s.svg", layout.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + fmt.Printf("✅ Generated: %s - %s\n", filename, layout.desc) + } +} + +// Performance optimization example +func performanceOptimizationExample() { + fmt.Println("\n⚡ Performance Optimization") + fmt.Println("---------------------------") + + // Short code for performance testing + shortCode := `fn quicksort(arr: &mut [T]) { + if arr.len() <= 1 { + return; + } + + let pivot_index = partition(arr); + let (left, right) = arr.split_at_mut(pivot_index); + + quicksort(left); + quicksort(&mut right[1..]); +} + +fn partition(arr: &mut [T]) -> usize { + let pivot_index = arr.len() - 1; + let mut i = 0; + + for j in 0..pivot_index { + if arr[j] <= arr[pivot_index] { + arr.swap(i, j); + i += 1; + } + } + + arr.swap(i, pivot_index); + i +}` + + // Performance-optimized configurations + perfConfigs := []struct { + name string + config func() *freezelib.Freeze + desc string + }{ + { + "minimal_overhead", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("monospace", 12). + WithWindow(false). + WithLineNumbers(false). + WithShadow(0, 0, 0). + WithPadding(10). + WithMargin(0) + }, + "Minimal processing overhead", + }, + { + "optimized_svg", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("system-ui", 13). + WithWindow(false). + WithLineNumbers(true). + WithShadow(0, 0, 0). + WithPadding(15). + WithDimensions(600, 400) // Fixed dimensions + }, + "SVG-optimized configuration", + }, + { + "batch_processing", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("monokai"). + WithFont("Courier", 12). + WithWindow(false). + WithLineNumbers(false). + WithPadding(12). + WithDimensions(500, 300) + }, + "Optimized for batch processing", + }, + } + + for _, config := range perfConfigs { + fmt.Printf("⚡ Testing %s...\n", config.name) + + freeze := config.config() + + // Generate multiple times to test performance + for i := 0; i < 3; i++ { + svgData, err := freeze.GenerateFromCode(shortCode, "rust") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + break + } + + filename := fmt.Sprintf("output/perf_%s_%d.svg", config.name, i+1) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + break + } + } + + fmt.Printf("✅ Generated 3 files for %s - %s\n", config.name, config.desc) + } +} + +// Responsive design example +func responsiveDesignExample() { + fmt.Println("\n📱 Responsive Design") + fmt.Println("--------------------") + + code := `@media (max-width: 768px) { + .container { + padding: 1rem; + margin: 0; + } + + .grid { + grid-template-columns: 1fr; + gap: 1rem; + } + + .card { + margin-bottom: 1rem; + } + + .navigation { + flex-direction: column; + } + + .nav-item { + width: 100%; + text-align: center; + padding: 0.75rem; + } +} + +@media (min-width: 769px) and (max-width: 1024px) { + .container { + max-width: 750px; + padding: 2rem; + } + + .grid { + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + } +} + +@media (min-width: 1025px) { + .container { + max-width: 1200px; + padding: 3rem; + } + + .grid { + grid-template-columns: repeat(3, 1fr); + gap: 2rem; + } + + .hero { + height: 60vh; + display: flex; + align-items: center; + justify-content: center; + } +}` + + // Different screen size simulations + screenSizes := []struct { + name string + width float64 + height float64 + desc string + }{ + {"mobile", 375, 600, "Mobile phone size"}, + {"tablet", 768, 800, "Tablet size"}, + {"desktop", 1200, 800, "Desktop size"}, + {"ultrawide", 1600, 900, "Ultrawide monitor"}, + } + + for _, size := range screenSizes { + fmt.Printf("📱 Creating %s responsive example...\n", size.name) + + freeze := freezelib.New(). + WithTheme("github"). + WithFont("system-ui", 13). + WithDimensions(size.width, size.height). + WithWindow(true). + WithLineNumbers(true). + WithPadding(20). + WithShadow(10, 0, 5) + + svgData, err := freeze.GenerateFromCode(code, "css") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/responsive_%s.svg", size.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + fmt.Printf("✅ Generated: %s (%dx%d) - %s\n", + filename, size.width, size.height, size.desc) + } +} + +// Branding example +func brandingExample() { + fmt.Println("\n🎨 Branding Examples") + fmt.Println("--------------------") + + code := `public class BrandService { + private final Logger logger = LoggerFactory.getLogger(BrandService.class); + private final BrandRepository brandRepository; + private final CacheManager cacheManager; + + public BrandService(BrandRepository brandRepository, CacheManager cacheManager) { + this.brandRepository = brandRepository; + this.cacheManager = cacheManager; + } + + @Cacheable("brands") + public Brand getBrandById(Long id) { + logger.info("Fetching brand with id: {}", id); + + return brandRepository.findById(id) + .orElseThrow(() -> new BrandNotFoundException("Brand not found: " + id)); + } + + @Transactional + public Brand createBrand(CreateBrandRequest request) { + validateBrandRequest(request); + + Brand brand = Brand.builder() + .name(request.getName()) + .description(request.getDescription()) + .logoUrl(request.getLogoUrl()) + .primaryColor(request.getPrimaryColor()) + .secondaryColor(request.getSecondaryColor()) + .createdAt(Instant.now()) + .build(); + + Brand savedBrand = brandRepository.save(brand); + cacheManager.evictCache("brands"); + + logger.info("Created new brand: {}", savedBrand.getName()); + return savedBrand; + } + + private void validateBrandRequest(CreateBrandRequest request) { + if (StringUtils.isBlank(request.getName())) { + throw new ValidationException("Brand name is required"); + } + + if (brandRepository.existsByName(request.getName())) { + throw new ValidationException("Brand name already exists"); + } + } +}` + + // Different brand styles + brandStyles := []struct { + name string + config func() *freezelib.Freeze + desc string + }{ + { + "corporate_blue", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github"). + WithFont("Arial", 14). + WithBackground("#f8f9fa"). + WithWindow(true). + WithLineNumbers(true). + WithShadow(8, 2, 4). + WithBorder(2, 8, "#0066cc"). + WithPadding(30) + }, + "Corporate blue branding", + }, + { + "startup_green", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("github-dark"). + WithFont("Inter", 14). + WithBackground("#0d1117"). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 10). + WithBorder(1, 12, "#00d084"). + WithPadding(25) + }, + "Startup green branding", + }, + { + "creative_purple", + func() *freezelib.Freeze { + return freezelib.New(). + WithTheme("dracula"). + WithFont("Poppins", 14). + WithBackground("#1a1a2e"). + WithWindow(true). + WithLineNumbers(true). + WithShadow(20, 0, 15). + WithBorder(2, 16, "#8b5cf6"). + WithPadding(35) + }, + "Creative purple branding", + }, + } + + for _, style := range brandStyles { + fmt.Printf("🎨 Creating %s style...\n", style.name) + + freeze := style.config() + svgData, err := freeze.GenerateFromCode(code, "java") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + filename := fmt.Sprintf("output/brand_%s.svg", style.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + fmt.Printf("✅ Generated: %s - %s\n", filename, style.desc) + } +} diff --git a/examples/07-batch/main.go b/examples/07-batch/main.go new file mode 100644 index 0000000..c4440d2 --- /dev/null +++ b/examples/07-batch/main.go @@ -0,0 +1,680 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/landaiqing/freezelib" +) + +func main() { + fmt.Println("📦 Batch Processing Examples") + fmt.Println("=============================") + + // Create output directory + os.MkdirAll("output", 0755) + os.MkdirAll("sample_files", 0755) + + // Run batch examples + createSampleFiles() + batchFileProcessingExample() + multiFormatBatchExample() + concurrentProcessingExample() + directoryProcessingExample() + + fmt.Println("\n✅ Batch processing examples completed!") + fmt.Println("📁 Check the 'output' directory for generated files.") +} + +// Create sample files for batch processing +func createSampleFiles() { + fmt.Println("\n📝 Creating Sample Files") + fmt.Println("------------------------") + + sampleFiles := map[string]string{ + "sample_files/main.go": `package main + +import ( + "fmt" + "net/http" + "log" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, World!") + }) + + fmt.Println("Server starting on :8080") + log.Fatal(http.ListenAndServe(":8080", nil)) +}`, + + "sample_files/utils.py": `import json +import logging +from typing import Dict, List, Any, Optional +from datetime import datetime + +logger = logging.getLogger(__name__) + +def load_config(config_path: str) -> Dict[str, Any]: + """Load configuration from JSON file.""" + try: + with open(config_path, 'r') as f: + return json.load(f) + except FileNotFoundError: + logger.error(f"Config file not found: {config_path}") + return {} + except json.JSONDecodeError as e: + logger.error(f"Invalid JSON in config file: {e}") + return {} + +def format_timestamp(timestamp: Optional[datetime] = None) -> str: + """Format timestamp to ISO string.""" + if timestamp is None: + timestamp = datetime.now() + return timestamp.isoformat() + +class DataProcessor: + def __init__(self, config: Dict[str, Any]): + self.config = config + self.processed_count = 0 + + def process_batch(self, items: List[Any]) -> List[Any]: + """Process a batch of items.""" + results = [] + for item in items: + processed = self.process_item(item) + if processed: + results.append(processed) + self.processed_count += 1 + return results + + def process_item(self, item: Any) -> Optional[Any]: + """Process a single item.""" + # Implementation depends on item type + return item`, + + "sample_files/api.js": `const express = require('express'); +const cors = require('cors'); +const helmet = require('helmet'); +const rateLimit = require('express-rate-limit'); + +const app = express(); +const PORT = process.env.PORT || 3000; + +// Middleware +app.use(helmet()); +app.use(cors()); +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true })); + +// Rate limiting +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // limit each IP to 100 requests per windowMs + message: 'Too many requests from this IP' +}); +app.use('/api/', limiter); + +// Routes +app.get('/api/health', (req, res) => { + res.json({ + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime() + }); +}); + +app.get('/api/users', async (req, res) => { + try { + const { page = 1, limit = 10 } = req.query; + const users = await getUsersPaginated(page, limit); + + res.json({ + data: users, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + total: await getTotalUsers() + } + }); + } catch (error) { + console.error('Error fetching users:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.listen(PORT, () => { + console.log('Server running on port \${PORT}\'); +});`, + + "sample_files/styles.css": `/* Modern CSS Reset and Base Styles */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +:root { + --primary-color: #3b82f6; + --secondary-color: #64748b; + --success-color: #10b981; + --warning-color: #f59e0b; + --error-color: #ef4444; + --background-color: #ffffff; + --surface-color: #f8fafc; + --text-primary: #1e293b; + --text-secondary: #64748b; + --border-color: #e2e8f0; + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + line-height: 1.6; + color: var(--text-primary); + background-color: var(--background-color); +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 1rem; +} + +.card { + background: var(--surface-color); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1.5rem; + box-shadow: var(--shadow); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15); +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 6px; + font-weight: 500; + text-decoration: none; + cursor: pointer; + transition: all 0.2s ease; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-primary:hover { + background-color: #2563eb; + transform: translateY(-1px); +}`, + + "sample_files/config.json": `{ + "database": { + "host": "localhost", + "port": 5432, + "name": "myapp", + "user": "postgres", + "password": "password", + "ssl": false, + "pool": { + "min": 2, + "max": 10, + "idle_timeout": "30s" + } + }, + "redis": { + "host": "localhost", + "port": 6379, + "password": "", + "db": 0, + "pool_size": 10 + }, + "server": { + "host": "0.0.0.0", + "port": 8080, + "read_timeout": "30s", + "write_timeout": "30s", + "idle_timeout": "60s" + }, + "logging": { + "level": "info", + "format": "json", + "output": "stdout" + }, + "features": { + "enable_metrics": true, + "enable_tracing": true, + "enable_profiling": false + } +}`, + } + + for filename, content := range sampleFiles { + err := os.WriteFile(filename, []byte(content), 0644) + if err != nil { + fmt.Printf("❌ Error creating %s: %v\n", filename, err) + continue + } + fmt.Printf("✅ Created: %s\n", filename) + } +} + +// Batch file processing example +func batchFileProcessingExample() { + fmt.Println("\n📦 Batch File Processing") + fmt.Println("------------------------") + + // Get all sample files + files, err := filepath.Glob("sample_files/*") + if err != nil { + fmt.Printf("❌ Error finding files: %v\n", err) + return + } + + // Create a consistent freeze instance for all files + freeze := freezelib.New(). + WithTheme("github-dark"). + WithFont("JetBrains Mono", 14). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 8). + WithPadding(20) + + fmt.Printf("🔄 Processing %d files...\n", len(files)) + + successCount := 0 + for _, file := range files { + fmt.Printf("📄 Processing: %s\n", file) + + // Detect language from file extension + ext := filepath.Ext(file) + lang := detectLanguage(ext) + + svgData, err := freeze.GenerateFromFile(file) + if err != nil { + fmt.Printf("❌ Error processing %s: %v\n", file, err) + continue + } + + // Create output filename + baseName := strings.TrimSuffix(filepath.Base(file), ext) + outputFile := fmt.Sprintf("output/batch_%s.svg", baseName) + + err = os.WriteFile(outputFile, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving %s: %v\n", outputFile, err) + continue + } + + fmt.Printf("✅ Generated: %s (language: %s)\n", outputFile, lang) + successCount++ + } + + fmt.Printf("📊 Batch processing completed: %d/%d files successful\n", + successCount, len(files)) +} + +// Multi-format batch example +func multiFormatBatchExample() { + fmt.Println("\n🎨 Multi-format Batch Processing") + fmt.Println("--------------------------------") + + code := `#include +#include +#include +#include + +template +class SmartVector { +private: + std::unique_ptr data; + size_t size_; + size_t capacity_; + +public: + SmartVector(size_t initial_capacity = 10) + : data(std::make_unique(initial_capacity)) + , size_(0) + , capacity_(initial_capacity) {} + + void push_back(const T& value) { + if (size_ >= capacity_) { + resize(); + } + data[size_++] = value; + } + + T& operator[](size_t index) { + if (index >= size_) { + throw std::out_of_range("Index out of range"); + } + return data[index]; + } + + size_t size() const { return size_; } + + void sort() { + std::sort(data.get(), data.get() + size_); + } + +private: + void resize() { + capacity_ *= 2; + auto new_data = std::make_unique(capacity_); + std::copy(data.get(), data.get() + size_, new_data.get()); + data = std::move(new_data); + } +}; + +int main() { + SmartVector vec; + + for (int i = 0; i < 15; ++i) { + vec.push_back(rand() % 100); + } + + vec.sort(); + + std::cout << "Sorted vector: "; + for (size_t i = 0; i < vec.size(); ++i) { + std::cout << vec[i] << " "; + } + std::cout << std::endl; + + return 0; +}` + + // Different format configurations + formats := []struct { + name string + format string + theme string + }{ + {"svg_light", "svg", "github"}, + {"svg_dark", "svg", "github-dark"}, + {"png_presentation", "png", "dracula"}, + {"png_print", "png", "github"}, + } + + freeze := freezelib.New(). + WithFont("Cascadia Code", 14). + WithWindow(true). + WithLineNumbers(true). + WithShadow(15, 0, 8). + WithPadding(25) + + for _, format := range formats { + fmt.Printf("🎨 Generating %s format...\n", format.name) + + freeze.WithTheme(format.theme) + + var data []byte + var err error + var filename string + + if format.format == "svg" { + data, err = freeze.GenerateFromCode(code, "cpp") + filename = fmt.Sprintf("output/multiformat_%s.svg", format.name) + } else { + data, err = freeze.GeneratePNGFromCode(code, "cpp") + filename = fmt.Sprintf("output/multiformat_%s.png", format.name) + } + + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + continue + } + + err = os.WriteFile(filename, data, 0644) + if err != nil { + fmt.Printf("❌ Error saving: %v\n", err) + continue + } + + // Show file size + info, _ := os.Stat(filename) + fmt.Printf("✅ Generated: %s (%d bytes)\n", filename, info.Size()) + } +} + +// Concurrent processing example +func concurrentProcessingExample() { + fmt.Println("\n⚡ Concurrent Processing") + fmt.Println("-----------------------") + + // Sample code snippets for concurrent processing + codeSnippets := []struct { + name string + code string + lang string + }{ + { + "snippet1", + `def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +print([fibonacci(i) for i in range(10)])`, + "python", + }, + { + "snippet2", + `function quickSort(arr) { + if (arr.length <= 1) return arr; + + const pivot = arr[Math.floor(arr.length / 2)]; + const left = arr.filter(x => x < pivot); + const middle = arr.filter(x => x === pivot); + const right = arr.filter(x => x > pivot); + + return [...quickSort(left), ...middle, ...quickSort(right)]; +}`, + "javascript", + }, + { + "snippet3", + `public class BinarySearch { + public static int search(int[] arr, int target) { + int left = 0, right = arr.length - 1; + + while (left <= right) { + int mid = left + (right - left) / 2; + + if (arr[mid] == target) return mid; + if (arr[mid] < target) left = mid + 1; + else right = mid - 1; + } + + return -1; + } +}`, + "java", + }, + { + "snippet4", + `use std::collections::HashMap; + +fn word_count(text: &str) -> HashMap { + text.split_whitespace() + .map(|word| word.to_lowercase()) + .fold(HashMap::new(), |mut acc, word| { + *acc.entry(word).or_insert(0) += 1; + acc + }) +}`, + "rust", + }, + } + + // Create freeze instance + freeze := freezelib.New(). + WithTheme("nord"). + WithFont("JetBrains Mono", 13). + WithWindow(true). + WithLineNumbers(true). + WithShadow(10, 0, 5). + WithPadding(20) + + // Use goroutines for concurrent processing + var wg sync.WaitGroup + results := make(chan string, len(codeSnippets)) + + fmt.Printf("🚀 Processing %d snippets concurrently...\n", len(codeSnippets)) + + for _, snippet := range codeSnippets { + wg.Add(1) + go func(s struct { + name string + code string + lang string + }) { + defer wg.Done() + + svgData, err := freeze.GenerateFromCode(s.code, s.lang) + if err != nil { + results <- fmt.Sprintf("❌ Error processing %s: %v", s.name, err) + return + } + + filename := fmt.Sprintf("output/concurrent_%s.svg", s.name) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + results <- fmt.Sprintf("❌ Error saving %s: %v", filename, err) + return + } + + results <- fmt.Sprintf("✅ Generated: %s", filename) + }(snippet) + } + + // Wait for all goroutines to complete + go func() { + wg.Wait() + close(results) + }() + + // Collect results + for result := range results { + fmt.Println(result) + } +} + +// Directory processing example +func directoryProcessingExample() { + fmt.Println("\n📁 Directory Processing") + fmt.Println("-----------------------") + + // Process all files in sample_files directory + err := filepath.Walk("sample_files", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip directories + if info.IsDir() { + return nil + } + + // Only process certain file types + ext := filepath.Ext(path) + if !isSupportedFile(ext) { + return nil + } + + fmt.Printf("📄 Processing directory file: %s\n", path) + + // Create themed freeze instance based on file type + theme := getThemeForFile(ext) + freeze := freezelib.New(). + WithTheme(theme). + WithFont("SF Mono", 13). + WithWindow(true). + WithLineNumbers(true). + WithPadding(20) + + svgData, err := freeze.GenerateFromFile(path) + if err != nil { + fmt.Printf("❌ Error processing %s: %v\n", path, err) + return nil + } + + // Create output filename + baseName := strings.TrimSuffix(filepath.Base(path), ext) + outputFile := fmt.Sprintf("output/directory_%s.svg", baseName) + + err = os.WriteFile(outputFile, svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving %s: %v\n", outputFile, err) + return nil + } + + fmt.Printf("✅ Generated: %s (theme: %s)\n", outputFile, theme) + return nil + }) + + if err != nil { + fmt.Printf("❌ Error walking directory: %v\n", err) + } +} + +// Helper functions +func detectLanguage(ext string) string { + switch ext { + case ".go": + return "go" + case ".py": + return "python" + case ".js": + return "javascript" + case ".css": + return "css" + case ".json": + return "json" + default: + return "text" + } +} + +func isSupportedFile(ext string) bool { + supported := []string{".go", ".py", ".js", ".css", ".json", ".md", ".txt"} + for _, s := range supported { + if ext == s { + return true + } + } + return false +} + +func getThemeForFile(ext string) string { + switch ext { + case ".go": + return "github-dark" + case ".py": + return "monokai" + case ".js": + return "dracula" + case ".css": + return "github" + case ".json": + return "nord" + default: + return "github" + } +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..580268a --- /dev/null +++ b/examples/README.md @@ -0,0 +1,113 @@ +# FreezeLib Examples + +**Language / 语言**: [English](README.md) | [中文](README_CN.md) + +This directory contains comprehensive examples demonstrating various features of FreezeLib. + +## 📁 Example Categories + +### [01-basic/](01-basic/) - Basic Usage +- Simple code screenshot generation +- Basic configuration +- Getting started examples + +### [02-formats/](02-formats/) - Output Formats +- SVG output examples +- PNG output examples +- Format comparison +- Quality settings + +### [03-themes/](03-themes/) - Theme Showcase +- Popular themes demonstration +- Theme comparison +- Custom theme creation + +### [04-languages/](04-languages/) - Programming Languages +- Syntax highlighting for different languages +- Language-specific optimizations +- Multi-language projects + +### [05-terminal/](05-terminal/) - Terminal Output +- ANSI color support +- Terminal styling +- Command output screenshots + +### [06-advanced/](06-advanced/) - Advanced Configuration +- Complex styling options +- Performance optimization +- Custom fonts and layouts + +### [07-batch/](07-batch/) - Batch Processing +- Multiple file processing +- Automated workflows +- Bulk operations + +## 🚀 Quick Start + +To run all examples: + +```bash +cd examples +go run run_all_examples.go +``` + +To run specific category: + +```bash +cd examples/01-basic +go run main.go +``` + +## 📊 Output Formats + +Each example category demonstrates: +- **SVG**: Vector format, perfect for web and documentation +- **PNG**: Raster format, ideal for presentations and social media +- **Quality comparisons**: Different settings and their effects + +## 🎨 Visual Features Demonstrated + +- **Syntax Highlighting**: 100+ programming languages +- **Themes**: Light, dark, and custom themes +- **Window Decorations**: macOS-style window controls +- **Line Numbers**: Optional line numbering +- **Shadows and Borders**: Visual enhancement effects +- **Custom Fonts**: Typography options +- **ANSI Colors**: Terminal output rendering +- **Responsive Sizing**: Adaptive dimensions + +## 📝 Code Examples Include + +- **Web Development**: HTML, CSS, JavaScript, TypeScript +- **Backend**: Go, Python, Java, C#, Rust +- **Mobile**: Swift, Kotlin, Dart +- **DevOps**: Docker, YAML, Shell scripts +- **Data**: SQL, JSON, CSV processing +- **Documentation**: Markdown, configuration files + +## 🔧 Configuration Examples + +Each category includes examples of: +- Basic configuration +- Advanced customization +- Performance optimization +- Error handling +- Best practices + +## 📖 Learning Path + +1. **Start with [01-basic/](01-basic/)** - Learn fundamental concepts +2. **Explore [02-formats/](02-formats/)** - Understand output options +3. **Try [03-themes/](03-themes/)** - Discover visual styles +4. **Check [04-languages/](04-languages/)** - See language support +5. **Advanced topics** - Dive into specialized use cases + +## 🤝 Contributing Examples + +To add new examples: + +1. Choose appropriate category or create new one +2. Follow the naming convention: `example_name.go` +3. Include both code and generated output +4. Add documentation in README.md +5. Test with `go run main.go` \ No newline at end of file diff --git a/examples/README_CN.md b/examples/README_CN.md new file mode 100644 index 0000000..6cc1cf8 --- /dev/null +++ b/examples/README_CN.md @@ -0,0 +1,114 @@ +# FreezeLib 示例集合 + +**Language / 语言**: [English](README.md) | [中文](README_CN.md) + +本目录包含了展示 FreezeLib 各种功能的综合示例。 + +## 📁 示例分类 + +### [01-basic/](01-basic/) - 基础用法 +- 简单代码截图生成 +- 基本配置 +- 入门示例 + +### [02-formats/](02-formats/) - 输出格式 +- SVG 输出示例 +- PNG 输出示例 +- 格式对比 +- 质量设置 + +### [03-themes/](03-themes/) - 主题展示 +- 流行主题演示 +- 主题对比 +- 自定义主题创建 + +### [04-languages/](04-languages/) - 编程语言 +- 不同语言的语法高亮 +- 语言特定优化 +- 多语言项目 + +### [05-terminal/](05-terminal/) - 终端输出 +- ANSI 颜色支持 +- 终端样式 +- 命令输出截图 + +### [06-advanced/](06-advanced/) - 高级配置 +- 复杂样式选项 +- 性能优化 +- 自定义字体和布局 + +### [07-batch/](07-batch/) - 批量处理 +- 多文件处理 +- 自动化工作流 +- 批量操作 + +## 🚀 快速开始 + +运行所有示例: + +```bash +cd examples +go run run_all_examples.go +``` + +运行特定分类: + +```bash +cd examples/01-basic +go run main.go +``` + +## 📊 输出格式 + +每个示例分类都演示: +- **SVG**: 矢量格式,完美适用于网页和文档 +- **PNG**: 栅格格式,适合演示和社交媒体 +- **质量对比**: 不同设置及其效果 + +## 🎨 展示的视觉功能 + +- **语法高亮**: 100+ 种编程语言 +- **主题**: 浅色、深色和自定义主题 +- **窗口装饰**: macOS 风格窗口控件 +- **行号**: 可选行号显示 +- **阴影和边框**: 视觉增强效果 +- **自定义字体**: 排版选项 +- **ANSI 颜色**: 终端输出渲染 +- **响应式尺寸**: 自适应尺寸 + +## 📝 代码示例包括 + +- **Web 开发**: HTML, CSS, JavaScript, TypeScript +- **后端**: Go, Python, Java, C#, Rust +- **移动端**: Swift, Kotlin, Dart +- **DevOps**: Docker, YAML, Shell 脚本 +- **数据**: SQL, JSON, CSV 处理 +- **文档**: Markdown, 配置文件 + +## 🔧 配置示例 + +每个分类都包含以下示例: +- 基本配置 +- 高级自定义 +- 性能优化 +- 错误处理 +- 最佳实践 + +## 📖 学习路径 + +1. **从 [01-basic/](01-basic/) 开始** - 学习基本概念 +2. **探索 [02-formats/](02-formats/)** - 了解输出选项 +3. **尝试 [03-themes/](03-themes/)** - 发现视觉样式 +4. **查看 [04-languages/](04-languages/)** - 了解语言支持 +5. **高级主题** - 深入专业用例 + +## 🤝 贡献示例 + +添加新示例: + +1. 选择合适的分类或创建新分类 +2. 遵循命名约定:`example_name.go` +3. 包含代码和生成的输出 +4. 在 README.md 中添加文档 +5. 使用 `go run main.go` 测试 + diff --git a/font/JetBrainsMono-Regular.ttf b/font/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3b75b2690d8b096ce4737e637ce6d106a3315dc4 GIT binary patch literal 274744 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

    wfA%)~>MF?yK_5y^d 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

    4(C5 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 @?VdJoq}`JRhF5Tf&}Xq{xsdSYd&T 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 literal 0 HcmV?d00001 diff --git a/font/JetBrainsMonoNL-Regular.ttf b/font/JetBrainsMonoNL-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..70d2ec9e28d6e06e923629b78b8a12c12e841589 GIT binary patch literal 208576 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

    M*a5~oB;oGMxw@F(#} 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 100 { + return fmt.Errorf("font size too large, got %.2f", size) + } + return nil +} + +// GetFontHeightToWidthRatio returns the typical height to width ratio for monospace fonts +func GetFontHeightToWidthRatio() float64 { + return 1.68 +} + +// CalculateTextWidth estimates the width of text in pixels +func CalculateTextWidth(text string, fontSize float64) float64 { + return float64(len(text)) * (fontSize / GetFontHeightToWidthRatio()) +} + +// CalculateLineHeight calculates the line height in pixels +func CalculateLineHeight(fontSize, lineHeightRatio float64) float64 { + return fontSize * lineHeightRatio +} + +// GetEmbeddedFontData returns embedded font data if available +func GetEmbeddedFontData(fontName string) []byte { + switch fontName { + case "JetBrains Mono", "JetBrainsMono": + return JetBrainsMonoTTF + case "JetBrains Mono NL", "JetBrainsMonoNL": + return JetBrainsMonoNLTTF + default: + return nil + } +} + +// FontConfig represents font configuration +type FontConfig struct { + Family string + Size float64 + Ligatures bool + File string +} + +// NewFontConfig creates a new font configuration with defaults +func NewFontConfig() *FontConfig { + return &FontConfig{ + Family: GetDefaultFontFamily(), + Size: GetDefaultFontSize(), + Ligatures: true, + File: "", + } +} + +// Validate validates the font configuration +func (fc *FontConfig) Validate() error { + if err := ValidateFontFamily(fc.Family); err != nil { + return err + } + if err := ValidateFontSize(fc.Size); err != nil { + return err + } + return nil +} + +// ToFormatterOptions converts font config to formatter options +func (fc *FontConfig) ToFormatterOptions() ([]formatter.Option, error) { + return FontOptions(fc.Family, fc.Size, fc.Ligatures, fc.File) +} diff --git a/freeze.go b/freeze.go new file mode 100644 index 0000000..aeb9dd2 --- /dev/null +++ b/freeze.go @@ -0,0 +1,310 @@ +// 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" +) + +// 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) +} + +// 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) +} + +// 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) +} + +// 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 +} + +// isPNGFile checks if the filename has a PNG extension +func isPNGFile(filename string) bool { + return len(filename) > 4 && filename[len(filename)-4:] == ".png" +} + +// Version information +const ( + Version = "1.0.0" + Author = "Charm" +) diff --git a/generator.go b/generator.go new file mode 100644 index 0000000..b142ea6 --- /dev/null +++ b/generator.go @@ -0,0 +1,443 @@ +package freezelib + +import ( + "bytes" + "context" + "errors" + "fmt" + "github.com/landaiqing/freezelib/font" + "github.com/landaiqing/freezelib/svg" + "os" + "strings" + + "github.com/alecthomas/chroma/v2" + formatter "github.com/alecthomas/chroma/v2/formatters/svg" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/alecthomas/chroma/v2/styles" + "github.com/beevik/etree" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/cellbuf" + "github.com/kanrichan/resvg-go" +) + +const ( + defaultFontSize = 14.0 + defaultLineHeight = 1.2 +) + +// Generator handles the core screenshot generation logic +type Generator struct { + config *Config +} + +// NewGenerator creates a new generator with the given configuration +func NewGenerator(config *Config) *Generator { + if config == nil { + config = DefaultConfig() + } + return &Generator{config: config} +} + +// GenerateFromCode generates an SVG from source code +func (g *Generator) GenerateFromCode(code, language string) ([]byte, error) { + if err := g.config.Validate(); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + // Set language if provided + if language != "" { + g.config.Language = language + } + + // Get lexer for the language + var lexer chroma.Lexer + if g.config.Language != "" { + lexer = lexers.Get(g.config.Language) + } + if lexer == nil { + lexer = lexers.Analyse(code) + } + if lexer == nil { + return nil, errors.New("could not determine language for syntax highlighting") + } + + return g.generateSVG(code, lexer, false) +} + +// GenerateFromFile generates an SVG from a source code file +func (g *Generator) GenerateFromFile(filename string) ([]byte, error) { + if err := g.config.Validate(); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + // Read file content + content, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + + code := string(content) + + // Get lexer from filename + lexer := lexers.Get(filename) + if lexer == nil { + lexer = lexers.Analyse(code) + } + if lexer == nil { + return nil, errors.New("could not determine language for syntax highlighting") + } + + return g.generateSVG(code, lexer, false) +} + +// GenerateFromANSI generates an SVG from ANSI terminal output +func (g *Generator) GenerateFromANSI(ansiOutput string) ([]byte, error) { + if err := g.config.Validate(); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + // For ANSI output, we use a text lexer but handle ANSI sequences specially + strippedInput := ansi.Strip(ansiOutput) + it := chroma.Literator(chroma.Token{Type: chroma.Text, Value: strippedInput}) + + return g.generateSVGFromIterator(ansiOutput, it, true) +} + +// generateSVG is the core SVG generation function +func (g *Generator) generateSVG(input string, lexer chroma.Lexer, isAnsi bool) ([]byte, error) { + // Create token iterator + var it chroma.Iterator + var err error + if isAnsi { + strippedInput := ansi.Strip(input) + it = chroma.Literator(chroma.Token{Type: chroma.Text, Value: strippedInput}) + } else { + it, err = chroma.Coalesce(lexer).Tokenise(nil, input) + if err != nil { + return nil, fmt.Errorf("could not tokenize input: %w", err) + } + } + + return g.generateSVGFromIterator(input, it, isAnsi) +} + +// generateSVGFromIterator generates SVG from a token iterator +func (g *Generator) generateSVGFromIterator(input string, it chroma.Iterator, isAnsi bool) ([]byte, error) { + config := g.config + + // Calculate scale factor + scale := 1.0 + autoHeight := config.Height == 0 + autoWidth := config.Width == 0 + + // Expand padding and margin + expandedMargin := config.expandMargin(scale) + expandedPadding := config.expandPadding(scale) + + // Process input based on line selection + processedInput := input + if len(config.Lines) == 2 { + processedInput = cutLines(input, config.Lines) + } + + // Handle text wrapping + if config.Wrap > 0 { + processedInput = cellbuf.Wrap(processedInput, config.Wrap, "") + } + + // Get style + style, ok := styles.Registry[strings.ToLower(config.Theme)] + if !ok || style == nil { + style = styles.Get("github") // fallback to github style + } + + // Add background color to style if not present + if !style.Has(chroma.Background) { + var err error + style, err = style.Builder().Add(chroma.Background, "bg:"+config.Background).Build() + if err != nil { + return nil, fmt.Errorf("could not add background: %w", err) + } + } + + // Get font options + fontOptions, err := font.FontOptions(config.Font.Family, config.Font.Size, config.Font.Ligatures, config.Font.File) + if err != nil { + return nil, fmt.Errorf("invalid font options: %w", err) + } + + // Create SVG formatter + f := formatter.New(fontOptions...) + + // Format to SVG + buf := &bytes.Buffer{} + err = f.Format(buf, style, it) + if err != nil { + return nil, fmt.Errorf("could not format to SVG: %w", err) + } + + // Parse SVG document + doc := etree.NewDocument() + _, err = doc.ReadFrom(buf) + if err != nil { + return nil, fmt.Errorf("could not parse SVG: %w", err) + } + + elements := doc.ChildElements() + if len(elements) < 1 { + return nil, errors.New("invalid SVG output") + } + + image := elements[0] + + // Calculate dimensions + w, h := svg.GetDimensions(image) + imageWidth := float64(w) * scale + imageHeight := float64(h) * scale + + // Adjust for font size and line height + imageHeight *= config.Font.Size / defaultFontSize + imageHeight *= config.LineHeight / defaultLineHeight + + terminalWidth := imageWidth + terminalHeight := imageHeight + + hPadding := expandedPadding[left] + expandedPadding[right] + hMargin := expandedMargin[left] + expandedMargin[right] + vMargin := expandedMargin[top] + expandedMargin[bottom] + vPadding := expandedPadding[top] + expandedPadding[bottom] + + // Calculate final dimensions + if !autoWidth { + imageWidth = config.Width + terminalWidth = config.Width - hMargin + } else { + imageWidth += hMargin + hPadding + terminalWidth += hPadding + } + + if !autoHeight { + imageHeight = config.Height + terminalHeight = config.Height - vMargin + } else { + imageHeight += vMargin + vPadding + terminalHeight += vPadding + } + + // Get terminal background element + terminal := image.SelectElement("rect") + if terminal == nil { + return nil, errors.New("could not find terminal background element") + } + + // Add window controls if enabled + if config.Window { + windowControls := svg.NewWindowControls(5.5*scale, 19.0*scale, 12.0*scale) + svg.Move(windowControls, expandedMargin[left], expandedMargin[top]) + image.AddChild(windowControls) + expandedPadding[top] += 15 * scale + } + + // Add corner radius + if config.Border.Radius > 0 { + svg.AddCornerRadius(terminal, config.Border.Radius*scale) + } + + // Add shadow + if config.Shadow.Blur > 0 || config.Shadow.X > 0 || config.Shadow.Y > 0 { + id := "shadow" + svg.AddShadow(image, id, config.Shadow.X*scale, config.Shadow.Y*scale, config.Shadow.Blur*scale) + terminal.CreateAttr("filter", fmt.Sprintf("url(#%s)", id)) + } + + // Process text elements + textGroup := image.SelectElement("g") + if textGroup != nil { + textGroup.CreateAttr("font-size", fmt.Sprintf("%.2fpx", config.Font.Size*scale)) + textGroup.CreateAttr("clip-path", "url(#terminalMask)") + text := textGroup.SelectElements("text") + + offsetLine := 0 + if len(config.Lines) > 0 { + offsetLine = config.Lines[0] + } + + lineHeight := config.LineHeight * scale + + for i, line := range text { + if isAnsi { + line.SetText("") + } + + // Add line numbers if enabled + if config.ShowLineNumbers { + ln := etree.NewElement("tspan") + ln.CreateAttr("xml:space", "preserve") + ln.CreateAttr("fill", style.Get(chroma.LineNumbers).Colour.String()) + ln.SetText(fmt.Sprintf("%3d ", i+1+offsetLine)) + line.InsertChildAt(0, ln) + } + + // Position the line + x := expandedPadding[left] + expandedMargin[left] + y := (float64(i+1))*(config.Font.Size*lineHeight) + expandedPadding[top] + expandedMargin[top] + + svg.Move(line, x, y) + + // Remove lines that are outside the visible area + if y > imageHeight-expandedMargin[bottom]-expandedPadding[bottom] { + textGroup.RemoveChild(line) + } + } + + // Process ANSI sequences if needed + if isAnsi { + processANSI(processedInput, text, textGroup, config, scale) + } + } + + // Calculate auto width based on content + if autoWidth { + tabWidth := 4 + if isAnsi { + tabWidth = 6 + } + strippedInput := ansi.Strip(processedInput) + longestLine := lipgloss.Width(strings.ReplaceAll(strippedInput, "\t", strings.Repeat(" ", tabWidth))) + terminalWidth = float64(longestLine+1) * (config.Font.Size / font.GetFontHeightToWidthRatio()) + terminalWidth *= scale + terminalWidth += hPadding + imageWidth = terminalWidth + hMargin + } + + // Add border + if config.Border.Width > 0 { + svg.AddOutline(terminal, config.Border.Width, config.Border.Color) + terminalHeight -= config.Border.Width * 2 + terminalWidth -= config.Border.Width * 2 + } + + // Adjust for line numbers + if config.ShowLineNumbers { + if autoWidth { + terminalWidth += config.Font.Size * 3 * scale + imageWidth += config.Font.Size * 3 * scale + } else { + terminalWidth -= config.Font.Size * 3 + } + } + + // Add clipping path if needed + if !autoHeight || !autoWidth { + svg.AddClipPath(image, "terminalMask", + expandedMargin[left], expandedMargin[top], + terminalWidth, terminalHeight-expandedPadding[bottom]) + } + + // Set final positions and dimensions + svg.Move(terminal, max(expandedMargin[left], config.Border.Width/2), max(expandedMargin[top], config.Border.Width/2)) + svg.SetDimensions(image, imageWidth, imageHeight) + svg.SetDimensions(terminal, terminalWidth, terminalHeight) + + // Convert to bytes + return doc.WriteToBytes() +} + +// ConvertToPNG converts SVG data to PNG format +func (g *Generator) ConvertToPNG(svgData []byte, width, height float64) ([]byte, error) { + // Parse SVG document + doc := etree.NewDocument() + err := doc.ReadFromBytes(svgData) + if err != nil { + return nil, fmt.Errorf("could not parse SVG: %w", err) + } + + // Use resvg for conversion + worker, err := resvg.NewDefaultWorker(context.Background()) + if err != nil { + return nil, fmt.Errorf("could not create resvg worker: %w", err) + } + defer worker.Close() + + fontdb, err := worker.NewFontDBDefault() + if err != nil { + return nil, fmt.Errorf("could not create font database: %w", err) + } + defer fontdb.Close() + + // Load embedded fonts + if len(font.JetBrainsMonoTTF) > 0 { + err = fontdb.LoadFontData(font.JetBrainsMonoTTF) + if err != nil { + return nil, fmt.Errorf("could not load JetBrains Mono font: %w", err) + } + } + + pixmap, err := worker.NewPixmap(uint32(width), uint32(height)) + if err != nil { + return nil, fmt.Errorf("could not create pixmap: %w", err) + } + defer pixmap.Close() + + tree, err := worker.NewTreeFromData(svgData, &resvg.Options{ + Dpi: 192, + ShapeRenderingMode: resvg.ShapeRenderingModeGeometricPrecision, + TextRenderingMode: resvg.TextRenderingModeOptimizeLegibility, + ImageRenderingMode: resvg.ImageRenderingModeOptimizeQuality, + DefaultSizeWidth: float32(width), + DefaultSizeHeight: float32(height), + }) + if err != nil { + return nil, fmt.Errorf("could not create SVG tree: %w", err) + } + defer tree.Close() + + err = tree.ConvertText(fontdb) + if err != nil { + return nil, fmt.Errorf("could not convert text: %w", err) + } + + err = tree.Render(resvg.TransformIdentity(), pixmap) + if err != nil { + return nil, fmt.Errorf("could not render SVG: %w", err) + } + + pngData, err := pixmap.EncodePNG() + if err != nil { + return nil, fmt.Errorf("could not encode PNG: %w", err) + } + + return pngData, nil +} + +// cutLines cuts the input to the specified line range +func cutLines(input string, lines []int) string { + if len(lines) != 2 { + return input + } + + inputLines := strings.Split(input, "\n") + start := lines[0] + end := lines[1] + + if start < 0 { + start = 0 + } + if end >= len(inputLines) || end < 0 { + end = len(inputLines) - 1 + } + if start > end { + return "" + } + + return strings.Join(inputLines[start:end+1], "\n") +} + +// max returns the maximum of two float64 values +func max(a, b float64) float64 { + if a > b { + return a + } + return b +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ad3925c --- /dev/null +++ b/go.mod @@ -0,0 +1,29 @@ +module github.com/landaiqing/freezelib + +go 1.23.0 + +toolchain go1.24.3 + +require ( + github.com/alecthomas/chroma/v2 v2.19.0 + github.com/beevik/etree v1.5.1 + github.com/charmbracelet/lipgloss v1.1.0 + github.com/charmbracelet/x/ansi v0.9.3 + github.com/charmbracelet/x/cellbuf v0.0.13 + github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 + github.com/mattn/go-runewidth v0.0.16 +) + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/colorprofile v0.3.1 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sys v0.34.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..aab6579 --- /dev/null +++ b/go.sum @@ -0,0 +1,52 @@ +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4= +github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/beevik/etree v1.5.1 h1:TC3zyxYp+81wAmbsi8SWUpZCurbxa6S8RITYRSkNRwo= +github.com/beevik/etree v1.5.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= +github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= +github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY= +github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5/go.mod h1:c9+VS9GaommgIOzNWb5ze4lYwfT8BZ2UDyGiuQTT7yc= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0= +github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/presets.go b/presets.go new file mode 100644 index 0000000..1540c37 --- /dev/null +++ b/presets.go @@ -0,0 +1,194 @@ +package freezelib + +// BasePreset returns a basic configuration for simple code screenshots +func BasePreset() *Config { + config := DefaultConfig() + config.Background = "#171717" + config.SetPadding(20) + config.SetMargin(0) + config.Window = false + config.Border = Border{Radius: 0, Width: 0, Color: "#515151"} + config.Shadow = Shadow{Blur: 0, X: 0, Y: 0} + config.ShowLineNumbers = false + return config +} + +// FullPreset returns a macOS-like configuration with window controls and shadow +func FullPreset() *Config { + config := DefaultConfig() + config.Background = "#282c34" + config.SetPadding(20, 40, 20, 20) + config.SetMargin(20) + config.Window = true + config.Border = Border{Radius: 8, Width: 0, Color: "#515151"} + config.Shadow = Shadow{Blur: 20, X: 0, Y: 10} + config.ShowLineNumbers = false + config.Theme = "github-dark" + return config +} + +// TerminalPreset returns a configuration optimized for terminal output +func TerminalPreset() *Config { + config := DefaultConfig() + config.Background = "#0d1117" + config.SetPadding(15) + config.SetMargin(10) + config.Window = false + config.Border = Border{Radius: 6, Width: 1, Color: "#30363d"} + config.Shadow = Shadow{Blur: 15, X: 0, Y: 5} + config.ShowLineNumbers = false + config.Theme = "github-dark" + config.Font.Family = "JetBrains Mono" + config.Font.Size = 13 + return config +} + +// PresentationPreset returns a configuration suitable for presentations +func PresentationPreset() *Config { + config := DefaultConfig() + config.Background = "#ffffff" + config.SetPadding(40) + config.SetMargin(30) + config.Window = true + config.Border = Border{Radius: 12, Width: 2, Color: "#e1e4e8"} + config.Shadow = Shadow{Blur: 30, X: 0, Y: 15} + config.ShowLineNumbers = true + config.Theme = "github" + config.Font.Size = 16 + config.LineHeight = 1.4 + return config +} + +// MinimalPreset returns a minimal configuration with no decorations +func MinimalPreset() *Config { + config := DefaultConfig() + config.Background = "#ffffff" + config.SetPadding(10) + config.SetMargin(0) + config.Window = false + config.Border = Border{Radius: 0, Width: 0, Color: ""} + config.Shadow = Shadow{Blur: 0, X: 0, Y: 0} + config.ShowLineNumbers = false + config.Theme = "github" + return config +} + +// DarkPreset returns a dark theme configuration +func DarkPreset() *Config { + config := DefaultConfig() + config.Background = "#1e1e1e" + config.SetPadding(25) + config.SetMargin(15) + config.Window = false + config.Border = Border{Radius: 8, Width: 1, Color: "#3c3c3c"} + config.Shadow = Shadow{Blur: 20, X: 0, Y: 8} + config.ShowLineNumbers = false + config.Theme = "dracula" + config.Font.Family = "Fira Code" + config.Font.Size = 14 + config.Font.Ligatures = true + return config +} + +// LightPreset returns a light theme configuration +func LightPreset() *Config { + config := DefaultConfig() + config.Background = "#fafbfc" + config.SetPadding(25) + config.SetMargin(15) + config.Window = false + config.Border = Border{Radius: 8, Width: 1, Color: "#d1d5da"} + config.Shadow = Shadow{Blur: 20, X: 0, Y: 8} + config.ShowLineNumbers = false + config.Theme = "github" + config.Font.Family = "SF Mono" + config.Font.Size = 14 + return config +} + +// RetroPreset returns a retro terminal-style configuration +func RetroPreset() *Config { + config := DefaultConfig() + config.Background = "#000000" + config.SetPadding(20) + config.SetMargin(10) + config.Window = false + config.Border = Border{Radius: 0, Width: 2, Color: "#00ff00"} + config.Shadow = Shadow{Blur: 0, X: 0, Y: 0} + config.ShowLineNumbers = false + config.Theme = "monokai" + config.Font.Family = "Courier New" + config.Font.Size = 12 + config.Font.Ligatures = false + return config +} + +// NeonPreset returns a neon-style configuration +func NeonPreset() *Config { + config := DefaultConfig() + config.Background = "#0a0a0a" + config.SetPadding(30) + config.SetMargin(20) + config.Window = false + config.Border = Border{Radius: 10, Width: 2, Color: "#ff00ff"} + config.Shadow = Shadow{Blur: 25, X: 0, Y: 0} + config.ShowLineNumbers = false + config.Theme = "vim" + config.Font.Family = "Fira Code" + config.Font.Size = 14 + config.Font.Ligatures = true + return config +} + +// CompactPreset returns a compact configuration for small code snippets +func CompactPreset() *Config { + config := DefaultConfig() + config.Background = "#f6f8fa" + config.SetPadding(10) + config.SetMargin(5) + config.Window = false + config.Border = Border{Radius: 4, Width: 1, Color: "#d0d7de"} + config.Shadow = Shadow{Blur: 5, X: 0, Y: 2} + config.ShowLineNumbers = false + config.Theme = "github" + config.Font.Size = 12 + config.LineHeight = 1.1 + return config +} + +// PresetMap contains all available presets +var PresetMap = map[string]func() *Config{ + "base": BasePreset, + "full": FullPreset, + "terminal": TerminalPreset, + "presentation": PresentationPreset, + "minimal": MinimalPreset, + "dark": DarkPreset, + "light": LightPreset, + "retro": RetroPreset, + "neon": NeonPreset, + "compact": CompactPreset, +} + +// GetPreset returns a preset configuration by name +func GetPreset(name string) *Config { + if preset, exists := PresetMap[name]; exists { + return preset() + } + return DefaultConfig() +} + +// ListPresets returns a list of available preset names +func ListPresets() []string { + presets := make([]string, 0, len(PresetMap)) + for name := range PresetMap { + presets = append(presets, name) + } + return presets +} + +// IsValidPreset checks if a preset name is valid +func IsValidPreset(name string) bool { + _, exists := PresetMap[name] + return exists +} diff --git a/quickfreeze.go b/quickfreeze.go new file mode 100644 index 0000000..5a68c6a --- /dev/null +++ b/quickfreeze.go @@ -0,0 +1,348 @@ +package freezelib + +import ( + "fmt" + "strings" +) + +// QuickFreeze provides a simplified, chainable API for quick code screenshots +type QuickFreeze struct { + config *Config +} + +// NewQuickFreeze creates a new QuickFreeze instance with default configuration +func NewQuickFreeze() *QuickFreeze { + return &QuickFreeze{ + config: DefaultConfig(), + } +} + +// NewQuickFreezeWithPreset creates a new QuickFreeze instance with a preset +func NewQuickFreezeWithPreset(presetName string) *QuickFreeze { + return &QuickFreeze{ + config: GetPreset(presetName), + } +} + +// WithTheme sets the syntax highlighting theme +func (qf *QuickFreeze) WithTheme(theme string) *QuickFreeze { + qf.config.SetTheme(theme) + return qf +} + +// WithFont sets the font family and size +func (qf *QuickFreeze) WithFont(family string, size float64) *QuickFreeze { + qf.config.SetFont(family, size) + return qf +} + +// WithBackground sets the background color +func (qf *QuickFreeze) WithBackground(color string) *QuickFreeze { + qf.config.SetBackground(color) + return qf +} + +// WithWindow enables window controls +func (qf *QuickFreeze) WithWindow() *QuickFreeze { + qf.config.SetWindow(true) + return qf +} + +// WithoutWindow disables window controls +func (qf *QuickFreeze) WithoutWindow() *QuickFreeze { + qf.config.SetWindow(false) + return qf +} + +// WithLineNumbers enables line numbers +func (qf *QuickFreeze) WithLineNumbers() *QuickFreeze { + qf.config.SetLineNumbers(true) + return qf +} + +// WithoutLineNumbers disables line numbers +func (qf *QuickFreeze) WithoutLineNumbers() *QuickFreeze { + qf.config.SetLineNumbers(false) + return qf +} + +// WithShadow adds a shadow effect +func (qf *QuickFreeze) WithShadow() *QuickFreeze { + qf.config.SetShadow(20, 0, 10) + return qf +} + +// WithCustomShadow adds a custom shadow effect +func (qf *QuickFreeze) WithCustomShadow(blur, x, y float64) *QuickFreeze { + qf.config.SetShadow(blur, x, y) + return qf +} + +// WithoutShadow removes shadow effect +func (qf *QuickFreeze) WithoutShadow() *QuickFreeze { + qf.config.SetShadow(0, 0, 0) + return qf +} + +// WithBorder adds a border +func (qf *QuickFreeze) WithBorder() *QuickFreeze { + qf.config.SetBorder(1, 8, "#515151") + return qf +} + +// WithCustomBorder adds a custom border +func (qf *QuickFreeze) WithCustomBorder(width, radius float64, color string) *QuickFreeze { + qf.config.SetBorder(width, radius, color) + return qf +} + +// WithoutBorder removes border +func (qf *QuickFreeze) WithoutBorder() *QuickFreeze { + qf.config.SetBorder(0, 0, "") + return qf +} + +// WithPadding sets padding (1, 2, or 4 values like CSS) +func (qf *QuickFreeze) WithPadding(values ...float64) *QuickFreeze { + qf.config.SetPadding(values...) + return qf +} + +// WithMargin sets margin (1, 2, or 4 values like CSS) +func (qf *QuickFreeze) WithMargin(values ...float64) *QuickFreeze { + qf.config.SetMargin(values...) + return qf +} + +// WithDimensions sets specific width and height +func (qf *QuickFreeze) WithDimensions(width, height float64) *QuickFreeze { + qf.config.SetDimensions(width, height) + return qf +} + +// WithWidth sets specific width (height auto) +func (qf *QuickFreeze) WithWidth(width float64) *QuickFreeze { + qf.config.Width = width + return qf +} + +// WithHeight sets specific height (width auto) +func (qf *QuickFreeze) WithHeight(height float64) *QuickFreeze { + qf.config.Height = height + return qf +} + +// WithLines sets the line range to capture (1-indexed) +func (qf *QuickFreeze) WithLines(start, end int) *QuickFreeze { + qf.config.SetLines(start, end) + return qf +} + +// WithLanguage sets the programming language for syntax highlighting +func (qf *QuickFreeze) WithLanguage(language string) *QuickFreeze { + qf.config.SetLanguage(language) + return qf +} + +// WithLineHeight sets the line height ratio +func (qf *QuickFreeze) WithLineHeight(ratio float64) *QuickFreeze { + qf.config.LineHeight = ratio + return qf +} + +// WithWrap sets text wrapping at specified column +func (qf *QuickFreeze) WithWrap(columns int) *QuickFreeze { + qf.config.Wrap = columns + return qf +} + +// CodeToSVG generates SVG from source code +func (qf *QuickFreeze) CodeToSVG(code string) ([]byte, error) { + generator := NewGenerator(qf.config) + return generator.GenerateFromCode(code, qf.config.Language) +} + +// CodeToPNG generates PNG from source code +func (qf *QuickFreeze) CodeToPNG(code string) ([]byte, error) { + generator := NewGenerator(qf.config) + svgData, err := generator.GenerateFromCode(code, qf.config.Language) + if err != nil { + return nil, err + } + + width := qf.config.Width + height := qf.config.Height + if width == 0 || height == 0 { + width = 800 * 4 + height = 600 * 4 + } else { + width *= 4 + height *= 4 + } + + return generator.ConvertToPNG(svgData, width, height) +} + +// FileToSVG generates SVG from a source code file +func (qf *QuickFreeze) FileToSVG(filename string) ([]byte, error) { + generator := NewGenerator(qf.config) + return generator.GenerateFromFile(filename) +} + +// FileToPNG generates PNG from a source code file +func (qf *QuickFreeze) FileToPNG(filename string) ([]byte, error) { + generator := NewGenerator(qf.config) + svgData, err := generator.GenerateFromFile(filename) + if err != nil { + return nil, err + } + + width := qf.config.Width + height := qf.config.Height + if width == 0 || height == 0 { + width = 800 * 4 + height = 600 * 4 + } else { + width *= 4 + height *= 4 + } + + return generator.ConvertToPNG(svgData, width, height) +} + +// ANSIToSVG generates SVG from ANSI terminal output +func (qf *QuickFreeze) ANSIToSVG(ansiOutput string) ([]byte, error) { + generator := NewGenerator(qf.config) + return generator.GenerateFromANSI(ansiOutput) +} + +// ANSIToPNG generates PNG from ANSI terminal output +func (qf *QuickFreeze) ANSIToPNG(ansiOutput string) ([]byte, error) { + generator := NewGenerator(qf.config) + svgData, err := generator.GenerateFromANSI(ansiOutput) + if err != nil { + return nil, err + } + + width := qf.config.Width + height := qf.config.Height + if width == 0 || height == 0 { + width = 800 * 4 + height = 600 * 4 + } else { + width *= 4 + height *= 4 + } + + return generator.ConvertToPNG(svgData, width, height) +} + +// SaveCodeToFile generates and saves code screenshot to file +func (qf *QuickFreeze) SaveCodeToFile(code, filename string) error { + var data []byte + var err error + + if isPNGFile(filename) { + data, err = qf.CodeToPNG(code) + } else { + data, err = qf.CodeToSVG(code) + } + + if err != nil { + return err + } + + return saveToFile(data, filename) +} + +// SaveFileToFile generates and saves file screenshot to file +func (qf *QuickFreeze) SaveFileToFile(inputFile, outputFile string) error { + var data []byte + var err error + + if isPNGFile(outputFile) { + data, err = qf.FileToPNG(inputFile) + } else { + data, err = qf.FileToSVG(inputFile) + } + + if err != nil { + return err + } + + return saveToFile(data, outputFile) +} + +// SaveANSIToFile generates and saves ANSI screenshot to file +func (qf *QuickFreeze) SaveANSIToFile(ansiOutput, filename string) error { + var data []byte + var err error + + if isPNGFile(filename) { + data, err = qf.ANSIToPNG(ansiOutput) + } else { + data, err = qf.ANSIToSVG(ansiOutput) + } + + if err != nil { + return err + } + + return saveToFile(data, filename) +} + +// Config returns the current configuration +func (qf *QuickFreeze) Config() *Config { + return qf.config +} + +// Clone creates a copy of the QuickFreeze instance +func (qf *QuickFreeze) Clone() *QuickFreeze { + return &QuickFreeze{ + config: qf.config.Clone(), + } +} + +// Reset resets the configuration to defaults +func (qf *QuickFreeze) Reset() *QuickFreeze { + qf.config = DefaultConfig() + return qf +} + +// ResetToPreset resets the configuration to a specific preset +func (qf *QuickFreeze) ResetToPreset(presetName string) *QuickFreeze { + qf.config = GetPreset(presetName) + return qf +} + +// String returns a string representation of the current configuration +func (qf *QuickFreeze) String() string { + var parts []string + + parts = append(parts, fmt.Sprintf("Theme: %s", qf.config.Theme)) + parts = append(parts, fmt.Sprintf("Font: %s %.1fpx", qf.config.Font.Family, qf.config.Font.Size)) + parts = append(parts, fmt.Sprintf("Background: %s", qf.config.Background)) + + if qf.config.Window { + parts = append(parts, "Window: enabled") + } + + if qf.config.ShowLineNumbers { + parts = append(parts, "Line numbers: enabled") + } + + if qf.config.Shadow.Blur > 0 { + parts = append(parts, fmt.Sprintf("Shadow: blur=%.1f", qf.config.Shadow.Blur)) + } + + if qf.config.Border.Width > 0 { + parts = append(parts, fmt.Sprintf("Border: width=%.1f", qf.config.Border.Width)) + } + + return "QuickFreeze{" + strings.Join(parts, ", ") + "}" +} + +// saveToFile is a helper function to save data to file +func saveToFile(data []byte, filename string) error { + return NewWithConfig(DefaultConfig()).SaveToFile(data, filename) +} diff --git a/sample/basic_example.svg b/sample/basic_example.svg new file mode 100644 index 0000000..6373887 --- /dev/null +++ b/sample/basic_example.svg @@ -0,0 +1,15 @@ + + + + + +package main + +import "fmt" + +func main() { +    fmt.Println("Hello, World!") +    fmt.Println("This is a beautiful code screenshot!") +} + + diff --git a/sample/chained_example.svg b/sample/chained_example.svg new file mode 100644 index 0000000..680d89e --- /dev/null +++ b/sample/chained_example.svg @@ -0,0 +1,24 @@ + + + + + + 1 #include <iostream> + 2 #include <vector> + 3 #include <algorithm> + 4 + 5 int main() { + 6     std::vector<int> numbers = {52819}; + 7      + 8     std::sort(numbers.begin(), numbers.end()); + 9      + 10     std::cout << "Sorted numbers: "; + 11     for (const auto& num : numbers) { + 12         std::cout << num << " "; + 13     } + 14     std::cout << std::endl; + 15      + 16     return 0; + 17 } + + diff --git a/sample/custom_config_example.svg b/sample/custom_config_example.svg new file mode 100644 index 0000000..5240174 --- /dev/null +++ b/sample/custom_config_example.svg @@ -0,0 +1,25 @@ + + + + + + 1 import numpy as np + 2 import matplotlib.pyplot as plt + 3 + 4 def plot_sine_wave(): + 5     x = np.linspace(0, 2 * np.pi, 100) + 6     y = np.sin(x) + 7      + 8     plt.figure(figsize=(10, 6)) + 9     plt.plot(x, y, 'b-', linewidth=2, label='sin(x)') + 10     plt.xlabel('x') + 11     plt.ylabel('sin(x)') + 12     plt.title('Sine Wave') + 13     plt.grid(True, alpha=0.3) + 14     plt.legend() + 15     plt.show() + 16 + 17 if __name__ == "__main__": + 18     plot_sine_wave() + + diff --git a/sample/file_example.svg b/sample/file_example.svg new file mode 100644 index 0000000..34aa56e --- /dev/null +++ b/sample/file_example.svg @@ -0,0 +1,19 @@ + + + + + + 1 use std::collections::HashMap; + 2 + 3 fn main() { + 4     let mut scores = HashMap::new(); + 5      + 6     scores.insert(String::from("Blue"), 10); + 7     scores.insert(String::from("Yellow"), 50); + 8      + 9     for (key, value) in &scores { + 10         println!("{}{}", key, value); + 11     } + 12 } + + diff --git a/sample/main.go b/sample/main.go new file mode 100644 index 0000000..a81e36b --- /dev/null +++ b/sample/main.go @@ -0,0 +1,329 @@ +package main + +import ( + "fmt" + "os" + + "github.com/landaiqing/freezelib" +) + +func main() { + fmt.Println("🎨 Freeze Library Examples") + fmt.Println("========================") + + // Run all examples + basicExample() + quickFreezeExample() + terminalExample() + customConfigExample() + fileExample() + presetExample() + chainedExample() + + fmt.Println("\n✅ All examples completed successfully!") + fmt.Println("Check the generated files in the current directory.") +} + +func basicExample() { + fmt.Println("\n📝 Basic Example") + fmt.Println("----------------") + + // Create a new freeze instance + freeze := freezelib.New() + + // Go code to screenshot + code := `package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") + fmt.Println("This is a beautiful code screenshot!") +}` + + // Generate SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + // Save to file + err = os.WriteFile("basic_example.svg", svgData, 0644) + if err != nil { + fmt.Printf("Error saving file: %v\n", err) + return + } + + fmt.Println("✓ Generated basic_example.svg") +} + +func quickFreezeExample() { + fmt.Println("\n⚡ QuickFreeze Example") + fmt.Println("---------------------") + + // Use QuickFreeze for simplified API + qf := freezelib.NewQuickFreeze() + + // JavaScript code with styling + code := `function fibonacci(n) { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +console.log('Fibonacci sequence:'); +for (let i = 0; i < 10; i++) { + console.log('F(' + i + ') = ' + fibonacci(i)); +}` + + // Chain styling options + svgData, err := qf.WithTheme("dracula"). + WithFont("Fira Code", 14). + WithWindow(). + WithShadow(). + WithLineNumbers(). + WithLanguage("javascript"). + CodeToSVG(code) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + err = os.WriteFile("quickfreeze_example.svg", svgData, 0644) + if err != nil { + fmt.Printf("Error saving file: %v\n", err) + return + } + + fmt.Println("✓ Generated quickfreeze_example.svg") +} + +func terminalExample() { + fmt.Println("\n💻 Terminal Example") + fmt.Println("-------------------") + + // Use terminal preset for ANSI output + freeze := freezelib.NewWithConfig(freezelib.TerminalPreset()) + + // Colored terminal output + terminalOutput := "\033[32m✓ SUCCESS\033[0m: Build completed successfully\n" + + "\033[33m⚠ WARNING\033[0m: Deprecated function used in main.go:42\n" + + "\033[31m✗ ERROR\033[0m: File not found: config.json\n" + + "\033[36mINFO\033[0m: Starting server on port 8080\n" + + "\033[35mDEBUG\033[0m: Loading configuration from ~/.config/app" + + svgData, err := freeze.GenerateFromANSI(terminalOutput) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + err = os.WriteFile("terminal_example.svg", svgData, 0644) + if err != nil { + fmt.Printf("Error saving file: %v\n", err) + return + } + + fmt.Println("✓ Generated terminal_example.svg") +} + +func customConfigExample() { + fmt.Println("\n⚙️ Custom Config Example") + fmt.Println("-------------------------") + + // Create custom configuration + config := freezelib.DefaultConfig() + + // Customize appearance + config.Theme = "github" + config.Background = "#f6f8fa" + config.Font.Family = "JetBrains Mono" + config.Font.Size = 16 + config.LineHeight = 1.4 + + // Layout settings + config.SetPadding(30) + config.SetMargin(20) + config.Width = 800 + + // Effects + config.Window = true + config.ShowLineNumbers = true + config.Border.Radius = 12 + config.Border.Width = 2 + config.Border.Color = "#d1d9e0" + config.Shadow.Blur = 25 + config.Shadow.Y = 15 + + // Create freeze instance with custom config + freeze := freezelib.NewWithConfig(config) + + // Python code + code := `import numpy as np +import matplotlib.pyplot as plt + +def plot_sine_wave(): + x = np.linspace(0, 2 * np.pi, 100) + y = np.sin(x) + + plt.figure(figsize=(10, 6)) + plt.plot(x, y, 'b-', linewidth=2, label='sin(x)') + plt.xlabel('x') + plt.ylabel('sin(x)') + plt.title('Sine Wave') + plt.grid(True, alpha=0.3) + plt.legend() + plt.show() + +if __name__ == "__main__": + plot_sine_wave()` + + svgData, err := freeze.GenerateFromCode(code, "python") + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + err = os.WriteFile("custom_config_example.svg", svgData, 0644) + if err != nil { + fmt.Printf("Error saving file: %v\n", err) + return + } + + fmt.Println("✓ Generated custom_config_example.svg") +} + +func fileExample() { + fmt.Println("\n📁 File Example") + fmt.Println("---------------") + + // Create a sample Rust file + sampleCode := `use std::collections::HashMap; + +fn main() { + let mut scores = HashMap::new(); + + scores.insert(String::from("Blue"), 10); + scores.insert(String::from("Yellow"), 50); + + for (key, value) in &scores { + println!("{}: {}", key, value); + } +}` + + // Create sample file + err := os.WriteFile("sample.rs", []byte(sampleCode), 0644) + if err != nil { + fmt.Printf("Error creating sample file: %v\n", err) + return + } + + // Use presentation preset + freeze := freezelib.NewWithConfig(freezelib.PresentationPreset()) + + // Generate from file + svgData, err := freeze.GenerateFromFile("sample.rs") + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + err = os.WriteFile("file_example.svg", svgData, 0644) + if err != nil { + fmt.Printf("Error saving file: %v\n", err) + return + } + + fmt.Println("✓ Generated file_example.svg") + fmt.Println("✓ Created sample.rs") +} + +func presetExample() { + fmt.Println("\n🎨 Preset Example") + fmt.Println("-----------------") + + code := `const express = require('express'); +const app = express(); + +app.get('/', (req, res) => { + res.json({ message: 'Hello, World!' }); +}); + +app.listen(3000, () => { + console.log('Server running on port 3000'); +});` + + // Try different presets + presets := []string{"dark", "light", "minimal", "retro"} + + for _, preset := range presets { + freeze := freezelib.NewWithPreset(preset) + svgData, err := freeze.GenerateFromCode(code, "javascript") + if err != nil { + fmt.Printf("Error with preset %s: %v\n", preset, err) + continue + } + + filename := fmt.Sprintf("preset_%s_example.svg", preset) + err = os.WriteFile(filename, svgData, 0644) + if err != nil { + fmt.Printf("Error saving %s: %v\n", filename, err) + continue + } + + fmt.Printf("✓ Generated %s\n", filename) + } +} + +func chainedExample() { + fmt.Println("\n🔗 Chained Methods Example") + fmt.Println("---------------------------") + + // Create base freeze instance + freeze := freezelib.New() + + code := `#include +#include +#include + +int main() { + std::vector numbers = {5, 2, 8, 1, 9}; + + std::sort(numbers.begin(), numbers.end()); + + std::cout << "Sorted numbers: "; + for (const auto& num : numbers) { + std::cout << num << " "; + } + std::cout << std::endl; + + return 0; +}` + + // Chain multiple styling methods + svgData, err := freeze. + WithTheme("monokai"). + WithFont("Cascadia Code", 15). + WithBackground("#2d2d2d"). + WithWindow(true). + WithLineNumbers(true). + WithShadow(20, 0, 10). + WithBorder(1, 10, "#444444"). + WithPadding(25). + WithMargin(15). + GenerateFromCode(code, "cpp") + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + err = os.WriteFile("chained_example.svg", svgData, 0644) + if err != nil { + fmt.Printf("Error saving file: %v\n", err) + return + } + + fmt.Println("✓ Generated chained_example.svg") +} diff --git a/sample/preset_dark_example.svg b/sample/preset_dark_example.svg new file mode 100644 index 0000000..c375c96 --- /dev/null +++ b/sample/preset_dark_example.svg @@ -0,0 +1,17 @@ + + + + + +const express = require('express'); +const app = express(); + +app.get('/', (req, res) => { +  res.json({ message: 'Hello, World!' }); +}); + +app.listen(3000, () => { +  console.log('Server running on port 3000'); +}); + + diff --git a/sample/preset_light_example.svg b/sample/preset_light_example.svg new file mode 100644 index 0000000..a0cbb7c --- /dev/null +++ b/sample/preset_light_example.svg @@ -0,0 +1,17 @@ + + + + + +const express = require('express'); +const app = express(); + +app.get('/', (req, res) => { +  res.json({ message: 'Hello, World!' }); +}); + +app.listen(3000, () => { +  console.log('Server running on port 3000'); +}); + + diff --git a/sample/preset_minimal_example.svg b/sample/preset_minimal_example.svg new file mode 100644 index 0000000..26f5e67 --- /dev/null +++ b/sample/preset_minimal_example.svg @@ -0,0 +1,17 @@ + + + + + +const express = require('express'); +const app = express(); + +app.get('/', (req, res) => { +  res.json({ message: 'Hello, World!' }); +}); + +app.listen(3000, () => { +  console.log('Server running on port 3000'); +}); + + diff --git a/sample/preset_retro_example.svg b/sample/preset_retro_example.svg new file mode 100644 index 0000000..c0c09e3 --- /dev/null +++ b/sample/preset_retro_example.svg @@ -0,0 +1,17 @@ + + + + + +const express = require('express'); +const app = express(); + +app.get('/', (reqres) => { +  res.json({ message: 'Hello, World!' }); +}); + +app.listen(3000, () => { +  console.log('Server running on port 3000'); +}); + + diff --git a/sample/quickfreeze_example.svg b/sample/quickfreeze_example.svg new file mode 100644 index 0000000..d76d7a2 --- /dev/null +++ b/sample/quickfreeze_example.svg @@ -0,0 +1,16 @@ + + + + + + 1 function fibonacci(n) { + 2     if (n <= 1return n; + 3     return fibonacci(n - 1+ fibonacci(n - 2); + 4 } + 5 + 6 console.log('Fibonacci sequence:'); + 7 for (let i = 0; i < 10; i++) { + 8     console.log('F(' + i + ') = ' + fibonacci(i)); + 9 } + + diff --git a/sample/sample.rs b/sample/sample.rs new file mode 100644 index 0000000..fa2b37e --- /dev/null +++ b/sample/sample.rs @@ -0,0 +1,12 @@ +use std::collections::HashMap; + +fn main() { + let mut scores = HashMap::new(); + + scores.insert(String::from("Blue"), 10); + scores.insert(String::from("Yellow"), 50); + + for (key, value) in &scores { + println!("{}: {}", key, value); + } +} \ No newline at end of file diff --git a/sample/simple_test.go b/sample/simple_test.go new file mode 100644 index 0000000..d56f793 --- /dev/null +++ b/sample/simple_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "github.com/landaiqing/freezelib" + "os" +) + +func simpleTest() { + fmt.Println("🧪 Simple Test") + fmt.Println("==============") + + // Create a new freeze instance + freeze := freezelib.New() + + // Simple Go code to test + code := `package main + +import "fmt" + +func main() { + fmt.Println("Hello from FreezeLib!") +}` + + // Generate SVG + svgData, err := freeze.GenerateFromCode(code, "go") + if err != nil { + fmt.Printf("❌ Error: %v\n", err) + return + } + + // Save to file + err = os.WriteFile("simple_test.svg", svgData, 0644) + if err != nil { + fmt.Printf("❌ Error saving file: %v\n", err) + return + } + + fmt.Printf("✅ Generated simple_test.svg (%d bytes)\n", len(svgData)) + + // Test QuickFreeze API + qf := freezelib.NewQuickFreeze() + svgData2, err := qf.WithTheme("github").CodeToSVG(code) + if err != nil { + fmt.Printf("❌ QuickFreeze Error: %v\n", err) + return + } + + err = os.WriteFile("quickfreeze_test.svg", svgData2, 0644) + if err != nil { + fmt.Printf("❌ Error saving QuickFreeze file: %v\n", err) + return + } + + fmt.Printf("✅ Generated quickfreeze_test.svg (%d bytes)\n", len(svgData2)) + + // Test ANSI output + ansiOutput := "\033[32m✓ SUCCESS\033[0m: Test passed\n\033[31m✗ ERROR\033[0m: Test failed" + ansiData, err := freeze.GenerateFromANSI(ansiOutput) + if err != nil { + fmt.Printf("❌ ANSI Error: %v\n", err) + return + } + + err = os.WriteFile("ansi_test.svg", ansiData, 0644) + if err != nil { + fmt.Printf("❌ Error saving ANSI file: %v\n", err) + return + } + + fmt.Printf("✅ Generated ansi_test.svg (%d bytes)\n", len(ansiData)) + fmt.Println("🎉 All tests passed!") +} + +func init() { + // Run simple test instead of full examples + if len(os.Args) > 1 && os.Args[1] == "test" { + simpleTest() + os.Exit(0) + } +} diff --git a/sample/terminal_example.svg b/sample/terminal_example.svg new file mode 100644 index 0000000..7df700d --- /dev/null +++ b/sample/terminal_example.svg @@ -0,0 +1,8 @@ + + + + + +✓ SUCCESS: Build completed successfully⚠ WARNING: Deprecated function used in main.go:42✗ ERROR: File not found: config.jsonINFO: Starting server on port 8080DEBUG: Loading configuration from ~/.config/app + + diff --git a/svg/svg.go b/svg/svg.go new file mode 100644 index 0000000..05b7aa6 --- /dev/null +++ b/svg/svg.go @@ -0,0 +1,220 @@ +package svg + +import ( + "fmt" + "strconv" + "strings" + + "github.com/beevik/etree" +) + +// AddShadow adds a definition of a shadow to the with the given id. +func AddShadow(element *etree.Element, id string, x, y, blur float64) { + f := etree.NewElement("filter") + f.CreateAttr("id", id) + f.CreateAttr("filterUnits", "userSpaceOnUse") + + b := etree.NewElement("feGaussianBlur") + b.CreateAttr("in", "SourceAlpha") + b.CreateAttr("stdDeviation", fmt.Sprintf("%.2f", blur)) + + o := etree.NewElement("feOffset") + o.CreateAttr("result", "offsetblur") + o.CreateAttr("dx", fmt.Sprintf("%.2f", x)) + o.CreateAttr("dy", fmt.Sprintf("%.2f", y)) + + m := etree.NewElement("feMerge") + mn1 := etree.NewElement("feMergeNode") + mn2 := etree.NewElement("feMergeNode") + mn2.CreateAttr("in", "SourceGraphic") + m.AddChild(mn1) + m.AddChild(mn2) + + f.AddChild(b) + f.AddChild(o) + f.AddChild(m) + + defs := etree.NewElement("defs") + defs.AddChild(f) + element.AddChild(defs) +} + +// AddClipPath adds a definition of a clip path to the with the given id. +func AddClipPath(element *etree.Element, id string, x, y, w, h float64) { + p := etree.NewElement("clipPath") + p.CreateAttr("id", id) + + rect := etree.NewElement("rect") + rect.CreateAttr("x", fmt.Sprintf("%.2f", x)) + rect.CreateAttr("y", fmt.Sprintf("%.2f", y)) + rect.CreateAttr("width", fmt.Sprintf("%.2f", w)) + rect.CreateAttr("height", fmt.Sprintf("%.2f", h)) + + p.AddChild(rect) + + defs := etree.NewElement("defs") + defs.AddChild(p) + element.AddChild(defs) +} + +// AddCornerRadius adds corner radius to an element. +func AddCornerRadius(e *etree.Element, radius float64) { + e.CreateAttr("rx", fmt.Sprintf("%.2f", radius)) + e.CreateAttr("ry", fmt.Sprintf("%.2f", radius)) +} + +// Move moves the given element to the (x, y) position. +func Move(e *etree.Element, x, y float64) { + e.CreateAttr("x", fmt.Sprintf("%.2fpx", x)) + e.CreateAttr("y", fmt.Sprintf("%.2fpx", y)) +} + +// AddOutline adds an outline to the given element. +func AddOutline(e *etree.Element, width float64, color string) { + e.CreateAttr("stroke", color) + e.CreateAttr("stroke-width", fmt.Sprintf("%.2f", width)) +} + +const ( + red string = "#FF5A54" + yellow string = "#E6BF29" + green string = "#52C12B" +) + +// NewWindowControls returns a colorful window bar element. +func NewWindowControls(r float64, x, y float64) *etree.Element { + bar := etree.NewElement("svg") + for i, color := range []string{red, yellow, green} { + circle := etree.NewElement("circle") + circle.CreateAttr("cx", fmt.Sprintf("%.2f", float64(i+1)*float64(x)-float64(r))) + circle.CreateAttr("cy", fmt.Sprintf("%.2f", y)) + circle.CreateAttr("r", fmt.Sprintf("%.2f", r)) + circle.CreateAttr("fill", color) + bar.AddChild(circle) + } + return bar +} + +// SetDimensions sets the width and height of the given element. +func SetDimensions(element *etree.Element, width, height float64) { + widthAttr := element.SelectAttr("width") + heightAttr := element.SelectAttr("height") + if heightAttr != nil { + heightAttr.Value = fmt.Sprintf("%.2f", height) + } + if widthAttr != nil { + widthAttr.Value = fmt.Sprintf("%.2f", width) + } +} + +// GetDimensions returns the width and height of the element. +func GetDimensions(element *etree.Element) (int, int) { + widthValue := element.SelectAttrValue("width", "0px") + heightValue := element.SelectAttrValue("height", "0px") + width := dimensionToInt(widthValue) + height := dimensionToInt(heightValue) + return width, height +} + +// dimensionToInt converts dimension strings to integers +func dimensionToInt(dimension string) int { + dimension = strings.TrimSuffix(dimension, "px") + val, err := strconv.Atoi(dimension) + if err != nil { + return 0 + } + return val +} + +// CreateSVGElement creates a new SVG root element with basic attributes +func CreateSVGElement(width, height float64) *etree.Element { + svg := etree.NewElement("svg") + svg.CreateAttr("xmlns", "http://www.w3.org/2000/svg") + svg.CreateAttr("width", fmt.Sprintf("%.2f", width)) + svg.CreateAttr("height", fmt.Sprintf("%.2f", height)) + svg.CreateAttr("viewBox", fmt.Sprintf("0 0 %.2f %.2f", width, height)) + return svg +} + +// CreateRect creates a rectangle element +func CreateRect(x, y, width, height float64, fill string) *etree.Element { + rect := etree.NewElement("rect") + rect.CreateAttr("x", fmt.Sprintf("%.2f", x)) + rect.CreateAttr("y", fmt.Sprintf("%.2f", y)) + rect.CreateAttr("width", fmt.Sprintf("%.2f", width)) + rect.CreateAttr("height", fmt.Sprintf("%.2f", height)) + if fill != "" { + rect.CreateAttr("fill", fill) + } + return rect +} + +// CreateText creates a text element +func CreateText(x, y float64, content string) *etree.Element { + text := etree.NewElement("text") + text.CreateAttr("x", fmt.Sprintf("%.2f", x)) + text.CreateAttr("y", fmt.Sprintf("%.2f", y)) + text.SetText(content) + return text +} + +// CreateGroup creates a group element +func CreateGroup() *etree.Element { + return etree.NewElement("g") +} + +// SetFontAttributes sets font-related attributes on an element +func SetFontAttributes(element *etree.Element, family string, size float64) { + if family != "" { + element.CreateAttr("font-family", family) + } + if size > 0 { + element.CreateAttr("font-size", fmt.Sprintf("%.2fpx", size)) + } +} + +// SetTextAttributes sets text-related attributes +func SetTextAttributes(element *etree.Element, fill, textAnchor string) { + if fill != "" { + element.CreateAttr("fill", fill) + } + if textAnchor != "" { + element.CreateAttr("text-anchor", textAnchor) + } +} + +// AddStyle adds a style attribute to an element +func AddStyle(element *etree.Element, style string) { + existing := element.SelectAttrValue("style", "") + if existing != "" { + style = existing + "; " + style + } + element.CreateAttr("style", style) +} + +// Max returns the maximum of two float64 values +func Max(a, b float64) float64 { + if a > b { + return a + } + return b +} + +// Min returns the minimum of two float64 values +func Min(a, b float64) float64 { + if a < b { + return a + } + return b +} + +// Clamp constrains a value between min and max +func Clamp(value, min, max float64) float64 { + if value < min { + return min + } + if value > max { + return max + } + return value +}