diff --git a/assets/webui/index.html b/assets/webui/index.html
index 6ef4fbb..5d0100f 100644
--- a/assets/webui/index.html
+++ b/assets/webui/index.html
@@ -42,7 +42,7 @@
-
+
-
+
diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js
index 4972b4d..5462901 100644
--- a/assets/webui/ofdgo.js
+++ b/assets/webui/ofdgo.js
@@ -94,7 +94,7 @@ const el = {
continuousButton: document.querySelector("#continuousButton"),
annotationButton: document.querySelector("#annotationButton"),
imageDPI: document.querySelector("#imageDPI"),
- pageExportFormat: document.querySelector("#pageExportFormat"),
+ exportFormat: document.querySelector("#exportFormat"),
exportPageButton: document.querySelector("#exportPageButton"),
exportButton: document.querySelector("#exportButton"),
emptyState: document.querySelector("#emptyState"),
@@ -199,9 +199,9 @@ document.addEventListener("selectionchange", () => {
unmountPage(index);
}
});
-el.pageExportFormat.addEventListener("change", () => updateDPIControl());
-el.exportPageButton.addEventListener("click", exportCurrentPage);
-el.exportButton.addEventListener("click", exportPDF);
+el.exportFormat.addEventListener("change", () => updateDPIControl());
+el.exportPageButton.addEventListener("click", () => exportFile(false));
+el.exportButton.addEventListener("click", () => exportFile(true));
el.refreshAppButton.addEventListener("click", refreshApplication);
el.pageInput.addEventListener("change", () => {
const page = Number.parseInt(el.pageInput.value, 10);
@@ -593,15 +593,15 @@ async function refreshApplication() {
async function loadExportFormats() {
const formats = await callWASM("ofdgoExportFormats") || [];
state.exportFormats = formats;
- el.pageExportFormat.replaceChildren();
+ el.exportFormat.replaceChildren();
for (const format of formats) {
const option = document.createElement("option");
option.value = format.value;
option.textContent = format.label;
- el.pageExportFormat.append(option);
+ el.exportFormat.append(option);
}
if (formats.length) {
- el.pageExportFormat.value = formats[0].value;
+ el.exportFormat.value = formats[0].value;
}
updateDPIControl();
}
@@ -1487,61 +1487,32 @@ async function downloadAttachment(attachment) {
}
}
-async function exportPDF() {
+async function exportFile(whole) {
if (!state.doc || document.body.hasAttribute("aria-busy")) {
return;
}
- const openSeq = state.openSeq;
- state.exporting = true;
- updateControls();
- setBusy(true, "正在生成 PDF", 45, STATUS.exporting);
- try {
- const result = await callWASM("ofdgoExportPDF");
- if (openSeq !== state.openSeq) {
- return;
- }
- setProgress("正在保存 PDF", 86);
- const bytes = result.bytes;
- downloadBytes(bytes, "application/pdf", pdfFileName());
- setStatus(`PDF 导出完成 ${formatBytes(result.size || bytes.length)}`);
- } catch (err) {
- if (openSeq === state.openSeq) {
- showError(err, false);
- }
- } finally {
- state.exporting = false;
- if (openSeq === state.openSeq) {
- setBusy(false);
- updateControls();
- }
- }
-}
-
-async function exportCurrentPage() {
- if (!state.doc || document.body.hasAttribute("aria-busy")) {
- return;
- }
- const format = el.pageExportFormat.value;
+ const format = exportFormatInfo(el.exportFormat.value);
if (!format) {
return;
}
- const info = exportFormatInfo(format);
- const label = info?.label || String(format || "").toUpperCase();
+ const label = whole ? (format.value === "pdf" ? "PDF" : "ZIP") : format.label;
const openSeq = state.openSeq;
- const fileName = pageFileName(info?.extension || format);
+ const fileName = whole ? `${baseFileName()}.${label.toLowerCase()}` : pageFileName(format.extension);
state.exporting = true;
updateControls();
- setBusy(true, `正在生成 ${label}`, 45, STATUS.pageExporting);
+ setBusy(true, `正在生成 ${label}`, null, whole ? STATUS.exporting : STATUS.pageExporting);
try {
- const dpi = exportFormatUsesDPI(format) ? currentImageDPI() : 0;
- const result = await callWASM("ofdgoExportPage", state.pageIndex, format, dpi);
+ const dpi = exportFormatUsesDPI(format.value) ? currentImageDPI() : 0;
+ const result = whole
+ ? await callWASM("ofdgoExportDocument", format.value, dpi)
+ : await callWASM("ofdgoExportPage", state.pageIndex, format.value, dpi);
if (openSeq !== state.openSeq) {
return;
}
- setProgress(`正在保存 ${result.label || label}`, 86);
+ setProgress(`正在保存 ${result.label}`, 100);
const bytes = result.bytes;
- downloadBytes(bytes, result.mime || info?.mime || "application/octet-stream", fileName);
- setStatus(`${result.label || label} 导出完成 ${formatBytes(result.size || bytes.length, result.label || label)}`);
+ downloadBytes(bytes, result.mime, fileName);
+ setStatus(`${result.label} 导出完成 ${formatBytes(bytes.length, result.label)}`);
} catch (err) {
if (openSeq === state.openSeq) {
showError(err, false);
@@ -3224,9 +3195,9 @@ function updateControls() {
el.fitButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "width");
el.fitHeightButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "height");
updateAnnotationButton();
- el.pageExportFormat.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
+ el.exportFormat.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
el.exportPageButton.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
- el.exportButton.disabled = !hasDoc || state.exporting;
+ el.exportButton.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
updateDPIControl();
}
@@ -3329,13 +3300,9 @@ function baseFileName() {
return base.replace(/[\\/:*?"<>|]+/g, "_").trim() || "ofdgo";
}
-function pdfFileName() {
- return `${baseFileName()}.pdf`;
-}
-
function pageFileName(extension) {
const page = String(state.pageIndex + 1).padStart(String(state.doc?.pageCount || 1).length, "0");
- return `${baseFileName()}_p${page}.${extension || "bin"}`;
+ return `${baseFileName()}_p${page}.${extension}`;
}
function exportFormatInfo(value) {
@@ -3347,7 +3314,7 @@ function exportFormatUsesDPI(value) {
}
function updateDPIControl() {
- el.imageDPI.disabled = state.exporting || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value);
+ el.imageDPI.disabled = state.exporting || !state.doc || !exportFormatUsesDPI(el.exportFormat.value);
}
function currentImageDPI() {
diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go
index af5f4b7..34095ba 100644
--- a/internal/webui/wasm.go
+++ b/internal/webui/wasm.go
@@ -43,7 +43,7 @@ func RunWASM() {
registerCallback("ofdgoSearchPage", searchPage)
registerCallback("ofdgoExportFormats", exportFormats)
registerCallback("ofdgoExportPage", exportPage)
- registerCallback("ofdgoExportPDF", exportPDF)
+ registerCallback("ofdgoExportDocument", exportDocument)
registerCallback("ofdgoMatchFontFiles", matchFontFiles)
select {}
}
@@ -246,29 +246,27 @@ func exportPage(args []js.Value) (any, error) {
return nil, err
}
return successResult(map[string]any{
- "bytes": bytesToJS(data),
- "size": len(data),
- "format": format.Value,
- "label": format.Label,
- "extension": format.Extension,
- "mime": format.MIME,
+ "bytes": bytesToJS(data),
+ "label": format.Label,
+ "mime": format.MIME,
}), nil
}
-// exportPDF 导出OFD文档为PDF
+// exportDocument 导出OFD文档
// 入参: args 浏览器参数
-// 返回: any PDF结果, error 错误信息
-func exportPDF(args []js.Value) (any, error) {
+// 返回: any 导出结果, error 错误信息
+func exportDocument(args []js.Value) (any, error) {
if currentSession == nil {
return nil, fmt.Errorf("ofd document is not opened")
}
- data, err := currentSession.ExportPDF()
+ data, format, err := currentSession.ExportDocument(args[0].String(), args[1].Float())
if err != nil {
return nil, err
}
return successResult(map[string]any{
"bytes": bytesToJS(data),
- "size": len(data),
+ "label": format.Label,
+ "mime": format.MIME,
}), nil
}
diff --git a/internal/webui/webui.go b/internal/webui/webui.go
index e99c110..679897f 100644
--- a/internal/webui/webui.go
+++ b/internal/webui/webui.go
@@ -15,10 +15,12 @@
package webui
import (
+ "archive/zip"
"bytes"
"fmt"
"image/jpeg"
"path"
+ "strconv"
"strings"
"time"
@@ -629,6 +631,54 @@ func signatureStampInfos(positions []ofdgo.SignatureStampPosition) []SignatureSt
return infos
}
+// ExportDocument 导出文档为PDF或逐页打包ZIP
+// 入参: value 导出格式, dpi 图片DPI
+// 返回: []byte 文件数据, ExportFormat 导出格式, error 错误信息
+func (s *Session) ExportDocument(value string, dpi float64) ([]byte, ExportFormat, error) {
+ format, ok := exportFormat(value)
+ if !ok {
+ return nil, ExportFormat{}, fmt.Errorf("unsupported export format %s", value)
+ }
+ if format.Value == "pdf" {
+ data, err := s.ExportPDF()
+ return data, format, err
+ }
+ if s == nil || s.Reader == nil || s.Renderer == nil || s.doc == nil {
+ return nil, ExportFormat{}, fmt.Errorf("ofd document is not opened")
+ }
+ count := len(s.doc.Pages.Page)
+ if count == 0 {
+ return nil, ExportFormat{}, fmt.Errorf("no pages found")
+ }
+ var buf bytes.Buffer
+ archive := zip.NewWriter(&buf)
+ method := zip.Deflate
+ if format.Value == "png" || format.Value == "jpg" {
+ method = zip.Store
+ }
+ width := len(strconv.Itoa(count))
+ for index := range count {
+ data, _, err := s.ExportPage(index, format.Value, dpi)
+ if err != nil {
+ return nil, ExportFormat{}, fmt.Errorf("failed to export page %d: %w", index+1, err)
+ }
+ entry, err := archive.CreateHeader(&zip.FileHeader{
+ Name: fmt.Sprintf("%0*d.%s", width, index+1, format.Extension),
+ Method: method,
+ })
+ if err != nil {
+ return nil, ExportFormat{}, err
+ }
+ if _, err := entry.Write(data); err != nil {
+ return nil, ExportFormat{}, err
+ }
+ }
+ if err := archive.Close(); err != nil {
+ return nil, ExportFormat{}, err
+ }
+ return buf.Bytes(), ExportFormat{Value: "zip", Label: "ZIP", Extension: "zip", MIME: "application/zip"}, nil
+}
+
// ExportPDF 导出文档为PDF
// 返回: []byte PDF文件数据, error 错误信息
func (s *Session) ExportPDF() ([]byte, error) {