mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 09:37:26 +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 {
|
} finally {
|
||||||
state.pageRenderRunning = false;
|
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/canvas v0.0.0-20260923214215-09804640d00c
|
||||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1
|
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/crypto v0.57.0
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/text v0.42.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/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 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
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-20260930062146-7c39a20d9430 h1:UBP+ZmqxoxQ7p6dFZI/6yGSLmgqfTQUvO7h+DUfWsWY=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20260930050241-ecc0b0d74180/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
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 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
|
|||||||
+4
-25
@@ -83,9 +83,8 @@ type apiResult struct {
|
|||||||
// editorClipboard 当前编辑文档中的对象快照与剪贴板标识
|
// editorClipboard 当前编辑文档中的对象快照与剪贴板标识
|
||||||
type editorClipboard struct {
|
type editorClipboard struct {
|
||||||
token string
|
token string
|
||||||
objects []ofdgo.GraphicObject
|
selection *ofdgo.ObjectSelection
|
||||||
composite *ofdgo.CompositeSelection
|
composite *ofdgo.CompositeSelection
|
||||||
annotations *ofdgo.AnnotationSelection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorInfo 编辑文档信息与操作状态
|
// editorInfo 编辑文档信息与操作状态
|
||||||
@@ -1669,10 +1668,7 @@ func captureEditorSelection(page int, ids []string) (*editorClipboard, error) {
|
|||||||
objects, annotations := splitEditorSelection(ids)
|
objects, annotations := splitEditorSelection(ids)
|
||||||
result := &editorClipboard{}
|
result := &editorClipboard{}
|
||||||
var err error
|
var err error
|
||||||
result.objects, err = currentEditor.Objects(page, objects)
|
result.selection, err = currentEditor.CaptureSelection(page, objects, annotations)
|
||||||
if err == nil && len(annotations) != 0 {
|
|
||||||
result.annotations, err = currentEditor.CaptureAnnotations(page, annotations)
|
|
||||||
}
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1680,24 +1676,10 @@ func captureEditorSelection(page int, ids []string) (*editorClipboard, error) {
|
|||||||
// 入参: page 目标页, clipboard 快照, dx、dy 位移
|
// 入参: page 目标页, clipboard 快照, dx、dy 位移
|
||||||
// 返回: []string 新画布标识, error 错误信息
|
// 返回: []string 新画布标识, error 错误信息
|
||||||
func pasteEditorSelection(page int, clipboard *editorClipboard, dx, dy float64) ([]string, error) {
|
func pasteEditorSelection(page int, clipboard *editorClipboard, dx, dy float64) ([]string, error) {
|
||||||
var ids []string
|
ids, annotations, err := currentEditor.PasteSelection(page, clipboard.selection, dx, dy)
|
||||||
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
|
|
||||||
}
|
|
||||||
for _, id := range annotations {
|
for _, id := range annotations {
|
||||||
ids = append(ids, "annotation:"+id)
|
ids = append(ids, "annotation:"+id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return ids, err
|
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()
|
page, dx, dy := args[0].Int(), args[2].Float(), args[3].Float()
|
||||||
if key != "" {
|
if key != "" {
|
||||||
if copiedObjects.annotations != nil {
|
|
||||||
return nil, fmt.Errorf("annotations cannot be nested in a page object")
|
|
||||||
}
|
|
||||||
path, err := compositePath(key)
|
path, err := compositePath(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2688,7 +2667,7 @@ func pasteObjects(args []js.Value) (any, error) {
|
|||||||
if copiedObjects.composite != nil {
|
if copiedObjects.composite != nil {
|
||||||
indexes, err = currentEditor.PasteCompositeObjects(page, path, copiedObjects.composite, dx, dy)
|
indexes, err = currentEditor.PasteCompositeObjects(page, path, copiedObjects.composite, dx, dy)
|
||||||
} else {
|
} else {
|
||||||
indexes, err = currentEditor.CopyObjectsToComposite(page, path, copiedObjects.objects, dx, dy)
|
indexes, err = currentEditor.PasteSelectionToComposite(page, path, copiedObjects.selection, dx, dy)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AnnotationSelection 保存当前编辑器内可重复粘贴的注解原文及资源上下文
|
// AnnotationSelection 保存当前编辑器内可重复粘贴的注解原文及资源上下文
|
||||||
@@ -151,10 +150,18 @@ func (e *Editor) PasteAnnotations(page int, selection *AnnotationSelection, dx,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
common := root.child("CommonData")
|
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
|
var added []byte
|
||||||
for _, name := range selection.resources {
|
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)...)
|
added = append(added, editorXMLText("DocumentRes", "/"+name)...)
|
||||||
|
registered[name] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(added) != 0 {
|
if len(added) != 0 {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
@@ -40,18 +41,18 @@ func (e *Editor) AddAttachment(name string, data []byte) (string, error) {
|
|||||||
if err := validateAttachmentName(name); err != nil {
|
if err := validateAttachmentName(name); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := e.prepareSourceIDs(); err != nil {
|
maximum, err := e.sourceMaxID(nil)
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
base := e.source
|
base := e.source
|
||||||
var err error
|
|
||||||
if base == nil {
|
if base == nil {
|
||||||
base, err = e.importBase()
|
base, err = e.importBase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id := e.nextID()
|
id := strconv.Itoa(maximum + 1)
|
||||||
file := e.packageName("Attachments/Attachment_" + id)
|
file := e.packageName("Attachments/Attachment_" + id)
|
||||||
attrs := ofdAttrs{}
|
attrs := ofdAttrs{}
|
||||||
attrs.add("ID", id)
|
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 {
|
if err := e.commitAnnotationParts(base, parts); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
e.maxID, e.source.idsReady = maximum+1, true
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,11 +136,14 @@ func (e *Editor) RenameAttachment(id, name string) error {
|
|||||||
// 入参: id 附件标识, data 文件数据,调用后可复用
|
// 入参: id 附件标识, data 文件数据,调用后可复用
|
||||||
// 返回: error 附件结构或提交错误
|
// 返回: error 附件结构或提交错误
|
||||||
func (e *Editor) ReplaceAttachment(id string, data []byte) 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) {
|
var maximum int
|
||||||
if err := e.prepareSourceIDs(); err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
file := e.packageName("Attachments/Attachment_" + e.nextID())
|
file := e.packageName("Attachments/Attachment_" + strconv.Itoa(maximum+1))
|
||||||
var location bytes.Buffer
|
var location bytes.Buffer
|
||||||
if err := xml.EscapeText(&location, []byte("/"+file)); err != nil {
|
if err := xml.EscapeText(&location, []byte("/"+file)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -161,6 +166,11 @@ func (e *Editor) ReplaceAttachment(id string, data []byte) error {
|
|||||||
parts[file] = bytes.Clone(data)
|
parts[file] = bytes.Clone(data)
|
||||||
return fragment, nil
|
return fragment, nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.maxID, e.source.idsReady = maximum+1, true
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAttachment 删除附件声明,保存时清理可确认无引用的内容,支持撤销和重做
|
// 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]
|
layer := &e.pages[page].Content.Layer[indexes[0].layer]
|
||||||
_, siblings := e.objectOrderIndexes(page, layer, ids[0])
|
_, 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)
|
orderable := make(map[*editorXML]bool)
|
||||||
for i, object := range selectedObjects {
|
for i, object := range selectedObjects {
|
||||||
capability := e.objectCapabilities(object, orderable)
|
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")
|
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))
|
selected := make([]bool, len(objects))
|
||||||
for _, index := range indexes {
|
for _, index := range indexes {
|
||||||
selected[slices.Index(siblings, index.index)] = true
|
selected[positions[index.index]] = true
|
||||||
}
|
}
|
||||||
orderEditorObjects(objects, selected, order)
|
orderEditorObjects(objects, selected, order)
|
||||||
for i, index := range siblings {
|
for i, index := range siblings {
|
||||||
@@ -507,11 +512,17 @@ func editorDistribution(boxes []Box, indexes []editorObjectPosition, axis string
|
|||||||
// replaceLayers 替换图层容器,隔离历史快照与后续的增删、排序操作
|
// replaceLayers 替换图层容器,隔离历史快照与后续的增删、排序操作
|
||||||
// 入参: page 页面索引, layers 新图层列表
|
// 入参: page 页面索引, layers 新图层列表
|
||||||
func (e *Editor) replaceLayers(page int, layers []Layer) {
|
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) {
|
if reflect.DeepEqual(before, layers) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
after := copyEditorPage(PageContent{Content: Content{Layer: layers}}).Content.Layer
|
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) {
|
apply := func(e *Editor, layers []Layer) {
|
||||||
e.pages[page].Content.Layer = copyEditorPage(PageContent{Content: Content{Layer: layers}}).Content.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