initial commit

This commit is contained in:
2025-07-18 19:02:23 +08:00
commit d2630e4503
43 changed files with 8014 additions and 0 deletions

264
.gitignore vendored Normal file
View File

@@ -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

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
.idea/freezelib.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/freezelib.iml" filepath="$PROJECT_DIR$/.idea/freezelib.iml" />
</modules>
</component>
</project>

18
LICENSE Normal file
View File

@@ -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.

287
README.md Normal file
View File

@@ -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.

287
README_CN.md Normal file
View File

@@ -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 团队创造了如此美观的工具。

264
USAGE.md Normal file
View File

@@ -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 文件,展示库的各种功能。

264
USAGE_EN.md Normal file
View File

@@ -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.

299
ansi.go Normal file
View File

@@ -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",
}

242
config.go Normal file
View File

@@ -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
)

173
examples/01-basic/main.go Normal file
View File

@@ -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)
}

337
examples/02-formats/main.go Normal file
View File

@@ -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 (
<div className="todo-app">
<h1>Todo List</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add a todo..."
/>
<button onClick={addTodo}>Add</button>
</div>
);
};
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())
}
}

395
examples/03-themes/main.go Normal file
View File

@@ -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 := `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Web App</title>
<style>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 1.5rem;
margin-bottom: 1rem;
}
.btn-primary {
background: #007bff;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Our App</h1>
<div class="card">
<h2>Features</h2>
<p>This is a modern web application with responsive design.</p>
<button class="btn-primary">Get Started</button>
</div>
</div>
</body>
</html>`
// 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<i32> = 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
}

View File

