mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
This commit is contained in:
1 parent
8ddc2fdeed
commit
66f0d8a928
5 files changed
+208
-102
No files matched your search
@@ -129,6 +129,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="signatureList" class="signature-list"></div>
|
<div id="signatureList" class="signature-list"></div>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="annotationPanel" class="annotation-panel" aria-labelledby="annotationPanelTitle" hidden>
|
||||||
|
<div id="annotationPanelTitle" class="panel-title">注解</div>
|
||||||
|
<div id="annotationList" class="annotation-list"></div>
|
||||||
|
</section>
|
||||||
<section class="doc-font-panel" aria-labelledby="docFontPanelTitle">
|
<section class="doc-font-panel" aria-labelledby="docFontPanelTitle">
|
||||||
<div class="panel-row">
|
<div class="panel-row">
|
||||||
<div id="docFontPanelTitle" class="panel-title compact">OFD 字体</div>
|
<div id="docFontPanelTitle" class="panel-title compact">OFD 字体</div>
|
||||||
|
|||||||
+36
-20
@@ -614,7 +614,7 @@ button.outline-link:hover {
|
|||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.progress-spinner,
|
.progress-spinner,
|
||||||
.stamp-highlight {
|
.region-highlight {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,17 +694,17 @@ button.outline-link:hover {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stamp-highlight {
|
.region-highlight {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
border: 2px solid var(--accent);
|
border: 2px solid var(--accent);
|
||||||
background: rgba(15, 123, 104, 0.08);
|
background: rgba(15, 123, 104, 0.08);
|
||||||
box-shadow: 0 0 0 3px rgba(15, 123, 104, 0.16);
|
box-shadow: 0 0 0 3px rgba(15, 123, 104, 0.16);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
animation: stampPulse 0.9s ease-in-out 2;
|
animation: regionPulse 0.9s ease-in-out 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes stampPulse {
|
@keyframes regionPulse {
|
||||||
50% {
|
50% {
|
||||||
border-color: var(--accent-strong);
|
border-color: var(--accent-strong);
|
||||||
box-shadow: 0 0 0 5px rgba(15, 123, 104, 0.18);
|
box-shadow: 0 0 0 5px rgba(15, 123, 104, 0.18);
|
||||||
@@ -742,6 +742,7 @@ button.outline-link:hover {
|
|||||||
.font-panel,
|
.font-panel,
|
||||||
.doc-font-panel,
|
.doc-font-panel,
|
||||||
.attachment-panel,
|
.attachment-panel,
|
||||||
|
.annotation-panel,
|
||||||
.signature-panel {
|
.signature-panel {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
@@ -794,12 +795,14 @@ button.outline-link:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.doc-font-list,
|
.doc-font-list,
|
||||||
|
.annotation-list,
|
||||||
.signature-list {
|
.signature-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.doc-font-row,
|
.doc-font-row,
|
||||||
|
.annotation-row,
|
||||||
.signature-row {
|
.signature-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -812,6 +815,7 @@ button.outline-link:hover {
|
|||||||
.page-list-item:not(:disabled):hover,
|
.page-list-item:not(:disabled):hover,
|
||||||
.doc-font-row:hover,
|
.doc-font-row:hover,
|
||||||
.signature-row:hover,
|
.signature-row:hover,
|
||||||
|
.annotation-row:hover,
|
||||||
.font-row:hover {
|
.font-row:hover {
|
||||||
border-color: var(--line-strong);
|
border-color: var(--line-strong);
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
@@ -838,24 +842,20 @@ button.outline-link:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.doc-font-head,
|
.doc-font-head,
|
||||||
|
.annotation-head,
|
||||||
.signature-head {
|
.signature-head {
|
||||||
display: grid;
|
display: flow-root;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
|
||||||
align-items: start;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.doc-font-name,
|
.doc-font-name,
|
||||||
.signature-name {
|
.signature-name {
|
||||||
overflow: hidden;
|
|
||||||
color: #1f2428;
|
color: #1f2428;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-overflow: ellipsis;
|
overflow-wrap: anywhere;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-name-button {
|
.info-name-button {
|
||||||
display: inline;
|
display: inline;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -867,14 +867,15 @@ button.outline-link:hover {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-name-button:not(:disabled):hover {
|
.info-name-button:not(:disabled):hover {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--accent-strong);
|
color: var(--accent-strong);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-name-with-stamps {
|
.annotation-head .info-name-button {
|
||||||
white-space: normal;
|
color: var(--accent-strong);
|
||||||
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-stamp-group {
|
.signature-stamp-group {
|
||||||
@@ -882,6 +883,10 @@ button.outline-link:hover {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signature-stamp-item {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.signature-stamp-link {
|
.signature-stamp-link {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -906,6 +911,17 @@ button.outline-link:hover {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.doc-font-badges,
|
||||||
|
.signature-badges,
|
||||||
|
.annotation-head > .font-badge {
|
||||||
|
float: right;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.annotation-row .info-value {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.font-badge {
|
.font-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -937,7 +953,7 @@ button.outline-link:hover {
|
|||||||
color: #803f38;
|
color: #803f38;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-line {
|
.info-line {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 30px minmax(0, 1fr);
|
grid-template-columns: 30px minmax(0, 1fr);
|
||||||
align-items: start;
|
align-items: start;
|
||||||
@@ -947,21 +963,21 @@ button.outline-link:hover {
|
|||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-label {
|
.info-label {
|
||||||
color: #7b8791;
|
color: #7b8791;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-value {
|
.info-value {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-line.ok .signature-value {
|
.info-line.ok .info-value {
|
||||||
color: var(--accent-strong);
|
color: var(--accent-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.signature-line.fail .signature-value {
|
.info-line.fail .info-value {
|
||||||
color: #803f38;
|
color: #803f38;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+99
-64
@@ -115,6 +115,8 @@ const el = {
|
|||||||
signaturePanel: document.querySelector("#signaturePanel"),
|
signaturePanel: document.querySelector("#signaturePanel"),
|
||||||
signatureSummary: document.querySelector("#signatureSummary"),
|
signatureSummary: document.querySelector("#signatureSummary"),
|
||||||
signatureList: document.querySelector("#signatureList"),
|
signatureList: document.querySelector("#signatureList"),
|
||||||
|
annotationPanel: document.querySelector("#annotationPanel"),
|
||||||
|
annotationList: document.querySelector("#annotationList"),
|
||||||
docFontList: document.querySelector("#docFontList"),
|
docFontList: document.querySelector("#docFontList"),
|
||||||
docFontSummary: document.querySelector("#docFontSummary"),
|
docFontSummary: document.querySelector("#docFontSummary"),
|
||||||
availableFontSummary: document.querySelector("#availableFontSummary"),
|
availableFontSummary: document.querySelector("#availableFontSummary"),
|
||||||
@@ -1245,15 +1247,15 @@ async function loadDocumentDetails(openSeq) {
|
|||||||
|
|
||||||
async function renderPage(index, options = {}) {
|
async function renderPage(index, options = {}) {
|
||||||
if (!state.doc) {
|
if (!state.doc) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
const openSeq = options.openSeq || state.openSeq;
|
const openSeq = options.openSeq || state.openSeq;
|
||||||
if (openSeq !== state.openSeq) {
|
if (openSeq !== state.openSeq) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
const pageCount = state.doc.pageCount || 0;
|
const pageCount = state.doc.pageCount || 0;
|
||||||
if (index < 0 || index >= pageCount) {
|
if (index < 0 || index >= pageCount) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
const pageSeq = ++state.pageSeq;
|
const pageSeq = ++state.pageSeq;
|
||||||
if (!state.exporting) {
|
if (!state.exporting) {
|
||||||
@@ -1271,18 +1273,20 @@ async function renderPage(index, options = {}) {
|
|||||||
if (options.scroll !== false) {
|
if (options.scroll !== false) {
|
||||||
scrollToPage(index);
|
scrollToPage(index);
|
||||||
}
|
}
|
||||||
await renderFlowPage(index, { throwError: true, openSeq, priority: 0 });
|
const page = await renderFlowPage(index, { throwError: true, openSeq, priority: 0 });
|
||||||
if (openSeq !== state.openSeq || index !== state.pageIndex) {
|
if (openSeq !== state.openSeq || index !== state.pageIndex) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
if (options.scroll !== false) {
|
if (options.scroll !== false) {
|
||||||
scrollToPage(index);
|
scrollToPage(index);
|
||||||
}
|
}
|
||||||
queueNearbyPages(index, openSeq);
|
queueNearbyPages(index, openSeq);
|
||||||
|
return page;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (openSeq === state.openSeq) {
|
if (openSeq === state.openSeq) {
|
||||||
showError(err, false);
|
showError(err, false);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
if (!options.keepBusy && !state.exporting && openSeq === state.openSeq && pageSeq === state.pageSeq) {
|
if (!options.keepBusy && !state.exporting && openSeq === state.openSeq && pageSeq === state.pageSeq) {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
@@ -2071,6 +2075,7 @@ function renderMeta() {
|
|||||||
el.pageTotal.textContent = String(doc.pageCount || 0);
|
el.pageTotal.textContent = String(doc.pageCount || 0);
|
||||||
renderAttachments();
|
renderAttachments();
|
||||||
renderSignatures();
|
renderSignatures();
|
||||||
|
renderAnnotations();
|
||||||
renderDocumentFonts();
|
renderDocumentFonts();
|
||||||
renderFontList();
|
renderFontList();
|
||||||
updateLocalFontButton();
|
updateLocalFontButton();
|
||||||
@@ -2140,17 +2145,17 @@ function renderSignatures() {
|
|||||||
badges.className = "signature-badges";
|
badges.className = "signature-badges";
|
||||||
badges.append(fontBadge(signature.status === "valid" ? "通过" : signature.status === "invalid" ? "异常" : "未验", signature.status));
|
badges.append(fontBadge(signature.status === "valid" ? "通过" : signature.status === "invalid" ? "异常" : "未验", signature.status));
|
||||||
|
|
||||||
head.append(name, badges);
|
head.append(badges, name);
|
||||||
row.append(head);
|
row.append(head);
|
||||||
appendSignatureLine(row, "编号", signature.id);
|
appendInfoLine(row, "编号", signature.id);
|
||||||
appendSignatureLine(row, "版本", signature.version);
|
appendInfoLine(row, "版本", signature.version);
|
||||||
appendSignatureLine(row, "章图", signature.sealType);
|
appendInfoLine(row, "章图", signature.sealType);
|
||||||
appendSignatureLine(row, "章号", signature.sealId);
|
appendInfoLine(row, "章号", signature.sealId);
|
||||||
appendSignatureLine(row, "章名", signature.sealName);
|
appendInfoLine(row, "章名", signature.sealName);
|
||||||
appendSignatureLine(row, "厂商", signature.sealVendor);
|
appendInfoLine(row, "厂商", signature.sealVendor);
|
||||||
appendSignatureLine(row, "签者", signature.signer);
|
appendInfoLine(row, "签者", signature.signer);
|
||||||
appendSignatureLine(row, "时间", formatSignatureTime(signature.signatureDateTime));
|
appendInfoLine(row, "时间", formatDocumentTime(signature.signatureDateTime));
|
||||||
appendSignatureLine(row, "机构", signatureAgency(signature));
|
appendInfoLine(row, "机构", signatureAgency(signature));
|
||||||
appendSignatureCheck(row, "原文", signature.dataHashChecked, signature.dataHashOK);
|
appendSignatureCheck(row, "原文", signature.dataHashChecked, signature.dataHashOK);
|
||||||
appendSignatureCheck(row, "签名", signature.signedValueChecked, signature.signedValueOK);
|
appendSignatureCheck(row, "签名", signature.signedValueChecked, signature.signedValueOK);
|
||||||
if (signature.type !== "Sign") {
|
if (signature.type !== "Sign") {
|
||||||
@@ -2161,18 +2166,18 @@ function renderSignatures() {
|
|||||||
appendSignaturePolicy(row, "签期", signature.signatureTimeChecked, signature.signatureTimeOK);
|
appendSignaturePolicy(row, "签期", signature.signatureTimeChecked, signature.signatureTimeOK);
|
||||||
appendSignaturePolicy(row, "章期", signature.sealTimeChecked, signature.sealTimeOK);
|
appendSignaturePolicy(row, "章期", signature.sealTimeChecked, signature.sealTimeOK);
|
||||||
if (signature.sealCertTimeChecked) {
|
if (signature.sealCertTimeChecked) {
|
||||||
appendSignatureLine(row, "制期", signature.sealCertTimeOK ? "有效" : "失效", signature.sealCertTimeOK ? "ok" : "");
|
appendInfoLine(row, "制期", signature.sealCertTimeOK ? "有效" : "失效", signature.sealCertTimeOK ? "ok" : "");
|
||||||
}
|
}
|
||||||
appendSignaturePolicy(row, "时效", signature.certTimeChecked, signature.certTimeOK);
|
appendSignaturePolicy(row, "时效", signature.certTimeChecked, signature.certTimeOK);
|
||||||
appendSignatureCheck(row, "信任", signature.certTrustChecked, signature.certTrustOK);
|
appendSignatureCheck(row, "信任", signature.certTrustChecked, signature.certTrustOK);
|
||||||
appendSignatureLine(row, "保护", signatureReferenceText(signature), signatureReferenceStatus(signature));
|
appendInfoLine(row, "保护", signatureReferenceText(signature), signatureReferenceStatus(signature));
|
||||||
appendSignatureLine(row, "算法", signature.signatureMethod);
|
appendInfoLine(row, "算法", signature.signatureMethod);
|
||||||
appendSignatureLine(row, "散列", signature.digestMethod);
|
appendInfoLine(row, "散列", signature.digestMethod);
|
||||||
appendSignatureLine(row, "序号", signature.signSerial);
|
appendInfoLine(row, "序号", signature.signSerial);
|
||||||
appendSignatureLine(row, "主体", signature.signSubject && signature.signSubject !== signature.signer ? signature.signSubject : "");
|
appendInfoLine(row, "主体", signature.signSubject && signature.signSubject !== signature.signer ? signature.signSubject : "");
|
||||||
appendSignatureLine(row, "颁发", signature.signIssuer);
|
appendInfoLine(row, "颁发", signature.signIssuer);
|
||||||
appendSignatureLine(row, "章证", signature.sealSubject);
|
appendInfoLine(row, "章证", signature.sealSubject);
|
||||||
appendSignatureLine(row, "错误", signature.error, "fail");
|
appendInfoLine(row, "错误", signature.error, "fail");
|
||||||
fragment.append(row);
|
fragment.append(row);
|
||||||
}
|
}
|
||||||
el.signatureList.append(fragment);
|
el.signatureList.append(fragment);
|
||||||
@@ -2196,20 +2201,17 @@ function signatureSummary(signatures) {
|
|||||||
|
|
||||||
function signatureNameNode(signature) {
|
function signatureNameNode(signature) {
|
||||||
const stamps = signature.stamps || [];
|
const stamps = signature.stamps || [];
|
||||||
|
const name = document.createElement("div");
|
||||||
|
name.className = "signature-name";
|
||||||
if (!stamps.length) {
|
if (!stamps.length) {
|
||||||
const name = document.createElement("div");
|
|
||||||
name.className = "signature-name";
|
|
||||||
name.textContent = "签名";
|
name.textContent = "签名";
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
const name = document.createElement("div");
|
|
||||||
name.className = "signature-name signature-name-with-stamps";
|
|
||||||
|
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.className = "signature-name-button";
|
button.className = "info-name-button";
|
||||||
button.textContent = "签名";
|
button.textContent = "签名";
|
||||||
button.addEventListener("click", () => focusSignatureStamp(stamps[0]));
|
button.addEventListener("click", () => focusPageRegion(stamps[0], "签名"));
|
||||||
name.append(button, signatureStampGroup(stamps));
|
name.append(button, signatureStampGroup(stamps));
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -2217,19 +2219,17 @@ function signatureNameNode(signature) {
|
|||||||
function signatureStampGroup(stamps) {
|
function signatureStampGroup(stamps) {
|
||||||
const group = document.createElement("span");
|
const group = document.createElement("span");
|
||||||
group.className = "signature-stamp-group";
|
group.className = "signature-stamp-group";
|
||||||
group.append("(");
|
|
||||||
for (const [index, stamp] of stamps.entries()) {
|
for (const [index, stamp] of stamps.entries()) {
|
||||||
if (index > 0) {
|
const item = document.createElement("span");
|
||||||
group.append("、");
|
item.className = "signature-stamp-item";
|
||||||
}
|
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.className = "signature-stamp-link";
|
button.className = "signature-stamp-link";
|
||||||
button.textContent = `第${index + 1}处`;
|
button.textContent = `第${index + 1}处`;
|
||||||
button.addEventListener("click", () => focusSignatureStamp(stamp));
|
button.addEventListener("click", () => focusPageRegion(stamp, "签名"));
|
||||||
group.append(button);
|
item.append(index === 0 ? "(" : "", button, index === stamps.length - 1 ? ")" : "、");
|
||||||
|
group.append(item);
|
||||||
}
|
}
|
||||||
group.append(")");
|
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2261,24 +2261,24 @@ function signatureReferenceStatus(signature) {
|
|||||||
return signature.referenceChecked === count ? "ok" : "";
|
return signature.referenceChecked === count ? "ok" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSignatureTime(value) {
|
function formatDocumentTime(value) {
|
||||||
const text = String(value || "").trim();
|
const text = String(value || "").trim();
|
||||||
return text.replace(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/, "$1-$2-$3 $4:$5:$6$7$8").replace("T", " ");
|
return text.replace(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/, "$1-$2-$3 $4:$5:$6$7$8").replace("T", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendSignatureLine(row, label, value, status = "") {
|
function appendInfoLine(row, label, value, status = "") {
|
||||||
if (!value && value !== 0) {
|
if (!value && value !== 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const line = document.createElement("div");
|
const line = document.createElement("div");
|
||||||
line.className = status ? `signature-line ${status}` : "signature-line";
|
line.className = status ? `info-line ${status}` : "info-line";
|
||||||
|
|
||||||
const key = document.createElement("span");
|
const key = document.createElement("span");
|
||||||
key.className = "signature-label";
|
key.className = "info-label";
|
||||||
key.textContent = label;
|
key.textContent = label;
|
||||||
|
|
||||||
const text = document.createElement("span");
|
const text = document.createElement("span");
|
||||||
text.className = "signature-value";
|
text.className = "info-value";
|
||||||
text.textContent = String(value);
|
text.textContent = String(value);
|
||||||
|
|
||||||
line.append(key, text);
|
line.append(key, text);
|
||||||
@@ -2286,7 +2286,7 @@ function appendSignatureLine(row, label, value, status = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function appendSignatureCheck(row, label, checked, ok) {
|
function appendSignatureCheck(row, label, checked, ok) {
|
||||||
appendSignatureLine(row, label, checked ? ok ? "通过" : "失败" : "未验", checked ? ok ? "ok" : "fail" : "");
|
appendInfoLine(row, label, checked ? ok ? "通过" : "失败" : "未验", checked ? ok ? "ok" : "fail" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendSignaturePolicy(row, label, checked, ok) {
|
function appendSignaturePolicy(row, label, checked, ok) {
|
||||||
@@ -2296,41 +2296,76 @@ function appendSignaturePolicy(row, label, checked, ok) {
|
|||||||
appendSignatureCheck(row, label, checked, ok);
|
appendSignatureCheck(row, label, checked, ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function focusSignatureStamp(stamp) {
|
async function focusPageRegion(region, label) {
|
||||||
const pageIndex = (stamp.page || 0) - 1;
|
const pageIndex = region.page - 1;
|
||||||
if (!state.doc || pageIndex < 0) {
|
if (!state.doc || pageIndex < 0 || document.body.hasAttribute("aria-busy")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const openSeq = state.openSeq;
|
||||||
|
if (!await renderPage(pageIndex, { fit: false, scroll: false })) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await renderPage(pageIndex, { fit: false, scroll: false });
|
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
highlightSignatureStamp(stamp);
|
if (openSeq !== state.openSeq || pageIndex !== state.pageIndex) {
|
||||||
setStatus(`签名已定位 第 ${stamp.page} 页`);
|
return;
|
||||||
|
}
|
||||||
|
highlightPageRegion(region);
|
||||||
|
setStatus(`${label}已定位 第 ${region.page} 页`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlightSignatureStamp(stamp) {
|
function highlightPageRegion(region) {
|
||||||
clearStampHighlights();
|
clearRegionHighlights();
|
||||||
const pageIndex = (stamp.page || 0) - 1;
|
const pageIndex = region.page - 1;
|
||||||
const shell = pageShell(pageIndex);
|
const shell = pageShell(pageIndex);
|
||||||
if (!shell || !stamp.width || !stamp.height) {
|
if (!shell || !(region.width > 0 && region.height > 0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mark = document.createElement("div");
|
const mark = document.createElement("div");
|
||||||
mark.className = "stamp-highlight";
|
mark.className = "region-highlight";
|
||||||
mark.style.left = `${stamp.x * MM_TO_PX * state.scale}px`;
|
mark.style.left = `${region.x * MM_TO_PX * state.scale}px`;
|
||||||
mark.style.top = `${stamp.y * MM_TO_PX * state.scale}px`;
|
mark.style.top = `${region.y * MM_TO_PX * state.scale}px`;
|
||||||
mark.style.width = `${stamp.width * MM_TO_PX * state.scale}px`;
|
mark.style.width = `${region.width * MM_TO_PX * state.scale}px`;
|
||||||
mark.style.height = `${stamp.height * MM_TO_PX * state.scale}px`;
|
mark.style.height = `${region.height * MM_TO_PX * state.scale}px`;
|
||||||
shell.append(mark);
|
shell.append(mark);
|
||||||
mark.scrollIntoView({ block: "nearest", inline: "nearest" });
|
mark.scrollIntoView({ block: "nearest", inline: "nearest" });
|
||||||
window.setTimeout(() => mark.remove(), 1800);
|
window.setTimeout(() => mark.remove(), 1800);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearStampHighlights() {
|
function clearRegionHighlights() {
|
||||||
for (const mark of el.svgHost.querySelectorAll(".stamp-highlight")) {
|
for (const mark of el.svgHost.querySelectorAll(".region-highlight")) {
|
||||||
mark.remove();
|
mark.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderAnnotations() {
|
||||||
|
const annotations = (state.doc?.annotations || []).filter((annotation) => annotation.visible);
|
||||||
|
el.annotationPanel.hidden = !annotations.length;
|
||||||
|
el.annotationList.replaceChildren();
|
||||||
|
const types = { Link: "链接", Path: "路径", Highlight: "高亮", Stamp: "印章", Watermark: "水印" };
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
for (const annotation of annotations) {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "annotation-row";
|
||||||
|
const head = document.createElement("div");
|
||||||
|
head.className = "annotation-head";
|
||||||
|
const name = document.createElement("button");
|
||||||
|
name.type = "button";
|
||||||
|
name.className = "info-name-button";
|
||||||
|
name.textContent = `第 ${annotation.page} 页`;
|
||||||
|
name.disabled = !state.renderAnnotations || annotation.width <= 0 || annotation.height <= 0;
|
||||||
|
name.title = state.renderAnnotations ? "定位注解" : "注解已隐藏";
|
||||||
|
name.addEventListener("click", () => focusPageRegion(annotation, "注解"));
|
||||||
|
const badge = fontBadge(types[annotation.type] || "注解", "");
|
||||||
|
head.append(badge, name);
|
||||||
|
row.append(head);
|
||||||
|
appendInfoLine(row, "内容", annotation.remark);
|
||||||
|
appendInfoLine(row, "作者", annotation.creator);
|
||||||
|
appendInfoLine(row, "时间", formatDocumentTime(annotation.lastModDate));
|
||||||
|
fragment.append(row);
|
||||||
|
}
|
||||||
|
el.annotationList.append(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
function renderDocumentFonts() {
|
function renderDocumentFonts() {
|
||||||
const fonts = state.doc?.fonts || [];
|
const fonts = state.doc?.fonts || [];
|
||||||
el.docFontList.replaceChildren();
|
el.docFontList.replaceChildren();
|
||||||
@@ -2363,7 +2398,7 @@ function renderDocumentFonts() {
|
|||||||
const resultText = fontResult(font);
|
const resultText = fontResult(font);
|
||||||
const metaText = fontMeta(font);
|
const metaText = fontMeta(font);
|
||||||
|
|
||||||
head.append(name, badges);
|
head.append(badges, name);
|
||||||
row.append(head);
|
row.append(head);
|
||||||
appendFontDetail(row, metaText);
|
appendFontDetail(row, metaText);
|
||||||
appendFontDetail(row, resultText);
|
appendFontDetail(row, resultText);
|
||||||
@@ -2452,7 +2487,7 @@ function setContinuous(continuous) {
|
|||||||
function toggleContinuous() {
|
function toggleContinuous() {
|
||||||
const anchor = state.fitMode === "free" ? scaleAnchor() : null;
|
const anchor = state.fitMode === "free" ? scaleAnchor() : null;
|
||||||
setContinuous(!state.continuous);
|
setContinuous(!state.continuous);
|
||||||
clearStampHighlights();
|
clearRegionHighlights();
|
||||||
if (state.fitMode === "free") {
|
if (state.fitMode === "free") {
|
||||||
restoreScaleAnchor(anchor);
|
restoreScaleAnchor(anchor);
|
||||||
} else {
|
} else {
|
||||||
@@ -2541,7 +2576,7 @@ function setScale(nextScale, updateStatus = true, fitMode = "free") {
|
|||||||
state.fitMode = fitMode;
|
state.fitMode = fitMode;
|
||||||
state.scale = scale;
|
state.scale = scale;
|
||||||
if (layoutChanged) {
|
if (layoutChanged) {
|
||||||
clearStampHighlights();
|
clearRegionHighlights();
|
||||||
layoutPages();
|
layoutPages();
|
||||||
}
|
}
|
||||||
updateFitSpace();
|
updateFitSpace();
|
||||||
|
|||||||
+20
-18
@@ -114,24 +114,25 @@ type Session struct {
|
|||||||
|
|
||||||
// DocumentInfo 文档信息
|
// DocumentInfo 文档信息
|
||||||
type DocumentInfo struct {
|
type DocumentInfo struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
DocType string `json:"docType"`
|
DocType string `json:"docType"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Author string `json:"author"`
|
Author string `json:"author"`
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
CreationDate string `json:"creationDate"`
|
CreationDate string `json:"creationDate"`
|
||||||
ModDate string `json:"modDate"`
|
ModDate string `json:"modDate"`
|
||||||
PageCount int `json:"pageCount"`
|
PageCount int `json:"pageCount"`
|
||||||
FontCount int `json:"fontCount"`
|
FontCount int `json:"fontCount"`
|
||||||
SignatureCount int `json:"signatureCount"`
|
SignatureCount int `json:"signatureCount"`
|
||||||
SignatureError string `json:"signatureError,omitempty"`
|
SignatureError string `json:"signatureError,omitempty"`
|
||||||
AttachmentError string `json:"attachmentError,omitempty"`
|
AttachmentError string `json:"attachmentError,omitempty"`
|
||||||
Attachments []AttachmentInfo `json:"attachments,omitempty"`
|
Attachments []AttachmentInfo `json:"attachments,omitempty"`
|
||||||
Fonts []FontInfo `json:"fonts"`
|
Fonts []FontInfo `json:"fonts"`
|
||||||
Signatures []SignatureInfo `json:"signatures"`
|
Signatures []SignatureInfo `json:"signatures"`
|
||||||
Pages []PageInfo `json:"pages"`
|
Annotations []ofdgo.AnnotationInfo `json:"annotations,omitempty"`
|
||||||
Outlines []OutlineInfo `json:"outlines,omitempty"`
|
Pages []PageInfo `json:"pages"`
|
||||||
DetailsPending bool `json:"detailsPending,omitempty"`
|
Outlines []OutlineInfo `json:"outlines,omitempty"`
|
||||||
|
DetailsPending bool `json:"detailsPending,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutlineInfo 目录节点信息
|
// OutlineInfo 目录节点信息
|
||||||
@@ -320,6 +321,7 @@ func (s *Session) Info() DocumentInfo {
|
|||||||
Pages: make([]PageInfo, 0, len(s.doc.Pages.Page)),
|
Pages: make([]PageInfo, 0, len(s.doc.Pages.Page)),
|
||||||
Outlines: s.doc.OutlineInfos(),
|
Outlines: s.doc.OutlineInfos(),
|
||||||
}
|
}
|
||||||
|
info.Annotations, _ = s.Reader.AnnotationInfos()
|
||||||
if attachments, err := s.Reader.Attachments(); err == nil {
|
if attachments, err := s.Reader.Attachments(); err == nil {
|
||||||
for _, attachment := range attachments {
|
for _, attachment := range attachments {
|
||||||
if attachment.Visible {
|
if attachment.Visible {
|
||||||
|
|||||||
@@ -42,9 +42,27 @@ type Annotation struct {
|
|||||||
Creator string `xml:"Creator,attr"`
|
Creator string `xml:"Creator,attr"`
|
||||||
LastModDate string `xml:"LastModDate,attr"`
|
LastModDate string `xml:"LastModDate,attr"`
|
||||||
Visible *bool `xml:"Visible,attr"`
|
Visible *bool `xml:"Visible,attr"`
|
||||||
|
Remark string `xml:"Remark"`
|
||||||
Appearance Appearance
|
Appearance Appearance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnnotationInfo 注释信息,Page从1开始,位置和尺寸使用页面毫米坐标
|
||||||
|
type AnnotationInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageID string `json:"pageId"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Subtype string `json:"subtype,omitempty"`
|
||||||
|
Creator string `json:"creator,omitempty"`
|
||||||
|
LastModDate string `json:"lastModDate,omitempty"`
|
||||||
|
Remark string `json:"remark,omitempty"`
|
||||||
|
Visible bool `json:"visible"`
|
||||||
|
X float64 `json:"x"`
|
||||||
|
Y float64 `json:"y"`
|
||||||
|
Width float64 `json:"width"`
|
||||||
|
Height float64 `json:"height"`
|
||||||
|
}
|
||||||
|
|
||||||
// Appearance 注释外观
|
// Appearance 注释外观
|
||||||
type Appearance struct {
|
type Appearance struct {
|
||||||
Boundary string `xml:"Boundary,attr"`
|
Boundary string `xml:"Boundary,attr"`
|
||||||
@@ -55,6 +73,37 @@ type Appearance struct {
|
|||||||
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
|
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnnotationInfos 按页面顺序获取注释信息,包含不可见注释
|
||||||
|
// 返回: []AnnotationInfo 注释信息, error 错误信息
|
||||||
|
func (r *Reader) AnnotationInfos() ([]AnnotationInfo, error) {
|
||||||
|
doc, err := r.Doc()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var infos []AnnotationInfo
|
||||||
|
for index, page := range doc.Pages.Page {
|
||||||
|
for _, annotation := range r.Annots[page.ID] {
|
||||||
|
box, _ := ParseBox(annotation.Appearance.Boundary)
|
||||||
|
infos = append(infos, AnnotationInfo{
|
||||||
|
ID: annotation.ID,
|
||||||
|
Page: index + 1,
|
||||||
|
PageID: page.ID,
|
||||||
|
Type: annotation.Type,
|
||||||
|
Subtype: annotation.Subtype,
|
||||||
|
Creator: annotation.Creator,
|
||||||
|
LastModDate: annotation.LastModDate,
|
||||||
|
Remark: annotation.Remark,
|
||||||
|
Visible: annotation.Visible == nil || *annotation.Visible,
|
||||||
|
X: box.X,
|
||||||
|
Y: box.Y,
|
||||||
|
Width: box.W,
|
||||||
|
Height: box.H,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return infos, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseAnnotations 解析注释文件
|
// parseAnnotations 解析注释文件
|
||||||
// 入参: doc 文档结构
|
// 入参: doc 文档结构
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
|
|||||||
Reference in new issue
Block a user