From d97c6f4e6fb23c8d21806d2d1471a0dad38e18c9 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 19 Oct 2026 08:43:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BB=9F=E4=B8=80=E8=BE=93=E5=85=A5):=20?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=B5=81=E5=BC=8F=E8=BE=93=E5=85=A5=E5=8F=8A?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 +++- internal/base/file/file.go | 22 ++ internal/base/image/color.go | 2 +- internal/base/image/encoded.go | 4 +- internal/base/image/image.go | 4 +- internal/base/image/input.go | 4 +- internal/base/image/tiff.go | 2 +- internal/base/threed/u3d_lineset.go | 6 +- internal/base/threed/u3d_motion.go | 8 +- internal/base/threed/u3d_resource_reader.go | 6 +- internal/webui/webui.go | 98 ++++++-- internal/webui/webui_native.go | 45 ++-- internal/webui/webui_wasm.go | 12 +- internal/webview/webview.go | 4 +- ofdgo.go | 253 ++++++++++++++++++-- ofdgo_annot.go | 4 +- ofdgo_archive.go | 15 +- ofdgo_compression.go | 10 +- ofdgo_compression_parallel.go | 2 +- ofdgo_compression_pattern.go | 2 +- ofdgo_compression_raster.go | 2 +- ofdgo_compression_resources.go | 2 +- ofdgo_convert.go | 51 ++-- ofdgo_crypto_editor.go | 2 +- ofdgo_crypto_package.go | 2 +- ofdgo_editor_action.go | 6 +- ofdgo_editor_bookmark.go | 10 +- ofdgo_editor_font_binding.go | 2 +- ofdgo_editor_font_cleanup.go | 2 +- ofdgo_editor_image_binary.go | 2 +- ofdgo_editor_metadata.go | 6 +- ofdgo_editor_outline.go | 10 +- ofdgo_editor_package.go | 18 +- ofdgo_editor_pages.go | 4 +- ofdgo_editor_resource_scan.go | 2 +- ofdgo_editor_resource_style.go | 4 +- ofdgo_editor_resources.go | 2 +- ofdgo_extract_image.go | 16 +- ofdgo_format.go | 14 +- ofdgo_invoice.go | 2 +- ofdgo_package.go | 2 +- ofdgo_pdf.go | 16 +- ofdgo_reader.go | 18 +- ofdgo_reader_dependencies.go | 2 +- ofdgo_render_compile_image.go | 2 +- ofdgo_render_output.go | 12 +- ofdgo_render_svg.go | 2 +- ofdgo_renderer_page_canvas.go | 18 +- ofdgo_renderer_pdf_action.go | 2 +- ofdgo_renderer_pdf_media.go | 2 +- ofdgo_renderer_stamp_canvas.go | 4 +- ofdgo_renderer_svg_canvas.go | 2 +- ofdgo_resource.go | 2 +- ofdgo_sign.go | 4 +- ofdgo_sign_verify.go | 26 +- ofdgo_sign_write.go | 8 +- ofdgo_structure.go | 2 +- ofdgo_version.go | 2 +- ofdgo_version_edit.go | 2 +- ofdgo_writer.go | 2 +- 60 files changed, 570 insertions(+), 262 deletions(-) diff --git a/README.md b/README.md index 732aac3..55654a2 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ func main() { package main import ( + "context" "log" "os" @@ -129,7 +130,12 @@ import ( func main() { // 1. 打开文档 - reader, err := ofdgo.Open("test.ofd") + input, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.InputOptions{}) + if err != nil { + log.Fatal(err) + } + defer input.Close() + reader, err := ofdgo.NewReader(input) if err != nil { log.Fatal(err) } @@ -166,15 +172,11 @@ import ( func main() { // 1. 打开文档 - pdfFile, err := os.Open("test.pdf") - if err != nil { - log.Fatal(err) - } - defer pdfFile.Close() - info, err := pdfFile.Stat() + input, err := ofdgo.OpenFile(context.Background(), "test.pdf", ofdgo.InputOptions{}) if err != nil { log.Fatal(err) } + defer input.Close() // 2. 创建文件 ofdFile, err := os.Create("test.ofd") if err != nil { @@ -182,7 +184,7 @@ func main() { } defer ofdFile.Close() // 3. 转换文档 - report, err := ofdgo.ConvertPDF(context.Background(), pdfFile, info.Size(), ofdFile, ofdgo.PDFImportOptions{}) + report, err := ofdgo.ConvertPDF(context.Background(), input, ofdFile, ofdgo.PDFImportOptions{}) if err != nil { log.Fatal(err) } @@ -198,6 +200,7 @@ func main() { package main import ( + "context" "log" "os" @@ -206,7 +209,12 @@ import ( func main() { // 1. 打开OFD文件 - reader, err := ofdgo.Open("test.ofd") + input, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.InputOptions{}) + if err != nil { + log.Fatal(err) + } + defer input.Close() + reader, err := ofdgo.NewReader(input) if err != nil { log.Fatal(err) } @@ -230,6 +238,7 @@ func main() { package main import ( + "context" "log" "os" @@ -238,12 +247,13 @@ import ( func main() { // 1. 打开OFD文件 - data, err := os.ReadFile("test.ofd") + input, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.InputOptions{}) if err != nil { log.Fatal(err) } + defer input.Close() // 2. 验证OFD签名 - reports, err := ofdgo.VerifySignaturesBytes(data) + reports, err := ofdgo.VerifySignatures(input) if err != nil { log.Fatal(err) } @@ -276,6 +286,7 @@ func main() { package main import ( + "context" "encoding/json" "log" "os" @@ -285,7 +296,12 @@ import ( func main() { // 1. 打开OFD文件 - reader, err := ofdgo.Open("invoice.ofd") + input, err := ofdgo.OpenFile(context.Background(), "invoice.ofd", ofdgo.InputOptions{}) + if err != nil { + log.Fatal(err) + } + defer input.Close() + reader, err := ofdgo.NewReader(input) if err != nil { log.Fatal(err) } diff --git a/internal/base/file/file.go b/internal/base/file/file.go index 235a7e8..24a9aac 100644 --- a/internal/base/file/file.go +++ b/internal/base/file/file.go @@ -52,6 +52,28 @@ type Writer struct { closed bool } +// NewTemp 创建私有输入缓存文件,调用方负责关闭和删除 +// 入参: directory 缓存目录,空值使用系统临时目录 +// 返回: *os.File 缓存文件, error 创建错误 +func NewTemp(directory string) (*os.File, error) { + if directory == "" { + directory = os.TempDir() + } + for range 100 { + n, err := rand.Int(rand.Reader, big.NewInt(10000000)) + if err != nil { + return nil, err + } + name := filepath.Join(directory, fmt.Sprintf("ofdgo-%07d.tmp", n.Int64())) + file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if errors.Is(err, os.ErrExist) { + continue + } + return file, err + } + return nil, os.ErrExist +} + // NewTarget 记录目标当前状态,允许目标尚不存在 // 入参: name 目标路径 // 返回: *Target 保存目标, error 路径或文件错误 diff --git a/internal/base/image/color.go b/internal/base/image/color.go index 363e223..cd63e30 100644 --- a/internal/base/image/color.go +++ b/internal/base/image/color.go @@ -96,7 +96,7 @@ func pngColorProfile(ctx context.Context, data []byte) ([]byte, *pngColor, error if err != nil { return nil, nil, err } - profile, err := io.ReadAll(io.LimitReader(contextInput{ReadCloser: reader, context: ctx}, profileLimit+1)) + profile, err := io.ReadAll(io.LimitReader(contextInput{ReadCloser: reader, ctx: ctx}, profileLimit+1)) closeErr := reader.Close() if err != nil { return nil, nil, err diff --git a/internal/base/image/encoded.go b/internal/base/image/encoded.go index 675c5b2..20067b5 100644 --- a/internal/base/image/encoded.go +++ b/internal/base/image/encoded.go @@ -139,7 +139,7 @@ func (img *EncodedImage) decodeContext(ctx context.Context, pending chan struct{ close(pending) img.mu.Unlock() }() - pixels, _, err := image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(img.data)), context: ctx}) + pixels, _, err := image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(img.data)), ctx: ctx}) if err == nil && (img.format == "jpeg" || img.profile != nil) { var profile *pdfgo.ColorSpace if img.profile != nil { @@ -184,7 +184,7 @@ func newEncodedImage(data []byte) (*EncodedImage, error) { // 入参: ctx 取消上下文, data 只读图片编码 // 返回: *EncodedImage 惰性图片, error 头信息、格式或取消错误 func newEncodedImageContext(ctx context.Context, data []byte) (*EncodedImage, error) { - config, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + config, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if cancelErr := ctx.Err(); cancelErr != nil { return nil, cancelErr } diff --git a/internal/base/image/image.go b/internal/base/image/image.go index 3dc2a42..4ae477f 100644 --- a/internal/base/image/image.go +++ b/internal/base/image/image.go @@ -170,7 +170,7 @@ func DecodeContext(ctx context.Context, data []byte) (img image.Image, format st img, err := DecodeTIFF(ctx, data, layout) return img, "tiff", err } - img, format, err = image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + img, format, err = image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err == nil { return img, NormalizeFormat(format), nil } @@ -267,7 +267,7 @@ func DecodeConfigDataContext(ctx context.Context, data []byte) (config image.Con cfg.Width, cfg.Height = size.X, size.Y return cfg, "tiff", nil } - cfg, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + cfg, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err == nil { return cfg, NormalizeFormat(format), nil } diff --git a/internal/base/image/input.go b/internal/base/image/input.go index e55cba9..26ad16c 100644 --- a/internal/base/image/input.go +++ b/internal/base/image/input.go @@ -22,14 +22,14 @@ import ( // contextInput 在读取图像编码时检查调用方取消状态 type contextInput struct { io.ReadCloser - context context.Context + ctx context.Context } // Read 检查取消状态后读取原始数据 // 入参: data 数据缓冲区 // 返回: int 读取字节数, error 读取或取消错误 func (r contextInput) Read(data []byte) (int, error) { - if err := r.context.Err(); err != nil { + if err := r.ctx.Err(); err != nil { return 0, err } return r.ReadCloser.Read(data) diff --git a/internal/base/image/tiff.go b/internal/base/image/tiff.go index 94ff86d..9863577 100644 --- a/internal/base/image/tiff.go +++ b/internal/base/image/tiff.go @@ -165,7 +165,7 @@ func readTIFFDirectory(ctx context.Context, input io.Reader, order binary.ByteOr if offset < position { return result, position, ErrTIFFRandomAccess } - reader := contextInput{ReadCloser: io.NopCloser(input), context: ctx} + reader := contextInput{ReadCloser: io.NopCloser(input), ctx: ctx} if _, err := io.CopyN(io.Discard, reader, offset-position); err != nil { return result, position, err } diff --git a/internal/base/threed/u3d_lineset.go b/internal/base/threed/u3d_lineset.go index f3fa789..25d7ccd 100644 --- a/internal/base/threed/u3d_lineset.go +++ b/internal/base/threed/u3d_lineset.go @@ -211,11 +211,11 @@ func (s *u3dLineState) accumulateCorners(mesh *U3DMesh, shadingIndex uint32, cor } // value 按指定上下文和位宽读取点集或线集字段 -// 入参: context 压缩上下文, small 是否为8位字段 +// 入参: ctx 压缩上下文, small 是否为8位字段 // 返回: uint32 字段值 -func (r *u3dLineReader) value(context int, small bool) uint32 { +func (r *u3dLineReader) value(ctx int, small bool) uint32 { if r.bits != nil { - return r.bits.dynamicValue(&r.hist[context], small) + return r.bits.dynamicValue(&r.hist[ctx], small) } if small { return uint32(r.values.u8()) diff --git a/internal/base/threed/u3d_motion.go b/internal/base/threed/u3d_motion.go index 58737f9..f4fe35d 100644 --- a/internal/base/threed/u3d_motion.go +++ b/internal/base/threed/u3d_motion.go @@ -167,15 +167,15 @@ func (r *u3dResourceReader) motionFrame(frame *U3DKeyFrame, previous U3DKeyFrame } // motionDifference 用共享幅值上下文恢复一至三个差分分量 -// 入参: previous 前一帧分量, scale 逆量化系数, context 符号上下文,下一项为幅值上下文 +// 入参: previous 前一帧分量, scale 逆量化系数, ctx 符号上下文,下一项为幅值上下文 // 返回: [3]float32 重建分量, error 符号、数值或读取错误 -func (r *u3dResourceReader) motionDifference(previous []float32, scale float32, context int) (values [3]float32, err error) { - signs := r.value(context, true) +func (r *u3dResourceReader) motionDifference(previous []float32, scale float32, ctx int) (values [3]float32, err error) { + signs := r.value(ctx, true) if signs >= 1< 0 { - entries[completed-1].Source.(*browserReaderAt).data = nil + inputs[completed-1].data = nil } return awaitExport(args[3], completed, total) }} @@ -1517,10 +1558,16 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) { if len(args) < 3 { return nil, fmt.Errorf("missing open arguments") } - source, size, err := e.sourceFromValue(args[0]) + input, err := e.inputFromValue(args[0]) if err != nil { return nil, err } + keep := false + defer func() { + if !keep { + input.Close() + } + }() fonts, err := e.fontsFromJS(args[1]) if err != nil { return nil, err @@ -1535,7 +1582,7 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) { defer clear(credentials.Password) options.ReaderOptions = append(options.ReaderOptions, ofdgo.WithCredentials(credentials)) } - reader, err := ofdgo.NewReader(source, size, options.ReaderOptions...) + reader, err := ofdgo.NewReader(input, options.ReaderOptions...) if err != nil { return nil, err } @@ -1552,6 +1599,8 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) { e.currentSession = nil } e.currentSession = session + session.input = input + keep = true return e.currentSession.Summary(), nil } @@ -5456,10 +5505,16 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) { if args[0].IsNull() { return nil, nil } - source, size, err := e.sourceFromValue(args[0]) + input, err := e.inputFromValue(args[0]) if err != nil { return nil, err } + keep := false + defer func() { + if !keep { + input.Close() + } + }() var options []ofdgo.ReaderOption if len(args) > 1 && !args[1].IsNull() && !args[1].IsUndefined() { credentials, err := e.credentialsFromJS(args[1]) @@ -5469,10 +5524,11 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) { defer clear(credentials.Password) options = append(options, ofdgo.WithCredentials(credentials)) } - reader, err := ofdgo.NewReader(source, size, options...) + reader, err := ofdgo.NewReader(input, options...) if err != nil { return nil, err } + reader.Closer = input doc, err := reader.Doc() if err != nil { reader.Close() @@ -5483,6 +5539,7 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) { return nil, fmt.Errorf("document has no pages") } e.pendingImport = reader + keep = true entries := make([]DocumentEntry, reader.DocumentCount()) signed := doc.Signatures != "" for index, body := range reader.OFD.DocBody { @@ -6123,6 +6180,7 @@ func (e *businessEngine) selectDocumentVersion(args []platformValue) (any, error if err != nil { return nil, err } + session.input, e.currentSession.input = e.currentSession.input, nil _ = e.currentSession.Close() e.currentSession = session e.clearImport() @@ -6280,6 +6338,7 @@ func (e *businessEngine) selectDocument(args []platformValue) (any, error) { if err != nil { return nil, err } + session.input, e.currentSession.input = e.currentSession.input, nil _ = e.currentSession.Close() e.currentSession, e.currentEditor = session, session.editor if e.pendingImport != nil { @@ -6380,6 +6439,9 @@ func (e *businessEngine) previewEditor(editor *ofdgo.Editor, annotations bool) ( session.Renderer.SetFontFS(session.fontFS) editor.SetFontFS(session.fontFS) } + if editor == e.currentEditor { + session.input, e.currentSession.input = e.currentSession.input, nil + } _ = e.currentSession.Close() } editor.SetRenderBackends(session.Renderer.Backends()) diff --git a/internal/webui/webui_native.go b/internal/webui/webui_native.go index 6109ee3..7a70922 100644 --- a/internal/webui/webui_native.go +++ b/internal/webui/webui_native.go @@ -89,7 +89,7 @@ type NativeEngine struct { // nativeReader 在会话当前操作上下文中分块读取,不捕获已结束请求的上下文 type nativeReader struct { engine *NativeEngine - source io.ReaderAt + input io.ReaderAt } // nativeCall 保存单次worker适配状态 @@ -139,7 +139,7 @@ func NewNativeEngine(resolve NativeResolver) *NativeEngine { return &NativeEngin func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit func(any)) (result map[string]any) { e.once.Do(func() { e.gate = make(chan struct{}, 1) - e.resolveSource = e.source + e.resolveInput = e.input e.conversionPool = e.ConversionPool }) if ctx == nil { @@ -236,7 +236,7 @@ func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit f // Close 释放会话,不关闭宿主持有的ReaderAt资源 func (e *NativeEngine) Close() error { - e.once.Do(func() { e.gate = make(chan struct{}, 1); e.resolveSource = e.source }) + e.once.Do(func() { e.gate = make(chan struct{}, 1); e.resolveInput = e.input }) e.gate <- struct{}{} defer func() { <-e.gate }() e.closed = true @@ -253,8 +253,8 @@ func (e *NativeEngine) Close() error { return err } -// source 读取授权源并以宿主报告的大小为准 -func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int64, error) { +// input 读取授权源并以宿主报告的大小为准 +func (e *NativeEngine) input(ctx context.Context, value any) (io.ReaderAt, int64, error) { if err := ctx.Err(); err != nil { return nil, 0, err } @@ -262,7 +262,7 @@ func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int6 value = p.value } if data, ok := value.([]byte); ok { - return nativeReader{engine: e, source: bytes.NewReader(data)}, int64(len(data)), nil + return nativeReader{engine: e, input: bytes.NewReader(data)}, int64(len(data)), nil } descriptor, ok := value.(map[string]any) if !ok { @@ -272,14 +272,14 @@ func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int6 if !ok || token == "" || e.Resolve == nil { return nil, 0, fmt.Errorf("unauthorized native file") } - source, size, err := e.Resolve(token) + input, size, err := e.Resolve(token) if err != nil { return nil, 0, err } - if source == nil || size < 0 { - return nil, 0, fmt.Errorf("invalid native file source") + if input == nil || size < 0 { + return nil, 0, fmt.Errorf("invalid native file input") } - return nativeReader{engine: e, source: source}, size, nil + return nativeReader{engine: e, input: input}, size, nil } // ReadAt 分块传递取消检查,宿主ReadAt自身阻塞时由宿主控制超时 @@ -290,7 +290,7 @@ func (r nativeReader) ReadAt(data []byte, offset int64) (int, error) { return total, err } n := min(len(data), 256<<10) - got, err := r.source.ReadAt(data[:n], offset) + got, err := r.input.ReadAt(data[:n], offset) total += got if err != nil { return total, err @@ -307,7 +307,7 @@ func (r nativeReader) ReadAt(data []byte, offset int64) (int, error) { // 入参: ctx 调用上下文, value 资源参数 // 返回: []byte 资源数据, error 读取错误 func (e *NativeEngine) readBytes(ctx context.Context, value any) ([]byte, error) { - source, size, err := e.source(ctx, value) + input, size, err := e.input(ctx, value) if err != nil { return nil, err } @@ -320,7 +320,7 @@ func (e *NativeEngine) readBytes(ctx context.Context, value any) ([]byte, error) return nil, err } n := int(min(int64(256<<10), size-offset)) - got, err := source.ReadAt(output[int(offset):int(offset)+n], offset) + got, err := input.ReadAt(output[int(offset):int(offset)+n], offset) if got != n { return nil, io.ErrUnexpectedEOF } @@ -540,7 +540,7 @@ func (c *nativeCall) progress(kind, stage string, phased bool) nativeCallback { // readCallback 创建有界随机读取回调 func (c *nativeCall) readCallback(value any) (nativeCallback, int64, error) { - source, size, err := c.engine.source(c.ctx, value) + input, size, err := c.engine.input(c.ctx, value) if err != nil { return nil, 0, err } @@ -553,7 +553,7 @@ func (c *nativeCall) readCallback(value any) (nativeCallback, int64, error) { return nil, fmt.Errorf("invalid native read range") } data := make([]byte, n) - got, err := source.ReadAt(data, offset) + got, err := input.ReadAt(data, offset) if got != n { return nil, io.ErrUnexpectedEOF } @@ -876,9 +876,13 @@ func nativeOutputBase(value platformValue) string { return base } -// sourceFromValue 读取宿主授权的随机读取源 -func (e *businessEngine) sourceFromValue(value platformValue) (io.ReaderAt, int64, error) { - return e.resolveSource(e.operationContext(), value.value) +// inputFromValue 读取宿主授权的随机读取源 +func (e *businessEngine) inputFromValue(value platformValue) (*ofdgo.Input, error) { + reader, size, err := e.resolveInput(e.operationContext(), value.value) + if err != nil { + return nil, err + } + return ofdgo.NewInput(reader, size) } // platformValueOf 包装参数 @@ -1024,11 +1028,12 @@ func (e *businessEngine) bytesFromJS(v platformValue) ([]byte, error) { if data, ok := v.value.([]byte); ok { return append([]byte(nil), data...), nil } - source, size, err := e.sourceFromValue(v) + input, err := e.inputFromValue(v) if err != nil { return nil, err } - return io.ReadAll(io.NewSectionReader(source, 0, size)) + defer input.Close() + return io.ReadAll(io.NewSectionReader(input, 0, input.Size())) } // successResult 保留二进制返回结构 diff --git a/internal/webui/webui_wasm.go b/internal/webui/webui_wasm.go index 4e5e04b..e2b8038 100644 --- a/internal/webui/webui_wasm.go +++ b/internal/webui/webui_wasm.go @@ -21,8 +21,9 @@ import ( "context" "encoding/json" "fmt" - "io" "syscall/js" + + "github.com/xiaoqidun/ofdgo" ) const ( @@ -220,8 +221,11 @@ func platformParseJSON(data []byte) map[string]any { return result } -// sourceFromValue 读取浏览器二进制源 -func (e *businessEngine) sourceFromValue(value platformValue) (io.ReaderAt, int64, error) { +// inputFromValue 读取浏览器二进制源 +func (e *businessEngine) inputFromValue(value platformValue) (*ofdgo.Input, error) { data, err := e.bytesFromJS(value) - return bytes.NewReader(data), int64(len(data)), err + if err != nil { + return nil, err + } + return ofdgo.NewInput(bytes.NewReader(data), int64(len(data))) } diff --git a/internal/webview/webview.go b/internal/webview/webview.go index f48b20d..c28835a 100644 --- a/internal/webview/webview.go +++ b/internal/webview/webview.go @@ -475,11 +475,11 @@ func (h *Host) call(ctx context.Context, name string, value any) (any, error) { } descriptor, _ := args["data"].(map[string]any) key, _ := descriptor["nativeFile"].(string) - source, size, err := h.files.resolve(key) + input, size, err := h.files.resolve(key) if err != nil { return nil, err } - _, err = io.Copy(writer, io.NewSectionReader(source, 0, size)) + _, err = io.Copy(writer, io.NewSectionReader(input, 0, size)) return nil, err case "fonts": h.fontOnce.Do(h.readFonts) diff --git a/ofdgo.go b/ofdgo.go index 718a4ef..6bd19d3 100644 --- a/ofdgo.go +++ b/ofdgo.go @@ -17,18 +17,50 @@ package ofdgo import ( "archive/zip" + "bytes" + "context" + "errors" + "fmt" "image" "io" "io/fs" + "os" + "runtime" + "sync" basefile "github.com/xiaoqidun/ofdgo/internal/base/file" ) -// Open 打开OFD文件 -// 返回的阅读器持有文件,使用完毕后需调用Close -// 入参: path 文件路径, options 阅读选项 -// 返回: *Reader 阅读器实例, error 错误信息 -func Open(path string, options ...ReaderOption) (*Reader, error) { +// Input 统一文件、内存及顺序流的随机读取,可并发读取和关闭,不可复制 +// 调用方应在阅读、编辑及转换结束后调用Close,借入的数据需在此期间保持不变 +// 借入的随机读取器应支持并发ReadAt,Close不会关闭调用方传入的读取器 +type Input struct { + mu sync.RWMutex + reader io.ReaderAt + size int64 + path string + file *os.File + temp bool +} + +// InputOptions 设置顺序输入的缓存及大小限制,单位为字节 +// MemoryLimit为0时使用8MiB,浏览器WASM仅用内存;负值表示仅用内存 +// TempDir为空时使用系统临时目录,不自动创建调用方指定的目录 +// MaxSize为0时不限制输入大小,OpenFile和ReadInput均检查此限制 +// 无临时文件系统的平台可显式使用仅内存模式,并通过MaxSize限制输入 +type InputOptions struct { + MemoryLimit int64 + MaxSize int64 + TempDir string +} + +// OpenFile 打开文件输入,不复制文件内容,Close释放文件句柄 +// 入参: ctx 取消上下文, path 文件路径, options 输入选项 +// 返回: *Input 输入源, error 打开或大小错误 +func OpenFile(ctx context.Context, path string, options InputOptions) (*Input, error) { + if err := ctx.Err(); err != nil { + return nil, err + } f, err := basefile.Open(path) if err != nil { return nil, err @@ -38,34 +70,215 @@ func Open(path string, options ...ReaderOption) (*Reader, error) { f.Close() return nil, err } - r, err := zip.NewReader(f, info.Size()) + if !info.Mode().IsRegular() { + f.Close() + return nil, fmt.Errorf("input is not a regular file") + } + input, err := NewInput(f, info.Size()) if err != nil { f.Close() return nil, err } - reader := &Reader{ - Path: path, - Zip: r, - Closer: f, - } - if err := reader.open(options); err != nil { - reader.Close() + input.file, input.path = f, path + if err := input.checkOptions(ctx, options); err != nil { + input.Close() return nil, err } - return reader, nil + return input, nil } -// NewReader 从IO读取器创建OFD阅读器 -// 读取器的生命周期由调用方管理,Reader.Close不会关闭它 -// 入参: r IO读取器, size 数据大小, options 阅读选项 +// NewInput 借用指定长度的随机读取源,不复制数据或接管其生命周期 +// 入参: reader 随机读取器, size 数据大小 +// 返回: *Input 输入源, error 参数错误 +func NewInput(reader io.ReaderAt, size int64) (*Input, error) { + if reader == nil || size < 0 { + return nil, fmt.Errorf("invalid input") + } + return &Input{reader: reader, size: size}, nil +} + +// ReadInput 从当前位置接收顺序输入,超过内存阈值后转入临时文件 +// 已知大小且支持随机访问的可定位读取器直接复用,不移动其当前位置 +// 不关闭传入流;取消无法中断阻塞的Read,调用方应自行设置超时或关闭流 +// 入参: ctx 取消上下文, reader 输入流, options 输入选项 +// 返回: *Input 输入源, error 读取、缓存或取消错误 +func ReadInput(ctx context.Context, reader io.Reader, options InputOptions) (*Input, error) { + if reader == nil || options.MaxSize < 0 { + return nil, fmt.Errorf("invalid input options") + } + if err := ctx.Err(); err != nil { + return nil, err + } + if random, ok := reader.(io.ReaderAt); ok { + if seeker, ok := reader.(io.Seeker); ok { + var size int64 = -1 + if sized, ok := reader.(interface{ Size() int64 }); ok { + size = sized.Size() + } else if stated, ok := reader.(interface{ Stat() (os.FileInfo, error) }); ok { + info, err := stated.Stat() + if err != nil { + return nil, err + } + if info.Mode().IsRegular() { + size = info.Size() + } + } + if size >= 0 { + offset, err := seeker.Seek(0, io.SeekCurrent) + if err != nil { + return nil, err + } + if offset < 0 || offset > size { + return nil, fmt.Errorf("invalid input offset") + } + input, err := NewInput(io.NewSectionReader(random, offset, size-offset), size-offset) + if err != nil { + return nil, err + } + if err := input.checkOptions(ctx, options); err != nil { + return nil, err + } + return input, nil + } + } + } + limit := options.MemoryLimit + if limit == 0 { + limit = 8 << 20 + if runtime.GOOS == "js" { + limit = -1 + } + } + input := &Input{} + success := false + defer func() { + if !success { + input.Close() + } + }() + var memory bytes.Buffer + buffer := make([]byte, 64<<10) + empty := 0 + for { + if err := ctx.Err(); err != nil { + return nil, err + } + n, err := reader.Read(buffer) + if n < 0 || n > len(buffer) { + return nil, fmt.Errorf("invalid input read count") + } + if err != nil && err != io.EOF { + return nil, err + } + if failure := ctx.Err(); failure != nil { + return nil, failure + } + if n > 0 { + empty = 0 + if input.size > int64(^uint64(0)>>1)-int64(n) || options.MaxSize > 0 && int64(n) > options.MaxSize-input.size { + return nil, fmt.Errorf("input exceeds size limit") + } + if input.file == nil && limit >= 0 && input.size+int64(n) > limit { + file, failure := basefile.NewTemp(options.TempDir) + if failure != nil { + return nil, failure + } + input.file, input.temp = file, true + if _, failure = file.Write(memory.Bytes()); failure != nil { + return nil, failure + } + memory = bytes.Buffer{} + } + var output io.Writer = &memory + if input.file != nil { + output = input.file + } + if _, failure := output.Write(buffer[:n]); failure != nil { + return nil, failure + } + input.size += int64(n) + } else if err == nil { + empty++ + if empty >= 100 { + return nil, io.ErrNoProgress + } + } + if err != nil { + break + } + } + if err := ctx.Err(); err != nil { + return nil, err + } + input.reader = bytes.NewReader(memory.Bytes()) + if input.file != nil { + input.reader = input.file + } + success = true + return input, nil +} + +// checkOptions 检查取消状态及输入大小 +// 入参: ctx 取消上下文, options 输入选项 +// 返回: error 取消或大小错误 +func (s *Input) checkOptions(ctx context.Context, options InputOptions) error { + if options.MaxSize < 0 || options.MaxSize > 0 && s.size > options.MaxSize { + return fmt.Errorf("input exceeds size limit") + } + return ctx.Err() +} + +// Size 返回输入字节数,关闭后仍可查询 +// 返回: int64 数据大小 +func (s *Input) Size() int64 { + return s.size +} + +// ReadAt 读取指定位置的数据,关闭后返回os.ErrClosed +// 入参: data 目标缓冲区, offset 字节偏移 +// 返回: int 读取字节数, error 读取错误 +func (s *Input) ReadAt(data []byte, offset int64) (int, error) { + s.mu.RLock() + defer s.mu.RUnlock() + if s.reader == nil { + return 0, os.ErrClosed + } + return io.NewSectionReader(s.reader, 0, s.size).ReadAt(data, offset) +} + +// Close 释放自有文件和缓存,等待正在进行的读取结束,可重复调用 +// 返回: error 文件关闭或临时文件删除错误 +func (s *Input) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + s.reader = nil + if s.file == nil { + return nil + } + file := s.file + s.file = nil + err := file.Close() + if s.temp { + err = errors.Join(err, os.Remove(file.Name())) + } + return err +} + +// NewReader 从统一输入源创建OFD阅读器,不接管输入源生命周期 +// 阅读器及其派生编辑器使用结束后,调用方再关闭输入源 +// 入参: input 输入源, options 阅读选项 // 返回: *Reader 阅读器实例, error 错误信息 -func NewReader(r io.ReaderAt, size int64, options ...ReaderOption) (*Reader, error) { - zr, err := zip.NewReader(r, size) +func NewReader(input *Input, options ...ReaderOption) (*Reader, error) { + if input == nil { + return nil, fmt.Errorf("missing input") + } + zr, err := zip.NewReader(input, input.Size()) if err != nil { return nil, err } reader := &Reader{ - Zip: zr, + Path: input.path, + Zip: zr, } if err := reader.open(options); err != nil { return nil, err diff --git a/ofdgo_annot.go b/ofdgo_annot.go index 5510280..00893ea 100644 --- a/ofdgo_annot.go +++ b/ofdgo_annot.go @@ -276,7 +276,7 @@ func (r *Reader) parseAnnotations(ctx context.Context, doc *Document) error { continue } var pageAnnot PageAnnot - err = xml.NewDecoder(imageInput{ReadCloser: af, context: ctx}).Decode(&pageAnnot) + err = xml.NewDecoder(imageInput{ReadCloser: af, ctx: ctx}).Decode(&pageAnnot) _ = af.Close() if err != nil { continue @@ -310,7 +310,7 @@ func (r *Reader) annotationIndexContext(ctx context.Context, doc *Document) (Ann } defer f.Close() var annotations Annotations - if err := xml.NewDecoder(imageInput{ReadCloser: f, context: ctx}).Decode(&annotations); err != nil { + if err := xml.NewDecoder(imageInput{ReadCloser: f, ctx: ctx}).Decode(&annotations); err != nil { return Annotations{}, err } pageIDs := make(map[string]string, len(doc.Pages.Page)) diff --git a/ofdgo_archive.go b/ofdgo_archive.go index e17375b..e7d7aa4 100644 --- a/ofdgo_archive.go +++ b/ofdgo_archive.go @@ -27,9 +27,8 @@ import ( // ArchiveFile 描述已生成的归档文件,不关闭调用方提供的源读取器 type ArchiveFile struct { - Name string - Source io.ReaderAt - Size int64 + Name string + Input *Input } // ArchiveOptions 设置ZIP封装,压缩不重新编码归档内已生成的文档 @@ -47,13 +46,13 @@ func WriteArchive(ctx context.Context, writer io.Writer, files []ArchiveFile, op } names := map[string]bool{} for _, file := range files { - if !fs.ValidPath(file.Name) || file.Name == "." || strings.Contains(file.Name, "\\") || names[file.Name] || file.Source == nil || file.Size < 0 { + if !fs.ValidPath(file.Name) || file.Name == "." || strings.Contains(file.Name, "\\") || names[file.Name] || file.Input == nil { return 0, fmt.Errorf("invalid archive file %q", file.Name) } names[file.Name] = true } var count int64 - archive := zip.NewWriter(convertWriter{context: ctx, writer: writer, count: &count}) + archive := zip.NewWriter(convertWriter{ctx: ctx, writer: writer, count: &count}) configureArchive(archive, options.Compression) var buffer []byte for i, file := range files { @@ -77,15 +76,15 @@ func WriteArchive(ctx context.Context, writer io.Writer, files []ArchiveFile, op if err != nil { return count, err } - input := convertReader{context: ctx, source: file.Source} + input := convertReader{ctx: ctx, input: file.Input} if buffer == nil { buffer = make([]byte, 32<<10) } - written, err := io.CopyBuffer(entry, io.NewSectionReader(input, 0, file.Size), buffer) + written, err := io.CopyBuffer(entry, io.NewSectionReader(input, 0, file.Input.Size()), buffer) if err != nil { return count, err } - if written != file.Size { + if written != file.Input.Size() { return count, io.ErrUnexpectedEOF } } diff --git a/ofdgo_compression.go b/ofdgo_compression.go index c9699a8..aea51b5 100644 --- a/ofdgo_compression.go +++ b/ofdgo_compression.go @@ -99,7 +99,7 @@ func (e *Editor) WriteToWithOptions(ctx context.Context, writer io.Writer, optio return 0, err } var count int64 - return snapshot.WriteTo(convertWriter{context: ctx, writer: writer, count: &count}) + return snapshot.WriteTo(convertWriter{ctx: ctx, writer: writer, count: &count}) } // WritePagesToWithOptions 按指定顺序另存页面,仅为所选页面准备资源和压缩计划 @@ -116,7 +116,7 @@ func (e *Editor) WritePagesToWithOptions(ctx context.Context, writer io.Writer, snapshot.OnWriteProgress = editorProgress(e.OnWriteProgress).withContext(ctx) snapshot.output = &outputOptimization{ctx: ctx, options: options.Compression} var count int64 - return snapshot.WritePagesTo(convertWriter{context: ctx, writer: writer, count: &count}, indexes) + return snapshot.WritePagesTo(convertWriter{ctx: ctx, writer: writer, count: &count}, indexes) } // outputSnapshot 创建只读输出配置,原签名存在时仅调整ZIP封装 @@ -211,7 +211,7 @@ func (r *Reader) compressionImageSafety(ctx context.Context, images []ImageInfo) if err != nil { return nil, false, err } - buffer := bufio.NewReader(imageInput{ReadCloser: input, context: ctx}) + buffer := bufio.NewReader(imageInput{ReadCloser: input, ctx: ctx}) found, err := editorXMLPrefix(buffer) if err != nil || !found && (structured || strings.EqualFold(path.Ext(name), ".xml")) { safe = false @@ -432,7 +432,7 @@ func (e *Editor) compressOutputFile(file *zip.File, buffer []byte, limit int) ([ deflater.Reset(output) } defer deflater.Close() - source := imageInput{ReadCloser: input, context: e.output.ctx} + source := imageInput{ReadCloser: input, ctx: e.output.ctx} _, err = io.CopyBuffer(deflater, source, buffer) if output.exceeded { _, err = io.CopyBuffer(io.Discard, source, buffer) @@ -542,7 +542,7 @@ func (e *Editor) writeOutputFile(archive *zip.Writer, file *zip.File, buffer []b if err != nil { return err } - if _, err := io.CopyBuffer(entry, imageInput{ReadCloser: io.NopCloser(payload), context: e.output.ctx}, buffer); err != nil { + if _, err := io.CopyBuffer(entry, imageInput{ReadCloser: io.NopCloser(payload), ctx: e.output.ctx}, buffer); err != nil { return err } e.output.completed++ diff --git a/ofdgo_compression_parallel.go b/ofdgo_compression_parallel.go index d1cb4ef..bb3026f 100644 --- a/ofdgo_compression_parallel.go +++ b/ofdgo_compression_parallel.go @@ -208,7 +208,7 @@ func imageWorkingSet(ctx context.Context, data []byte, limit int) int { } return len(data) + tiffImageWorkingSet(layout, available) } - config, _, err := baseimage.DecodeConfig(imageInput{ReadCloser: io.NopCloser(input), context: ctx}) + config, _, err := baseimage.DecodeConfig(imageInput{ReadCloser: io.NopCloser(input), ctx: ctx}) if err != nil || config.Width <= 0 || config.Height <= 0 { return limit } diff --git a/ofdgo_compression_pattern.go b/ofdgo_compression_pattern.go index aaf6fab..0392c4b 100644 --- a/ofdgo_compression_pattern.go +++ b/ofdgo_compression_pattern.go @@ -67,7 +67,7 @@ func (e *Editor) compressPatternPaints() error { } hash := sha256.New() var size int64 - x := newOFDXML(convertWriter{writer: hash, count: &size, context: e.output.ctx}) + x := newOFDXML(convertWriter{writer: hash, count: &size, ctx: e.output.ctx}) x.ctx = e.output.ctx x.color("FillColor", fill) x.color("StrokeColor", (*FillColor)(stroke)) diff --git a/ofdgo_compression_raster.go b/ofdgo_compression_raster.go index 1215758..194743d 100644 --- a/ofdgo_compression_raster.go +++ b/ofdgo_compression_raster.go @@ -108,7 +108,7 @@ func optimizeBMPResource(ctx context.Context, data []byte, options CompressionOp if depth == 16 || compression == 3 { pixels, err = baseimage.DecodeBMPContext(ctx, data) } else { - pixels, _, err = image.Decode(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + pixels, _, err = image.Decode(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) } if err != nil { return nil, err diff --git a/ofdgo_compression_resources.go b/ofdgo_compression_resources.go index ab75a67..560ef5f 100644 --- a/ofdgo_compression_resources.go +++ b/ofdgo_compression_resources.go @@ -460,7 +460,7 @@ func outputResourceDigest(ctx context.Context, reader *Reader, parts map[string] } } hash := sha256.New() - _, err := io.CopyBuffer(hash, imageInput{ReadCloser: input, context: ctx}, buffer) + _, err := io.CopyBuffer(hash, imageInput{ReadCloser: input, ctx: ctx}, buffer) closeErr := input.Close() if err != nil { return [32]byte{}, err diff --git a/ofdgo_convert.go b/ofdgo_convert.go index 38c0073..519cd61 100644 --- a/ofdgo_convert.go +++ b/ofdgo_convert.go @@ -84,15 +84,15 @@ type ConvertReport struct { // convertReader 将取消上下文传递到随机读取 type convertReader struct { - context context.Context - source io.ReaderAt + ctx context.Context + input io.ReaderAt } // convertWriter 将取消上下文传递到写入并统计实际字节数 type convertWriter struct { - context context.Context - writer io.Writer - count *int64 + ctx context.Context + writer io.Writer + count *int64 } // OutputFormats 返回独立的输出格式列表,不依赖WebUI或具体渲染后端 @@ -113,9 +113,9 @@ func OutputFormats() []OutputFormat { // 原始模式完整OFD另存保持源字节;选页和跨格式输出重新生成文档,压缩仅作用于输出副本 // 输入按需随机读取,输出分块写出;解析对象及后端可能缓存资源,不保证恒定内存 // 出错时报告保留已有诊断,调用方应丢弃未完成输出;只有返回nil才表示转换成功 -// 入参: ctx 取消上下文, source 源数据, size 源字节数, output 输出流, options 转换选项 +// 入参: ctx 取消上下文, input 输入源, output 输出流, options 转换选项 // 返回: ConvertReport 转换报告, error 错误信息 -func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writer, options ConvertOptions) (report ConvertReport, err error) { +func Convert(ctx context.Context, input *Input, output io.Writer, options ConvertOptions) (report ConvertReport, err error) { if options.Pool != nil { if err = options.Pool.acquire(ctx, false, 0); err != nil { return report, err @@ -174,13 +174,14 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ if err = progress("open", 0, 0); err != nil { return report, err } - if source == nil || size <= 0 { - return report, fmt.Errorf("empty document source") + if input == nil || input.Size() <= 0 { + return report, fmt.Errorf("empty document input") } - source = convertReader{context: ctx, source: source} + size := input.Size() + operationInput := &Input{reader: convertReader{ctx: ctx, input: input}, size: size} report.Input = strings.ToLower(strings.TrimSpace(options.Input)) if report.Input == "" { - report.Input, err = DetectFormat(source, size) + report.Input, err = DetectFormat(operationInput) if err != nil { return report, err } @@ -199,7 +200,7 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ return report, fmt.Errorf("missing output writer") } if report.Input == "pdf" && format == "pdf" && options.Compression.Mode != CompressionUnchanged && options.Pages == nil && strings.TrimSpace(options.PageRange) == "" { - pdfReader, openErr := pdfgo.NewReaderWithOptions(source, size, pdfgo.ReaderOptions{Password: options.PDF.Password, PasswordUTF8: options.PDF.PasswordUTF8}) + pdfReader, openErr := pdfgo.NewReaderWithOptions(operationInput, size, pdfgo.ReaderOptions{Password: options.PDF.Password, PasswordUTF8: options.PDF.PasswordUTF8}) if openErr != nil { return report, openErr } @@ -233,13 +234,13 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ } return nil } - editor, report.PDF, err = ImportPDF(ctx, source, size, pdfOptions) + editor, report.PDF, err = ImportPDF(ctx, operationInput, pdfOptions) if err != nil { return report, err } count = editor.PageCount() case "ofd": - reader, err = NewReader(source, size, options.ReaderOptions...) + reader, err = NewReader(operationInput, options.ReaderOptions...) if err != nil { return report, err } @@ -260,13 +261,13 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ return report, err } report.Pages = len(indices) - writer := convertWriter{context: ctx, writer: output, count: &report.Bytes} + writer := convertWriter{ctx: ctx, writer: output, count: &report.Bytes} if format == "ofd" { if options.Pages == nil && editor == nil && options.Fonts == FontKeep && options.Compression.Mode == CompressionUnchanged { if err = progress("write", 0, 0); err != nil { return report, err } - _, err = io.Copy(writer, io.NewSectionReader(source, 0, size)) + _, err = io.Copy(writer, io.NewSectionReader(operationInput, 0, size)) report.Unchanged = err == nil return report, err } @@ -301,7 +302,7 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ if err = progress("write", 0, 0); err != nil { return report, err } - _, err = io.Copy(writer, io.NewSectionReader(source, 0, size)) + _, err = io.Copy(writer, io.NewSectionReader(operationInput, 0, size)) report.Unchanged = err == nil return report, err } @@ -352,7 +353,7 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ return report, readErr } if err = options.PageOutput(index, func(output io.Writer) error { - return renderer.RenderTo(page, convertWriter{context: ctx, writer: output, count: &report.Bytes}, format) + return renderer.RenderTo(page, convertWriter{ctx: ctx, writer: output, count: &report.Bytes}, format) }); err != nil { return report, fmt.Errorf("export page %d: %w", index+1, err) } @@ -366,12 +367,12 @@ func Convert(ctx context.Context, source io.ReaderAt, size int64, output io.Writ // 入参: data 目标缓冲, offset 源偏移 // 返回: int 读取字节数, error 读取错误 func (r convertReader) ReadAt(data []byte, offset int64) (int, error) { - if err := r.context.Err(); err != nil { + if err := r.ctx.Err(); err != nil { return 0, err } - n, err := r.source.ReadAt(data, offset) + n, err := r.input.ReadAt(data, offset) if err == nil { - err = r.context.Err() + err = r.ctx.Err() } return n, err } @@ -380,7 +381,7 @@ func (r convertReader) ReadAt(data []byte, offset int64) (int, error) { // 入参: data 待写数据 // 返回: int 写入字节数, error 写入错误 func (w convertWriter) Write(data []byte) (int, error) { - if err := w.context.Err(); err != nil { + if err := w.ctx.Err(); err != nil { return 0, err } n, err := w.writer.Write(data) @@ -389,7 +390,7 @@ func (w convertWriter) Write(data []byte) (int, error) { err = io.ErrShortWrite } if err == nil { - err = w.context.Err() + err = w.ctx.Err() } return n, err } @@ -398,7 +399,7 @@ func (w convertWriter) Write(data []byte) (int, error) { // 入参: data 待写文本 // 返回: int 写入字节数, error 写入、短写或取消错误 func (w convertWriter) WriteString(data string) (int, error) { - if err := w.context.Err(); err != nil { + if err := w.ctx.Err(); err != nil { return 0, err } n, err := io.WriteString(w.writer, data) @@ -407,7 +408,7 @@ func (w convertWriter) WriteString(data string) (int, error) { err = io.ErrShortWrite } if err == nil { - err = w.context.Err() + err = w.ctx.Err() } return n, err } diff --git a/ofdgo_crypto_editor.go b/ofdgo_crypto_editor.go index a1e56af..cb4f34e 100644 --- a/ofdgo_crypto_editor.go +++ b/ofdgo_crypto_editor.go @@ -64,7 +64,7 @@ func (e *Editor) writeEncrypted(writer io.Writer) (int64, error) { return 0, err } defer clear(plaintext.Bytes()) - r, err := NewReader(bytes.NewReader(plaintext.Bytes()), int64(plaintext.Len())) + r, err := NewReader(&Input{reader: bytes.NewReader(plaintext.Bytes()), size: int64(plaintext.Len())}) if err != nil { return 0, err } diff --git a/ofdgo_crypto_package.go b/ofdgo_crypto_package.go index 22eb40a..17e5823 100644 --- a/ofdgo_crypto_package.go +++ b/ofdgo_crypto_package.go @@ -93,7 +93,7 @@ type encryptionEntry struct { // 入参: data 明文OFD包, options 加密策略 // 返回: []byte 加密OFD包, error 错误信息 func EncryptPackage(data []byte, options EncryptionOptions) ([]byte, error) { - r, err := NewReader(bytes.NewReader(data), int64(len(data))) + r, err := NewReader(&Input{reader: bytes.NewReader(data), size: int64(len(data))}) if err != nil { return nil, err } diff --git a/ofdgo_editor_action.go b/ofdgo_editor_action.go index 0ef4841..8fdc2ce 100644 --- a/ofdgo_editor_action.go +++ b/ofdgo_editor_action.go @@ -66,10 +66,10 @@ func (e *Editor) SetDocumentActions(actions []Action) error { return err } patch := editorXMLPatch{root.close, root.close, encoded} - context := root + ctx := root if node := root.child("Actions"); node != nil { patch.start, patch.end = node.start, node.end - context = node + ctx = node } else { for _, child := range root.children { if child.matchesOFD(child.name.Local) && slices.Contains([]string{"VPreferences", "Bookmarks", "Annotations", "CustomTags", "Attachments", "Extensions"}, child.name.Local) { @@ -78,7 +78,7 @@ func (e *Editor) SetDocumentActions(actions []Action) error { } } } - patch.data, err = editorXMLGenerated(encoded, context.name.Space) + patch.data, err = editorXMLGenerated(encoded, ctx.name.Space) if err != nil { return err } diff --git a/ofdgo_editor_bookmark.go b/ofdgo_editor_bookmark.go index b1bc7a6..b12347e 100644 --- a/ofdgo_editor_bookmark.go +++ b/ofdgo_editor_bookmark.go @@ -396,19 +396,19 @@ func editorBookmarkDefinition(data []byte, name string, next *Bookmark, add bool } container := root.child("Bookmarks") if add { - context := container - if context == nil { - context = root + ctx := container + if ctx == nil { + ctx = root } attrs, err := next.Dest.attributes() if err != nil { return nil, err } - dest, err := context.containerXML("Dest", attrs, nil) + dest, err := ctx.containerXML("Dest", attrs, nil) if err != nil { return nil, err } - item, err := context.containerXML("Bookmark", ofdAttrs{{Name: xml.Name{Local: "Name"}, Value: next.Name}}, dest) + item, err := ctx.containerXML("Bookmark", ofdAttrs{{Name: xml.Name{Local: "Name"}, Value: next.Name}}, dest) if err != nil { return nil, err } diff --git a/ofdgo_editor_font_binding.go b/ofdgo_editor_font_binding.go index 81bc808..c4f7868 100644 --- a/ofdgo_editor_font_binding.go +++ b/ofdgo_editor_font_binding.go @@ -311,7 +311,7 @@ func (e *Editor) prepareFontBinding(ctx context.Context, scan *editorFontDocumen } file, node := text.file, text.node var object TextObject - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(file.data[node.start:node.end])), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(file.data[node.start:node.end])), ctx: ctx}) if err := decoder.Decode(&object); err != nil { return nil, err } diff --git a/ofdgo_editor_font_cleanup.go b/ofdgo_editor_font_cleanup.go index 9fc2d93..84aca90 100644 --- a/ofdgo_editor_font_cleanup.go +++ b/ofdgo_editor_font_cleanup.go @@ -129,7 +129,7 @@ func (r *Reader) fontCleanupCandidate(ctx context.Context, name string) (bool, e } else if file, ok := r.packageFile(name); ok { size = file.UncompressedSize64 } - candidate, err := fontCleanupDirectory(imageInput{ReadCloser: input, context: ctx}, size) + candidate, err := fontCleanupDirectory(imageInput{ReadCloser: input, ctx: ctx}, size) if err == io.EOF || err == io.ErrUnexpectedEOF { err = nil } diff --git a/ofdgo_editor_image_binary.go b/ofdgo_editor_image_binary.go index eb4d2e8..7cdd3ad 100644 --- a/ofdgo_editor_image_binary.go +++ b/ofdgo_editor_image_binary.go @@ -51,7 +51,7 @@ func editorBinaryImage(ctx context.Context, data []byte, img image.Image) ([]byt return nil, ctx.Err() } var err error - img, _, err = image.Decode(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + img, _, err = image.Decode(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err != nil { return nil, ctx.Err() } diff --git a/ofdgo_editor_metadata.go b/ofdgo_editor_metadata.go index 651ed96..6e8162c 100644 --- a/ofdgo_editor_metadata.go +++ b/ofdgo_editor_metadata.go @@ -185,9 +185,9 @@ func editorDocInfoXML(data []byte, docID string, index int) ([]byte, *editorXML, // 返回: []byte 修改后的XML, error 编码错误 func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byte, error) { container := info.child("CustomDatas") - context := info + ctx := info if container != nil { - context = container + ctx = container } var items []CustomData if values != nil { @@ -226,7 +226,7 @@ func editorCustomDatas(data []byte, info *editorXML, values *CustomDatas) ([]byt if node == nil { var value bytes.Buffer _ = xml.EscapeText(&value, []byte(item.Value)) - entry, err := context.containerXML("CustomData", ofdAttrs{{Name: xml.Name{Local: "Name"}, Value: item.Name}}, value.Bytes()) + entry, err := ctx.containerXML("CustomData", ofdAttrs{{Name: xml.Name{Local: "Name"}, Value: item.Name}}, value.Bytes()) if err != nil { return nil, err } diff --git a/ofdgo_editor_outline.go b/ofdgo_editor_outline.go index 5e7ab48..f09d9c3 100644 --- a/ofdgo_editor_outline.go +++ b/ofdgo_editor_outline.go @@ -205,17 +205,17 @@ func (e *Editor) SetOutlineActions(path []int, actions []Action) error { if err != nil { return err } - context := node + ctx := node if existing := node.child("Actions"); existing != nil { - context = existing + ctx = existing } - encoded, err = editorXMLGenerated(encoded, context.name.Space) + encoded, err = editorXMLGenerated(encoded, ctx.name.Space) if err != nil { return err } var patch editorXMLPatch - if context != node { - patch = editorXMLPatch{context.start, context.end, encoded} + if ctx != node { + patch = editorXMLPatch{ctx.start, ctx.end, encoded} } else { if node.open != node.end { encoded = append(encoded, data[node.open:node.close]...) diff --git a/ofdgo_editor_package.go b/ofdgo_editor_package.go index 10e5a23..b0be6ed 100644 --- a/ofdgo_editor_package.go +++ b/ofdgo_editor_package.go @@ -101,7 +101,7 @@ func (e *Editor) sourcePartsPrepared(ctx context.Context, progress editorProgres } generated[name] = known if (reader.DocumentCount() > 1 || reader.OFD.DocBody[reader.documentIndex].versioned) && !known.stagedLeaf(nil) || bound && known.objects == nil { - input := imageInput{ReadCloser: data.open(), context: ctx} + input := imageInput{ReadCloser: data.open(), ctx: ctx} parts[name], err = io.ReadAll(input) input.Close() if err != nil { @@ -454,15 +454,15 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err } } var added []byte - context := source.root.child("Content") - if context == nil { - context = source.root + ctx := source.root.child("Content") + if ctx == nil { + ctx = source.root } for _, layer := range page.Content.Layer[len(source.original.Content.Layer):] { if len(layer.Objects) == 0 { continue } - encoded, err := e.sourceLayerXML(layer, context.name.Space) + encoded, err := e.sourceLayerXML(layer, ctx.name.Space) if err != nil { return nil, err } @@ -471,7 +471,7 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err if len(added) != 0 { content := source.root.child("Content") if content == nil { - value, err := context.containerXML("Content", nil, added) + value, err := ctx.containerXML("Content", nil, added) if err != nil { return nil, err } @@ -492,12 +492,12 @@ func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, err return nil, err } patch := editorXMLPatch{source.root.close, source.root.close, encoded} - context := source.root + ctx := source.root if node := source.root.child("Actions"); node != nil { patch.start, patch.end = node.start, node.end - context = node + ctx = node } - patch.data, err = editorXMLGenerated(encoded, context.name.Space) + patch.data, err = editorXMLGenerated(encoded, ctx.name.Space) if err != nil { return nil, err } diff --git a/ofdgo_editor_pages.go b/ofdgo_editor_pages.go index f0cad25..fdc22d4 100644 --- a/ofdgo_editor_pages.go +++ b/ofdgo_editor_pages.go @@ -88,7 +88,7 @@ func (e *Editor) pruneDocumentReferences(ctx context.Context, parts map[string][ return err } } else if known := generated[name]; data == nil && known.staged != nil { - input := imageInput{ReadCloser: known.staged.open(), context: ctx} + input := imageInput{ReadCloser: known.staged.open(), ctx: ctx} var err error data, err = io.ReadAll(input) input.Close() @@ -196,7 +196,7 @@ func (e *Editor) pruneDocumentReferences(ctx context.Context, parts map[string][ // 入参: ctx 取消上下文, data 创作XML, removed 已删除页面标识 // 返回: []byte 更新后的XML, error 解析或取消错误 func pruneCreatedPageReferences(ctx context.Context, data []byte, removed map[string]bool) ([]byte, error) { - d := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + d := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) var stack []pageReferenceNode var patches []editorXMLPatch root := false diff --git a/ofdgo_editor_resource_scan.go b/ofdgo_editor_resource_scan.go index dd34997..c6ac1bf 100644 --- a/ofdgo_editor_resource_scan.go +++ b/ofdgo_editor_resource_scan.go @@ -184,7 +184,7 @@ func editorResourceDefinitions(ctx context.Context, data []byte, name string, so result.definitions = make(map[string]*editorResourceRefs) outer := &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} scan := editorReferenceScan{ctx: ctx, refs: outer, name: name, safe: true} - input := bufio.NewReader(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + input := bufio.NewReader(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if found, err := editorXMLPrefix(input); err != nil || !found { return nil, err } diff --git a/ofdgo_editor_resource_style.go b/ofdgo_editor_resource_style.go index e2b8815..5d855d2 100644 --- a/ofdgo_editor_resource_style.go +++ b/ofdgo_editor_resource_style.go @@ -109,7 +109,7 @@ func (c *resourceCatalog) styleDefinition(ctx context.Context, item ResourceInfo if err != nil { return err } - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(target); err != nil { return err } @@ -488,7 +488,7 @@ func validateResourceColors(ctx context.Context, catalog *resourceCatalog, space } if resourceColorNode(node) && sameResourceID(id, space.ID) { var paint FillColor - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(file.data[node.start:node.end])), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(file.data[node.start:node.end])), ctx: ctx}) if err := decoder.Decode(&paint); err != nil { return err } diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index 486ebb2..e63f90a 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -335,7 +335,7 @@ func (r *editorResourceRefs) scanContext(ctx context.Context, input io.Reader, n if ctx.Err() != nil { return false, false } - input = imageInput{ReadCloser: io.NopCloser(input), context: ctx} + input = imageInput{ReadCloser: io.NopCloser(input), ctx: ctx} } buffer := bufio.NewReader(input) found, err := editorXMLPrefix(buffer) diff --git a/ofdgo_extract_image.go b/ofdgo_extract_image.go index 08cd403..b919d21 100644 --- a/ofdgo_extract_image.go +++ b/ofdgo_extract_image.go @@ -37,14 +37,14 @@ type ImageInfo struct { // imageOutput 在分块写出图片时检查调用方取消状态 type imageOutput struct { - context context.Context - writer io.Writer + ctx context.Context + writer io.Writer } // imageInput 在读取资源数据时检查调用方取消状态 type imageInput struct { io.ReadCloser - context context.Context + ctx context.Context } // imageResourceKey 合并有效十进制标识,非标准标识保留原值,不相互混淆 @@ -61,7 +61,7 @@ func imageResourceKey(id string) string { // 入参: data 数据缓冲区 // 返回: int 读取字节数, error 读取或取消错误 func (r imageInput) Read(data []byte) (int, error) { - if err := r.context.Err(); err != nil { + if err := r.ctx.Err(); err != nil { return 0, err } return r.ReadCloser.Read(data) @@ -71,7 +71,7 @@ func (r imageInput) Read(data []byte) (int, error) { // 入参: data 数据块 // 返回: int 写入字节数, error 写入或取消错误 func (w imageOutput) Write(data []byte) (int, error) { - if err := w.context.Err(); err != nil { + if err := w.ctx.Err(); err != nil { return 0, err } return w.writer.Write(data) @@ -128,7 +128,7 @@ func (r *Reader) Images(ctx context.Context) ([]ImageInfo, error) { return nil, err } var res Res - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(&res); err != nil { return nil, err } @@ -185,7 +185,7 @@ func (r *Reader) Image(ctx context.Context, id string) (ImageInfo, error) { return ImageInfo{}, err } var res Res - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(&res); err != nil { return ImageInfo{}, err } @@ -233,7 +233,7 @@ func (r *Reader) OpenImage(ctx context.Context, id string) (io.ReadCloser, error if err != nil { return nil, err } - return imageInput{ReadCloser: input, context: ctx}, nil + return imageInput{ReadCloser: input, ctx: ctx}, nil } // WriteImages 将原始图片逐个写入ZIP,不改变图片编码,文件序号避免同名覆盖 diff --git a/ofdgo_format.go b/ofdgo_format.go index 6eab94e..5a9378b 100644 --- a/ofdgo_format.go +++ b/ofdgo_format.go @@ -24,16 +24,16 @@ import ( // DetectFormat 按文件内容识别PDF或OFD,不读取压缩内容或关闭输入 // 识别结果不代表文档完整有效,仍需通过对应阅读器解析 -// 入参: source 随机读取源, size 文件字节数 +// 入参: input 输入源 // 返回: string 格式标识pdf或ofd, error 读取错误或不支持的格式 -func DetectFormat(source io.ReaderAt, size int64) (string, error) { - if source == nil || size <= 0 { - return "", fmt.Errorf("empty document source") +func DetectFormat(input *Input) (string, error) { + if input == nil || input.Size() <= 0 { + return "", fmt.Errorf("empty document input") } - source = io.NewSectionReader(source, 0, size) + size := input.Size() var header [1024]byte data := header[:min(size, int64(len(header)))] - n, err := source.ReadAt(data, 0) + n, err := input.ReadAt(data, 0) if err != nil && err != io.EOF { return "", err } @@ -43,7 +43,7 @@ func DetectFormat(source io.ReaderAt, size int64) (string, error) { if bytes.HasPrefix(data, []byte("%PDF-")) { return "pdf", nil } - archive, err := zip.NewReader(source, size) + archive, err := zip.NewReader(input, size) if err == nil { reader := &Reader{Zip: archive} if err := reader.indexPackage(); err != nil { diff --git a/ofdgo_invoice.go b/ofdgo_invoice.go index 605239c..7f5c6e8 100644 --- a/ofdgo_invoice.go +++ b/ofdgo_invoice.go @@ -344,7 +344,7 @@ func (r *Reader) invoiceData(ctx context.Context, location string) ([]byte, erro return nil, err } defer stream.Close() - data, err := io.ReadAll(io.LimitReader(imageInput{ReadCloser: stream, context: ctx}, invoiceXMLLimit+1)) + data, err := io.ReadAll(io.LimitReader(imageInput{ReadCloser: stream, ctx: ctx}, invoiceXMLLimit+1)) if err != nil { return data, err } diff --git a/ofdgo_package.go b/ofdgo_package.go index 24f1a28..104e5f8 100644 --- a/ofdgo_package.go +++ b/ofdgo_package.go @@ -104,7 +104,7 @@ func packageDataEqual(ctx context.Context, reader *Reader, name string, data []b return false, err } defer stream.Close() - input := imageInput{ReadCloser: stream, context: ctx} + input := imageInput{ReadCloser: stream, ctx: ctx} var buffer [32 << 10]byte for len(data) != 0 { block := buffer[:min(len(buffer), len(data))] diff --git a/ofdgo_pdf.go b/ofdgo_pdf.go index f5365b4..9805366 100644 --- a/ofdgo_pdf.go +++ b/ofdgo_pdf.go @@ -203,9 +203,13 @@ func (o pdfObjectOrigin) merge(other pdfObjectOrigin) pdfObjectOrigin { } // ImportPDF 将PDF内容转换为独立OFD编辑文档,失败时不返回部分结果 -// 入参: ctx 取消上下文, source PDF数据, size 字节数, options 转换选项 +// 返回的编辑文档独立于输入源,函数返回后可关闭输入源 +// 入参: ctx 取消上下文, input 输入源, options 转换选项 // 返回: *Editor 编辑文档, PDFImportReport 转换统计, error 错误信息 -func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFImportOptions) (*Editor, PDFImportReport, error) { +func ImportPDF(ctx context.Context, input *Input, options PDFImportOptions) (*Editor, PDFImportReport, error) { + if input == nil { + return nil, PDFImportReport{}, fmt.Errorf("missing input") + } if err := ctx.Err(); err != nil { return nil, PDFImportReport{}, err } @@ -241,7 +245,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI readWarning = func(diagnostic pdfgo.Diagnostic) { diagnostics = append(diagnostics, diagnostic) } readOptions.Warning = func(diagnostic pdfgo.Diagnostic) { readWarning(diagnostic) } } - reader, err := pdfgo.NewReaderWithOptions(convertReader{context: ctx, source: source}, size, readOptions) + reader, err := pdfgo.NewReaderWithOptions(convertReader{ctx: ctx, input: input}, input.Size(), readOptions) if err != nil { return nil, PDFImportReport{}, err } @@ -370,10 +374,10 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI } // ConvertPDF 将PDF转换并写入OFD,转换阶段失败时不写入目标 -// 入参: ctx 取消上下文, source PDF数据, size 字节数, output OFD输出, options 转换选项 +// 入参: ctx 取消上下文, input 输入源, output OFD输出, options 转换选项 // 返回: PDFImportReport 转换统计, error 错误信息 -func ConvertPDF(ctx context.Context, source io.ReaderAt, size int64, output io.Writer, options PDFImportOptions) (PDFImportReport, error) { - editor, report, err := ImportPDF(ctx, source, size, options) +func ConvertPDF(ctx context.Context, input *Input, output io.Writer, options PDFImportOptions) (PDFImportReport, error) { + editor, report, err := ImportPDF(ctx, input, options) if err != nil { return PDFImportReport{}, err } diff --git a/ofdgo_reader.go b/ofdgo_reader.go index 7b1fdf2..381ce85 100644 --- a/ofdgo_reader.go +++ b/ofdgo_reader.go @@ -140,7 +140,7 @@ func (r *Reader) DocumentPageCountContext(ctx context.Context, index int) (int, return 0, err } var doc documentPageList - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(&doc); err != nil { return 0, fmt.Errorf("failed to unmarshal document.xml: %w", err) } @@ -278,7 +278,7 @@ func (r *Reader) docStructureContext(ctx context.Context) (*Document, error) { return nil, err } var doc Document - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(&doc); err != nil { return nil, fmt.Errorf("failed to unmarshal document.xml: %w", err) } @@ -581,7 +581,7 @@ func (r *Reader) OpenAttachmentContext(ctx context.Context, id string) (io.ReadC stream.Close() return nil, err } - return imageInput{ReadCloser: stream, context: ctx}, nil + return imageInput{ReadCloser: stream, ctx: ctx}, nil } } if err := ctx.Err(); err != nil { @@ -733,7 +733,7 @@ func (r *Reader) initRootContext(ctx context.Context) error { return fmt.Errorf("failed to read ofd.xml: %w", err) } var ofd OFD - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(&ofd); err != nil { return fmt.Errorf("failed to unmarshal ofd.xml: %w", err) } @@ -856,7 +856,7 @@ func (r *Reader) readFileViewContext(ctx context.Context, name string) ([]byte, return nil, err } defer input.Close() - data, err := io.ReadAll(imageInput{ReadCloser: input, context: ctx}) + data, err := io.ReadAll(imageInput{ReadCloser: input, ctx: ctx}) if err != nil { return nil, err } @@ -949,7 +949,7 @@ func (r *Reader) loadResContext(ctx context.Context, resPath string) error { return ctx.Err() } var res Res - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(&res); err != nil { return ctx.Err() } @@ -1059,7 +1059,7 @@ func (r *Reader) readPageContent(ctx context.Context, page Page, textOnly, templ return nil, err } var content PageContent - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if textOnly { err = xml.NewTokenDecoder(&textPageTokens{Decoder: decoder}).Decode(&content) } else { @@ -1123,7 +1123,7 @@ func (r *Reader) readPageHeaderContext(ctx context.Context, page Page) (PageCont } defer f.Close() var content PageContent - d := xml.NewDecoder(imageInput{ReadCloser: f, context: ctx}) + d := xml.NewDecoder(imageInput{ReadCloser: f, ctx: ctx}) var root xml.Name scan: for { @@ -1239,7 +1239,7 @@ func (r *Reader) readDocumentPartContext(ctx context.Context, loc string, value if err != nil { return "", err } - decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx}) if err := decoder.Decode(value); err != nil { return "", fmt.Errorf("failed to unmarshal %s: %w", path.Base(fullPath), err) } diff --git a/ofdgo_reader_dependencies.go b/ofdgo_reader_dependencies.go index 7fe16bb..4c0498b 100644 --- a/ofdgo_reader_dependencies.go +++ b/ofdgo_reader_dependencies.go @@ -247,7 +247,7 @@ func (r *Reader) scanDocumentFileReferences(ctx context.Context, name string, re return err } defer input.Close() - decoder := xml.NewDecoder(imageInput{ReadCloser: input, context: ctx}) + decoder := xml.NewDecoder(imageInput{ReadCloser: input, ctx: ctx}) var stack []xml.Name var positions []*editorXML root, base := "", "" diff --git a/ofdgo_render_compile_image.go b/ofdgo_render_compile_image.go index 35d8456..4576145 100644 --- a/ofdgo_render_compile_image.go +++ b/ofdgo_render_compile_image.go @@ -161,7 +161,7 @@ func (c *semanticCompiler) drawStampContent(stamp Stamp) error { return err } if stamp.Type == "ofd" { - reader, err := NewReader(bytes.NewReader(stamp.Data), int64(len(stamp.Data))) + reader, err := NewReader(&Input{reader: bytes.NewReader(stamp.Data), size: int64(len(stamp.Data))}) if err != nil { return err } diff --git a/ofdgo_render_output.go b/ofdgo_render_output.go index f566494..a8f4991 100644 --- a/ofdgo_render_output.go +++ b/ofdgo_render_output.go @@ -120,7 +120,7 @@ func (r *Renderer) RenderToZIP(writer io.Writer, format string, indices ...int) return err } var countWritten int64 - archive := zip.NewWriter(convertWriter{context: ctx, writer: writer, count: &countWritten}) + archive := zip.NewWriter(convertWriter{ctx: ctx, writer: writer, count: &countWritten}) configureArchive(archive, r.Compression) method := zip.Deflate if extension == "png" || extension == "jpg" { @@ -176,7 +176,7 @@ func (r *Renderer) RenderToText(page *PageContent, writer io.Writer) error { return err } var written int64 - writer = convertWriter{context: ctx, writer: writer, count: &written} + writer = convertWriter{ctx: ctx, writer: writer, count: &written} _, err = text.WriteTo(writer) if err != nil { return err @@ -207,7 +207,7 @@ func (r *Renderer) RenderToMultiPageText(writer io.Writer, indices ...int) error return err } var count int64 - writer = convertWriter{context: ctx, writer: writer, count: &count} + writer = convertWriter{ctx: ctx, writer: writer, count: &count} written := false for i, index := range indices { text, err := r.PageTextByIndexContext(ctx, index) @@ -248,7 +248,7 @@ func (r *Renderer) RenderToPNG(page *PageContent, writer io.Writer) error { return err } var written int64 - writer = convertWriter{context: r.outputContext(), writer: writer, count: &written} + writer = convertWriter{ctx: r.outputContext(), writer: writer, count: &written} img, err := r.RenderToImage(page) if err != nil { return err @@ -277,7 +277,7 @@ func (r *Renderer) RenderToJPEG(page *PageContent, writer io.Writer, options *jp return err } var written int64 - writer = convertWriter{context: r.outputContext(), writer: writer, count: &written} + writer = convertWriter{ctx: r.outputContext(), writer: writer, count: &written} img, err := r.RenderToImage(page) if err != nil { return err @@ -340,7 +340,7 @@ func (r *Renderer) RenderToEPS(page *PageContent, writer io.Writer) error { return fmt.Errorf("eps: %w", ErrBackendUnavailable) } var written int64 - writer = convertWriter{context: r.outputContext(), writer: writer, count: &written} + writer = convertWriter{ctx: r.outputContext(), writer: writer, count: &written} return r.backends.EPS.RenderEPS(r, page, writer) } diff --git a/ofdgo_render_svg.go b/ofdgo_render_svg.go index faf4908..8bfa701 100644 --- a/ofdgo_render_svg.go +++ b/ofdgo_render_svg.go @@ -73,6 +73,6 @@ func (r *Renderer) renderSVG(page *PageContent, writer io.Writer, mode SVGMode) return SVGResources{}, fmt.Errorf("svg: %w", ErrBackendUnavailable) } var written int64 - writer = convertWriter{context: r.outputContext(), writer: writer, count: &written} + writer = convertWriter{ctx: r.outputContext(), writer: writer, count: &written} return r.backends.SVG.RenderSVG(r, page, writer, mode) } diff --git a/ofdgo_renderer_page_canvas.go b/ofdgo_renderer_page_canvas.go index 6e175eb..9c14a21 100644 --- a/ofdgo_renderer_page_canvas.go +++ b/ofdgo_renderer_page_canvas.go @@ -21,7 +21,7 @@ import ( // canvasPageVisitor 将公共页面遍历结果交给Canvas解释器 type canvasPageVisitor struct { renderer *Renderer - context *canvas.Context + ctx *canvas.Context height float64 } @@ -98,11 +98,11 @@ func (v *canvasPageVisitor) DrawObject(object *GraphicObject, state RenderState) } switch object.Type { case "TextObject": - v.renderer.renderText(v.context, object.TextObject, v.height, state.Defaults, state.Parent, state.BoundaryInCTM, clip) + v.renderer.renderText(v.ctx, object.TextObject, v.height, state.Defaults, state.Parent, state.BoundaryInCTM, clip) case "PathObject": - v.renderer.renderPath(v.context, object.PathObject, v.height, state.Defaults, state.Parent, state.BoundaryInCTM, clip) + v.renderer.renderPath(v.ctx, object.PathObject, v.height, state.Defaults, state.Parent, state.BoundaryInCTM, clip) case "ImageObject": - v.renderer.renderImage(v.context, object.ImageObject, v.height, state.Parent, state.BoundaryInCTM, clip) + v.renderer.renderImage(v.ctx, object.ImageObject, v.height, state.Parent, state.BoundaryInCTM, clip) } return v.renderer.renderError } @@ -111,7 +111,7 @@ func (v *canvasPageVisitor) DrawObject(object *GraphicObject, state RenderState) // 入参: stamp 签章 // 返回: error 绘制错误 func (v *canvasPageVisitor) DrawStamp(stamp Stamp) error { - v.renderer.renderStamp(v.context, stamp, v.height) + v.renderer.renderStamp(v.ctx, stamp, v.height) return v.renderer.renderError } @@ -119,7 +119,7 @@ func (v *canvasPageVisitor) DrawStamp(stamp Stamp) error { // 入参: object 源对象 // 返回: bool 是否建立分组 func (v *canvasPageVisitor) BeginObject(object *GraphicObject) bool { - groups, ok := v.context.Renderer.(interface { + groups, ok := v.ctx.Renderer.(interface { beginObject(*GraphicObject) bool endObject() }) @@ -130,7 +130,7 @@ func (v *canvasPageVisitor) BeginObject(object *GraphicObject) bool { // 入参: id 注解标识 // 返回: bool 是否建立分组 func (v *canvasPageVisitor) BeginAnnotation(id string) bool { - groups, ok := v.context.Renderer.(interface { + groups, ok := v.ctx.Renderer.(interface { beginAnnotation(string) bool endObject() }) @@ -139,13 +139,13 @@ func (v *canvasPageVisitor) BeginAnnotation(id string) bool { // EndObject 结束对象或注解分组 func (v *canvasPageVisitor) EndObject() { - v.context.Renderer.(interface{ endObject() }).endObject() + v.ctx.Renderer.(interface{ endObject() }).endObject() } // SkipObjects 保留不可见对象的编号 // 入参: count 对象数量 func (v *canvasPageVisitor) SkipObjects(count int) { - if groups, ok := v.context.Renderer.(interface{ skipObjects(int) }); ok { + if groups, ok := v.ctx.Renderer.(interface{ skipObjects(int) }); ok { groups.skipObjects(count) } } diff --git a/ofdgo_renderer_pdf_action.go b/ofdgo_renderer_pdf_action.go index febf1d8..e228c33 100644 --- a/ofdgo_renderer_pdf_action.go +++ b/ofdgo_renderer_pdf_action.go @@ -271,7 +271,7 @@ func (n *pdfNavigation) writeAttachments(ctx context.Context, options *pdfgo.Rew if err != nil { return fmt.Errorf("read attachment %s: %w", id, err) } - data, err := io.ReadAll(imageInput{ReadCloser: input, context: ctx}) + data, err := io.ReadAll(imageInput{ReadCloser: input, ctx: ctx}) closeErr := input.Close() if err != nil { return fmt.Errorf("read attachment %s: %w", id, err) diff --git a/ofdgo_renderer_pdf_media.go b/ofdgo_renderer_pdf_media.go index 8820f7c..a34249d 100644 --- a/ofdgo_renderer_pdf_media.go +++ b/ofdgo_renderer_pdf_media.go @@ -78,7 +78,7 @@ func (n *pdfNavigation) loadMediaFiles(ctx context.Context, files map[string]*pd if err != nil { return err } - data, err := io.ReadAll(imageInput{ReadCloser: input, context: ctx}) + data, err := io.ReadAll(imageInput{ReadCloser: input, ctx: ctx}) closeErr := input.Close() if err != nil { return err diff --git a/ofdgo_renderer_stamp_canvas.go b/ofdgo_renderer_stamp_canvas.go index 9ad9ebe..6b431db 100644 --- a/ofdgo_renderer_stamp_canvas.go +++ b/ofdgo_renderer_stamp_canvas.go @@ -49,7 +49,7 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) { } return } - reader, err := NewReader(bytes.NewReader(s.Data), int64(len(s.Data))) + reader, err := NewReader(&Input{reader: bytes.NewReader(s.Data), size: int64(len(s.Data))}) if err == nil { defer reader.Close() doc, err := reader.DocContext(pageContext) @@ -114,7 +114,7 @@ func (r *Renderer) renderOFDStampImage(data []byte) image.Image { r.renderError = err return nil } - reader, err := NewReader(bytes.NewReader(data), int64(len(data))) + reader, err := NewReader(&Input{reader: bytes.NewReader(data), size: int64(len(data))}) if err != nil { r.renderError = err return nil diff --git a/ofdgo_renderer_svg_canvas.go b/ofdgo_renderer_svg_canvas.go index 60bf304..6c180f9 100644 --- a/ofdgo_renderer_svg_canvas.go +++ b/ofdgo_renderer_svg_canvas.go @@ -106,7 +106,7 @@ func (s *svgResourceRenderer) RenderImage(img image.Image, m canvas.Matrix) { size := img.Bounds().Size() m = m.Translate(0, float64(size.Y)) matrix := Matrix{a: m[0][0], b: -m[1][0], c: -m[0][1], d: m[1][1], e: m[0][2], f: height - m[1][2]} - writer := imageOutput{context: ctx, writer: s.writer} + writer := imageOutput{ctx: ctx, writer: s.writer} if _, s.err = fmt.Fprintf(writer, `