mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
8de701dbd4
commit
d37f41cd90
21 files changed
+280
-57
No files matched your search
@@ -597,6 +597,7 @@
|
|||||||
<button id="infoButton" class="small-button" type="button" aria-label="修改文档信息" aria-haspopup="dialog" aria-controls="infoPanel" hidden>修改</button>
|
<button id="infoButton" class="small-button" type="button" aria-label="修改文档信息" aria-haspopup="dialog" aria-controls="infoPanel" hidden>修改</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="editNotice" class="font-permission-hint" role="status" hidden></div>
|
<div id="editNotice" class="font-permission-hint" role="status" hidden></div>
|
||||||
|
<select id="documentSelect" class="document-select" aria-label="选择文档" title="切换文档" hidden></select>
|
||||||
<dl class="meta-list">
|
<dl class="meta-list">
|
||||||
<div>
|
<div>
|
||||||
<dt>文件</dt>
|
<dt>文件</dt>
|
||||||
|
|||||||
@@ -1516,6 +1516,15 @@ body[data-hide-meta] .meta-panel {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.document-select {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
height: 28px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 0 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.app-panel {
|
.app-panel {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
|
|||||||
+54
-2
@@ -51,6 +51,7 @@ const state = {
|
|||||||
editorInfo: null,
|
editorInfo: null,
|
||||||
editorViews: new Map(),
|
editorViews: new Map(),
|
||||||
savedRevision: null,
|
savedRevision: null,
|
||||||
|
packageDirty: false,
|
||||||
encryptionDirty: false,
|
encryptionDirty: false,
|
||||||
insertObject: null,
|
insertObject: null,
|
||||||
importPageCount: 0,
|
importPageCount: 0,
|
||||||
@@ -231,6 +232,7 @@ const el = {
|
|||||||
textFontAdd: document.querySelector("#textFontAdd"),
|
textFontAdd: document.querySelector("#textFontAdd"),
|
||||||
paragraphButton: document.querySelector("#paragraphButton"),
|
paragraphButton: document.querySelector("#paragraphButton"),
|
||||||
infoButton: document.querySelector("#infoButton"),
|
infoButton: document.querySelector("#infoButton"),
|
||||||
|
documentSelect: document.querySelector("#documentSelect"),
|
||||||
infoPanel: document.querySelector("#infoPanel"),
|
infoPanel: document.querySelector("#infoPanel"),
|
||||||
infoForm: document.querySelector("#infoForm"),
|
infoForm: document.querySelector("#infoForm"),
|
||||||
infoTitle: document.querySelector("#infoTitle"),
|
infoTitle: document.querySelector("#infoTitle"),
|
||||||
@@ -611,10 +613,12 @@ const canvasEditor = new CanvasEditor(el.viewerPanel, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function updatePendingChanges() {
|
function updatePendingChanges() {
|
||||||
setDirty(state.encryptionDirty || Boolean(state.editorInfo) && (state.editorInfo.revision !== state.savedRevision
|
setDirty(state.packageDirty || state.encryptionDirty || Boolean(state.editorInfo) && (state.editorInfo.revision !== state.savedRevision
|
||||||
|| canvasEditor.textChanged() || canvasEditor.cropChanged() || canvasEditor.nudgeChanged()));
|
|| canvasEditor.textChanged() || canvasEditor.cropChanged() || canvasEditor.nudgeChanged()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
el.documentSelect.addEventListener("change", () => switchDocument(Number(el.documentSelect.value)));
|
||||||
|
|
||||||
el.editorTools.addEventListener("pointerdown", (event) => {
|
el.editorTools.addEventListener("pointerdown", (event) => {
|
||||||
if ((canvasEditor.input || canvasEditor.nudge) && event.target.closest("button")) event.preventDefault();
|
if ((canvasEditor.input || canvasEditor.nudge) && event.target.closest("button")) event.preventDefault();
|
||||||
});
|
});
|
||||||
@@ -1929,7 +1933,40 @@ 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(state.encryptionDirty || doc.revision !== state.savedRevision);
|
setDirty(state.packageDirty || state.encryptionDirty || doc.revision !== state.savedRevision);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function switchDocument(index) {
|
||||||
|
if (!state.doc || document.body.hasAttribute("aria-busy") || state.exporting || formDialogOpen()) return;
|
||||||
|
const previous = state.doc.documentIndex || 0;
|
||||||
|
if (index === previous) return;
|
||||||
|
if (!await canvasEditor.commitNudge() || !await canvasEditor.commitText() || !await canvasEditor.commitCrop()) {
|
||||||
|
el.documentSelect.value = String(previous);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const openSeq = ++state.openSeq;
|
||||||
|
setBusy(true, "正在切换文档");
|
||||||
|
try {
|
||||||
|
const doc = await callWASM("ofdgoSelectDocument", index);
|
||||||
|
if (openSeq !== state.openSeq) return;
|
||||||
|
state.packageDirty ||= state.dirty;
|
||||||
|
state.savedRevision = doc.revision;
|
||||||
|
state.editorViews.clear();
|
||||||
|
state.objectClipboard = state.styleClipboard = null;
|
||||||
|
resetCompositeScope();
|
||||||
|
canvasEditor.clear();
|
||||||
|
canvasEditor.setTool("");
|
||||||
|
state.ofdBytes = null;
|
||||||
|
setEditorInfo(doc);
|
||||||
|
await openDocument({ doc, openSeq, skipAutoFonts: true, resetScroll: true, clearSelection: true, fitMode: state.fitMode, scale: state.scale });
|
||||||
|
} catch (error) {
|
||||||
|
if (openSeq === state.openSeq) {
|
||||||
|
el.documentSelect.value = String(previous);
|
||||||
|
showError(error, false);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (openSeq === state.openSeq) setBusy(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function editorLocation() {
|
function editorLocation() {
|
||||||
@@ -2110,6 +2147,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.packageDirty = false;
|
||||||
state.encryptionDirty = false;
|
state.encryptionDirty = false;
|
||||||
setEditorInfo(doc);
|
setEditorInfo(doc);
|
||||||
el.createPanel.close();
|
el.createPanel.close();
|
||||||
@@ -3616,6 +3654,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.packageDirty = false;
|
||||||
state.encryptionDirty = false;
|
state.encryptionDirty = false;
|
||||||
canvasEditor.clear();
|
canvasEditor.clear();
|
||||||
setDirty(false);
|
setDirty(false);
|
||||||
@@ -5299,6 +5338,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.packageDirty = false;
|
||||||
state.encryptionDirty = false;
|
state.encryptionDirty = false;
|
||||||
setDirty(false);
|
setDirty(false);
|
||||||
}
|
}
|
||||||
@@ -7383,6 +7423,16 @@ function renderMeta(keepDetails = false) {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
const doc = state.doc || {};
|
const doc = state.doc || {};
|
||||||
|
el.documentSelect.hidden = (doc.documents?.length || 0) < 2;
|
||||||
|
renderMetaContent(el.documentSelect, doc.documents || [], () => {
|
||||||
|
el.documentSelect.replaceChildren(...(doc.documents || []).map(entry => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = String(entry.index);
|
||||||
|
option.textContent = `${entry.index + 1} · ${entry.title || "未命名"}`;
|
||||||
|
return option;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
el.documentSelect.value = String(doc.documentIndex || 0);
|
||||||
el.metaPanel.setAttribute("aria-busy", String(!!doc.detailsPending));
|
el.metaPanel.setAttribute("aria-busy", String(!!doc.detailsPending));
|
||||||
document.title = `OFDGo WebUI - ${state.fileName}`;
|
document.title = `OFDGo WebUI - ${state.fileName}`;
|
||||||
el.metaFile.textContent = state.fileName;
|
el.metaFile.textContent = state.fileName;
|
||||||
@@ -8200,6 +8250,7 @@ function updateControls() {
|
|||||||
el.attachmentAdd.disabled = !state.editing || !state.ready || state.exporting;
|
el.attachmentAdd.disabled = !state.editing || !state.ready || state.exporting;
|
||||||
renderMetaContent(el.attachmentList, [state.doc?.attachments, state.doc?.attachmentError, state.editing], renderAttachments);
|
renderMetaContent(el.attachmentList, [state.doc?.attachments, state.doc?.attachmentError, state.editing], renderAttachments);
|
||||||
const hasDoc = Boolean(state.doc);
|
const hasDoc = Boolean(state.doc);
|
||||||
|
el.documentSelect.disabled = !hasDoc || !state.ready || state.exporting || document.body.hasAttribute("aria-busy");
|
||||||
updateDisplayControls();
|
updateDisplayControls();
|
||||||
updateEditorTools();
|
updateEditorTools();
|
||||||
const pageCount = state.doc ? state.doc.pageCount : 0;
|
const pageCount = state.doc ? state.doc.pageCount : 0;
|
||||||
@@ -8403,6 +8454,7 @@ function showError(err, empty = !state.doc) {
|
|||||||
|
|
||||||
function setBusy(busy, text = "", percent = 0, status = "") {
|
function setBusy(busy, text = "", percent = 0, status = "") {
|
||||||
document.body.toggleAttribute("aria-busy", busy);
|
document.body.toggleAttribute("aria-busy", busy);
|
||||||
|
el.documentSelect.disabled = busy || !state.doc || !state.ready || state.exporting;
|
||||||
renderSecurity();
|
renderSecurity();
|
||||||
updateDisplayControls();
|
updateDisplayControls();
|
||||||
el.editButton.disabled = busy || Boolean(sealPreview) || !state.doc || !state.ready || state.exporting;
|
el.editButton.disabled = busy || Boolean(sealPreview) || !state.doc || !state.ready || state.exporting;
|
||||||
|
|||||||
@@ -536,6 +536,12 @@ export class CanvasEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start(event) {
|
start(event) {
|
||||||
|
if (event.target === this.viewer) {
|
||||||
|
const rect = this.viewer.getBoundingClientRect();
|
||||||
|
const left = rect.left + (this.viewer.clientLeft || 0), top = rect.top + (this.viewer.clientTop || 0);
|
||||||
|
if (event.clientX < left || event.clientX >= left + this.viewer.clientWidth
|
||||||
|
|| event.clientY < top || event.clientY >= top + this.viewer.clientHeight) return;
|
||||||
|
}
|
||||||
if (this.nudge) {
|
if (this.nudge) {
|
||||||
this.commitNudge();
|
this.commitNudge();
|
||||||
return;
|
return;
|
||||||
|
|||||||
+24
-1
@@ -184,6 +184,7 @@ func (r *browserReaderAt) ReadAt(data []byte, offset int64) (int, error) {
|
|||||||
// RunWASM 注册浏览器WASM接口并阻塞运行
|
// RunWASM 注册浏览器WASM接口并阻塞运行
|
||||||
func RunWASM() {
|
func RunWASM() {
|
||||||
registerCallback("ofdgoOpen", openDocument)
|
registerCallback("ofdgoOpen", openDocument)
|
||||||
|
registerCallback("ofdgoSelectDocument", selectDocument)
|
||||||
registerAsyncCallback("ofdgoConvertPDF", convertPDFDocument)
|
registerAsyncCallback("ofdgoConvertPDF", convertPDFDocument)
|
||||||
registerAsyncCallback("ofdgoConvertFile", convertFile)
|
registerAsyncCallback("ofdgoConvertFile", convertFile)
|
||||||
registerAsyncCallback("ofdgoPackFiles", packFiles)
|
registerAsyncCallback("ofdgoPackFiles", packFiles)
|
||||||
@@ -3359,6 +3360,27 @@ func changeAttachment(args []js.Value) (any, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selectDocument 切换包内文档,保留编辑快照并清除跨文档剪贴板
|
||||||
|
// 入参: args 文档索引
|
||||||
|
// 返回: any 文档信息, error 错误信息
|
||||||
|
func selectDocument(args []js.Value) (any, error) {
|
||||||
|
if currentSession == nil || len(args) == 0 {
|
||||||
|
return nil, fmt.Errorf("ofd document is not opened")
|
||||||
|
}
|
||||||
|
session, err := currentSession.Document(args[0].Int())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_ = currentSession.Close()
|
||||||
|
currentSession, currentEditor = session, session.editor
|
||||||
|
if pendingImport != nil {
|
||||||
|
_ = pendingImport.Close()
|
||||||
|
pendingImport = nil
|
||||||
|
}
|
||||||
|
copiedObjects, copiedStyle = nil, nil
|
||||||
|
return editorSummary(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// editDocument 将已打开文档接入编辑器,沿用页面、资源及字体配置
|
// editDocument 将已打开文档接入编辑器,沿用页面、资源及字体配置
|
||||||
// 入参: args 当前页索引,省略时使用首页
|
// 入参: args 当前页索引,省略时使用首页
|
||||||
// 返回: any 编辑状态, error 错误信息
|
// 返回: any 编辑状态, error 错误信息
|
||||||
@@ -3367,6 +3389,7 @@ func editDocument(args []js.Value) (any, error) {
|
|||||||
return nil, fmt.Errorf("ofd document is not opened")
|
return nil, fmt.Errorf("ofd document is not opened")
|
||||||
}
|
}
|
||||||
if currentEditor != nil {
|
if currentEditor != nil {
|
||||||
|
currentSession.editing = true
|
||||||
return editorSummary(), nil
|
return editorSummary(), nil
|
||||||
}
|
}
|
||||||
editor, err := currentSession.Reader.Editor()
|
editor, err := currentSession.Reader.Editor()
|
||||||
@@ -3952,7 +3975,7 @@ func verifySignatures(args []js.Value) (any, error) {
|
|||||||
Certificates: append(files["certificates"], files["roots"]...), Required: policy.RequireRevocation, VerifyTime: &now,
|
Certificates: append(files["certificates"], files["roots"]...), Required: policy.RequireRevocation, VerifyTime: &now,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
reports, err := currentSession.Reader.VerifySignatures(options...)
|
reports, err := currentSession.Reader.VerifyDocumentSignatures(currentSession.Reader.DocumentIndex(), options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-1
@@ -72,6 +72,8 @@ type pageBoxInfo struct {
|
|||||||
|
|
||||||
// DocumentInfo 文档信息
|
// DocumentInfo 文档信息
|
||||||
type DocumentInfo struct {
|
type DocumentInfo struct {
|
||||||
|
DocumentIndex int `json:"documentIndex"`
|
||||||
|
Documents []DocumentEntry `json:"documents,omitempty"`
|
||||||
Encryption EncryptionInfo `json:"encryption"`
|
Encryption EncryptionInfo `json:"encryption"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
DocType string `json:"docType"`
|
DocType string `json:"docType"`
|
||||||
@@ -96,6 +98,12 @@ type DocumentInfo struct {
|
|||||||
DetailsPending bool `json:"detailsPending,omitempty"`
|
DetailsPending bool `json:"detailsPending,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DocumentEntry 包内文档入口,不预先解析其他文档的页面和资源
|
||||||
|
type DocumentEntry struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
// DocumentDetails 不含页面数组的文档补充信息
|
// DocumentDetails 不含页面数组的文档补充信息
|
||||||
type DocumentDetails struct {
|
type DocumentDetails struct {
|
||||||
FontCount int `json:"fontCount"`
|
FontCount int `json:"fontCount"`
|
||||||
@@ -287,6 +295,45 @@ func (s *Session) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Document 创建指定文档的会话,保留编辑内容和字体配置,不复用页面缓存
|
||||||
|
// 不关闭原会话,输入文件的生命周期由原会话管理
|
||||||
|
// 入参: index 文档索引,从0开始
|
||||||
|
// 返回: *Session 文档会话, error 错误信息
|
||||||
|
func (s *Session) Document(index int) (*Session, error) {
|
||||||
|
reader := s.Reader
|
||||||
|
if s.editor != nil {
|
||||||
|
var err error
|
||||||
|
reader, err = s.editor.Reader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader, err := reader.Document(index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
next, err := newSession(reader, OpenOptions{RenderAnnotations: s.Renderer.RenderAnnotations})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ofdgo.WithRenderBackends(s.Renderer.Backends())(next.Renderer)
|
||||||
|
next.fontFS = s.fontFS
|
||||||
|
if next.fontFS != nil {
|
||||||
|
next.Renderer.SetFontFS(next.fontFS)
|
||||||
|
}
|
||||||
|
next.editor, err = reader.Editor()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
next.editor.SetHistoryLimit(100)
|
||||||
|
if next.fontFS != nil {
|
||||||
|
next.editor.SetFontFS(next.fontFS)
|
||||||
|
}
|
||||||
|
next.editor.SetRenderBackends(next.Renderer.Backends())
|
||||||
|
next.editing = true
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetFonts 更新字体配置并保留文档、页面和验签结果
|
// SetFonts 更新字体配置并保留文档、页面和验签结果
|
||||||
// 入参: fonts 字体文件列表
|
// 入参: fonts 字体文件列表
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
@@ -376,6 +423,7 @@ func (s *Session) SearchPage(index int, query string) ([]ofdgo.TextMatch, error)
|
|||||||
// 返回: DocumentInfo 文档信息
|
// 返回: DocumentInfo 文档信息
|
||||||
func (s *Session) Summary() DocumentInfo {
|
func (s *Session) Summary() DocumentInfo {
|
||||||
info := DocumentInfo{
|
info := DocumentInfo{
|
||||||
|
DocumentIndex: s.Reader.DocumentIndex(),
|
||||||
Encryption: s.encryptionInfo(),
|
Encryption: s.encryptionInfo(),
|
||||||
Version: s.Reader.Version(),
|
Version: s.Reader.Version(),
|
||||||
DocType: s.Reader.DocType(),
|
DocType: s.Reader.DocType(),
|
||||||
@@ -384,6 +432,9 @@ func (s *Session) Summary() DocumentInfo {
|
|||||||
Outlines: s.doc.OutlineInfos(),
|
Outlines: s.doc.OutlineInfos(),
|
||||||
DetailsPending: true,
|
DetailsPending: true,
|
||||||
}
|
}
|
||||||
|
for index, body := range s.Reader.OFD.DocBody {
|
||||||
|
info.Documents = append(info.Documents, DocumentEntry{Index: index, Title: body.DocInfo.Title})
|
||||||
|
}
|
||||||
if docInfo, err := s.Reader.DocInfo(); err == nil && docInfo != nil {
|
if docInfo, err := s.Reader.DocInfo(); err == nil && docInfo != nil {
|
||||||
info.Title = docInfo.Title
|
info.Title = docInfo.Title
|
||||||
info.Author = docInfo.Author
|
info.Author = docInfo.Author
|
||||||
@@ -788,7 +839,7 @@ func (s *Session) signatureInfos() ([]SignatureInfo, error) {
|
|||||||
return s.signatures, s.signatureError
|
return s.signatures, s.signatureError
|
||||||
}
|
}
|
||||||
s.signaturesRead = true
|
s.signaturesRead = true
|
||||||
reports, err := s.Reader.VerifySignatures()
|
reports, err := s.Reader.VerifyDocumentSignatures(s.Reader.DocumentIndex())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.signatureError = err
|
s.signatureError = err
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ func (e *Editor) editAnnotations(index int, ids []string, edit func([]byte, *edi
|
|||||||
if len(patches) != 0 {
|
if len(patches) != 0 {
|
||||||
parts[name] = editorPatchXML(data, patches)
|
parts[name] = editorPatchXML(data, patches)
|
||||||
if remaining == 0 {
|
if remaining == 0 {
|
||||||
docName, err := editorPageLocation(reader, nil, "", "/"+cleanPackagePath(reader.OFD.DocBody[0].DocRoot))
|
docName, err := editorPageLocation(reader, nil, "", "/"+cleanPackagePath(reader.OFD.DocBody[reader.documentIndex].DocRoot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ func (e *Editor) appendAnnotations(index int, annotations []byte) error {
|
|||||||
}
|
}
|
||||||
parts := map[string][]byte{name: data, file: content}
|
parts := map[string][]byte{name: data, file: content}
|
||||||
if base.document.Annotations == "" {
|
if base.document.Annotations == "" {
|
||||||
docName := cleanPackagePath(reader.OFD.DocBody[0].DocRoot)
|
docName := cleanPackagePath(reader.OFD.DocBody[reader.documentIndex].DocRoot)
|
||||||
docData, err := reader.readFile(docName)
|
docData, err := reader.readFile(docName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -391,7 +391,7 @@ func (e *Editor) commitAnnotationParts(base *editorSource, parts map[string][]by
|
|||||||
}
|
}
|
||||||
files[name] = data
|
files[name] = data
|
||||||
}
|
}
|
||||||
reader := &Reader{Zip: base.reader.Zip, files: files}
|
reader := &Reader{Zip: base.reader.Zip, files: files, encryption: base.reader.encryption, documentIndex: base.reader.documentIndex}
|
||||||
if err := reader.initRoot(); err != nil {
|
if err := reader.initRoot(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func (e *Editor) PasteAnnotations(page int, selection *AnnotationSelection, dx,
|
|||||||
edit.source.annotationStates = states
|
edit.source.annotationStates = states
|
||||||
if len(selection.resources) != 0 {
|
if len(selection.resources) != 0 {
|
||||||
reader := edit.source.reader
|
reader := edit.source.reader
|
||||||
name := cleanPackagePath(reader.OFD.DocBody[0].DocRoot)
|
name := cleanPackagePath(reader.OFD.DocBody[reader.documentIndex].DocRoot)
|
||||||
data, err := reader.readFile(name)
|
data, err := reader.readFile(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (e *Editor) AddAttachment(name string, data []byte) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
entry = bytes.TrimPrefix(entry, []byte(xml.Header))
|
entry = bytes.TrimPrefix(entry, []byte(xml.Header))
|
||||||
docName := cleanPackagePath(base.reader.OFD.DocBody[0].DocRoot)
|
docName := cleanPackagePath(base.reader.OFD.DocBody[base.reader.documentIndex].DocRoot)
|
||||||
docData, err := base.reader.readFile(docName)
|
docData, err := base.reader.readFile(docName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -190,7 +190,7 @@ func (e *Editor) editAttachment(id string, change func([]byte, *editorXML, map[s
|
|||||||
if base == nil {
|
if base == nil {
|
||||||
return fmt.Errorf("attachment not found: %s", id)
|
return fmt.Errorf("attachment not found: %s", id)
|
||||||
}
|
}
|
||||||
name := cleanPackagePath(base.reader.OFD.DocBody[0].DocRoot)
|
name := cleanPackagePath(base.reader.OFD.DocBody[base.reader.documentIndex].DocRoot)
|
||||||
external := strings.TrimSpace(base.document.Attachments.Path) != ""
|
external := strings.TrimSpace(base.document.Attachments.Path) != ""
|
||||||
if external {
|
if external {
|
||||||
name = base.reader.ResPath(base.document.Attachments.Path)
|
name = base.reader.ResPath(base.document.Attachments.Path)
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ func (e *Editor) ImportObjects(source *Editor, sourcePage int, ids []string, pag
|
|||||||
return copied, err
|
return copied, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportPages 将来源主文档中的指定页面插入目标位置,保持输入顺序并作为一次撤销操作
|
// ImportPages 将来源当前文档中的指定页面插入目标位置,保持输入顺序并作为一次撤销操作
|
||||||
// 页面索引从0开始,at可等于当前页数;复制关联模板、注释、签章外观和实际引用的资源
|
// 页面索引从0开始,at可等于当前页数;复制关联模板、注释、签章外观和实际引用的资源
|
||||||
// 不导入来源元数据和目录,指向未选页面的跳转被移除;签名数据仅保留原始凭据,不代表合并后文档有效
|
// 不导入来源元数据和目录,指向未选页面的跳转被移除;签名数据仅保留原始凭据,不代表合并后文档有效
|
||||||
// 返回后可关闭来源Reader,目标原Reader的生命周期要求不变
|
// 返回后可关闭来源Reader,目标原Reader的生命周期要求不变
|
||||||
@@ -450,7 +450,7 @@ func (e *Editor) prepareImport(source *Reader, doc *Document, selected map[strin
|
|||||||
for _, resource := range e.resources {
|
for _, resource := range e.resources {
|
||||||
m.files[resource.name] = nil
|
m.files[resource.name] = nil
|
||||||
}
|
}
|
||||||
m.documentXML, err = m.readFile(source.ResPath(source.OFD.DocBody[0].DocRoot))
|
m.documentXML, err = m.readFile(source.ResPath(source.OFD.DocBody[source.documentIndex].DocRoot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -530,7 +530,7 @@ func (m *editorPageImport) readFile(name string) ([]byte, error) {
|
|||||||
// outlines 筛选指向导入页面的目录树并复用标准动作迁移
|
// outlines 筛选指向导入页面的目录树并复用标准动作迁移
|
||||||
// 返回: []byte 目录片段, error 错误信息
|
// 返回: []byte 目录片段, error 错误信息
|
||||||
func (m *editorPageImport) outlines() ([]byte, error) {
|
func (m *editorPageImport) outlines() ([]byte, error) {
|
||||||
name := m.reader.ResPath(m.reader.OFD.DocBody[0].DocRoot)
|
name := m.reader.ResPath(m.reader.OFD.DocBody[m.reader.documentIndex].DocRoot)
|
||||||
data, err := m.readFile(name)
|
data, err := m.readFile(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1132,7 +1132,7 @@ func (m *editorPageImport) attachment(id string) (string, error) {
|
|||||||
if _, exists := m.attachments[id]; exists {
|
if _, exists := m.attachments[id]; exists {
|
||||||
return m.id(id), nil
|
return m.id(id), nil
|
||||||
}
|
}
|
||||||
name := m.reader.ResPath(m.reader.OFD.DocBody[0].DocRoot)
|
name := m.reader.ResPath(m.reader.OFD.DocBody[m.reader.documentIndex].DocRoot)
|
||||||
if m.doc.Attachments.Path != "" {
|
if m.doc.Attachments.Path != "" {
|
||||||
name = m.reader.ResPath(m.doc.Attachments.Path)
|
name = m.reader.ResPath(m.doc.Attachments.Path)
|
||||||
}
|
}
|
||||||
@@ -1215,7 +1215,7 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
|
|||||||
files = make(map[string][]byte)
|
files = make(map[string][]byte)
|
||||||
}
|
}
|
||||||
maps.Copy(files, m.files)
|
maps.Copy(files, m.files)
|
||||||
name := base.reader.ResPath(base.reader.OFD.DocBody[0].DocRoot)
|
name := base.reader.ResPath(base.reader.OFD.DocBody[base.reader.documentIndex].DocRoot)
|
||||||
if file, ok := base.reader.packageFile(name); ok {
|
if file, ok := base.reader.packageFile(name); ok {
|
||||||
name = cleanPackagePath(file.Name)
|
name = cleanPackagePath(file.Name)
|
||||||
}
|
}
|
||||||
@@ -1318,10 +1318,10 @@ func (m *editorPageImport) merge(base *editorSource, refs []Page, annotations, s
|
|||||||
if file, ok := base.reader.packageFile(name); ok {
|
if file, ok := base.reader.packageFile(name); ok {
|
||||||
name = cleanPackagePath(file.Name)
|
name = cleanPackagePath(file.Name)
|
||||||
}
|
}
|
||||||
files[name] = editorXMLSetText(ofd, root.child("DocBody"), [][2]string{{"Signatures", loc}})
|
files[name] = editorXMLSetText(ofd, root.childAt("DocBody", base.reader.documentIndex), [][2]string{{"Signatures", loc}})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reader := &Reader{Zip: base.reader.Zip, files: files}
|
reader := &Reader{Zip: base.reader.Zip, files: files, encryption: base.reader.encryption, documentIndex: base.reader.documentIndex}
|
||||||
if err := reader.initRoot(); err != nil {
|
if err := reader.initRoot(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -29,7 +29,7 @@ func (e *Editor) sourceInfoXML() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, info, err := editorDocInfoXML(data, e.source.fallbackDocID)
|
data, info, err := editorDocInfoXML(data, e.source.fallbackDocID, e.source.reader.documentIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -50,16 +50,16 @@ func (e *Editor) sourceInfoXML() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return editorCustomDatas(data, root.child("DocBody").child("DocInfo"), after.CustomDatas)
|
return editorCustomDatas(data, root.childAt("DocBody", e.source.reader.documentIndex).child("DocInfo"), after.CustomDatas)
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorCreatorXML 更新首份文档的制作软件,移除原软件版本,保留其余根索引内容
|
// editorCreatorXML 更新指定文档的制作软件,移除原软件版本,保留其余根索引内容
|
||||||
// 入参: data 当前根索引XML, docID 缺少文档描述时使用的标识
|
// 入参: data 当前根索引XML, docID 缺少文档描述时使用的标识, index 文档索引
|
||||||
// 返回: []byte 修改后的XML, error 解析错误
|
// 返回: []byte 修改后的XML, error 解析错误
|
||||||
func editorCreatorXML(data []byte, docID string) ([]byte, error) {
|
func editorCreatorXML(data []byte, docID string, index int) ([]byte, error) {
|
||||||
data, info, err := editorDocInfoXML(data, docID)
|
data, info, err := editorDocInfoXML(data, docID, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -67,14 +67,14 @@ func editorCreatorXML(data []byte, docID string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// editorDocInfoXML 获取文档描述,修改缺少描述的源文件时补建标准容器及标识
|
// editorDocInfoXML 获取文档描述,修改缺少描述的源文件时补建标准容器及标识
|
||||||
// 入参: data 根索引XML, docID 缺少文档描述时使用的标识
|
// 入参: data 根索引XML, docID 缺少文档描述时使用的标识, index 文档索引
|
||||||
// 返回: []byte 根索引XML, *editorXML 描述节点, error 解析错误
|
// 返回: []byte 根索引XML, *editorXML 描述节点, error 解析错误
|
||||||
func editorDocInfoXML(data []byte, docID string) ([]byte, *editorXML, error) {
|
func editorDocInfoXML(data []byte, docID string, index int) ([]byte, *editorXML, error) {
|
||||||
root, err := parseEditorXML(data)
|
root, err := parseEditorXML(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
body := root.child("DocBody")
|
body := root.childAt("DocBody", index)
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return nil, nil, fmt.Errorf("document has no DocBody")
|
return nil, nil, fmt.Errorf("document has no DocBody")
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ func editorDocInfoXML(data []byte, docID string) ([]byte, *editorXML, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return data, root.child("DocBody").child("DocInfo"), nil
|
return data, root.childAt("DocBody", index).child("DocInfo"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorCustomDatas 更新自定义字段,保留原字段及容器的扩展内容
|
// editorCustomDatas 更新自定义字段,保留原字段及容器的扩展内容
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ type PageCapabilities struct {
|
|||||||
Resize bool
|
Resize bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editor 打开主文档进行保真编辑,不修改输入包,不以权限声明或签名限制编辑
|
// Editor 打开当前文档进行保真编辑,不修改输入包,不以权限声明或签名限制编辑
|
||||||
// 原权限和签名数据保留,修改受保护内容会使原签名失效,不重新签名
|
// 原权限和签名数据保留,修改受保护内容会使原签名失效,不重新签名
|
||||||
// 页面按需解析,未修改条目直接保留;编辑器及其Reader快照使用期间不得关闭输入Reader
|
// 页面按需解析,未修改条目直接保留;编辑器及其Reader快照使用期间不得关闭输入Reader
|
||||||
// 返回: *Editor 编辑器, error 错误信息
|
// 返回: *Editor 编辑器, error 错误信息
|
||||||
@@ -205,7 +205,7 @@ func (c ObjectCapabilities) editError() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cloneEditorReader 复用已验证的只读包索引,独立保存可变文档与读取缓存
|
// cloneEditorReader 复用已验证的只读包索引,独立保存可变文档与读取缓存
|
||||||
// 入参: reader 已加载主文档的阅读器
|
// 入参: reader 已加载当前文档的阅读器
|
||||||
// 返回: *Reader 不拥有输入文件的独立阅读器
|
// 返回: *Reader 不拥有输入文件的独立阅读器
|
||||||
func cloneEditorReader(reader *Reader) *Reader {
|
func cloneEditorReader(reader *Reader) *Reader {
|
||||||
next := *reader
|
next := *reader
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ func (e *Editor) outlineXML() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
if e.source != nil {
|
if e.source != nil {
|
||||||
reader := e.source.reader
|
reader := e.source.reader
|
||||||
data, err := reader.readFile(reader.ResPath(reader.OFD.DocBody[0].DocRoot))
|
data, err := reader.readFile(reader.ResPath(reader.OFD.DocBody[reader.documentIndex].DocRoot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (e *Editor) sourceParts(progress editorProgress) (map[string][]byte, error)
|
|||||||
}
|
}
|
||||||
pagesChanged := !slices.Equal(pageRefs, source.document.Pages.Page)
|
pagesChanged := !slices.Equal(pageRefs, source.document.Pages.Page)
|
||||||
if pagesChanged || len(resourceFiles) != 0 || len(parts) != 0 && e.maxID != source.document.CommonData.MaxUnitID {
|
if pagesChanged || len(resourceFiles) != 0 || len(parts) != 0 && e.maxID != source.document.CommonData.MaxUnitID {
|
||||||
name := reader.ResPath(reader.OFD.DocBody[0].DocRoot)
|
name := reader.ResPath(reader.OFD.DocBody[reader.documentIndex].DocRoot)
|
||||||
data, err := reader.readFile(name)
|
data, err := reader.readFile(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -199,7 +199,7 @@ func (e *Editor) sourceParts(progress editorProgress) (map[string][]byte, error)
|
|||||||
parts["OFD.xml"] = data
|
parts["OFD.xml"] = data
|
||||||
}
|
}
|
||||||
if e.outlines != nil {
|
if e.outlines != nil {
|
||||||
name := reader.ResPath(reader.OFD.DocBody[0].DocRoot)
|
name := reader.ResPath(reader.OFD.DocBody[reader.documentIndex].DocRoot)
|
||||||
data, ok := parts[name]
|
data, ok := parts[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
var err error
|
var err error
|
||||||
@@ -468,7 +468,7 @@ func (e *Editor) writeSource(writer io.Writer, fonts map[string][]byte, progress
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data, err = editorCreatorXML(data, e.source.fallbackDocID)
|
data, err = editorCreatorXML(data, e.source.fallbackDocID, reader.documentIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -592,7 +592,7 @@ func (e *Editor) sourceReader(progress editorProgress) (*Reader, error) {
|
|||||||
files = make(map[string][]byte)
|
files = make(map[string][]byte)
|
||||||
}
|
}
|
||||||
maps.Copy(files, parts)
|
maps.Copy(files, parts)
|
||||||
reader := &Reader{Zip: e.source.reader.Zip, files: files, encryption: e.encryption}
|
reader := &Reader{Zip: e.source.reader.Zip, files: files, encryption: e.encryption, documentIndex: e.source.reader.documentIndex}
|
||||||
if err := reader.initRoot(); err != nil {
|
if err := reader.initRoot(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// prunePageReferences 在输出快照中删除已移除页面的标准引用,不修改历史快照或其他文档
|
// prunePageReferences 在输出快照中删除已移除页面的标准引用,不修改历史快照或其他文档
|
||||||
// 沿主文档、页面、模板、资源、注释和签名索引检查,不进入附件、私有扩展和签名原始凭据
|
// 沿当前文档、页面、模板、资源、注释和签名索引检查,不进入附件、私有扩展和签名原始凭据
|
||||||
// 入参: parts 输出覆盖条目
|
// 入参: parts 输出覆盖条目
|
||||||
// 返回: error 读取或解析错误
|
// 返回: error 读取或解析错误
|
||||||
func (e *Editor) prunePageReferences(parts map[string][]byte) error {
|
func (e *Editor) prunePageReferences(parts map[string][]byte) error {
|
||||||
@@ -48,7 +48,7 @@ func (e *Editor) prunePageReferences(parts map[string][]byte) error {
|
|||||||
bookmarks[bookmark.Name] = true
|
bookmarks[bookmark.Name] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue := []string{reader.ResPath(reader.OFD.DocBody[0].DocRoot)}
|
queue := []string{reader.ResPath(reader.OFD.DocBody[reader.documentIndex].DocRoot)}
|
||||||
if name := e.source.document.Signatures; name != "" {
|
if name := e.source.document.Signatures; name != "" {
|
||||||
queue = append(queue, reader.ResPath(name))
|
queue = append(queue, reader.ResPath(name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,11 @@ type editorResourceRefs struct {
|
|||||||
// compactSourceResources 在保存副本中清理无引用字体、图片和已删除附件,并裁剪可完整确认用字的原有字体
|
// compactSourceResources 在保存副本中清理无引用字体、图片和已删除附件,并裁剪可完整确认用字的原有字体
|
||||||
// 按GB/T33190-2016附录A检查标识和路径引用,包括模板、注释、底纹、裁剪、版本和其他文档
|
// 按GB/T33190-2016附录A检查标识和路径引用,包括模板、注释、底纹、裁剪、版本和其他文档
|
||||||
// 全包检查保留孤立XML中的引用;未知命名空间、扩展数据或无法解析的XML使本次清理跳过
|
// 全包检查保留孤立XML中的引用;未知命名空间、扩展数据或无法解析的XML使本次清理跳过
|
||||||
|
// 多文档包保留原资源,避免独立文档的同号资源和共享文件被误删或裁剪
|
||||||
// 入参: parts 已修改和新增的包内条目, progress 保存进度回调
|
// 入参: parts 已修改和新增的包内条目, progress 保存进度回调
|
||||||
// 返回: map[string]bool 可移除的二进制条目, error 读取错误
|
// 返回: map[string]bool 可移除的二进制条目, error 读取错误
|
||||||
func (e *Editor) compactSourceResources(parts map[string][]byte, progress editorProgress) (map[string]bool, error) {
|
func (e *Editor) compactSourceResources(parts map[string][]byte, progress editorProgress) (map[string]bool, error) {
|
||||||
if e.output != nil && e.output.protected {
|
if e.source.reader.DocumentCount() > 1 || e.output != nil && e.output.protected {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if len(parts) == 0 && e.revision == 0 && (e.output == nil || e.output.options.Mode == CompressionUnchanged) {
|
if len(parts) == 0 && e.revision == 0 && (e.output == nil || e.output.options.Mode == CompressionUnchanged) {
|
||||||
|
|||||||
+11
-1
@@ -84,9 +84,19 @@ func parseEditorXML(data []byte) (*editorXML, error) {
|
|||||||
// 入参: name 节点名称
|
// 入参: name 节点名称
|
||||||
// 返回: *editorXML 节点,不存在时为nil
|
// 返回: *editorXML 节点,不存在时为nil
|
||||||
func (n *editorXML) child(name string) *editorXML {
|
func (n *editorXML) child(name string) *editorXML {
|
||||||
|
return n.childAt(name, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// childAt 查找指定序号的OFD直接子节点
|
||||||
|
// 入参: name 节点名称, index 从0开始的节点序号
|
||||||
|
// 返回: *editorXML 节点,不存在时为nil
|
||||||
|
func (n *editorXML) childAt(name string, index int) *editorXML {
|
||||||
for _, child := range n.children {
|
for _, child := range n.children {
|
||||||
if child.name.Local == name && (child.name.Space == n.name.Space || child.name.Space == ofdNamespace || child.name.Space == "") {
|
if child.name.Local == name && (child.name.Space == n.name.Space || child.name.Space == ofdNamespace || child.name.Space == "") {
|
||||||
return child
|
if index == 0 {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
index--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+39
-5
@@ -46,6 +46,7 @@ type Reader struct {
|
|||||||
compositeGraphicUnitCache map[string]*CompositeGraphicUnit
|
compositeGraphicUnitCache map[string]*CompositeGraphicUnit
|
||||||
pageHeaderCache map[string]PageContent
|
pageHeaderCache map[string]PageContent
|
||||||
doc *Document
|
doc *Document
|
||||||
|
documentIndex int
|
||||||
Stamps map[string][]Stamp
|
Stamps map[string][]Stamp
|
||||||
Annots map[string][]Annotation
|
Annots map[string][]Annotation
|
||||||
annotationFiles map[string][]string
|
annotationFiles map[string][]string
|
||||||
@@ -67,16 +68,49 @@ func (r *Reader) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doc 获取主文档结构
|
// DocumentCount 获取包内文档数量
|
||||||
|
// 返回: int 文档数量
|
||||||
|
func (r *Reader) DocumentCount() int {
|
||||||
|
if r.OFD == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(r.OFD.DocBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DocumentIndex 获取当前文档索引,从0开始
|
||||||
|
// 返回: int 文档索引
|
||||||
|
func (r *Reader) DocumentIndex() int {
|
||||||
|
return r.documentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Document 创建指定文档的独立阅读器,不改变当前文档或混用资源缓存
|
||||||
|
// 返回的阅读器不关闭输入文件,使用期间不得关闭持有输入文件的原阅读器
|
||||||
|
// 入参: index 文档索引,从0开始
|
||||||
|
// 返回: *Reader 文档阅读器, error 错误信息
|
||||||
|
func (r *Reader) Document(index int) (*Reader, error) {
|
||||||
|
if index < 0 || index >= r.DocumentCount() {
|
||||||
|
return nil, fmt.Errorf("document index %d out of range", index)
|
||||||
|
}
|
||||||
|
next := &Reader{Path: r.Path, Zip: r.Zip, files: r.files, encryption: r.encryption, documentIndex: index}
|
||||||
|
if err := next.initRoot(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := next.Doc(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doc 获取当前文档结构
|
||||||
// 返回: *Document 文档结构, error 错误信息
|
// 返回: *Document 文档结构, error 错误信息
|
||||||
func (r *Reader) Doc() (*Document, error) {
|
func (r *Reader) Doc() (*Document, error) {
|
||||||
if r.doc != nil {
|
if r.doc != nil {
|
||||||
return r.doc, nil
|
return r.doc, nil
|
||||||
}
|
}
|
||||||
if r.OFD == nil || len(r.OFD.DocBody) == 0 {
|
if r.OFD == nil || r.documentIndex < 0 || r.documentIndex >= len(r.OFD.DocBody) {
|
||||||
return nil, fmt.Errorf("no docbody found")
|
return nil, fmt.Errorf("no docbody found")
|
||||||
}
|
}
|
||||||
docAttr := r.OFD.DocBody[0]
|
docAttr := r.OFD.DocBody[r.documentIndex]
|
||||||
docRootPath := docAttr.DocRoot
|
docRootPath := docAttr.DocRoot
|
||||||
r.RootDir = path.Dir(docRootPath)
|
r.RootDir = path.Dir(docRootPath)
|
||||||
data, err := r.readFile(docRootPath)
|
data, err := r.readFile(docRootPath)
|
||||||
@@ -197,10 +231,10 @@ func (r *Reader) DocType() string {
|
|||||||
// DocInfo 获取文档元数据
|
// DocInfo 获取文档元数据
|
||||||
// 返回: *DocInfo 元数据, error 错误信息
|
// 返回: *DocInfo 元数据, error 错误信息
|
||||||
func (r *Reader) DocInfo() (*DocInfo, error) {
|
func (r *Reader) DocInfo() (*DocInfo, error) {
|
||||||
if r.OFD == nil || len(r.OFD.DocBody) == 0 {
|
if r.OFD == nil || r.documentIndex < 0 || r.documentIndex >= len(r.OFD.DocBody) {
|
||||||
return nil, fmt.Errorf("no docbody found")
|
return nil, fmt.Errorf("no docbody found")
|
||||||
}
|
}
|
||||||
return &r.OFD.DocBody[0].DocInfo, nil
|
return &r.OFD.DocBody[r.documentIndex].DocInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permissions 获取文档权限信息
|
// Permissions 获取文档权限信息
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ func (r *Reader) VerifyDocumentSignatures(index int, opts ...SignatureVerifyOpti
|
|||||||
}
|
}
|
||||||
body := r.OFD.DocBody[index]
|
body := r.OFD.DocBody[index]
|
||||||
options.DocIndex, options.DocRoot = index, r.signatureCoveragePath(body.DocRoot)
|
options.DocIndex, options.DocRoot = index, r.signatureCoveragePath(body.DocRoot)
|
||||||
if index == 0 && r.doc != nil && r.doc.Signatures == "" {
|
if index == r.documentIndex && r.doc != nil && r.doc.Signatures == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
data, err := r.readFile(body.DocRoot)
|
data, err := r.readFile(body.DocRoot)
|
||||||
@@ -316,7 +316,10 @@ func (r *Reader) VerifyDocumentSignatures(index int, opts ...SignatureVerifyOpti
|
|||||||
if doc.Signatures == "" {
|
if doc.Signatures == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
view := *r
|
view := &Reader{Zip: r.Zip, files: r.files, encryption: r.encryption, documentIndex: index}
|
||||||
|
if err := view.initRoot(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
view.doc, view.RootDir = &doc, path.Dir(body.DocRoot)
|
view.doc, view.RootDir = &doc, path.Dir(body.DocRoot)
|
||||||
sigListPath := view.ResPath(doc.Signatures)
|
sigListPath := view.ResPath(doc.Signatures)
|
||||||
data, err = view.readFile(sigListPath)
|
data, err = view.readFile(sigListPath)
|
||||||
|
|||||||
+45
-12
@@ -109,6 +109,12 @@ func SignEditorTo(writer io.Writer, editor *Editor, options SignatureWriteOption
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
if editor.source != nil {
|
||||||
|
r, err = r.Document(editor.source.reader.DocumentIndex())
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
r.encryption = editor.encryption
|
r.encryption = editor.encryption
|
||||||
options.progress = editorProgress(editor.OnWriteProgress)
|
options.progress = editorProgress(editor.OnWriteProgress)
|
||||||
return r.SignTo(writer, options)
|
return r.SignTo(writer, options)
|
||||||
@@ -167,7 +173,7 @@ func signatureWriteOutput(r *Reader, options SignatureWriteOptions) ([]byte, err
|
|||||||
if err := options.progress.report("sign", 0, 1); err != nil {
|
if err := options.progress.report("sign", 0, 1); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := signatureWritePackage(parts, options)
|
data, err := signatureWritePackage(parts, options, r.DocumentIndex())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -213,27 +219,49 @@ func signatureWriteParts(r *Reader) (map[string][]byte, error) {
|
|||||||
|
|
||||||
// signatureWritePackage 完成输出优化后计算签名,不改写已有签名保护的资源
|
// signatureWritePackage 完成输出优化后计算签名,不改写已有签名保护的资源
|
||||||
// 签名目录采用Sign_N兼容OFDRW容器,标识使用独立的sN签章域
|
// 签名目录采用Sign_N兼容OFDRW容器,标识使用独立的sN签章域
|
||||||
// 入参: parts 独立包文件, options 签署选项
|
// 入参: parts 独立包文件, options 签署选项, index 文档索引
|
||||||
// 返回: []byte 完整包, error 错误信息
|
// 返回: []byte 完整包, error 错误信息
|
||||||
func signatureWritePackage(parts map[string][]byte, options SignatureWriteOptions) ([]byte, error) {
|
func signatureWritePackage(parts map[string][]byte, options SignatureWriteOptions, index int) ([]byte, error) {
|
||||||
if err := options.Compression.Validate(); err != nil {
|
if err := options.Compression.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.Mode != SignatureAppend && options.Mode != SignatureReplace {
|
if options.Mode != SignatureAppend && options.Mode != SignatureReplace {
|
||||||
return nil, fmt.Errorf("unsupported signature write mode")
|
return nil, fmt.Errorf("unsupported signature write mode")
|
||||||
}
|
}
|
||||||
r := &Reader{files: parts}
|
r := &Reader{files: parts, documentIndex: index}
|
||||||
if err := r.initRoot(); err != nil {
|
if err := r.initRoot(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(r.OFD.DocBody) != 1 {
|
|
||||||
return nil, fmt.Errorf("signature writing requires exactly one document")
|
|
||||||
}
|
|
||||||
doc, err := r.Doc()
|
doc, err := r.Doc()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.Compression.Mode != CompressionUnchanged && doc.Signatures == "" {
|
protected := make(map[string][]byte)
|
||||||
|
for other := range r.OFD.DocBody {
|
||||||
|
if other == index {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
view, err := r.Document(other)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if view.doc.Signatures == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
reports, err := r.VerifyDocumentSignatures(other)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, report := range reports {
|
||||||
|
if report.Error != "" {
|
||||||
|
return nil, fmt.Errorf("cannot preserve document %d signature: %s", other, report.Error)
|
||||||
|
}
|
||||||
|
for _, ref := range report.References {
|
||||||
|
protected[ref.Path] = bytes.Clone(parts[ref.Path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Compression.Mode != CompressionUnchanged && doc.Signatures == "" && len(protected) == 0 {
|
||||||
images, err := r.Images(context.Background())
|
images, err := r.Images(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -271,7 +299,7 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
|
|||||||
delete(parts, name)
|
delete(parts, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r = &Reader{files: parts}
|
r = &Reader{files: parts, documentIndex: index}
|
||||||
if err := r.initRoot(); err != nil {
|
if err := r.initRoot(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -307,7 +335,7 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
|
|||||||
verifyOptions := append([]SignatureVerifyOption(nil), options.ExistingVerifyOptions...)
|
verifyOptions := append([]SignatureVerifyOption(nil), options.ExistingVerifyOptions...)
|
||||||
verifyOptions = append(verifyOptions, WithSignatureCert(identity.cert.Raw))
|
verifyOptions = append(verifyOptions, WithSignatureCert(identity.cert.Raw))
|
||||||
if options.Mode == SignatureAppend && oldCount > 0 {
|
if options.Mode == SignatureAppend && oldCount > 0 {
|
||||||
reports, err := r.VerifySignatures(verifyOptions...)
|
reports, err := r.VerifyDocumentSignatures(index, verifyOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -330,7 +358,7 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body := root.child("DocBody")
|
body := root.childAt("DocBody", index)
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return nil, fmt.Errorf("missing document body")
|
return nil, fmt.Errorf("missing document body")
|
||||||
}
|
}
|
||||||
@@ -411,6 +439,11 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for name, before := range protected {
|
||||||
|
if !bytes.Equal(before, parts[name]) {
|
||||||
|
return nil, fmt.Errorf("another document signature protects %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
output, err := signatureWriteZIP(parts, options.Compression)
|
output, err := signatureWriteZIP(parts, options.Compression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -419,7 +452,7 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reports, err := check.VerifySignatures(verifyOptions...)
|
reports, err := check.VerifyDocumentSignatures(index, verifyOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user