diff --git a/assets/webui/ofdgo.css b/assets/webui/ofdgo.css index 6cc6721..74aa749 100644 --- a/assets/webui/ofdgo.css +++ b/assets/webui/ofdgo.css @@ -2937,7 +2937,7 @@ button.outline-link:hover { @media (min-width: 901px) and (max-width: 1600px) and (pointer: fine) { .editor-toolbar { - --editor-control-width: 54px; + --editor-control-width: 52px; } .editor-group { @@ -2950,7 +2950,7 @@ button.outline-link:hover { } .editor-group:has(#textFont) { - grid-template-columns: 136px 32px; + grid-template-columns: 128px 32px; } .editor-paragraph { diff --git a/go.mod b/go.mod index d4f4287..c3d0e1d 100644 --- a/go.mod +++ b/go.mod @@ -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-20260928160629-4a36d082a27e + github.com/xiaoqidun/pdfgo v0.0.0-20260929061025-aafe265ab017 golang.org/x/crypto v0.57.0 golang.org/x/image v0.46.0 golang.org/x/text v0.42.0 diff --git a/go.sum b/go.sum index adf1786..6e7f78f 100644 --- a/go.sum +++ b/go.sum @@ -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-20260928160629-4a36d082a27e h1:yc6JbpAQG36mxW6R+mNoAib9tbqTUAQxRcXjGLy6GRA= -github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ= +github.com/xiaoqidun/pdfgo v0.0.0-20260929061025-aafe265ab017 h1:VS7X7ESz+3xXj9A1AlmAwQgzxXrWvSfaMklKoIBpUKQ= +github.com/xiaoqidun/pdfgo v0.0.0-20260929061025-aafe265ab017/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= diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go index 0df9185..0dc02c6 100644 --- a/internal/webui/wasm.go +++ b/internal/webui/wasm.go @@ -634,6 +634,7 @@ func configureDocument(args []js.Value) (any, error) { if currentSession.Renderer.RenderAnnotations != args[1].Bool() { currentSession.Renderer.RenderAnnotations = args[1].Bool() clear(currentSession.textCache) + currentSession.textOrder = nil } return currentSession.Summary(), nil } diff --git a/internal/webui/webui.go b/internal/webui/webui.go index 9573019..9328bfc 100644 --- a/internal/webui/webui.go +++ b/internal/webui/webui.go @@ -20,12 +20,16 @@ import ( "io" "path" "reflect" + "slices" "strings" "time" "github.com/xiaoqidun/ofdgo" ) +// sessionTextCacheLimit 限制会话保留的页面文字索引数量 +const sessionTextCacheLimit = 32 + // OpenOptions 打开OFD文档选项 type OpenOptions struct { Fonts []FontFile @@ -48,6 +52,7 @@ type Session struct { pageCache map[int]*ofdgo.PageContent boxCache []pageBoxInfo textCache map[int]*ofdgo.PageText + textOrder []int svgFonts map[string][]byte fontScan *ofdgo.FontInfoScanner fontInfos []FontInfo @@ -296,6 +301,7 @@ func (s *Session) SetFonts(fonts []FontFile) error { s.fontFS = nil } clear(s.textCache) + s.textOrder = nil clear(s.svgFonts) s.resetFontInfo() return nil @@ -305,23 +311,44 @@ func (s *Session) SetFonts(fonts []FontFile) error { // 入参: index 页面索引 // 返回: *ofdgo.PageText 页面文字, error 错误信息 func (s *Session) PageText(index int) (*ofdgo.PageText, error) { - text := s.textCache[index] + text := s.cachedPageText(index) if text == nil { var err error text, err = s.readPageText(index) if err != nil { return nil, err } - s.textCache[index] = text + s.cachePageText(index, text) } return text, nil } +// cachedPageText 获取缓存索引并更新最近访问顺序 +func (s *Session) cachedPageText(index int) *ofdgo.PageText { + text := s.textCache[index] + if text != nil { + s.cachePageText(index, text) + } + return text +} + +// cachePageText 保留最近访问的文字索引,避免全文搜索持续占用内存 +func (s *Session) cachePageText(index int, text *ofdgo.PageText) { + if position := slices.Index(s.textOrder, index); position >= 0 { + s.textOrder = slices.Delete(s.textOrder, position, position+1) + } else if len(s.textOrder) == sessionTextCacheLimit { + delete(s.textCache, s.textOrder[0]) + s.textOrder = slices.Delete(s.textOrder, 0, 1) + } + s.textOrder = append(s.textOrder, index) + s.textCache[index] = text +} + // PageTextString 获取页面原文,不为全文复制保留额外的页面和文字索引 // 入参: index 页面索引 // 返回: string 页面原文, error 错误信息 func (s *Session) PageTextString(index int) (string, error) { - text := s.textCache[index] + text := s.cachedPageText(index) if text == nil { var err error text, err = s.readPageText(index) @@ -460,9 +487,9 @@ func (s *Session) RenderPage(index int, backend string, dpi float64, raster bool } var buf bytes.Buffer renderer := *s.Renderer - if s.textCache[index] == nil { + if s.cachedPageText(index) == nil { renderer.OnPageText = func(_ *ofdgo.PageContent, text *ofdgo.PageText) { - s.textCache[index] = text + s.cachePageText(index, text) } } if raster { @@ -480,6 +507,7 @@ func (s *Session) RenderPage(index int, backend string, dpi float64, raster bool if err != nil { return PageSVG{}, err } + clear(s.svgFonts) for i, font := range resources.Fonts { s.svgFonts[font.Name] = font.Data resources.Fonts[i].Data = nil @@ -501,12 +529,13 @@ func (s *Session) SetRenderBackend(name string) error { previous, next := s.Renderer.Backends().Info(), backends.Info() if previous.Compiler != next.Compiler || previous.Fonts != next.Fonts || previous.Geometry != next.Geometry { clear(s.textCache) + s.textOrder = nil } ofdgo.WithRenderBackends(backends)(s.Renderer) return nil } -// SVGFontData 获取已渲染页面引用的字体数据 +// SVGFontData 获取最近渲染页面引用的字体数据 // 入参: name 字体资源标识 // 返回: []byte 字体数据, error 错误信息 func (s *Session) SVGFontData(name string) ([]byte, error) { diff --git a/ofdgo_convert.go b/ofdgo_convert.go index 6001d30..2bf3970 100644 --- a/ofdgo_convert.go +++ b/ofdgo_convert.go @@ -137,9 +137,11 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ return err } if options.OnProgress != nil { - return options.OnProgress(ConvertProgress{Stage: stage, Completed: completed, Total: total}) + if err := options.OnProgress(ConvertProgress{Stage: stage, Completed: completed, Total: total}); err != nil { + return err + } } - return nil + return ctx.Err() } writeProgress := func(stage string, completed, total int) error { if stage == "write" { diff --git a/ofdgo_editor.go b/ofdgo_editor.go index 75be0c4..e18a661 100644 --- a/ofdgo_editor.go +++ b/ofdgo_editor.go @@ -455,6 +455,11 @@ func (e *Editor) AddImage(data []byte) (string, error) { if err != nil { return "", err } + return e.addImage(data, config, format, nil) +} + +// addImage 注册图片资源,复用调用方已完成解码的像素数据 +func (e *Editor) addImage(data []byte, config image.Config, format string, decoded image.Image) (string, error) { if (format != "png" && format != "jpeg" && format != "jbig2") || config.Width <= 0 || config.Height <= 0 { return "", fmt.Errorf("only PNG, JPEG and JBIG2 images are supported") } @@ -466,7 +471,7 @@ func (e *Editor) AddImage(data []byte) (string, error) { return "", err } if format == "png" && uint64(config.Width)*uint64(config.Height) <= 64<<20 { - if compact := editorBinaryImage(data); compact != nil { + if compact := editorBinaryImage(data, decoded); compact != nil { data, format = compact, "jbig2" } } @@ -734,12 +739,15 @@ func (e *Editor) nextID() string { } // editorBinaryImage 尝试纯黑白无损编码,不二值化,不改变透明度,无体积收益时保留原图 -// 入参: data PNG图片数据 +// 入参: data PNG图片数据, img 已解码像素,为nil时读取原图 // 返回: []byte 更小的JBIG2数据,不适用时为nil -func editorBinaryImage(data []byte) []byte { - img, _, err := image.Decode(bytes.NewReader(data)) - if err != nil { - return nil +func editorBinaryImage(data []byte, img image.Image) []byte { + if img == nil { + var err error + img, _, err = image.Decode(bytes.NewReader(data)) + if err != nil { + return nil + } } var output bytes.Buffer if err := jbig2.Encode(&output, img, &jbig2.Options{MaxPageBytes: uint64(len(data))}); err != nil || output.Len() >= len(data) { diff --git a/ofdgo_editor_images.go b/ofdgo_editor_images.go index 5d4f5f2..8773374 100644 --- a/ofdgo_editor_images.go +++ b/ofdgo_editor_images.go @@ -87,6 +87,9 @@ func (e *Editor) ImportImages(ctx context.Context, sources []ImagePageSource, at if err != nil { return fmt.Errorf("image %q: %w", source.Name, err) } + if err := ctx.Err(); err != nil { + return err + } page, err := e.addImagePage(data, options) if err != nil { return fmt.Errorf("image %q: %w", source.Name, err) @@ -112,7 +115,8 @@ func (e *Editor) addImagePage(data []byte, options ImagePageOptions) (int, error if format != "png" && format != "jpeg" && format != "jbig2" { return 0, fmt.Errorf("only PNG, JPEG and JBIG2 images are supported") } - if _, _, err := image.Decode(bytes.NewReader(data)); err != nil { + decoded, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { return 0, err } metadata, err := imagePageMetadata(data, format, options.DPI) @@ -133,7 +137,7 @@ func (e *Editor) addImagePage(data []byte, options ImagePageOptions) (int, error if !finite(w) || !finite(h) || w <= 0 || h <= 0 { return 0, fmt.Errorf("invalid image page dimensions") } - resource, err := e.AddImage(data) + resource, err := e.addImage(data, config, format, decoded) if err != nil { return 0, err }