mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:59:27 +08:00
1 parent
d56a84f92a
commit
b0b194895a
8 files changed
+263
-109
No files matched your search
+13
-8
@@ -489,16 +489,21 @@
|
|||||||
<div class="form-actions"><div id="credentialsStatus" class="form-status" role="status" aria-live="polite"></div><button id="credentialsCancel" class="button" type="button">取消</button><button class="button" type="submit">解锁</button></div>
|
<div class="form-actions"><div id="credentialsStatus" class="form-status" role="status" aria-live="polite"></div><button id="credentialsCancel" class="button" type="button">取消</button><button class="button" type="submit">解锁</button></div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
<dialog id="encryptionPanel" class="form-dialog" aria-labelledby="encryptionTitle">
|
||||||
|
<form id="encryptionForm">
|
||||||
|
<h2 id="encryptionTitle" class="form-title">加密设置</h2>
|
||||||
|
<div class="form-row"><label for="encryptionType">方式</label><select id="encryptionType" autofocus><option value="none">明文</option><option value="password">口令</option><option value="certificate">证书</option><option id="encryptionKeep" value="keep" hidden>保留</option></select></div>
|
||||||
|
<div id="encryptionCurrentRow" class="form-row" hidden><span>状态</span><div class="encryption-current"><span id="encryptionCurrent"></span><button id="encryptionChange" class="button" type="button">修改</button></div></div>
|
||||||
|
<div id="encryptionUserRow" class="form-row"><label for="encryptionUser">用户</label><input id="encryptionUser" type="text" autocomplete="off" spellcheck="false"></div>
|
||||||
|
<div id="encryptionPasswordRow" class="form-row"><label for="encryptionPassword">口令</label><input id="encryptionPassword" type="password" autocomplete="new-password"></div>
|
||||||
|
<div id="encryptionConfirmRow" class="form-row"><label for="encryptionConfirm">确认</label><input id="encryptionConfirm" type="password" autocomplete="new-password"></div>
|
||||||
|
<div id="encryptionCertificatesRow" class="form-row" hidden><label for="encryptionCertificates">证书</label><input id="encryptionCertificates" type="file" accept=".pem,.cer,.crt,.der" multiple></div>
|
||||||
|
<div class="form-actions"><div id="encryptionStatus" class="form-status" role="status" aria-live="polite"></div><button id="encryptionCancel" class="button" type="button">取消</button><button id="encryptionSubmit" class="button" type="submit">确定</button></div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
<dialog id="exportPanel" class="form-dialog" aria-labelledby="exportTitle">
|
<dialog id="exportPanel" class="form-dialog" aria-labelledby="exportTitle">
|
||||||
<form id="exportForm">
|
<form id="exportForm">
|
||||||
<h2 id="exportTitle" class="form-title">导出文档</h2>
|
<h2 id="exportTitle" class="form-title">导出文档</h2>
|
||||||
<div id="encryptionFields" hidden>
|
|
||||||
<div class="form-row"><label for="encryptionType">方式</label><select id="encryptionType"><option value="password">口令</option><option value="certificate">证书</option></select></div>
|
|
||||||
<div id="encryptionUserRow" class="form-row"><label for="encryptionUser">用户</label><input id="encryptionUser" type="text" autocomplete="off" spellcheck="false"></div>
|
|
||||||
<div id="encryptionPasswordRow" class="form-row"><label for="encryptionPassword">口令</label><input id="encryptionPassword" type="password" autocomplete="new-password"></div>
|
|
||||||
<div id="encryptionConfirmRow" class="form-row"><label for="encryptionConfirm">确认</label><input id="encryptionConfirm" type="password" autocomplete="new-password"></div>
|
|
||||||
<div id="encryptionCertificatesRow" class="form-row" hidden><label for="encryptionCertificates">证书</label><input id="encryptionCertificates" type="file" accept=".pem,.cer,.crt,.der" multiple></div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<span id="exportScopeLabel">范围</span>
|
<span id="exportScopeLabel">范围</span>
|
||||||
<div class="form-options" role="radiogroup" aria-labelledby="exportScopeLabel">
|
<div class="form-options" role="radiogroup" aria-labelledby="exportScopeLabel">
|
||||||
@@ -631,7 +636,7 @@
|
|||||||
<div id="conversionWarnings" class="attachment-detail"></div>
|
<div id="conversionWarnings" class="attachment-detail"></div>
|
||||||
</details>
|
</details>
|
||||||
<section id="securityPanel" class="security-panel" aria-labelledby="securityPanelTitle" hidden>
|
<section id="securityPanel" class="security-panel" aria-labelledby="securityPanelTitle" hidden>
|
||||||
<div class="panel-row"><div id="securityPanelTitle" class="panel-title compact">安全</div><div class="panel-actions"><button id="encryptSaveButton" class="small-button" type="button" title="加密另存" aria-label="加密另存" aria-haspopup="dialog" aria-controls="exportPanel" disabled>加密</button><button id="signButton" class="small-button" type="button" aria-haspopup="dialog" aria-controls="signPanel" disabled>签章</button><button id="verifyButton" class="small-button" type="button" aria-haspopup="dialog" aria-controls="verifyPanel" disabled>验签</button></div></div>
|
<div class="panel-row"><div id="securityPanelTitle" class="panel-title compact">安全</div><div class="panel-actions"><button id="encryptSaveButton" class="small-button" type="button" title="加密设置" aria-label="加密设置" aria-haspopup="dialog" aria-controls="encryptionPanel" disabled>加密</button><button id="signButton" class="small-button" type="button" aria-haspopup="dialog" aria-controls="signPanel" disabled>签章</button><button id="verifyButton" class="small-button" type="button" aria-haspopup="dialog" aria-controls="verifyPanel" disabled>验签</button></div></div>
|
||||||
<dl class="meta-list"><div><dt>加密</dt><dd id="securityEncryption">未加密</dd></div><div id="securityMethodRow" hidden><dt>算法</dt><dd id="securityMethod"></dd></div></dl>
|
<dl class="meta-list"><div><dt>加密</dt><dd id="securityEncryption">未加密</dd></div><div id="securityMethodRow" hidden><dt>算法</dt><dd id="securityMethod"></dd></div></dl>
|
||||||
</section>
|
</section>
|
||||||
<section id="attachmentPanel" class="attachment-panel" aria-labelledby="attachmentPanelTitle" hidden>
|
<section id="attachmentPanel" class="attachment-panel" aria-labelledby="attachmentPanelTitle" hidden>
|
||||||
|
|||||||
@@ -1135,6 +1135,23 @@ button:not(:disabled):hover {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.encryption-current {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: var(--control-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.encryption-current > span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.encryption-current > button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.annotation-placement {
|
.annotation-placement {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
|||||||
+131
-58
@@ -51,6 +51,7 @@ const state = {
|
|||||||
editorInfo: null,
|
editorInfo: null,
|
||||||
editorViews: new Map(),
|
editorViews: new Map(),
|
||||||
savedRevision: null,
|
savedRevision: null,
|
||||||
|
encryptionDirty: false,
|
||||||
insertObject: null,
|
insertObject: null,
|
||||||
importPageCount: 0,
|
importPageCount: 0,
|
||||||
importEncrypted: false,
|
importEncrypted: false,
|
||||||
@@ -118,7 +119,7 @@ const state = {
|
|||||||
exportFormats: [],
|
exportFormats: [],
|
||||||
exportPages: null,
|
exportPages: null,
|
||||||
exportBackdrop: false,
|
exportBackdrop: false,
|
||||||
exportEncrypted: false,
|
encryptionChanged: false,
|
||||||
showPages: !COMPACT_LAYOUT.matches,
|
showPages: !COMPACT_LAYOUT.matches,
|
||||||
showMeta: !COMPACT_LAYOUT.matches,
|
showMeta: !COMPACT_LAYOUT.matches,
|
||||||
};
|
};
|
||||||
@@ -178,7 +179,15 @@ const el = {
|
|||||||
credentialsPassword: document.querySelector("#credentialsPassword"),
|
credentialsPassword: document.querySelector("#credentialsPassword"),
|
||||||
credentialsStatus: document.querySelector("#credentialsStatus"),
|
credentialsStatus: document.querySelector("#credentialsStatus"),
|
||||||
credentialsCancel: document.querySelector("#credentialsCancel"),
|
credentialsCancel: document.querySelector("#credentialsCancel"),
|
||||||
encryptionFields: document.querySelector("#encryptionFields"),
|
encryptionPanel: document.querySelector("#encryptionPanel"),
|
||||||
|
encryptionForm: document.querySelector("#encryptionForm"),
|
||||||
|
encryptionKeep: document.querySelector("#encryptionKeep"),
|
||||||
|
encryptionCurrentRow: document.querySelector("#encryptionCurrentRow"),
|
||||||
|
encryptionCurrent: document.querySelector("#encryptionCurrent"),
|
||||||
|
encryptionChange: document.querySelector("#encryptionChange"),
|
||||||
|
encryptionCancel: document.querySelector("#encryptionCancel"),
|
||||||
|
encryptionSubmit: document.querySelector("#encryptionSubmit"),
|
||||||
|
encryptionStatus: document.querySelector("#encryptionStatus"),
|
||||||
encryptionType: document.querySelector("#encryptionType"),
|
encryptionType: document.querySelector("#encryptionType"),
|
||||||
encryptionUserRow: document.querySelector("#encryptionUserRow"),
|
encryptionUserRow: document.querySelector("#encryptionUserRow"),
|
||||||
encryptionPasswordRow: document.querySelector("#encryptionPasswordRow"),
|
encryptionPasswordRow: document.querySelector("#encryptionPasswordRow"),
|
||||||
@@ -588,7 +597,7 @@ const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function updatePendingChanges() {
|
function updatePendingChanges() {
|
||||||
setDirty(Boolean(state.editorInfo) && (state.editorInfo.revision !== state.savedRevision
|
setDirty(state.encryptionDirty || Boolean(state.editorInfo) && (state.editorInfo.revision !== state.savedRevision
|
||||||
|| canvasEditor.textChanged() || canvasEditor.cropChanged() || canvasEditor.nudgeChanged()));
|
|| canvasEditor.textChanged() || canvasEditor.cropChanged() || canvasEditor.nudgeChanged()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,7 +1145,7 @@ el.insertPanel.addEventListener("close", () => { state.insertObject = null; });
|
|||||||
el.insertForm.addEventListener("submit", insertObject);
|
el.insertForm.addEventListener("submit", insertObject);
|
||||||
el.textFontAdd.addEventListener("click", () => openFontFile(el.fontInput));
|
el.textFontAdd.addEventListener("click", () => openFontFile(el.fontInput));
|
||||||
el.saveButton.addEventListener("click", () => exportFile(true, null, "ofd"));
|
el.saveButton.addEventListener("click", () => exportFile(true, null, "ofd"));
|
||||||
el.encryptSaveButton.addEventListener("click", () => openExportPanel(true));
|
editorClick(el.encryptSaveButton, openEncryptionPanel);
|
||||||
el.signButton.addEventListener("click", () => {
|
el.signButton.addEventListener("click", () => {
|
||||||
if (!state.doc || document.body.hasAttribute("aria-busy")) return;
|
if (!state.doc || document.body.hasAttribute("aria-busy")) return;
|
||||||
el.signForm.reset();
|
el.signForm.reset();
|
||||||
@@ -1259,8 +1268,19 @@ el.outputCompression.addEventListener("change", () => {
|
|||||||
el.exportPageButton.addEventListener("click", () => exportFile(false));
|
el.exportPageButton.addEventListener("click", () => exportFile(false));
|
||||||
el.exportButton.addEventListener("click", () => openExportPanel());
|
el.exportButton.addEventListener("click", () => openExportPanel());
|
||||||
el.exportCancel.addEventListener("click", () => el.exportPanel.close());
|
el.exportCancel.addEventListener("click", () => el.exportPanel.close());
|
||||||
el.exportPanel.addEventListener("close", () => { if (!el.exportPanel.open) clearEncryptionForm(); });
|
el.encryptionPanel.addEventListener("close", () => { if (!el.encryptionPanel.open) clearEncryptionForm(); });
|
||||||
el.encryptionType.addEventListener("change", updateEncryptionType);
|
el.encryptionPanel.addEventListener("cancel", event => { if (el.encryptionForm.inert) event.preventDefault(); });
|
||||||
|
el.encryptionCancel.addEventListener("click", () => el.encryptionPanel.close());
|
||||||
|
el.encryptionType.addEventListener("change", () => {
|
||||||
|
state.encryptionChanged = false;
|
||||||
|
updateEncryptionType();
|
||||||
|
});
|
||||||
|
el.encryptionChange.addEventListener("click", () => {
|
||||||
|
state.encryptionChanged = true;
|
||||||
|
updateEncryptionType();
|
||||||
|
(el.encryptionType.value === "password" ? el.encryptionPassword : el.encryptionCertificates).focus();
|
||||||
|
});
|
||||||
|
el.encryptionForm.addEventListener("submit", applyEncryption);
|
||||||
el.cancelExportButton.addEventListener("click", cancelExport);
|
el.cancelExportButton.addEventListener("click", cancelExport);
|
||||||
el.exportPanel.addEventListener("pointerdown", (event) => {
|
el.exportPanel.addEventListener("pointerdown", (event) => {
|
||||||
state.exportBackdrop = event.target === el.exportPanel;
|
state.exportBackdrop = event.target === el.exportPanel;
|
||||||
@@ -1279,29 +1299,6 @@ el.exportRange.addEventListener("input", updateExportRange);
|
|||||||
el.exportForm.addEventListener("submit", async (event) => {
|
el.exportForm.addEventListener("submit", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!el.exportSubmit.disabled) {
|
if (!el.exportSubmit.disabled) {
|
||||||
if (state.exportEncrypted) {
|
|
||||||
const certificates = el.encryptionType.value === "certificate";
|
|
||||||
if (certificates && !el.encryptionCertificates.files?.length) {
|
|
||||||
el.exportRangeStatus.textContent = "请选择接收者证书";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!certificates && !el.encryptionPassword.value) {
|
|
||||||
el.exportRangeStatus.textContent = "请输入口令";
|
|
||||||
el.encryptionPassword.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!certificates && el.encryptionPassword.value !== el.encryptionConfirm.value) {
|
|
||||||
el.exportRangeStatus.textContent = "口令不一致";
|
|
||||||
el.encryptionConfirm.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const encryption = certificates
|
|
||||||
? {password:new Uint8Array(), userName:"", recipientFiles:Array.from(el.encryptionCertificates.files)}
|
|
||||||
: { password: new TextEncoder().encode(el.encryptionPassword.value), userName: el.encryptionUser.value.trim() };
|
|
||||||
clearEncryptionForm();
|
|
||||||
try { return await exportFile(true, state.exportPages, "ofd", encryption); }
|
|
||||||
finally { encryption.password.fill(0); }
|
|
||||||
}
|
|
||||||
return exportFile(true, state.exportPages);
|
return exportFile(true, state.exportPages);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1494,7 +1491,7 @@ function handleKeyDown(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formDialogOpen() {
|
function formDialogOpen() {
|
||||||
return Boolean(sealPreview) || el.signPanel.open || el.verifyPanel.open || el.credentialsPanel.open || batchElements.Panel.open || el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open || el.paragraphPanel.open || el.importPanel.open || el.infoPanel.open
|
return Boolean(sealPreview) || el.signPanel.open || el.verifyPanel.open || el.credentialsPanel.open || el.encryptionPanel.open || batchElements.Panel.open || el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open || el.paragraphPanel.open || el.importPanel.open || el.infoPanel.open
|
||||||
|| el.batchPagesPanel.open || el.objectStylePanel.open || el.sourceTextPanel.open || el.outlinePanel.open || el.objectBoundsPanel.open || el.annotationNote.open || el.annotationCreate.open || el.objectPicker.open;
|
|| el.batchPagesPanel.open || el.objectStylePanel.open || el.sourceTextPanel.open || el.outlinePanel.open || el.objectBoundsPanel.open || el.annotationNote.open || el.annotationCreate.open || el.objectPicker.open;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1918,7 +1915,7 @@ function discardChanges() {
|
|||||||
function setEditorInfo(doc) {
|
function setEditorInfo(doc) {
|
||||||
state.editorInfo = { revision: doc.revision, canUndo: doc.canUndo, canRedo: doc.canRedo,
|
state.editorInfo = { revision: doc.revision, canUndo: doc.canUndo, canRedo: doc.canRedo,
|
||||||
pageCapabilities: doc.pageCapabilities, editWarnings: doc.editWarnings || [] };
|
pageCapabilities: doc.pageCapabilities, editWarnings: doc.editWarnings || [] };
|
||||||
setDirty(doc.revision !== state.savedRevision);
|
setDirty(state.encryptionDirty || doc.revision !== state.savedRevision);
|
||||||
}
|
}
|
||||||
|
|
||||||
function editorLocation() {
|
function editorLocation() {
|
||||||
@@ -2097,6 +2094,7 @@ async function createDocument(event) {
|
|||||||
state.fileName = `${title.replace(/[\\/:*?"<>|]+/g, "_").replace(/\.ofd$/i, "")}.ofd`;
|
state.fileName = `${title.replace(/[\\/:*?"<>|]+/g, "_").replace(/\.ofd$/i, "")}.ofd`;
|
||||||
state.conversionWarnings = [];
|
state.conversionWarnings = [];
|
||||||
state.savedRevision = null;
|
state.savedRevision = null;
|
||||||
|
state.encryptionDirty = false;
|
||||||
setEditorInfo(doc);
|
setEditorInfo(doc);
|
||||||
el.createPanel.close();
|
el.createPanel.close();
|
||||||
await openDocument({ doc, openSeq, skipAutoFonts: true, resetScroll: true });
|
await openDocument({ doc, openSeq, skipAutoFonts: true, resetScroll: true });
|
||||||
@@ -3558,6 +3556,7 @@ async function openOFD(file, remote = null) {
|
|||||||
let bytes = downloaded ? downloaded.bytes : new Uint8Array(await file.arrayBuffer());
|
let bytes = downloaded ? downloaded.bytes : new Uint8Array(await file.arrayBuffer());
|
||||||
const name = downloaded ? downloaded.name : file.name;
|
const name = downloaded ? downloaded.name : file.name;
|
||||||
let warnings = [];
|
let warnings = [];
|
||||||
|
let convertedDoc;
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3576,11 +3575,12 @@ async function openOFD(file, remote = null) {
|
|||||||
}
|
}
|
||||||
if (openSeq !== state.openSeq) return;
|
if (openSeq !== state.openSeq) return;
|
||||||
bytes = converted.bytes;
|
bytes = converted.bytes;
|
||||||
|
convertedDoc = converted.doc;
|
||||||
warnings = JSON.parse(converted.warnings || "null") || [];
|
warnings = JSON.parse(converted.warnings || "null") || [];
|
||||||
}
|
}
|
||||||
await ensureWASM();
|
await ensureWASM();
|
||||||
if (openSeq !== state.openSeq) return;
|
if (openSeq !== state.openSeq) return;
|
||||||
const doc = await openWithCredentials(await fontManager.files(fontManager.userFonts), openSeq, bytes);
|
const doc = convertedDoc || await openWithCredentials(await fontManager.files(fontManager.userFonts), openSeq, bytes);
|
||||||
if (openSeq !== state.openSeq) return;
|
if (openSeq !== state.openSeq) return;
|
||||||
state.ofdBytes = bytes;
|
state.ofdBytes = bytes;
|
||||||
state.conversionWarnings = warnings;
|
state.conversionWarnings = warnings;
|
||||||
@@ -3598,6 +3598,7 @@ async function openOFD(file, remote = null) {
|
|||||||
state.editorViews.clear();
|
state.editorViews.clear();
|
||||||
state.fontRenderPending = false;
|
state.fontRenderPending = false;
|
||||||
state.savedRevision = null;
|
state.savedRevision = null;
|
||||||
|
state.encryptionDirty = false;
|
||||||
canvasEditor.clear();
|
canvasEditor.clear();
|
||||||
setDirty(false);
|
setDirty(false);
|
||||||
el.createPanel.close();
|
el.createPanel.close();
|
||||||
@@ -4030,6 +4031,7 @@ function clearSecurityForms() {
|
|||||||
if (sealPreview) finishSealPosition(false);
|
if (sealPreview) finishSealPosition(false);
|
||||||
finishCredentials(null);
|
finishCredentials(null);
|
||||||
clearEncryptionForm();
|
clearEncryptionForm();
|
||||||
|
el.encryptionPanel.close();
|
||||||
el.signKey.value = "";
|
el.signKey.value = "";
|
||||||
el.signKeyPassword.value = "";
|
el.signKeyPassword.value = "";
|
||||||
if (el.signPanel.open) el.signPanel.close();
|
if (el.signPanel.open) el.signPanel.close();
|
||||||
@@ -4088,8 +4090,13 @@ async function openWithCredentials(fonts, openSeq, data = state.ofdBytes, import
|
|||||||
try {
|
try {
|
||||||
while (openSeq === state.openSeq) {
|
while (openSeq === state.openSeq) {
|
||||||
try {
|
try {
|
||||||
return pdf ? await callWASM("ofdgoConvertPDF", data, fonts, credentials?.password || null)
|
if (pdf) {
|
||||||
: importing ? await callWASM("ofdgoLoadImport", data.slice(), credentials)
|
const converted = await callWASM("ofdgoConvertPDF", data, fonts, credentials?.password || null);
|
||||||
|
if (openSeq !== state.openSeq) return converted;
|
||||||
|
converted.doc = await callWASM("ofdgoOpen", converted.bytes, fonts, state.renderAnnotations, credentials);
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
return importing ? await callWASM("ofdgoLoadImport", data.slice(), credentials)
|
||||||
: await callWASM("ofdgoOpen", data, fonts, state.renderAnnotations, credentials);
|
: await callWASM("ofdgoOpen", data, fonts, state.renderAnnotations, credentials);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (openSeq !== state.openSeq || !(pdf ? err.code === "pdfPassword" : ["credentialsRequired", "invalidCredentials"].includes(err.code))) throw err;
|
if (openSeq !== state.openSeq || !(pdf ? err.code === "pdfPassword" : ["credentialsRequired", "invalidCredentials"].includes(err.code))) throw err;
|
||||||
@@ -4426,14 +4433,90 @@ async function downloadResource(attachment, api, label) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encryptionMode() {
|
||||||
|
const info = state.doc?.encryption;
|
||||||
|
if (!info?.encrypted) return "none";
|
||||||
|
if (info.layers !== 1 || info.policyRequired) return "keep";
|
||||||
|
return info.method === "1.1.1" ? "password" : info.method === "1.1.2" ? "certificate" : "keep";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEncryptionPanel() {
|
||||||
|
if (!state.doc || document.body.hasAttribute("aria-busy")) return;
|
||||||
|
clearEncryptionForm();
|
||||||
|
el.encryptionType.value = encryptionMode();
|
||||||
|
el.encryptionKeep.hidden = el.encryptionType.value !== "keep";
|
||||||
|
updateEncryptionType();
|
||||||
|
el.encryptionPanel.showModal();
|
||||||
|
}
|
||||||
|
|
||||||
function updateEncryptionType() {
|
function updateEncryptionType() {
|
||||||
const certificates = el.encryptionType.value === "certificate";
|
const mode = el.encryptionType.value;
|
||||||
el.encryptionUserRow.hidden = el.encryptionPasswordRow.hidden = el.encryptionConfirmRow.hidden = certificates;
|
const existing = mode !== "none" && mode === encryptionMode() && !state.encryptionChanged;
|
||||||
|
const password = mode === "password" && !existing;
|
||||||
|
const certificates = mode === "certificate" && !existing;
|
||||||
|
el.encryptionCurrentRow.hidden = !existing;
|
||||||
|
el.encryptionCurrent.textContent = mode === "password" ? "已设置" : (state.doc?.encryption?.users || []).join("、") || "已加密";
|
||||||
|
el.encryptionChange.hidden = mode === "keep";
|
||||||
|
el.encryptionUserRow.hidden = el.encryptionPasswordRow.hidden = el.encryptionConfirmRow.hidden = !password;
|
||||||
el.encryptionCertificatesRow.hidden = !certificates;
|
el.encryptionCertificatesRow.hidden = !certificates;
|
||||||
el.encryptionPassword.required = el.encryptionConfirm.required = state.exportEncrypted && !certificates;
|
el.encryptionPassword.required = el.encryptionConfirm.required = password;
|
||||||
el.encryptionCertificates.required = state.exportEncrypted && certificates;
|
el.encryptionCertificates.required = certificates;
|
||||||
|
el.encryptionUser.value = mode === "password" && encryptionMode() === mode ? state.doc.encryption.users?.[0] || "" : "";
|
||||||
el.encryptionPassword.value = el.encryptionConfirm.value = "";
|
el.encryptionPassword.value = el.encryptionConfirm.value = "";
|
||||||
el.encryptionCertificates.value = "";
|
el.encryptionCertificates.value = "";
|
||||||
|
el.encryptionStatus.textContent = mode === "keep" && state.doc?.encryption?.policyRequired ? "请重新设置加密方式" : "";
|
||||||
|
el.encryptionSubmit.disabled = mode === "keep" && Boolean(state.doc?.encryption?.policyRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyEncryption(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!state.doc || document.body.hasAttribute("aria-busy") || el.encryptionSubmit.disabled) return;
|
||||||
|
const mode = el.encryptionType.value;
|
||||||
|
if (mode === encryptionMode() && !state.encryptionChanged) {
|
||||||
|
el.encryptionPanel.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode === "password" && !el.encryptionPassword.value) {
|
||||||
|
el.encryptionStatus.textContent = "请输入口令";
|
||||||
|
el.encryptionPassword.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode === "password" && el.encryptionPassword.value !== el.encryptionConfirm.value) {
|
||||||
|
el.encryptionStatus.textContent = "口令不一致";
|
||||||
|
el.encryptionConfirm.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mode === "certificate" && !el.encryptionCertificates.files?.length) {
|
||||||
|
el.encryptionStatus.textContent = "请选择接收者证书";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const openSeq = state.openSeq;
|
||||||
|
const options = mode === "none" ? null : { password: new TextEncoder().encode(el.encryptionPassword.value), userName: el.encryptionUser.value.trim() };
|
||||||
|
el.encryptionForm.inert = true;
|
||||||
|
setBusy(true, "正在设置加密");
|
||||||
|
try {
|
||||||
|
if (mode === "certificate") options.recipients = await encryptionRecipients(Array.from(el.encryptionCertificates.files));
|
||||||
|
if (openSeq !== state.openSeq) return;
|
||||||
|
const doc = await callWASM("ofdgoSetEncryption", options);
|
||||||
|
if (openSeq !== state.openSeq) return;
|
||||||
|
if (!state.editorInfo) state.savedRevision = doc.revision;
|
||||||
|
state.encryptionDirty = true;
|
||||||
|
setEditorInfo(doc);
|
||||||
|
state.doc.encryption = doc.encryption;
|
||||||
|
state.ofdBytes = null;
|
||||||
|
el.encryptionPanel.close();
|
||||||
|
renderSecurity();
|
||||||
|
setStatus("加密设置已更新");
|
||||||
|
} catch (err) {
|
||||||
|
if (openSeq === state.openSeq) el.encryptionStatus.textContent = err.message;
|
||||||
|
} finally {
|
||||||
|
options?.password.fill(0);
|
||||||
|
if (openSeq === state.openSeq) {
|
||||||
|
el.encryptionPassword.value = el.encryptionConfirm.value = "";
|
||||||
|
el.encryptionForm.inert = false;
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function encryptionRecipients(files) {
|
async function encryptionRecipients(files) {
|
||||||
@@ -4448,11 +4531,12 @@ async function encryptionRecipients(files) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearEncryptionForm() {
|
function clearEncryptionForm() {
|
||||||
|
el.encryptionForm.inert = false;
|
||||||
el.encryptionPassword.value = "";
|
el.encryptionPassword.value = "";
|
||||||
el.encryptionConfirm.value = "";
|
el.encryptionConfirm.value = "";
|
||||||
el.encryptionUser.value = "";
|
el.encryptionUser.value = "";
|
||||||
el.encryptionCertificates.value = "";
|
el.encryptionCertificates.value = "";
|
||||||
state.exportEncrypted = false;
|
state.encryptionChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSecurity() {
|
function renderSecurity() {
|
||||||
@@ -4705,18 +4789,11 @@ async function verifyDocument(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportPanel(encrypted = false) {
|
function openExportPanel() {
|
||||||
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
el.exportForm.reset();
|
el.exportForm.reset();
|
||||||
clearEncryptionForm();
|
|
||||||
state.exportEncrypted = encrypted;
|
|
||||||
el.encryptionFields.hidden = !encrypted;
|
|
||||||
el.encryptionPassword.required = el.encryptionConfirm.required = encrypted;
|
|
||||||
updateEncryptionType();
|
|
||||||
el.exportTitle.textContent = encrypted ? "加密另存" : "导出文档";
|
|
||||||
el.exportSubmit.textContent = encrypted ? "保存" : "导出";
|
|
||||||
if (state.editing && state.pageSelection.size) {
|
if (state.editing && state.pageSelection.size) {
|
||||||
el.exportSpecified.checked = true;
|
el.exportSpecified.checked = true;
|
||||||
el.exportRange.value = selectedPageRange();
|
el.exportRange.value = selectedPageRange();
|
||||||
@@ -5097,14 +5174,14 @@ function compressionOptions(value) {
|
|||||||
return preset > 2 ? { mode: 2, level: preset - 2 } : { mode: preset };
|
return preset > 2 ? { mode: 2, level: preset - 2 } : { mode: preset };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportFile(whole, indices = null, value = el.exportFormat.value, encryption = null) {
|
async function exportFile(whole, indices = null, value = el.exportFormat.value) {
|
||||||
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const saving = value === "ofd";
|
const saving = value === "ofd";
|
||||||
const compression = compressionOptions(el.outputCompression.value);
|
const compression = compressionOptions(el.outputCompression.value);
|
||||||
const outputOptions = compression.mode ? [compression] : [];
|
const outputOptions = compression.mode ? [compression] : [];
|
||||||
if (saving && !state.editorInfo && !state.ofdBytes && !encryption) {
|
if (saving && !state.editorInfo && !state.ofdBytes) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!saving && state.doc.encryption?.encrypted && !window.confirm("此格式将输出明文,是否继续?")) return;
|
if (!saving && state.doc.encryption?.encrypted && !window.confirm("此格式将输出明文,是否继续?")) return;
|
||||||
@@ -5140,15 +5217,10 @@ async function exportFile(whole, indices = null, value = el.exportFormat.value,
|
|||||||
openSeq = state.openSeq;
|
openSeq = state.openSeq;
|
||||||
setBusy(true, "正在生成文件", null, status);
|
setBusy(true, "正在生成文件", null, status);
|
||||||
}
|
}
|
||||||
if (encryption?.recipientFiles) {
|
const readingSave = saving && !state.editorInfo && indices === null && compression.mode === 0;
|
||||||
encryption.recipients = await encryptionRecipients(encryption.recipientFiles);
|
|
||||||
delete encryption.recipientFiles;
|
|
||||||
if (openSeq !== state.openSeq) return;
|
|
||||||
}
|
|
||||||
const readingSave = saving && !state.editorInfo && !encryption && indices === null && compression.mode === 0;
|
|
||||||
const result = readingSave
|
const result = readingSave
|
||||||
? { label: "OFD", size: state.ofdBytes.byteLength, mime, blob: state.ofdBytes }
|
? { label: "OFD", size: state.ofdBytes.byteLength, mime, blob: state.ofdBytes }
|
||||||
: encryption ? await callWASM("ofdgoSaveEncrypted", indices, encryption, ...outputOptions, file) : saving ? await callWASM("ofdgoSaveDocument", indices, ...outputOptions, file) : whole
|
: saving ? await callWASM("ofdgoSaveDocument", indices, ...outputOptions, file) : whole
|
||||||
? await callWASM("ofdgoExportDocument", format.value, dpi, indices, state.renderBackend, ...outputOptions, file)
|
? await callWASM("ofdgoExportDocument", format.value, dpi, indices, state.renderBackend, ...outputOptions, file)
|
||||||
: await callWASM("ofdgoExportPage", pageIndex, format.value, dpi, state.renderBackend, ...outputOptions, file);
|
: await callWASM("ofdgoExportPage", pageIndex, format.value, dpi, state.renderBackend, ...outputOptions, file);
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
@@ -5165,6 +5237,7 @@ async function exportFile(whole, indices = null, value = el.exportFormat.value,
|
|||||||
}
|
}
|
||||||
if (saving && indices === null && state.editorInfo) {
|
if (saving && indices === null && state.editorInfo) {
|
||||||
state.savedRevision = state.editorInfo.revision;
|
state.savedRevision = state.editorInfo.revision;
|
||||||
|
state.encryptionDirty = false;
|
||||||
setDirty(false);
|
setDirty(false);
|
||||||
}
|
}
|
||||||
setStatus(`${result.label}已${saving ? "保存" : "导出"}(${formatBytes(result.size)})`);
|
setStatus(`${result.label}已${saving ? "保存" : "导出"}(${formatBytes(result.size)})`);
|
||||||
@@ -8094,11 +8167,11 @@ async function callWASM(name, ...args) {
|
|||||||
request.fontData = new Map();
|
request.fontData = new Map();
|
||||||
args.push([...request.localFonts.keys()]);
|
args.push([...request.localFonts.keys()]);
|
||||||
}
|
}
|
||||||
if (name === "ofdgoImportImages" || name === "ofdgoExportImages" || name === "ofdgoConvertPDF" || name === "ofdgoExportPage" || name === "ofdgoExportDocument" || name === "ofdgoExportAttachment" || name === "ofdgoSaveDocument" || name === "ofdgoSaveEncrypted" || name === "ofdgoSaveSigned" || name === "ofdgoImportPages") {
|
if (name === "ofdgoImportImages" || name === "ofdgoExportImages" || name === "ofdgoConvertPDF" || name === "ofdgoExportPage" || name === "ofdgoExportDocument" || name === "ofdgoExportAttachment" || name === "ofdgoSaveDocument" || name === "ofdgoSaveSigned" || name === "ofdgoImportPages") {
|
||||||
state.exportRequestID = id;
|
state.exportRequestID = id;
|
||||||
el.cancelExportButton.hidden = false;
|
el.cancelExportButton.hidden = false;
|
||||||
el.cancelExportButton.disabled = false;
|
el.cancelExportButton.disabled = false;
|
||||||
el.cancelExportButton.title = { ofdgoImportImages: "取消导入", ofdgoConvertPDF: "取消转换", ofdgoSaveDocument: "取消保存", ofdgoSaveEncrypted: "取消保存", ofdgoSaveSigned: "取消签署", ofdgoImportPages: "取消导入", ofdgoExportAttachment: "取消下载" }[name] || "取消导出";
|
el.cancelExportButton.title = { ofdgoImportImages: "取消导入", ofdgoConvertPDF: "取消转换", ofdgoSaveDocument: "取消保存", ofdgoSaveSigned: "取消签署", ofdgoImportPages: "取消导入", ofdgoExportAttachment: "取消下载" }[name] || "取消导出";
|
||||||
el.cancelExportButton.setAttribute("aria-label", el.cancelExportButton.title);
|
el.cancelExportButton.setAttribute("aria-label", el.cancelExportButton.title);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ self.onmessage = ({ data }) => {
|
|||||||
operations.get(data.id)?.abort();
|
operations.get(data.id)?.abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.name === "ofdgoImportImages" || data.name === "ofdgoExportImages" || data.name === "ofdgoConvertFile" || data.name === "ofdgoPackFiles" || data.name === "ofdgoConvertPDF" || data.name === "ofdgoExportPage" || data.name === "ofdgoExportDocument" || data.name === "ofdgoExportAttachment" || data.name === "ofdgoSaveDocument" || data.name === "ofdgoSaveEncrypted" || data.name === "ofdgoSaveSigned" || data.name === "ofdgoImportPages") {
|
if (data.name === "ofdgoImportImages" || data.name === "ofdgoExportImages" || data.name === "ofdgoConvertFile" || data.name === "ofdgoPackFiles" || data.name === "ofdgoConvertPDF" || data.name === "ofdgoExportPage" || data.name === "ofdgoExportDocument" || data.name === "ofdgoExportAttachment" || data.name === "ofdgoSaveDocument" || data.name === "ofdgoSaveSigned" || data.name === "ofdgoImportPages") {
|
||||||
operations.set(data.id, new AbortController());
|
operations.set(data.id, new AbortController());
|
||||||
}
|
}
|
||||||
pending = pending.then(() => data.name === "ofdgoConvertFile" || data.name === "ofdgoPackFiles" ? handleBatchMessage(data) : handleMessage(data));
|
pending = pending.then(() => data.name === "ofdgoConvertFile" || data.name === "ofdgoPackFiles" ? handleBatchMessage(data) : handleMessage(data));
|
||||||
@@ -167,10 +167,10 @@ async function handleMessage({ id, name, args }) {
|
|||||||
const signal = operations.get(id)?.signal;
|
const signal = operations.get(id)?.signal;
|
||||||
const importing = name === "ofdgoImportPages" || name === "ofdgoImportImages";
|
const importing = name === "ofdgoImportPages" || name === "ofdgoImportImages";
|
||||||
const converting = name === "ofdgoConvertPDF";
|
const converting = name === "ofdgoConvertPDF";
|
||||||
const saving = name === "ofdgoSaveDocument" || name === "ofdgoSaveEncrypted" || name === "ofdgoSaveSigned";
|
const saving = name === "ofdgoSaveDocument" || name === "ofdgoSaveSigned";
|
||||||
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 === "ofdgoOpen" ? args[3]?.password : name === "ofdgoSaveEncrypted" || 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;
|
||||||
try {
|
try {
|
||||||
signal?.throwIfAborted();
|
signal?.throwIfAborted();
|
||||||
if (name === "ofdgoImportImages") {
|
if (name === "ofdgoImportImages") {
|
||||||
|
|||||||
+67
-28
@@ -266,7 +266,7 @@ func RunWASM() {
|
|||||||
registerCallback("ofdgoUndo", func([]js.Value) (any, error) { return restoreEditor(false) })
|
registerCallback("ofdgoUndo", func([]js.Value) (any, error) { return restoreEditor(false) })
|
||||||
registerCallback("ofdgoRedo", func([]js.Value) (any, error) { return restoreEditor(true) })
|
registerCallback("ofdgoRedo", func([]js.Value) (any, error) { return restoreEditor(true) })
|
||||||
registerAsyncCallback("ofdgoSaveDocument", saveDocument)
|
registerAsyncCallback("ofdgoSaveDocument", saveDocument)
|
||||||
registerAsyncCallback("ofdgoSaveEncrypted", saveEncrypted)
|
registerAsyncCallback("ofdgoSetEncryption", setEncryption)
|
||||||
registerAsyncCallback("ofdgoSaveSigned", saveSigned)
|
registerAsyncCallback("ofdgoSaveSigned", saveSigned)
|
||||||
registerCallback("ofdgoPreviewSignatureSeal", previewSignatureSeal)
|
registerCallback("ofdgoPreviewSignatureSeal", previewSignatureSeal)
|
||||||
registerCallback("ofdgoSignaturePlacement", signaturePlacement)
|
registerCallback("ofdgoSignaturePlacement", signaturePlacement)
|
||||||
@@ -403,10 +403,21 @@ func convertPDFDocument(args []js.Value) (any, error) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
options.RendererOptions = []ofdgo.RendererOption{ofdgo.WithFontFS(fontSources...)}
|
options.RendererOptions = []ofdgo.RendererOption{ofdgo.WithFontFS(fontSources...)}
|
||||||
report, err := ofdgo.ConvertPDF(context.Background(), bytes.NewReader(data), int64(len(data)), &output, options)
|
editor, report, err := ofdgo.ImportPDF(context.Background(), bytes.NewReader(data), int64(len(data)), options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(options.Password) != 0 {
|
||||||
|
if err := editor.SetEncryption(&ofdgo.EncryptionOptions{Password: options.Password}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.OnWriteProgress = func(stage string, completed, total int) error {
|
||||||
|
return options.OnProgress("write."+stage, completed, total)
|
||||||
|
}
|
||||||
|
if _, err := editor.WriteTo(&output); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
warnings, err := json.Marshal(report.Warnings)
|
warnings, err := json.Marshal(report.Warnings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -3555,6 +3566,7 @@ func editDocument(args []js.Value) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
currentEditor = editor
|
currentEditor = editor
|
||||||
|
currentSession.editor = editor
|
||||||
currentSession.editing = true
|
currentSession.editing = true
|
||||||
copiedObjects = nil
|
copiedObjects = nil
|
||||||
copiedStyle = nil
|
copiedStyle = nil
|
||||||
@@ -3608,6 +3620,7 @@ func previewEditor(editor *ofdgo.Editor, annotations bool) (editorInfo, error) {
|
|||||||
}
|
}
|
||||||
editor.SetRenderBackends(session.Renderer.Backends())
|
editor.SetRenderBackends(session.Renderer.Backends())
|
||||||
currentSession = session
|
currentSession = session
|
||||||
|
session.editor = editor
|
||||||
session.editing = true
|
session.editing = true
|
||||||
if currentEditor != editor {
|
if currentEditor != editor {
|
||||||
copiedObjects = nil
|
copiedObjects = nil
|
||||||
@@ -4118,20 +4131,60 @@ func verifySignatures(args []js.Value) (any, error) {
|
|||||||
return map[string]any{"signatures": infos, "signatureCount": len(infos), "signatureError": ""}, nil
|
return map[string]any{"signatures": infos, "signatureCount": len(infos), "signatureError": ""}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveEncrypted 以独立快照加密另存,不修改当前文档的输出策略
|
// setEncryption 设置当前文档的保存策略,不修改页面或撤销历史
|
||||||
// 入参: args 加密选项、写出回调、进度回调和可选页面索引
|
// 入参: args 加密选项,null选择明文保存
|
||||||
// 返回: any 保存结果, error 错误信息
|
// 返回: any 编辑状态, error 错误信息
|
||||||
func saveEncrypted(args []js.Value) (any, error) {
|
func setEncryption(args []js.Value) (any, error) {
|
||||||
if currentSession == nil || len(args) < 3 {
|
if currentSession == nil || len(args) != 1 {
|
||||||
return nil, fmt.Errorf("missing encrypted save arguments")
|
return nil, fmt.Errorf("missing encryption arguments")
|
||||||
}
|
}
|
||||||
password, err := bytesFromJS(args[0].Get("password"))
|
options, err := encryptionOptionsFromJS(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer clear(password)
|
if options != nil {
|
||||||
options := &ofdgo.EncryptionOptions{Password: password, UserName: args[0].Get("userName").String()}
|
defer clear(options.Password)
|
||||||
if recipients := args[0].Get("recipients"); !recipients.IsUndefined() && !recipients.IsNull() {
|
}
|
||||||
|
if currentEditor == nil {
|
||||||
|
if _, err := editDocument(nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := currentEditor.SetEncryption(options); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return editorSummary(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encryptionOptionsFromJS 读取加密选项并校验接收者证书
|
||||||
|
// 入参: value 加密选项,null选择明文保存
|
||||||
|
// 返回: *ofdgo.EncryptionOptions 加密选项, error 错误信息
|
||||||
|
func encryptionOptionsFromJS(value js.Value) (*ofdgo.EncryptionOptions, error) {
|
||||||
|
if value.IsNull() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if value.Type() != js.TypeObject {
|
||||||
|
return nil, ofdgo.ErrEncryptionPolicyRequired
|
||||||
|
}
|
||||||
|
var password []byte
|
||||||
|
if field := value.Get("password"); !field.IsNull() && !field.IsUndefined() {
|
||||||
|
var err error
|
||||||
|
password, err = bytesFromJS(field)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options := &ofdgo.EncryptionOptions{Password: password}
|
||||||
|
success := false
|
||||||
|
defer func() {
|
||||||
|
if !success {
|
||||||
|
clear(password)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if user := value.Get("userName"); user.Type() == js.TypeString {
|
||||||
|
options.UserName = user.String()
|
||||||
|
}
|
||||||
|
if recipients := value.Get("recipients"); !recipients.IsUndefined() && !recipients.IsNull() {
|
||||||
for i := 0; i < recipients.Length(); i++ {
|
for i := 0; i < recipients.Length(); i++ {
|
||||||
item := recipients.Index(i)
|
item := recipients.Index(i)
|
||||||
data, err := bytesFromJS(item.Get("certificate"))
|
data, err := bytesFromJS(item.Get("certificate"))
|
||||||
@@ -4148,22 +4201,8 @@ func saveEncrypted(args []js.Value) (any, error) {
|
|||||||
if len(password) == 0 && len(options.Recipients) == 0 {
|
if len(password) == 0 && len(options.Recipients) == 0 {
|
||||||
return nil, ofdgo.ErrEncryptionPolicyRequired
|
return nil, ofdgo.ErrEncryptionPolicyRequired
|
||||||
}
|
}
|
||||||
reader := currentSession.Reader
|
success = true
|
||||||
if currentEditor != nil {
|
return options, nil
|
||||||
reader, err = currentEditor.Reader()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
}
|
|
||||||
editor, err := reader.Editor()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := editor.SetEncryption(options); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return saveEditor(editor, args[1:])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// previewSignatureSeal 渲染独立印章预览并读取所选页面区域,不接触私钥或修改文档
|
// previewSignatureSeal 渲染独立印章预览并读取所选页面区域,不接触私钥或修改文档
|
||||||
|
|||||||
+11
-6
@@ -62,6 +62,7 @@ type Session struct {
|
|||||||
signatureError error
|
signatureError error
|
||||||
signaturesRead bool
|
signaturesRead bool
|
||||||
editing bool
|
editing bool
|
||||||
|
editor *ofdgo.Editor
|
||||||
}
|
}
|
||||||
|
|
||||||
// pageBoxInfo 按页索引缓存真实区域,valid区分未读取与零值区域
|
// pageBoxInfo 按页索引缓存真实区域,valid区分未读取与零值区域
|
||||||
@@ -251,10 +252,11 @@ type SignatureStampInfo struct {
|
|||||||
|
|
||||||
// EncryptionInfo 仅向界面传递非敏感加密状态
|
// EncryptionInfo 仅向界面传递非敏感加密状态
|
||||||
type EncryptionInfo struct {
|
type EncryptionInfo struct {
|
||||||
Encrypted bool `json:"encrypted"`
|
Encrypted bool `json:"encrypted"`
|
||||||
Method string `json:"method,omitempty"`
|
Method string `json:"method,omitempty"`
|
||||||
Users []string `json:"users,omitempty"`
|
Users []string `json:"users,omitempty"`
|
||||||
Layers int `json:"layers,omitempty"`
|
Layers int `json:"layers,omitempty"`
|
||||||
|
PolicyRequired bool `json:"policyRequired,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open 打开浏览器内存中的OFD文档
|
// Open 打开浏览器内存中的OFD文档
|
||||||
@@ -768,11 +770,14 @@ func exportFormat(value string) (ExportFormat, bool) {
|
|||||||
return ExportFormat{}, false
|
return ExportFormat{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// encryptionInfo 获取当前文档的加密来源,不包含凭据
|
// encryptionInfo 获取当前文档的保存策略,不包含凭据
|
||||||
// 返回: EncryptionInfo 加密状态
|
// 返回: EncryptionInfo 加密状态
|
||||||
func (s *Session) encryptionInfo() EncryptionInfo {
|
func (s *Session) encryptionInfo() EncryptionInfo {
|
||||||
info := s.Reader.Encryption()
|
info := s.Reader.Encryption()
|
||||||
return EncryptionInfo{Encrypted: info.Encrypted, Method: info.Method, Users: info.Users, Layers: info.Layers}
|
if s.editor != nil {
|
||||||
|
info = s.editor.Encryption()
|
||||||
|
}
|
||||||
|
return EncryptionInfo{Encrypted: info.Encrypted, Method: info.Method, Users: info.Users, Layers: info.Layers, PolicyRequired: info.PolicyRequired}
|
||||||
}
|
}
|
||||||
|
|
||||||
// signatureInfos 获取签名验证信息
|
// signatureInfos 获取签名验证信息
|
||||||
|
|||||||
+15
-6
@@ -76,11 +76,13 @@ type EncryptionOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EncryptionInfo 不含敏感凭据的文档加密信息
|
// EncryptionInfo 不含敏感凭据的文档加密信息
|
||||||
|
// PolicyRequired表示无法继承原策略,保存前需显式设置加密方式
|
||||||
type EncryptionInfo struct {
|
type EncryptionInfo struct {
|
||||||
Encrypted bool
|
Encrypted bool
|
||||||
Method string
|
Method string
|
||||||
Users []string
|
Users []string
|
||||||
Layers int
|
Layers int
|
||||||
|
PolicyRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// encryptionState 保存解密来源与可继承的输出策略
|
// encryptionState 保存解密来源与可继承的输出策略
|
||||||
@@ -113,10 +115,17 @@ func WithCryptoProvider(provider CryptoProvider) ReaderOption {
|
|||||||
// Encryption 获取文档加密状态,不暴露口令或私钥
|
// Encryption 获取文档加密状态,不暴露口令或私钥
|
||||||
// 返回: EncryptionInfo 加密状态
|
// 返回: EncryptionInfo 加密状态
|
||||||
func (r *Reader) Encryption() EncryptionInfo {
|
func (r *Reader) Encryption() EncryptionInfo {
|
||||||
if r.encryption == nil {
|
return r.encryption.summary()
|
||||||
|
}
|
||||||
|
|
||||||
|
// summary 复制不含凭据的加密状态
|
||||||
|
// 返回: EncryptionInfo 加密状态
|
||||||
|
func (s *encryptionState) summary() EncryptionInfo {
|
||||||
|
if s == nil {
|
||||||
return EncryptionInfo{}
|
return EncryptionInfo{}
|
||||||
}
|
}
|
||||||
info := r.encryption.info
|
info := s.info
|
||||||
|
info.PolicyRequired = s.options == nil
|
||||||
info.Users = append([]string(nil), info.Users...)
|
info.Users = append([]string(nil), info.Users...)
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Encryption 获取当前保存策略,不暴露口令或私钥
|
||||||
|
// 返回: EncryptionInfo 加密状态
|
||||||
|
func (e *Editor) Encryption() EncryptionInfo {
|
||||||
|
return e.encryption.summary()
|
||||||
|
}
|
||||||
|
|
||||||
// SetEncryption 设置后续保存的加密策略,nil显式选择明文保存
|
// SetEncryption 设置后续保存的加密策略,nil显式选择明文保存
|
||||||
// 打开单用户口令或证书加密文档时默认继承原策略;多人口令和多层加密需显式选择输出策略
|
// 打开单用户口令或证书加密文档时默认继承原策略;多人口令和多层加密需显式选择输出策略
|
||||||
// 新的加密密钥与初始向量在每次保存时独立生成,设置不改变文档内容或撤销历史
|
// 新的加密密钥与初始向量在每次保存时独立生成,设置不改变文档内容或撤销历史
|
||||||
|
|||||||
Reference in new issue
Block a user