mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
1 parent
fb4f03465b
commit
413d41c31d
7 files changed
+261
-73
No files matched your search
+107
-29
@@ -674,7 +674,7 @@ function createPrintDialog(tab) {
|
|||||||
show: panel => documentDialogs.openFor(tab, panel),
|
show: panel => documentDialogs.openFor(tab, panel),
|
||||||
download: (bytes, mime, name) => nativeHost ? nativeHost.download(bytes, mime, name) : downloadBytes(bytes, mime, name),
|
download: (bytes, mime, name) => nativeHost ? nativeHost.download(bytes, mime, name) : downloadBytes(bytes, mime, name),
|
||||||
cancel: tab => {
|
cancel: tab => {
|
||||||
for (const [id, request] of wasmRequests) if (request.tab === tab && ["ofdgoPreparePrint", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF"].includes(request.name)) tab.worker?.postMessage({type:"cancel", id});
|
for (const [id, request] of wasmRequests) if (request.tab === tab && ["ofdgoPreparePrint", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF"].includes(request.name)) cancelOperation(id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -886,7 +886,7 @@ resourceElements.Form.addEventListener("submit", event => event.preventDefault()
|
|||||||
resourceElements.Panel.addEventListener("close", () => {
|
resourceElements.Panel.addEventListener("close", () => {
|
||||||
rememberResourceView();
|
rememberResourceView();
|
||||||
resourceManager.sequence++;
|
resourceManager.sequence++;
|
||||||
if (resourceManager.requestID) wasmWorker?.postMessage({type: "cancel", id: resourceManager.requestID});
|
if (resourceManager.requestID) cancelOperation(resourceManager.requestID);
|
||||||
releaseResourcePreview();
|
releaseResourcePreview();
|
||||||
});
|
});
|
||||||
resourceElements.List.closest(".resource-list-container").addEventListener("scroll", renderResourceWindow, {passive: true});
|
resourceElements.List.closest(".resource-list-container").addEventListener("scroll", renderResourceWindow, {passive: true});
|
||||||
@@ -2633,6 +2633,7 @@ async function closeWorkspace() {
|
|||||||
if (workspace.closing) return;
|
if (workspace.closing) return;
|
||||||
workspace.closing = true;
|
workspace.closing = true;
|
||||||
try {
|
try {
|
||||||
|
for (const tab of workspace.tabs) cancelDocumentPreparation(tab);
|
||||||
await Promise.all(workspace.tabs.filter(tab => tab.state.saving && tab.saveCompletion).map(tab => tab.saveCompletion));
|
await Promise.all(workspace.tabs.filter(tab => tab.state.saving && tab.saveCompletion).map(tab => tab.saveCompletion));
|
||||||
if (workspace.tabs.some(tab => tab.state.saving)) { setStatus("文档正在保存,请稍后关闭"); return; }
|
if (workspace.tabs.some(tab => tab.state.saving)) { setStatus("文档正在保存,请稍后关闭"); return; }
|
||||||
if (batch.running && !window.confirm("批量转换尚未完成,是否退出?")) return;
|
if (batch.running && !window.confirm("批量转换尚未完成,是否退出?")) return;
|
||||||
@@ -3080,6 +3081,7 @@ async function closeFile(tab = documentTab(), keepTab = false) {
|
|||||||
queueMicrotask(() => { if (workspace.closeDefault === tab) workspace.closeDefault = null; });
|
queueMicrotask(() => { if (workspace.closeDefault === tab) workspace.closeDefault = null; });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
cancelDocumentPreparation(tab);
|
||||||
if (tab.state.saving && tab.saveCompletion) await tab.saveCompletion;
|
if (tab.state.saving && tab.saveCompletion) await tab.saveCompletion;
|
||||||
if (tab.closed) return;
|
if (tab.closed) return;
|
||||||
if (tab.opening || tab.state.exporting) {
|
if (tab.opening || tab.state.exporting) {
|
||||||
@@ -5487,8 +5489,6 @@ async function loadWASM(tab = documentTab()) {
|
|||||||
}
|
}
|
||||||
const visible = isForegroundDocument(tab);
|
const visible = isForegroundDocument(tab);
|
||||||
if (data.type === "progress") tab.progress = {text: data.text, percent: data.percent};
|
if (data.type === "progress") tab.progress = {text: data.text, percent: data.percent};
|
||||||
if (data.type === "conversion") tab.progress = {text: data.phase === "convert" ? `正在转换页面 · ${data.completed} / ${data.total}` : "正在转换文档",
|
|
||||||
percent: data.total ? 28 + data.completed / data.total * 22 : null};
|
|
||||||
if (data.type === "font") {
|
if (data.type === "font") {
|
||||||
try {
|
try {
|
||||||
const request = wasmRequests.get(data.id);
|
const request = wasmRequests.get(data.id);
|
||||||
@@ -5505,11 +5505,13 @@ async function loadWASM(tab = documentTab()) {
|
|||||||
if (data.type === "progress") {
|
if (data.type === "progress") {
|
||||||
if (visible) setProgress(data.text, data.percent);
|
if (visible) setProgress(data.text, data.percent);
|
||||||
} else if (data.type === "conversion") {
|
} else if (data.type === "conversion") {
|
||||||
if (visible && wasmRequests.get(data.id)?.name !== "ofdgoLoadPDF" && wasmRequests.get(data.id)?.openSeq === state.openSeq && !el.cancelExportButton.disabled) {
|
const request = wasmRequests.get(data.id);
|
||||||
if (data.phase === "open") setProgress("正在读取文档", null);
|
if (request?.name === "ofdgoLoadPDF") {
|
||||||
else if (data.phase === "pages") setProgress(data.completed ? `正在整理页面 · ${data.completed} 页` : "正在整理页面", null);
|
for (const pending of wasmRequests.values()) if (pending.tab === tab && pending.preparing) operationProgress(pending, data);
|
||||||
else if (data.phase === "convert") setProgress(`正在转换页面 · ${data.completed} / ${data.total} 页`, data.total ? 28 + data.completed / data.total * 22 : null);
|
} else if (request?.name === "ofdgoOpenPDF") {
|
||||||
else setProgress("正在生成文档", null);
|
const text = data.phase === "open" ? "正在读取文档" : data.phase === "pages" ? "正在整理页面" : "正在准备阅读";
|
||||||
|
tab.progress = {text, percent:null};
|
||||||
|
if (visible && !el.cancelExportButton.disabled) setProgress(text, null);
|
||||||
}
|
}
|
||||||
} else if (data.type === "export") {
|
} else if (data.type === "export") {
|
||||||
if (data.id === state.saveRequestID && state.saving && !state.exporting) {
|
if (data.id === state.saveRequestID && state.saving && !state.exporting) {
|
||||||
@@ -5555,6 +5557,7 @@ async function loadWASM(tab = documentTab()) {
|
|||||||
} else {
|
} else {
|
||||||
const request = wasmRequests.get(data.id);
|
const request = wasmRequests.get(data.id);
|
||||||
if (!request || request.tab !== tab) return;
|
if (!request || request.tab !== tab) return;
|
||||||
|
if (data.code === "documentPending" && !request.canceled) { prepareOperation(data.id, request); return; }
|
||||||
wasmRequests.delete(data.id);
|
wasmRequests.delete(data.id);
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
request.resolve(data.data);
|
request.resolve(data.data);
|
||||||
@@ -5563,7 +5566,7 @@ async function loadWASM(tab = documentTab()) {
|
|||||||
err.code = data.code;
|
err.code = data.code;
|
||||||
if (data.errorName) err.name = data.errorName;
|
if (data.errorName) err.name = data.errorName;
|
||||||
err.missingGlyphs = data.missingGlyphs;
|
err.missingGlyphs = data.missingGlyphs;
|
||||||
if (data.canceled) {
|
if (data.canceled || request.canceled) {
|
||||||
err.name = "AbortError";
|
err.name = "AbortError";
|
||||||
}
|
}
|
||||||
request.reject(err);
|
request.reject(err);
|
||||||
@@ -6317,6 +6320,7 @@ async function openWithCredentials(fonts, openSeq, data = state.ofdBytes, import
|
|||||||
try {
|
try {
|
||||||
if (pdf) {
|
if (pdf) {
|
||||||
state.pdfComplete = false;
|
state.pdfComplete = false;
|
||||||
|
tab.pdfTask = null;
|
||||||
return await callWASM("ofdgoOpenPDF", data, fonts, credentials?.password || null, state.renderAnnotations);
|
return await callWASM("ofdgoOpenPDF", data, fonts, credentials?.password || null, state.renderAnnotations);
|
||||||
}
|
}
|
||||||
return importing ? await callWASM("ofdgoLoadImport", data?.nativeFile ? data : data.slice(), credentials)
|
return importing ? await callWASM("ofdgoLoadImport", data?.nativeFile ? data : data.slice(), credentials)
|
||||||
@@ -6526,33 +6530,36 @@ async function openDocument(options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function completePDF(tab) {
|
function completePDF(tab) {
|
||||||
const state = tab.state, source = state.ofdBytes, wasmSeq = state.wasmSeq;
|
const state = tab.state, source = state.ofdBytes, wasmSeq = state.wasmSeq;
|
||||||
if (tab.pdfTask?.wasmSeq === wasmSeq || state.pdfComplete || !state.sourcePDF || !source || state.editorInfo) return;
|
if (state.pdfComplete || !state.sourcePDF || state.editorInfo) return Promise.resolve();
|
||||||
const task = {wasmSeq};
|
if (tab.pdfTask?.wasmSeq === wasmSeq && tab.pdfTask.source === source) return tab.pdfTask.promise;
|
||||||
|
const task = {wasmSeq, source};
|
||||||
tab.pdfTask = task;
|
tab.pdfTask = task;
|
||||||
const current = () => !tab.closed && state.ofdBytes === source && state.wasmSeq === wasmSeq;
|
const current = () => !tab.closed && tab.pdfTask === task && state.ofdBytes === source && state.wasmSeq === wasmSeq;
|
||||||
try {
|
task.promise = (async () => {
|
||||||
while (current()) {
|
while (current()) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 0));
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
if (!current()) return;
|
if (!current()) break;
|
||||||
const result = await callTabWASM(tab, "ofdgoLoadPDF");
|
const result = await callTabWASM(tab, "ofdgoLoadPDF");
|
||||||
if (!current()) return;
|
if (!current()) break;
|
||||||
if (!result.done) continue;
|
if (!result.done) continue;
|
||||||
state.pdfComplete = true;
|
state.pdfComplete = true;
|
||||||
|
tab.pdfTask = null;
|
||||||
state.conversionWarnings = JSON.parse(result.warnings || "null") || [];
|
state.conversionWarnings = JSON.parse(result.warnings || "null") || [];
|
||||||
if (!state.editorInfo) Object.assign(state.doc, result.doc);
|
if (!state.editorInfo) Object.assign(state.doc, result.doc);
|
||||||
if (isForegroundDocument(tab)) loadDocumentDetails(state.openSeq);
|
if (isForegroundDocument(tab)) loadDocumentDetails(state.openSeq);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
throw new DOMException("文档已关闭", "AbortError");
|
||||||
if (current() && !err.canceled) {
|
})();
|
||||||
|
task.promise.catch(err => {
|
||||||
|
if (current() && err.name !== "AbortError") {
|
||||||
tab.status = err.message;
|
tab.status = err.message;
|
||||||
if (isForegroundDocument(tab)) showError(err, false);
|
if (isForegroundDocument(tab)) showError(err, false);
|
||||||
}
|
}
|
||||||
} finally {
|
});
|
||||||
if (tab.pdfTask === task) tab.pdfTask = null;
|
return task.promise;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadDocumentDetails(openSeq) {
|
async function loadDocumentDetails(openSeq) {
|
||||||
@@ -10065,7 +10072,7 @@ function cancelExport() {
|
|||||||
}
|
}
|
||||||
const tab = documentTab();
|
const tab = documentTab();
|
||||||
const target = tab.state;
|
const target = tab.state;
|
||||||
const requestID = target.exportRequestID || target.saveRequestID;
|
const requestID = operationRequest(tab)?.id || target.exportRequestID || target.saveRequestID;
|
||||||
if (!requestID || el.cancelExportButton.disabled) {
|
if (!requestID || el.cancelExportButton.disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -10078,7 +10085,7 @@ function cancelExport() {
|
|||||||
el.importStatus.textContent = "正在取消操作";
|
el.importStatus.textContent = "正在取消操作";
|
||||||
}
|
}
|
||||||
setProgress("正在取消操作", null);
|
setProgress("正在取消操作", null);
|
||||||
tab.worker.postMessage({ type: "cancel", id: requestID });
|
cancelOperation(requestID);
|
||||||
}
|
}
|
||||||
|
|
||||||
function releaseMediaPlayers(surface) {
|
function releaseMediaPlayers(surface) {
|
||||||
@@ -10092,7 +10099,7 @@ function releaseMediaPlayer(id, entry) {
|
|||||||
if (state.mediaPlayers?.get(id) !== entry) return;
|
if (state.mediaPlayers?.get(id) !== entry) return;
|
||||||
state.mediaPlayers.delete(id);
|
state.mediaPlayers.delete(id);
|
||||||
for (const [requestID, request] of wasmRequests) {
|
for (const [requestID, request] of wasmRequests) {
|
||||||
if (request.mediaID === id) wasmWorker?.postMessage({type: "cancel", id: requestID});
|
if (request.tab === documentTab() && request.mediaID === id) cancelOperation(requestID);
|
||||||
}
|
}
|
||||||
entry.finish?.();
|
entry.finish?.();
|
||||||
closeMediaWindow(entry);
|
closeMediaWindow(entry);
|
||||||
@@ -12348,6 +12355,72 @@ async function callWASM(name, ...args) {
|
|||||||
return callTabWASM(documentTab(), name, ...args);
|
return callTabWASM(documentTab(), name, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function operationRequest(tab) {
|
||||||
|
return [...wasmRequests.values()].findLast(request => request.tab === tab && request.cancellable && !request.name.includes("Print"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function operationProgress(request, progress) {
|
||||||
|
const text = request.name.includes("Print") ? "正在准备打印" : request.name === "ofdgoEditDocument" ? "正在准备编辑" : request.name.includes("Save") ? "正在准备保存" : request.name.includes("Export") ? "正在准备导出" : "正在准备文档";
|
||||||
|
const completed = progress?.phase === "convert" && progress.total > 0;
|
||||||
|
const label = completed ? `${text} · ${progress.completed} / ${progress.total} 页` : text;
|
||||||
|
if (request.name.includes("Print")) {
|
||||||
|
if (request.tab.print?.context) request.tab.print.status(label, true);
|
||||||
|
} else if (operationRequest(request.tab) === request) {
|
||||||
|
request.tab.progress = {text:label, percent:completed ? progress.completed / progress.total * 100 : null};
|
||||||
|
if (isForegroundDocument(request.tab)) setProgress(label, request.tab.progress.percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishOperation(request) {
|
||||||
|
if (request.tab.preparationBusy && !operationRequest(request.tab)) {
|
||||||
|
request.tab.preparationBusy = false;
|
||||||
|
if (!request.tab.closed) setBusy(false, "", 0, "", request.tab);
|
||||||
|
}
|
||||||
|
if (request.cancellable && isForegroundDocument(request.tab) && !operationRequest(request.tab)) {
|
||||||
|
el.cancelExportButton.hidden = !request.tab.state.saving && !request.tab.state.exporting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelDocumentPreparation(tab) {
|
||||||
|
if (!tab.state.sourcePDF || tab.state.pdfComplete) return;
|
||||||
|
for (const [id, request] of wasmRequests) if (request.tab === tab && (request.preparing || request.name === "ofdgoPrepareSave")) cancelOperation(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelOperation(id) {
|
||||||
|
const request = wasmRequests.get(id);
|
||||||
|
if (!request) return;
|
||||||
|
request.canceled = true;
|
||||||
|
if (request.preparing) {
|
||||||
|
wasmRequests.delete(id);
|
||||||
|
request.reject(new DOMException("操作已取消", "AbortError"));
|
||||||
|
} else request.tab.worker?.postMessage({type:"cancel", id});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function prepareOperation(id, request) {
|
||||||
|
request.preparing = true;
|
||||||
|
request.cancellable = true;
|
||||||
|
const tab = request.tab;
|
||||||
|
if (!request.name.includes("Print")) {
|
||||||
|
if (!tab.busy) tab.preparationBusy = true;
|
||||||
|
setBusy(true, tab.progress?.text || "正在准备文档", null, "", tab);
|
||||||
|
if (isForegroundDocument(tab)) {
|
||||||
|
el.cancelExportButton.hidden = false;
|
||||||
|
el.cancelExportButton.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
operationProgress(request);
|
||||||
|
try {
|
||||||
|
await completePDF(tab);
|
||||||
|
if (wasmRequests.get(id) !== request || request.canceled || tab.closed) return;
|
||||||
|
request.preparing = false;
|
||||||
|
request.send();
|
||||||
|
} catch (err) {
|
||||||
|
if (wasmRequests.get(id) !== request) return;
|
||||||
|
wasmRequests.delete(id);
|
||||||
|
request.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function callTabWASM(tab, name, ...args) {
|
async function callTabWASM(tab, name, ...args) {
|
||||||
const state = tab.state;
|
const state = tab.state;
|
||||||
const visible = isForegroundDocument(tab);
|
const visible = isForegroundDocument(tab);
|
||||||
@@ -12369,7 +12442,10 @@ async function callTabWASM(tab, name, ...args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (name === "ofdgoResources" && workspace.active === tab && resourceElements.Panel.open) resourceManager.requestID = id;
|
if (name === "ofdgoResources" && workspace.active === tab && resourceElements.Panel.open) resourceManager.requestID = id;
|
||||||
wasmRequests.set(id, { name, resolve, reject, openSeq: state.openSeq, tab });
|
const request = {name, openSeq:state.openSeq, tab, id, cancellable:name === "ofdgoEditDocument",
|
||||||
|
resolve:value => { finishOperation(request); resolve(value); }, reject:err => { finishOperation(request); reject(err); }};
|
||||||
|
wasmRequests.set(id, request);
|
||||||
|
if (request.cancellable && visible) { el.cancelExportButton.hidden = false; el.cancelExportButton.disabled = false; }
|
||||||
if (name === "ofdgoExportMedia") wasmRequests.get(id).mediaID = args[0];
|
if (name === "ofdgoExportMedia") wasmRequests.get(id).mediaID = args[0];
|
||||||
if (name === "ofdgoOpenPDF" || state.sourcePDF && !state.editorInfo) {
|
if (name === "ofdgoOpenPDF" || state.sourcePDF && !state.editorInfo) {
|
||||||
const request = wasmRequests.get(id);
|
const request = wasmRequests.get(id);
|
||||||
@@ -12394,10 +12470,11 @@ async function callTabWASM(tab, name, ...args) {
|
|||||||
const message = {id, name, args};
|
const message = {id, name, args};
|
||||||
if (name === "ofdgoLoadPDF") message.background = true;
|
if (name === "ofdgoLoadPDF") message.background = true;
|
||||||
if (name === "ofdgoRenderPage" && [...state.pageInFlight.values()].some(task => task.index === args[0] && pageView.pageRenderPriority(task, state) >= 6)) message.preload = true;
|
if (name === "ofdgoRenderPage" && [...state.pageInFlight.values()].some(task => task.index === args[0] && pageView.pageRenderPriority(task, state) >= 6)) message.preload = true;
|
||||||
tab.worker.postMessage(message, transfer);
|
request.send = () => tab.worker.postMessage(message, transfer);
|
||||||
|
request.send();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
wasmRequests.delete(id);
|
wasmRequests.delete(id);
|
||||||
reject(err);
|
request.reject(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -12411,7 +12488,7 @@ function showError(err, empty = !state.doc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setBusy(busy, text = "", percent = 0, status = "", tab = documentTab()) {
|
function setBusy(busy, text = "", percent = 0, status = "", tab = documentTab()) {
|
||||||
busy ||= Boolean(tab.editTask);
|
busy ||= Boolean(tab.editTask || operationRequest(tab));
|
||||||
tab.busy = busy;
|
tab.busy = busy;
|
||||||
if (text) tab.progress = {text, percent};
|
if (text) tab.progress = {text, percent};
|
||||||
if (status) tab.status = status;
|
if (status) tab.status = status;
|
||||||
@@ -12468,6 +12545,7 @@ function setBusy(busy, text = "", percent = 0, status = "", tab = documentTab())
|
|||||||
pageView.endPan();
|
pageView.endPan();
|
||||||
el.progressPanel.hidden = !text;
|
el.progressPanel.hidden = !text;
|
||||||
setProgress(text, percent, status);
|
setProgress(text, percent, status);
|
||||||
|
if (operationRequest(tab)) { el.cancelExportButton.hidden = false; el.cancelExportButton.disabled = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProgress(text = "", percent = 0, status = "", tab = documentTab()) {
|
function setProgress(text = "", percent = 0, status = "", tab = documentTab()) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ self.onmessage = ({ data }) => {
|
|||||||
if (engine.closed) return;
|
if (engine.closed) return;
|
||||||
const opened = JSON.parse(globalThis.ofdgoEngineOpen(engineID));
|
const opened = JSON.parse(globalThis.ofdgoEngineOpen(engineID));
|
||||||
if (!opened.ok) throw new Error(opened.error);
|
if (!opened.ok) throw new Error(opened.error);
|
||||||
const backends = JSON.parse(await globalThis.ofdgoEngineCall(engineID, "ofdgoRenderBackends"));
|
const backends = JSON.parse(await globalThis.ofdgoEngineCall(engineID, "ofdgoRenderBackends", null));
|
||||||
if (!backends.ok) throw new Error(backends.error);
|
if (!backends.ok) throw new Error(backends.error);
|
||||||
if (!engine.closed) self.postMessage({ engineID, type: "ready", backends: backends.data, memory: engineMemory.buffer.byteLength });
|
if (!engine.closed) self.postMessage({ engineID, type: "ready", backends: backends.data, memory: engineMemory.buffer.byteLength });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -141,7 +141,7 @@ async function handleBatchMessage(engine, { id, name, args }) {
|
|||||||
if (name === "ofdgoConvertFile") {
|
if (name === "ofdgoConvertFile") {
|
||||||
const [file, options, fonts, destination] = args;
|
const [file, options, fonts, destination] = args;
|
||||||
credentials = options.credentials;
|
credentials = options.credentials;
|
||||||
const formats = JSON.parse(await globalThis.ofdgoEngineCall(engine.id, "ofdgoOutputFormats")).data;
|
const formats = JSON.parse(await globalThis.ofdgoEngineCall(engine.id, "ofdgoOutputFormats", null)).data;
|
||||||
const format = formats.find(item => item.value === options.format);
|
const format = formats.find(item => item.value === options.format);
|
||||||
if (!format) throw new Error("不支持此格式");
|
if (!format) throw new Error("不支持此格式");
|
||||||
root = destination;
|
root = destination;
|
||||||
@@ -178,7 +178,7 @@ async function handleBatchMessage(engine, { id, name, args }) {
|
|||||||
chunks = [];
|
chunks = [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
payload = await globalThis.ofdgoEngineCall(engine.id, "ofdgoConvertFile", read(file), file.size, options, write,
|
payload = await globalThis.ofdgoEngineCall(engine.id, "ofdgoConvertFile", done => finish(done), read(file), file.size, options, write,
|
||||||
(phase, completed, total, done) => {
|
(phase, completed, total, done) => {
|
||||||
postMessage({ id, type: "conversion", phase, completed, total, memory: engineMemory.buffer.byteLength });
|
postMessage({ id, type: "conversion", phase, completed, total, memory: engineMemory.buffer.byteLength });
|
||||||
finish(done);
|
finish(done);
|
||||||
@@ -188,7 +188,7 @@ async function handleBatchMessage(engine, { id, name, args }) {
|
|||||||
} else {
|
} else {
|
||||||
const [entries, handle, compression] = args;
|
const [entries, handle, compression] = args;
|
||||||
if (handle) output = await handle.createWritable();
|
if (handle) output = await handle.createWritable();
|
||||||
payload = await globalThis.ofdgoEngineCall(engine.id, "ofdgoPackFiles", entries.map(entry => ({ name: entry.name, size: entry.file.size })),
|
payload = await globalThis.ofdgoEngineCall(engine.id, "ofdgoPackFiles", done => finish(done), entries.map(entry => ({ name: entry.name, size: entry.file.size })),
|
||||||
index => read(entries[index].file), write,
|
index => read(entries[index].file), write,
|
||||||
(completed, total, done) => { postMessage({ id, type: "conversion", phase: "pack", completed, total, memory: engineMemory.buffer.byteLength }); finish(done); },
|
(completed, total, done) => { postMessage({ id, type: "conversion", phase: "pack", completed, total, memory: engineMemory.buffer.byteLength }); finish(done); },
|
||||||
done => finish(done), compression);
|
done => finish(done), compression);
|
||||||
@@ -240,6 +240,10 @@ async function handleMessage(engine, { id, name, args }) {
|
|||||||
const converting = name === "ofdgoOpenPDF";
|
const converting = name === "ofdgoOpenPDF";
|
||||||
const saving = name === "ofdgoSaveDocument" || name === "ofdgoSaveSnapshot" || name === "ofdgoSaveSigned";
|
const saving = name === "ofdgoSaveDocument" || name === "ofdgoSaveSnapshot" || name === "ofdgoSaveSigned";
|
||||||
const resources = ["ofdgoPreparePrint", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF", "ofdgoResources", "ofdgoPrepareSave"].includes(name);
|
const resources = ["ofdgoPreparePrint", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF", "ofdgoResources", "ofdgoPrepareSave"].includes(name);
|
||||||
|
if (engine.pdf) {
|
||||||
|
const checked = JSON.parse(await globalThis.ofdgoEngineCall(engine.id, "ofdgoCheckDocument", null, name));
|
||||||
|
if (!checked.ok) { postMessage({id, ...checked}); operations.delete(id); return; }
|
||||||
|
}
|
||||||
const key = name === "ofdgoOpen" ? args[3]?.key : name === "ofdgoSaveSigned" || name === "ofdgoLoadImport" ? args[1]?.key : null;
|
const key = name === "ofdgoOpen" ? args[3]?.key : name === "ofdgoSaveSigned" || name === "ofdgoLoadImport" ? args[1]?.key : null;
|
||||||
const keyPassword = name === "ofdgoOpen" ? args[3]?.keyPassword : name === "ofdgoSaveSigned" || name === "ofdgoLoadImport" ? args[1]?.keyPassword : null;
|
const keyPassword = name === "ofdgoOpen" ? args[3]?.keyPassword : name === "ofdgoSaveSigned" || name === "ofdgoLoadImport" ? args[1]?.keyPassword : null;
|
||||||
const password = converting ? args[2] : name === "ofdgoSetEncryption" ? args[0]?.password : name === "ofdgoOpen" ? args[3]?.password : name === "ofdgoLoadImport" ? args[1]?.password : null;
|
const password = converting ? args[2] : name === "ofdgoSetEncryption" ? args[0]?.password : name === "ofdgoOpen" ? args[3]?.password : name === "ofdgoLoadImport" ? args[1]?.password : null;
|
||||||
@@ -339,7 +343,7 @@ async function handleMessage(engine, { id, name, args }) {
|
|||||||
}
|
}
|
||||||
if (engine.pdf && name !== "ofdgoOpenPDF") {
|
if (engine.pdf && name !== "ofdgoOpenPDF") {
|
||||||
channel ||= new MessageChannel();
|
channel ||= new MessageChannel();
|
||||||
const context = await globalThis.ofdgoEngineCall(engine.id, "ofdgoPDFContext", (phase, completed, total, done) => {
|
const context = await globalThis.ofdgoEngineCall(engine.id, "ofdgoPDFContext", null, (phase, completed, total, done) => {
|
||||||
postMessage({id, type: "conversion", phase, completed, total});
|
postMessage({id, type: "conversion", phase, completed, total});
|
||||||
channel.port1.onmessage = () => done("", signal.aborted);
|
channel.port1.onmessage = () => done("", signal.aborted);
|
||||||
channel.port2.postMessage(null);
|
channel.port2.postMessage(null);
|
||||||
@@ -347,7 +351,11 @@ async function handleMessage(engine, { id, name, args }) {
|
|||||||
engine.pdf = JSON.parse(context).data === true;
|
engine.pdf = JSON.parse(context).data === true;
|
||||||
}
|
}
|
||||||
const started = name === "ofdgoOpen" || name === "ofdgoRenderPage" ? performance.now() : null;
|
const started = name === "ofdgoOpen" || name === "ofdgoRenderPage" ? performance.now() : null;
|
||||||
const payload = await globalThis.ofdgoEngineCall(engine.id, name, ...args);
|
const payload = await globalThis.ofdgoEngineCall(engine.id, name, done => {
|
||||||
|
channel ||= new MessageChannel();
|
||||||
|
channel.port1.onmessage = () => done("", signal.aborted);
|
||||||
|
channel.port2.postMessage(null);
|
||||||
|
}, ...args);
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
const result = typeof payload === "string" ? JSON.parse(payload) : payload;
|
const result = typeof payload === "string" ? JSON.parse(payload) : payload;
|
||||||
if (result.ok && name === "ofdgoOpenPDF") engine.pdf = true;
|
if (result.ok && name === "ofdgoOpenPDF") engine.pdf = true;
|
||||||
|
|||||||
+45
-10
@@ -1591,14 +1591,31 @@ func (e *businessEngine) loadPDF(args []platformValue) (any, error) {
|
|||||||
return map[string]any{"done": true, "doc": e.currentSession.Summary(), "warnings": string(warnings)}, nil
|
return map[string]any{"done": true, "doc": e.currentSession.Summary(), "warnings": string(warnings)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// preparePDF 按命令准备页面快照或完整文档,不将未完成内容交给编辑和输出
|
// checkDocument 检查命令所需内容,不隐式转换或产生输出
|
||||||
|
// 入参: name 命令名称
|
||||||
|
// 返回: error 文档尚未完整时返回ErrIncompleteDocument
|
||||||
|
func (e *businessEngine) checkDocument(name string) error {
|
||||||
|
if e.pdf == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch name {
|
||||||
|
case "ofdgoCheckDocument", "ofdgoPDFContext", "ofdgoOpenPDF", "ofdgoOpen", "ofdgoLoadPDF", "ofdgoConfigure", "ofdgoDocumentInfo", "ofdgoSVGFontData", "ofdgoRenderBackends", "ofdgoOutputFormats", "ofdgoExportFormats", "ofdgoParsePageRange", "ofdgoMatchFontFiles", "ofdgoOutlineLinks", "ofdgoDocumentNavigation", "ofdgoInvoices", "ofdgoRenderPage", "ofdgoSearchPage", "ofdgoPageTextString", "ofdgoOpenLinks", "ofdgoDiscardPrint", "ofdgoDiscardSave", "ofdgoDetectFormat", "ofdgoConvertFile", "ofdgoPackFiles", "ofdgoLoadImport", "ofdgoCreateDocument", "ofdgoLayoutPrint", "ofdgoPrintSide", "ofdgoPrintPDF":
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ofdgo.ErrIncompleteDocument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// preparePDF 只为页面读取和后台加载绑定转换上下文,不补全其他命令
|
||||||
// 入参: name 命令名称, args 命令参数
|
// 入参: name 命令名称, args 命令参数
|
||||||
// 返回: error 转换或取消错误
|
// 返回: error 转换或取消错误
|
||||||
func (e *businessEngine) preparePDF(name string, args []platformValue) error {
|
func (e *businessEngine) preparePDF(name string, args []platformValue) error {
|
||||||
if e.pdf == nil {
|
if e.pdf == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if name == "ofdgoPDFContext" || name == "ofdgoOpenPDF" || name == "ofdgoOpen" {
|
switch name {
|
||||||
|
case "ofdgoLoadPDF", "ofdgoRenderPage", "ofdgoSearchPage", "ofdgoPageTextString", "ofdgoOpenLinks":
|
||||||
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
e.pdf.operation, e.pdf.report = e.newEditorOperationContext(e.pdf.progress)
|
e.pdf.operation, e.pdf.report = e.newEditorOperationContext(e.pdf.progress)
|
||||||
@@ -1619,7 +1636,7 @@ func (e *businessEngine) preparePDF(name string, args []platformValue) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch name {
|
switch name {
|
||||||
case "ofdgoLoadPDF", "ofdgoConfigure", "ofdgoDocumentInfo", "ofdgoSVGFontData", "ofdgoRenderBackends", "ofdgoOutputFormats", "ofdgoExportFormats", "ofdgoParsePageRange", "ofdgoMatchFontFiles", "ofdgoOutlineLinks", "ofdgoDocumentNavigation", "ofdgoInvoices":
|
case "ofdgoLoadPDF":
|
||||||
return nil
|
return nil
|
||||||
case "ofdgoOpenLinks":
|
case "ofdgoOpenLinks":
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
@@ -1644,8 +1661,7 @@ func (e *businessEngine) preparePDF(name string, args []platformValue) error {
|
|||||||
e.pdf.page = index
|
e.pdf.page = index
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
_, err := e.finishPDF()
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5950,7 +5966,11 @@ func (e *businessEngine) createDocument(args []platformValue) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
editor.SetHistoryLimit(100)
|
editor.SetHistoryLimit(100)
|
||||||
return e.previewEditor(editor, args[3].Bool())
|
info, err := e.previewEditor(editor, args[3].Bool())
|
||||||
|
if err == nil {
|
||||||
|
e.clearPDF()
|
||||||
|
}
|
||||||
|
return info, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateInfo 修改完整文档描述,按字段来源保留自定义内容的扩展属性
|
// updateInfo 修改完整文档描述,按字段来源保留自定义内容的扩展属性
|
||||||
@@ -6044,7 +6064,7 @@ func (e *businessEngine) documentVersions(args []platformValue) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
versions, err := reader.DocumentVersions()
|
versions, err := reader.DocumentVersionsContext(e.operationContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -6635,7 +6655,8 @@ func (e *businessEngine) editDocument(args []platformValue) (any, error) {
|
|||||||
e.currentSession.editing = true
|
e.currentSession.editing = true
|
||||||
return e.editorSummary(), nil
|
return e.editorSummary(), nil
|
||||||
}
|
}
|
||||||
editor, err := e.currentSession.Reader.Editor()
|
ctx := e.operationContext()
|
||||||
|
editor, err := e.currentSession.Reader.EditorContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -6648,13 +6669,19 @@ func (e *businessEngine) editDocument(args []platformValue) (any, error) {
|
|||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
page = args[0].Int()
|
page = args[0].Int()
|
||||||
}
|
}
|
||||||
content, err := editor.Page(page)
|
content, err := editor.PageContext(ctx, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := e.syncEditorPageIDs(page, content); err != nil {
|
if err := e.syncEditorPageIDs(page, content); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
e.currentEditor = editor
|
e.currentEditor = editor
|
||||||
e.currentSession.editor = editor
|
e.currentSession.editor = editor
|
||||||
e.currentSession.editing = true
|
e.currentSession.editing = true
|
||||||
@@ -7557,7 +7584,7 @@ func (e *businessEngine) saveDocument(args []platformValue) (any, error) {
|
|||||||
if e.currentSession == nil {
|
if e.currentSession == nil {
|
||||||
return nil, fmt.Errorf("ofd document is not opened")
|
return nil, fmt.Errorf("ofd document is not opened")
|
||||||
}
|
}
|
||||||
editor, err := e.currentSession.Reader.Editor()
|
editor, err := e.currentSession.Reader.EditorContext(e.operationContext())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -7662,6 +7689,12 @@ func (c *outputContext) Err() error {
|
|||||||
// businessCommands 共用业务命令表
|
// businessCommands 共用业务命令表
|
||||||
func (e *businessEngine) businessCommands() map[string]func([]platformValue) (any, error) {
|
func (e *businessEngine) businessCommands() map[string]func([]platformValue) (any, error) {
|
||||||
return map[string]func([]platformValue) (any, error){
|
return map[string]func([]platformValue) (any, error){
|
||||||
|
"ofdgoCheckDocument": func(args []platformValue) (any, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, fmt.Errorf("missing operation name")
|
||||||
|
}
|
||||||
|
return nil, e.checkDocument(args[0].String())
|
||||||
|
},
|
||||||
"ofdgoPreparePrint": e.preparePrint,
|
"ofdgoPreparePrint": e.preparePrint,
|
||||||
"ofdgoLayoutPrint": e.layoutPrint,
|
"ofdgoLayoutPrint": e.layoutPrint,
|
||||||
"ofdgoPrintSide": e.printSide,
|
"ofdgoPrintSide": e.printSide,
|
||||||
@@ -7818,6 +7851,8 @@ func (e *businessEngine) operationContext() context.Context {
|
|||||||
func failureResult(err error) apiResult {
|
func failureResult(err error) apiResult {
|
||||||
result := apiResult{Error: err.Error()}
|
result := apiResult{Error: err.Error()}
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, ofdgo.ErrIncompleteDocument):
|
||||||
|
result.Code, result.Error = "documentPending", "文档尚未准备完成"
|
||||||
case errors.Is(err, ofdgo.ErrPrintDenied):
|
case errors.Is(err, ofdgo.ErrPrintDenied):
|
||||||
result.Code, result.Error = "printDenied", "文档禁止打印"
|
result.Code, result.Error = "printDenied", "文档禁止打印"
|
||||||
case errors.Is(err, ofdgo.ErrPDFPassword):
|
case errors.Is(err, ofdgo.ErrPDFPassword):
|
||||||
|
|||||||
@@ -311,6 +311,9 @@ func (e *NativeEngine) Call(ctx context.Context, name string, args []any, emit f
|
|||||||
if fn == nil {
|
if fn == nil {
|
||||||
return nativeFailure(fmt.Errorf("unknown operation: %s", name))
|
return nativeFailure(fmt.Errorf("unknown operation: %s", name))
|
||||||
}
|
}
|
||||||
|
if err := e.checkDocument(name); err != nil {
|
||||||
|
return nativeFailure(err)
|
||||||
|
}
|
||||||
call := &nativeCall{engine: e, ctx: ctx, emit: emit}
|
call := &nativeCall{engine: e, ctx: ctx, emit: emit}
|
||||||
if e.pdf != nil {
|
if e.pdf != nil {
|
||||||
e.pdf.progress = platformValueOf(call.progress("conversion", "", true))
|
e.pdf.progress = platformValueOf(call.progress("conversion", "", true))
|
||||||
|
|||||||
@@ -68,32 +68,48 @@ func (w *wasmEngines) open(args []js.Value) (any, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// call 按会话标识分派业务命令,不提供默认会话
|
// call 按会话标识分派业务命令,统一绑定本次调用的取消上下文
|
||||||
// 入参: args 会话标识、命令及业务参数
|
// 入参: args 会话标识、命令、取消检查点及业务参数
|
||||||
// 返回: any 业务结果, error 调用错误
|
// 返回: any 业务结果, error 调用错误
|
||||||
func (w *wasmEngines) call(args []js.Value) (any, error) {
|
func (w *wasmEngines) call(args []js.Value) (any, error) {
|
||||||
if len(args) < 2 || args[0].Type() != js.TypeString || args[1].Type() != js.TypeString {
|
if len(args) < 3 || args[0].Type() != js.TypeString || args[1].Type() != js.TypeString {
|
||||||
return nil, fmt.Errorf("missing engine ID or operation")
|
return nil, fmt.Errorf("missing engine ID or operation")
|
||||||
}
|
}
|
||||||
e := w.engines[args[0].String()]
|
e := w.engines[args[0].String()]
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil, fmt.Errorf("unknown engine: %s", args[0].String())
|
return nil, fmt.Errorf("unknown engine: %s", args[0].String())
|
||||||
}
|
}
|
||||||
if e.pdf != nil {
|
name := args[1].String()
|
||||||
defer func() {
|
fn := e.businessCommands()[name]
|
||||||
if e.currentSession != nil {
|
|
||||||
e.currentSession.Renderer.OutputContext = context.Background()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
fn := e.businessCommands()[args[1].String()]
|
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
return nil, fmt.Errorf("unknown operation: %s", args[1].String())
|
return nil, fmt.Errorf("unknown operation: %s", name)
|
||||||
}
|
}
|
||||||
if err := e.preparePDF(args[1].String(), args[2:]); err != nil {
|
if err := e.checkDocument(name); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return fn(args[2:])
|
e.ctx = context.Background()
|
||||||
|
if args[2].Type() == js.TypeFunction {
|
||||||
|
e.ctx = e.newOutputContext(args[2])
|
||||||
|
}
|
||||||
|
ctx := e.ctx
|
||||||
|
if name != "ofdgoSaveSnapshot" && e.currentSession != nil {
|
||||||
|
e.currentSession.Renderer.OutputContext = ctx
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if e.ctx == ctx {
|
||||||
|
e.ctx = nil
|
||||||
|
}
|
||||||
|
if name != "ofdgoSaveSnapshot" && e.currentSession != nil {
|
||||||
|
e.currentSession.Renderer.OutputContext = context.Background()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := e.preparePDF(name, args[3:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fn(args[3:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// close 释放会话的文档、编辑器、导入及剪贴板,不关闭共享资源
|
// close 释放会话的文档、编辑器、导入及剪贴板,不关闭共享资源
|
||||||
|
|||||||
+22
-2
@@ -223,7 +223,14 @@ func (e *Editor) SetInfo(info DocInfo) {
|
|||||||
// 入参: index 页面索引
|
// 入参: index 页面索引
|
||||||
// 返回: *PageContent 页面内容, error 错误信息
|
// 返回: *PageContent 页面内容, error 错误信息
|
||||||
func (e *Editor) Page(index int) (*PageContent, error) {
|
func (e *Editor) Page(index int) (*PageContent, error) {
|
||||||
page, err := e.page(index)
|
return e.PageContext(context.Background(), index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageContext 获取独立页面副本,读取、解析及整理对象时响应取消
|
||||||
|
// 入参: ctx 取消上下文, index 页面索引
|
||||||
|
// 返回: *PageContent 页面内容, error 读取或取消错误
|
||||||
|
func (e *Editor) PageContext(ctx context.Context, index int) (*PageContent, error) {
|
||||||
|
page, err := e.pageContext(ctx, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -233,6 +240,9 @@ func (e *Editor) Page(index int) (*PageContent, error) {
|
|||||||
layer := &result.Content.Layer[i]
|
layer := &result.Content.Layer[i]
|
||||||
layer.TextObject, layer.PathObject, layer.ImageObject, layer.CompositeGraphicUnit = nil, nil, nil, nil
|
layer.TextObject, layer.PathObject, layer.ImageObject, layer.CompositeGraphicUnit = nil, nil, nil, nil
|
||||||
for _, object := range layer.Objects {
|
for _, object := range layer.Objects {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
switch object.Type {
|
switch object.Type {
|
||||||
case "TextObject":
|
case "TextObject":
|
||||||
object.TextObject.layout = nil
|
object.TextObject.layout = nil
|
||||||
@@ -820,11 +830,21 @@ func (r editorResource) definition() string {
|
|||||||
// 入参: index 页面索引
|
// 入参: index 页面索引
|
||||||
// 返回: *PageContent 页面内容, error 错误信息
|
// 返回: *PageContent 页面内容, error 错误信息
|
||||||
func (e *Editor) page(index int) (*PageContent, error) {
|
func (e *Editor) page(index int) (*PageContent, error) {
|
||||||
|
return e.pageContext(context.Background(), index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pageContext 按索引加载内部页面并响应取消
|
||||||
|
// 入参: ctx 取消上下文, index 页面索引
|
||||||
|
// 返回: *PageContent 页面内容, error 读取或取消错误
|
||||||
|
func (e *Editor) pageContext(ctx context.Context, index int) (*PageContent, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if index < 0 || index >= len(e.pages) {
|
if index < 0 || index >= len(e.pages) {
|
||||||
return nil, fmt.Errorf("page index %d out of range", index)
|
return nil, fmt.Errorf("page index %d out of range", index)
|
||||||
}
|
}
|
||||||
if e.source != nil {
|
if e.source != nil {
|
||||||
if err := e.loadSourcePage(index); err != nil {
|
if err := e.loadSourcePageContext(ctx, index); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-12
@@ -397,15 +397,18 @@ func (e *Editor) sourceRGB() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// repairSourceObjectIDs 按实际节点区分重复对象,只修复没有引用歧义的编号
|
// repairSourceObjectIDs 按实际节点区分重复对象,只修复没有引用歧义的编号
|
||||||
// 入参: data 页面原文, root 页面节点
|
// 入参: ctx 取消上下文, data 页面原文, root 页面节点
|
||||||
// 返回: []byte 编辑副本, bool 是否修复, error 错误信息
|
// 返回: []byte 编辑副本, bool 是否修复, error 错误信息
|
||||||
func (e *Editor) repairSourceObjectIDs(data []byte, root *editorXML) ([]byte, bool, error) {
|
func (e *Editor) repairSourceObjectIDs(ctx context.Context, data []byte, root *editorXML) ([]byte, bool, error) {
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
duplicates := make(map[string]bool)
|
duplicates := make(map[string]bool)
|
||||||
var nodes []*editorXML
|
var nodes []*editorXML
|
||||||
var visit func(*editorXML) error
|
var visit func(*editorXML) error
|
||||||
visit = func(parent *editorXML) error {
|
visit = func(parent *editorXML) error {
|
||||||
for _, node := range parent.children {
|
for _, node := range parent.children {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !ofdXMLChild(parent.name, node.name) {
|
if !ofdXMLChild(parent.name, node.name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -440,15 +443,18 @@ func (e *Editor) repairSourceObjectIDs(data []byte, root *editorXML) ([]byte, bo
|
|||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
return data, false, nil
|
return data, false, nil
|
||||||
}
|
}
|
||||||
if err := e.checkDuplicateObjectReferences(duplicates); err != nil {
|
if err := e.checkDuplicateObjectReferences(ctx, duplicates); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
if err := e.prepareSourceIDs(); err != nil {
|
if err := e.prepareSourceIDsContext(ctx); err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
maximum := e.maxID
|
maximum := e.maxID
|
||||||
var patches []editorXMLPatch
|
var patches []editorXMLPatch
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
maximum++
|
maximum++
|
||||||
encoded, err := editorXMLAttribute(data, node, "ID", strconv.Itoa(maximum))
|
encoded, err := editorXMLAttribute(data, node, "ID", strconv.Itoa(maximum))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -461,9 +467,9 @@ func (e *Editor) repairSourceObjectIDs(data []byte, root *editorXML) ([]byte, bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkDuplicateObjectReferences 检查编号引用的目标歧义
|
// checkDuplicateObjectReferences 检查编号引用的目标歧义
|
||||||
// 入参: duplicates 重复编号
|
// 入参: ctx 取消上下文, duplicates 重复编号
|
||||||
// 返回: error 引用歧义或读取错误
|
// 返回: error 引用歧义或读取错误
|
||||||
func (e *Editor) checkDuplicateObjectReferences(duplicates map[string]bool) error {
|
func (e *Editor) checkDuplicateObjectReferences(ctx context.Context, duplicates map[string]bool) error {
|
||||||
reader := e.source.reader
|
reader := e.source.reader
|
||||||
for name := range reader.fileNames {
|
for name := range reader.fileNames {
|
||||||
if !strings.EqualFold(path.Ext(name), ".xml") {
|
if !strings.EqualFold(path.Ext(name), ".xml") {
|
||||||
@@ -475,6 +481,10 @@ func (e *Editor) checkDuplicateObjectReferences(duplicates map[string]bool) erro
|
|||||||
}
|
}
|
||||||
decoder := xml.NewDecoder(input)
|
decoder := xml.NewDecoder(input)
|
||||||
for {
|
for {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
input.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
token, readErr := decoder.Token()
|
token, readErr := decoder.Token()
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
input.Close()
|
input.Close()
|
||||||
@@ -500,6 +510,16 @@ func (e *Editor) checkDuplicateObjectReferences(duplicates map[string]bool) erro
|
|||||||
// 入参: index 页面索引
|
// 入参: index 页面索引
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (e *Editor) loadSourcePage(index int) error {
|
func (e *Editor) loadSourcePage(index int) error {
|
||||||
|
return e.loadSourcePageContext(context.Background(), index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadSourcePageContext 解析原始页面,取消时不提交未完成页面
|
||||||
|
// 入参: ctx 取消上下文, index 页面索引
|
||||||
|
// 返回: error 读取、解析或取消错误
|
||||||
|
func (e *Editor) loadSourcePageContext(ctx context.Context, index int) error {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
source := e.source.pages[e.pages[index].ID]
|
source := e.source.pages[e.pages[index].ID]
|
||||||
if source == nil || source.original != nil {
|
if source == nil || source.original != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -507,24 +527,24 @@ func (e *Editor) loadSourcePage(index int) error {
|
|||||||
loaded := *source
|
loaded := *source
|
||||||
source = &loaded
|
source = &loaded
|
||||||
reader := e.source.reader
|
reader := e.source.reader
|
||||||
data, err := reader.readFile(reader.ResPath(source.ref.BaseLoc))
|
data, err := reader.readFileViewContext(ctx, reader.ResPath(source.ref.BaseLoc))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
root, err := parseEditorXML(data)
|
root, err := parseEditorXMLContext(ctx, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
page, err := reader.PageContent(source.ref)
|
page, err := reader.PageContentContext(ctx, source.ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
data, repaired, err := e.repairSourceObjectIDs(data, root)
|
data, repaired, err := e.repairSourceObjectIDs(ctx, data, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if repaired {
|
if repaired {
|
||||||
root, err = parseEditorXML(data)
|
root, err = parseEditorXMLContext(ctx, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -547,13 +567,17 @@ func (e *Editor) loadSourcePage(index int) error {
|
|||||||
}
|
}
|
||||||
source.data, source.root, source.nodes = data, root, nodes
|
source.data, source.root, source.nodes = data, root, nodes
|
||||||
source.repaired = repaired
|
source.repaired = repaired
|
||||||
|
origins := make(map[string]*editorObjectOrigin)
|
||||||
for i := range page.Content.Layer {
|
for i := range page.Content.Layer {
|
||||||
layer := &page.Content.Layer[i]
|
layer := &page.Content.Layer[i]
|
||||||
for j, object := range layer.Objects {
|
for j, object := range layer.Objects {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
id := editorObjectID(object)
|
id := editorObjectID(object)
|
||||||
if node := nodes[id]; node != nil {
|
if node := nodes[id]; node != nil {
|
||||||
origin := &editorObjectOrigin{page: source, data: data, node: node, object: object}
|
origin := &editorObjectOrigin{page: source, data: data, node: node, object: object}
|
||||||
e.source.origins[id] = origin
|
origins[id] = origin
|
||||||
if editorXMLTransformable(node) {
|
if editorXMLTransformable(node) {
|
||||||
resolved, err := e.resolveEditorStyle(object, layer.DrawParam)
|
resolved, err := e.resolveEditorStyle(object, layer.DrawParam)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -565,6 +589,10 @@ func (e *Editor) loadSourcePage(index int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
maps.Copy(e.source.origins, origins)
|
||||||
source.original = page
|
source.original = page
|
||||||
e.source.pages[source.ref.ID] = source
|
e.source.pages[source.ref.ID] = source
|
||||||
e.pages[index] = copyEditorPage(*page)
|
e.pages[index] = copyEditorPage(*page)
|
||||||
|
|||||||
Reference in new issue
Block a user