Files
go-pixelnebula/cache/compress.go
2025-04-18 17:24:18 +08:00

254 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cache
import (
"bytes"
"compress/gzip"
"io"
"strings"
"sync"
)
var (
// 字符串构建器对象池
optimizeBuilderPool = sync.Pool{
New: func() interface{} {
return new(strings.Builder)
},
}
// Gzip Writer对象池
gzipWriterPool = sync.Pool{
New: func() interface{} {
writer, _ := gzip.NewWriterLevel(nil, gzip.BestCompression)
return writer
},
}
// Gzip Reader对象池
gzipReaderPool = sync.Pool{
New: func() interface{} {
return new(gzip.Reader)
},
}
// 字节缓冲区对象池
bytesBufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
)
// CompressOptions 压缩配置选项
type CompressOptions struct {
Enabled bool // 是否启用压缩
Level int // 压缩级别,范围从-2(不压缩)到9(最高压缩)
MinSize int // 最小压缩大小小于此大小的SVG不压缩
CompressionRatio float64 // 最小压缩比率,压缩后大小/原始大小,小于此比率才使用压缩结果
}
// DefaultCompressOptions 默认压缩配置
var DefaultCompressOptions = CompressOptions{
Enabled: true,
Level: 9, // 默认使用最高压缩级别
MinSize: 1024, // 默认最小压缩大小为1KB
CompressionRatio: 0.8, // 默认最小压缩比率为0.8
}
// CompressSVG 压缩SVG字符串
func CompressSVG(svg string, options CompressOptions) ([]byte, bool) {
if !options.Enabled {
return []byte(svg), false
}
// 如果SVG太小不进行压缩
if len(svg) < options.MinSize {
return []byte(svg), false
}
// 从对象池获取缓冲区
buf := bytesBufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer bytesBufferPool.Put(buf)
// 从对象池获取gzip写入器
writer := gzipWriterPool.Get().(*gzip.Writer)
writer.Reset(buf)
defer gzipWriterPool.Put(writer)
// 写入数据
if _, err := writer.Write([]byte(svg)); err != nil {
return []byte(svg), false
}
// 关闭写入器
if err := writer.Close(); err != nil {
return []byte(svg), false
}
// 获取压缩后的数据
compressed := make([]byte, buf.Len())
copy(compressed, buf.Bytes())
// 计算压缩比率
ratio := float64(len(compressed)) / float64(len(svg))
if ratio > options.CompressionRatio {
// 压缩效果不好,返回原始数据
return []byte(svg), false
}
return compressed, true
}
// DecompressSVG 解压缩SVG数据
func DecompressSVG(data []byte, isCompressed bool) (string, error) {
// 如果数据未压缩,直接返回字符串
if !isCompressed {
return string(data), nil
}
// 检查数据是否为gzip格式
if !isGzipped(data) {
return string(data), nil
}
// 获取缓冲区
buf := bytesBufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer bytesBufferPool.Put(buf)
// 创建Reader
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return string(data), err
}
defer reader.Close()
// 读取解压缩后的数据
if _, err := io.Copy(buf, reader); err != nil {
return string(data), err
}
return buf.String(), nil
}
// isGzipped 检查数据是否为gzip格式
func isGzipped(data []byte) bool {
// gzip文件的魔数是0x1f 0x8b
return len(data) > 2 && data[0] == 0x1f && data[1] == 0x8b
}
// OptimizeSVG 优化SVG字符串移除不必要的空白和注释
func OptimizeSVG(svg string) string {
// 如果SVG太小不进行优化
if len(svg) < 100 {
return svg
}
// 移除XML注释
svg = removeXMLComments(svg)
// 移除多余的空白
svg = removeExtraWhitespace(svg)
return svg
}
// removeXMLComments 移除XML注释 - 优化版本
func removeXMLComments(svg string) string {
// 获取构建器
sb := optimizeBuilderPool.Get().(*strings.Builder)
sb.Reset()
sb.Grow(len(svg))
defer optimizeBuilderPool.Put(sb)
for i := 0; i < len(svg); {
// 检查是否遇到注释开始
if i+3 < len(svg) && svg[i] == '<' && svg[i+1] == '!' && svg[i+2] == '-' && svg[i+3] == '-' {
// 查找注释结束
end := i + 4
for end+2 <= len(svg) {
if svg[end] == '-' && svg[end+1] == '-' && svg[end+2] == '>' {
end += 3
break
}
end++
}
// 跳过注释
i = end
} else {
// 添加非注释字符
sb.WriteByte(svg[i])
i++
}
}
return sb.String()
}
// removeExtraWhitespace 移除多余的空白 - 优化版本
func removeExtraWhitespace(svg string) string {
// 获取构建器
sb := optimizeBuilderPool.Get().(*strings.Builder)
sb.Reset()
sb.Grow(len(svg))
defer optimizeBuilderPool.Put(sb)
inTag := false
inQuote := false
quoteChar := byte(0)
lastWasSpace := false
for i := 0; i < len(svg); i++ {
c := svg[i]
// 处理引号内的内容
if inQuote {
sb.WriteByte(c)
if c == quoteChar {
inQuote = false
}
continue
}
// 处理标签
if c == '<' {
inTag = true
lastWasSpace = false
sb.WriteByte(c)
continue
}
if c == '>' {
inTag = false
lastWasSpace = false
sb.WriteByte(c)
continue
}
// 处理引号开始
if (c == '"' || c == '\'') && inTag {
inQuote = true
quoteChar = c
sb.WriteByte(c)
continue
}
// 处理空白字符
if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
// 压缩连续空白
if !lastWasSpace && (inTag || i > 0 && svg[i-1] != '>') {
sb.WriteByte(' ')
lastWasSpace = true
}
continue
}
// 处理普通字符
lastWasSpace = false
sb.WriteByte(c)
}
return sb.String()
}