From d5944b30fffe1fa391222d48ab2410f7af919c57 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Sun, 11 Oct 2026 01:48:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E5=8E=8B=E7=BC=A9):=20?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=A4=9A=E6=96=87=E6=A1=A3=E5=AD=97=E4=BD=93?= =?UTF-8?q?=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 | 100 +++++++++++++++++++++++++++++++++ ofdgo_compression_resources.go | 37 +++++++++++- ofdgo_editor_reference.go | 12 ++++ ofdgo_editor_resources.go | 9 +-- 4 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 ofdgo_compression_fonts.go diff --git a/ofdgo_compression_fonts.go b/ofdgo_compression_fonts.go new file mode 100644 index 0000000..e781b43 --- /dev/null +++ b/ofdgo_compression_fonts.go @@ -0,0 +1,100 @@ +// 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" + "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 读取或取消错误 +func (r *Reader) compressionFontUsage(ctx context.Context, parts map[string][]byte, generated map[string]editorGeneratedReferences, documents map[string]bool, resources []string, references map[string]*editorResourceRefs, read func(string) ([]byte, error)) (map[string]*editorFontUsage, error) { + bindings := make(map[string][]compressionFontBinding) + for _, name := range resources { + if err := ctx.Err(); err != nil { + return nil, err + } + data, err := read(name) + if err != nil { + return nil, err + } + root, err := parseEditorXML(data) + if err != nil { + return nil, err + } + fonts := root.child("Fonts") + if fonts == nil { + continue + } + for _, font := range fonts.children { + file := font.child("FontFile") + if file == nil { + continue + } + var value string + if err := xml.Unmarshal(data[file.start:file.end], &value); err != nil { + return nil, err + } + 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 { + for id, usage := range source.fonts { + refs.fontUsage(id).merge(usage, false) + } + used[name] = true + } + } + 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 + } + } + return result, ctx.Err() +} diff --git a/ofdgo_compression_resources.go b/ofdgo_compression_resources.go index 80f9267..3210efa 100644 --- a/ofdgo_compression_resources.go +++ b/ofdgo_compression_resources.go @@ -74,6 +74,10 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea return reader.readFileView(name) } refs := editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} + var fontReferences map[string]*editorResourceRefs + if reader != nil && reader.DocumentCount() > 1 && e.backends.FontResources != nil { + fontReferences = make(map[string]*editorResourceRefs) + } var resources []string actual := make(map[string]string) for _, name := range slices.Sorted(maps.Keys(names)) { @@ -94,6 +98,9 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea data, replaced := parts[name] if known, ok := generated[name]; ok && replaced && known.refs != nil && known.matches(data) { refs.merge(known.refs) + if fontReferences != nil && len(known.refs.fonts) != 0 { + fontReferences[name] = known.refs + } continue } var input io.ReadCloser @@ -110,7 +117,11 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea return err } } - resource, safe := refs.scan(imageInput{ReadCloser: input, context: e.output.ctx}, name) + scanned := &refs + if fontReferences != nil { + scanned = &editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)} + } + resource, safe := scanned.scan(imageInput{ReadCloser: input, context: e.output.ctx}, name) input.Close() if err := e.output.ctx.Err(); err != nil { return err @@ -118,6 +129,12 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea if !safe { return nil } + if fontReferences != nil { + if resource || len(scanned.fonts) != 0 { + fontReferences[name] = scanned + } + refs.merge(scanned) + } if resource { resources = append(resources, name) } @@ -168,6 +185,13 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea } } } + var fontUsage map[string]*editorFontUsage + if fontReferences != nil { + fontUsage, err = reader.compressionFontUsage(e.output.ctx, parts, generated, refs.documents, resources, fontReferences, read) + if err != nil { + return err + } + } aliases := make(map[string]string) locations := maps.Clone(actual) reserved := make(map[string]bool, len(actual)) @@ -227,6 +251,17 @@ func (e *Editor) compressResourceReferences(parts map[string][]byte, reader *Rea } source.key.hash = sha256.Sum256(data) } + if usage := fontUsage[key]; role == "FontFile" && usage != nil && !usage.unsafe { + candidate, err := e.backends.FontResources.SubsetSourceFont(data, FontUsage{Characters: slices.Sorted(maps.Keys(usage.chars)), Glyphs: slices.Sorted(maps.Keys(usage.glyphs))}) + if err != nil { + return fmt.Errorf("subset font %s: %w", key, err) + } + if len(candidate) != 0 && len(candidate) < len(data) { + data = candidate + parts[actual[key]] = data + source.key.hash = sha256.Sum256(data) + } + } if role == "MediaFile" { options, demand := e.outputImageOptions(key) cacheKey := outputImageKey{hash: source.key.hash, options: options, size: demand} diff --git a/ofdgo_editor_reference.go b/ofdgo_editor_reference.go index 0a54aca..f6ba006 100644 --- a/ofdgo_editor_reference.go +++ b/ofdgo_editor_reference.go @@ -143,6 +143,12 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { } if len(s.stack) == 0 { s.seen = true + if token.Name.Local == "Document" { + if s.refs.documents == nil { + s.refs.documents = make(map[string]bool) + } + s.refs.documents[s.name] = true + } s.resource = token.Name.Local == "Res" if s.resource { for _, attr := range token.Attr { @@ -238,6 +244,12 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { func (r *editorResourceRefs) merge(other *editorResourceRefs) { maps.Copy(r.ids, other.ids) maps.Copy(r.files, other.files) + if len(other.documents) != 0 { + if r.documents == nil { + r.documents = make(map[string]bool) + } + maps.Copy(r.documents, other.documents) + } if len(other.versions) != 0 { if r.versions == nil { r.versions = make(map[string]bool) diff --git a/ofdgo_editor_resources.go b/ofdgo_editor_resources.go index f70c74d..a630ee3 100644 --- a/ofdgo_editor_resources.go +++ b/ofdgo_editor_resources.go @@ -37,10 +37,11 @@ const ( // editorResourceRefs 保存全包资源标识和直接文件引用,不以页面是否加载判断资源存活 type editorResourceRefs struct { - ids map[string]bool - files map[string]bool - fonts map[string]*editorFontUsage - versions map[string]bool + ids map[string]bool + files map[string]bool + fonts map[string]*editorFontUsage + versions map[string]bool + documents map[string]bool } // compactSourceReferences 合并自产页面引用,版本文档按当前版本范围扫描