mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:44:23 +08:00
This commit is contained in:
1 parent
47562c4e7a
commit
2ddde73b65
16 files changed
+389
-90
No files matched your search
@@ -0,0 +1,187 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// siblings 获取同一原始容器内的直接绘制成员,不跨越资源和页块
|
||||
// 返回: []*editorCompositeNode 按绘制顺序排列的成员
|
||||
func (n *editorCompositeNode) siblings() []*editorCompositeNode {
|
||||
var result []*editorCompositeNode
|
||||
for _, member := range n.owner.children {
|
||||
if member.span.parent == n.span.parent {
|
||||
result = append(result, member)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DeleteCompositeObjects 删除内部成员,保留容器、未选对象及其他实例,一次撤销恢复全部
|
||||
// 入参: page 页面索引, path 父复合路径, indexes 成员序号
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) DeleteCompositeObjects(page int, path ObjectPath, indexes []int) error {
|
||||
return e.editCompositeObjects(page, path, indexes, func(_ *Renderer, nodes []*editorCompositeNode, _ []CompositeMember) error {
|
||||
for _, node := range nodes {
|
||||
node.owner.patches = append(node.owner.patches, editorXMLPatch{node.span.start, node.span.end, nil})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// CopyCompositeObjects 在原容器末尾复制选区并按页面毫米平移,保留原文并重映射副本内部标识
|
||||
// 入参: page 页面索引, path 父复合路径, indexes 同一容器的成员序号, dx、dy 页面位移
|
||||
// 返回: []int 按绘制顺序排列的副本序号, error 错误信息
|
||||
func (e *Editor) CopyCompositeObjects(page int, path ObjectPath, indexes []int, dx, dy float64) ([]int, error) {
|
||||
if !finite(dx) || !finite(dy) {
|
||||
return nil, fmt.Errorf("copy requires finite offsets")
|
||||
}
|
||||
var result []int
|
||||
err := e.editCompositeObjects(page, path, indexes, func(renderer *Renderer, nodes []*editorCompositeNode, members []CompositeMember) error {
|
||||
container := nodes[0].span.parent
|
||||
for i, node := range nodes {
|
||||
if !members[i].Capabilities.Copy || node.span.parent != container {
|
||||
return fmt.Errorf("copy requires editable members in the same container")
|
||||
}
|
||||
}
|
||||
slices.SortFunc(nodes, func(a, b *editorCompositeNode) int { return a.index - b.index })
|
||||
ids := make(map[string]string)
|
||||
for _, node := range nodes {
|
||||
if err := e.collectCompositeIDs(node.node, ids); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var data []byte
|
||||
for _, node := range nodes {
|
||||
copy := *node
|
||||
if err := e.transformCompositeMember(renderer, ©, TranslationMatrix(dx, dy)); err != nil {
|
||||
return err
|
||||
}
|
||||
fragment, err := editorXMLRemapIDs(copy.data, copy.node, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, fragment...)
|
||||
}
|
||||
owner := nodes[0].owner
|
||||
owner.patches = append(owner.patches, editorXMLPatch{container.close, container.close, data})
|
||||
next := 0
|
||||
for _, member := range owner.children {
|
||||
if container.start < member.span.start && member.span.end <= container.close {
|
||||
next = member.index + 1
|
||||
}
|
||||
}
|
||||
for i := range nodes {
|
||||
result = append(result, next+i)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// OrderCompositeObjects 调整同一直接容器内的绘制顺序,不改变页块和其他实例
|
||||
// 入参: page 页面索引, path 父复合路径, indexes 成员序号, order 为up、down、top或bottom
|
||||
// 返回: []int 调整后的选区序号, error 错误信息
|
||||
func (e *Editor) OrderCompositeObjects(page int, path ObjectPath, indexes []int, order string) ([]int, error) {
|
||||
if !slices.Contains([]string{"up", "down", "top", "bottom"}, order) {
|
||||
return nil, fmt.Errorf("invalid object order %q", order)
|
||||
}
|
||||
var result []int
|
||||
err := e.editCompositeObjects(page, path, indexes, func(_ *Renderer, nodes []*editorCompositeNode, members []CompositeMember) error {
|
||||
siblings := nodes[0].siblings()
|
||||
selected := make([]bool, len(siblings))
|
||||
for j, node := range nodes {
|
||||
i := slices.Index(siblings, node)
|
||||
if i < 0 || !members[j].Capabilities.Order {
|
||||
return fmt.Errorf("ordering requires members in the same container")
|
||||
}
|
||||
selected[i] = true
|
||||
}
|
||||
ordered := slices.Clone(siblings)
|
||||
orderEditorObjects(ordered, selected, order)
|
||||
for i, node := range siblings {
|
||||
if node != ordered[i] {
|
||||
node.owner.patches = append(node.owner.patches, editorXMLPatch{node.span.start, node.span.end, ordered[i].data})
|
||||
}
|
||||
if slices.Contains(nodes, ordered[i]) {
|
||||
result = append(result, node.index)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ReplaceCompositeImage 替换内部图片数据,保留变换、裁剪、蒙版、边框和透明度
|
||||
// 入参: page 页面索引, path 父复合路径, index 图片序号, data PNG或JPEG数据
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) ReplaceCompositeImage(page int, path ObjectPath, index int, data []byte) error {
|
||||
return e.editCompositeObjects(page, path, []int{index}, func(_ *Renderer, nodes []*editorCompositeNode, _ []CompositeMember) error {
|
||||
node := nodes[0]
|
||||
if node.object.Type != "ImageObject" {
|
||||
return fmt.Errorf("composite member is not an image")
|
||||
}
|
||||
object := node.object
|
||||
id, err := e.AddImage(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
object.ImageObject.ResourceID = id
|
||||
return node.update(object)
|
||||
})
|
||||
}
|
||||
|
||||
// update 合并成员属性变化,不重写原文中未修改的字段
|
||||
// 入参: object 修改后的对象
|
||||
// 返回: error 错误信息
|
||||
func (n *editorCompositeNode) update(object GraphicObject) error {
|
||||
data, err := editorXMLObject(n.data, n.node, n.object, object)
|
||||
if err != nil || bytes.Equal(data, n.data) {
|
||||
return err
|
||||
}
|
||||
next, err := newEditorCompositeNode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.data, n.node, n.object, n.changed = data, next.node, next.object, true
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectCompositeIDs 为已校验副本中的标准标识分配新值,拒绝重复标识
|
||||
// 入参: node 原节点, ids 原标识与新标识
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) collectCompositeIDs(node *editorXML, ids map[string]string) error {
|
||||
if id := editorResourceID(node.attr("ID")); id != "" {
|
||||
if ids[id] != "" {
|
||||
return fmt.Errorf("duplicate object ID %q", id)
|
||||
}
|
||||
ids[id] = e.nextID()
|
||||
}
|
||||
for _, child := range node.children {
|
||||
if err := e.collectCompositeIDs(child, ids); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user