From e222cddabb43bcd2d0a4b46a5224b2788e5e1231 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 19 Oct 2026 09:29:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=BE=93=E5=85=A5=E8=BE=93=E5=87=BA):=20?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E8=BE=93=E5=85=A5=E8=BE=93=E5=87=BA=E4=B8=8E?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E7=9B=AE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 ++++++----- internal/base/file/file.go | 4 +- internal/webui/webui.go | 64 ++++++++--------- internal/webui/webui_native.go | 44 ++++++------ internal/webui/webui_wasm.go | 6 +- internal/webview/webview.go | 4 +- internal/webview/webview_files.go | 14 ++-- ofdgo.go | 102 +++++++++++++------------- ofdgo_archive.go | 12 ++-- ofdgo_convert.go | 30 ++++---- ofdgo_crypto_editor.go | 2 +- ofdgo_crypto_package.go | 2 +- ofdgo_editor.go | 5 +- ofdgo_format.go | 14 ++-- ofdgo_pdf.go | 14 ++-- ofdgo_render_compile_image.go | 2 +- ofdgo_renderer_stamp_canvas.go | 4 +- ofdgo_sign_verify.go | 6 +- ofdgo_sign_write.go | 8 +-- ofdgo_writer.go | 115 +++++++++++++++++++----------- 20 files changed, 264 insertions(+), 230 deletions(-) diff --git a/README.md b/README.md index ef74b9b..337930b 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ go get -u github.com/xiaoqidun/ofdgo package main import ( + "context" "log" "os" @@ -105,12 +106,11 @@ func main() { log.Fatal(err) } // 3. 保存文档 - ofdFile, err := os.Create("test.ofd") + output, err := ofdgo.NewFileOutput("test.ofd") if err != nil { log.Fatal(err) } - defer ofdFile.Close() - if _, err := editor.WriteTo(ofdFile); err != nil { + if _, err := editor.SaveAs(context.Background(), output, ofdgo.WriteOptions{}); err != nil { log.Fatal(err) } } @@ -123,19 +123,18 @@ package main import ( "context" "log" - "os" "github.com/xiaoqidun/ofdgo" ) func main() { // 1. 打开文档 - source, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.SourceOptions{}) + input, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.InputOptions{}) if err != nil { log.Fatal(err) } - defer source.Close() - reader, err := ofdgo.NewReader(source) + defer input.Close() + reader, err := ofdgo.NewReader(input) if err != nil { log.Fatal(err) } @@ -147,12 +146,11 @@ func main() { } editor.Info.Title = "编辑后的" // 3. 另存文档 - ofdFile, err := os.Create("edited.ofd") + output, err := ofdgo.NewFileOutput("edited.ofd") if err != nil { log.Fatal(err) } - defer ofdFile.Close() - if _, err := editor.WriteTo(ofdFile); err != nil { + if _, err := editor.SaveAs(context.Background(), output, ofdgo.WriteOptions{}); err != nil { log.Fatal(err) } } @@ -172,11 +170,11 @@ import ( func main() { // 1. 打开文档 - source, err := ofdgo.OpenFile(context.Background(), "test.pdf", ofdgo.SourceOptions{}) + input, err := ofdgo.OpenFile(context.Background(), "test.pdf", ofdgo.InputOptions{}) if err != nil { log.Fatal(err) } - defer source.Close() + defer input.Close() // 2. 创建文件 ofdFile, err := os.Create("test.ofd") if err != nil { @@ -184,7 +182,7 @@ func main() { } defer ofdFile.Close() // 3. 转换文档 - report, err := ofdgo.ConvertPDF(context.Background(), source, ofdFile, ofdgo.PDFImportOptions{}) + report, err := ofdgo.ConvertPDF(context.Background(), input, ofdFile, ofdgo.PDFImportOptions{}) if err != nil { log.Fatal(err) } @@ -209,12 +207,12 @@ import ( func main() { // 1. 打开OFD文件 - source, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.SourceOptions{}) + input, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.InputOptions{}) if err != nil { log.Fatal(err) } - defer source.Close() - reader, err := ofdgo.NewReader(source) + defer input.Close() + reader, err := ofdgo.NewReader(input) if err != nil { log.Fatal(err) } @@ -247,13 +245,13 @@ import ( func main() { // 1. 打开OFD文件 - source, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.SourceOptions{}) + input, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.InputOptions{}) if err != nil { log.Fatal(err) } - defer source.Close() + defer input.Close() // 2. 验证OFD签名 - reports, err := ofdgo.VerifySignatures(source) + reports, err := ofdgo.VerifySignatures(input) if err != nil { log.Fatal(err) } @@ -296,12 +294,12 @@ import ( func main() { // 1. 打开OFD文件 - source, err := ofdgo.OpenFile(context.Background(), "invoice.ofd", ofdgo.SourceOptions{}) + input, err := ofdgo.OpenFile(context.Background(), "invoice.ofd", ofdgo.InputOptions{}) if err != nil { log.Fatal(err) } - defer source.Close() - reader, err := ofdgo.NewReader(source) + 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 24a9aac..2635ace 100644 --- a/internal/base/file/file.go +++ b/internal/base/file/file.go @@ -183,9 +183,9 @@ func (w *Writer) Write(data []byte) (int, error) { return n, err } -// Close 完成写入并替换目标,成功后刷新目标状态 +// Commit 完成写入并替换目标,成功后刷新目标状态 // 返回: error 提交错误 -func (w *Writer) Close() error { +func (w *Writer) Commit() error { w.mu.Lock() defer w.mu.Unlock() if w.closed { diff --git a/internal/webui/webui.go b/internal/webui/webui.go index eecf869..63b7af8 100644 --- a/internal/webui/webui.go +++ b/internal/webui/webui.go @@ -57,7 +57,7 @@ type FontInfo = ofdgo.FontInfo type Session struct { Reader *ofdgo.Reader Renderer *ofdgo.Renderer - source *ofdgo.Source + input *ofdgo.Input fontFS *ofdgo.FontFS doc *ofdgo.Document pageCache map[int]*ofdgo.PageContent @@ -425,7 +425,7 @@ type businessEngine struct { copiedObjects *editorClipboard copiedStyle *ofdgo.GraphicObject ctx context.Context - resolveSource func(context.Context, any) (io.ReaderAt, int64, error) + resolveInput func(context.Context, any) (io.ReaderAt, int64, error) conversionPool *ofdgo.ConversionPool } @@ -436,21 +436,21 @@ func Open(data []byte, opts OpenOptions) (*Session, error) { if len(data) == 0 { return nil, fmt.Errorf("empty ofd data") } - source, err := ofdgo.NewSource(bytes.NewReader(data), int64(len(data))) + input, err := ofdgo.NewInput(bytes.NewReader(data), int64(len(data))) if err != nil { return nil, err } - reader, err := ofdgo.NewReader(source, opts.ReaderOptions...) + reader, err := ofdgo.NewReader(input, opts.ReaderOptions...) if err != nil { - source.Close() + input.Close() return nil, err } session, err := newSession(reader, opts) if err != nil { - source.Close() + input.Close() return nil, err } - session.source = source + session.input = input return session, nil } @@ -461,7 +461,7 @@ func (s *Session) Close() error { return nil } reader := s.Reader - source := s.source + source := s.input *s = Session{} var err error if reader != nil { @@ -1336,12 +1336,12 @@ func (e *businessEngine) detectDocumentFormat(args []platformValue) (any, error) return nil, fmt.Errorf("missing detection arguments") } reader := &browserReaderAt{read: args[0], size: int64(args[1].Float()), checkpoint: args[2]} - source, err := ofdgo.NewSource(reader, reader.size) + input, err := ofdgo.NewInput(reader, reader.size) if err != nil { return nil, err } - defer source.Close() - return ofdgo.DetectFormat(source) + defer input.Close() + return ofdgo.DetectFormat(input) } // convertPDFDocument 通过库转换PDF,不替换当前文档会话 @@ -1351,11 +1351,11 @@ func (e *businessEngine) convertPDFDocument(args []platformValue) (any, error) { if len(args) != 6 { return nil, fmt.Errorf("missing PDF data") } - source, err := e.sourceFromValue(args[0]) + input, err := e.inputFromValue(args[0]) if err != nil { return nil, err } - defer source.Close() + defer input.Close() fonts, err := e.fontsFromJS(args[1]) if err != nil { return nil, err @@ -1376,7 +1376,7 @@ func (e *businessEngine) convertPDFDocument(args []platformValue) (any, error) { })) } options.RendererOptions = []ofdgo.RendererOption{ofdgo.WithFontFS(fontSources...)} - editor, report, err := ofdgo.ImportPDF(e.operationContext(), source, options) + editor, report, err := ofdgo.ImportPDF(e.operationContext(), input, options) if err != nil { return nil, err } @@ -1478,12 +1478,12 @@ func (e *businessEngine) convertFile(args []platformValue) (any, error) { return nil, err } reader := &browserReaderAt{read: args[0], checkpoint: args[6], size: int64(args[1].Float())} - source, err := ofdgo.NewSource(reader, reader.size) + input, err := ofdgo.NewInput(reader, reader.size) if err != nil { return nil, err } - defer source.Close() - report, err := ofdgo.Convert(e.newOutputContext(args[6]), source, writer, options) + defer input.Close() + report, err := ofdgo.Convert(e.newOutputContext(args[6]), input, writer, options) if err != nil { return nil, err } @@ -1514,8 +1514,8 @@ func (e *businessEngine) packFiles(args []platformValue) (any, error) { inputs := make([]*browserReaderAt, files.Length()) defer func() { for _, entry := range entries { - if entry.Source != nil { - entry.Source.Close() + if entry.Input != nil { + entry.Input.Close() } } }() @@ -1524,12 +1524,12 @@ func (e *businessEngine) packFiles(args []platformValue) (any, error) { name := file.Get("name").String() read := args[1].Invoke(i) reader := &browserReaderAt{read: read, checkpoint: args[4], size: int64(file.Get("size").Float())} - source, err := ofdgo.NewSource(reader, reader.size) + input, err := ofdgo.NewInput(reader, reader.size) if err != nil { return nil, err } inputs[i] = reader - entries[i] = ofdgo.ArchiveFile{Name: name, Source: source} + entries[i] = ofdgo.ArchiveFile{Name: name, Input: input} } options := ofdgo.ArchiveOptions{OnProgress: func(completed, total int) error { if completed > 0 { @@ -1558,14 +1558,14 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) { if len(args) < 3 { return nil, fmt.Errorf("missing open arguments") } - source, err := e.sourceFromValue(args[0]) + input, err := e.inputFromValue(args[0]) if err != nil { return nil, err } keep := false defer func() { if !keep { - source.Close() + input.Close() } }() fonts, err := e.fontsFromJS(args[1]) @@ -1582,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, options.ReaderOptions...) + reader, err := ofdgo.NewReader(input, options.ReaderOptions...) if err != nil { return nil, err } @@ -1599,7 +1599,7 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) { e.currentSession = nil } e.currentSession = session - session.source = source + session.input = input keep = true return e.currentSession.Summary(), nil } @@ -5505,14 +5505,14 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) { if args[0].IsNull() { return nil, nil } - source, err := e.sourceFromValue(args[0]) + input, err := e.inputFromValue(args[0]) if err != nil { return nil, err } keep := false defer func() { if !keep { - source.Close() + input.Close() } }() var options []ofdgo.ReaderOption @@ -5524,11 +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, options...) + reader, err := ofdgo.NewReader(input, options...) if err != nil { return nil, err } - reader.Closer = source + reader.Closer = input doc, err := reader.Doc() if err != nil { reader.Close() @@ -6180,7 +6180,7 @@ func (e *businessEngine) selectDocumentVersion(args []platformValue) (any, error if err != nil { return nil, err } - session.source, e.currentSession.source = e.currentSession.source, nil + session.input, e.currentSession.input = e.currentSession.input, nil _ = e.currentSession.Close() e.currentSession = session e.clearImport() @@ -6338,7 +6338,7 @@ func (e *businessEngine) selectDocument(args []platformValue) (any, error) { if err != nil { return nil, err } - session.source, e.currentSession.source = e.currentSession.source, nil + session.input, e.currentSession.input = e.currentSession.input, nil _ = e.currentSession.Close() e.currentSession, e.currentEditor = session, session.editor if e.pendingImport != nil { @@ -6440,7 +6440,7 @@ func (e *businessEngine) previewEditor(editor *ofdgo.Editor, annotations bool) ( editor.SetFontFS(session.fontFS) } if editor == e.currentEditor { - session.source, e.currentSession.source = e.currentSession.source, nil + session.input, e.currentSession.input = e.currentSession.input, nil } _ = e.currentSession.Close() } diff --git a/internal/webui/webui_native.go b/internal/webui/webui_native.go index d8e0eed..31eb18c 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,13 +876,13 @@ func nativeOutputBase(value platformValue) string { return base } -// sourceFromValue 读取宿主授权的随机读取源 -func (e *businessEngine) sourceFromValue(value platformValue) (*ofdgo.Source, error) { - reader, size, err := 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.NewSource(reader, size) + return ofdgo.NewInput(reader, size) } // platformValueOf 包装参数 @@ -1028,12 +1028,12 @@ func (e *businessEngine) bytesFromJS(v platformValue) ([]byte, error) { if data, ok := v.value.([]byte); ok { return append([]byte(nil), data...), nil } - source, err := e.sourceFromValue(v) + input, err := e.inputFromValue(v) if err != nil { return nil, err } - defer source.Close() - return io.ReadAll(io.NewSectionReader(source, 0, source.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 e147f83..e2b8038 100644 --- a/internal/webui/webui_wasm.go +++ b/internal/webui/webui_wasm.go @@ -221,11 +221,11 @@ func platformParseJSON(data []byte) map[string]any { return result } -// sourceFromValue 读取浏览器二进制源 -func (e *businessEngine) sourceFromValue(value platformValue) (*ofdgo.Source, error) { +// inputFromValue 读取浏览器二进制源 +func (e *businessEngine) inputFromValue(value platformValue) (*ofdgo.Input, error) { data, err := e.bytesFromJS(value) if err != nil { return nil, err } - return ofdgo.NewSource(bytes.NewReader(data), int64(len(data))) + 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/internal/webview/webview_files.go b/internal/webview/webview_files.go index ed8ead0..34c6ff3 100644 --- a/internal/webview/webview_files.go +++ b/internal/webview/webview_files.go @@ -30,6 +30,7 @@ import ( "sync" "time" + "github.com/xiaoqidun/ofdgo" basefile "github.com/xiaoqidun/ofdgo/internal/base/file" ) @@ -43,7 +44,7 @@ type fileEntry struct { directory bool owned bool temporary bool - saveTarget *basefile.Target + saveTarget ofdgo.Output readers int retired bool } @@ -61,7 +62,7 @@ type fileStore struct { type fileWriter struct { id string store *fileStore - output *basefile.Writer + output ofdgo.WriteTransaction } // newFileStore 创建独立文件授权表 @@ -127,7 +128,7 @@ func (s *fileStore) add(name string, directory bool) (map[string]any, error) { id := rand.Text() entry := &fileEntry{path: name, name: filepath.Base(name), directory: directory} if !directory { - entry.saveTarget, err = basefile.NewTarget(name) + entry.saveTarget, err = ofdgo.NewFileOutput(name) if err != nil { return nil, err } @@ -417,7 +418,7 @@ func (s *fileStore) begin(ctx context.Context, id string) (*fileWriter, error) { } if entry.saveTarget == nil { var err error - entry.saveTarget, err = basefile.NewTarget(entry.path) + entry.saveTarget, err = ofdgo.NewFileOutput(entry.path) if err != nil { return nil, err } @@ -489,7 +490,10 @@ func (w *fileWriter) Write(data []byte) (int, error) { // 返回: error 提交错误 func (w *fileWriter) Close() error { defer w.release() - return w.output.Close() + if err := w.output.Commit(); err != nil { + return errors.Join(err, w.output.Abort()) + } + return nil } // Abort 删除未提交的临时输出 diff --git a/ofdgo.go b/ofdgo.go index 59d3027..6bd19d3 100644 --- a/ofdgo.go +++ b/ofdgo.go @@ -31,10 +31,10 @@ import ( basefile "github.com/xiaoqidun/ofdgo/internal/base/file" ) -// Source 统一文件、内存及顺序流的随机读取,可并发读取和关闭,不可复制 +// Input 统一文件、内存及顺序流的随机读取,可并发读取和关闭,不可复制 // 调用方应在阅读、编辑及转换结束后调用Close,借入的数据需在此期间保持不变 // 借入的随机读取器应支持并发ReadAt,Close不会关闭调用方传入的读取器 -type Source struct { +type Input struct { mu sync.RWMutex reader io.ReaderAt size int64 @@ -43,12 +43,12 @@ type Source struct { temp bool } -// SourceOptions 设置顺序输入的缓存及大小限制,单位为字节 +// InputOptions 设置顺序输入的缓存及大小限制,单位为字节 // MemoryLimit为0时使用8MiB,浏览器WASM仅用内存;负值表示仅用内存 // TempDir为空时使用系统临时目录,不自动创建调用方指定的目录 -// MaxSize为0时不限制输入大小,OpenFile和ReadSource均检查此限制 +// MaxSize为0时不限制输入大小,OpenFile和ReadInput均检查此限制 // 无临时文件系统的平台可显式使用仅内存模式,并通过MaxSize限制输入 -type SourceOptions struct { +type InputOptions struct { MemoryLimit int64 MaxSize int64 TempDir string @@ -56,8 +56,8 @@ type SourceOptions struct { // OpenFile 打开文件输入,不复制文件内容,Close释放文件句柄 // 入参: ctx 取消上下文, path 文件路径, options 输入选项 -// 返回: *Source 输入源, error 打开或大小错误 -func OpenFile(ctx context.Context, path string, options SourceOptions) (*Source, error) { +// 返回: *Input 输入源, error 打开或大小错误 +func OpenFile(ctx context.Context, path string, options InputOptions) (*Input, error) { if err := ctx.Err(); err != nil { return nil, err } @@ -72,39 +72,39 @@ func OpenFile(ctx context.Context, path string, options SourceOptions) (*Source, } if !info.Mode().IsRegular() { f.Close() - return nil, fmt.Errorf("source is not a regular file") + return nil, fmt.Errorf("input is not a regular file") } - source, err := NewSource(f, info.Size()) + input, err := NewInput(f, info.Size()) if err != nil { f.Close() return nil, err } - source.file, source.path = f, path - if err := source.checkOptions(ctx, options); err != nil { - source.Close() + input.file, input.path = f, path + if err := input.checkOptions(ctx, options); err != nil { + input.Close() return nil, err } - return source, nil + return input, nil } -// NewSource 借用指定长度的随机读取源,不复制数据或接管其生命周期 +// NewInput 借用指定长度的随机读取源,不复制数据或接管其生命周期 // 入参: reader 随机读取器, size 数据大小 -// 返回: *Source 输入源, error 参数错误 -func NewSource(reader io.ReaderAt, size int64) (*Source, error) { +// 返回: *Input 输入源, error 参数错误 +func NewInput(reader io.ReaderAt, size int64) (*Input, error) { if reader == nil || size < 0 { - return nil, fmt.Errorf("invalid source") + return nil, fmt.Errorf("invalid input") } - return &Source{reader: reader, size: size}, nil + return &Input{reader: reader, size: size}, nil } -// ReadSource 从当前位置接收顺序输入,超过内存阈值后转入临时文件 +// ReadInput 从当前位置接收顺序输入,超过内存阈值后转入临时文件 // 已知大小且支持随机访问的可定位读取器直接复用,不移动其当前位置 // 不关闭传入流;取消无法中断阻塞的Read,调用方应自行设置超时或关闭流 // 入参: ctx 取消上下文, reader 输入流, options 输入选项 -// 返回: *Source 输入源, error 读取、缓存或取消错误 -func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (*Source, error) { +// 返回: *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 source options") + return nil, fmt.Errorf("invalid input options") } if err := ctx.Err(); err != nil { return nil, err @@ -129,16 +129,16 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (* return nil, err } if offset < 0 || offset > size { - return nil, fmt.Errorf("invalid source offset") + return nil, fmt.Errorf("invalid input offset") } - source, err := NewSource(io.NewSectionReader(random, offset, size-offset), size-offset) + input, err := NewInput(io.NewSectionReader(random, offset, size-offset), size-offset) if err != nil { return nil, err } - if err := source.checkOptions(ctx, options); err != nil { + if err := input.checkOptions(ctx, options); err != nil { return nil, err } - return source, nil + return input, nil } } } @@ -149,11 +149,11 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (* limit = -1 } } - source := &Source{} + input := &Input{} success := false defer func() { if !success { - source.Close() + input.Close() } }() var memory bytes.Buffer @@ -165,7 +165,7 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (* } n, err := reader.Read(buffer) if n < 0 || n > len(buffer) { - return nil, fmt.Errorf("invalid source read count") + return nil, fmt.Errorf("invalid input read count") } if err != nil && err != io.EOF { return nil, err @@ -175,28 +175,28 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (* } if n > 0 { empty = 0 - if source.size > int64(^uint64(0)>>1)-int64(n) || options.MaxSize > 0 && int64(n) > options.MaxSize-source.size { - return nil, fmt.Errorf("source exceeds size limit") + 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 source.file == nil && limit >= 0 && source.size+int64(n) > limit { + if input.file == nil && limit >= 0 && input.size+int64(n) > limit { file, failure := basefile.NewTemp(options.TempDir) if failure != nil { return nil, failure } - source.file, source.temp = file, true + 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 source.file != nil { - output = source.file + if input.file != nil { + output = input.file } if _, failure := output.Write(buffer[:n]); failure != nil { return nil, failure } - source.size += int64(n) + input.size += int64(n) } else if err == nil { empty++ if empty >= 100 { @@ -210,34 +210,34 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (* if err := ctx.Err(); err != nil { return nil, err } - source.reader = bytes.NewReader(memory.Bytes()) - if source.file != nil { - source.reader = source.file + input.reader = bytes.NewReader(memory.Bytes()) + if input.file != nil { + input.reader = input.file } success = true - return source, nil + return input, nil } // checkOptions 检查取消状态及输入大小 // 入参: ctx 取消上下文, options 输入选项 // 返回: error 取消或大小错误 -func (s *Source) checkOptions(ctx context.Context, options SourceOptions) 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("source exceeds size limit") + return fmt.Errorf("input exceeds size limit") } return ctx.Err() } // Size 返回输入字节数,关闭后仍可查询 // 返回: int64 数据大小 -func (s *Source) Size() int64 { +func (s *Input) Size() int64 { return s.size } // ReadAt 读取指定位置的数据,关闭后返回os.ErrClosed // 入参: data 目标缓冲区, offset 字节偏移 // 返回: int 读取字节数, error 读取错误 -func (s *Source) ReadAt(data []byte, offset int64) (int, error) { +func (s *Input) ReadAt(data []byte, offset int64) (int, error) { s.mu.RLock() defer s.mu.RUnlock() if s.reader == nil { @@ -248,7 +248,7 @@ func (s *Source) ReadAt(data []byte, offset int64) (int, error) { // Close 释放自有文件和缓存,等待正在进行的读取结束,可重复调用 // 返回: error 文件关闭或临时文件删除错误 -func (s *Source) Close() error { +func (s *Input) Close() error { s.mu.Lock() defer s.mu.Unlock() s.reader = nil @@ -266,18 +266,18 @@ func (s *Source) Close() error { // NewReader 从统一输入源创建OFD阅读器,不接管输入源生命周期 // 阅读器及其派生编辑器使用结束后,调用方再关闭输入源 -// 入参: source 输入源, options 阅读选项 +// 入参: input 输入源, options 阅读选项 // 返回: *Reader 阅读器实例, error 错误信息 -func NewReader(source *Source, options ...ReaderOption) (*Reader, error) { - if source == nil { - return nil, fmt.Errorf("missing source") +func NewReader(input *Input, options ...ReaderOption) (*Reader, error) { + if input == nil { + return nil, fmt.Errorf("missing input") } - zr, err := zip.NewReader(source, source.Size()) + zr, err := zip.NewReader(input, input.Size()) if err != nil { return nil, err } reader := &Reader{ - Path: source.path, + Path: input.path, Zip: zr, } if err := reader.open(options); err != nil { diff --git a/ofdgo_archive.go b/ofdgo_archive.go index e0c2e3f..e7d7aa4 100644 --- a/ofdgo_archive.go +++ b/ofdgo_archive.go @@ -27,8 +27,8 @@ import ( // ArchiveFile 描述已生成的归档文件,不关闭调用方提供的源读取器 type ArchiveFile struct { - Name string - Source *Source + Name string + Input *Input } // ArchiveOptions 设置ZIP封装,压缩不重新编码归档内已生成的文档 @@ -46,7 +46,7 @@ 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 { + 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 @@ -76,15 +76,15 @@ func WriteArchive(ctx context.Context, writer io.Writer, files []ArchiveFile, op if err != nil { return count, err } - source := convertReader{ctx: 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(source, 0, file.Source.Size()), buffer) + written, err := io.CopyBuffer(entry, io.NewSectionReader(input, 0, file.Input.Size()), buffer) if err != nil { return count, err } - if written != file.Source.Size() { + if written != file.Input.Size() { return count, io.ErrUnexpectedEOF } } diff --git a/ofdgo_convert.go b/ofdgo_convert.go index dbd92bf..519cd61 100644 --- a/ofdgo_convert.go +++ b/ofdgo_convert.go @@ -84,8 +84,8 @@ type ConvertReport struct { // convertReader 将取消上下文传递到随机读取 type convertReader struct { - ctx context.Context - source io.ReaderAt + ctx context.Context + input io.ReaderAt } // convertWriter 将取消上下文传递到写入并统计实际字节数 @@ -113,9 +113,9 @@ func OutputFormats() []OutputFormat { // 原始模式完整OFD另存保持源字节;选页和跨格式输出重新生成文档,压缩仅作用于输出副本 // 输入按需随机读取,输出分块写出;解析对象及后端可能缓存资源,不保证恒定内存 // 出错时报告保留已有诊断,调用方应丢弃未完成输出;只有返回nil才表示转换成功 -// 入参: ctx 取消上下文, source 输入源, output 输出流, options 转换选项 +// 入参: ctx 取消上下文, input 输入源, output 输出流, options 转换选项 // 返回: ConvertReport 转换报告, error 错误信息 -func Convert(ctx context.Context, source *Source, 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,14 +174,14 @@ func Convert(ctx context.Context, source *Source, output io.Writer, options Conv if err = progress("open", 0, 0); err != nil { return report, err } - if source == nil || source.Size() <= 0 { - return report, fmt.Errorf("empty document source") + if input == nil || input.Size() <= 0 { + return report, fmt.Errorf("empty document input") } - size := source.Size() - operationSource := &Source{reader: convertReader{ctx: ctx, source: source}, size: size} + 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(operationSource) + report.Input, err = DetectFormat(operationInput) if err != nil { return report, err } @@ -200,7 +200,7 @@ func Convert(ctx context.Context, source *Source, output io.Writer, options Conv 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(operationSource, 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 } @@ -234,13 +234,13 @@ func Convert(ctx context.Context, source *Source, output io.Writer, options Conv } return nil } - editor, report.PDF, err = ImportPDF(ctx, operationSource, pdfOptions) + editor, report.PDF, err = ImportPDF(ctx, operationInput, pdfOptions) if err != nil { return report, err } count = editor.PageCount() case "ofd": - reader, err = NewReader(operationSource, options.ReaderOptions...) + reader, err = NewReader(operationInput, options.ReaderOptions...) if err != nil { return report, err } @@ -267,7 +267,7 @@ func Convert(ctx context.Context, source *Source, output io.Writer, options Conv if err = progress("write", 0, 0); err != nil { return report, err } - _, err = io.Copy(writer, io.NewSectionReader(operationSource, 0, size)) + _, err = io.Copy(writer, io.NewSectionReader(operationInput, 0, size)) report.Unchanged = err == nil return report, err } @@ -302,7 +302,7 @@ func Convert(ctx context.Context, source *Source, output io.Writer, options Conv if err = progress("write", 0, 0); err != nil { return report, err } - _, err = io.Copy(writer, io.NewSectionReader(operationSource, 0, size)) + _, err = io.Copy(writer, io.NewSectionReader(operationInput, 0, size)) report.Unchanged = err == nil return report, err } @@ -370,7 +370,7 @@ func (r convertReader) ReadAt(data []byte, offset int64) (int, error) { 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.ctx.Err() } diff --git a/ofdgo_crypto_editor.go b/ofdgo_crypto_editor.go index 99d28cb..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(&Source{reader: bytes.NewReader(plaintext.Bytes()), size: 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 7d7d116..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(&Source{reader: bytes.NewReader(data), size: 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.go b/ofdgo_editor.go index 45f73cd..99bce64 100644 --- a/ofdgo_editor.go +++ b/ofdgo_editor.go @@ -33,7 +33,6 @@ import ( "time" "unicode/utf8" - basefile "github.com/xiaoqidun/ofdgo/internal/base/file" baseimage "github.com/xiaoqidun/ofdgo/internal/base/image" ) @@ -41,7 +40,7 @@ import ( const editorPathValidationLimit = 1024 // Editor 编辑OFD文档,长度单位为毫米,页面索引从0开始,实例需串行使用 -// Info可修改文档元数据,通过方法管理页面、对象和资源,WriteTo输出副本,Save和SaveAs通过绑定路径保存 +// Info可修改文档元数据,通过方法管理页面、对象和资源,WriteTo输出副本,Save和SaveAs通过输出事务保存 // OnWriteProgress可选,按fonts、pages、references、resources、compress、write阶段同步回报进度,返回错误则停止保存 // completed和total为当前阶段已处理及总工作项,compress累计完成条目数且total为0,write阶段不计数 // 阶段可重复,准备完成不代表保存成功 @@ -74,7 +73,7 @@ type Editor struct { outlines []byte encryption *encryptionState output *outputOptimization - saveTarget *basefile.Target + saveTarget Output validatedPaths *renderCache[string, bool] richIndex *richTextIndex } diff --git a/ofdgo_format.go b/ofdgo_format.go index d779c9e..5a9378b 100644 --- a/ofdgo_format.go +++ b/ofdgo_format.go @@ -24,16 +24,16 @@ import ( // DetectFormat 按文件内容识别PDF或OFD,不读取压缩内容或关闭输入 // 识别结果不代表文档完整有效,仍需通过对应阅读器解析 -// 入参: source 输入源 +// 入参: input 输入源 // 返回: string 格式标识pdf或ofd, error 读取错误或不支持的格式 -func DetectFormat(source *Source) (string, error) { - if source == nil || source.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") } - size := source.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 *Source) (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_pdf.go b/ofdgo_pdf.go index 47ef7b2..6b9293e 100644 --- a/ofdgo_pdf.go +++ b/ofdgo_pdf.go @@ -204,10 +204,10 @@ func (o pdfObjectOrigin) merge(other pdfObjectOrigin) pdfObjectOrigin { // ImportPDF 将PDF内容转换为独立OFD编辑文档,失败时不返回部分结果 // 返回的编辑文档独立于输入源,函数返回后可关闭输入源 -// 入参: ctx 取消上下文, source 输入源, options 转换选项 +// 入参: ctx 取消上下文, input 输入源, options 转换选项 // 返回: *Editor 编辑文档, PDFImportReport 转换统计, error 错误信息 -func ImportPDF(ctx context.Context, source *Source, options PDFImportOptions) (*Editor, PDFImportReport, error) { - if source == nil { +func ImportPDF(ctx context.Context, input *Input, options PDFImportOptions) (*Editor, PDFImportReport, error) { + if input == nil { return nil, PDFImportReport{}, fmt.Errorf("missing source") } if err := ctx.Err(); err != nil { @@ -245,7 +245,7 @@ func ImportPDF(ctx context.Context, source *Source, options PDFImportOptions) (* readWarning = func(diagnostic pdfgo.Diagnostic) { diagnostics = append(diagnostics, diagnostic) } readOptions.Warning = func(diagnostic pdfgo.Diagnostic) { readWarning(diagnostic) } } - reader, err := pdfgo.NewReaderWithOptions(convertReader{ctx: ctx, source: source}, source.Size(), readOptions) + reader, err := pdfgo.NewReaderWithOptions(convertReader{ctx: ctx, input: input}, input.Size(), readOptions) if err != nil { return nil, PDFImportReport{}, err } @@ -374,10 +374,10 @@ func ImportPDF(ctx context.Context, source *Source, options PDFImportOptions) (* } // ConvertPDF 将PDF转换并写入OFD,转换阶段失败时不写入目标 -// 入参: ctx 取消上下文, source 输入源, output OFD输出, options 转换选项 +// 入参: ctx 取消上下文, input 输入源, output OFD输出, options 转换选项 // 返回: PDFImportReport 转换统计, error 错误信息 -func ConvertPDF(ctx context.Context, source *Source, output io.Writer, options PDFImportOptions) (PDFImportReport, error) { - editor, report, err := ImportPDF(ctx, source, 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_render_compile_image.go b/ofdgo_render_compile_image.go index f1be8df..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(&Source{reader: bytes.NewReader(stamp.Data), size: 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_renderer_stamp_canvas.go b/ofdgo_renderer_stamp_canvas.go index 41a4eaf..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(&Source{reader: bytes.NewReader(s.Data), size: 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(&Source{reader: bytes.NewReader(data), size: 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_sign_verify.go b/ofdgo_sign_verify.go index 1e18a2e..a50e461 100644 --- a/ofdgo_sign_verify.go +++ b/ofdgo_sign_verify.go @@ -236,10 +236,10 @@ func WithSignatureVerifyTime(t time.Time) SignatureVerifyOption { } // VerifySignatures 验证输入源中的OFD签名,不接管输入源生命周期 -// 入参: source 输入源, opts 签名验证选项 +// 入参: input 输入源, opts 签名验证选项 // 返回: []SignatureVerifyReport 签名验证报告, error 错误信息 -func VerifySignatures(source *Source, opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) { - reader, err := NewReader(source) +func VerifySignatures(input *Input, opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) { + reader, err := NewReader(input) if err != nil { return nil, err } diff --git a/ofdgo_sign_write.go b/ofdgo_sign_write.go index 5095f29..8cc7009 100644 --- a/ofdgo_sign_write.go +++ b/ofdgo_sign_write.go @@ -84,7 +84,7 @@ type signatureIDSequence uint64 // 入参: data 最终包字节, options 签署选项 // 返回: []byte 签署后的完整包, error 错误信息 func SignPackage(data []byte, options SignatureWriteOptions) ([]byte, error) { - r, err := NewReader(&Source{reader: bytes.NewReader(data), size: int64(len(data))}, options.ReaderOptions...) + r, err := NewReader(&Input{reader: bytes.NewReader(data), size: int64(len(data))}, options.ReaderOptions...) if err != nil { return nil, err } @@ -109,7 +109,7 @@ func SignEditorTo(writer io.Writer, editor *Editor, options SignatureWriteOption if _, err := editor.writePlaintext(&plain); err != nil { return 0, err } - r, err := NewReader(&Source{reader: bytes.NewReader(plain.Bytes()), size: int64(plain.Len())}) + r, err := NewReader(&Input{reader: bytes.NewReader(plain.Bytes()), size: int64(plain.Len())}) if err != nil { return 0, err } @@ -192,7 +192,7 @@ func signatureWriteOutput(r *Reader, options SignatureWriteOptions) ([]byte, err if encryption != nil { defer clear(data) if options.progress != nil { - signed, err := NewReader(&Source{reader: bytes.NewReader(data), size: int64(len(data))}) + signed, err := NewReader(&Input{reader: bytes.NewReader(data), size: int64(len(data))}) if err != nil { return nil, err } @@ -434,7 +434,7 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption if err != nil { return nil, err } - check, err := NewReader(&Source{reader: bytes.NewReader(output), size: int64(len(output))}) + check, err := NewReader(&Input{reader: bytes.NewReader(output), size: int64(len(output))}) if err != nil { return nil, err } diff --git a/ofdgo_writer.go b/ofdgo_writer.go index 1909362..d06ecd6 100644 --- a/ofdgo_writer.go +++ b/ofdgo_writer.go @@ -34,6 +34,28 @@ import ( basefile "github.com/xiaoqidun/ofdgo/internal/base/file" ) +// Output 创建完整文档的输出事务,可用于文件或远程存储 +// 实现需支持并发调用Begin,按目标协调并发写入并校验版本冲突 +// Begin失败时由实现释放资源;事务持有ctx,提交前应检查取消状态 +type Output interface { + Begin(ctx context.Context) (WriteTransaction, error) +} + +// WriteTransaction 暂存文档并提交完整内容,不直接覆盖已提交版本 +// Commit成功后释放资源并更新目标版本;失败时不得自动重试提交 +// Abort释放未提交资源且可重复调用,不撤销已提交内容 +// 远程提交结果不确定时,由实现返回可识别错误并重新确认目标版本 +type WriteTransaction interface { + io.Writer + Commit() error + Abort() error +} + +// fileOutput 使用本地文件事务保存文档 +type fileOutput struct { + target *basefile.Target +} + // editorProgress 编辑与保存阶段的进度回调,nil表示不回报进度 type editorProgress func(stage string, completed, total int) error @@ -63,11 +85,12 @@ var ErrSaveTarget = errors.New("save target is not set") // ErrSaveConflict 表示保存目标在打开或最近保存后被其他操作修改 var ErrSaveConflict = basefile.ErrChanged -// ErrSaveBusy 表示目标文件正在提交其他写入事务 +// ErrSaveBusy 表示保存目标已有其他写入事务 var ErrSaveBusy = basefile.ErrBusy // Snapshot 创建独立编辑快照,完成后可与原编辑器并行使用 // 创建过程需与原实例串行,保留修订及保存目标,不复制撤销记录和进度回调 +// 保存目标与原实例共享,由输出实现协调并发事务及提交版本 // 原包压缩数据直接复制,快照不依赖原阅读器及文件句柄,内存随快照释放 // 自定义后端及字体文件系统仍由调用方提供,需支持并行读取 // 入参: ctx 取消上下文 @@ -120,76 +143,86 @@ func (e *Editor) Snapshot(ctx context.Context) (*Editor, error) { return snapshot, nil } -// SetSavePath 绑定保存目标并记录当前文件状态,不读取或修改编辑内容 -// 绑定已有文件时应在打开后立即调用;空路径解除绑定,调用需与编辑操作串行 -// 入参: name 目标路径 -// 返回: error 路径或文件状态错误 -func (e *Editor) SetSavePath(name string) error { - if name == "" { - e.saveTarget = nil - return nil - } +// NewFileOutput 创建本地保存目标并记录当前文件状态,不创建或修改文件 +// 同一目标应复用返回值,以便后续保存检测外部修改 +// 替换前比较文件标识、大小和修改时间,不提供跨进程排他锁 +// 入参: name 文件路径 +// 返回: Output 保存目标, error 路径或文件状态错误 +func NewFileOutput(name string) (Output, error) { target, err := basefile.NewTarget(name) if err != nil { - return err + return nil, err } - e.saveTarget = target - return nil + return &fileOutput{target: target}, nil } -// SavePath 返回当前保存目标,未绑定时返回空字符串 -// 返回: string 绝对文件路径 -func (e *Editor) SavePath() string { - if e.saveTarget == nil { - return "" - } - return e.saveTarget.Name() +// Begin 创建本地文件写入事务 +// 入参: ctx 取消上下文 +// 返回: WriteTransaction 写入事务, error 文件状态错误 +func (o *fileOutput) Begin(ctx context.Context) (WriteTransaction, error) { + return o.target.Begin(ctx) +} + +// SetOutput 绑定保存目标,不读取或修改编辑内容,nil解除绑定 +// 调用需与编辑操作串行,目标生命周期由调用方管理 +// 入参: output 保存目标 +func (e *Editor) SetOutput(output Output) { + e.saveTarget = output +} + +// Output 返回当前保存目标,未绑定时返回nil +// 返回: Output 保存目标 +func (e *Editor) Output() Output { + return e.saveTarget } // Save 将当前完整文档写回已绑定目标,不改变编辑状态及撤销记录 -// 调用需与编辑操作串行,支持取消;失败保留原文件,成功后可继续编辑和保存 +// 调用需与编辑操作串行;后台保存应先创建Snapshot,成功后可继续编辑和保存 // 入参: ctx 取消上下文, options 输出配置 // 返回: int64 输出字节数, error 保存错误 func (e *Editor) Save(ctx context.Context, options WriteOptions) (int64, error) { - if e.saveTarget == nil { - return 0, ErrSaveTarget - } - return e.saveFile(ctx, e.saveTarget, options) + return e.saveOutput(ctx, e.saveTarget, options) } // SaveAs 将完整文档另存,成功后切换保存目标,不改变撤销记录 // 调用需与编辑操作串行,取消或失败时保持原目标 -// 入参: ctx 取消上下文, name 目标路径, options 输出配置 +// 入参: ctx 取消上下文, output 保存目标, options 输出配置 // 返回: int64 输出字节数, error 保存错误 -func (e *Editor) SaveAs(ctx context.Context, name string, options WriteOptions) (int64, error) { - target, err := basefile.NewTarget(name) - if err != nil { - return 0, err - } - if e.saveTarget != nil && target.Name() == e.saveTarget.Name() { - target = e.saveTarget - } - n, err := e.saveFile(ctx, target, options) +func (e *Editor) SaveAs(ctx context.Context, output Output, options WriteOptions) (int64, error) { + n, err := e.saveOutput(ctx, output, options) if err == nil { - e.saveTarget = target + e.saveTarget = output } return n, err } -// saveFile 使用共用文件事务提交文档 +// saveOutput 使用输出事务提交文档并释放失败事务 // 入参: ctx 取消上下文, target 保存目标, options 输出配置 // 返回: int64 输出字节数, error 保存错误 -func (e *Editor) saveFile(ctx context.Context, target *basefile.Target, options WriteOptions) (int64, error) { +func (e *Editor) saveOutput(ctx context.Context, target Output, options WriteOptions) (n int64, err error) { + if target == nil { + return 0, ErrSaveTarget + } + if err := ctx.Err(); err != nil { + return 0, err + } writer, err := target.Begin(ctx) if err != nil { return 0, err } - defer writer.Abort() - n, err := e.WriteToWithOptions(ctx, writer, options) + defer func() { + if err != nil { + err = errors.Join(err, writer.Abort()) + } + }() + n, err = e.WriteToWithOptions(ctx, writer, options) if err != nil { return n, err } - return n, writer.Close() + if err = ctx.Err(); err != nil { + return n, err + } + return n, writer.Commit() } // WriteTo 逐个条目写出OFD,不关闭调用方输出流,出错时应丢弃本次输出