mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +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>
|
<button id="annotationButton" class="button" type="button" data-short="注" title="关闭注解" aria-label="关闭注解" aria-pressed="true">注解</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tool-group">
|
<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>
|
<select id="imageDPI" class="format-select dpi-select" title="图片 DPI" aria-label="图片 DPI" disabled>
|
||||||
<option value="96">96</option>
|
<option value="96">96</option>
|
||||||
<option value="150">150</option>
|
<option value="150">150</option>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<option value="600">600</option>
|
<option value="600">600</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="exportPageButton" class="button" type="button" data-short="单" title="导出单页" aria-label="导出单页" disabled>单页</button>
|
<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>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
+23
-56
@@ -94,7 +94,7 @@ const el = {
|
|||||||
continuousButton: document.querySelector("#continuousButton"),
|
continuousButton: document.querySelector("#continuousButton"),
|
||||||
annotationButton: document.querySelector("#annotationButton"),
|
annotationButton: document.querySelector("#annotationButton"),
|
||||||
imageDPI: document.querySelector("#imageDPI"),
|
imageDPI: document.querySelector("#imageDPI"),
|
||||||
pageExportFormat: document.querySelector("#pageExportFormat"),
|
exportFormat: document.querySelector("#exportFormat"),
|
||||||
exportPageButton: document.querySelector("#exportPageButton"),
|
exportPageButton: document.querySelector("#exportPageButton"),
|
||||||
exportButton: document.querySelector("#exportButton"),
|
exportButton: document.querySelector("#exportButton"),
|
||||||
emptyState: document.querySelector("#emptyState"),
|
emptyState: document.querySelector("#emptyState"),
|
||||||
@@ -199,9 +199,9 @@ document.addEventListener("selectionchange", () => {
|
|||||||
unmountPage(index);
|
unmountPage(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
el.pageExportFormat.addEventListener("change", () => updateDPIControl());
|
el.exportFormat.addEventListener("change", () => updateDPIControl());
|
||||||
el.exportPageButton.addEventListener("click", exportCurrentPage);
|
el.exportPageButton.addEventListener("click", () => exportFile(false));
|
||||||
el.exportButton.addEventListener("click", exportPDF);
|
el.exportButton.addEventListener("click", () => exportFile(true));
|
||||||
el.refreshAppButton.addEventListener("click", refreshApplication);
|
el.refreshAppButton.addEventListener("click", refreshApplication);
|
||||||
el.pageInput.addEventListener("change", () => {
|
el.pageInput.addEventListener("change", () => {
|
||||||
const page = Number.parseInt(el.pageInput.value, 10);
|
const page = Number.parseInt(el.pageInput.value, 10);
|
||||||
@@ -593,15 +593,15 @@ async function refreshApplication() {
|
|||||||
async function loadExportFormats() {
|
async function loadExportFormats() {
|
||||||
const formats = await callWASM("ofdgoExportFormats") || [];
|
const formats = await callWASM("ofdgoExportFormats") || [];
|
||||||
state.exportFormats = formats;
|
state.exportFormats = formats;
|
||||||
el.pageExportFormat.replaceChildren();
|
el.exportFormat.replaceChildren();
|
||||||
for (const format of formats) {
|
for (const format of formats) {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = format.value;
|
option.value = format.value;
|
||||||
option.textContent = format.label;
|
option.textContent = format.label;
|
||||||
el.pageExportFormat.append(option);
|
el.exportFormat.append(option);
|
||||||
}
|
}
|
||||||
if (formats.length) {
|
if (formats.length) {
|
||||||
el.pageExportFormat.value = formats[0].value;
|
el.exportFormat.value = formats[0].value;
|
||||||
}
|
}
|
||||||
updateDPIControl();
|
updateDPIControl();
|
||||||
}
|
}
|
||||||
@@ -1487,61 +1487,32 @@ async function downloadAttachment(attachment) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportPDF() {
|
async function exportFile(whole) {
|
||||||
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const openSeq = state.openSeq;
|
const format = exportFormatInfo(el.exportFormat.value);
|
||||||
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;
|
|
||||||
if (!format) {
|
if (!format) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const info = exportFormatInfo(format);
|
const label = whole ? (format.value === "pdf" ? "PDF" : "ZIP") : format.label;
|
||||||
const label = info?.label || String(format || "").toUpperCase();
|
|
||||||
const openSeq = state.openSeq;
|
const openSeq = state.openSeq;
|
||||||
const fileName = pageFileName(info?.extension || format);
|
const fileName = whole ? `${baseFileName()}.${label.toLowerCase()}` : pageFileName(format.extension);
|
||||||
state.exporting = true;
|
state.exporting = true;
|
||||||
updateControls();
|
updateControls();
|
||||||
setBusy(true, `正在生成 ${label}`, 45, STATUS.pageExporting);
|
setBusy(true, `正在生成 ${label}`, null, whole ? STATUS.exporting : STATUS.pageExporting);
|
||||||
try {
|
try {
|
||||||
const dpi = exportFormatUsesDPI(format) ? currentImageDPI() : 0;
|
const dpi = exportFormatUsesDPI(format.value) ? currentImageDPI() : 0;
|
||||||
const result = await callWASM("ofdgoExportPage", state.pageIndex, format, dpi);
|
const result = whole
|
||||||
|
? await callWASM("ofdgoExportDocument", format.value, dpi)
|
||||||
|
: await callWASM("ofdgoExportPage", state.pageIndex, format.value, dpi);
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setProgress(`正在保存 ${result.label || label}`, 86);
|
setProgress(`正在保存 ${result.label}`, 100);
|
||||||
const bytes = result.bytes;
|
const bytes = result.bytes;
|
||||||
downloadBytes(bytes, result.mime || info?.mime || "application/octet-stream", fileName);
|
downloadBytes(bytes, result.mime, fileName);
|
||||||
setStatus(`${result.label || label} 导出完成 ${formatBytes(result.size || bytes.length, result.label || label)}`);
|
setStatus(`${result.label} 导出完成 ${formatBytes(bytes.length, result.label)}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (openSeq === state.openSeq) {
|
if (openSeq === state.openSeq) {
|
||||||
showError(err, false);
|
showError(err, false);
|
||||||
@@ -3224,9 +3195,9 @@ function updateControls() {
|
|||||||
el.fitButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "width");
|
el.fitButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "width");
|
||||||
el.fitHeightButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "height");
|
el.fitHeightButton.toggleAttribute("aria-pressed", hasDoc && state.fitMode === "height");
|
||||||
updateAnnotationButton();
|
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.exportPageButton.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
|
||||||
el.exportButton.disabled = !hasDoc || state.exporting;
|
el.exportButton.disabled = !hasDoc || state.exporting || !state.exportFormats.length;
|
||||||
updateDPIControl();
|
updateDPIControl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3329,13 +3300,9 @@ function baseFileName() {
|
|||||||
return base.replace(/[\\/:*?"<>|]+/g, "_").trim() || "ofdgo";
|
return base.replace(/[\\/:*?"<>|]+/g, "_").trim() || "ofdgo";
|
||||||
}
|
}
|
||||||
|
|
||||||
function pdfFileName() {
|
|
||||||
return `${baseFileName()}.pdf`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pageFileName(extension) {
|
function pageFileName(extension) {
|
||||||
const page = String(state.pageIndex + 1).padStart(String(state.doc?.pageCount || 1).length, "0");
|
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) {
|
function exportFormatInfo(value) {
|
||||||
@@ -3347,7 +3314,7 @@ function exportFormatUsesDPI(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateDPIControl() {
|
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() {
|
function currentImageDPI() {
|
||||||
|
|||||||
+10
-12
@@ -43,7 +43,7 @@ func RunWASM() {
|
|||||||
registerCallback("ofdgoSearchPage", searchPage)
|
registerCallback("ofdgoSearchPage", searchPage)
|
||||||
registerCallback("ofdgoExportFormats", exportFormats)
|
registerCallback("ofdgoExportFormats", exportFormats)
|
||||||
registerCallback("ofdgoExportPage", exportPage)
|
registerCallback("ofdgoExportPage", exportPage)
|
||||||
registerCallback("ofdgoExportPDF", exportPDF)
|
registerCallback("ofdgoExportDocument", exportDocument)
|
||||||
registerCallback("ofdgoMatchFontFiles", matchFontFiles)
|
registerCallback("ofdgoMatchFontFiles", matchFontFiles)
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
@@ -246,29 +246,27 @@ func exportPage(args []js.Value) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return successResult(map[string]any{
|
return successResult(map[string]any{
|
||||||
"bytes": bytesToJS(data),
|
"bytes": bytesToJS(data),
|
||||||
"size": len(data),
|
"label": format.Label,
|
||||||
"format": format.Value,
|
"mime": format.MIME,
|
||||||
"label": format.Label,
|
|
||||||
"extension": format.Extension,
|
|
||||||
"mime": format.MIME,
|
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// exportPDF 导出OFD文档为PDF
|
// exportDocument 导出OFD文档
|
||||||
// 入参: args 浏览器参数
|
// 入参: args 浏览器参数
|
||||||
// 返回: any PDF结果, error 错误信息
|
// 返回: any 导出结果, error 错误信息
|
||||||
func exportPDF(args []js.Value) (any, error) {
|
func exportDocument(args []js.Value) (any, error) {
|
||||||
if currentSession == nil {
|
if currentSession == nil {
|
||||||
return nil, fmt.Errorf("ofd document is not opened")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return successResult(map[string]any{
|
return successResult(map[string]any{
|
||||||
"bytes": bytesToJS(data),
|
"bytes": bytesToJS(data),
|
||||||
"size": len(data),
|
"label": format.Label,
|
||||||
|
"mime": format.MIME,
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,12 @@
|
|||||||
package webui
|
package webui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -629,6 +631,54 @@ func signatureStampInfos(positions []ofdgo.SignatureStampPosition) []SignatureSt
|
|||||||
return infos
|
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
|
// ExportPDF 导出文档为PDF
|
||||||
// 返回: []byte PDF文件数据, error 错误信息
|
// 返回: []byte PDF文件数据, error 错误信息
|
||||||
func (s *Session) ExportPDF() ([]byte, error) {
|
func (s *Session) ExportPDF() ([]byte, error) {
|
||||||
|
|||||||
Reference in new issue
Block a user