254 lines
5.3 KiB
Go
254 lines
5.3 KiB
Go
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()
|
||
}
|