mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
1 parent
cef4756217
commit
b8ed92221b
8 files changed
+65
-21
No files matched your search
@@ -2937,7 +2937,7 @@ button.outline-link:hover {
|
|||||||
|
|
||||||
@media (min-width: 901px) and (max-width: 1600px) and (pointer: fine) {
|
@media (min-width: 901px) and (max-width: 1600px) and (pointer: fine) {
|
||||||
.editor-toolbar {
|
.editor-toolbar {
|
||||||
--editor-control-width: 54px;
|
--editor-control-width: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-group {
|
.editor-group {
|
||||||
@@ -2950,7 +2950,7 @@ button.outline-link:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editor-group:has(#textFont) {
|
.editor-group:has(#textFont) {
|
||||||
grid-template-columns: 136px 32px;
|
grid-template-columns: 128px 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-paragraph {
|
.editor-paragraph {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ require (
|
|||||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1
|
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/crypto v0.57.0
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/text v0.42.0
|
golang.org/x/text v0.42.0
|
||||||
|
|||||||
@@ -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/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 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
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-20260929061025-aafe265ab017 h1:VS7X7ESz+3xXj9A1AlmAwQgzxXrWvSfaMklKoIBpUKQ=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
|
|||||||
@@ -634,6 +634,7 @@ func configureDocument(args []js.Value) (any, error) {
|
|||||||
if currentSession.Renderer.RenderAnnotations != args[1].Bool() {
|
if currentSession.Renderer.RenderAnnotations != args[1].Bool() {
|
||||||
currentSession.Renderer.RenderAnnotations = args[1].Bool()
|
currentSession.Renderer.RenderAnnotations = args[1].Bool()
|
||||||
clear(currentSession.textCache)
|
clear(currentSession.textCache)
|
||||||
|
currentSession.textOrder = nil
|
||||||
}
|
}
|
||||||
return currentSession.Summary(), nil
|
return currentSession.Summary(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-6
@@ -20,12 +20,16 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xiaoqidun/ofdgo"
|
"github.com/xiaoqidun/ofdgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// sessionTextCacheLimit 限制会话保留的页面文字索引数量
|
||||||
|
const sessionTextCacheLimit = 32
|
||||||
|
|
||||||
// OpenOptions 打开OFD文档选项
|
// OpenOptions 打开OFD文档选项
|
||||||
type OpenOptions struct {
|
type OpenOptions struct {
|
||||||
Fonts []FontFile
|
Fonts []FontFile
|
||||||
@@ -48,6 +52,7 @@ type Session struct {
|
|||||||
pageCache map[int]*ofdgo.PageContent
|
pageCache map[int]*ofdgo.PageContent
|
||||||
boxCache []pageBoxInfo
|
boxCache []pageBoxInfo
|
||||||
textCache map[int]*ofdgo.PageText
|
textCache map[int]*ofdgo.PageText
|
||||||
|
textOrder []int
|
||||||
svgFonts map[string][]byte
|
svgFonts map[string][]byte
|
||||||
fontScan *ofdgo.FontInfoScanner
|
fontScan *ofdgo.FontInfoScanner
|
||||||
fontInfos []FontInfo
|
fontInfos []FontInfo
|
||||||
@@ -296,6 +301,7 @@ func (s *Session) SetFonts(fonts []FontFile) error {
|
|||||||
s.fontFS = nil
|
s.fontFS = nil
|
||||||
}
|
}
|
||||||
clear(s.textCache)
|
clear(s.textCache)
|
||||||
|
s.textOrder = nil
|
||||||
clear(s.svgFonts)
|
clear(s.svgFonts)
|
||||||
s.resetFontInfo()
|
s.resetFontInfo()
|
||||||
return nil
|
return nil
|
||||||
@@ -305,23 +311,44 @@ func (s *Session) SetFonts(fonts []FontFile) error {
|
|||||||
// 入参: index 页面索引
|
// 入参: index 页面索引
|
||||||
// 返回: *ofdgo.PageText 页面文字, error 错误信息
|
// 返回: *ofdgo.PageText 页面文字, error 错误信息
|
||||||
func (s *Session) PageText(index int) (*ofdgo.PageText, error) {
|
func (s *Session) PageText(index int) (*ofdgo.PageText, error) {
|
||||||
text := s.textCache[index]
|
text := s.cachedPageText(index)
|
||||||
if text == nil {
|
if text == nil {
|
||||||
var err error
|
var err error
|
||||||
text, err = s.readPageText(index)
|
text, err = s.readPageText(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.textCache[index] = text
|
s.cachePageText(index, text)
|
||||||
}
|
}
|
||||||
return text, nil
|
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 获取页面原文,不为全文复制保留额外的页面和文字索引
|
// PageTextString 获取页面原文,不为全文复制保留额外的页面和文字索引
|
||||||
// 入参: index 页面索引
|
// 入参: index 页面索引
|
||||||
// 返回: string 页面原文, error 错误信息
|
// 返回: string 页面原文, error 错误信息
|
||||||
func (s *Session) PageTextString(index int) (string, error) {
|
func (s *Session) PageTextString(index int) (string, error) {
|
||||||
text := s.textCache[index]
|
text := s.cachedPageText(index)
|
||||||
if text == nil {
|
if text == nil {
|
||||||
var err error
|
var err error
|
||||||
text, err = s.readPageText(index)
|
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
|
var buf bytes.Buffer
|
||||||
renderer := *s.Renderer
|
renderer := *s.Renderer
|
||||||
if s.textCache[index] == nil {
|
if s.cachedPageText(index) == nil {
|
||||||
renderer.OnPageText = func(_ *ofdgo.PageContent, text *ofdgo.PageText) {
|
renderer.OnPageText = func(_ *ofdgo.PageContent, text *ofdgo.PageText) {
|
||||||
s.textCache[index] = text
|
s.cachePageText(index, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if raster {
|
if raster {
|
||||||
@@ -480,6 +507,7 @@ func (s *Session) RenderPage(index int, backend string, dpi float64, raster bool
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return PageSVG{}, err
|
return PageSVG{}, err
|
||||||
}
|
}
|
||||||
|
clear(s.svgFonts)
|
||||||
for i, font := range resources.Fonts {
|
for i, font := range resources.Fonts {
|
||||||
s.svgFonts[font.Name] = font.Data
|
s.svgFonts[font.Name] = font.Data
|
||||||
resources.Fonts[i].Data = nil
|
resources.Fonts[i].Data = nil
|
||||||
@@ -501,12 +529,13 @@ func (s *Session) SetRenderBackend(name string) error {
|
|||||||
previous, next := s.Renderer.Backends().Info(), backends.Info()
|
previous, next := s.Renderer.Backends().Info(), backends.Info()
|
||||||
if previous.Compiler != next.Compiler || previous.Fonts != next.Fonts || previous.Geometry != next.Geometry {
|
if previous.Compiler != next.Compiler || previous.Fonts != next.Fonts || previous.Geometry != next.Geometry {
|
||||||
clear(s.textCache)
|
clear(s.textCache)
|
||||||
|
s.textOrder = nil
|
||||||
}
|
}
|
||||||
ofdgo.WithRenderBackends(backends)(s.Renderer)
|
ofdgo.WithRenderBackends(backends)(s.Renderer)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SVGFontData 获取已渲染页面引用的字体数据
|
// SVGFontData 获取最近渲染页面引用的字体数据
|
||||||
// 入参: name 字体资源标识
|
// 入参: name 字体资源标识
|
||||||
// 返回: []byte 字体数据, error 错误信息
|
// 返回: []byte 字体数据, error 错误信息
|
||||||
func (s *Session) SVGFontData(name string) ([]byte, error) {
|
func (s *Session) SVGFontData(name string) ([]byte, error) {
|
||||||
|
|||||||
+4
-2
@@ -137,9 +137,11 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if options.OnProgress != nil {
|
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 {
|
writeProgress := func(stage string, completed, total int) error {
|
||||||
if stage == "write" {
|
if stage == "write" {
|
||||||
|
|||||||
+14
-6
@@ -455,6 +455,11 @@ func (e *Editor) AddImage(data []byte) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
if (format != "png" && format != "jpeg" && format != "jbig2") || config.Width <= 0 || config.Height <= 0 {
|
||||||
return "", fmt.Errorf("only PNG, JPEG and JBIG2 images are supported")
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
if format == "png" && uint64(config.Width)*uint64(config.Height) <= 64<<20 {
|
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"
|
data, format = compact, "jbig2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -734,12 +739,15 @@ func (e *Editor) nextID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// editorBinaryImage 尝试纯黑白无损编码,不二值化,不改变透明度,无体积收益时保留原图
|
// editorBinaryImage 尝试纯黑白无损编码,不二值化,不改变透明度,无体积收益时保留原图
|
||||||
// 入参: data PNG图片数据
|
// 入参: data PNG图片数据, img 已解码像素,为nil时读取原图
|
||||||
// 返回: []byte 更小的JBIG2数据,不适用时为nil
|
// 返回: []byte 更小的JBIG2数据,不适用时为nil
|
||||||
func editorBinaryImage(data []byte) []byte {
|
func editorBinaryImage(data []byte, img image.Image) []byte {
|
||||||
img, _, err := image.Decode(bytes.NewReader(data))
|
if img == nil {
|
||||||
if err != nil {
|
var err error
|
||||||
return nil
|
img, _, err = image.Decode(bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
if err := jbig2.Encode(&output, img, &jbig2.Options{MaxPageBytes: uint64(len(data))}); err != nil || output.Len() >= len(data) {
|
if err := jbig2.Encode(&output, img, &jbig2.Options{MaxPageBytes: uint64(len(data))}); err != nil || output.Len() >= len(data) {
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ func (e *Editor) ImportImages(ctx context.Context, sources []ImagePageSource, at
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("image %q: %w", source.Name, err)
|
return fmt.Errorf("image %q: %w", source.Name, err)
|
||||||
}
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
page, err := e.addImagePage(data, options)
|
page, err := e.addImagePage(data, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("image %q: %w", source.Name, err)
|
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" {
|
if format != "png" && format != "jpeg" && format != "jbig2" {
|
||||||
return 0, fmt.Errorf("only PNG, JPEG and JBIG2 images are supported")
|
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
|
return 0, err
|
||||||
}
|
}
|
||||||
metadata, err := imagePageMetadata(data, format, options.DPI)
|
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 {
|
if !finite(w) || !finite(h) || w <= 0 || h <= 0 {
|
||||||
return 0, fmt.Errorf("invalid image page dimensions")
|
return 0, fmt.Errorf("invalid image page dimensions")
|
||||||
}
|
}
|
||||||
resource, err := e.AddImage(data)
|
resource, err := e.addImage(data, config, format, decoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user