refactor(重构提升): 优化缓存验签与界面
build.yaml / build (push) Successful in 44s

This commit is contained in:
xiaoqidun committed 2026-09-29 00:13:55 +08:00
1 parent 4d0a909de5
commit b85d7457c1
13 files changed
+98 -42

No files matched your search

+9 -3
View File
@@ -48,6 +48,12 @@ select {
font: inherit;
}
select,
option {
text-align: left;
text-align-last: left;
}
button {
border: 1px solid var(--line-strong);
background: #ffffff;
@@ -135,7 +141,6 @@ a:focus-visible {
border-radius: 0;
background: #fff;
color: #1f2428;
text-align: center;
}
.editor-paragraph > .editor-field,
@@ -650,7 +655,8 @@ button:not(:disabled):hover {
inset: 0;
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
padding-left: 4px;
padding-right: 12px;
pointer-events: none;
}
@@ -789,7 +795,6 @@ button:not(:disabled):hover {
height: var(--control-height);
padding: 0 6px;
vertical-align: middle;
text-align: center;
font: inherit;
border: 1px solid var(--line);
border-radius: 0;
@@ -802,6 +807,7 @@ button:not(:disabled):hover {
}
.convert-table input {
text-align: center;
font-variant-numeric: tabular-nums;
}
+2 -1
View File
@@ -2912,7 +2912,7 @@ function releasePageResources() {
const images = new Set(), fonts = new Set();
for (const page of state.pageCache.values()) {
for (const name of page.imageNames) images.add(name);
for (const font of page.fonts) fonts.add(font.name);
for (const font of page.fonts || []) fonts.add(font.name);
}
for (const [name, image] of state.svgImages) {
if (!images.has(name)) {
@@ -5495,6 +5495,7 @@ function trimPageCache() {
}
}
}
if (!state.pageInFlight.size) releasePageResources();
}
async function loadSVGFonts(fonts, openSeq) {
+1 -1
View File
@@ -9,7 +9,7 @@ require (
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
github.com/xiaoqidun/jbig2 v1.1.1
github.com/xiaoqidun/pdfgo v0.0.0-20260928145637-87d467fc8ef1
github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e
golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0
golang.org/x/text v0.42.0
+2 -2
View File
@@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260928145637-87d467fc8ef1 h1:meZvn/eb/AAbr6qfsP41UZZsW/g1v67a2CUYGzoJpS0=
github.com/xiaoqidun/pdfgo v0.0.0-20260928145637-87d467fc8ef1/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e h1:yc6JbpAQG36mxW6R+mNoAib9tbqTUAQxRcXjGLy6GRA=
github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
+1 -1
View File
@@ -72,7 +72,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer {
DrawParams: reader.drawParamCache,
CompositeGraphicUnits: reader.compositeGraphicUnitCache,
templatePageCache: make(map[string]*PageContent),
imageCache: make(map[string]image.Image),
imageCache: renderCache[string, image.Image]{limit: imageCacheLimit},
backends: defaultRenderBackends(),
backendStates: make(map[any]any),
}
+24 -5
View File
@@ -25,6 +25,9 @@ import (
"sync"
)
// imageCacheLimit 限制图片缓存的编码数据及预计解码内存
const imageCacheLimit = 64 << 20
// EncodedImage 保留PNG或JPEG原始编码,首次读取像素时解码
// Bytes返回只读数据,Image返回共享只读像素,解码错误由Image返回
type EncodedImage struct {
@@ -110,21 +113,37 @@ func newEncodedImage(data []byte) (*EncodedImage, error) {
// 返回: image.Image 图片对象, error 读取错误
func (r *Renderer) cachedImageResource(resPath string) (image.Image, error) {
resPath = cleanPackagePath(r.Reader.ResPath(resPath))
img, ok := r.imageCache[resPath]
img, ok := r.imageCache.get(resPath)
if !ok {
var err error
img, err = r.readImageResource(resPath)
if err != nil {
return nil, err
}
if r.imageCache == nil {
r.imageCache = make(map[string]image.Image)
}
r.imageCache[resPath] = img
r.imageCache.put(resPath, img, imageResourceCost(img, r.imageCache.limit))
}
return img, nil
}
// imageResourceCost 预留十六位像素展开空间,超预算图片不进入缓存
// 入参: img 图片资源, limit 缓存预算
// 返回: int 估算字节数或超预算标记
func imageResourceCost(img image.Image, limit int) int {
cost := 256
w, h := img.Bounds().Dx(), img.Bounds().Dy()
if limit < cost || w <= 0 || h <= 0 || w > (limit-cost)/8/h {
return limit + 1
}
cost += w * h * 8
if encoded, ok := img.(*EncodedImage); ok {
if len(encoded.data) > limit-cost {
return limit + 1
}
cost += len(encoded.data)
}
return cost
}
// decodeImageResource 读取或复用图片资源,按输出需要解码像素
// 入参: resPath 图片资源路径
// 返回: image.Image 图片对象, error 读取或解码错误
+20 -7
View File
@@ -15,6 +15,7 @@
package ofdgo
import (
"cmp"
"fmt"
"slices"
"strconv"
@@ -25,7 +26,7 @@ import (
// 入参: value 页码表达式,如1-6,7,8-10, count 文档总页数
// 返回: []int 页面索引, error 错误信息
func ParsePageRange(value string, count int) ([]int, error) {
selected := make(map[int]bool)
var intervals [][2]int
for _, part := range strings.Split(strings.ReplaceAll(value, ",", ","), ",") {
first, last, interval := strings.Cut(strings.TrimSpace(part), "-")
start, err := strconv.Atoi(strings.TrimSpace(first))
@@ -42,15 +43,27 @@ func ParsePageRange(value string, count int) ([]int, error) {
if start < 1 || end < start || end > count {
return nil, fmt.Errorf("page range %q outside 1-%d", part, count)
}
for index := start - 1; index < end; index++ {
selected[index] = true
intervals = append(intervals, [2]int{start - 1, end})
}
slices.SortFunc(intervals, func(a, b [2]int) int { return cmp.Compare(a[0], b[0]) })
merged := intervals[:0]
for _, interval := range intervals {
if len(merged) != 0 && interval[0] <= merged[len(merged)-1][1] {
merged[len(merged)-1][1] = max(merged[len(merged)-1][1], interval[1])
} else {
merged = append(merged, interval)
}
}
indices := make([]int, 0, len(selected))
for index := range selected {
indices = append(indices, index)
length := 0
for _, interval := range merged {
length += interval[1] - interval[0]
}
indices := make([]int, 0, length)
for _, interval := range merged {
for index := interval[0]; index < interval[1]; index++ {
indices = append(indices, index)
}
}
slices.Sort(indices)
return indices, nil
}
+4 -2
View File
@@ -55,11 +55,13 @@ type Reader struct {
encryption *encryptionState
}
// Close 关闭阅读器
// Close 关闭阅读器持有的文件,重复调用不再关闭底层文件
// 返回: error 错误信息
func (r *Reader) Close() error {
if r.Closer != nil {
return r.Closer.Close()
closer := r.Closer
r.Closer = nil
return closer.Close()
}
return nil
}
+2 -2
View File
@@ -34,7 +34,7 @@ type Renderer struct {
DrawParams map[string]*DrawParam
CompositeGraphicUnits map[string]*CompositeGraphicUnit
templatePageCache map[string]*PageContent
imageCache map[string]image.Image
imageCache renderCache[string, image.Image]
fontDirs []string
fontFS []fs.FS
decodeImages bool
@@ -57,7 +57,7 @@ type RendererOption func(*Renderer)
// ClearCache 释放派生字体、图片、模板与后端缓存,不改变文档和渲染配置
func (r *Renderer) ClearCache() {
r.backendStates = make(map[any]any)
r.imageCache = make(map[string]image.Image)
r.imageCache = renderCache[string, image.Image]{limit: imageCacheLimit}
r.templatePageCache = make(map[string]*PageContent)
r.resetFontCache()
}
+3 -10
View File
@@ -74,14 +74,7 @@ func textGlyphPath(face *canvas.FontFace, glyph textGlyph, outlines *sfntOutline
width = face.MmPerEm * float64(face.Font.GlyphAdvance(glyphID))
}
if face.FauxBold != 0 {
d := face.FauxBold * face.Size
if face.Font.IsTrueType {
d = -d
}
origFastStroke := canvas.FastStroke
canvas.FastStroke = true
p = p.Offset(d, canvas.Tolerance)
canvas.FastStroke = origFastStroke
p = p.Offset(face.FauxBold*face.Size, canvas.Tolerance)
}
if face.FauxItalic != 0 {
p = p.Transform(canvas.Identity.Shear(face.FauxItalic, 0))
@@ -105,7 +98,7 @@ func (r *Renderer) cachedTextGlyphPath(face *canvas.FontFace, glyph textGlyph) (
direction: face.Direction,
glyph: glyph,
}
if cached, ok := r.canvasState().textGlyphPathCache[key]; ok {
if cached, ok := r.canvasState().textGlyphPathCache.get(key); ok {
return cached.path, cached.width
}
outlines := r.canvasState().fontOutlines[face.Font]
@@ -118,7 +111,7 @@ func (r *Renderer) cachedTextGlyphPath(face *canvas.FontFace, glyph textGlyph) (
r.renderError = err
return &canvas.Path{}, 0
}
r.canvasState().textGlyphPathCache[key] = textGlyphPathCacheValue{path: path, width: width}
r.canvasState().textGlyphPathCache.put(key, textGlyphPathCacheValue{path: path, width: width}, 256+len(path.Data())*8+len(glyph.Text)+len(face.Language))
return path, width
}
+2 -2
View File
@@ -138,7 +138,7 @@ func (r *Renderer) canvasEncodedImage(img image.Image) image.Image {
return img
}
state := r.canvasState()
if encoded, ok := state.images[source]; ok {
if encoded, ok := state.images.get(source); ok {
return encoded
}
var encoded *canvasimage.Image
@@ -148,7 +148,7 @@ func (r *Renderer) canvasEncodedImage(img image.Image) image.Image {
encoded, _ = canvasimage.NewPNGImage(bytes.NewReader(source.Bytes()))
}
encoded.Bytes = source.Bytes()
state.images[source] = encoded
state.images.put(source, encoded, 2*imageResourceCost(source, state.images.limit/2))
return encoded
}
+4 -4
View File
@@ -22,10 +22,10 @@ import (
// canvasBackendState 保存Canvas字体、字形和编码图片适配缓存
type canvasBackendState struct {
images map[*EncodedImage]image.Image
images renderCache[*EncodedImage, image.Image]
fontMap map[string]*canvas.FontFamily
svgFontCache map[*canvas.Font]SVGFont
textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue
textGlyphPathCache renderCache[textGlyphPathCacheKey, textGlyphPathCacheValue]
fontOutlines map[*canvas.Font]*sfntOutliner
}
@@ -37,7 +37,7 @@ func (r *Renderer) canvasState() *canvasBackendState {
return state.(*canvasBackendState)
}
state := &canvasBackendState{
images: make(map[*EncodedImage]image.Image),
images: renderCache[*EncodedImage, image.Image]{limit: imageCacheLimit},
}
state.resetFonts()
r.backendStates[key] = state
@@ -48,6 +48,6 @@ func (r *Renderer) canvasState() *canvasBackendState {
func (s *canvasBackendState) resetFonts() {
s.fontMap = make(map[string]*canvas.FontFamily)
s.svgFontCache = make(map[*canvas.Font]SVGFont)
s.textGlyphPathCache = make(map[textGlyphPathCacheKey]textGlyphPathCacheValue)
s.textGlyphPathCache = renderCache[textGlyphPathCacheKey, textGlyphPathCacheValue]{limit: 16 << 20}
s.fontOutlines = make(map[*canvas.Font]*sfntOutliner)
}
+24 -2
View File
@@ -29,6 +29,7 @@ import (
"encoding/pem"
"encoding/xml"
"fmt"
"hash"
"io"
"math/big"
"path"
@@ -36,6 +37,7 @@ import (
"time"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/sm3"
"github.com/emmansun/gmsm/smx509"
)
@@ -489,16 +491,36 @@ func (r *Reader) verifySignatureReference(sigPath, method string, ref SignatureR
result.Error = err.Error()
return result
}
data, err := r.readFile(refPath)
var digest hash.Hash
switch {
case strings.TrimSpace(method) == "":
digest = md5.New()
case isSM3DigestMethod(method):
digest = sm3.New()
default:
algorithm, ok := signatureDigestHash(method)
if !ok {
result.Error = fmt.Sprintf("unsupported digest method: %s", method)
return result
}
digest = algorithm.New()
}
input, err := r.openFile(refPath)
if err != nil {
result.Error = err.Error()
return result
}
actual, err := signatureDigest(method, data)
_, err = io.Copy(digest, input)
closeErr := input.Close()
if err != nil {
result.Error = err.Error()
return result
}
if closeErr != nil {
result.Error = closeErr.Error()
return result
}
actual := digest.Sum(nil)
result.CheckValue = checkValue
result.Actual = actual
result.Checked = true