feat(输入输出): 统一输入输出与保存目标
build.yaml / build (push) Successful in 46s

This commit is contained in:
xiaoqidun committed 2026-10-19 09:29:36 +08:00
1 parent b06df462ea
commit e222cddabb
20 files changed
+262 -228

No files matched your search

+20 -22
View File
@@ -77,6 +77,7 @@ go get -u github.com/xiaoqidun/ofdgo
package main package main
import ( import (
"context"
"log" "log"
"os" "os"
@@ -105,12 +106,11 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
// 3. 保存文档 // 3. 保存文档
ofdFile, err := os.Create("test.ofd") output, err := ofdgo.NewFileOutput("test.ofd")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer ofdFile.Close() if _, err := editor.SaveAs(context.Background(), output, ofdgo.WriteOptions{}); err != nil {
if _, err := editor.WriteTo(ofdFile); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
@@ -123,19 +123,18 @@ package main
import ( import (
"context" "context"
"log" "log"
"os"
"github.com/xiaoqidun/ofdgo" "github.com/xiaoqidun/ofdgo"
) )
func main() { func main() {
// 1. 打开文档 // 1. 打开文档
source, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.SourceOptions{}) input, err := ofdgo.OpenFile(context.Background(), "test.ofd", ofdgo.InputOptions{})
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer source.Close() defer input.Close()
reader, err := ofdgo.NewReader(source) reader, err := ofdgo.NewReader(input)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -147,12 +146,11 @@ func main() {
} }
editor.Info.Title = "编辑后的" editor.Info.Title = "编辑后的"
// 3. 另存文档 // 3. 另存文档
ofdFile, err := os.Create("edited.ofd") output, err := ofdgo.NewFileOutput("edited.ofd")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer ofdFile.Close() if _, err := editor.SaveAs(context.Background(), output, ofdgo.WriteOptions{}); err != nil {
if _, err := editor.WriteTo(ofdFile); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
@@ -172,11 +170,11 @@ import (
func main() { func main() {
// 1. 打开文档 // 1. 打开文档
source, err := ofdgo.OpenFile(context.Background(), "test.pdf", ofdgo.SourceOptions{}) input, err := ofdgo.OpenFile(context.Background(), "test.pdf", ofdgo.InputOptions{})
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer source.Close() defer input.Close()
// 2. 创建文件 // 2. 创建文件
ofdFile, err := os.Create("test.ofd") ofdFile, err := os.Create("test.ofd")
if err != nil { if err != nil {
@@ -184,7 +182,7 @@ func main() {
} }
defer ofdFile.Close() defer ofdFile.Close()
// 3. 转换文档 // 3. 转换文档
report, err := ofdgo.ConvertPDF(context.Background(), source, ofdFile, ofdgo.PDFImportOptions{}) report, err := ofdgo.ConvertPDF(context.Background(), input, ofdFile, ofdgo.PDFImportOptions{})
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -209,12 +207,12 @@ import (
func main() { func main() {
// 1. 打开OFD文件 // 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer source.Close() defer input.Close()
reader, err := ofdgo.NewReader(source) reader, err := ofdgo.NewReader(input)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -247,13 +245,13 @@ import (
func main() { func main() {
// 1. 打开OFD文件 // 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer source.Close() defer input.Close()
// 2. 验证OFD签名 // 2. 验证OFD签名
reports, err := ofdgo.VerifySignatures(source) reports, err := ofdgo.VerifySignatures(input)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -296,12 +294,12 @@ import (
func main() { func main() {
// 1. 打开OFD文件 // 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer source.Close() defer input.Close()
reader, err := ofdgo.NewReader(source) reader, err := ofdgo.NewReader(input)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
+2 -2
View File
@@ -183,9 +183,9 @@ func (w *Writer) Write(data []byte) (int, error) {
return n, err return n, err
} }
// Close 完成写入并替换目标,成功后刷新目标状态 // Commit 完成写入并替换目标,成功后刷新目标状态
// 返回: error 提交错误 // 返回: error 提交错误
func (w *Writer) Close() error { func (w *Writer) Commit() error {
w.mu.Lock() w.mu.Lock()
defer w.mu.Unlock() defer w.mu.Unlock()
if w.closed { if w.closed {
+32 -32
View File
@@ -57,7 +57,7 @@ type FontInfo = ofdgo.FontInfo
type Session struct { type Session struct {
Reader *ofdgo.Reader Reader *ofdgo.Reader
Renderer *ofdgo.Renderer Renderer *ofdgo.Renderer
source *ofdgo.Source input *ofdgo.Input
fontFS *ofdgo.FontFS fontFS *ofdgo.FontFS
doc *ofdgo.Document doc *ofdgo.Document
pageCache map[int]*ofdgo.PageContent pageCache map[int]*ofdgo.PageContent
@@ -425,7 +425,7 @@ type businessEngine struct {
copiedObjects *editorClipboard copiedObjects *editorClipboard
copiedStyle *ofdgo.GraphicObject copiedStyle *ofdgo.GraphicObject
ctx context.Context ctx context.Context
resolveSource func(context.Context, any) (io.ReaderAt, int64, error) resolveInput func(context.Context, any) (io.ReaderAt, int64, error)
conversionPool *ofdgo.ConversionPool conversionPool *ofdgo.ConversionPool
} }
@@ -436,21 +436,21 @@ func Open(data []byte, opts OpenOptions) (*Session, error) {
if len(data) == 0 { if len(data) == 0 {
return nil, fmt.Errorf("empty ofd data") 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 { if err != nil {
return nil, err return nil, err
} }
reader, err := ofdgo.NewReader(source, opts.ReaderOptions...) reader, err := ofdgo.NewReader(input, opts.ReaderOptions...)
if err != nil { if err != nil {
source.Close() input.Close()
return nil, err return nil, err
} }
session, err := newSession(reader, opts) session, err := newSession(reader, opts)
if err != nil { if err != nil {
source.Close() input.Close()
return nil, err return nil, err
} }
session.source = source session.input = input
return session, nil return session, nil
} }
@@ -461,7 +461,7 @@ func (s *Session) Close() error {
return nil return nil
} }
reader := s.Reader reader := s.Reader
source := s.source source := s.input
*s = Session{} *s = Session{}
var err error var err error
if reader != nil { if reader != nil {
@@ -1336,12 +1336,12 @@ func (e *businessEngine) detectDocumentFormat(args []platformValue) (any, error)
return nil, fmt.Errorf("missing detection arguments") return nil, fmt.Errorf("missing detection arguments")
} }
reader := &browserReaderAt{read: args[0], size: int64(args[1].Float()), checkpoint: args[2]} 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 { if err != nil {
return nil, err return nil, err
} }
defer source.Close() defer input.Close()
return ofdgo.DetectFormat(source) return ofdgo.DetectFormat(input)
} }
// convertPDFDocument 通过库转换PDF,不替换当前文档会话 // convertPDFDocument 通过库转换PDF,不替换当前文档会话
@@ -1351,11 +1351,11 @@ func (e *businessEngine) convertPDFDocument(args []platformValue) (any, error) {
if len(args) != 6 { if len(args) != 6 {
return nil, fmt.Errorf("missing PDF data") return nil, fmt.Errorf("missing PDF data")
} }
source, err := e.sourceFromValue(args[0]) input, err := e.inputFromValue(args[0])
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer source.Close() defer input.Close()
fonts, err := e.fontsFromJS(args[1]) fonts, err := e.fontsFromJS(args[1])
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1376,7 +1376,7 @@ func (e *businessEngine) convertPDFDocument(args []platformValue) (any, error) {
})) }))
} }
options.RendererOptions = []ofdgo.RendererOption{ofdgo.WithFontFS(fontSources...)} 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 { if err != nil {
return nil, err return nil, err
} }
@@ -1478,12 +1478,12 @@ func (e *businessEngine) convertFile(args []platformValue) (any, error) {
return nil, err return nil, err
} }
reader := &browserReaderAt{read: args[0], checkpoint: args[6], size: int64(args[1].Float())} 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 { if err != nil {
return nil, err return nil, err
} }
defer source.Close() defer input.Close()
report, err := ofdgo.Convert(e.newOutputContext(args[6]), source, writer, options) report, err := ofdgo.Convert(e.newOutputContext(args[6]), input, writer, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1514,8 +1514,8 @@ func (e *businessEngine) packFiles(args []platformValue) (any, error) {
inputs := make([]*browserReaderAt, files.Length()) inputs := make([]*browserReaderAt, files.Length())
defer func() { defer func() {
for _, entry := range entries { for _, entry := range entries {
if entry.Source != nil { if entry.Input != nil {
entry.Source.Close() entry.Input.Close()
} }
} }
}() }()
@@ -1524,12 +1524,12 @@ func (e *businessEngine) packFiles(args []platformValue) (any, error) {
name := file.Get("name").String() name := file.Get("name").String()
read := args[1].Invoke(i) read := args[1].Invoke(i)
reader := &browserReaderAt{read: read, checkpoint: args[4], size: int64(file.Get("size").Float())} 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 { if err != nil {
return nil, err return nil, err
} }
inputs[i] = reader 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 { options := ofdgo.ArchiveOptions{OnProgress: func(completed, total int) error {
if completed > 0 { if completed > 0 {
@@ -1558,14 +1558,14 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) {
if len(args) < 3 { if len(args) < 3 {
return nil, fmt.Errorf("missing open arguments") return nil, fmt.Errorf("missing open arguments")
} }
source, err := e.sourceFromValue(args[0]) input, err := e.inputFromValue(args[0])
if err != nil { if err != nil {
return nil, err return nil, err
} }
keep := false keep := false
defer func() { defer func() {
if !keep { if !keep {
source.Close() input.Close()
} }
}() }()
fonts, err := e.fontsFromJS(args[1]) fonts, err := e.fontsFromJS(args[1])
@@ -1582,7 +1582,7 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) {
defer clear(credentials.Password) defer clear(credentials.Password)
options.ReaderOptions = append(options.ReaderOptions, ofdgo.WithCredentials(credentials)) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -1599,7 +1599,7 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) {
e.currentSession = nil e.currentSession = nil
} }
e.currentSession = session e.currentSession = session
session.source = source session.input = input
keep = true keep = true
return e.currentSession.Summary(), nil return e.currentSession.Summary(), nil
} }
@@ -5505,14 +5505,14 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) {
if args[0].IsNull() { if args[0].IsNull() {
return nil, nil return nil, nil
} }
source, err := e.sourceFromValue(args[0]) input, err := e.inputFromValue(args[0])
if err != nil { if err != nil {
return nil, err return nil, err
} }
keep := false keep := false
defer func() { defer func() {
if !keep { if !keep {
source.Close() input.Close()
} }
}() }()
var options []ofdgo.ReaderOption var options []ofdgo.ReaderOption
@@ -5524,11 +5524,11 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) {
defer clear(credentials.Password) defer clear(credentials.Password)
options = append(options, ofdgo.WithCredentials(credentials)) options = append(options, ofdgo.WithCredentials(credentials))
} }
reader, err := ofdgo.NewReader(source, options...) reader, err := ofdgo.NewReader(input, options...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
reader.Closer = source reader.Closer = input
doc, err := reader.Doc() doc, err := reader.Doc()
if err != nil { if err != nil {
reader.Close() reader.Close()
@@ -6180,7 +6180,7 @@ func (e *businessEngine) selectDocumentVersion(args []platformValue) (any, error
if err != nil { if err != nil {
return nil, err 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.Close()
e.currentSession = session e.currentSession = session
e.clearImport() e.clearImport()
@@ -6338,7 +6338,7 @@ func (e *businessEngine) selectDocument(args []platformValue) (any, error) {
if err != nil { if err != nil {
return nil, err 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.Close()
e.currentSession, e.currentEditor = session, session.editor e.currentSession, e.currentEditor = session, session.editor
if e.pendingImport != nil { if e.pendingImport != nil {
@@ -6440,7 +6440,7 @@ func (e *businessEngine) previewEditor(editor *ofdgo.Editor, annotations bool) (
editor.SetFontFS(session.fontFS) editor.SetFontFS(session.fontFS)
} }
if editor == e.currentEditor { 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() _ = e.currentSession.Close()
} }
+22 -22
View File
@@ -89,7 +89,7 @@ type NativeEngine struct {
// nativeReader 在会话当前操作上下文中分块读取,不捕获已结束请求的上下文 // nativeReader 在会话当前操作上下文中分块读取,不捕获已结束请求的上下文
type nativeReader struct { type nativeReader struct {
engine *NativeEngine engine *NativeEngine
source io.ReaderAt input io.ReaderAt
} }
// nativeCall 保存单次worker适配状态 // 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) { func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit func(any)) (result map[string]any) {
e.once.Do(func() { e.once.Do(func() {
e.gate = make(chan struct{}, 1) e.gate = make(chan struct{}, 1)
e.resolveSource = e.source e.resolveInput = e.input
e.conversionPool = e.ConversionPool e.conversionPool = e.ConversionPool
}) })
if ctx == nil { if ctx == nil {
@@ -236,7 +236,7 @@ func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit f
// Close 释放会话,不关闭宿主持有的ReaderAt资源 // Close 释放会话,不关闭宿主持有的ReaderAt资源
func (e *NativeEngine) Close() error { 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{}{} e.gate <- struct{}{}
defer func() { <-e.gate }() defer func() { <-e.gate }()
e.closed = true e.closed = true
@@ -253,8 +253,8 @@ func (e *NativeEngine) Close() error {
return err return err
} }
// source 读取授权源并以宿主报告的大小为准 // Input 读取授权源并以宿主报告的大小为准
func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int64, error) { func (e *NativeEngine) input(ctx context.Context, value any) (io.ReaderAt, int64, error) {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, 0, err return nil, 0, err
} }
@@ -262,7 +262,7 @@ func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int6
value = p.value value = p.value
} }
if data, ok := value.([]byte); ok { 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) descriptor, ok := value.(map[string]any)
if !ok { if !ok {
@@ -272,14 +272,14 @@ func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int6
if !ok || token == "" || e.Resolve == nil { if !ok || token == "" || e.Resolve == nil {
return nil, 0, fmt.Errorf("unauthorized native file") return nil, 0, fmt.Errorf("unauthorized native file")
} }
source, size, err := e.Resolve(token) input, size, err := e.Resolve(token)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
if source == nil || size < 0 { if input == nil || size < 0 {
return nil, 0, fmt.Errorf("invalid native file source") 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自身阻塞时由宿主控制超时 // ReadAt 分块传递取消检查,宿主ReadAt自身阻塞时由宿主控制超时
@@ -290,7 +290,7 @@ func (r nativeReader) ReadAt(data []byte, offset int64) (int, error) {
return total, err return total, err
} }
n := min(len(data), 256<<10) n := min(len(data), 256<<10)
got, err := r.source.ReadAt(data[:n], offset) got, err := r.input.ReadAt(data[:n], offset)
total += got total += got
if err != nil { if err != nil {
return total, err return total, err
@@ -307,7 +307,7 @@ func (r nativeReader) ReadAt(data []byte, offset int64) (int, error) {
// 入参: ctx 调用上下文, value 资源参数 // 入参: ctx 调用上下文, value 资源参数
// 返回: []byte 资源数据, error 读取错误 // 返回: []byte 资源数据, error 读取错误
func (e *NativeEngine) readBytes(ctx context.Context, value any) ([]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 { if err != nil {
return nil, err return nil, err
} }
@@ -320,7 +320,7 @@ func (e *NativeEngine) readBytes(ctx context.Context, value any) ([]byte, error)
return nil, err return nil, err
} }
n := int(min(int64(256<<10), size-offset)) 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 { if got != n {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
@@ -540,7 +540,7 @@ func (c *nativeCall) progress(kind, stage string, phased bool) nativeCallback {
// readCallback 创建有界随机读取回调 // readCallback 创建有界随机读取回调
func (c *nativeCall) readCallback(value any) (nativeCallback, int64, error) { 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 { if err != nil {
return nil, 0, err 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") return nil, fmt.Errorf("invalid native read range")
} }
data := make([]byte, n) data := make([]byte, n)
got, err := source.ReadAt(data, offset) got, err := input.ReadAt(data, offset)
if got != n { if got != n {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
@@ -876,13 +876,13 @@ func nativeOutputBase(value platformValue) string {
return base return base
} }
// sourceFromValue 读取宿主授权的随机读取源 // inputFromValue 读取宿主授权的随机读取源
func (e *businessEngine) sourceFromValue(value platformValue) (*ofdgo.Source, error) { func (e *businessEngine) inputFromValue(value platformValue) (*ofdgo.Input, error) {
reader, size, err := e.resolveSource(e.operationContext(), value.value) reader, size, err := e.resolveInput(e.operationContext(), value.value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ofdgo.NewSource(reader, size) return ofdgo.NewInput(reader, size)
} }
// platformValueOf 包装参数 // platformValueOf 包装参数
@@ -1028,12 +1028,12 @@ func (e *businessEngine) bytesFromJS(v platformValue) ([]byte, error) {
if data, ok := v.value.([]byte); ok { if data, ok := v.value.([]byte); ok {
return append([]byte(nil), data...), nil return append([]byte(nil), data...), nil
} }
source, err := e.sourceFromValue(v) input, err := e.inputFromValue(v)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer source.Close() defer input.Close()
return io.ReadAll(io.NewSectionReader(source, 0, source.Size())) return io.ReadAll(io.NewSectionReader(input, 0, input.Size()))
} }
// successResult 保留二进制返回结构 // successResult 保留二进制返回结构
+3 -3
View File
@@ -221,11 +221,11 @@ func platformParseJSON(data []byte) map[string]any {
return result return result
} }
// sourceFromValue 读取浏览器二进制源 // inputFromValue 读取浏览器二进制源
func (e *businessEngine) sourceFromValue(value platformValue) (*ofdgo.Source, error) { func (e *businessEngine) inputFromValue(value platformValue) (*ofdgo.Input, error) {
data, err := e.bytesFromJS(value) data, err := e.bytesFromJS(value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ofdgo.NewSource(bytes.NewReader(data), int64(len(data))) return ofdgo.NewInput(bytes.NewReader(data), int64(len(data)))
} }
+2 -2
View File
@@ -475,11 +475,11 @@ func (h *Host) call(ctx context.Context, name string, value any) (any, error) {
} }
descriptor, _ := args["data"].(map[string]any) descriptor, _ := args["data"].(map[string]any)
key, _ := descriptor["nativeFile"].(string) key, _ := descriptor["nativeFile"].(string)
source, size, err := h.files.resolve(key) input, size, err := h.files.resolve(key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = io.Copy(writer, io.NewSectionReader(source, 0, size)) _, err = io.Copy(writer, io.NewSectionReader(input, 0, size))
return nil, err return nil, err
case "fonts": case "fonts":
h.fontOnce.Do(h.readFonts) h.fontOnce.Do(h.readFonts)
+9 -5
View File
@@ -30,6 +30,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/xiaoqidun/ofdgo"
basefile "github.com/xiaoqidun/ofdgo/internal/base/file" basefile "github.com/xiaoqidun/ofdgo/internal/base/file"
) )
@@ -43,7 +44,7 @@ type fileEntry struct {
directory bool directory bool
owned bool owned bool
temporary bool temporary bool
saveTarget *basefile.Target saveTarget ofdgo.Output
readers int readers int
retired bool retired bool
} }
@@ -61,7 +62,7 @@ type fileStore struct {
type fileWriter struct { type fileWriter struct {
id string id string
store *fileStore store *fileStore
output *basefile.Writer output ofdgo.WriteTransaction
} }
// newFileStore 创建独立文件授权表 // newFileStore 创建独立文件授权表
@@ -127,7 +128,7 @@ func (s *fileStore) add(name string, directory bool) (map[string]any, error) {
id := rand.Text() id := rand.Text()
entry := &fileEntry{path: name, name: filepath.Base(name), directory: directory} entry := &fileEntry{path: name, name: filepath.Base(name), directory: directory}
if !directory { if !directory {
entry.saveTarget, err = basefile.NewTarget(name) entry.saveTarget, err = ofdgo.NewFileOutput(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -417,7 +418,7 @@ func (s *fileStore) begin(ctx context.Context, id string) (*fileWriter, error) {
} }
if entry.saveTarget == nil { if entry.saveTarget == nil {
var err error var err error
entry.saveTarget, err = basefile.NewTarget(entry.path) entry.saveTarget, err = ofdgo.NewFileOutput(entry.path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -489,7 +490,10 @@ func (w *fileWriter) Write(data []byte) (int, error) {
// 返回: error 提交错误 // 返回: error 提交错误
func (w *fileWriter) Close() error { func (w *fileWriter) Close() error {
defer w.release() defer w.release()
return w.output.Close() if err := w.output.Commit(); err != nil {
return errors.Join(err, w.output.Abort())
}
return nil
} }
// Abort 删除未提交的临时输出 // Abort 删除未提交的临时输出
+51 -51
View File
@@ -31,10 +31,10 @@ import (
basefile "github.com/xiaoqidun/ofdgo/internal/base/file" basefile "github.com/xiaoqidun/ofdgo/internal/base/file"
) )
// Source 统一文件、内存及顺序流的随机读取,可并发读取和关闭,不可复制 // Input 统一文件、内存及顺序流的随机读取,可并发读取和关闭,不可复制
// 调用方应在阅读、编辑及转换结束后调用Close,借入的数据需在此期间保持不变 // 调用方应在阅读、编辑及转换结束后调用Close,借入的数据需在此期间保持不变
// 借入的随机读取器应支持并发ReadAt,Close不会关闭调用方传入的读取器 // 借入的随机读取器应支持并发ReadAt,Close不会关闭调用方传入的读取器
type Source struct { type Input struct {
mu sync.RWMutex mu sync.RWMutex
reader io.ReaderAt reader io.ReaderAt
size int64 size int64
@@ -43,12 +43,12 @@ type Source struct {
temp bool temp bool
} }
// SourceOptions 设置顺序输入的缓存及大小限制,单位为字节 // InputOptions 设置顺序输入的缓存及大小限制,单位为字节
// MemoryLimit为0时使用8MiB,浏览器WASM仅用内存;负值表示仅用内存 // MemoryLimit为0时使用8MiB,浏览器WASM仅用内存;负值表示仅用内存
// TempDir为空时使用系统临时目录,不自动创建调用方指定的目录 // TempDir为空时使用系统临时目录,不自动创建调用方指定的目录
// MaxSize为0时不限制输入大小,OpenFile和ReadSource均检查此限制 // MaxSize为0时不限制输入大小,OpenFile和ReadInput均检查此限制
// 无临时文件系统的平台可显式使用仅内存模式,并通过MaxSize限制输入 // 无临时文件系统的平台可显式使用仅内存模式,并通过MaxSize限制输入
type SourceOptions struct { type InputOptions struct {
MemoryLimit int64 MemoryLimit int64
MaxSize int64 MaxSize int64
TempDir string TempDir string
@@ -56,8 +56,8 @@ type SourceOptions struct {
// OpenFile 打开文件输入,不复制文件内容,Close释放文件句柄 // OpenFile 打开文件输入,不复制文件内容,Close释放文件句柄
// 入参: ctx 取消上下文, path 文件路径, options 输入选项 // 入参: ctx 取消上下文, path 文件路径, options 输入选项
// 返回: *Source 输入源, error 打开或大小错误 // 返回: *Input 输入源, error 打开或大小错误
func OpenFile(ctx context.Context, path string, options SourceOptions) (*Source, error) { func OpenFile(ctx context.Context, path string, options InputOptions) (*Input, error) {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, err return nil, err
} }
@@ -72,39 +72,39 @@ func OpenFile(ctx context.Context, path string, options SourceOptions) (*Source,
} }
if !info.Mode().IsRegular() { if !info.Mode().IsRegular() {
f.Close() 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 { if err != nil {
f.Close() f.Close()
return nil, err return nil, err
} }
source.file, source.path = f, path input.file, input.path = f, path
if err := source.checkOptions(ctx, options); err != nil { if err := input.checkOptions(ctx, options); err != nil {
source.Close() input.Close()
return nil, err return nil, err
} }
return source, nil return input, nil
} }
// NewSource 借用指定长度的随机读取源,不复制数据或接管其生命周期 // NewInput 借用指定长度的随机读取源,不复制数据或接管其生命周期
// 入参: reader 随机读取器, size 数据大小 // 入参: reader 随机读取器, size 数据大小
// 返回: *Source 输入源, error 参数错误 // 返回: *Input 输入源, error 参数错误
func NewSource(reader io.ReaderAt, size int64) (*Source, error) { func NewInput(reader io.ReaderAt, size int64) (*Input, error) {
if reader == nil || size < 0 { 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,调用方应自行设置超时或关闭流 // 不关闭传入流;取消无法中断阻塞的Read,调用方应自行设置超时或关闭流
// 入参: ctx 取消上下文, reader 输入流, options 输入选项 // 入参: ctx 取消上下文, reader 输入流, options 输入选项
// 返回: *Source 输入源, error 读取、缓存或取消错误 // 返回: *Input 输入源, error 读取、缓存或取消错误
func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (*Source, error) { func ReadInput(ctx context.Context, reader io.Reader, options InputOptions) (*Input, error) {
if reader == nil || options.MaxSize < 0 { 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 { if err := ctx.Err(); err != nil {
return nil, err return nil, err
@@ -129,16 +129,16 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (*
return nil, err return nil, err
} }
if offset < 0 || offset > size { 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 { if err != nil {
return nil, err return nil, err
} }
if err := source.checkOptions(ctx, options); err != nil { if err := input.checkOptions(ctx, options); err != nil {
return nil, err 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 limit = -1
} }
} }
source := &Source{} input := &Input{}
success := false success := false
defer func() { defer func() {
if !success { if !success {
source.Close() input.Close()
} }
}() }()
var memory bytes.Buffer var memory bytes.Buffer
@@ -165,7 +165,7 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (*
} }
n, err := reader.Read(buffer) n, err := reader.Read(buffer)
if n < 0 || n > len(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 { if err != nil && err != io.EOF {
return nil, err return nil, err
@@ -175,28 +175,28 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (*
} }
if n > 0 { if n > 0 {
empty = 0 empty = 0
if source.size > int64(^uint64(0)>>1)-int64(n) || options.MaxSize > 0 && int64(n) > options.MaxSize-source.size { if input.size > int64(^uint64(0)>>1)-int64(n) || options.MaxSize > 0 && int64(n) > options.MaxSize-input.size {
return nil, fmt.Errorf("source exceeds size limit") 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) file, failure := basefile.NewTemp(options.TempDir)
if failure != nil { if failure != nil {
return nil, failure return nil, failure
} }
source.file, source.temp = file, true input.file, input.temp = file, true
if _, failure = file.Write(memory.Bytes()); failure != nil { if _, failure = file.Write(memory.Bytes()); failure != nil {
return nil, failure return nil, failure
} }
memory = bytes.Buffer{} memory = bytes.Buffer{}
} }
var output io.Writer = &memory var output io.Writer = &memory
if source.file != nil { if input.file != nil {
output = source.file output = input.file
} }
if _, failure := output.Write(buffer[:n]); failure != nil { if _, failure := output.Write(buffer[:n]); failure != nil {
return nil, failure return nil, failure
} }
source.size += int64(n) input.size += int64(n)
} else if err == nil { } else if err == nil {
empty++ empty++
if empty >= 100 { if empty >= 100 {
@@ -210,34 +210,34 @@ func ReadSource(ctx context.Context, reader io.Reader, options SourceOptions) (*
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, err return nil, err
} }
source.reader = bytes.NewReader(memory.Bytes()) input.reader = bytes.NewReader(memory.Bytes())
if source.file != nil { if input.file != nil {
source.reader = source.file input.reader = input.file
} }
success = true success = true
return source, nil return input, nil
} }
// checkOptions 检查取消状态及输入大小 // checkOptions 检查取消状态及输入大小
// 入参: ctx 取消上下文, options 输入选项 // 入参: ctx 取消上下文, options 输入选项
// 返回: error 取消或大小错误 // 返回: 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 { 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() return ctx.Err()
} }
// Size 返回输入字节数,关闭后仍可查询 // Size 返回输入字节数,关闭后仍可查询
// 返回: int64 数据大小 // 返回: int64 数据大小
func (s *Source) Size() int64 { func (s *Input) Size() int64 {
return s.size return s.size
} }
// ReadAt 读取指定位置的数据,关闭后返回os.ErrClosed // ReadAt 读取指定位置的数据,关闭后返回os.ErrClosed
// 入参: data 目标缓冲区, offset 字节偏移 // 入参: data 目标缓冲区, offset 字节偏移
// 返回: int 读取字节数, error 读取错误 // 返回: 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() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
if s.reader == nil { if s.reader == nil {
@@ -248,7 +248,7 @@ func (s *Source) ReadAt(data []byte, offset int64) (int, error) {
// Close 释放自有文件和缓存,等待正在进行的读取结束,可重复调用 // Close 释放自有文件和缓存,等待正在进行的读取结束,可重复调用
// 返回: error 文件关闭或临时文件删除错误 // 返回: error 文件关闭或临时文件删除错误
func (s *Source) Close() error { func (s *Input) Close() error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
s.reader = nil s.reader = nil
@@ -266,18 +266,18 @@ func (s *Source) Close() error {
// NewReader 从统一输入源创建OFD阅读器,不接管输入源生命周期 // NewReader 从统一输入源创建OFD阅读器,不接管输入源生命周期
// 阅读器及其派生编辑器使用结束后,调用方再关闭输入源 // 阅读器及其派生编辑器使用结束后,调用方再关闭输入源
// 入参: source 输入源, options 阅读选项 // 入参: input 输入源, options 阅读选项
// 返回: *Reader 阅读器实例, error 错误信息 // 返回: *Reader 阅读器实例, error 错误信息
func NewReader(source *Source, options ...ReaderOption) (*Reader, error) { func NewReader(input *Input, options ...ReaderOption) (*Reader, error) {
if source == nil { if input == nil {
return nil, fmt.Errorf("missing source") return nil, fmt.Errorf("missing input")
} }
zr, err := zip.NewReader(source, source.Size()) zr, err := zip.NewReader(input, input.Size())
if err != nil { if err != nil {
return nil, err return nil, err
} }
reader := &Reader{ reader := &Reader{
Path: source.path, Path: input.path,
Zip: zr, Zip: zr,
} }
if err := reader.open(options); err != nil { if err := reader.open(options); err != nil {
+5 -5
View File
@@ -28,7 +28,7 @@ import (
// ArchiveFile 描述已生成的归档文件,不关闭调用方提供的源读取器 // ArchiveFile 描述已生成的归档文件,不关闭调用方提供的源读取器
type ArchiveFile struct { type ArchiveFile struct {
Name string Name string
Source *Source Input *Input
} }
// ArchiveOptions 设置ZIP封装,压缩不重新编码归档内已生成的文档 // ArchiveOptions 设置ZIP封装,压缩不重新编码归档内已生成的文档
@@ -46,7 +46,7 @@ func WriteArchive(ctx context.Context, writer io.Writer, files []ArchiveFile, op
} }
names := map[string]bool{} names := map[string]bool{}
for _, file := range files { 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) return 0, fmt.Errorf("invalid archive file %q", file.Name)
} }
names[file.Name] = true names[file.Name] = true
@@ -76,15 +76,15 @@ func WriteArchive(ctx context.Context, writer io.Writer, files []ArchiveFile, op
if err != nil { if err != nil {
return count, err return count, err
} }
source := convertReader{ctx: ctx, source: file.Source} input := convertReader{ctx: ctx, input: file.Input}
if buffer == nil { if buffer == nil {
buffer = make([]byte, 32<<10) 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 { if err != nil {
return count, err return count, err
} }
if written != file.Source.Size() { if written != file.Input.Size() {
return count, io.ErrUnexpectedEOF return count, io.ErrUnexpectedEOF
} }
} }
+14 -14
View File
@@ -85,7 +85,7 @@ type ConvertReport struct {
// convertReader 将取消上下文传递到随机读取 // convertReader 将取消上下文传递到随机读取
type convertReader struct { type convertReader struct {
ctx context.Context ctx context.Context
source io.ReaderAt input io.ReaderAt
} }
// convertWriter 将取消上下文传递到写入并统计实际字节数 // convertWriter 将取消上下文传递到写入并统计实际字节数
@@ -113,9 +113,9 @@ func OutputFormats() []OutputFormat {
// 原始模式完整OFD另存保持源字节;选页和跨格式输出重新生成文档,压缩仅作用于输出副本 // 原始模式完整OFD另存保持源字节;选页和跨格式输出重新生成文档,压缩仅作用于输出副本
// 输入按需随机读取,输出分块写出;解析对象及后端可能缓存资源,不保证恒定内存 // 输入按需随机读取,输出分块写出;解析对象及后端可能缓存资源,不保证恒定内存
// 出错时报告保留已有诊断,调用方应丢弃未完成输出;只有返回nil才表示转换成功 // 出错时报告保留已有诊断,调用方应丢弃未完成输出;只有返回nil才表示转换成功
// 入参: ctx 取消上下文, source 输入源, output 输出流, options 转换选项 // 入参: ctx 取消上下文, input 输入源, output 输出流, options 转换选项
// 返回: ConvertReport 转换报告, error 错误信息 // 返回: 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 options.Pool != nil {
if err = options.Pool.acquire(ctx, false, 0); err != nil { if err = options.Pool.acquire(ctx, false, 0); err != nil {
return report, err 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 { if err = progress("open", 0, 0); err != nil {
return report, err return report, err
} }
if source == nil || source.Size() <= 0 { if input == nil || input.Size() <= 0 {
return report, fmt.Errorf("empty document source") return report, fmt.Errorf("empty document input")
} }
size := source.Size() size := input.Size()
operationSource := &Source{reader: convertReader{ctx: ctx, source: source}, size: size} operationInput := &Input{reader: convertReader{ctx: ctx, input: input}, size: size}
report.Input = strings.ToLower(strings.TrimSpace(options.Input)) report.Input = strings.ToLower(strings.TrimSpace(options.Input))
if report.Input == "" { if report.Input == "" {
report.Input, err = DetectFormat(operationSource) report.Input, err = DetectFormat(operationInput)
if err != nil { if err != nil {
return report, err 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") return report, fmt.Errorf("missing output writer")
} }
if report.Input == "pdf" && format == "pdf" && options.Compression.Mode != CompressionUnchanged && options.Pages == nil && strings.TrimSpace(options.PageRange) == "" { 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 { if openErr != nil {
return report, openErr return report, openErr
} }
@@ -234,13 +234,13 @@ func Convert(ctx context.Context, source *Source, output io.Writer, options Conv
} }
return nil return nil
} }
editor, report.PDF, err = ImportPDF(ctx, operationSource, pdfOptions) editor, report.PDF, err = ImportPDF(ctx, operationInput, pdfOptions)
if err != nil { if err != nil {
return report, err return report, err
} }
count = editor.PageCount() count = editor.PageCount()
case "ofd": case "ofd":
reader, err = NewReader(operationSource, options.ReaderOptions...) reader, err = NewReader(operationInput, options.ReaderOptions...)
if err != nil { if err != nil {
return report, err 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 { if err = progress("write", 0, 0); err != nil {
return report, err 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 report.Unchanged = err == nil
return report, err 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 { if err = progress("write", 0, 0); err != nil {
return report, err 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 report.Unchanged = err == nil
return report, err return report, err
} }
@@ -370,7 +370,7 @@ func (r convertReader) ReadAt(data []byte, offset int64) (int, error) {
if err := r.ctx.Err(); err != nil { if err := r.ctx.Err(); err != nil {
return 0, err return 0, err
} }
n, err := r.source.ReadAt(data, offset) n, err := r.input.ReadAt(data, offset)
if err == nil { if err == nil {
err = r.ctx.Err() err = r.ctx.Err()
} }
+1 -1
View File
@@ -64,7 +64,7 @@ func (e *Editor) writeEncrypted(writer io.Writer) (int64, error) {
return 0, err return 0, err
} }
defer clear(plaintext.Bytes()) 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 { if err != nil {
return 0, err return 0, err
} }
+1 -1
View File
@@ -93,7 +93,7 @@ type encryptionEntry struct {
// 入参: data 明文OFD包, options 加密策略 // 入参: data 明文OFD包, options 加密策略
// 返回: []byte 加密OFD包, error 错误信息 // 返回: []byte 加密OFD包, error 错误信息
func EncryptPackage(data []byte, options EncryptionOptions) ([]byte, 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 { if err != nil {
return nil, err return nil, err
} }
+2 -3
View File
@@ -33,7 +33,6 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
basefile "github.com/xiaoqidun/ofdgo/internal/base/file"
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image" baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
) )
@@ -41,7 +40,7 @@ import (
const editorPathValidationLimit = 1024 const editorPathValidationLimit = 1024
// Editor 编辑OFD文档,长度单位为毫米,页面索引从0开始,实例需串行使用 // Editor 编辑OFD文档,长度单位为毫米,页面索引从0开始,实例需串行使用
// Info可修改文档元数据,通过方法管理页面、对象和资源,WriteTo输出副本,Save和SaveAs通过绑定路径保存 // Info可修改文档元数据,通过方法管理页面、对象和资源,WriteTo输出副本,Save和SaveAs通过输出事务保存
// OnWriteProgress可选,按fonts、pages、references、resources、compress、write阶段同步回报进度,返回错误则停止保存 // OnWriteProgress可选,按fonts、pages、references、resources、compress、write阶段同步回报进度,返回错误则停止保存
// completed和total为当前阶段已处理及总工作项,compress累计完成条目数且total为0,write阶段不计数 // completed和total为当前阶段已处理及总工作项,compress累计完成条目数且total为0,write阶段不计数
// 阶段可重复,准备完成不代表保存成功 // 阶段可重复,准备完成不代表保存成功
@@ -74,7 +73,7 @@ type Editor struct {
outlines []byte outlines []byte
encryption *encryptionState encryption *encryptionState
output *outputOptimization output *outputOptimization
saveTarget *basefile.Target saveTarget Output
validatedPaths *renderCache[string, bool] validatedPaths *renderCache[string, bool]
richIndex *richTextIndex richIndex *richTextIndex
} }
+7 -7
View File
@@ -24,16 +24,16 @@ import (
// DetectFormat 按文件内容识别PDF或OFD,不读取压缩内容或关闭输入 // DetectFormat 按文件内容识别PDF或OFD,不读取压缩内容或关闭输入
// 识别结果不代表文档完整有效,仍需通过对应阅读器解析 // 识别结果不代表文档完整有效,仍需通过对应阅读器解析
// 入参: source 输入源 // 入参: input 输入源
// 返回: string 格式标识pdf或ofd, error 读取错误或不支持的格式 // 返回: string 格式标识pdf或ofd, error 读取错误或不支持的格式
func DetectFormat(source *Source) (string, error) { func DetectFormat(input *Input) (string, error) {
if source == nil || source.Size() <= 0 { if input == nil || input.Size() <= 0 {
return "", fmt.Errorf("empty document source") return "", fmt.Errorf("empty document input")
} }
size := source.Size() size := input.Size()
var header [1024]byte var header [1024]byte
data := header[:min(size, int64(len(header)))] 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 { if err != nil && err != io.EOF {
return "", err return "", err
} }
@@ -43,7 +43,7 @@ func DetectFormat(source *Source) (string, error) {
if bytes.HasPrefix(data, []byte("%PDF-")) { if bytes.HasPrefix(data, []byte("%PDF-")) {
return "pdf", nil return "pdf", nil
} }
archive, err := zip.NewReader(source, size) archive, err := zip.NewReader(input, size)
if err == nil { if err == nil {
reader := &Reader{Zip: archive} reader := &Reader{Zip: archive}
if err := reader.indexPackage(); err != nil { if err := reader.indexPackage(); err != nil {
+7 -7
View File
@@ -204,10 +204,10 @@ func (o pdfObjectOrigin) merge(other pdfObjectOrigin) pdfObjectOrigin {
// ImportPDF 将PDF内容转换为独立OFD编辑文档,失败时不返回部分结果 // ImportPDF 将PDF内容转换为独立OFD编辑文档,失败时不返回部分结果
// 返回的编辑文档独立于输入源,函数返回后可关闭输入源 // 返回的编辑文档独立于输入源,函数返回后可关闭输入源
// 入参: ctx 取消上下文, source 输入源, options 转换选项 // 入参: ctx 取消上下文, input 输入源, options 转换选项
// 返回: *Editor 编辑文档, PDFImportReport 转换统计, error 错误信息 // 返回: *Editor 编辑文档, PDFImportReport 转换统计, error 错误信息
func ImportPDF(ctx context.Context, source *Source, options PDFImportOptions) (*Editor, PDFImportReport, error) { func ImportPDF(ctx context.Context, input *Input, options PDFImportOptions) (*Editor, PDFImportReport, error) {
if source == nil { if input == nil {
return nil, PDFImportReport{}, fmt.Errorf("missing source") return nil, PDFImportReport{}, fmt.Errorf("missing source")
} }
if err := ctx.Err(); err != nil { 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) } readWarning = func(diagnostic pdfgo.Diagnostic) { diagnostics = append(diagnostics, diagnostic) }
readOptions.Warning = func(diagnostic pdfgo.Diagnostic) { readWarning(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 { if err != nil {
return nil, PDFImportReport{}, err return nil, PDFImportReport{}, err
} }
@@ -374,10 +374,10 @@ func ImportPDF(ctx context.Context, source *Source, options PDFImportOptions) (*
} }
// ConvertPDF 将PDF转换并写入OFD,转换阶段失败时不写入目标 // ConvertPDF 将PDF转换并写入OFD,转换阶段失败时不写入目标
// 入参: ctx 取消上下文, source 输入源, output OFD输出, options 转换选项 // 入参: ctx 取消上下文, input 输入源, output OFD输出, options 转换选项
// 返回: PDFImportReport 转换统计, error 错误信息 // 返回: PDFImportReport 转换统计, error 错误信息
func ConvertPDF(ctx context.Context, source *Source, output io.Writer, options PDFImportOptions) (PDFImportReport, error) { func ConvertPDF(ctx context.Context, input *Input, output io.Writer, options PDFImportOptions) (PDFImportReport, error) {
editor, report, err := ImportPDF(ctx, source, options) editor, report, err := ImportPDF(ctx, input, options)
if err != nil { if err != nil {
return PDFImportReport{}, err return PDFImportReport{}, err
} }
+1 -1
View File
@@ -161,7 +161,7 @@ func (c *semanticCompiler) drawStampContent(stamp Stamp) error {
return err return err
} }
if stamp.Type == "ofd" { 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 { if err != nil {
return err return err
} }
+2 -2
View File
@@ -49,7 +49,7 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
} }
return 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 { if err == nil {
defer reader.Close() defer reader.Close()
doc, err := reader.DocContext(pageContext) doc, err := reader.DocContext(pageContext)
@@ -114,7 +114,7 @@ func (r *Renderer) renderOFDStampImage(data []byte) image.Image {
r.renderError = err r.renderError = err
return nil 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 { if err != nil {
r.renderError = err r.renderError = err
return nil return nil
+3 -3
View File
@@ -236,10 +236,10 @@ func WithSignatureVerifyTime(t time.Time) SignatureVerifyOption {
} }
// VerifySignatures 验证输入源中的OFD签名,不接管输入源生命周期 // VerifySignatures 验证输入源中的OFD签名,不接管输入源生命周期
// 入参: source 输入源, opts 签名验证选项 // 入参: input 输入源, opts 签名验证选项
// 返回: []SignatureVerifyReport 签名验证报告, error 错误信息 // 返回: []SignatureVerifyReport 签名验证报告, error 错误信息
func VerifySignatures(source *Source, opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) { func VerifySignatures(input *Input, opts ...SignatureVerifyOption) ([]SignatureVerifyReport, error) {
reader, err := NewReader(source) reader, err := NewReader(input)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+4 -4
View File
@@ -84,7 +84,7 @@ type signatureIDSequence uint64
// 入参: data 最终包字节, options 签署选项 // 入参: data 最终包字节, options 签署选项
// 返回: []byte 签署后的完整包, error 错误信息 // 返回: []byte 签署后的完整包, error 错误信息
func SignPackage(data []byte, options SignatureWriteOptions) ([]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 { if err != nil {
return nil, err return nil, err
} }
@@ -109,7 +109,7 @@ func SignEditorTo(writer io.Writer, editor *Editor, options SignatureWriteOption
if _, err := editor.writePlaintext(&plain); err != nil { if _, err := editor.writePlaintext(&plain); err != nil {
return 0, err 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 { if err != nil {
return 0, err return 0, err
} }
@@ -192,7 +192,7 @@ func signatureWriteOutput(r *Reader, options SignatureWriteOptions) ([]byte, err
if encryption != nil { if encryption != nil {
defer clear(data) defer clear(data)
if options.progress != nil { 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 { if err != nil {
return nil, err return nil, err
} }
@@ -434,7 +434,7 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
+74 -41
View File
@@ -34,6 +34,28 @@ import (
basefile "github.com/xiaoqidun/ofdgo/internal/base/file" 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表示不回报进度 // editorProgress 编辑与保存阶段的进度回调,nil表示不回报进度
type editorProgress func(stage string, completed, total int) error type editorProgress func(stage string, completed, total int) error
@@ -63,11 +85,12 @@ var ErrSaveTarget = errors.New("save target is not set")
// ErrSaveConflict 表示保存目标在打开或最近保存后被其他操作修改 // ErrSaveConflict 表示保存目标在打开或最近保存后被其他操作修改
var ErrSaveConflict = basefile.ErrChanged var ErrSaveConflict = basefile.ErrChanged
// ErrSaveBusy 表示目标文件正在提交其他写入事务 // ErrSaveBusy 表示保存目标已有其他写入事务
var ErrSaveBusy = basefile.ErrBusy var ErrSaveBusy = basefile.ErrBusy
// Snapshot 创建独立编辑快照,完成后可与原编辑器并行使用 // Snapshot 创建独立编辑快照,完成后可与原编辑器并行使用
// 创建过程需与原实例串行,保留修订及保存目标,不复制撤销记录和进度回调 // 创建过程需与原实例串行,保留修订及保存目标,不复制撤销记录和进度回调
// 保存目标与原实例共享,由输出实现协调并发事务及提交版本
// 原包压缩数据直接复制,快照不依赖原阅读器及文件句柄,内存随快照释放 // 原包压缩数据直接复制,快照不依赖原阅读器及文件句柄,内存随快照释放
// 自定义后端及字体文件系统仍由调用方提供,需支持并行读取 // 自定义后端及字体文件系统仍由调用方提供,需支持并行读取
// 入参: ctx 取消上下文 // 入参: ctx 取消上下文
@@ -120,76 +143,86 @@ func (e *Editor) Snapshot(ctx context.Context) (*Editor, error) {
return snapshot, nil return snapshot, nil
} }
// SetSavePath 绑定保存目标并记录当前文件状态,不读取或修改编辑内容 // NewFileOutput 创建本地保存目标并记录当前文件状态,不创建或修改文件
// 绑定已有文件时应在打开后立即调用;空路径解除绑定,调用需与编辑操作串行 // 同一目标应复用返回值,以便后续保存检测外部修改
// 入参: name 目标路径 // 替换前比较文件标识、大小和修改时间,不提供跨进程排他锁
// 返回: error 路径或文件状态错误 // 入参: name 文件路径
func (e *Editor) SetSavePath(name string) error { // 返回: Output 保存目标, error 路径或文件状态错误
if name == "" { func NewFileOutput(name string) (Output, error) {
e.saveTarget = nil
return nil
}
target, err := basefile.NewTarget(name) target, err := basefile.NewTarget(name)
if err != nil { if err != nil {
return err return nil, err
} }
e.saveTarget = target return &fileOutput{target: target}, nil
return nil
} }
// SavePath 返回当前保存目标,未绑定时返回空字符串 // Begin 创建本地文件写入事务
// 返回: string 绝对文件路径 // 入参: ctx 取消上下文
func (e *Editor) SavePath() string { // 返回: WriteTransaction 写入事务, error 文件状态错误
if e.saveTarget == nil { func (o *fileOutput) Begin(ctx context.Context) (WriteTransaction, error) {
return "" return o.target.Begin(ctx)
} }
return e.saveTarget.Name()
// SetOutput 绑定保存目标,不读取或修改编辑内容,nil解除绑定
// 调用需与编辑操作串行,目标生命周期由调用方管理
// 入参: output 保存目标
func (e *Editor) SetOutput(output Output) {
e.saveTarget = output
}
// Output 返回当前保存目标,未绑定时返回nil
// 返回: Output 保存目标
func (e *Editor) Output() Output {
return e.saveTarget
} }
// Save 将当前完整文档写回已绑定目标,不改变编辑状态及撤销记录 // Save 将当前完整文档写回已绑定目标,不改变编辑状态及撤销记录
// 调用需与编辑操作串行,支持取消;失败保留原文件,成功后可继续编辑和保存 // 调用需与编辑操作串行;后台保存应先创建Snapshot,成功后可继续编辑和保存
// 入参: ctx 取消上下文, options 输出配置 // 入参: ctx 取消上下文, options 输出配置
// 返回: int64 输出字节数, error 保存错误 // 返回: int64 输出字节数, error 保存错误
func (e *Editor) Save(ctx context.Context, options WriteOptions) (int64, error) { func (e *Editor) Save(ctx context.Context, options WriteOptions) (int64, error) {
if e.saveTarget == nil { return e.saveOutput(ctx, e.saveTarget, options)
return 0, ErrSaveTarget
}
return e.saveFile(ctx, e.saveTarget, options)
} }
// SaveAs 将完整文档另存,成功后切换保存目标,不改变撤销记录 // SaveAs 将完整文档另存,成功后切换保存目标,不改变撤销记录
// 调用需与编辑操作串行,取消或失败时保持原目标 // 调用需与编辑操作串行,取消或失败时保持原目标
// 入参: ctx 取消上下文, name 目标路径, options 输出配置 // 入参: ctx 取消上下文, output 保存目标, options 输出配置
// 返回: int64 输出字节数, error 保存错误 // 返回: int64 输出字节数, error 保存错误
func (e *Editor) SaveAs(ctx context.Context, name string, options WriteOptions) (int64, error) { func (e *Editor) SaveAs(ctx context.Context, output Output, options WriteOptions) (int64, error) {
target, err := basefile.NewTarget(name) n, err := e.saveOutput(ctx, output, options)
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)
if err == nil { if err == nil {
e.saveTarget = target e.saveTarget = output
} }
return n, err return n, err
} }
// saveFile 使用共用文件事务提交文档 // saveOutput 使用输出事务提交文档并释放失败事务
// 入参: ctx 取消上下文, target 保存目标, options 输出配置 // 入参: ctx 取消上下文, target 保存目标, options 输出配置
// 返回: int64 输出字节数, error 保存错误 // 返回: 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) writer, err := target.Begin(ctx)
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer writer.Abort() defer func() {
n, err := e.WriteToWithOptions(ctx, writer, options) if err != nil {
err = errors.Join(err, writer.Abort())
}
}()
n, err = e.WriteToWithOptions(ctx, writer, options)
if err != nil { if err != nil {
return n, err return n, err
} }
return n, writer.Close() if err = ctx.Err(); err != nil {
return n, err
}
return n, writer.Commit()
} }
// WriteTo 逐个条目写出OFD,不关闭调用方输出流,出错时应丢弃本次输出 // WriteTo 逐个条目写出OFD,不关闭调用方输出流,出错时应丢弃本次输出