✨ initial commit
264
.gitignore
vendored
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
896
examples/04-languages/main.go
Normal 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)
|
||||
}
|
||||
}
|
411
examples/05-terminal/main.go
Normal 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")
|
||||
}
|
587
examples/06-advanced/main.go
Normal 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
@@ -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
@@ -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
@@ -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` 测试
|
||||
|
BIN
font/JetBrainsMono-Regular.ttf
Normal file
BIN
font/JetBrainsMonoNL-Regular.ttf
Normal file
165
font/font.go
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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">"fmt"</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">"Hello, World!"</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">"This is a beautiful code screenshot!"</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 |
24
sample/chained_example.svg
Normal 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"><iostream></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"><vector></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"><algorithm></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"><</tspan><tspan fill="#66d9ef">int</tspan><tspan fill="#f92672">></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"><<</tspan> <tspan fill="#e6db74">"Sorted numbers: "</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">&</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"><<</tspan> num <tspan fill="#f92672"><<</tspan> <tspan fill="#e6db74">" "</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"><<</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 |
25
sample/custom_config_example.svg
Normal 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">'b-'</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">'sin(x)'</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">'x'</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">'sin(x)'</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">'Sine Wave'</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">"__main__"</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
@@ -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">"Blue"</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">"Yellow"</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">&</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">"</tspan><tspan fill="#0a3069">{}</tspan><tspan fill="#0a3069">: </tspan><tspan fill="#0a3069">{}</tspan><tspan fill="#0a3069">"</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
@@ -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")
|
||||
}
|
17
sample/preset_dark_example.svg
Normal 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">'express'</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">'/'</tspan>, (req, res) => {
|
||||
</text><text x="40.00px" y="124.00px" xml:space="preserve"> res.json({ message<tspan fill="#ff79c6">:</tspan> <tspan fill="#f1fa8c">'Hello, World!'</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>, () => {
|
||||
</text><text x="40.00px" y="191.20px" xml:space="preserve"> console.log(<tspan fill="#f1fa8c">'Server running on port 3000'</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 |
17
sample/preset_light_example.svg
Normal 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">'express'</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">'/'</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">=></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">'Hello, World!'</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">=></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">'Server running on port 3000'</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 |
17
sample/preset_minimal_example.svg
Normal 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">'express'</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">'/'</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">=></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">'Hello, World!'</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">=></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">'Server running on port 3000'</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 |
17
sample/preset_retro_example.svg
Normal 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">'express'</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">'/'</tspan>, (<tspan fill="#a6e22e">req</tspan>, <tspan fill="#a6e22e">res</tspan>) => {
|
||||
</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">'Hello, World!'</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>, () => {
|
||||
</text><text x="30.00px" y="159.60px" xml:space="preserve"> <tspan fill="#a6e22e">console</tspan>.<tspan fill="#a6e22e">log</tspan>(<tspan fill="#e6db74">'Server running on port 3000'</tspan>);
|
||||
</text><text x="30.00px" y="174.00px" xml:space="preserve">});</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
16
sample/quickfreeze_example.svg
Normal 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"><=</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">'Fibonacci sequence:'</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"><</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">'F('</tspan> <tspan fill="#ff79c6">+</tspan> i <tspan fill="#ff79c6">+</tspan> <tspan fill="#f1fa8c">') = '</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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
8
sample/terminal_example.svg
Normal 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
@@ -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
|
||||
}
|