mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
1 parent
a29fbbd31b
commit
ca8f22dba1
4 files changed
+85
-70
No files matched your search
@@ -42,7 +42,7 @@
|
||||
<button id="annotationButton" class="button" type="button" data-short="注" title="关闭注解" aria-label="关闭注解" aria-pressed="true">注解</button>
|
||||
</div>
|
||||
<div class="tool-group">
|
||||
<select id="pageExportFormat" class="format-select" title="单页格式" aria-label="单页格式" disabled></select>
|
||||
<select id="exportFormat" class="format-select" title="导出格式" aria-label="导出格式" disabled></select>
|
||||
<select id="imageDPI" class="format-select dpi-select" title="图片 DPI" aria-label="图片 DPI" disabled>
|
||||
<option value="96">96</option>
|
||||
<option value="150">150</option>
|
||||
@@ -50,7 +50,7 @@
|
||||
<option value="600">600</option>
|
||||
</select>
|
||||
<button id="exportPageButton" class="button" type="button" data-short="单" title="导出单页" aria-label="导出单页" disabled>单页</button>
|
||||
<button id="exportButton" class="button" type="button" data-short="全" title="导出文档 PDF" aria-label="导出文档 PDF" disabled>文档</button>
|
||||
<button id="exportButton" class="button" type="button" data-short="全" title="导出文档" aria-label="导出文档" disabled>文档</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
+23
-56
@@ -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() {
|
||||
|
||||
+10
-12
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user