mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 15:06:46 +08:00
This commit is contained in:
1 parent
9f2ef7e212
commit
2ffbf573ba
11 files changed
+497
-128
No files matched your search
@@ -0,0 +1,236 @@
|
||||
// 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"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// GroupCompositeObjects 组合内部同一容器的连续成员,保留标识、绘制顺序及共享资源
|
||||
// 入参: page 页面索引, path 父路径, indexes 至少两个成员序号
|
||||
// 返回: int 新组合序号, error 错误信息
|
||||
func (e *Editor) GroupCompositeObjects(page int, path ObjectPath, indexes []int) (int, error) {
|
||||
if len(indexes) < 2 {
|
||||
return 0, fmt.Errorf("grouping requires at least two objects")
|
||||
}
|
||||
var result int
|
||||
err := e.editCompositeObjects(page, path, indexes, func(_ *Renderer, nodes []*editorCompositeNode, members []CompositeMember) error {
|
||||
for _, member := range members {
|
||||
if !member.Capabilities.Copy || !member.Capabilities.Order {
|
||||
return fmt.Errorf("composite member cannot be grouped")
|
||||
}
|
||||
}
|
||||
slices.SortFunc(nodes, func(a, b *editorCompositeNode) int { return a.index - b.index })
|
||||
siblings := nodes[0].siblings()
|
||||
first := slices.Index(siblings, nodes[0])
|
||||
var content []byte
|
||||
for i, node := range nodes {
|
||||
if first+i >= len(siblings) || siblings[first+i] != node {
|
||||
return fmt.Errorf("grouping requires consecutive members in the same container")
|
||||
}
|
||||
content = append(content, bytes.TrimPrefix(node.data, []byte(xml.Header))...)
|
||||
}
|
||||
content, err := editorXMLContainer("Content", nil, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err = editorXMLContainer("CompositeObject", ofdAttrs{{Name: xml.Name{Local: "ID"}, Value: e.nextID()}, {Name: xml.Name{Local: "Boundary"}, Value: "0 0 1 1"}}, bytes.TrimPrefix(content, []byte(xml.Header)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, node := range nodes {
|
||||
var replacement []byte
|
||||
if i == 0 {
|
||||
replacement = bytes.TrimPrefix(content, []byte(xml.Header))
|
||||
}
|
||||
node.owner.patches = append(node.owner.patches, editorXMLPatch{node.span.start, node.span.end, replacement})
|
||||
}
|
||||
result = nodes[0].index
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// UngroupCompositeObject 展开内部组合,隔离引用资源并保留父容器的样式和裁剪
|
||||
// 入参: page 页面索引, path 父路径, index 组合序号
|
||||
// 返回: []int 展开后的成员序号, error 错误信息
|
||||
func (e *Editor) UngroupCompositeObject(page int, path ObjectPath, index int) ([]int, error) {
|
||||
var result []int
|
||||
err := e.editCompositeObjects(page, path, []int{index}, func(renderer *Renderer, nodes []*editorCompositeNode, members []CompositeMember) error {
|
||||
node := nodes[0]
|
||||
if !members[0].Capabilities.Order {
|
||||
return fmt.Errorf("composite container cannot be reordered")
|
||||
}
|
||||
return e.ungroupCompositeNode(renderer, node, func(objects []GraphicObject) error {
|
||||
var content []byte
|
||||
for i, object := range objects {
|
||||
content = append(content, object.origin.data...)
|
||||
maps.Copy(node.owner.states, object.CompositeGraphicUnit.states)
|
||||
node.owner.states[editorObjectID(object)] = object.state
|
||||
result = append(result, index+i)
|
||||
}
|
||||
delete(node.owner.states, editorObjectID(node.object))
|
||||
node.owner.patches = append(node.owner.patches, editorXMLPatch{node.span.start, node.span.end, content})
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ungroupCompositeNode 将组合展开至所在坐标系,祖先透明度和裁剪仅由原父级应用一次
|
||||
// 入参: renderer 当前读取器, node 待展开组合, replace 原子替换回调
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) ungroupCompositeNode(renderer *Renderer, node *editorCompositeNode, replace func([]GraphicObject) error) error {
|
||||
if !e.compositeUngroupable(node.node) {
|
||||
return fmt.Errorf("group container cannot be removed without losing attributes")
|
||||
}
|
||||
copy, err := newEditorCompositeNode(node.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy.setStates(node.states)
|
||||
if !node.boundaryInCTM {
|
||||
if err := copy.convertCoordinates(node.parent, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
copy.parent, copy.boundaryInCTM, copy.visible = IdentityMatrix, true, true
|
||||
copy.defaults, copy.drawParams = node.defaults, node.drawParams
|
||||
members, err := e.compositeMembers(copy, renderer.Reader, renderer, make(map[string]bool))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(members) == 0 {
|
||||
return fmt.Errorf("group has no objects")
|
||||
}
|
||||
for _, member := range members {
|
||||
if !member.transformable() || !editorXMLCopyable(member.node) {
|
||||
return fmt.Errorf("group member cannot be expanded")
|
||||
}
|
||||
}
|
||||
return e.pasteCompositeSelection(&CompositeSelection{editor: e, nodes: members}, func(objects []GraphicObject) error {
|
||||
ids := make(map[string]string)
|
||||
if copy.ref != nil {
|
||||
for _, object := range objects {
|
||||
if err := e.collectCompositeIDs(object.origin.node, ids); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, object := range objects {
|
||||
data := object.origin.data
|
||||
if len(ids) != 0 {
|
||||
data, err = editorXMLRemapIDs(data, object.origin.node, ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
member, err := newEditorCompositeNode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
states := maps.Clone(object.CompositeGraphicUnit.states)
|
||||
if states == nil {
|
||||
states = make(map[string]editorCompositeState)
|
||||
}
|
||||
states[editorObjectID(object)] = object.state
|
||||
if len(ids) != 0 {
|
||||
states = remapCompositeStates(states, ids)
|
||||
}
|
||||
if object.Type == "CompositeObject" || object.Type == "CompositeGraphicUnit" {
|
||||
if err := member.convertCoordinates(IdentityMatrix, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !node.boundaryInCTM {
|
||||
if err := member.convertCoordinates(node.parent, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
member.node.parent = &editorXML{name: xml.Name{Local: "Layer"}, parent: &editorXML{name: xml.Name{Local: "Content"}}}
|
||||
member.object.state = states[editorObjectID(member.object)]
|
||||
member.object.TextObject.layout = member.object.state.layout
|
||||
member.object.CompositeGraphicUnit.states = states
|
||||
member.object.origin = &editorObjectOrigin{editor: e, data: member.data, node: member.node, object: member.object}
|
||||
objects[i] = member.object
|
||||
}
|
||||
return replace(objects)
|
||||
})
|
||||
}
|
||||
|
||||
// compositeUngroupable 检查待移除的容器及引用链,子对象原文不展开重写
|
||||
// 入参: node 组合节点
|
||||
// 返回: bool 是否可解组
|
||||
func (e *Editor) compositeUngroupable(node *editorXML) bool {
|
||||
seen := make(map[string]bool)
|
||||
for {
|
||||
if !editorUngroupable(node) {
|
||||
return false
|
||||
}
|
||||
id := node.attr("ResourceID")
|
||||
if id == "" {
|
||||
return true
|
||||
}
|
||||
if seen[id] {
|
||||
return false
|
||||
}
|
||||
seen[id] = true
|
||||
resource, err := e.compositeDefinition(id)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
node = resource.node
|
||||
}
|
||||
}
|
||||
|
||||
// compositeDefinition 读取复合资源原文,不加载图片或字体数据
|
||||
// 入参: id 资源标识
|
||||
// 返回: *editorCompositeNode 定义节点, error 错误信息
|
||||
func (e *Editor) compositeDefinition(id string) (*editorCompositeNode, error) {
|
||||
var data []byte
|
||||
for _, resource := range e.resources {
|
||||
if resource.composite == id {
|
||||
data = resource.data
|
||||
break
|
||||
}
|
||||
}
|
||||
if data == nil && e.source != nil {
|
||||
var err error
|
||||
data, err = e.source.reader.readFile(e.source.reader.resourceFiles[id])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
root, err := parseEditorXML(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if units := root.child("CompositeGraphicUnits"); units != nil {
|
||||
for _, unit := range units.children {
|
||||
if unit.name.Local == "CompositeGraphicUnit" && unit.attr("ID") == id {
|
||||
fragment, err := editorXMLStandalone(data[unit.start:unit.end], unit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newEditorCompositeNode(fragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("composite resource %q not found", id)
|
||||
}
|
||||
Reference in new issue
Block a user