Files
ofdgo/ofdgo_editor_selection.go
T
2026-09-30 11:44:15 +08:00

180 lines
6.3 KiB
Go

// 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"
"slices"
)
// SelectionBounds 获取正文与注解混合选区的实际范围,复用同一次度量的资源缓存
// 入参: page 页面索引, objects 正文标识, annotations 注解标识,各列表不得重复
// 返回: Box 页面毫米范围, error 标识或度量错误
func (e *Editor) SelectionBounds(page int, objects, annotations []string) (Box, error) {
boxes, _, _, err := e.selectionGeometry(page, objects, annotations)
if err != nil {
return Box{}, err
}
var bounds Box
for _, box := range boxes {
bounds = unionTextBox(bounds, box)
}
if bounds.W <= 0 || bounds.H <= 0 {
return Box{}, fmt.Errorf("selection has no visible bounds")
}
return bounds, nil
}
// AlignSelection 将正文与注解统一对齐,单选对齐页面,多选对齐选区,一次撤销恢复全部
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, alignment 为left、center、right、top、middle或bottom
// 返回: error 标识、度量或编辑错误
func (e *Editor) AlignSelection(page int, objects, annotations []string, alignment string) error {
if !slices.Contains([]string{"left", "center", "right", "top", "middle", "bottom"}, alignment) {
return fmt.Errorf("invalid alignment %q", alignment)
}
boxes, _, target, err := e.selectionGeometry(page, objects, annotations)
if err != nil || len(boxes) == 0 {
return err
}
if len(boxes) > 1 {
target = Box{}
for _, box := range boxes {
target = unionTextBox(target, box)
}
}
matrices := make([]Matrix, len(boxes))
for i, box := range boxes {
matrices[i] = editorAlignment(box, target, alignment)
}
return e.transformSelection(page, objects, annotations, matrices)
}
// DistributeSelection 按正文与注解实际范围等距分布,固定两端并校验位置顺序,一次撤销恢复全部
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, axis 为horizontal或vertical
// 返回: error 标识、度量、重叠或编辑错误
func (e *Editor) DistributeSelection(page int, objects, annotations []string, axis string) error {
if axis != "horizontal" && axis != "vertical" {
return fmt.Errorf("invalid distribution axis %q", axis)
}
boxes, positions, _, err := e.selectionGeometry(page, objects, annotations)
if err != nil || len(boxes) < 3 {
return err
}
matrices, err := editorDistribution(boxes, positions, axis)
if err != nil {
return err
}
return e.transformSelection(page, objects, annotations, matrices)
}
// selectionGeometry 一次读取页面与资源,按正文、注解标识顺序度量并保留绘制位置
// 入参: page 页面索引, objects 正文标识, annotations 注解标识
// 返回: []Box 成员范围, []editorObjectPosition 绘制位置, Box 页面范围, error 标识或度量错误
func (e *Editor) selectionGeometry(page int, objects, annotations []string) ([]Box, []editorObjectPosition, Box, error) {
items, positions, err := e.selectedObjects(page, objects)
if err != nil {
return nil, nil, Box{}, err
}
reader, err := e.Reader()
if err != nil {
return nil, nil, Box{}, err
}
defer reader.Close()
content, err := reader.PageContentByIndex(page)
if err != nil {
return nil, nil, Box{}, err
}
renderer := e.newRenderer(reader)
paper, err := renderer.GetPageBox(content)
if err != nil {
return nil, nil, Box{}, err
}
boxes := make([]Box, 0, len(items)+len(annotations))
for i, item := range items {
box, err := renderer.ObjectBounds(item, e.pages[page].Content.Layer[positions[i].layer].DrawParam)
if err != nil {
return nil, nil, Box{}, err
}
boxes = append(boxes, box)
}
list := reader.Annots[content.ID]
lookup := make(map[string]int, len(list))
for i, annotation := range list {
lookup[annotation.ID] = i
}
seen := make(map[string]bool, len(annotations))
for _, id := range annotations {
index, found := lookup[id]
if seen[id] || !found {
return nil, nil, Box{}, fmt.Errorf("duplicate or missing annotation %q", id)
}
seen[id] = true
box, _, err := renderer.AnnotationGeometry(list[index])
if err != nil {
return nil, nil, Box{}, err
}
if box.W <= 0 || box.H <= 0 {
box, err = ParseBox(list[index].Appearance.Boundary)
if err != nil {
return nil, nil, Box{}, err
}
}
boxes = append(boxes, box)
positions = append(positions, editorObjectPosition{layer: len(content.Content.Layer), index: index})
}
for _, box := range boxes {
if !finite(box.X) || !finite(box.Y) || !finite(box.W) || !finite(box.H) || box.W <= 0 || box.H <= 0 {
return nil, nil, Box{}, fmt.Errorf("selection member has no visible bounds")
}
}
return boxes, positions, paper, nil
}
// transformSelection 原子应用成员平移,跳过未改变的成员及历史记录
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, matrices 按正文、注解排列的平移
// 返回: error 编辑错误
func (e *Editor) transformSelection(page int, objects, annotations []string, matrices []Matrix) error {
return e.Transaction(func(edit *Editor) error {
items, _, err := edit.selectedObjects(page, objects)
if err != nil {
return err
}
changes := make(map[string]Matrix, len(objects))
var updates []GraphicObject
for i, id := range objects {
if matrices[i] != IdentityMatrix {
changes[id] = matrices[i]
updates = append(updates, items[i])
}
}
if len(updates) != 0 {
if err := edit.transformObjects(page, updates, func(object GraphicObject) (GraphicObject, error) {
return edit.transformMatrix(object, changes[editorObjectID(object)])
}); err != nil {
return err
}
}
for i, id := range annotations {
matrix := matrices[len(objects)+i]
if matrix != IdentityMatrix {
if err := edit.MoveAnnotations(page, []string{id}, matrix.e, matrix.f); err != nil {
return err
}
}
}
return nil
})
}