feat(编辑能力): 修复字体选择与裁剪边界问题
build.yaml / build (push) Successful in 35s

This commit is contained in:
xiaoqidun committed 2026-09-16 18:32:20 +08:00
1 parent 71e67be9c1
commit 1914b5329a
25 files changed
+747 -253

No files matched your search

+63 -11
View File
@@ -26,18 +26,19 @@ import (
"strings"
)
// editorResourceRefs 保存全包资源标识和直接文件引用,不以页面是否加载判断资源存活。
// editorResourceRefs 保存全包资源标识和直接文件引用,不以页面是否加载判断资源存活
type editorResourceRefs struct {
ids map[string]bool
files map[string]bool
fonts map[string]*editorFontUsage
}
// pruneSourceResources 在保存副本中清理无引用字体和图片,保留其他资源及原始编辑状态。
// 按GB/T 33190-2016附录A检查标识和路径引用,包括模板、注释、底纹、裁剪、版本和其他文档。
// 全包检查保留孤立XML中的引用;未知命名空间、扩展数据或无法解析的XML使本次清理跳过。
// compactSourceResources 在保存副本中清理无引用字体和图片,并裁剪可完整确认用字的原有字体
// 按GB/T 33190-2016附录A检查标识和路径引用,包括模板、注释、底纹、裁剪、版本和其他文档
// 全包检查保留孤立XML中的引用;未知命名空间、扩展数据或无法解析的XML使本次清理跳过
// 入参: parts 已修改和新增的包内条目
// 返回: map[string]bool 可移除的二进制条目, error 读取错误
func (e *Editor) pruneSourceResources(parts map[string][]byte) (map[string]bool, error) {
func (e *Editor) compactSourceResources(parts map[string][]byte) (map[string]bool, error) {
if len(parts) == 0 {
return nil, nil
}
@@ -52,7 +53,7 @@ func (e *Editor) pruneSourceResources(parts map[string][]byte) (map[string]bool,
for name := range parts {
names[name] = true
}
refs := editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool)}
refs := editorResourceRefs{ids: make(map[string]bool), files: make(map[string]bool), fonts: make(map[string]*editorFontUsage)}
var resources []string
for _, name := range slices.Sorted(maps.Keys(names)) {
if strings.HasSuffix(name, "/") {
@@ -80,6 +81,7 @@ func (e *Editor) pruneSourceResources(parts map[string][]byte) (map[string]bool,
removed := make(map[string]bool)
usedFiles := maps.Clone(refs.files)
updates := make(map[string][]byte)
fontFiles := make(map[string]*editorFontUsage)
for _, name := range resources {
data, ok := parts[name]
if !ok {
@@ -114,9 +116,21 @@ func (e *Editor) pruneSourceResources(parts map[string][]byte) (map[string]bool,
return nil, nil
}
if location := editorResourceLocation(name, root.attr("BaseLoc"), value); location != "" {
alternate := strings.ToLower(cleanPackagePath(resolveResourcePath(name, root.attr("BaseLoc"), value)))
if field == "FontFile" {
usage := refs.fonts[id]
charset := node.attr("Charset")
unsafe := id == "" || charset != "" && !strings.EqualFold(charset, "unicode") || location != alternate
for _, file := range []string{location, alternate} {
if fontFiles[file] == nil {
fontFiles[file] = newEditorFontUsage()
}
fontFiles[file].merge(usage, unsafe || keep && usage == nil)
}
}
if keep {
usedFiles[location] = true
usedFiles[strings.ToLower(cleanPackagePath(resolveResourcePath(name, root.attr("BaseLoc"), value)))] = true
usedFiles[alternate] = true
} else {
removed[location] = true
}
@@ -139,6 +153,20 @@ func (e *Editor) pruneSourceResources(parts map[string][]byte) (map[string]bool,
for name := range usedFiles {
delete(removed, name)
}
for name := range names {
key := strings.ToLower(cleanPackagePath(name))
usage := fontFiles[key]
if usage == nil || usage.unsafe || refs.files[key] || removed[key] || parts[name] != nil {
continue
}
data, err := reader.readFile(name)
if err != nil {
return nil, err
}
if subset := subsetSourceFont(data, usage); subset != nil {
updates[name] = subset
}
}
maps.Copy(parts, updates)
for name := range parts {
if removed[strings.ToLower(cleanPackagePath(name))] {
@@ -148,7 +176,7 @@ func (e *Editor) pruneSourceResources(parts map[string][]byte) (map[string]bool,
return removed, nil
}
// scan 流式读取包内XML引用,二进制条目只探测文件头,不展开页面对象或图片像素。
// scan 流式读取包内XML引用,二进制条目只探测文件头,不展开页面对象或图片像素
// 入参: input 文件流, name 包内路径
// 返回: bool 是否资源索引, bool 是否可完整判断引用
func (r *editorResourceRefs) scan(input io.Reader, name string) (bool, bool) {
@@ -166,6 +194,7 @@ func (r *editorResourceRefs) scan(input io.Reader, name string) (bool, bool) {
}
decoder := xml.NewDecoder(buffer)
var stack []string
var fonts []*editorFontUsage
var text strings.Builder
resource, base := false, ""
for {
@@ -194,6 +223,13 @@ func (r *editorResourceRefs) scan(input io.Reader, name string) (bool, bool) {
}
}
}
var usage *editorFontUsage
if len(fonts) != 0 {
usage = fonts[len(fonts)-1]
if usage != nil && (stack[len(stack)-1] == "TextCode" || stack[len(stack)-1] == "Glyphs") {
usage.unsafe = true
}
}
for _, attr := range token.Attr {
if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" || attr.Name.Space == "http://www.w3.org/XML/1998/namespace" {
continue
@@ -204,14 +240,30 @@ func (r *editorResourceRefs) scan(input io.Reader, name string) (bool, bool) {
if attr.Name.Local != "ID" {
r.reference(name, base, attr.Name.Local, attr.Value, resource)
}
if attr.Name.Local == "Font" {
usage = r.fontUsage(attr.Value)
if token.Name.Local != "TextObject" && token.Name.Local != "Text" {
usage.unsafe = true
}
} else if attr.Name.Local == "Substitution" {
r.fontUsage(attr.Value).unsafe = true
}
}
stack = append(stack, token.Name.Local)
fonts = append(fonts, usage)
text.Reset()
case xml.CharData:
text.Write(token)
case xml.EndElement:
r.reference(name, base, token.Name.Local, text.String(), resource)
if (token.Name.Local == "Substitution" || token.Name.Local == "Font") && strings.TrimSpace(text.String()) != "" {
r.fontUsage(text.String()).unsafe = true
}
if usage := fonts[len(fonts)-1]; usage != nil {
usage.text(token.Name.Local, text.String())
}
stack = stack[:len(stack)-1]
fonts = fonts[:len(fonts)-1]
text.Reset()
case xml.Directive:
return false, false
@@ -219,7 +271,7 @@ func (r *editorResourceRefs) scan(input io.Reader, name string) (bool, bool) {
}
}
// reference 记录标准资源标识和路径引用,不将普通数字或资源自身ID视为引用。
// reference 记录标准资源标识和路径引用,不将普通数字或资源自身ID视为引用
// 入参: name 当前文件, base 资源目录, key 字段, value 值, resource 是否资源索引
func (r *editorResourceRefs) reference(name, base, key, value string, resource bool) {
value = strings.TrimSpace(value)
@@ -248,7 +300,7 @@ func (r *editorResourceRefs) reference(name, base, key, value string, resource b
r.files[strings.ToLower(cleanPackagePath(resolveResourcePath(name, base, value)))] = true
}
// editorResourceID 规范化ST_ID与ST_RefID的十进制表示。
// editorResourceID 规范化ST_ID与ST_RefID的十进制表示
// 入参: value 标识文本
// 返回: string 标准十进制标识,无效时为空
func editorResourceID(value string) string {
@@ -259,7 +311,7 @@ func editorResourceID(value string) string {
return strconv.FormatUint(id, 10)
}
// editorResourceLocation 按ST_Loc与Res.BaseLoc解析路径,大小写折叠用于保守保留共享文件。
// editorResourceLocation 按ST_Loc与Res.BaseLoc解析路径,大小写折叠用于保守保留共享文件
// 入参: name 所在XML, base 资源目录, value 路径值
// 返回: string 包内路径
func editorResourceLocation(name, base, value string) string {