feat(功能强化): 新增注解查看与定位,优化签名及字体信息排版
build.yaml / build (push) Successful in 38s

This commit is contained in:
xiaoqidun committed 2026-09-12 20:53:21 +08:00
1 parent 8ddc2fdeed
commit 66f0d8a928
5 files changed
+189 -83

No files matched your search

+4
View File
@@ -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
View File
@@ -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;
} }
+98 -63
View File
@@ -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 || [];
if (!stamps.length) {
const name = document.createElement("div"); const name = document.createElement("div");
name.className = "signature-name"; name.className = "signature-name";
if (!stamps.length) {
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();
+2
View File
@@ -129,6 +129,7 @@ type DocumentInfo struct {
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"`
Annotations []ofdgo.AnnotationInfo `json:"annotations,omitempty"`
Pages []PageInfo `json:"pages"` Pages []PageInfo `json:"pages"`
Outlines []OutlineInfo `json:"outlines,omitempty"` Outlines []OutlineInfo `json:"outlines,omitempty"`
DetailsPending bool `json:"detailsPending,omitempty"` DetailsPending bool `json:"detailsPending,omitempty"`
@@ -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 {
+49
View File
@@ -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 错误信息