From c3f6b61dfdd74962a7c77f31566ae971216c6073 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sun, 11 Oct 2026 11:37:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=A4=9A=E6=96=87=E6=A1=A3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E6=B8=85=E7=90=86=E4=B8=8E=E5=8E=8B=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ofdgo_compression_fonts.go | 58 +++++++----------------------- ofdgo_editor_resource_scan.go | 6 ++-- ofdgo_editor_resource_scope.go | 66 ++++++++++++++++++++++++++++++++++ ofdgo_editor_resources.go | 33 +++++++++++++---- 4 files changed, 107 insertions(+), 56 deletions(-) create mode 100644 ofdgo_editor_resource_scope.go diff --git a/ofdgo_compression_fonts.go b/ofdgo_compression_fonts.go index ce2de31..b6361c0 100644 --- a/ofdgo_compression_fonts.go +++ b/ofdgo_compression_fonts.go @@ -17,18 +17,9 @@ package ofdgo import ( "context" "encoding/xml" - "maps" - "slices" "strings" ) -// compressionFontBinding 保存资源索引中的字体标识、文件路径及可裁剪状态 -type compressionFontBinding struct { - id string - file string - unsafe bool -} - // compressionFontUsage 按文档合并共享字体用字,版本编辑仅统计选中入口 // 入参: ctx 取消上下文, parts 输出改动, generated 自产页面引用, documents 文档入口, resources 资源索引, references 各文件引用, read 输出文件读取方法 // 返回: map[string]*editorFontUsage 各字体文件的用字,无法确定作用域时为空, error 读取或取消错误 @@ -41,11 +32,19 @@ func (r *Reader) compressionFontUsage(ctx context.Context, parts map[string][]by return nil, err } } - bindings := make(map[string][]compressionFontBinding) + scoped, err := r.resourceReferenceScopes(ctx, parts, generated, documents, resources, references, selected) + if err != nil || scoped == nil { + return nil, err + } + result := make(map[string]*editorFontUsage) for _, name := range resources { if err := ctx.Err(); err != nil { return nil, err } + refs := scoped[name] + if refs == nil { + continue + } data, err := read(name) if err != nil { return nil, err @@ -70,43 +69,10 @@ func (r *Reader) compressionFontUsage(ctx context.Context, parts map[string][]by location := editorResourceLocation(name, root.attr("BaseLoc"), value) id, charset := editorResourceID(font.attr("ID")), font.attr("Charset") unsafe := id == "" || charset != "" && !strings.EqualFold(charset, "unicode") || location != cleanPackagePath(resolveResourcePath(name, root.attr("BaseLoc"), value)) - bindings[name] = append(bindings[name], compressionFontBinding{id: id, file: location, unsafe: unsafe}) - } - } - used := make(map[string]bool) - result := make(map[string]*editorFontUsage) - for _, entry := range slices.Sorted(maps.Keys(documents)) { - scope, err := r.versionFileDependencies(ctx, entry, parts, generated) - if err != nil { - return nil, err - } - refs := editorResourceRefs{fonts: make(map[string]*editorFontUsage)} - for name := range scope { - if source := references[name]; source != nil { - if selected == "" || selected == entry { - for id, usage := range source.fonts { - refs.fontUsage(id).merge(usage, false) - } - } - used[name] = true + if result[location] == nil { + result[location] = newEditorFontUsage() } - } - if selected != "" && selected != entry { - continue - } - for name := range scope { - for _, binding := range bindings[name] { - usage := refs.fonts[binding.id] - if result[binding.file] == nil { - result[binding.file] = newEditorFontUsage() - } - result[binding.file].merge(usage, binding.unsafe) - } - } - } - for name, refs := range references { - if !used[name] && (len(refs.fonts) != 0 || len(bindings[name]) != 0) { - return nil, nil + result[location].merge(refs.fonts[id], unsafe) } } return result, ctx.Err() diff --git a/ofdgo_editor_resource_scan.go b/ofdgo_editor_resource_scan.go index 5e33bd9..1739292 100644 --- a/ofdgo_editor_resource_scan.go +++ b/ofdgo_editor_resource_scan.go @@ -24,8 +24,8 @@ import ( ) // scanEditorResources 优先扫描结构依赖,原始数据不按XML解析,孤立文件仍保留引用检查 -// 入参: ctx 取消上下文, reader 原包, parts 输出改动, generated 自产引用, names 扫描范围, progress 扫描进度, collect 是否保留各文件的字体引用 -// 返回: *editorResourceRefs 全部引用, []string 资源索引, map[string]*editorResourceRefs 字体引用, bool 是否可安全优化, error 读取或取消错误 +// 入参: ctx 取消上下文, reader 原包, parts 输出改动, generated 自产引用, names 扫描范围, progress 扫描进度, collect 是否保留各文件的资源引用 +// 返回: *editorResourceRefs 全部引用, []string 资源索引, map[string]*editorResourceRefs 文件引用, bool 是否可安全优化, error 读取或取消错误 func scanEditorResources(ctx context.Context, reader *Reader, parts map[string][]byte, generated map[string]editorGeneratedReferences, names map[string]bool, progress editorProgress, collect bool) (*editorResourceRefs, []string, map[string]*editorResourceRefs, bool, error) { refs := &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage), roles: make(map[string]int), documents: make(map[string]bool), versions: make(map[string]bool)} var resources []string @@ -133,7 +133,7 @@ func scanEditorResources(ctx context.Context, reader *Reader, parts map[string][ if resource { resources = append(resources, name) } - if collect && (resource || len(local.fonts) != 0) { + if collect && (resource || len(local.ids) != 0 || len(local.fonts) != 0) { references[name] = local } } diff --git a/ofdgo_editor_resource_scope.go b/ofdgo_editor_resource_scope.go new file mode 100644 index 0000000..690c2ad --- /dev/null +++ b/ofdgo_editor_resource_scope.go @@ -0,0 +1,66 @@ +// Copyright 2025-2026 肖其顿 (XIAO QI DUN) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ofdgo + +import ( + "context" + "maps" + "slices" +) + +// resourceReferenceScopes 按文档归集资源标识和字体用字,共享资源索引合并所属文档的引用 +// 入参: ctx 取消上下文, parts 输出改动, generated 自产页面引用, documents 文档入口, resources 资源索引, references 文件引用, selected 限定的文档入口,空值统计全部文档 +// 返回: map[string]*editorResourceRefs 各资源索引的引用,存在无法归属的引用时为空, error 读取或取消错误 +func (r *Reader) resourceReferenceScopes(ctx context.Context, parts map[string][]byte, generated map[string]editorGeneratedReferences, documents map[string]bool, resources []string, references map[string]*editorResourceRefs, selected string) (map[string]*editorResourceRefs, error) { + indexes := make(map[string]bool, len(resources)) + for _, name := range resources { + indexes[name] = true + } + used := make(map[string]bool) + result := make(map[string]*editorResourceRefs) + for _, entry := range slices.Sorted(maps.Keys(documents)) { + scope, err := r.versionFileDependencies(ctx, entry, parts, generated) + if err != nil { + return nil, err + } + refs := &editorResourceRefs{ids: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} + for name := range scope { + if source := references[name]; source != nil { + used[name] = true + if selected == "" || selected == entry { + refs.merge(&editorResourceRefs{ids: source.ids, fonts: source.fonts}) + } + } + } + if selected != "" && selected != entry { + continue + } + for name := range scope { + if !indexes[name] { + continue + } + if result[name] == nil { + result[name] = &editorResourceRefs{ids: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} + } + result[name].merge(refs) + } + } + for name, refs := range references { + if !used[name] && (len(refs.ids) != 0 || len(refs.fonts) != 0 || indexes[name]) { + return nil, nil + } + } + return result, ctx.Err() +} diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index ca34fb3..541f9d4 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -47,11 +47,12 @@ type editorResourceRefs struct { // compactSourceReferences 合并自产页面引用,版本文档按当前版本范围扫描 // 按GB/T33190-2016附录A检查标识和路径引用,保留扫描范围内孤立XML中的引用 -// 多文档、未知扩展或无法解析的XML不清理资源,避免误删共享或无法确认用途的条目 +// 多文档压缩按文档归集引用,未知扩展或无法解析的XML不清理资源 // 入参: parts 输出条目, progress 保存进度回调, generated 自产页面及引用 // 返回: map[string]bool 可移除条目, error 读取或取消错误 func (e *Editor) compactSourceReferences(parts map[string][]byte, progress editorProgress, generated map[string]editorGeneratedReferences) (map[string]bool, error) { - if e.source.reader.DocumentCount() > 1 || e.output != nil && e.output.protected { + multiple := e.source.reader.DocumentCount() > 1 + if e.output != nil && e.output.protected || multiple && (e.output == nil || e.output.options.Mode == CompressionUnchanged) { return nil, nil } if len(parts) == 0 && e.revision == 0 && (e.output == nil || e.output.options.Mode == CompressionUnchanged) { @@ -84,13 +85,27 @@ func (e *Editor) compactSourceReferences(parts map[string][]byte, progress edito } } } - refs, resources, _, safe, err := scanEditorResources(ctx, reader, parts, generated, names, progress, false) + refs, resources, references, safe, err := scanEditorResources(ctx, reader, parts, generated, names, progress, multiple) if err != nil { return nil, err } if !safe { return nil, nil } + var scoped map[string]*editorResourceRefs + if multiple { + selected := "" + if reader.OFD.DocBody[reader.documentIndex].versioned { + selected, err = reader.DocumentRoot() + if err != nil { + return nil, err + } + } + scoped, err = reader.resourceReferenceScopes(ctx, parts, generated, refs.documents, resources, references, selected) + if err != nil || scoped == nil { + return nil, err + } + } ordered := slices.Sorted(maps.Keys(names)) removed := maps.Clone(e.source.retiredAttachments) if removed == nil { @@ -115,7 +130,11 @@ func (e *Editor) compactSourceReferences(parts map[string][]byte, progress edito if err != nil { return nil, nil } - preserved := refs.versions[cleanPackagePath(name)] + usageRefs := refs + if multiple { + usageRefs = scoped[name] + } + preserved := refs.versions[cleanPackagePath(name)] || usageRefs == nil var patches []editorXMLPatch for _, group := range root.children { var members []editorXMLPatch @@ -130,7 +149,7 @@ func (e *Editor) compactSourceReferences(parts map[string][]byte, progress edito continue } id := editorResourceID(node.attr("ID")) - keep := preserved || id == "" || refs.ids[id] || field == "MediaFile" && node.attr("Type") != "Image" + keep := preserved || id == "" || usageRefs.ids[id] || field == "MediaFile" && node.attr("Type") != "Image" if file := node.child(field); file != nil { var value string if err := xml.Unmarshal(data[file.start:file.end], &value); err != nil { @@ -138,8 +157,8 @@ func (e *Editor) compactSourceReferences(parts map[string][]byte, progress edito } if location := editorResourceLocation(name, root.attr("BaseLoc"), value); location != "" { alternate := cleanPackagePath(resolveResourcePath(name, root.attr("BaseLoc"), value)) - if field == "FontFile" { - usage := refs.fonts[id] + if field == "FontFile" && !multiple { + usage := usageRefs.fonts[id] charset := node.attr("Charset") unsafe := preserved || id == "" || charset != "" && !strings.EqualFold(charset, "unicode") || location != alternate for _, file := range []string{location, alternate} {