mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
1 parent
22040ec836
commit
e4af1e7318
8 files changed
+125
-39
No files matched your search
@@ -5687,6 +5687,7 @@ async function processPageRenderQueue() {
|
||||
}
|
||||
} finally {
|
||||
state.pageRenderRunning = false;
|
||||
if (!state.pageInFlight.size) releasePageResources();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user