mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:53:44 +08:00
This commit is contained in:
1 parent
c68bf1f3c9
commit
d7e2456f67
6 files changed
+339
-141
No files matched your search
+169
-5
@@ -15,11 +15,179 @@
|
||||
package ofdgo
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/tdewolff/canvas"
|
||||
)
|
||||
|
||||
// CopyObjects 按原绘制顺序复制同页对象并平移,复用资源,提交一次撤销记录。
|
||||
// 入参: page 页面索引, ids 对象标识, dx、dy 毫米位移
|
||||
// 返回: []string 按绘制顺序排列的新对象标识, error 错误信息
|
||||
func (e *Editor) CopyObjects(page int, ids []string, dx, dy float64) ([]string, error) {
|
||||
if !finite(dx) || !finite(dy) {
|
||||
return nil, fmt.Errorf("copy requires finite offsets")
|
||||
}
|
||||
_, indexes, err := e.selectedObjects(page, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slices.Sort(indexes)
|
||||
before := e.pages[page].Content.Layer[0].Objects
|
||||
objects := make([]GraphicObject, len(indexes))
|
||||
result := make([]string, len(indexes))
|
||||
for i, index := range indexes {
|
||||
object, err := cloneEditorObject(before[index])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object, err = transformEditorObject(object, dx, dy, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = strconv.Itoa(e.maxID + i + 1)
|
||||
objects[i], err = e.prepareObject(result[i], object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
e.maxID += len(objects)
|
||||
e.replaceObjects(page, append(slices.Clone(before), objects...))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// OrderObjects 调整同页选区的绘制顺序,保留选中及未选中对象各自的相对顺序。
|
||||
// 入参: page 页面索引, ids 对象标识, order 为up、down、top或bottom
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) OrderObjects(page int, ids []string, order string) error {
|
||||
if !slices.Contains([]string{"up", "down", "top", "bottom"}, order) {
|
||||
return fmt.Errorf("invalid object order %q", order)
|
||||
}
|
||||
_, indexes, err := e.selectedObjects(page, ids)
|
||||
if err != nil || len(indexes) == 0 {
|
||||
return err
|
||||
}
|
||||
objects := slices.Clone(e.pages[page].Content.Layer[0].Objects)
|
||||
selected := make([]bool, len(objects))
|
||||
for _, index := range indexes {
|
||||
selected[index] = true
|
||||
}
|
||||
switch order {
|
||||
case "up":
|
||||
for i := len(objects) - 2; i >= 0; i-- {
|
||||
if selected[i] && !selected[i+1] {
|
||||
objects[i], objects[i+1] = objects[i+1], objects[i]
|
||||
selected[i], selected[i+1] = false, true
|
||||
}
|
||||
}
|
||||
case "down":
|
||||
for i := 1; i < len(objects); i++ {
|
||||
if selected[i] && !selected[i-1] {
|
||||
objects[i], objects[i-1] = objects[i-1], objects[i]
|
||||
selected[i], selected[i-1] = false, true
|
||||
}
|
||||
}
|
||||
default:
|
||||
ordered := make([]GraphicObject, 0, len(objects))
|
||||
for _, takeSelected := range []bool{order == "bottom", order != "bottom"} {
|
||||
for i, object := range objects {
|
||||
if selected[i] == takeSelected {
|
||||
ordered = append(ordered, object)
|
||||
}
|
||||
}
|
||||
}
|
||||
objects = ordered
|
||||
}
|
||||
e.replaceObjects(page, objects)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DistributeObjects 按可见范围等距分布同页对象,固定两端对象,保留绘制顺序。
|
||||
// 不足三个对象时不修改;允许负间距,无法保持位置顺序的重叠布局返回错误。
|
||||
// 入参: page 页面索引, ids 对象标识, axis 为horizontal或vertical
|
||||
// 返回: error 错误信息
|
||||
func (e *Editor) DistributeObjects(page int, ids []string, axis string) error {
|
||||
if axis != "horizontal" && axis != "vertical" {
|
||||
return fmt.Errorf("invalid distribution axis %q", axis)
|
||||
}
|
||||
objects, indexes, err := e.selectedObjects(page, ids)
|
||||
if err != nil || len(objects) < 3 {
|
||||
return err
|
||||
}
|
||||
boxes, err := e.objectBounds(page, objects)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
positions, sizes := make([]float64, len(boxes)), make([]float64, len(boxes))
|
||||
order := make([]int, len(boxes))
|
||||
total := 0.0
|
||||
for i, box := range boxes {
|
||||
positions[i], sizes[i] = box.X, box.W
|
||||
if axis == "vertical" {
|
||||
positions[i], sizes[i] = box.Y, box.H
|
||||
}
|
||||
order[i] = i
|
||||
total += sizes[i]
|
||||
}
|
||||
slices.SortFunc(order, func(i, j int) int {
|
||||
if c := cmp.Compare(positions[i], positions[j]); c != 0 {
|
||||
return c
|
||||
}
|
||||
return cmp.Compare(indexes[i], indexes[j])
|
||||
})
|
||||
first, last := order[0], order[len(order)-1]
|
||||
gap := (positions[last] + sizes[last] - positions[first] - total) / float64(len(order)-1)
|
||||
for i, index := range order[:len(order)-1] {
|
||||
step := sizes[index] + gap
|
||||
if canvas.Equal(step, 0) {
|
||||
step = 0
|
||||
}
|
||||
if step < 0 || step == 0 && indexes[index] > indexes[order[i+1]] {
|
||||
return fmt.Errorf("overlap prevents ordered equal-gap distribution")
|
||||
}
|
||||
}
|
||||
next := positions[first] + sizes[first] + gap
|
||||
updates := make([]GraphicObject, 0, len(order)-2)
|
||||
for _, index := range order[1 : len(order)-1] {
|
||||
position := next
|
||||
next += sizes[index] + gap
|
||||
if canvas.Equal(position, positions[index]) {
|
||||
continue
|
||||
}
|
||||
dx, dy := position-positions[index], 0.0
|
||||
if axis == "vertical" {
|
||||
dx, dy = 0, dx
|
||||
}
|
||||
object, err := cloneEditorObject(objects[index])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
object, err = transformEditorObject(object, dx, dy, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updates = append(updates, object)
|
||||
}
|
||||
return e.UpdateObjects(page, updates)
|
||||
}
|
||||
|
||||
// replaceObjects 替换正文对象列表,隔离历史快照与后续的增删、排序操作。
|
||||
func (e *Editor) replaceObjects(page int, objects []GraphicObject) {
|
||||
before := e.pages[page].Content.Layer[0].Objects
|
||||
if reflect.DeepEqual(before, objects) {
|
||||
return
|
||||
}
|
||||
before, after := slices.Clone(before), slices.Clone(objects)
|
||||
e.pages[page].Content.Layer[0].Objects = slices.Clone(after)
|
||||
if change := e.recordChange(); change != nil {
|
||||
change.undo = func(e *Editor) { e.pages[page].Content.Layer[0].Objects = slices.Clone(before) }
|
||||
change.redo = func(e *Editor) { e.pages[page].Content.Layer[0].Objects = slices.Clone(after) }
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateObjects 原子替换同页对象,按各对象ID定位,全部校验通过后提交一次历史记录。
|
||||
// 入参: page 页面索引, objects 新对象内容,ID不得重复
|
||||
// 返回: error 错误信息
|
||||
@@ -146,11 +314,7 @@ func (e *Editor) DeleteObjects(page int, ids []string) error {
|
||||
for _, index := range slices.Backward(indexes) {
|
||||
after = slices.Delete(after, index, index+1)
|
||||
}
|
||||
e.pages[page].Content.Layer[0].Objects = slices.Clone(after)
|
||||
if change := e.recordChange(); change != nil {
|
||||
change.undo = func(e *Editor) { e.pages[page].Content.Layer[0].Objects = slices.Clone(before) }
|
||||
change.redo = func(e *Editor) { e.pages[page].Content.Layer[0].Objects = slices.Clone(after) }
|
||||
}
|
||||
e.replaceObjects(page, after)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user