mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
655f725bbc
commit
7545fc10c1
2 files changed
+110
-6
No files matched your search
@@ -4,6 +4,25 @@
|
||||
# 在线体验
|
||||
[OFDGo WebUI](https://ofdgo.aite.me/),将OFDGo编译为WASM提供服务
|
||||
|
||||
# 链接接入
|
||||
```text
|
||||
https://ofdgo.aite.me/#url=<URL编码后的文件地址>
|
||||
```
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `url` | 是 | 文件地址,支持OFD、PDF |
|
||||
| `name` | 否 | 文件名称,默认自动获取 |
|
||||
| `page` | 否 | 物理页码,默认1,超过总页数取末页 |
|
||||
|
||||
参数用`&`连接,参数值需URL编码。跨域访问时,响应头需包含:
|
||||
|
||||
```http
|
||||
Access-Control-Allow-Origin: https://ofdgo.aite.me
|
||||
```
|
||||
|
||||
公开资源可设为`*`;自建阅读器替换为对应源(协议、域名及端口)。
|
||||
|
||||
# 一键部署
|
||||
```shell
|
||||
docker run -d -p 80:80 ccr.ccs.tencentyun.com/xiaoqidun/ofdgo:latest
|
||||
|
||||
+91
-6
@@ -23,6 +23,7 @@ const batch = { items: [], formats: [], running: false, canceled: false, worker:
|
||||
const batchElements = Object.fromEntries(["Button", "Panel", "Form", "Input", "Add", "Clear", "Count", "Format", "Destination", "DPIRow", "DPI", "List", "Empty", "Progress", "Status", "Close", "Cancel", "Start"].map(name => [name, document.querySelector(`#convert${name}`)]));
|
||||
|
||||
let wasmPromise = null;
|
||||
let remoteDownload = null;
|
||||
let wasmWorker = null;
|
||||
let wasmRequestID = 0;
|
||||
let wasmRecoveryTimer = 0;
|
||||
@@ -3050,6 +3051,12 @@ async function boot() {
|
||||
}).catch((err) => showError(err, !state.doc));
|
||||
});
|
||||
}
|
||||
try {
|
||||
const remote = remoteDocumentOptions(window.location.hash);
|
||||
if (remote) await openOFD({ name: remote.name }, remote);
|
||||
} catch (err) {
|
||||
showError(err, !state.doc);
|
||||
}
|
||||
} catch (err) {
|
||||
setStatus("引擎加载失败");
|
||||
setEmpty(String(err.message || err));
|
||||
@@ -3361,11 +3368,79 @@ async function recoverWASM() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openOFD(file) {
|
||||
function remoteDocumentOptions(hash) {
|
||||
const params = new URLSearchParams(hash.replace(/^#/, ""));
|
||||
for (const key of ["url", "name", "page"]) {
|
||||
if (params.getAll(key).length > 1) throw new Error(`重复的链接参数:${key}`);
|
||||
}
|
||||
if (!params.has("url")) return null;
|
||||
let url;
|
||||
try { url = new URL(params.get("url")); } catch { throw new Error("文件地址无效"); }
|
||||
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) throw new Error("文件地址必须为不含账号密码的 HTTP 或 HTTPS 地址");
|
||||
if (window.location.protocol === "https:" && url.protocol !== "https:") throw new Error("请使用 HTTPS 文件地址");
|
||||
const page = params.get("page") || "1";
|
||||
return { url: url.href, name: params.get("name") || "", pageIndex: /^[1-9]\d*$/.test(page) ? Math.min(Number(page), Number.MAX_SAFE_INTEGER) - 1 : 0 };
|
||||
}
|
||||
|
||||
function remoteDocumentName(name) {
|
||||
return String(name || "").split(/[\\/]/).pop().replace(/[\u0000-\u001f\u007f]/g, "").trim();
|
||||
}
|
||||
|
||||
async function downloadRemoteDocument(options) {
|
||||
const controller = new AbortController();
|
||||
remoteDownload = controller;
|
||||
el.cancelExportButton.hidden = false;
|
||||
el.cancelExportButton.disabled = false;
|
||||
el.cancelExportButton.title = "取消下载";
|
||||
el.cancelExportButton.setAttribute("aria-label", "取消下载");
|
||||
setProgress("正在下载文档", null);
|
||||
try {
|
||||
const response = await fetch(options.url, { mode: "cors", credentials: "omit", referrerPolicy: "no-referrer", cache: "no-store", signal: controller.signal });
|
||||
if (!response.ok) throw new Error(`文件下载失败(HTTP ${response.status})`);
|
||||
const total = Number(response.headers.get("Content-Length")) || 0;
|
||||
if (!response.body) throw new Error("下载内容为空");
|
||||
const chunks = [];
|
||||
let received = 0;
|
||||
const reader = response.body.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
received += value.byteLength;
|
||||
setProgress(`正在下载 ${(received / 1048576).toFixed(1)} MB`, total > 0 && received <= total ? received / total * 20 : null);
|
||||
}
|
||||
} finally { reader.releaseLock(); }
|
||||
controller.signal.throwIfAborted();
|
||||
const bytes = new Uint8Array(received);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
|
||||
const pdf = new TextDecoder("latin1").decode(bytes.subarray(0, 1024)).includes("%PDF-");
|
||||
if (!pdf && !(bytes[0] === 0x50 && bytes[1] === 0x4b && bytes[2] === 3 && bytes[3] === 4)) throw new Error("下载内容不是 OFD 或 PDF 文档");
|
||||
const disposition = response.headers.get("Content-Disposition") || "";
|
||||
let supplied = /(?:^|;)\s*filename\*=UTF-8'[^']*'([^;]+)/i.exec(disposition)?.[1];
|
||||
if (supplied) { try { supplied = decodeURIComponent(supplied.trim()); } catch { supplied = ""; } }
|
||||
if (!supplied) supplied = /(?:^|;)\s*filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;]*))/i.exec(disposition)?.slice(1).find(value => value !== undefined)?.replace(/\\(.)/g, "$1");
|
||||
let pathName = new URL(response.url || options.url).pathname.split("/").pop();
|
||||
try { pathName = decodeURIComponent(pathName); } catch { pathName = ""; }
|
||||
const name = remoteDocumentName(options.name) || remoteDocumentName(supplied) || remoteDocumentName(pathName) || `ofdgo.${pdf ? "pdf" : "ofd"}`;
|
||||
return { bytes, pdf, name };
|
||||
} catch (err) {
|
||||
if (err instanceof TypeError) throw new Error("下载失败,请检查网络、链接及跨域配置");
|
||||
throw err;
|
||||
} finally {
|
||||
if (remoteDownload === controller) {
|
||||
remoteDownload = null;
|
||||
el.cancelExportButton.hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openOFD(file, remote = null) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (!isDocumentFile(file)) {
|
||||
if (!remote && !isDocumentFile(file)) {
|
||||
el.ofdInput.value = "";
|
||||
showError(new Error("选择 OFD 文件"), !state.doc);
|
||||
return;
|
||||
@@ -3373,17 +3448,20 @@ async function openOFD(file) {
|
||||
if (!discardChanges()) {
|
||||
return;
|
||||
}
|
||||
remoteDownload?.abort();
|
||||
clearSecurityForms();
|
||||
state.wasmRecoveries = 0;
|
||||
const openSeq = ++state.openSeq;
|
||||
setBusy(true, "正在读取文档", 10, STATUS.opening);
|
||||
try {
|
||||
let bytes = new Uint8Array(await file.arrayBuffer());
|
||||
const downloaded = remote ? await downloadRemoteDocument(remote) : null;
|
||||
let bytes = downloaded ? downloaded.bytes : new Uint8Array(await file.arrayBuffer());
|
||||
const name = downloaded ? downloaded.name : file.name;
|
||||
let warnings = [];
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
if (/\.pdf$/i.test(file.name || "")) {
|
||||
if (downloaded ? downloaded.pdf : /\.pdf$/i.test(file.name || "")) {
|
||||
setProgress("正在转换 PDF", 25);
|
||||
await ensureWASM();
|
||||
if (openSeq !== state.openSeq) return;
|
||||
@@ -3400,9 +3478,12 @@ async function openOFD(file) {
|
||||
bytes = converted.bytes;
|
||||
warnings = JSON.parse(converted.warnings || "null") || [];
|
||||
}
|
||||
const remoteDoc = remote ? await openWithCredentials(await fontManager.files(fontManager.records()), openSeq, bytes) : null;
|
||||
if (openSeq !== state.openSeq) return;
|
||||
state.ofdBytes = bytes;
|
||||
state.conversionWarnings = warnings;
|
||||
state.fileName = (file.name || "ofdgo.ofd").replace(/\.pdf$/i, ".ofd");
|
||||
state.fileName = (name || "ofdgo.ofd").replace(/\.pdf$/i, ".ofd");
|
||||
if (remote && !/\.ofd$/i.test(state.fileName)) state.fileName += ".ofd";
|
||||
state.editing = false;
|
||||
state.composite = null;
|
||||
state.annotationCreate = null;
|
||||
@@ -3432,7 +3513,7 @@ async function openOFD(file) {
|
||||
el.objectBoundsPanel.close();
|
||||
el.outlinePanel.close();
|
||||
updateControls();
|
||||
await openDocument({ pageIndex: 0, resetScroll: true, openSeq });
|
||||
await openDocument({ ...(remoteDoc ? { doc: remoteDoc } : {}), pageIndex: remote?.pageIndex || 0, resetScroll: true, openSeq });
|
||||
} catch (err) {
|
||||
if (openSeq === state.openSeq) {
|
||||
if (err.name === "AbortError") setStatus("转换已取消");
|
||||
@@ -4975,6 +5056,10 @@ async function exportFile(whole, indices = null, value = el.exportFormat.value,
|
||||
}
|
||||
|
||||
function cancelExport() {
|
||||
if (remoteDownload) {
|
||||
remoteDownload.abort();
|
||||
return;
|
||||
}
|
||||
if (!state.exportRequestID || el.cancelExportButton.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user