mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
c3d8c0e8a0
commit
52c02d55ef
3 files changed
+109
-35
No files matched your search
@@ -49,6 +49,7 @@
|
||||
</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="printButton" class="button" type="button" data-short="印" title="打印文档" aria-label="打印文档" disabled>打印</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -137,7 +138,7 @@
|
||||
<button id="fontDirectoryButton" class="small-button" type="button" title="添加字体目录" aria-label="添加字体目录">目录</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="fontPermissionHint" class="font-permission-hint" hidden>需授权读取系统字体</div>
|
||||
<div id="fontPermissionHint" class="font-permission-hint" hidden>系统字体需授权</div>
|
||||
<div id="fontList" class="font-list"></div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
@@ -117,6 +117,15 @@ a:focus-visible {
|
||||
clip: rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.print-frame {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 100%;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.button,
|
||||
.icon-button {
|
||||
display: inline-flex;
|
||||
|
||||
+98
-34
@@ -10,7 +10,8 @@ const STATUS = {
|
||||
engine: "正在准备引擎",
|
||||
recovering: "正在恢复引擎",
|
||||
fonts: "正在匹配字体",
|
||||
exporting: "正在导出文档 PDF",
|
||||
exporting: "正在导出文档",
|
||||
printing: "正在准备打印",
|
||||
pageExporting: "正在导出单页",
|
||||
};
|
||||
const WASM_CALLBACKS = [
|
||||
@@ -28,6 +29,7 @@ let wasmPromise = null;
|
||||
let wasmModule = null;
|
||||
let wasmRecoveryTimer = 0;
|
||||
let fontDatabase = null;
|
||||
let printFrame = null;
|
||||
|
||||
const state = {
|
||||
ready: false,
|
||||
@@ -64,6 +66,7 @@ const state = {
|
||||
thumbnailObserver: null,
|
||||
visibleThumbnails: new Set(),
|
||||
exportFormats: [],
|
||||
outputting: false,
|
||||
showPages: !COMPACT_LAYOUT.matches,
|
||||
showMeta: !COMPACT_LAYOUT.matches,
|
||||
};
|
||||
@@ -93,6 +96,7 @@ const el = {
|
||||
pageExportFormat: document.querySelector("#pageExportFormat"),
|
||||
exportPageButton: document.querySelector("#exportPageButton"),
|
||||
exportButton: document.querySelector("#exportButton"),
|
||||
printButton: document.querySelector("#printButton"),
|
||||
emptyState: document.querySelector("#emptyState"),
|
||||
progressPanel: document.querySelector("#progressPanel"),
|
||||
progressLabel: document.querySelector("#progressLabel"),
|
||||
@@ -143,7 +147,8 @@ el.continuousButton.addEventListener("click", toggleContinuous);
|
||||
el.annotationButton.addEventListener("click", toggleAnnotations);
|
||||
el.pageExportFormat.addEventListener("change", () => updateDPIControl());
|
||||
el.exportPageButton.addEventListener("click", exportCurrentPage);
|
||||
el.exportButton.addEventListener("click", exportPDF);
|
||||
el.exportButton.addEventListener("click", () => outputPDF(false));
|
||||
el.printButton.addEventListener("click", () => outputPDF(true));
|
||||
el.refreshAppButton.addEventListener("click", refreshApplication);
|
||||
el.pageInput.addEventListener("change", () => {
|
||||
const page = Number.parseInt(el.pageInput.value, 10);
|
||||
@@ -692,7 +697,7 @@ async function loadLocalFonts() {
|
||||
return;
|
||||
}
|
||||
if (!canReadLocalFonts()) {
|
||||
setStatus("当前浏览器不支持读取系统字体");
|
||||
setStatus("不支持读取系统字体");
|
||||
return;
|
||||
}
|
||||
setBusy(true, state.doc ? "正在匹配字体" : "正在请求授权", 12, state.doc ? STATUS.fonts : "正在请求授权");
|
||||
@@ -708,7 +713,7 @@ async function loadLocalFonts() {
|
||||
}
|
||||
} catch (err) {
|
||||
if (err && err.name === "NotAllowedError") {
|
||||
setStatus("未授权读取系统字体");
|
||||
setStatus("系统字体未授权");
|
||||
} else {
|
||||
setStatus(String(err.message || err));
|
||||
}
|
||||
@@ -727,7 +732,7 @@ async function requestLocalFontsBeforeOpen() {
|
||||
setStatus(available.length ? `已授权 ${available.length} 个系统字体` : "未读取到系统字体");
|
||||
} catch (err) {
|
||||
if (err && err.name === "NotAllowedError") {
|
||||
setStatus("未授权读取系统字体");
|
||||
setStatus("系统字体未授权");
|
||||
return;
|
||||
}
|
||||
setStatus(String(err.message || err));
|
||||
@@ -766,7 +771,7 @@ async function autoLoadDocumentLocalFonts(openSeq) {
|
||||
return await loadDocumentLocalFonts(available, openSeq);
|
||||
} catch (err) {
|
||||
if (err && err.name === "NotAllowedError") {
|
||||
setStatus("未授权读取系统字体");
|
||||
setStatus("系统字体未授权");
|
||||
return false;
|
||||
}
|
||||
setStatus(String(err.message || err));
|
||||
@@ -786,7 +791,7 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
|
||||
}
|
||||
if (!docNames.length) {
|
||||
state.localFonts = [];
|
||||
setStatus("OFD 字体均为内嵌");
|
||||
setStatus("文档字体均为内嵌");
|
||||
updateFontSummary();
|
||||
renderFontList();
|
||||
return false;
|
||||
@@ -796,7 +801,7 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
|
||||
if (!selected.length) {
|
||||
selected = selectLocalFonts(available, [], 6);
|
||||
}
|
||||
const emptyStatus = available.length === 0 ? "未读取到系统字体" : "未匹配到文档所需字体";
|
||||
const emptyStatus = available.length === 0 ? "未读取到系统字体" : "未匹配到所需字体";
|
||||
const fonts = [];
|
||||
for (let i = 0; i < selected.length; i += 1) {
|
||||
setProgress(`正在读取字体 ${i + 1}/${selected.length}`, 20 + Math.round(i / selected.length * 60));
|
||||
@@ -1093,7 +1098,7 @@ function fontSourceText(source) {
|
||||
function updateLocalFontButton() {
|
||||
const supported = canReadLocalFonts();
|
||||
el.localFontButton.disabled = !supported;
|
||||
el.localFontButton.title = supported ? "授权读取浏览器可访问的系统字体" : "当前浏览器不支持读取系统字体";
|
||||
el.localFontButton.title = supported ? "读取系统字体" : "不支持读取系统字体";
|
||||
updateFontPermissionHint();
|
||||
}
|
||||
|
||||
@@ -1273,19 +1278,23 @@ async function renderPage(index, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
async function exportPDF() {
|
||||
if (!state.doc) {
|
||||
async function outputPDF(print) {
|
||||
if (!state.doc || state.outputting) {
|
||||
return;
|
||||
}
|
||||
if (print && !navigator.pdfViewerEnabled) {
|
||||
setStatus("请导出 PDF 后打印");
|
||||
return;
|
||||
}
|
||||
const openSeq = state.openSeq;
|
||||
setExportControlsDisabled(true);
|
||||
setBusy(true, "正在准备文档 PDF", 18, STATUS.exporting);
|
||||
setOutputting(true);
|
||||
setBusy(true, "正在准备 PDF", 18, print ? STATUS.printing : STATUS.exporting);
|
||||
try {
|
||||
await waitForPaint();
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
setProgress("正在生成文档 PDF", 45);
|
||||
setProgress("正在生成 PDF", 45);
|
||||
await waitForPaint();
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
@@ -1294,10 +1303,18 @@ async function exportPDF() {
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
setProgress("正在保存文档 PDF", 86);
|
||||
const bytes = result.bytes;
|
||||
downloadBytes(bytes, "application/pdf", pdfFileName());
|
||||
setStatus(`文档 PDF 已导出 ${formatBytes(result.size || bytes.length)}`);
|
||||
if (print) {
|
||||
setProgress("正在加载 PDF", 86);
|
||||
await printPDF(bytes, openSeq);
|
||||
if (openSeq === state.openSeq) {
|
||||
setStatus("已请求打印");
|
||||
}
|
||||
} else {
|
||||
setProgress("正在保存 PDF", 86);
|
||||
downloadBytes(bytes, "application/pdf", pdfFileName());
|
||||
setStatus(`PDF 已导出 ${formatBytes(result.size || bytes.length)}`);
|
||||
}
|
||||
} catch (err) {
|
||||
if (openSeq === state.openSeq) {
|
||||
showError(err, false);
|
||||
@@ -1305,13 +1322,60 @@ async function exportPDF() {
|
||||
} finally {
|
||||
if (openSeq === state.openSeq) {
|
||||
setBusy(false);
|
||||
updateControls();
|
||||
}
|
||||
setOutputting(false);
|
||||
}
|
||||
}
|
||||
|
||||
function clearPrintFrame() {
|
||||
if (!printFrame) {
|
||||
return;
|
||||
}
|
||||
URL.revokeObjectURL(printFrame.src);
|
||||
printFrame.remove();
|
||||
printFrame = null;
|
||||
}
|
||||
|
||||
function printPDF(bytes, openSeq) {
|
||||
clearPrintFrame();
|
||||
const frame = document.createElement("iframe");
|
||||
frame.className = "print-frame";
|
||||
frame.title = "文档打印";
|
||||
frame.tabIndex = -1;
|
||||
frame.setAttribute("aria-hidden", "true");
|
||||
frame.src = URL.createObjectURL(new Blob([bytes], { type: "application/pdf" }));
|
||||
printFrame = frame;
|
||||
return new Promise((resolve, reject) => {
|
||||
const clear = () => {
|
||||
if (printFrame === frame) {
|
||||
clearPrintFrame();
|
||||
}
|
||||
};
|
||||
const fail = () => {
|
||||
clear();
|
||||
reject(new Error("请导出 PDF 后打印"));
|
||||
};
|
||||
frame.onload = () => {
|
||||
if (openSeq !== state.openSeq) {
|
||||
clear();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
frame.contentWindow.addEventListener("afterprint", clear, { once: true });
|
||||
frame.contentWindow.print();
|
||||
resolve();
|
||||
} catch {
|
||||
fail();
|
||||
}
|
||||
};
|
||||
frame.onerror = fail;
|
||||
document.body.append(frame);
|
||||
});
|
||||
}
|
||||
|
||||
async function exportCurrentPage() {
|
||||
if (!state.doc) {
|
||||
if (!state.doc || state.outputting) {
|
||||
return;
|
||||
}
|
||||
const format = el.pageExportFormat.value;
|
||||
@@ -1321,7 +1385,7 @@ async function exportCurrentPage() {
|
||||
const info = exportFormatInfo(format);
|
||||
const label = info?.label || String(format || "").toUpperCase();
|
||||
const openSeq = state.openSeq;
|
||||
setExportControlsDisabled(true);
|
||||
setOutputting(true);
|
||||
setBusy(true, `正在准备 ${label}`, 18, STATUS.pageExporting);
|
||||
try {
|
||||
await waitForPaint();
|
||||
@@ -1349,8 +1413,8 @@ async function exportCurrentPage() {
|
||||
} finally {
|
||||
if (openSeq === state.openSeq) {
|
||||
setBusy(false);
|
||||
updateControls();
|
||||
}
|
||||
setOutputting(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2182,7 +2246,7 @@ async function focusSignatureStamp(stamp) {
|
||||
await renderPage(pageIndex, { fit: false, scroll: false });
|
||||
await nextFrame();
|
||||
highlightSignatureStamp(stamp);
|
||||
setStatus(stamp.page ? `已定位签名外观 第 ${stamp.page} 页` : "已定位签名外观");
|
||||
setStatus(stamp.page ? `已定位签名 第 ${stamp.page} 页` : "已定位签名");
|
||||
}
|
||||
|
||||
function highlightSignatureStamp(stamp) {
|
||||
@@ -2287,7 +2351,7 @@ function pageStatus(index, pageCount) {
|
||||
function statusText(status) {
|
||||
switch (status) {
|
||||
case "pending":
|
||||
return "待检查";
|
||||
return "待查";
|
||||
case "embedded":
|
||||
return "内嵌";
|
||||
case "matched":
|
||||
@@ -2510,6 +2574,7 @@ function updateFitSpace() {
|
||||
|
||||
function updateControls() {
|
||||
const hasDoc = Boolean(state.doc);
|
||||
const canExport = hasDoc && !state.outputting;
|
||||
const pageCount = state.doc ? state.doc.pageCount : 0;
|
||||
el.prevButton.disabled = !hasDoc || state.pageIndex <= 0;
|
||||
el.nextButton.disabled = !hasDoc || state.pageIndex >= pageCount - 1;
|
||||
@@ -2524,23 +2589,22 @@ 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.exportFormats.length;
|
||||
el.exportPageButton.disabled = !hasDoc || !state.exportFormats.length;
|
||||
el.exportButton.disabled = !hasDoc;
|
||||
el.pageExportFormat.disabled = !canExport || !state.exportFormats.length;
|
||||
el.exportPageButton.disabled = !canExport || !state.exportFormats.length;
|
||||
el.exportButton.disabled = !canExport;
|
||||
el.printButton.disabled = !canExport;
|
||||
updateDPIControl();
|
||||
}
|
||||
|
||||
function updateAnnotationButton() {
|
||||
el.annotationButton.setAttribute("aria-pressed", String(state.renderAnnotations));
|
||||
el.annotationButton.title = state.renderAnnotations ? "关闭注解渲染" : "开启注解渲染";
|
||||
el.annotationButton.title = state.renderAnnotations ? "关闭注解" : "开启注解";
|
||||
el.annotationButton.setAttribute("aria-label", el.annotationButton.title);
|
||||
}
|
||||
|
||||
function setExportControlsDisabled(disabled) {
|
||||
el.exportButton.disabled = disabled;
|
||||
el.exportPageButton.disabled = disabled;
|
||||
el.pageExportFormat.disabled = disabled;
|
||||
updateDPIControl(disabled);
|
||||
function setOutputting(outputting) {
|
||||
state.outputting = outputting;
|
||||
updateControls();
|
||||
}
|
||||
|
||||
function callWASM(name, ...args) {
|
||||
@@ -2659,8 +2723,8 @@ function exportFormatUsesDPI(value) {
|
||||
return value === "png" || value === "jpg";
|
||||
}
|
||||
|
||||
function updateDPIControl(disabled = false) {
|
||||
el.imageDPI.disabled = disabled || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value);
|
||||
function updateDPIControl() {
|
||||
el.imageDPI.disabled = state.outputting || !state.doc || !exportFormatUsesDPI(el.pageExportFormat.value);
|
||||
}
|
||||
|
||||
function currentImageDPI() {
|
||||
|
||||
Reference in new issue
Block a user