mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 20:22:39 +08:00
Compare commits
2
Commits
4a141466b2
...
3311f27786
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3311f27786 | ||
|
|
94f0cf2362 |
@@ -12,11 +12,11 @@ docker run -d -p 80:80 ccr.ccs.tencentyun.com/xiaoqidun/ofdgo:latest
|
|||||||
|
|
||||||
# 构建指南
|
# 构建指南
|
||||||
```batch
|
```batch
|
||||||
:: 1. 编译WASM资源
|
:: 1. 编译OFDGo WASM
|
||||||
set GOOS=js
|
set GOOS=js
|
||||||
set GOARCH=wasm
|
set GOARCH=wasm
|
||||||
go build -o assets/webui/ofdgo.wasm -trimpath -ldflags "-s -w -buildid=" ./cmd/webui/wasm.go
|
go build -o assets/webui/ofdgo.wasm -trimpath -ldflags "-s -w -buildid=" ./cmd/webui/wasm.go
|
||||||
:: 2. 构建OFDGo WebUI
|
:: 2. 编译OFDGo WebUI
|
||||||
set GOOS=windows
|
set GOOS=windows
|
||||||
set GOARCH=amd64
|
set GOARCH=amd64
|
||||||
go build -o ofdgo_webui.exe -trimpath -ldflags "-s -w -buildid=" ./cmd/webui/webui.go
|
go build -o ofdgo_webui.exe -trimpath -ldflags "-s -w -buildid=" ./cmd/webui/webui.go
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="app">
|
<div class="app">
|
||||||
<header class="toolbar app-bar">
|
<header class="toolbar app-bar">
|
||||||
<input id="ofdInput" class="file-input" type="file" accept=".ofd,application/ofd,application/zip">
|
<input id="ofdInput" class="file-input" type="file" accept=".ofd,application/ofd,application/zip">
|
||||||
<button id="ofdButton" class="button primary" type="button" data-short="打开">打开 OFD</button>
|
<button id="ofdButton" class="button primary" type="button" data-short="打开">打开</button>
|
||||||
<input id="fontInput" class="file-input" type="file" accept=".ttf,.otf,.ttc,font/*" multiple>
|
<input id="fontInput" class="file-input" type="file" accept=".ttf,.otf,.ttc,font/*" multiple>
|
||||||
<button id="togglePagesButton" class="icon-button" type="button" title="页面栏" aria-label="页面栏" aria-pressed="true">☰</button>
|
<button id="togglePagesButton" class="icon-button" type="button" title="页面栏" aria-label="页面栏" aria-pressed="true">☰</button>
|
||||||
<button id="toggleMetaButton" class="icon-button" type="button" title="信息栏" aria-label="信息栏" aria-pressed="true">ⓘ</button>
|
<button id="toggleMetaButton" class="icon-button" type="button" title="信息栏" aria-label="信息栏" aria-pressed="true">ⓘ</button>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
|
|
||||||
<footer class="app-footer app-bar">
|
<footer class="app-footer app-bar">
|
||||||
<a class="footer-brand" href="https://github.com/xiaoqidun/ofdgo" target="_blank" rel="noreferrer">OFDGo</a>
|
<a class="footer-brand" href="https://github.com/xiaoqidun/ofdgo" target="_blank" rel="noreferrer">OFDGo</a>
|
||||||
<div id="statusText" class="status-text">正在准备渲染引擎</div>
|
<div id="statusText" class="status-text">正在准备引擎</div>
|
||||||
<nav class="footer-links" aria-label="相关链接">
|
<nav class="footer-links" aria-label="相关链接">
|
||||||
<a href="https://aite.me/" target="_blank" rel="noreferrer">官方网站</a>
|
<a href="https://aite.me/" target="_blank" rel="noreferrer">官方网站</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
+40
-21
@@ -21,10 +21,10 @@ const FIT_WIDTH_MARGIN = 72;
|
|||||||
const FIT_HEIGHT_MARGIN = 86;
|
const FIT_HEIGHT_MARGIN = 86;
|
||||||
const COMPACT_LAYOUT = window.matchMedia("(max-width: 900px)");
|
const COMPACT_LAYOUT = window.matchMedia("(max-width: 900px)");
|
||||||
const STATUS = {
|
const STATUS = {
|
||||||
ready: "选择 OFD 文件开始阅读",
|
ready: "选择 OFD 文件",
|
||||||
opening: "正在打开 OFD",
|
opening: "正在打开 OFD",
|
||||||
engine: "正在准备渲染引擎",
|
engine: "正在准备引擎",
|
||||||
recovering: "正在恢复渲染引擎",
|
recovering: "正在恢复引擎",
|
||||||
fonts: "正在匹配字体",
|
fonts: "正在匹配字体",
|
||||||
exporting: "正在导出 PDF",
|
exporting: "正在导出 PDF",
|
||||||
};
|
};
|
||||||
@@ -135,10 +135,18 @@ COMPACT_LAYOUT.addEventListener("change", syncLayoutMode);
|
|||||||
el.viewerPanel.addEventListener("scroll", () => {
|
el.viewerPanel.addEventListener("scroll", () => {
|
||||||
schedulePageSync();
|
schedulePageSync();
|
||||||
});
|
});
|
||||||
|
el.viewerPanel.addEventListener("dblclick", openOFDFromViewer);
|
||||||
|
|
||||||
updateSidebarState();
|
updateSidebarState();
|
||||||
boot();
|
boot();
|
||||||
|
|
||||||
|
function openOFDFromViewer() {
|
||||||
|
if (state.doc || document.body.hasAttribute("aria-busy")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
el.ofdInput.click();
|
||||||
|
}
|
||||||
|
|
||||||
function toggleSidebar(side) {
|
function toggleSidebar(side) {
|
||||||
if (side === "pages") {
|
if (side === "pages") {
|
||||||
state.showPages = !state.showPages;
|
state.showPages = !state.showPages;
|
||||||
@@ -168,10 +176,10 @@ function syncLayoutMode(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function boot() {
|
async function boot() {
|
||||||
setBusy(true, "准备渲染引擎", 8, STATUS.engine);
|
setBusy(true, "正在准备引擎", 8, STATUS.engine);
|
||||||
try {
|
try {
|
||||||
await ensureWASM();
|
await ensureWASM();
|
||||||
setProgress("准备就绪", 70);
|
setProgress("引擎准备完成", 70);
|
||||||
setEmpty("选择 OFD 文件");
|
setEmpty("选择 OFD 文件");
|
||||||
updateFontSummary();
|
updateFontSummary();
|
||||||
renderFontList();
|
renderFontList();
|
||||||
@@ -210,18 +218,18 @@ async function loadWASM() {
|
|||||||
clearWASMCallbacks();
|
clearWASMCallbacks();
|
||||||
const go = new Go();
|
const go = new Go();
|
||||||
if (!wasmModule) {
|
if (!wasmModule) {
|
||||||
setProgress("下载渲染引擎", 16);
|
setProgress("正在下载引擎", 16);
|
||||||
const response = await fetch("./ofdgo.wasm");
|
const response = await fetch("./ofdgo.wasm");
|
||||||
try {
|
try {
|
||||||
setProgress("编译渲染引擎", 35);
|
setProgress("正在编译引擎", 35);
|
||||||
wasmModule = await WebAssembly.compileStreaming(response.clone());
|
wasmModule = await WebAssembly.compileStreaming(response.clone());
|
||||||
} catch {
|
} catch {
|
||||||
const bytes = await response.arrayBuffer();
|
const bytes = await response.arrayBuffer();
|
||||||
setProgress("编译渲染引擎", 45);
|
setProgress("正在编译引擎", 45);
|
||||||
wasmModule = await WebAssembly.compile(bytes);
|
wasmModule = await WebAssembly.compile(bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setProgress("启动渲染引擎", 58);
|
setProgress("正在启动引擎", 58);
|
||||||
const instance = await WebAssembly.instantiate(wasmModule, go.importObject);
|
const instance = await WebAssembly.instantiate(wasmModule, go.importObject);
|
||||||
go.run(instance).then(() => {
|
go.run(instance).then(() => {
|
||||||
markWASMExited(wasmSeq);
|
markWASMExited(wasmSeq);
|
||||||
@@ -270,7 +278,7 @@ async function recoverWASM() {
|
|||||||
state.wasmRecoveries += 1;
|
state.wasmRecoveries += 1;
|
||||||
const pageIndex = state.pageIndex;
|
const pageIndex = state.pageIndex;
|
||||||
const fitMode = state.fitMode || "width";
|
const fitMode = state.fitMode || "width";
|
||||||
setBusy(true, "恢复渲染引擎", 18, STATUS.recovering);
|
setBusy(true, "正在恢复引擎", 18, STATUS.recovering);
|
||||||
try {
|
try {
|
||||||
await ensureWASM();
|
await ensureWASM();
|
||||||
await openDocument({
|
await openDocument({
|
||||||
@@ -311,7 +319,7 @@ async function openSelectedOFD() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.wasmRecoveries = 0;
|
state.wasmRecoveries = 0;
|
||||||
setBusy(true, "读取 OFD 文件", 10, STATUS.opening);
|
setBusy(true, "正在读取 OFD", 10, STATUS.opening);
|
||||||
try {
|
try {
|
||||||
state.fileName = file.name || "ofdgo.ofd";
|
state.fileName = file.name || "ofdgo.ofd";
|
||||||
state.ofdBytes = new Uint8Array(await file.arrayBuffer());
|
state.ofdBytes = new Uint8Array(await file.arrayBuffer());
|
||||||
@@ -340,7 +348,7 @@ async function loadLocalFonts() {
|
|||||||
setStatus("当前浏览器不支持读取系统字体");
|
setStatus("当前浏览器不支持读取系统字体");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setBusy(true, state.doc ? "匹配系统字体" : "请求系统字体授权", 12, state.doc ? STATUS.fonts : "正在请求系统字体授权");
|
setBusy(true, state.doc ? "正在匹配字体" : "正在请求授权", 12, state.doc ? STATUS.fonts : "正在请求授权");
|
||||||
try {
|
try {
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
const available = await queryLocalFonts();
|
const available = await queryLocalFonts();
|
||||||
@@ -395,7 +403,7 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
|
|||||||
const emptyStatus = available.length === 0 ? "未读取到系统字体" : "未匹配到文档所需字体";
|
const emptyStatus = available.length === 0 ? "未读取到系统字体" : "未匹配到文档所需字体";
|
||||||
const fonts = [];
|
const fonts = [];
|
||||||
for (let i = 0; i < selected.length; i += 1) {
|
for (let i = 0; i < selected.length; i += 1) {
|
||||||
setProgress(`读取系统字体 ${i + 1}/${selected.length}`, 20 + Math.round(i / Math.max(1, selected.length) * 60));
|
setProgress(`正在读取字体 ${i + 1}/${selected.length}`, 20 + Math.round(i / Math.max(1, selected.length) * 60));
|
||||||
const item = selected[i];
|
const item = selected[i];
|
||||||
const blob = await item.blob();
|
const blob = await item.blob();
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
@@ -727,9 +735,9 @@ async function openDocument(options = {}) {
|
|||||||
updateFontSummary();
|
updateFontSummary();
|
||||||
renderFontList();
|
renderFontList();
|
||||||
}
|
}
|
||||||
setBusy(true, "打开 OFD", 20, STATUS.opening);
|
setBusy(true, "正在打开 OFD", 20, STATUS.opening);
|
||||||
try {
|
try {
|
||||||
setProgress("解析 OFD", 52);
|
setProgress("正在解析 OFD", 52);
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
return;
|
return;
|
||||||
@@ -745,7 +753,7 @@ async function openDocument(options = {}) {
|
|||||||
state.scale = 1;
|
state.scale = 1;
|
||||||
state.fitMode = options.fitMode || "width";
|
state.fitMode = options.fitMode || "width";
|
||||||
if (!options.skipAutoFonts && state.systemFontCatalog.length > 0) {
|
if (!options.skipAutoFonts && state.systemFontCatalog.length > 0) {
|
||||||
setProgress("匹配系统字体", 62, STATUS.fonts);
|
setProgress("正在匹配字体", 62, STATUS.fonts);
|
||||||
if (await loadDocumentLocalFonts(state.systemFontCatalog, openSeq)) {
|
if (await loadDocumentLocalFonts(state.systemFontCatalog, openSeq)) {
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
return;
|
return;
|
||||||
@@ -795,9 +803,9 @@ async function renderPage(index, options = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (options.keepBusy) {
|
if (options.keepBusy) {
|
||||||
setProgress("渲染页面", 76);
|
setProgress("正在渲染页面", 76);
|
||||||
} else {
|
} else {
|
||||||
setBusy(true, "渲染页面", 35, `正在渲染第 ${index + 1} 页`);
|
setBusy(true, "正在渲染页面", 35, "正在渲染页面");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setCurrentPage(index);
|
setCurrentPage(index);
|
||||||
@@ -832,17 +840,23 @@ async function exportPDF() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const openSeq = state.openSeq;
|
const openSeq = state.openSeq;
|
||||||
setBusy(true, "生成 PDF", 18, STATUS.exporting);
|
el.exportButton.disabled = true;
|
||||||
|
setBusy(true, "正在准备 PDF", 18, STATUS.exporting);
|
||||||
try {
|
try {
|
||||||
await nextFrame();
|
await waitForPaint();
|
||||||
|
if (openSeq !== state.openSeq) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setProgress("正在生成 PDF", 45);
|
||||||
|
await waitForPaint();
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setProgress("导出 PDF", 45);
|
|
||||||
const result = callWASM("ofdgoExportPDF");
|
const result = callWASM("ofdgoExportPDF");
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setProgress("正在保存 PDF", 86);
|
||||||
const bytes = base64ToBytes(result.base64);
|
const bytes = base64ToBytes(result.base64);
|
||||||
const blob = new Blob([bytes], { type: "application/pdf" });
|
const blob = new Blob([bytes], { type: "application/pdf" });
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
@@ -861,6 +875,7 @@ async function exportPDF() {
|
|||||||
} finally {
|
} finally {
|
||||||
if (openSeq === state.openSeq) {
|
if (openSeq === state.openSeq) {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
updateControls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1610,3 +1625,7 @@ function formatBytes(size) {
|
|||||||
function nextFrame() {
|
function nextFrame() {
|
||||||
return new Promise((resolve) => requestAnimationFrame(resolve));
|
return new Promise((resolve) => requestAnimationFrame(resolve));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForPaint() {
|
||||||
|
return new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user