fix(修复问题): 修复编辑缺陷并优化资源回收
build.yaml / build (push) Successful in 52s

This commit is contained in:
xiaoqidun committed 2026-09-30 14:26:00 +08:00
1 parent 22040ec836
commit e4af1e7318
8 files changed
+125 -39

No files matched your search

+1
View File
@@ -5687,6 +5687,7 @@ async function processPageRenderQueue() {
}
} finally {
state.pageRenderRunning = false;
if (!state.pageInFlight.size) releasePageResources();
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ require (
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
github.com/xiaoqidun/jbig2 v1.1.1
github.com/xiaoqidun/pdfgo v0.0.0-20260930050241-ecc0b0d74180
github.com/xiaoqidun/pdfgo v0.0.0-20260930062146-7c39a20d9430
golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0
golang.org/x/text v0.42.0
+2 -2
View File
@@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260930050241-ecc0b0d74180 h1:2/QHfh0NIg74xzT3KHjqN7u1S9U2F+4N0HZqPrJFxvw=
github.com/xiaoqidun/pdfgo v0.0.0-20260930050241-ecc0b0d74180/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xiaoqidun/pdfgo v0.0.0-20260930062146-7c39a20d9430 h1:UBP+ZmqxoxQ7p6dFZI/6yGSLmgqfTQUvO7h+DUfWsWY=
github.com/xiaoqidun/pdfgo v0.0.0-20260930062146-7c39a20d9430/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
+4 -25
View File
@@ -83,9 +83,8 @@ type apiResult struct {
// editorClipboard 当前编辑文档中的对象快照与剪贴板标识
type editorClipboard struct {
token string
objects []ofdgo.GraphicObject
selection *ofdgo.ObjectSelection
composite *ofdgo.CompositeSelection
annotations *ofdgo.AnnotationSelection
}
// editorInfo 编辑文档信息与操作状态
@@ -1669,10 +1668,7 @@ func captureEditorSelection(page int, ids []string) (*editorClipboard, error) {
objects, annotations := splitEditorSelection(ids)
result := &editorClipboard{}
var err error
result.objects, err = currentEditor.Objects(page, objects)
if err == nil && len(annotations) != 0 {
result.annotations, err = currentEditor.CaptureAnnotations(page, annotations)
}
result.selection, err = currentEditor.CaptureSelection(page, objects, annotations)
return result, err
}
@@ -1680,24 +1676,10 @@ func captureEditorSelection(page int, ids []string) (*editorClipboard, error) {
// 入参: page 目标页, clipboard 快照, dx、dy 位移
// 返回: []string 新画布标识, error 错误信息
func pasteEditorSelection(page int, clipboard *editorClipboard, dx, dy float64) ([]string, error) {
var ids []string
err := currentEditor.Transaction(func(edit *ofdgo.Editor) error {
var err error
ids, err = edit.CopyObjects(page, clipboard.objects, dx, dy)
if err != nil {
return err
}
if clipboard.annotations != nil {
annotations, err := edit.PasteAnnotations(page, clipboard.annotations, dx, dy)
if err != nil {
return err
}
ids, annotations, err := currentEditor.PasteSelection(page, clipboard.selection, dx, dy)
for _, id := range annotations {
ids = append(ids, "annotation:"+id)
}
}
return nil
})
return ids, err
}
@@ -2677,9 +2659,6 @@ func pasteObjects(args []js.Value) (any, error) {
}
page, dx, dy := args[0].Int(), args[2].Float(), args[3].Float()
if key != "" {
if copiedObjects.annotations != nil {
return nil, fmt.Errorf("annotations cannot be nested in a page object")
}
path, err := compositePath(key)
if err != nil {
return nil, err
@@ -2688,7 +2667,7 @@ func pasteObjects(args []js.Value) (any, error) {
if copiedObjects.composite != nil {
indexes, err = currentEditor.PasteCompositeObjects(page, path, copiedObjects.composite, dx, dy)
} else {
indexes, err = currentEditor.CopyObjectsToComposite(page, path, copiedObjects.objects, dx, dy)
indexes, err = currentEditor.PasteSelectionToComposite(page, path, copiedObjects.selection, dx, dy)
}
if err != nil {
return nil, err
+9 -2
View File
@@ -19,7 +19,6 @@ import (
"encoding/xml"
"fmt"
"maps"
"slices"
)
// AnnotationSelection 保存当前编辑器内可重复粘贴的注解原文及资源上下文
@@ -151,10 +150,18 @@ func (e *Editor) PasteAnnotations(page int, selection *AnnotationSelection, dx,
return err
}
common := root.child("CommonData")
registered := make(map[string]bool)
for _, name := range edit.source.document.CommonData.PublicRes {
registered[reader.ResPath(name)] = true
}
for _, name := range edit.source.document.CommonData.DocumentRes {
registered[reader.ResPath(name)] = true
}
var added []byte
for _, name := range selection.resources {
if !slices.ContainsFunc(append(slices.Clone(edit.source.document.CommonData.PublicRes), edit.source.document.CommonData.DocumentRes...), func(value string) bool { return reader.ResPath(value) == name }) {
if !registered[name] {
added = append(added, editorXMLText("DocumentRes", "/"+name)...)
registered[name] = true
}
}
if len(added) != 0 {
+16 -6
View File
@@ -20,6 +20,7 @@ import (
"fmt"
"maps"
"path"
"strconv"
"strings"
"unicode/utf8"
)
@@ -40,18 +41,18 @@ func (e *Editor) AddAttachment(name string, data []byte) (string, error) {
if err := validateAttachmentName(name); err != nil {
return "", err
}
if err := e.prepareSourceIDs(); err != nil {
maximum, err := e.sourceMaxID(nil)
if err != nil {
return "", err
}
base := e.source
var err error
if base == nil {
base, err = e.importBase()
if err != nil {
return "", err
}
}
id := e.nextID()
id := strconv.Itoa(maximum + 1)
file := e.packageName("Attachments/Attachment_" + id)
attrs := ofdAttrs{}
attrs.add("ID", id)
@@ -112,6 +113,7 @@ func (e *Editor) AddAttachment(name string, data []byte) (string, error) {
if err := e.commitAnnotationParts(base, parts); err != nil {
return "", err
}
e.maxID, e.source.idsReady = maximum+1, true
return id, nil
}
@@ -134,11 +136,14 @@ func (e *Editor) RenameAttachment(id, name string) error {
// 入参: id 附件标识, data 文件数据,调用后可复用
// 返回: error 附件结构或提交错误
func (e *Editor) ReplaceAttachment(id string, data []byte) error {
return e.editAttachment(id, func(fragment []byte, node *editorXML, parts map[string][]byte) ([]byte, error) {
if err := e.prepareSourceIDs(); err != nil {
var maximum int
err := e.editAttachment(id, func(fragment []byte, node *editorXML, parts map[string][]byte) ([]byte, error) {
var err error
maximum, err = e.sourceMaxID(nil)
if err != nil {
return nil, err
}
file := e.packageName("Attachments/Attachment_" + e.nextID())
file := e.packageName("Attachments/Attachment_" + strconv.Itoa(maximum+1))
var location bytes.Buffer
if err := xml.EscapeText(&location, []byte("/"+file)); err != nil {
return nil, err
@@ -161,6 +166,11 @@ func (e *Editor) ReplaceAttachment(id string, data []byte) error {
parts[file] = bytes.Clone(data)
return fragment, nil
})
if err != nil {
return err
}
e.maxID, e.source.idsReady = maximum+1, true
return nil
}
// DeleteAttachment 删除附件声明,保存时清理可确认无引用的内容,支持撤销和重做
+14 -3
View File
@@ -212,10 +212,15 @@ func (e *Editor) OrderObjects(page int, ids []string, order string) error {
}
layer := &e.pages[page].Content.Layer[indexes[0].layer]
_, siblings := e.objectOrderIndexes(page, layer, ids[0])
positions := make(map[int]int, len(siblings))
for i, index := range siblings {
positions[index] = i
}
orderable := make(map[*editorXML]bool)
for i, object := range selectedObjects {
capability := e.objectCapabilities(object, orderable)
if !capability.Order || indexes[i].layer != indexes[0].layer || !slices.Contains(siblings, indexes[i].index) {
_, exists := positions[indexes[i].index]
if !capability.Order || indexes[i].layer != indexes[0].layer || !exists {
return fmt.Errorf("ordering requires editable objects in the same container")
}
}
@@ -226,7 +231,7 @@ func (e *Editor) OrderObjects(page int, ids []string, order string) error {
}
selected := make([]bool, len(objects))
for _, index := range indexes {
selected[slices.Index(siblings, index.index)] = true
selected[positions[index.index]] = true
}
orderEditorObjects(objects, selected, order)
for i, index := range siblings {
@@ -507,11 +512,17 @@ func editorDistribution(boxes []Box, indexes []editorObjectPosition, axis string
// replaceLayers 替换图层容器,隔离历史快照与后续的增删、排序操作
// 入参: page 页面索引, layers 新图层列表
func (e *Editor) replaceLayers(page int, layers []Layer) {
before := copyEditorPage(e.pages[page]).Content.Layer
before := e.pages[page].Content.Layer
if reflect.DeepEqual(before, layers) {
return
}
after := copyEditorPage(PageContent{Content: Content{Layer: layers}}).Content.Layer
if e.historyLimit == 0 {
e.pages[page].Content.Layer = after
e.recordChange()
return
}
before = copyEditorPage(PageContent{Content: Content{Layer: before}}).Content.Layer
apply := func(e *Editor, layers []Layer) {
e.pages[page].Content.Layer = copyEditorPage(PageContent{Content: Content{Layer: layers}}).Content.Layer
}
+78
View File
@@ -0,0 +1,78 @@
// 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 "fmt"
// ObjectSelection 保存正文与注解的独立快照,共享当前编辑器的不可变资源
// 来源修改、删除或撤销不改变快照,不用于跨文档传输
type ObjectSelection struct {
editor *Editor
objects []GraphicObject
annotations *AnnotationSelection
}
// CaptureSelection 捕获正文与注解,不修改文档或历史
// 入参: page 页面索引, objects 正文标识, annotations 注解标识,各列表不得重复
// 返回: *ObjectSelection 独立快照, error 标识或资源错误
func (e *Editor) CaptureSelection(page int, objects, annotations []string) (*ObjectSelection, error) {
items, err := e.Objects(page, objects)
if err != nil {
return nil, err
}
selection := &ObjectSelection{editor: e, objects: items}
if len(annotations) != 0 {
selection.annotations, err = e.CaptureAnnotations(page, annotations)
if err != nil {
return nil, err
}
}
return selection, nil
}
// PasteSelection 原子粘贴正文与注解并平移,失败回滚,一次撤销恢复全部
// 入参: page 目标页面索引, selection 当前编辑器快照, dx、dy 页面毫米位移
// 返回: []string 新正文标识, []string 新注解标识, error 标识、资源或编辑错误
func (e *Editor) PasteSelection(page int, selection *ObjectSelection, dx, dy float64) ([]string, []string, error) {
if selection == nil || selection.editor != e || !finite(dx) || !finite(dy) {
return nil, nil, fmt.Errorf("invalid object selection or offset")
}
var objects, annotations []string
err := e.Transaction(func(edit *Editor) error {
var err error
objects, err = edit.CopyObjects(page, selection.objects, dx, dy)
if err == nil && selection.annotations != nil {
annotations, err = edit.PasteAnnotations(page, selection.annotations, dx, dy)
}
return err
})
if err != nil {
return nil, nil, err
}
return objects, annotations, nil
}
// PasteSelectionToComposite 将正文快照粘贴至内部容器,保留源外观,拒绝嵌套注解
// 入参: page 目标页面索引, path 父复合路径, selection 当前编辑器快照, dx、dy 页面毫米位移
// 返回: []int 新成员序号, error 选区、资源或编辑错误
func (e *Editor) PasteSelectionToComposite(page int, path ObjectPath, selection *ObjectSelection, dx, dy float64) ([]int, error) {
if selection == nil || selection.editor != e {
return nil, fmt.Errorf("object selection belongs to another editor")
}
if selection.annotations != nil {
return nil, fmt.Errorf("annotations cannot be nested in a page object")
}
return e.CopyObjectsToComposite(page, path, selection.objects, dx, dy)
}