feat(统一输入): 统一流式输入及接口命名
build.yaml / build (push) Successful in 43s

This commit is contained in:
xiaoqidun committed 2026-10-19 08:43:50 +08:00
1 parent 151ddb0a20
commit d97c6f4e6f
60 files changed
+570 -262

No files matched your search

+22
View File
@@ -52,6 +52,28 @@ type Writer struct {
closed bool
}
// NewTemp 创建私有输入缓存文件,调用方负责关闭和删除
// 入参: directory 缓存目录,空值使用系统临时目录
// 返回: *os.File 缓存文件, error 创建错误
func NewTemp(directory string) (*os.File, error) {
if directory == "" {
directory = os.TempDir()
}
for range 100 {
n, err := rand.Int(rand.Reader, big.NewInt(10000000))
if err != nil {
return nil, err
}
name := filepath.Join(directory, fmt.Sprintf("ofdgo-%07d.tmp", n.Int64()))
file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if errors.Is(err, os.ErrExist) {
continue
}
return file, err
}
return nil, os.ErrExist
}
// NewTarget 记录目标当前状态,允许目标尚不存在
// 入参: name 目标路径
// 返回: *Target 保存目标, error 路径或文件错误
+1 -1
View File
@@ -96,7 +96,7 @@ func pngColorProfile(ctx context.Context, data []byte) ([]byte, *pngColor, error
if err != nil {
return nil, nil, err
}
profile, err := io.ReadAll(io.LimitReader(contextInput{ReadCloser: reader, context: ctx}, profileLimit+1))
profile, err := io.ReadAll(io.LimitReader(contextInput{ReadCloser: reader, ctx: ctx}, profileLimit+1))
closeErr := reader.Close()
if err != nil {
return nil, nil, err
+2 -2
View File
@@ -139,7 +139,7 @@ func (img *EncodedImage) decodeContext(ctx context.Context, pending chan struct{
close(pending)
img.mu.Unlock()
}()
pixels, _, err := image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(img.data)), context: ctx})
pixels, _, err := image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(img.data)), ctx: ctx})
if err == nil && (img.format == "jpeg" || img.profile != nil) {
var profile *pdfgo.ColorSpace
if img.profile != nil {
@@ -184,7 +184,7 @@ func newEncodedImage(data []byte) (*EncodedImage, error) {
// 入参: ctx 取消上下文, data 只读图片编码
// 返回: *EncodedImage 惰性图片, error 头信息、格式或取消错误
func newEncodedImageContext(ctx context.Context, data []byte) (*EncodedImage, error) {
config, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx})
config, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx})
if cancelErr := ctx.Err(); cancelErr != nil {
return nil, cancelErr
}
+2 -2
View File
@@ -170,7 +170,7 @@ func DecodeContext(ctx context.Context, data []byte) (img image.Image, format st
img, err := DecodeTIFF(ctx, data, layout)
return img, "tiff", err
}
img, format, err = image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx})
img, format, err = image.Decode(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx})
if err == nil {
return img, NormalizeFormat(format), nil
}
@@ -267,7 +267,7 @@ func DecodeConfigDataContext(ctx context.Context, data []byte) (config image.Con
cfg.Width, cfg.Height = size.X, size.Y
return cfg, "tiff", nil
}
cfg, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), context: ctx})
cfg, format, err := image.DecodeConfig(contextInput{ReadCloser: io.NopCloser(bytes.NewReader(data)), ctx: ctx})
if err == nil {
return cfg, NormalizeFormat(format), nil
}
+2 -2
View File
@@ -22,14 +22,14 @@ import (
// contextInput 在读取图像编码时检查调用方取消状态
type contextInput struct {
io.ReadCloser
context context.Context
ctx context.Context
}
// Read 检查取消状态后读取原始数据
// 入参: data 数据缓冲区
// 返回: int 读取字节数, error 读取或取消错误
func (r contextInput) Read(data []byte) (int, error) {
if err := r.context.Err(); err != nil {
if err := r.ctx.Err(); err != nil {
return 0, err
}
return r.ReadCloser.Read(data)
+1 -1
View File
@@ -165,7 +165,7 @@ func readTIFFDirectory(ctx context.Context, input io.Reader, order binary.ByteOr
if offset < position {
return result, position, ErrTIFFRandomAccess
}
reader := contextInput{ReadCloser: io.NopCloser(input), context: ctx}
reader := contextInput{ReadCloser: io.NopCloser(input), ctx: ctx}
if _, err := io.CopyN(io.Discard, reader, offset-position); err != nil {
return result, position, err
}
+3 -3
View File
@@ -211,11 +211,11 @@ func (s *u3dLineState) accumulateCorners(mesh *U3DMesh, shadingIndex uint32, cor
}
// value 按指定上下文和位宽读取点集或线集字段
// 入参: context 压缩上下文, small 是否为8位字段
// 入参: ctx 压缩上下文, small 是否为8位字段
// 返回: uint32 字段值
func (r *u3dLineReader) value(context int, small bool) uint32 {
func (r *u3dLineReader) value(ctx int, small bool) uint32 {
if r.bits != nil {
return r.bits.dynamicValue(&r.hist[context], small)
return r.bits.dynamicValue(&r.hist[ctx], small)
}
if small {
return uint32(r.values.u8())
+4 -4
View File
@@ -167,15 +167,15 @@ func (r *u3dResourceReader) motionFrame(frame *U3DKeyFrame, previous U3DKeyFrame
}
// motionDifference 用共享幅值上下文恢复一至三个差分分量
// 入参: previous 前一帧分量, scale 逆量化系数, context 符号上下文,下一项为幅值上下文
// 入参: previous 前一帧分量, scale 逆量化系数, ctx 符号上下文,下一项为幅值上下文
// 返回: [3]float32 重建分量, error 符号、数值或读取错误
func (r *u3dResourceReader) motionDifference(previous []float32, scale float32, context int) (values [3]float32, err error) {
signs := r.value(context, true)
func (r *u3dResourceReader) motionDifference(previous []float32, scale float32, ctx int) (values [3]float32, err error) {
signs := r.value(ctx, true)
if signs >= 1<<len(previous) {
return values, fmt.Errorf("invalid U3D motion difference signs")
}
for index, prior := range previous {
difference := float64(r.value(context+1, false)) * float64(scale)
difference := float64(r.value(ctx+1, false)) * float64(scale)
if signs&(1<<index) != 0 {
difference = -difference
}
+3 -3
View File
@@ -30,9 +30,9 @@ type u3dResourceReader struct {
}
// value 读取指定上下文的压缩字段,首次使用时初始化算术区间
// 入参: context 压缩上下文, small 是否为8位字段
// 入参: ctx 压缩上下文, small 是否为8位字段
// 返回: uint32 字段值
func (r *u3dResourceReader) value(context int, small bool) uint32 {
func (r *u3dResourceReader) value(ctx int, small bool) uint32 {
if r.err() != nil {
return 0
}
@@ -52,7 +52,7 @@ func (r *u3dResourceReader) value(context int, small bool) uint32 {
return nil
}
}
return r.bits.dynamicValue(&r.hist[context], small)
return r.bits.dynamicValue(&r.hist[ctx], small)
}
// u32 读取普通32位字段,压缩开始后沿用当前算术区间
+80 -18
View File
@@ -57,6 +57,7 @@ type FontInfo = ofdgo.FontInfo
type Session struct {
Reader *ofdgo.Reader
Renderer *ofdgo.Renderer
input *ofdgo.Input
fontFS *ofdgo.FontFS
doc *ofdgo.Document
pageCache map[int]*ofdgo.PageContent
@@ -424,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
}
@@ -435,11 +436,22 @@ func Open(data []byte, opts OpenOptions) (*Session, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty ofd data")
}
reader, err := ofdgo.NewReader(bytes.NewReader(data), int64(len(data)), opts.ReaderOptions...)
input, err := ofdgo.NewInput(bytes.NewReader(data), int64(len(data)))
if err != nil {
return nil, err
}
return newSession(reader, opts)
reader, err := ofdgo.NewReader(input, opts.ReaderOptions...)
if err != nil {
input.Close()
return nil, err
}
session, err := newSession(reader, opts)
if err != nil {
input.Close()
return nil, err
}
session.input = input
return session, nil
}
// Close 关闭文档会话
@@ -449,11 +461,16 @@ func (s *Session) Close() error {
return nil
}
reader := s.Reader
input := s.input
*s = Session{}
var err error
if reader != nil {
return reader.Close()
err = reader.Close()
}
return nil
if input != nil {
err = errors.Join(err, input.Close())
}
return err
}
// Document 创建指定文档的会话,保留编辑内容和字体配置,不复用页面缓存
@@ -1318,8 +1335,13 @@ func (e *businessEngine) detectDocumentFormat(args []platformValue) (any, error)
if len(args) != 3 {
return nil, fmt.Errorf("missing detection arguments")
}
source := &browserReaderAt{read: args[0], size: int64(args[1].Float()), checkpoint: args[2]}
return ofdgo.DetectFormat(source, source.size)
reader := &browserReaderAt{read: args[0], size: int64(args[1].Float()), checkpoint: args[2]}
input, err := ofdgo.NewInput(reader, reader.size)
if err != nil {
return nil, err
}
defer input.Close()
return ofdgo.DetectFormat(input)
}
// convertPDFDocument 通过库转换PDF,不替换当前文档会话
@@ -1329,10 +1351,11 @@ func (e *businessEngine) convertPDFDocument(args []platformValue) (any, error) {
if len(args) != 6 {
return nil, fmt.Errorf("missing PDF data")
}
source, size, err := e.sourceFromValue(args[0])
input, err := e.inputFromValue(args[0])
if err != nil {
return nil, err
}
defer input.Close()
fonts, err := e.fontsFromJS(args[1])
if err != nil {
return nil, err
@@ -1353,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, size, options)
editor, report, err := ofdgo.ImportPDF(e.operationContext(), input, options)
if err != nil {
return nil, err
}
@@ -1454,8 +1477,13 @@ func (e *businessEngine) convertFile(args []platformValue) (any, error) {
} else if err := awaitExport(args[5], "open", "document."+format.Extension); err != nil {
return nil, err
}
source := &browserReaderAt{read: args[0], checkpoint: args[6], size: int64(args[1].Float())}
report, err := ofdgo.Convert(e.newOutputContext(args[6]), source, source.size, writer, options)
reader := &browserReaderAt{read: args[0], checkpoint: args[6], size: int64(args[1].Float())}
input, err := ofdgo.NewInput(reader, reader.size)
if err != nil {
return nil, err
}
defer input.Close()
report, err := ofdgo.Convert(e.newOutputContext(args[6]), input, writer, options)
if err != nil {
return nil, err
}
@@ -1483,16 +1511,29 @@ func (e *businessEngine) packFiles(args []platformValue) (any, error) {
writer := bufio.NewWriterSize(exportWriter{write: args[2]}, 1<<20)
files := args[0]
entries := make([]ofdgo.ArchiveFile, files.Length())
inputs := make([]*browserReaderAt, files.Length())
defer func() {
for _, entry := range entries {
if entry.Input != nil {
entry.Input.Close()
}
}
}()
for i := 0; i < files.Length(); i++ {
file := files.Index(i)
name := file.Get("name").String()
read := args[1].Invoke(i)
source := &browserReaderAt{read: read, checkpoint: args[4], size: int64(file.Get("size").Float())}
entries[i] = ofdgo.ArchiveFile{Name: name, Source: source, Size: source.size}
reader := &browserReaderAt{read: read, checkpoint: args[4], size: int64(file.Get("size").Float())}
input, err := ofdgo.NewInput(reader, reader.size)
if err != nil {
return nil, err
}
inputs[i] = reader
entries[i] = ofdgo.ArchiveFile{Name: name, Input: input}
}
options := ofdgo.ArchiveOptions{OnProgress: func(completed, total int) error {
if completed > 0 {
entries[completed-1].Source.(*browserReaderAt).data = nil
inputs[completed-1].data = nil
}
return awaitExport(args[3], completed, total)
}}
@@ -1517,10 +1558,16 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) {
if len(args) < 3 {
return nil, fmt.Errorf("missing open arguments")
}
source, size, err := e.sourceFromValue(args[0])
input, err := e.inputFromValue(args[0])
if err != nil {
return nil, err
}
keep := false
defer func() {
if !keep {
input.Close()
}
}()
fonts, err := e.fontsFromJS(args[1])
if err != nil {
return nil, err
@@ -1535,7 +1582,7 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) {
defer clear(credentials.Password)
options.ReaderOptions = append(options.ReaderOptions, ofdgo.WithCredentials(credentials))
}
reader, err := ofdgo.NewReader(source, size, options.ReaderOptions...)
reader, err := ofdgo.NewReader(input, options.ReaderOptions...)
if err != nil {
return nil, err
}
@@ -1552,6 +1599,8 @@ func (e *businessEngine) openDocument(args []platformValue) (any, error) {
e.currentSession = nil
}
e.currentSession = session
session.input = input
keep = true
return e.currentSession.Summary(), nil
}
@@ -5456,10 +5505,16 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) {
if args[0].IsNull() {
return nil, nil
}
source, size, err := e.sourceFromValue(args[0])
input, err := e.inputFromValue(args[0])
if err != nil {
return nil, err
}
keep := false
defer func() {
if !keep {
input.Close()
}
}()
var options []ofdgo.ReaderOption
if len(args) > 1 && !args[1].IsNull() && !args[1].IsUndefined() {
credentials, err := e.credentialsFromJS(args[1])
@@ -5469,10 +5524,11 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) {
defer clear(credentials.Password)
options = append(options, ofdgo.WithCredentials(credentials))
}
reader, err := ofdgo.NewReader(source, size, options...)
reader, err := ofdgo.NewReader(input, options...)
if err != nil {
return nil, err
}
reader.Closer = input
doc, err := reader.Doc()
if err != nil {
reader.Close()
@@ -5483,6 +5539,7 @@ func (e *businessEngine) loadImport(args []platformValue) (any, error) {
return nil, fmt.Errorf("document has no pages")
}
e.pendingImport = reader
keep = true
entries := make([]DocumentEntry, reader.DocumentCount())
signed := doc.Signatures != ""
for index, body := range reader.OFD.DocBody {
@@ -6123,6 +6180,7 @@ func (e *businessEngine) selectDocumentVersion(args []platformValue) (any, error
if err != nil {
return nil, err
}
session.input, e.currentSession.input = e.currentSession.input, nil
_ = e.currentSession.Close()
e.currentSession = session
e.clearImport()
@@ -6280,6 +6338,7 @@ func (e *businessEngine) selectDocument(args []platformValue) (any, error) {
if err != nil {
return nil, err
}
session.input, e.currentSession.input = e.currentSession.input, nil
_ = e.currentSession.Close()
e.currentSession, e.currentEditor = session, session.editor
if e.pendingImport != nil {
@@ -6380,6 +6439,9 @@ func (e *businessEngine) previewEditor(editor *ofdgo.Editor, annotations bool) (
session.Renderer.SetFontFS(session.fontFS)
editor.SetFontFS(session.fontFS)
}
if editor == e.currentEditor {
session.input, e.currentSession.input = e.currentSession.input, nil
}
_ = e.currentSession.Close()
}
editor.SetRenderBackends(session.Renderer.Backends())
+25 -20
View File
@@ -89,7 +89,7 @@ type NativeEngine struct {
// nativeReader 在会话当前操作上下文中分块读取,不捕获已结束请求的上下文
type nativeReader struct {
engine *NativeEngine
source io.ReaderAt
input io.ReaderAt
}
// nativeCall 保存单次worker适配状态
@@ -139,7 +139,7 @@ func NewNativeEngine(resolve NativeResolver) *NativeEngine { return &NativeEngin
func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit func(any)) (result map[string]any) {
e.once.Do(func() {
e.gate = make(chan struct{}, 1)
e.resolveSource = e.source
e.resolveInput = e.input
e.conversionPool = e.ConversionPool
})
if ctx == nil {
@@ -236,7 +236,7 @@ func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit f
// Close 释放会话,不关闭宿主持有的ReaderAt资源
func (e *NativeEngine) Close() error {
e.once.Do(func() { e.gate = make(chan struct{}, 1); e.resolveSource = e.source })
e.once.Do(func() { e.gate = make(chan struct{}, 1); e.resolveInput = e.input })
e.gate <- struct{}{}
defer func() { <-e.gate }()
e.closed = true
@@ -253,8 +253,8 @@ func (e *NativeEngine) Close() error {
return err
}
// source 读取授权源并以宿主报告的大小为准
func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int64, error) {
// input 读取授权源并以宿主报告的大小为准
func (e *NativeEngine) input(ctx context.Context, value any) (io.ReaderAt, int64, error) {
if err := ctx.Err(); err != nil {
return nil, 0, err
}
@@ -262,7 +262,7 @@ func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int6
value = p.value
}
if data, ok := value.([]byte); ok {
return nativeReader{engine: e, source: bytes.NewReader(data)}, int64(len(data)), nil
return nativeReader{engine: e, input: bytes.NewReader(data)}, int64(len(data)), nil
}
descriptor, ok := value.(map[string]any)
if !ok {
@@ -272,14 +272,14 @@ func (e *NativeEngine) source(ctx context.Context, value any) (io.ReaderAt, int6
if !ok || token == "" || e.Resolve == nil {
return nil, 0, fmt.Errorf("unauthorized native file")
}
source, size, err := e.Resolve(token)
input, size, err := e.Resolve(token)
if err != nil {
return nil, 0, err
}
if source == nil || size < 0 {
return nil, 0, fmt.Errorf("invalid native file source")
if input == nil || size < 0 {
return nil, 0, fmt.Errorf("invalid native file input")
}
return nativeReader{engine: e, source: source}, size, nil
return nativeReader{engine: e, input: input}, size, nil
}
// ReadAt 分块传递取消检查,宿主ReadAt自身阻塞时由宿主控制超时
@@ -290,7 +290,7 @@ func (r nativeReader) ReadAt(data []byte, offset int64) (int, error) {
return total, err
}
n := min(len(data), 256<<10)
got, err := r.source.ReadAt(data[:n], offset)
got, err := r.input.ReadAt(data[:n], offset)
total += got
if err != nil {
return total, err
@@ -307,7 +307,7 @@ func (r nativeReader) ReadAt(data []byte, offset int64) (int, error) {
// 入参: ctx 调用上下文, value 资源参数
// 返回: []byte 资源数据, error 读取错误
func (e *NativeEngine) readBytes(ctx context.Context, value any) ([]byte, error) {
source, size, err := e.source(ctx, value)
input, size, err := e.input(ctx, value)
if err != nil {
return nil, err
}
@@ -320,7 +320,7 @@ func (e *NativeEngine) readBytes(ctx context.Context, value any) ([]byte, error)
return nil, err
}
n := int(min(int64(256<<10), size-offset))
got, err := source.ReadAt(output[int(offset):int(offset)+n], offset)
got, err := input.ReadAt(output[int(offset):int(offset)+n], offset)
if got != n {
return nil, io.ErrUnexpectedEOF
}
@@ -540,7 +540,7 @@ func (c *nativeCall) progress(kind, stage string, phased bool) nativeCallback {
// readCallback 创建有界随机读取回调
func (c *nativeCall) readCallback(value any) (nativeCallback, int64, error) {
source, size, err := c.engine.source(c.ctx, value)
input, size, err := c.engine.input(c.ctx, value)
if err != nil {
return nil, 0, err
}
@@ -553,7 +553,7 @@ func (c *nativeCall) readCallback(value any) (nativeCallback, int64, error) {
return nil, fmt.Errorf("invalid native read range")
}
data := make([]byte, n)
got, err := source.ReadAt(data, offset)
got, err := input.ReadAt(data, offset)
if got != n {
return nil, io.ErrUnexpectedEOF
}
@@ -876,9 +876,13 @@ func nativeOutputBase(value platformValue) string {
return base
}
// sourceFromValue 读取宿主授权的随机读取源
func (e *businessEngine) sourceFromValue(value platformValue) (io.ReaderAt, int64, error) {
return e.resolveSource(e.operationContext(), value.value)
// inputFromValue 读取宿主授权的随机读取源
func (e *businessEngine) inputFromValue(value platformValue) (*ofdgo.Input, error) {
reader, size, err := e.resolveInput(e.operationContext(), value.value)
if err != nil {
return nil, err
}
return ofdgo.NewInput(reader, size)
}
// platformValueOf 包装参数
@@ -1024,11 +1028,12 @@ func (e *businessEngine) bytesFromJS(v platformValue) ([]byte, error) {
if data, ok := v.value.([]byte); ok {
return append([]byte(nil), data...), nil
}
source, size, err := e.sourceFromValue(v)
input, err := e.inputFromValue(v)
if err != nil {
return nil, err
}
return io.ReadAll(io.NewSectionReader(source, 0, size))
defer input.Close()
return io.ReadAll(io.NewSectionReader(input, 0, input.Size()))
}
// successResult 保留二进制返回结构
+8 -4
View File
@@ -21,8 +21,9 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"syscall/js"
"github.com/xiaoqidun/ofdgo"
)
const (
@@ -220,8 +221,11 @@ func platformParseJSON(data []byte) map[string]any {
return result
}
// sourceFromValue 读取浏览器二进制源
func (e *businessEngine) sourceFromValue(value platformValue) (io.ReaderAt, int64, error) {
// inputFromValue 读取浏览器二进制源
func (e *businessEngine) inputFromValue(value platformValue) (*ofdgo.Input, error) {
data, err := e.bytesFromJS(value)
return bytes.NewReader(data), int64(len(data)), err
if err != nil {
return nil, err
}
return ofdgo.NewInput(bytes.NewReader(data), int64(len(data)))
}
+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)
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)