@@ -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<String> names = List.of("Alice", "Bob", "Charlie", "David");
// Use streams to filter and transform
List<String> 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<Product>
{
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",
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task Manager</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Task Manager</h1>
<div class="task-form">
<input type="text" id="taskInput" placeholder="Add a new task...">
<button id="addTask">Add</button>
</div>
<ul id="taskList" class="task-list"></ul>
<div class="stats">
<p>Total tasks: <span id="totalTasks">0</span></p>
<p>Completed: <span id="completedTasks">0</span></p>
</div>
</div>
<script src="app.js"></script>
</body>
</html>`,
"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 =
<span onclick="toggleTask(${task.id})">${task.text}</span>
<button onclick="deleteTask(${task.id})">Delete</button>
;
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<T> {
getById(id: string): Promise<T>;
getAll(): Promise<T[]>;
create(item: T): Promise<T>;
update(id: string, item: T): Promise<T>;
delete(id: string): Promise<boolean>;
}
interface User {
id?: string;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
createdAt?: Date;
}
class UserRepository implements Repository<User> {
private users: Map<string, User> = new Map();
async getById(id: string): Promise<User> {
const user = this.users.get(id);
if (!user) {
throw new Error(User with id ${id} not found);
}
return user;
}
async getAll(): Promise<User[]> {
return Array.from(this.users.values());
}
async create(user: User): Promise<User> {
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<User> {
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<boolean> {
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)
}
}

View File

@@ -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.<anonymous> (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 <file>..." 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 <file>..." to update what will be committed)
(use "git restore <file>..." 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 <file>..." 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")
}

View File

@@ -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<User | null>;
findByEmail(email: string): Promise<User | null>;
create(user: CreateUserDto): Promise<User>;
update(id: string, updates: UpdateUserDto): Promise<User>;
delete(id: string): Promise<void>;
}
class PostgresUserRepository implements UserRepository {
constructor(private db: Database) {}
async findById(id: string): Promise<User | null> {
const result = await this.db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result.rows[0] || null;
}
async create(user: CreateUserDto): Promise<User> {
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<T: Ord>(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<T: Ord>(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)
}
}

680
examples/07-batch/main.go Normal file
View File

@@ -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 <iostream>
#include <vector>
#include <algorithm>
#include <memory>
template<typename T>
class SmartVector {
private:
std::unique_ptr<T[]> data;
size_t size_;
size_t capacity_;
public:
SmartVector(size_t initial_capacity = 10)
: data(std::make_unique<T[]>(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<T[]>(capacity_);
std::copy(data.get(), data.get() + size_, new_data.get());
data = std::move(new_data);
}
};
int main() {
SmartVector<int> 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<String, usize> {
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"
}
}

113
examples/README.md Normal file
View File

@@ -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`

114
examples/README_CN.md Normal file
View File

@@ -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` 测试

Binary file not shown.

Binary file not shown.

165
font/font.go Normal file
View File

@@ -0,0 +1,165 @@
package font
import (
"embed"
"fmt"
formatter "github.com/alecthomas/chroma/v2/formatters/svg"
)
//go:embed *.ttf
var fonts embed.FS
// JetBrainsMonoTTF contains the JetBrains Mono font data
var JetBrainsMonoTTF []byte
// JetBrainsMonoNLTTF contains the JetBrains Mono NL font data
var JetBrainsMonoNLTTF []byte
func init() {
var err error
JetBrainsMonoTTF, err = fonts.ReadFile("JetBrainsMono-Regular.ttf")
if err != nil {
// If embedded font is not available, use empty slice
JetBrainsMonoTTF = []byte{}
}
JetBrainsMonoNLTTF, err = fonts.ReadFile("JetBrainsMonoNL-Regular.ttf")
if err != nil {
// If embedded font is not available, use empty slice
JetBrainsMonoNLTTF = []byte{}
}
}
// FontOptions creates formatter options for the given font configuration
func FontOptions(family string, size float64, ligatures bool, fontFile string) ([]formatter.Option, error) {
var options []formatter.Option
// Set font family
if family != "" {
options = append(options, formatter.FontFamily(family))
}
// Embed font file if specified
if fontFile != "" {
option, err := formatter.EmbedFontFile(family, fontFile)
if err != nil {
return nil, fmt.Errorf("failed to embed font file: %w", err)
}
options = append(options, option)
}
return options, nil
}
// GetDefaultFontFamily returns the default font family
func GetDefaultFontFamily() string {
return "JetBrains Mono"
}
// GetDefaultFontSize returns the default font size
func GetDefaultFontSize() float64 {
return 14.0
}
// IsMonospaceFont checks if a font family is monospace
func IsMonospaceFont(family string) bool {
monospaceFonts := map[string]bool{
"JetBrains Mono": true,
"Fira Code": true,
"Source Code Pro": true,
"Monaco": true,
"Menlo": true,
"Consolas": true,
"Courier New": true,
"monospace": true,
"SF Mono": true,
"Cascadia Code": true,
"Ubuntu Mono": true,
"DejaVu Sans Mono": true,
"Liberation Mono": true,
"Inconsolata": true,
"Roboto Mono": true,
}
return monospaceFonts[family]
}
// ValidateFontFamily validates if a font family name is valid
func ValidateFontFamily(family string) error {
if family == "" {
return fmt.Errorf("font family cannot be empty")
}
return nil
}
// ValidateFontSize validates if a font size is valid
func ValidateFontSize(size float64) error {
if size <= 0 {
return fmt.Errorf("font size must be positive, got %.2f", size)
}
if size > 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)
}

310
freeze.go Normal file
View File

@@ -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"
)

443
generator.go Normal file
View File

@@ -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
}

29
go.mod Normal file
View File

@@ -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
)

52
go.sum Normal file
View File

@@ -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=

194
presets.go Normal file
View File

@@ -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
}

348
quickfreeze.go Normal file
View File

@@ -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)
}

15
sample/basic_example.svg Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="506.67" height="201.00" xmlns="http://www.w3.org/2000/svg">
<rect width="506.67" height="201.00" fill="#ffffff" x="0.00px" y="0.00px"/>
<g font-family="JetBrains Mono" font-size="14.00px" fill="#-00001" clip-path="url(#terminalMask)">
<text x="20.00px" y="36.80px" xml:space="preserve"><tspan fill="#cf222e">package</tspan> <tspan fill="#1f2328">main</tspan>
</text><text x="20.00px" y="53.60px" xml:space="preserve">
</text><text x="20.00px" y="70.40px" xml:space="preserve"><tspan fill="#cf222e">import</tspan> <tspan fill="#0a3069">&quot;fmt&quot;</tspan>
</text><text x="20.00px" y="87.20px" xml:space="preserve">
</text><text x="20.00px" y="104.00px" xml:space="preserve"><tspan fill="#cf222e">func</tspan> <tspan fill="#6639ba">main</tspan><tspan fill="#1f2328">()</tspan> <tspan fill="#1f2328">{</tspan>
</text><text x="20.00px" y="120.80px" xml:space="preserve">    <tspan fill="#1f2328">fmt</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#6639ba">Println</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&quot;Hello, World!&quot;</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="20.00px" y="137.60px" xml:space="preserve">    <tspan fill="#1f2328">fmt</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#6639ba">Println</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&quot;This is a beautiful code screenshot!&quot;</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="20.00px" y="154.40px" xml:space="preserve"><tspan fill="#1f2328">}</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="553.57" height="414.29" xmlns="http://www.w3.org/2000/svg">
<rect width="521.57" height="382.29" fill="#272822" rx="10.00" ry="10.00" filter="url(#shadow)" stroke="#444444" stroke-width="1.00" x="15.00px" y="15.00px"/>
<g font-family="Cascadia Code" font-size="15.00px" fill="#f8f8f2" clip-path="url(#terminalMask)">
<text x="40.00px" y="73.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 1 </tspan><tspan fill="#75715e">#include</tspan> <tspan fill="#75715e">&lt;iostream&gt;</tspan><tspan fill="#75715e">
</tspan></text><text x="40.00px" y="91.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 2 </tspan><tspan fill="#75715e">#include</tspan> <tspan fill="#75715e">&lt;vector&gt;</tspan><tspan fill="#75715e">
</tspan></text><text x="40.00px" y="109.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 3 </tspan><tspan fill="#75715e">#include</tspan> <tspan fill="#75715e">&lt;algorithm&gt;</tspan><tspan fill="#75715e">
</tspan></text><text x="40.00px" y="127.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 4 </tspan><tspan fill="#75715e"/>
</text><text x="40.00px" y="145.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 5 </tspan><tspan fill="#66d9ef">int</tspan> <tspan fill="#a6e22e">main</tspan>() {
</text><text x="40.00px" y="163.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 6 </tspan>    std<tspan fill="#f92672">::</tspan>vector<tspan fill="#f92672">&lt;</tspan><tspan fill="#66d9ef">int</tspan><tspan fill="#f92672">&gt;</tspan> numbers <tspan fill="#f92672">=</tspan> {<tspan fill="#ae81ff">5</tspan>, <tspan fill="#ae81ff">2</tspan>, <tspan fill="#ae81ff">8</tspan>, <tspan fill="#ae81ff">1</tspan>, <tspan fill="#ae81ff">9</tspan>};
</text><text x="40.00px" y="181.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 7 </tspan>    
</text><text x="40.00px" y="199.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 8 </tspan>    std<tspan fill="#f92672">::</tspan>sort(numbers.begin(), numbers.end());
</text><text x="40.00px" y="217.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 9 </tspan>    
</text><text x="40.00px" y="235.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 10 </tspan>    std<tspan fill="#f92672">::</tspan>cout <tspan fill="#f92672">&lt;&lt;</tspan> <tspan fill="#e6db74">&quot;Sorted numbers: &quot;</tspan>;
</text><text x="40.00px" y="253.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 11 </tspan>    <tspan fill="#66d9ef">for</tspan> (<tspan fill="#66d9ef">const</tspan> <tspan fill="#66d9ef">auto</tspan><tspan fill="#f92672">&amp;</tspan> num : numbers) {
</text><text x="40.00px" y="271.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 12 </tspan>        std<tspan fill="#f92672">::</tspan>cout <tspan fill="#f92672">&lt;&lt;</tspan> num <tspan fill="#f92672">&lt;&lt;</tspan> <tspan fill="#e6db74">&quot; &quot;</tspan>;
</text><text x="40.00px" y="289.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 13 </tspan>    }
</text><text x="40.00px" y="307.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 14 </tspan>    std<tspan fill="#f92672">::</tspan>cout <tspan fill="#f92672">&lt;&lt;</tspan> std<tspan fill="#f92672">::</tspan>endl;
</text><text x="40.00px" y="325.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 15 </tspan>    
</text><text x="40.00px" y="343.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 16 </tspan>    <tspan fill="#66d9ef">return</tspan> <tspan fill="#ae81ff">0</tspan>;
</text><text x="40.00px" y="361.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 17 </tspan>}</text>
</g>
<svg x="15.00px" y="15.00px"><circle cx="13.50" cy="12.00" r="5.50" fill="#FF5A54"/><circle cx="32.50" cy="12.00" r="5.50" fill="#E6BF29"/><circle cx="51.50" cy="12.00" r="5.50" fill="#52C12B"/></svg><defs><filter id="shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="20.00"/><feOffset result="offsetblur" dx="0.00" dy="10.00"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="800.00" height="538.67" xmlns="http://www.w3.org/2000/svg">
<rect width="708.00" height="494.67" fill="#ffffff" rx="12.00" ry="12.00" filter="url(#shadow)" stroke="#d1d9e0" stroke-width="2.00" x="20.00px" y="20.00px"/>
<g font-family="JetBrains Mono" font-size="16.00px" fill="#-00001" clip-path="url(#terminalMask)">
<text x="50.00px" y="87.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 1 </tspan><tspan fill="#cf222e">import</tspan> <tspan fill="#24292e">numpy</tspan> <tspan fill="#cf222e">as</tspan> <tspan fill="#24292e">np</tspan>
</text><text x="50.00px" y="109.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 2 </tspan><tspan fill="#cf222e">import</tspan> <tspan fill="#24292e">matplotlib.pyplot</tspan> <tspan fill="#cf222e">as</tspan> <tspan fill="#24292e">plt</tspan>
</text><text x="50.00px" y="132.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 3 </tspan>
</text><text x="50.00px" y="154.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 4 </tspan><tspan fill="#cf222e">def</tspan> <tspan fill="#6639ba">plot_sine_wave</tspan><tspan fill="#1f2328">():</tspan>
</text><text x="50.00px" y="177.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 5 </tspan>    x <tspan fill="#0550ae">=</tspan> np<tspan fill="#0550ae">.</tspan>linspace<tspan fill="#1f2328">(</tspan><tspan fill="#0550ae">0</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#0550ae">2</tspan> <tspan fill="#0550ae">*</tspan> np<tspan fill="#0550ae">.</tspan>pi<tspan fill="#1f2328">,</tspan> <tspan fill="#0550ae">100</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="50.00px" y="199.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 6 </tspan>    y <tspan fill="#0550ae">=</tspan> np<tspan fill="#0550ae">.</tspan>sin<tspan fill="#1f2328">(</tspan>x<tspan fill="#1f2328">)</tspan>
</text><text x="50.00px" y="221.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 7 </tspan>    
</text><text x="50.00px" y="244.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 8 </tspan>    plt<tspan fill="#0550ae">.</tspan>figure<tspan fill="#1f2328">(</tspan>figsize<tspan fill="#0550ae">=</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0550ae">10</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#0550ae">6</tspan><tspan fill="#1f2328">))</tspan>
</text><text x="50.00px" y="266.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 9 </tspan>    plt<tspan fill="#0550ae">.</tspan>plot<tspan fill="#1f2328">(</tspan>x<tspan fill="#1f2328">,</tspan> y<tspan fill="#1f2328">,</tspan> <tspan fill="#0a3069">&apos;b-&apos;</tspan><tspan fill="#1f2328">,</tspan> linewidth<tspan fill="#0550ae">=</tspan><tspan fill="#0550ae">2</tspan><tspan fill="#1f2328">,</tspan> label<tspan fill="#0550ae">=</tspan><tspan fill="#0a3069">&apos;sin(x)&apos;</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="50.00px" y="289.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 10 </tspan>    plt<tspan fill="#0550ae">.</tspan>xlabel<tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;x&apos;</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="50.00px" y="311.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 11 </tspan>    plt<tspan fill="#0550ae">.</tspan>ylabel<tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;sin(x)&apos;</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="50.00px" y="333.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 12 </tspan>    plt<tspan fill="#0550ae">.</tspan>title<tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;Sine Wave&apos;</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="50.00px" y="356.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 13 </tspan>    plt<tspan fill="#0550ae">.</tspan>grid<tspan fill="#1f2328">(</tspan><tspan fill="#cf222e">True</tspan><tspan fill="#1f2328">,</tspan> alpha<tspan fill="#0550ae">=</tspan><tspan fill="#0550ae">0.3</tspan><tspan fill="#1f2328">)</tspan>
</text><text x="50.00px" y="378.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 14 </tspan>    plt<tspan fill="#0550ae">.</tspan>legend<tspan fill="#1f2328">()</tspan>
</text><text x="50.00px" y="401.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 15 </tspan>    plt<tspan fill="#0550ae">.</tspan>show<tspan fill="#1f2328">()</tspan>
</text><text x="50.00px" y="423.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 16 </tspan>
</text><text x="50.00px" y="445.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 17 </tspan><tspan fill="#cf222e">if</tspan> <tspan fill="#953800">__name__</tspan> <tspan fill="#0550ae">==</tspan> <tspan fill="#0a3069">&quot;__main__&quot;</tspan><tspan fill="#1f2328">:</tspan>
</text><text x="50.00px" y="468.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 18 </tspan>    plot_sine_wave<tspan fill="#1f2328">()</tspan></text>
</g>
<svg x="20.00px" y="20.00px"><circle cx="13.50" cy="12.00" r="5.50" fill="#FF5A54"/><circle cx="32.50" cy="12.00" r="5.50" fill="#E6BF29"/><circle cx="51.50" cy="12.00" r="5.50" fill="#52C12B"/></svg><defs><filter id="shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="25.00"/><feOffset result="offsetblur" dx="0.00" dy="15.00"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><defs><clipPath id="terminalMask"><rect x="20.00" y="20.00" width="708.00" height="464.67"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

19
sample/file_example.svg Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="635.62" height="444.00" xmlns="http://www.w3.org/2000/svg">
<rect width="571.62" height="380.00" fill="#ffffff" rx="12.00" ry="12.00" filter="url(#shadow)" stroke="#e1e4e8" stroke-width="2.00" x="30.00px" y="30.00px"/>
<g font-family="JetBrains Mono" font-size="16.00px" fill="#-00001" clip-path="url(#terminalMask)">
<text x="70.00px" y="107.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 1 </tspan><tspan fill="#cf222e">use</tspan><tspan fill="#ffffff"> </tspan>std::collections::HashMap<tspan fill="#1f2328">;</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="129.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 2 </tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="152.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 3 </tspan><tspan fill="#ffffff"/><tspan fill="#cf222e">fn</tspan> <tspan fill="#6639ba">main</tspan><tspan fill="#1f2328">()</tspan><tspan fill="#ffffff"> </tspan><tspan fill="#1f2328">{</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="174.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 4 </tspan><tspan fill="#ffffff">    </tspan><tspan fill="#cf222e">let</tspan><tspan fill="#ffffff"> </tspan><tspan fill="#cf222e">mut</tspan><tspan fill="#ffffff"> </tspan>scores<tspan fill="#ffffff"> </tspan><tspan fill="#0550ae">=</tspan><tspan fill="#ffffff"> </tspan>HashMap::new<tspan fill="#1f2328">();</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="197.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 5 </tspan><tspan fill="#ffffff">    
</tspan></text><text x="70.00px" y="219.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 6 </tspan><tspan fill="#ffffff">    </tspan>scores<tspan fill="#1f2328">.</tspan>insert<tspan fill="#1f2328">(</tspan><tspan fill="#6639ba">String</tspan>::from<tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&quot;Blue&quot;</tspan><tspan fill="#1f2328">),</tspan><tspan fill="#ffffff"> </tspan><tspan fill="#0550ae">10</tspan><tspan fill="#1f2328">);</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="241.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 7 </tspan><tspan fill="#ffffff">    </tspan>scores<tspan fill="#1f2328">.</tspan>insert<tspan fill="#1f2328">(</tspan><tspan fill="#6639ba">String</tspan>::from<tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&quot;Yellow&quot;</tspan><tspan fill="#1f2328">),</tspan><tspan fill="#ffffff"> </tspan><tspan fill="#0550ae">50</tspan><tspan fill="#1f2328">);</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="264.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 8 </tspan><tspan fill="#ffffff">    
</tspan></text><text x="70.00px" y="286.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 9 </tspan><tspan fill="#ffffff">    </tspan><tspan fill="#cf222e">for</tspan><tspan fill="#ffffff"> </tspan><tspan fill="#1f2328">(</tspan>key<tspan fill="#1f2328">,</tspan><tspan fill="#ffffff"> </tspan>value<tspan fill="#1f2328">)</tspan><tspan fill="#ffffff"> </tspan><tspan fill="#cf222e">in</tspan><tspan fill="#ffffff"> </tspan><tspan fill="#0550ae">&amp;</tspan>scores<tspan fill="#ffffff"> </tspan><tspan fill="#1f2328">{</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="309.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 10 </tspan><tspan fill="#ffffff">        </tspan><tspan fill="#6639ba">println!</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&quot;</tspan><tspan fill="#0a3069">{}</tspan><tspan fill="#0a3069">: </tspan><tspan fill="#0a3069">{}</tspan><tspan fill="#0a3069">&quot;</tspan><tspan fill="#1f2328">,</tspan><tspan fill="#ffffff"> </tspan>key<tspan fill="#1f2328">,</tspan><tspan fill="#ffffff"> </tspan>value<tspan fill="#1f2328">);</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="331.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 11 </tspan><tspan fill="#ffffff">    </tspan><tspan fill="#1f2328">}</tspan><tspan fill="#ffffff">
</tspan></text><text x="70.00px" y="353.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 12 </tspan><tspan fill="#ffffff"/><tspan fill="#1f2328">}</tspan></text>
</g>
<svg x="30.00px" y="30.00px"><circle cx="13.50" cy="12.00" r="5.50" fill="#FF5A54"/><circle cx="32.50" cy="12.00" r="5.50" fill="#E6BF29"/><circle cx="51.50" cy="12.00" r="5.50" fill="#52C12B"/></svg><defs><filter id="shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="30.00"/><feOffset result="offsetblur" dx="0.00" dy="15.00"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

329
sample/main.go Normal file
View File

@@ -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 <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> 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")
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="463.33" height="274.00" xmlns="http://www.w3.org/2000/svg">
<rect width="431.33" height="242.00" fill="#282a36" rx="8.00" ry="8.00" filter="url(#shadow)" stroke="#3c3c3c" stroke-width="1.00" x="15.00px" y="15.00px"/>
<g font-family="Fira Code" font-size="14.00px" fill="#f8f8f2" clip-path="url(#terminalMask)">
<text x="40.00px" y="56.80px" xml:space="preserve"><tspan fill="#ff79c6">const</tspan> express <tspan fill="#ff79c6">=</tspan> require(<tspan fill="#f1fa8c">&apos;express&apos;</tspan>);
</text><text x="40.00px" y="73.60px" xml:space="preserve"><tspan fill="#ff79c6">const</tspan> app <tspan fill="#ff79c6">=</tspan> express();
</text><text x="40.00px" y="90.40px" xml:space="preserve">
</text><text x="40.00px" y="107.20px" xml:space="preserve">app.get(<tspan fill="#f1fa8c">&apos;/&apos;</tspan>, (req, res) =&gt; {
</text><text x="40.00px" y="124.00px" xml:space="preserve">  res.json({ message<tspan fill="#ff79c6">:</tspan> <tspan fill="#f1fa8c">&apos;Hello, World!&apos;</tspan> });
</text><text x="40.00px" y="140.80px" xml:space="preserve">});
</text><text x="40.00px" y="157.60px" xml:space="preserve">
</text><text x="40.00px" y="174.40px" xml:space="preserve">app.listen(<tspan fill="#bd93f9">3000</tspan>, () =&gt; {
</text><text x="40.00px" y="191.20px" xml:space="preserve">  console.log(<tspan fill="#f1fa8c">&apos;Server running on port 3000&apos;</tspan>);
</text><text x="40.00px" y="208.00px" xml:space="preserve">});</text>
</g>
<defs><filter id="shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="20.00"/><feOffset result="offsetblur" dx="0.00" dy="8.00"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="463.33" height="274.00" xmlns="http://www.w3.org/2000/svg">
<rect width="431.33" height="242.00" fill="#ffffff" rx="8.00" ry="8.00" filter="url(#shadow)" stroke="#d1d5da" stroke-width="1.00" x="15.00px" y="15.00px"/>
<g font-family="SF Mono" font-size="14.00px" fill="#-00001" clip-path="url(#terminalMask)">
<text x="40.00px" y="56.80px" xml:space="preserve"><tspan fill="#cf222e">const</tspan> <tspan fill="#1f2328">express</tspan> <tspan fill="#0550ae">=</tspan> <tspan fill="#1f2328">require</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;express&apos;</tspan><tspan fill="#1f2328">);</tspan>
</text><text x="40.00px" y="73.60px" xml:space="preserve"><tspan fill="#cf222e">const</tspan> <tspan fill="#1f2328">app</tspan> <tspan fill="#0550ae">=</tspan> <tspan fill="#1f2328">express</tspan><tspan fill="#1f2328">();</tspan>
</text><text x="40.00px" y="90.40px" xml:space="preserve">
</text><text x="40.00px" y="107.20px" xml:space="preserve"><tspan fill="#1f2328">app</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">get</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;/&apos;</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#1f2328">(</tspan><tspan fill="#1f2328">req</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#1f2328">res</tspan><tspan fill="#1f2328">)</tspan> <tspan fill="#1f2328">=&gt;</tspan> <tspan fill="#1f2328">{</tspan>
</text><text x="40.00px" y="124.00px" xml:space="preserve">  <tspan fill="#1f2328">res</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">json</tspan><tspan fill="#1f2328">({</tspan> <tspan fill="#1f2328">message</tspan><tspan fill="#0550ae">:</tspan> <tspan fill="#0a3069">&apos;Hello, World!&apos;</tspan> <tspan fill="#1f2328">});</tspan>
</text><text x="40.00px" y="140.80px" xml:space="preserve"><tspan fill="#1f2328">});</tspan>
</text><text x="40.00px" y="157.60px" xml:space="preserve">
</text><text x="40.00px" y="174.40px" xml:space="preserve"><tspan fill="#1f2328">app</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">listen</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0550ae">3000</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#1f2328">()</tspan> <tspan fill="#1f2328">=&gt;</tspan> <tspan fill="#1f2328">{</tspan>
</text><text x="40.00px" y="191.20px" xml:space="preserve">  <tspan fill="#1f2328">console</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">log</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;Server running on port 3000&apos;</tspan><tspan fill="#1f2328">);</tspan>
</text><text x="40.00px" y="208.00px" xml:space="preserve"><tspan fill="#1f2328">});</tspan></text>
</g>
<defs><filter id="shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="20.00"/><feOffset result="offsetblur" dx="0.00" dy="8.00"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="403.33" height="214.00" xmlns="http://www.w3.org/2000/svg">
<rect width="403.33" height="214.00" fill="#ffffff" x="0.00px" y="0.00px"/>
<g font-family="JetBrains Mono" font-size="14.00px" fill="#-00001" clip-path="url(#terminalMask)">
<text x="10.00px" y="26.80px" xml:space="preserve"><tspan fill="#cf222e">const</tspan> <tspan fill="#1f2328">express</tspan> <tspan fill="#0550ae">=</tspan> <tspan fill="#1f2328">require</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;express&apos;</tspan><tspan fill="#1f2328">);</tspan>
</text><text x="10.00px" y="43.60px" xml:space="preserve"><tspan fill="#cf222e">const</tspan> <tspan fill="#1f2328">app</tspan> <tspan fill="#0550ae">=</tspan> <tspan fill="#1f2328">express</tspan><tspan fill="#1f2328">();</tspan>
</text><text x="10.00px" y="60.40px" xml:space="preserve">
</text><text x="10.00px" y="77.20px" xml:space="preserve"><tspan fill="#1f2328">app</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">get</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;/&apos;</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#1f2328">(</tspan><tspan fill="#1f2328">req</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#1f2328">res</tspan><tspan fill="#1f2328">)</tspan> <tspan fill="#1f2328">=&gt;</tspan> <tspan fill="#1f2328">{</tspan>
</text><text x="10.00px" y="94.00px" xml:space="preserve">  <tspan fill="#1f2328">res</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">json</tspan><tspan fill="#1f2328">({</tspan> <tspan fill="#1f2328">message</tspan><tspan fill="#0550ae">:</tspan> <tspan fill="#0a3069">&apos;Hello, World!&apos;</tspan> <tspan fill="#1f2328">});</tspan>
</text><text x="10.00px" y="110.80px" xml:space="preserve"><tspan fill="#1f2328">});</tspan>
</text><text x="10.00px" y="127.60px" xml:space="preserve">
</text><text x="10.00px" y="144.40px" xml:space="preserve"><tspan fill="#1f2328">app</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">listen</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0550ae">3000</tspan><tspan fill="#1f2328">,</tspan> <tspan fill="#1f2328">()</tspan> <tspan fill="#1f2328">=&gt;</tspan> <tspan fill="#1f2328">{</tspan>
</text><text x="10.00px" y="161.20px" xml:space="preserve">  <tspan fill="#1f2328">console</tspan><tspan fill="#1f2328">.</tspan><tspan fill="#1f2328">log</tspan><tspan fill="#1f2328">(</tspan><tspan fill="#0a3069">&apos;Server running on port 3000&apos;</tspan><tspan fill="#1f2328">);</tspan>
</text><text x="10.00px" y="178.00px" xml:space="preserve"><tspan fill="#1f2328">});</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="388.57" height="226.29" xmlns="http://www.w3.org/2000/svg">
<rect width="364.57" height="202.29" fill="#272822" stroke="#00ff00" stroke-width="2.00" x="10.00px" y="10.00px"/>
<g font-family="Courier New" font-size="12.00px" fill="#f8f8f2" clip-path="url(#terminalMask)">
<text x="30.00px" y="44.40px" xml:space="preserve"><tspan fill="#66d9ef">const</tspan> <tspan fill="#a6e22e">express</tspan> <tspan fill="#f92672">=</tspan> <tspan fill="#a6e22e">require</tspan>(<tspan fill="#e6db74">&apos;express&apos;</tspan>);
</text><text x="30.00px" y="58.80px" xml:space="preserve"><tspan fill="#66d9ef">const</tspan> <tspan fill="#a6e22e">app</tspan> <tspan fill="#f92672">=</tspan> <tspan fill="#a6e22e">express</tspan>();
</text><text x="30.00px" y="73.20px" xml:space="preserve">
</text><text x="30.00px" y="87.60px" xml:space="preserve"><tspan fill="#a6e22e">app</tspan>.<tspan fill="#a6e22e">get</tspan>(<tspan fill="#e6db74">&apos;/&apos;</tspan>, (<tspan fill="#a6e22e">req</tspan>, <tspan fill="#a6e22e">res</tspan>) =&gt; {
</text><text x="30.00px" y="102.00px" xml:space="preserve">  <tspan fill="#a6e22e">res</tspan>.<tspan fill="#a6e22e">json</tspan>({ <tspan fill="#a6e22e">message</tspan><tspan fill="#f92672">:</tspan> <tspan fill="#e6db74">&apos;Hello, World!&apos;</tspan> });
</text><text x="30.00px" y="116.40px" xml:space="preserve">});
</text><text x="30.00px" y="130.80px" xml:space="preserve">
</text><text x="30.00px" y="145.20px" xml:space="preserve"><tspan fill="#a6e22e">app</tspan>.<tspan fill="#a6e22e">listen</tspan>(<tspan fill="#ae81ff">3000</tspan>, () =&gt; {
</text><text x="30.00px" y="159.60px" xml:space="preserve">  <tspan fill="#a6e22e">console</tspan>.<tspan fill="#a6e22e">log</tspan>(<tspan fill="#e6db74">&apos;Server running on port 3000&apos;</tspan>);
</text><text x="30.00px" y="174.00px" xml:space="preserve">});</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="507.00" height="218.00" xmlns="http://www.w3.org/2000/svg">
<rect width="507.00" height="218.00" fill="#282a36" filter="url(#shadow)" x="0.00px" y="0.00px"/>
<g font-family="Fira Code" font-size="14.00px" fill="#f8f8f2" clip-path="url(#terminalMask)">
<text x="20.00px" y="51.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 1 </tspan><tspan fill="#8be9fd" font-style="italic">function</tspan> fibonacci(n) {
</text><text x="20.00px" y="68.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 2 </tspan>    <tspan fill="#ff79c6">if</tspan> (n <tspan fill="#ff79c6">&lt;=</tspan> <tspan fill="#bd93f9">1</tspan>) <tspan fill="#ff79c6">return</tspan> n;
</text><text x="20.00px" y="85.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 3 </tspan>    <tspan fill="#ff79c6">return</tspan> fibonacci(n <tspan fill="#ff79c6">-</tspan> <tspan fill="#bd93f9">1</tspan>) <tspan fill="#ff79c6">+</tspan> fibonacci(n <tspan fill="#ff79c6">-</tspan> <tspan fill="#bd93f9">2</tspan>);
</text><text x="20.00px" y="102.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 4 </tspan>}
</text><text x="20.00px" y="119.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 5 </tspan>
</text><text x="20.00px" y="135.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 6 </tspan>console.log(<tspan fill="#f1fa8c">&apos;Fibonacci sequence:&apos;</tspan>);
</text><text x="20.00px" y="152.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 7 </tspan><tspan fill="#ff79c6">for</tspan> (<tspan fill="#8be9fd" font-style="italic">let</tspan> i <tspan fill="#ff79c6">=</tspan> <tspan fill="#bd93f9">0</tspan>; i <tspan fill="#ff79c6">&lt;</tspan> <tspan fill="#bd93f9">10</tspan>; i<tspan fill="#ff79c6">++</tspan>) {
</text><text x="20.00px" y="169.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 8 </tspan>    console.log(<tspan fill="#f1fa8c">&apos;F(&apos;</tspan> <tspan fill="#ff79c6">+</tspan> i <tspan fill="#ff79c6">+</tspan> <tspan fill="#f1fa8c">&apos;) = &apos;</tspan> <tspan fill="#ff79c6">+</tspan> fibonacci(i));
</text><text x="20.00px" y="186.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#7f7f7f"> 9 </tspan>}</text>
</g>
<svg x="0.00px" y="0.00px"><circle cx="13.50" cy="12.00" r="5.50" fill="#FF5A54"/><circle cx="32.50" cy="12.00" r="5.50" fill="#E6BF29"/><circle cx="51.50" cy="12.00" r="5.50" fill="#52C12B"/></svg><defs><filter id="shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="20.00"/><feOffset result="offsetblur" dx="0.00" dy="10.00"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

12
sample/sample.rs Normal file
View File

@@ -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);
}
}

81
sample/simple_test.go Normal file
View File

@@ -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)
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="436.90" height="152.14" xmlns="http://www.w3.org/2000/svg">
<rect width="414.90" height="130.14" fill="#0d1117" rx="6.00" ry="6.00" filter="url(#shadow)" stroke="#30363d" stroke-width="1.00" x="10.00px" y="10.00px"/>
<g font-family="JetBrains Mono" font-size="13.00px" fill="#e6edf3" clip-path="url(#terminalMask)">
<text x="25.00px" y="40.60px" xml:space="preserve"><tspan xml:space="preserve" fill="#00FF00">✓ SUCCESS</tspan><tspan xml:space="preserve">: Build completed successfully</tspan></text><text x="25.00px" y="56.20px" xml:space="preserve"><tspan xml:space="preserve" fill="#FFFF00">⚠ WARNING</tspan><tspan xml:space="preserve">: Deprecated function used in main.go:42</tspan></text><text x="25.00px" y="71.80px" xml:space="preserve"><tspan xml:space="preserve" fill="#FF0000">✗ ERROR</tspan><tspan xml:space="preserve">: File not found: config.json</tspan></text><text x="25.00px" y="87.40px" xml:space="preserve"><tspan xml:space="preserve" fill="#00FFFF">INFO</tspan><tspan xml:space="preserve">: Starting server on port 8080</tspan></text><text x="25.00px" y="103.00px" xml:space="preserve"><tspan xml:space="preserve" fill="#FF00FF">DEBUG</tspan><tspan xml:space="preserve">: Loading configuration from ~/.config/app</tspan></text>
</g>
<defs><filter id="shadow" filterUnits="userSpaceOnUse"><feGaussianBlur in="SourceAlpha" stdDeviation="15.00"/><feOffset result="offsetblur" dx="0.00" dy="5.00"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

220
svg/svg.go Normal file
View File

@@ -0,0 +1,220 @@
package svg
import (
"fmt"
"strconv"
"strings"
"github.com/beevik/etree"
)
// AddShadow adds a definition of a shadow to the <defs> 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 <defs> 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
}