From 92585edd1e2174eaa8ee352799f80352fb68224e Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Mon, 14 Sep 2026 13:16:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=96=87=E6=A1=A3=E5=AF=BC=E5=87=BA):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E6=A1=A3=E5=AF=BC=E5=87=BA=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo.css | 1 + assets/webui/ofdgo.js | 13 +++++++++++-- assets/webui/ofdgo.wasm.js | 6 ++++++ internal/webui/wasm.go | 5 +++++ ofdgo_renderer.go | 2 ++ ofdgo_renderer_output.go | 27 +++++++++++++++++++++------ 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/assets/webui/ofdgo.css b/assets/webui/ofdgo.css index cc97363..fd4c449 100644 --- a/assets/webui/ofdgo.css +++ b/assets/webui/ofdgo.css @@ -839,6 +839,7 @@ button.outline-link:hover { overflow: hidden; color: #1f2428; font-size: 13px; + font-variant-numeric: tabular-nums; text-overflow: ellipsis; white-space: nowrap; } diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 0a5532a..48cbb31 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -670,6 +670,16 @@ async function loadWASM() { } if (data.type === "progress") { setProgress(data.text, data.percent); + } else if (data.type === "export") { + if (wasmRequests.get(data.id)?.openSeq === state.openSeq && state.exporting) { + if (data.stage === "save") { + setProgress("正在保存", null); + } else if (data.completed === data.total) { + setProgress("正在封装", null); + } else { + setProgress(`正在导出 ${data.completed} / ${data.total} 页`, data.completed / data.total * 100); + } + } } else if (data.type === "ready") { state.ready = true; resolve(); @@ -1589,7 +1599,6 @@ async function exportFile(whole, indices = null) { if (openSeq !== state.openSeq) { return; } - setProgress(`正在保存 ${result.label}`, 100); if (result.blob) { downloadBytes(result.blob, result.mime, fileName); } @@ -3302,7 +3311,7 @@ async function callWASM(name, ...args) { } return new Promise((resolve, reject) => { const id = ++wasmRequestID; - wasmRequests.set(id, { resolve, reject }); + wasmRequests.set(id, { resolve, reject, openSeq: state.openSeq }); try { wasmWorker.postMessage({ id, name, args }); } catch (err) { diff --git a/assets/webui/ofdgo.wasm.js b/assets/webui/ofdgo.wasm.js index c256829..d47a4ed 100644 --- a/assets/webui/ofdgo.wasm.js +++ b/assets/webui/ofdgo.wasm.js @@ -24,11 +24,17 @@ async function handleMessage({ id, name, args }) { setTimeout(done, 0); } }); + if (name === "ofdgoExportDocument") { + args.push((completed, total) => { + self.postMessage({ id, type: "export", stage: "pages", completed, total }); + }); + } } const payload = await globalThis[name](...args); const result = typeof payload === "string" ? JSON.parse(payload) : payload; if (exporting && result.ok) { result.data.size = size; + self.postMessage({ id, type: "export", stage: "save" }); if (output) { await output.close(); } else { diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go index 15e32a8..9e191d8 100644 --- a/internal/webui/wasm.go +++ b/internal/webui/wasm.go @@ -335,6 +335,11 @@ func exportDocument(args []js.Value) (any, error) { if currentSession == nil { return nil, fmt.Errorf("ofd document is not opened") } + renderer := currentSession.Renderer + renderer.OnExportProgress = func(completed, total int) { + args[4].Invoke(completed, total) + } + defer func() { renderer.OnExportProgress = nil }() var indices []int if !args[2].IsNull() { indices = make([]int, args[2].Length()) diff --git a/ofdgo_renderer.go b/ofdgo_renderer.go index e661e1f..abeb778 100644 --- a/ofdgo_renderer.go +++ b/ofdgo_renderer.go @@ -24,11 +24,13 @@ import ( // Renderer 渲染器实现 // 通过NewRenderer创建,实例及共享的Reader需串行使用 // OnPageText可选,接收页面绘制时同步提取的文字,不包含图案和签名外观 +// OnExportProgress可选,同步回报文档导出的已处理页数及总页数,不代表封装或写入成功 type Renderer struct { Reader *Reader DPI float64 RenderAnnotations bool OnPageText func(*PageContent, *PageText) + OnExportProgress func(completed, total int) DrawParams map[string]*DrawParam CompositeGraphicUnits map[string]*CompositeGraphicUnit fontFamily *canvas.FontFamily diff --git a/ofdgo_renderer_output.go b/ofdgo_renderer_output.go index 57ac2cd..71f8451 100644 --- a/ofdgo_renderer_output.go +++ b/ofdgo_renderer_output.go @@ -90,7 +90,10 @@ func (r *Renderer) RenderToZIP(writer io.Writer, format string, indices ...int) method = zip.Store } width := len(strconv.Itoa(count)) - for _, index := range indices { + if r.OnExportProgress != nil { + r.OnExportProgress(0, len(indices)) + } + for i, index := range indices { page, err := r.Reader.PageContentByIndex(index) if err != nil { return fmt.Errorf("failed to read page %d: %w", index+1, err) @@ -105,6 +108,9 @@ func (r *Renderer) RenderToZIP(writer io.Writer, format string, indices ...int) if err := render(page, entry); err != nil { return fmt.Errorf("failed to export page %d: %w", index+1, err) } + if r.OnExportProgress != nil { + r.OnExportProgress(i+1, len(indices)) + } } return archive.Close() } @@ -248,7 +254,7 @@ func (r *Renderer) RenderToPDF(page *PageContent, writer io.Writer) error { return err } pages := []pdfPage{{Content: page, Box: box}} - return r.renderPDFPages(pages, writer) + return r.renderPDFPages(pages, writer, nil) } // RenderToEPS 渲染为EPS @@ -282,6 +288,9 @@ func (r *Renderer) RenderToMultiPagePDF(writer io.Writer, indices ...int) error if err != nil { return err } + if r.OnExportProgress != nil { + r.OnExportProgress(0, len(indices)) + } pages := make([]pdfPage, len(indices)) for i, index := range indices { page, err := r.Reader.PageContent(doc.Pages.Page[index]) @@ -294,7 +303,7 @@ func (r *Renderer) RenderToMultiPagePDF(writer io.Writer, indices ...int) error } pages[i] = pdfPage{Content: page, Box: box} } - return r.renderPDFPages(pages, writer) + return r.renderPDFPages(pages, writer, r.OnExportProgress) } // RenderPagesToPDF 按指定顺序导出已解析的页面,复用调用方的页面缓存 @@ -304,6 +313,9 @@ func (r *Renderer) RenderPagesToPDF(contents []*PageContent, writer io.Writer) e if len(contents) == 0 { return fmt.Errorf("no pages found") } + if r.OnExportProgress != nil { + r.OnExportProgress(0, len(contents)) + } pages := make([]pdfPage, len(contents)) for i, page := range contents { box, err := r.GetPageBox(page) @@ -312,13 +324,13 @@ func (r *Renderer) RenderPagesToPDF(contents []*PageContent, writer io.Writer) e } pages[i] = pdfPage{Content: page, Box: box} } - return r.renderPDFPages(pages, writer) + return r.renderPDFPages(pages, writer, r.OnExportProgress) } // renderPDFPages 渲染页面并复用调用方的字节缓冲 -// 入参: pages 页面列表, writer 输出流 +// 入参: pages 页面列表, writer 输出流, progress 页面处理进度 // 返回: error 错误信息 -func (r *Renderer) renderPDFPages(pages []pdfPage, writer io.Writer) error { +func (r *Renderer) renderPDFPages(pages []pdfPage, writer io.Writer, progress func(int, int)) error { navigation := newPDFNavigation(r, r.Reader.doc, pages) buf, direct := writer.(*bytes.Buffer) if !direct { @@ -337,6 +349,9 @@ func (r *Renderer) renderPDFPages(pages []pdfPage, writer io.Writer) error { buf.Truncate(start) return fmt.Errorf("failed to render page %d: %w", i+1, err) } + if progress != nil { + progress(i+1, len(pages)) + } } if err := p.Close(); err != nil { buf.Truncate(start)