mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
7b9b961196
commit
22040ec836
6 files changed
+255
-130
No files matched your search
@@ -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-20260930033808-2918854e21c8
|
github.com/xiaoqidun/pdfgo v0.0.0-20260930050241-ecc0b0d74180
|
||||||
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-20260930033808-2918854e21c8 h1:19c9Wr9PPJ/GEh4AlrDqiUgucq1bYSIvughgsPKvNis=
|
github.com/xiaoqidun/pdfgo v0.0.0-20260930050241-ecc0b0d74180 h1:2/QHfh0NIg74xzT3KHjqN7u1S9U2F+4N0HZqPrJFxvw=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20260930033808-2918854e21c8/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
github.com/xiaoqidun/pdfgo v0.0.0-20260930050241-ecc0b0d74180/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=
|
||||||
|
|||||||
+30
-102
@@ -1599,17 +1599,14 @@ func splitEditorSelection(ids []string) ([]string, []string) {
|
|||||||
// 返回: any 文档与新选区, error 错误信息
|
// 返回: any 文档与新选区, error 错误信息
|
||||||
func changeSelection(args []js.Value) (any, error) {
|
func changeSelection(args []js.Value) (any, error) {
|
||||||
var selected []string
|
var selected []string
|
||||||
result, err := changeObjects(func() error {
|
result, err := changeAtomicObjects(func() error {
|
||||||
page, operation := args[0].Int(), args[2].String()
|
page, operation := args[0].Int(), args[2].String()
|
||||||
objects, annotations := splitEditorSelection(stringsFromJS(args[1]))
|
objects, annotations := splitEditorSelection(stringsFromJS(args[1]))
|
||||||
switch operation {
|
switch operation {
|
||||||
case "align", "distribute":
|
case "align", "distribute":
|
||||||
return arrangeEditorSelection(page, stringsFromJS(args[1]), operation, args[3].String())
|
return arrangeEditorSelection(page, stringsFromJS(args[1]), operation, args[3].String())
|
||||||
case "delete":
|
case "delete":
|
||||||
if err := currentEditor.DeleteObjects(page, objects); err != nil {
|
return currentEditor.DeleteSelection(page, objects, annotations)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return currentEditor.DeleteAnnotations(page, annotations)
|
|
||||||
case "copy":
|
case "copy":
|
||||||
clipboard, err := captureEditorSelection(page, stringsFromJS(args[1]))
|
clipboard, err := captureEditorSelection(page, stringsFromJS(args[1]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1618,102 +1615,31 @@ func changeSelection(args []js.Value) (any, error) {
|
|||||||
selected, err = pasteEditorSelection(page, clipboard, args[3].Float(), args[4].Float())
|
selected, err = pasteEditorSelection(page, clipboard, args[3].Float(), args[4].Float())
|
||||||
return err
|
return err
|
||||||
case "style":
|
case "style":
|
||||||
style := objectStyle(args[3])
|
return currentEditor.StyleSelection(page, objects, annotations, objectStyle(args[3]))
|
||||||
if err := currentEditor.StyleObjects(page, objects, style); err != nil {
|
case "erase":
|
||||||
return err
|
return currentEditor.EraseSelection(page, objects, annotations, ofdgo.Box{
|
||||||
}
|
X: args[3].Float(), Y: args[4].Float(), W: args[5].Float(), H: args[6].Float(),
|
||||||
for _, id := range annotations {
|
})
|
||||||
path := ofdgo.ObjectPath{Annotation: id}
|
case "erasePath":
|
||||||
members, err := currentEditor.CompositeObjects(page, path)
|
return currentEditor.EraseSelectionPath(page, objects, annotations, pointsFromJS(args[3]))
|
||||||
if err != nil {
|
case "rotate":
|
||||||
return err
|
return currentEditor.RotateSelection(page, objects, annotations, args[3].Int())
|
||||||
}
|
case "flip":
|
||||||
indexes := make([]int, len(members))
|
return currentEditor.FlipSelection(page, objects, annotations, args[3].String())
|
||||||
for i := range indexes {
|
case "resize":
|
||||||
indexes[i] = i
|
return currentEditor.ResizeSelection(page, objects, annotations, ofdgo.Box{
|
||||||
}
|
X: args[3].Float(), Y: args[4].Float(), W: args[5].Float(), H: args[6].Float(),
|
||||||
if err := currentEditor.StyleCompositeObjects(page, path, indexes, style); err != nil {
|
})
|
||||||
return err
|
case "transform":
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case "erase", "erasePath":
|
|
||||||
var box ofdgo.Box
|
|
||||||
var points []ofdgo.Point
|
|
||||||
if operation == "erase" {
|
|
||||||
box = ofdgo.Box{X: args[3].Float(), Y: args[4].Float(), W: args[5].Float(), H: args[6].Float()}
|
|
||||||
if err := currentEditor.EraseObjects(page, objects, box); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
points = pointsFromJS(args[3])
|
|
||||||
if err := currentEditor.EraseObjectsPath(page, objects, points); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, id := range annotations {
|
|
||||||
path := ofdgo.ObjectPath{Annotation: id}
|
|
||||||
members, err := currentEditor.CompositeObjects(page, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
indexes := make([]int, len(members))
|
|
||||||
for i := range indexes {
|
|
||||||
indexes[i] = i
|
|
||||||
}
|
|
||||||
if operation == "erase" {
|
|
||||||
err = currentEditor.EraseCompositeObjects(page, path, indexes, box)
|
|
||||||
} else {
|
|
||||||
err = currentEditor.EraseCompositeObjectsPath(page, path, indexes, points)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
matrix := ofdgo.IdentityMatrix
|
|
||||||
if operation == "transform" {
|
|
||||||
dx, dy, scale := args[3].Float(), args[4].Float(), args[5].Float()
|
dx, dy, scale := args[3].Float(), args[4].Float(), args[5].Float()
|
||||||
if scale <= 0 {
|
if scale <= 0 {
|
||||||
return fmt.Errorf("scale must be positive")
|
return fmt.Errorf("scale must be positive")
|
||||||
}
|
}
|
||||||
matrix = ofdgo.NewMatrix(fmt.Sprintf("%g 0 0 %g %g %g", scale, scale, dx, dy))
|
matrix := ofdgo.NewMatrix(fmt.Sprintf("%g 0 0 %g %g %g", scale, scale, dx, dy))
|
||||||
} else {
|
return currentEditor.TransformSelection(page, objects, annotations, matrix)
|
||||||
bounds, err := editorSelectionBounds(page, objects, annotations)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
x, y := bounds.X+bounds.W/2, bounds.Y+bounds.H/2
|
|
||||||
switch operation {
|
|
||||||
case "rotate":
|
|
||||||
angle := float64(args[3].Int()) * math.Pi / 180
|
|
||||||
matrix = ofdgo.NewMatrix(fmt.Sprintf("%g %g %g %g 0 0", math.Cos(angle), math.Sin(angle), -math.Sin(angle), math.Cos(angle)))
|
|
||||||
case "flip":
|
|
||||||
if args[3].String() == "horizontal" {
|
|
||||||
matrix = ofdgo.NewMatrix("-1 0 0 1 0 0")
|
|
||||||
} else if args[3].String() == "vertical" {
|
|
||||||
matrix = ofdgo.NewMatrix("1 0 0 -1 0 0")
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid flip axis")
|
|
||||||
}
|
|
||||||
case "resize":
|
|
||||||
sx, sy := args[5].Float()/bounds.W, args[6].Float()/bounds.H
|
|
||||||
if sx <= 0 || sy <= 0 {
|
|
||||||
return fmt.Errorf("size must be positive")
|
|
||||||
}
|
|
||||||
matrix = ofdgo.NewMatrix(fmt.Sprintf("%g 0 0 %g %g %g", sx, sy, args[3].Float()-bounds.X*sx, args[4].Float()-bounds.Y*sy))
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported mixed selection operation %q", operation)
|
return fmt.Errorf("unsupported mixed selection operation %q", operation)
|
||||||
}
|
}
|
||||||
if operation != "resize" {
|
|
||||||
matrix = ofdgo.TranslationMatrix(x, y).Multiply(matrix).Multiply(ofdgo.TranslationMatrix(-x, -y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := currentEditor.TransformObjectsMatrix(page, objects, matrix); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return currentEditor.TransformAnnotations(page, annotations, matrix)
|
|
||||||
})
|
})
|
||||||
if err != nil || selected == nil {
|
if err != nil || selected == nil {
|
||||||
return result, err
|
return result, err
|
||||||
@@ -1736,13 +1662,6 @@ func arrangeEditorSelection(page int, ids []string, operation, direction string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// editorSelectionBounds 计算混合选区的实际内容范围
|
|
||||||
// 入参: page 页面索引, objects 正文标识, annotations 注解标识
|
|
||||||
// 返回: ofdgo.Box 页面毫米范围, error 错误信息
|
|
||||||
func editorSelectionBounds(page int, objects, annotations []string) (ofdgo.Box, error) {
|
|
||||||
return currentEditor.SelectionBounds(page, objects, annotations)
|
|
||||||
}
|
|
||||||
|
|
||||||
// captureEditorSelection 捕获正文与注解的独立副本
|
// captureEditorSelection 捕获正文与注解的独立副本
|
||||||
// 入参: page 页面索引, ids 画布标识
|
// 入参: page 页面索引, ids 画布标识
|
||||||
// 返回: *editorClipboard 快照, error 错误信息
|
// 返回: *editorClipboard 快照, error 错误信息
|
||||||
@@ -3828,11 +3747,20 @@ func eraseObjects(args []js.Value) (any, error) {
|
|||||||
// 入参: apply 待执行的编辑操作
|
// 入参: apply 待执行的编辑操作
|
||||||
// 返回: any 文档信息, error 错误信息
|
// 返回: any 文档信息, error 错误信息
|
||||||
func changeObjects(apply func() error) (any, error) {
|
func changeObjects(apply func() error) (any, error) {
|
||||||
|
return changeAtomicObjects(func() error {
|
||||||
|
return currentEditor.Transaction(func(*ofdgo.Editor) error { return apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// changeAtomicObjects 执行库层原子操作,按文档版本更新预览
|
||||||
|
// 入参: apply 已具备事务保证的编辑操作
|
||||||
|
// 返回: any 文档信息, error 错误信息
|
||||||
|
func changeAtomicObjects(apply func() error) (any, error) {
|
||||||
if currentEditor == nil {
|
if currentEditor == nil {
|
||||||
return nil, fmt.Errorf("no document is being edited")
|
return nil, fmt.Errorf("no document is being edited")
|
||||||
}
|
}
|
||||||
revision := currentEditor.Revision()
|
revision := currentEditor.Revision()
|
||||||
if err := currentEditor.Transaction(func(*ofdgo.Editor) error { return apply() }); err != nil {
|
if err := apply(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if currentEditor.Revision() == revision {
|
if currentEditor.Revision() == revision {
|
||||||
|
|||||||
@@ -162,10 +162,10 @@ func (e *Editor) TransformCompositeObjects(page int, path ObjectPath, indexes []
|
|||||||
// 入参: page 页面索引, path 父复合对象路径, indexes 成员序号, degrees 顺时针角度,为90度的整数倍
|
// 入参: page 页面索引, path 父复合对象路径, indexes 成员序号, degrees 顺时针角度,为90度的整数倍
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (e *Editor) RotateCompositeObjects(page int, path ObjectPath, indexes []int, degrees int) error {
|
func (e *Editor) RotateCompositeObjects(page int, path ObjectPath, indexes []int, degrees int) error {
|
||||||
if degrees%90 != 0 {
|
m, err := editorRotation(degrees)
|
||||||
return fmt.Errorf("rotation must be a multiple of 90 degrees")
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
m := []Matrix{IdentityMatrix, {b: 1, c: -1}, {a: -1, d: -1}, {b: -1, c: 1}}[(degrees%360+360)%360/90]
|
|
||||||
return e.changeCompositeObjects(page, path, indexes, func(box Box) Matrix { return compositeOrientation(box, m) })
|
return e.changeCompositeObjects(page, path, indexes, func(box Box) Matrix { return compositeOrientation(box, m) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,14 +173,9 @@ func (e *Editor) RotateCompositeObjects(page int, path ObjectPath, indexes []int
|
|||||||
// 入参: page 页面索引, path 父复合对象路径, indexes 成员序号, axis 为horizontal或vertical
|
// 入参: page 页面索引, path 父复合对象路径, indexes 成员序号, axis 为horizontal或vertical
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (e *Editor) FlipCompositeObjects(page int, path ObjectPath, indexes []int, axis string) error {
|
func (e *Editor) FlipCompositeObjects(page int, path ObjectPath, indexes []int, axis string) error {
|
||||||
m := IdentityMatrix
|
m, err := editorFlip(axis)
|
||||||
switch axis {
|
if err != nil {
|
||||||
case "horizontal":
|
return err
|
||||||
m.a = -1
|
|
||||||
case "vertical":
|
|
||||||
m.d = -1
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid flip axis %q", axis)
|
|
||||||
}
|
}
|
||||||
return e.changeCompositeObjects(page, path, indexes, func(box Box) Matrix { return compositeOrientation(box, m) })
|
return e.changeCompositeObjects(page, path, indexes, func(box Box) Matrix { return compositeOrientation(box, m) })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package ofdgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,6 +38,88 @@ func (e *Editor) SelectionBounds(page int, objects, annotations []string) (Box,
|
|||||||
return bounds, nil
|
return bounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransformSelection 原子变换正文与注解,失败回滚,一次撤销恢复全部
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, matrix 有限可逆变换
|
||||||
|
// 返回: error 标识、变换或编辑错误
|
||||||
|
func (e *Editor) TransformSelection(page int, objects, annotations []string, matrix Matrix) error {
|
||||||
|
if _, ok := matrix.Invert(); !ok || !finite(matrix.a) || !finite(matrix.b) || !finite(matrix.c) || !finite(matrix.d) || !finite(matrix.e) || !finite(matrix.f) {
|
||||||
|
return fmt.Errorf("selection transform must be finite and invertible")
|
||||||
|
}
|
||||||
|
if matrix == IdentityMatrix {
|
||||||
|
if _, _, err := e.selectedObjects(page, objects); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
seen := make(map[string]bool, len(annotations))
|
||||||
|
for _, id := range annotations {
|
||||||
|
if seen[id] {
|
||||||
|
return fmt.Errorf("duplicate annotation ID %q", id)
|
||||||
|
}
|
||||||
|
seen[id] = true
|
||||||
|
if _, err := e.Annotation(page, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.Transaction(func(edit *Editor) error {
|
||||||
|
if err := edit.TransformObjectsMatrix(page, objects, matrix); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return edit.TransformAnnotations(page, annotations, matrix)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotateSelection 绕正文与注解的共同可见范围中心旋转,保留相对位置及资源
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, degrees 顺时针角度,仅支持90度的整数倍
|
||||||
|
// 返回: error 标识、度量或编辑错误
|
||||||
|
func (e *Editor) RotateSelection(page int, objects, annotations []string, degrees int) error {
|
||||||
|
matrix, err := editorRotation(degrees)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.orientSelection(page, objects, annotations, matrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlipSelection 绕正文与注解的共同可见范围中心镜像,保留绘制层级
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, axis 为horizontal或vertical
|
||||||
|
// 返回: error 标识、度量或编辑错误
|
||||||
|
func (e *Editor) FlipSelection(page int, objects, annotations []string, axis string) error {
|
||||||
|
matrix, err := editorFlip(axis)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.orientSelection(page, objects, annotations, matrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizeSelection 按共同可见范围调整正文与注解的位置及尺寸,不重排文字或重采样图片
|
||||||
|
// 正文独立宽高遵循ResizeObjects规则,一次撤销恢复全部
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, box 页面毫米目标范围
|
||||||
|
// 返回: error 标识、度量或编辑错误
|
||||||
|
func (e *Editor) ResizeSelection(page int, objects, annotations []string, box Box) error {
|
||||||
|
if _, err := creationBox(editorBoxString(box)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bounds, err := e.SelectionBounds(page, objects, annotations)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sx, sy := box.W/bounds.W, box.H/bounds.H
|
||||||
|
if math.Abs(sx-sy) > 1e-9*math.Max(sx, sy) {
|
||||||
|
items, _, err := e.selectedObjects(page, objects)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, object := range items {
|
||||||
|
if !e.objectStretchable(object, make(map[string]bool)) {
|
||||||
|
return fmt.Errorf("nonuniform resizing requires images or image-only groups")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sy = sx
|
||||||
|
}
|
||||||
|
return e.TransformSelection(page, objects, annotations, Matrix{a: sx, d: sy, e: box.X - bounds.X*sx, f: box.Y - bounds.Y*sy})
|
||||||
|
}
|
||||||
|
|
||||||
// AlignSelection 将正文与注解统一对齐,单选对齐页面,多选对齐选区,一次撤销恢复全部
|
// AlignSelection 将正文与注解统一对齐,单选对齐页面,多选对齐选区,一次撤销恢复全部
|
||||||
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, alignment 为left、center、right、top、middle或bottom
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, alignment 为left、center、right、top、middle或bottom
|
||||||
// 返回: error 标识、度量或编辑错误
|
// 返回: error 标识、度量或编辑错误
|
||||||
@@ -79,6 +162,104 @@ func (e *Editor) DistributeSelection(page int, objects, annotations []string, ax
|
|||||||
return e.transformSelection(page, objects, annotations, matrices)
|
return e.transformSelection(page, objects, annotations, matrices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteSelection 原子删除正文与注解,一次撤销恢复全部
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识
|
||||||
|
// 返回: error 标识或编辑错误
|
||||||
|
func (e *Editor) DeleteSelection(page int, objects, annotations []string) error {
|
||||||
|
return e.Transaction(func(edit *Editor) error {
|
||||||
|
if err := edit.DeleteObjects(page, objects); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return edit.DeleteAnnotations(page, annotations)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StyleSelection 原子设置正文与注解内部图元的样式,保留未指定属性
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, style 样式增量
|
||||||
|
// 返回: error 标识、样式或编辑错误
|
||||||
|
func (e *Editor) StyleSelection(page int, objects, annotations []string, style ObjectStyle) error {
|
||||||
|
return e.Transaction(func(edit *Editor) error {
|
||||||
|
if err := edit.StyleObjects(page, objects, style); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return edit.editSelectionAnnotations(page, annotations, func(path ObjectPath, indexes []int) error {
|
||||||
|
return edit.StyleCompositeObjects(page, path, indexes, style)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EraseSelection 按页面矩形原子擦除正文与注解内部内容,不删除其他选区成员
|
||||||
|
// 保留原始资源,不用于敏感信息脱敏
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, box 页面毫米擦除范围
|
||||||
|
// 返回: error 标识、范围或编辑错误
|
||||||
|
func (e *Editor) EraseSelection(page int, objects, annotations []string, box Box) error {
|
||||||
|
return e.Transaction(func(edit *Editor) error {
|
||||||
|
if err := edit.EraseObjects(page, objects, box); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return edit.editSelectionAnnotations(page, annotations, func(path ObjectPath, indexes []int) error {
|
||||||
|
return edit.EraseCompositeObjects(page, path, indexes, box)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EraseSelectionPath 按页面多边形原子擦除正文与注解内部内容,不改变未擦除内容
|
||||||
|
// 保留原始资源,不用于敏感信息脱敏
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, points 页面毫米多边形
|
||||||
|
// 返回: error 标识、多边形或编辑错误
|
||||||
|
func (e *Editor) EraseSelectionPath(page int, objects, annotations []string, points []Point) error {
|
||||||
|
return e.Transaction(func(edit *Editor) error {
|
||||||
|
if err := edit.EraseObjectsPath(page, objects, points); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return edit.editSelectionAnnotations(page, annotations, func(path ObjectPath, indexes []int) error {
|
||||||
|
return edit.EraseCompositeObjectsPath(page, path, indexes, points)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// orientSelection 将直角旋转或镜像定位到混合选区中心,恒等变换仅校验标识
|
||||||
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识, matrix 方向变换
|
||||||
|
// 返回: error 标识、度量或编辑错误
|
||||||
|
func (e *Editor) orientSelection(page int, objects, annotations []string, matrix Matrix) error {
|
||||||
|
if matrix != IdentityMatrix {
|
||||||
|
bounds, err := e.SelectionBounds(page, objects, annotations)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x, y := bounds.X+bounds.W/2, bounds.Y+bounds.H/2
|
||||||
|
matrix = TranslationMatrix(x, y).Multiply(matrix).Multiply(TranslationMatrix(-x, -y))
|
||||||
|
}
|
||||||
|
return e.TransformSelection(page, objects, annotations, matrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// editSelectionAnnotations 对注解直接成员统一操作,重复或不存在的标识返回错误
|
||||||
|
// 入参: page 页面索引, ids 注解标识, apply 成员操作
|
||||||
|
// 返回: error 标识、度量或编辑错误
|
||||||
|
func (e *Editor) editSelectionAnnotations(page int, ids []string, apply func(ObjectPath, []int) error) error {
|
||||||
|
seen := make(map[string]bool, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
if seen[id] {
|
||||||
|
return fmt.Errorf("duplicate annotation ID %q", id)
|
||||||
|
}
|
||||||
|
seen[id] = true
|
||||||
|
path := ObjectPath{Annotation: id}
|
||||||
|
reader, _, _, members, err := e.compositeScope(page, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reader.Close()
|
||||||
|
indexes := make([]int, len(members))
|
||||||
|
for i := range indexes {
|
||||||
|
indexes[i] = i
|
||||||
|
}
|
||||||
|
if err := apply(path, indexes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// selectionGeometry 一次读取页面与资源,按正文、注解标识顺序度量并保留绘制位置
|
// selectionGeometry 一次读取页面与资源,按正文、注解标识顺序度量并保留绘制位置
|
||||||
// 入参: page 页面索引, objects 正文标识, annotations 注解标识
|
// 入参: page 页面索引, objects 正文标识, annotations 注解标识
|
||||||
// 返回: []Box 成员范围, []editorObjectPosition 绘制位置, Box 页面范围, error 标识或度量错误
|
// 返回: []Box 成员范围, []editorObjectPosition 绘制位置, Box 页面范围, error 标识或度量错误
|
||||||
|
|||||||
+35
-14
@@ -51,12 +51,11 @@ func (e *Editor) RotateObject(page int, id string, degrees int) error {
|
|||||||
// 入参: page 页面索引, ids 对象标识, degrees 顺时针角度,仅支持90度的整数倍
|
// 入参: page 页面索引, ids 对象标识, degrees 顺时针角度,仅支持90度的整数倍
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (e *Editor) RotateObjects(page int, ids []string, degrees int) error {
|
func (e *Editor) RotateObjects(page int, ids []string, degrees int) error {
|
||||||
if degrees%90 != 0 {
|
matrix, err := editorRotation(degrees)
|
||||||
return fmt.Errorf("rotation must be a multiple of 90 degrees")
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
turn := (degrees%360 + 360) % 360 / 90
|
return e.orientObjects(page, ids, matrix)
|
||||||
m := []Matrix{IdentityMatrix, {b: 1, c: -1}, {a: -1, d: -1}, {b: -1, c: 1}}[turn]
|
|
||||||
return e.orientObjects(page, ids, m)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlipObject 绕对象可见范围中心镜像
|
// FlipObject 绕对象可见范围中心镜像
|
||||||
@@ -71,16 +70,11 @@ func (e *Editor) FlipObject(page int, id, axis string) error {
|
|||||||
// 入参: page 页面索引, ids 对象标识, axis 为horizontal或vertical
|
// 入参: page 页面索引, ids 对象标识, axis 为horizontal或vertical
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (e *Editor) FlipObjects(page int, ids []string, axis string) error {
|
func (e *Editor) FlipObjects(page int, ids []string, axis string) error {
|
||||||
m := IdentityMatrix
|
matrix, err := editorFlip(axis)
|
||||||
switch axis {
|
if err != nil {
|
||||||
case "horizontal":
|
return err
|
||||||
m.a = -1
|
|
||||||
case "vertical":
|
|
||||||
m.d = -1
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid flip axis %q", axis)
|
|
||||||
}
|
}
|
||||||
return e.orientObjects(page, ids, m)
|
return e.orientObjects(page, ids, matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResizeObjects 按选区可见范围计算目标矩形的变换,图片及纯图片资源支持独立宽高,描边遵循OFD变换规则
|
// ResizeObjects 按选区可见范围计算目标矩形的变换,图片及纯图片资源支持独立宽高,描边遵循OFD变换规则
|
||||||
@@ -232,6 +226,33 @@ func (e *Editor) transformObject(object GraphicObject, dx, dy, scale float64) (G
|
|||||||
return transformEditorObject(object, dx, dy, scale)
|
return transformEditorObject(object, dx, dy, scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// editorRotation 返回无浮点误差的直角旋转矩阵
|
||||||
|
// 入参: degrees 顺时针角度,仅支持90度的整数倍
|
||||||
|
// 返回: Matrix 旋转, error 角度错误
|
||||||
|
func editorRotation(degrees int) (Matrix, error) {
|
||||||
|
if degrees%90 != 0 {
|
||||||
|
return Matrix{}, fmt.Errorf("rotation must be a multiple of 90 degrees")
|
||||||
|
}
|
||||||
|
turn := (degrees%360 + 360) % 360 / 90
|
||||||
|
return [...]Matrix{IdentityMatrix, {b: 1, c: -1}, {a: -1, d: -1}, {b: -1, c: 1}}[turn], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// editorFlip 返回指定方向的镜像矩阵
|
||||||
|
// 入参: axis 为horizontal或vertical
|
||||||
|
// 返回: Matrix 镜像, error 方向错误
|
||||||
|
func editorFlip(axis string) (Matrix, error) {
|
||||||
|
matrix := IdentityMatrix
|
||||||
|
switch axis {
|
||||||
|
case "horizontal":
|
||||||
|
matrix.a = -1
|
||||||
|
case "vertical":
|
||||||
|
matrix.d = -1
|
||||||
|
default:
|
||||||
|
return Matrix{}, fmt.Errorf("invalid flip axis %q", axis)
|
||||||
|
}
|
||||||
|
return matrix, nil
|
||||||
|
}
|
||||||
|
|
||||||
// orientObjects 更新Boundary与CTM,不重排文字或重采样图片
|
// orientObjects 更新Boundary与CTM,不重排文字或重采样图片
|
||||||
// 入参: page 页面索引, ids 对象标识, matrix 直角旋转或镜像矩阵
|
// 入参: page 页面索引, ids 对象标识, matrix 直角旋转或镜像矩阵
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
|
|||||||
Reference in new issue
Block a user