mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 14:31:12 +08:00
This commit is contained in:
1 parent
92ddacaf66
commit
e7686b5430
5 files changed
+129
-52
No files matched your search
+20
-14
@@ -307,8 +307,8 @@ export class CanvasEditor {
|
||||
const crop = this.crop?.item.index === index ? this.crop : null;
|
||||
if (crop) this.closeCrop(true);
|
||||
const pending = this.pendingSelection?.index === index;
|
||||
const selected = pending ? this.pendingSelection.ids || page.objects.slice(-1).map(item => item.id)
|
||||
: this.items().filter(item => item.index === index).map(item => item.id);
|
||||
const selected = new Set(pending ? this.pendingSelection.ids || page.objects.slice(-1).map(item => item.id)
|
||||
: this.items().filter(item => item.index === index).map(item => item.id));
|
||||
const selection = [];
|
||||
this.pages.set(surface, { index, width: page.width, height: page.height });
|
||||
const layer = document.createElement("div");
|
||||
@@ -359,24 +359,14 @@ export class CanvasEditor {
|
||||
}
|
||||
this.nodes.set(node, item);
|
||||
node.addEventListener("focus", () => this.select(item));
|
||||
const handles = !canEditObject(object, "transform") ? [] : lineShape(object.shape) ? ["start", "end"]
|
||||
: object.shape || object.oriented || canEditObject(object, "stretch") ? ["nw", "n", "ne", "e", "se", "s", "sw", "w"]
|
||||
: object.type === "TextObject" && canEditObject(object, "reflow") && canEditObject(object, "layoutKnown")
|
||||
? ["nw", "n", "ne", "e", "se", "s", "sw", "w"] : ["nw", "ne", "sw", "se"];
|
||||
for (const corner of handles) {
|
||||
const handle = document.createElement("span");
|
||||
handle.className = `edit-handle edit-${corner}`;
|
||||
handle.dataset.corner = corner;
|
||||
node.append(handle);
|
||||
}
|
||||
this.place(item);
|
||||
layer.append(node);
|
||||
if (selected.includes(object.id)) {
|
||||
if (selected.has(object.id)) {
|
||||
selection.push(item);
|
||||
}
|
||||
}
|
||||
surface.append(layer);
|
||||
if (selected.length) this.setSelection(selection);
|
||||
if (selected.size) this.setSelection(selection);
|
||||
if (pending) this.pendingSelection = null;
|
||||
if (crop && selection.length === 1 && selection[0].id === crop.item.id
|
||||
&& ["x", "y", "width", "height"].every(key => selection[0][key] === crop.item[key])) {
|
||||
@@ -487,6 +477,9 @@ export class CanvasEditor {
|
||||
for (const item of this.items()) {
|
||||
item.node.classList.remove("selected", "multi-selected");
|
||||
item.node.setAttribute("aria-pressed", "false");
|
||||
for (const handle of [...item.node.children]) {
|
||||
if (handle.dataset.corner) handle.remove();
|
||||
}
|
||||
}
|
||||
this.selected = items.length < 2 ? items[0] || null : { ...selectionBounds(items), items,
|
||||
id: items.map(item => item.id), index: items[0].index, page: items[0].page, surface: items[0].surface };
|
||||
@@ -503,6 +496,19 @@ export class CanvasEditor {
|
||||
this.nodes.set(node, this.selected);
|
||||
this.place(this.selected);
|
||||
this.selected.surface.append(node);
|
||||
} else if (items.length === 1) {
|
||||
const item = items[0];
|
||||
const handles = !canEditObject(item, "transform") ? [] : lineShape(item.shape) ? ["start", "end"]
|
||||
: item.shape || item.oriented || canEditObject(item, "stretch") ? ["nw", "n", "ne", "e", "se", "s", "sw", "w"]
|
||||
: item.type === "TextObject" && canEditObject(item, "reflow") && canEditObject(item, "layoutKnown")
|
||||
? ["nw", "n", "ne", "e", "se", "s", "sw", "w"] : ["nw", "ne", "sw", "se"];
|
||||
for (const corner of handles) {
|
||||
const handle = document.createElement("span");
|
||||
handle.className = `edit-handle edit-${corner}`;
|
||||
handle.dataset.corner = corner;
|
||||
item.node.append(handle);
|
||||
}
|
||||
this.place(item);
|
||||
}
|
||||
for (const item of items) {
|
||||
item.node.classList.add("selected");
|
||||
|
||||
@@ -732,6 +732,10 @@ func editorObjects(index int, text *ofdgo.PageText) ([]any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := currentEditor.PageObjectInfo(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
textBoxes := make(map[string]ofdgo.Box)
|
||||
for _, run := range text.Runs {
|
||||
for _, box := range run.Boxes {
|
||||
@@ -770,10 +774,7 @@ func editorObjects(index int, text *ofdgo.PageText) ([]any, error) {
|
||||
default:
|
||||
continue
|
||||
}
|
||||
capability, err := currentEditor.ObjectCapabilities(index, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
capability := info[id].Capabilities
|
||||
var box ofdgo.Box
|
||||
var contours []ofdgo.ObjectContour
|
||||
switch object.Type {
|
||||
@@ -786,10 +787,7 @@ func editorObjects(index int, text *ofdgo.PageText) ([]any, error) {
|
||||
continue
|
||||
}
|
||||
if box.W > 0 && box.H > 0 {
|
||||
position, err := currentEditor.ObjectPosition(index, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
position := info[id].Position
|
||||
item := map[string]any{"id": id, "type": object.Type, "x": box.X, "y": box.Y, "width": box.W, "height": box.H, "order": order, "position": position.Index, "count": position.Count, "container": position.Container,
|
||||
"capabilities": editorCapabilities(capability, object.Type)}
|
||||
editorAppearance(item, object, capability.Paint, editorPathScale(object.PathObject))
|
||||
|
||||
+77
-22
@@ -144,6 +144,53 @@ type ObjectPosition struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// ObjectInfo 对象的编辑能力及直接容器位置
|
||||
type ObjectInfo struct {
|
||||
Capabilities ObjectCapabilities
|
||||
Position ObjectPosition
|
||||
}
|
||||
|
||||
// PageObjectInfo 批量获取页面对象能力与位置,不重复扫描同一容器
|
||||
// 每次按当前页面计算,变更和撤销后无需失效旧缓存
|
||||
// 入参: page 页面索引
|
||||
// 返回: map[string]ObjectInfo 对象标识与信息, error 错误信息
|
||||
func (e *Editor) PageObjectInfo(page int) (map[string]ObjectInfo, error) {
|
||||
content, err := e.page(page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make(map[string]ObjectInfo)
|
||||
orderable := make(map[*editorXML]bool)
|
||||
for i := range content.Content.Layer {
|
||||
layer := &content.Content.Layer[i]
|
||||
nodes := e.objectOrderNodes(page, layer)
|
||||
groups := make(map[*editorXML][]string)
|
||||
for _, object := range layer.Objects {
|
||||
id := editorObjectID(object)
|
||||
parent := e.objectOrderParent(nodes, id)
|
||||
groups[parent] = append(groups[parent], id)
|
||||
if id != "" {
|
||||
result[id] = ObjectInfo{Capabilities: e.objectCapabilities(object, orderable)}
|
||||
}
|
||||
}
|
||||
for parent, ids := range groups {
|
||||
container := layer.ID
|
||||
if parent != nil {
|
||||
container = parent.attr("ID")
|
||||
}
|
||||
for index, id := range ids {
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
info := result[id]
|
||||
info.Position = ObjectPosition{Layer: layer.ID, Container: container, Index: index, Count: len(ids)}
|
||||
result[id] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ObjectPosition 获取对象的直接容器及排序位置
|
||||
// 入参: page 页面索引, id 对象标识
|
||||
// 返回: ObjectPosition 对象位置, error 错误信息
|
||||
@@ -170,36 +217,44 @@ func (e *Editor) ObjectPosition(page int, id string) (ObjectPosition, error) {
|
||||
// 入参: page 页面索引, layer 图层, id 对象标识
|
||||
// 返回: *editorXML 原始容器, []int 对象索引
|
||||
func (e *Editor) objectOrderIndexes(page int, layer *Layer, id string) (*editorXML, []int) {
|
||||
var nodes map[string]*editorXML
|
||||
var parent *editorXML
|
||||
if e.originalPage(page) {
|
||||
source := e.source.pages[e.pages[page].ID]
|
||||
if slices.ContainsFunc(source.original.Content.Layer, func(original Layer) bool { return original.ID == layer.ID }) {
|
||||
nodes = source.nodes
|
||||
}
|
||||
}
|
||||
if nodes != nil {
|
||||
if node := nodes[id]; node != nil {
|
||||
parent = node.parent
|
||||
} else if origin := e.objectOrigin(id); origin != nil {
|
||||
parent = origin.node.parent
|
||||
}
|
||||
}
|
||||
nodes := e.objectOrderNodes(page, layer)
|
||||
parent := e.objectOrderParent(nodes, id)
|
||||
var indexes []int
|
||||
for i, object := range layer.Objects {
|
||||
node := nodes[editorObjectID(object)]
|
||||
if node == nil && nodes != nil {
|
||||
if origin := e.objectOrigin(editorObjectID(object)); origin != nil {
|
||||
node = origin.node
|
||||
}
|
||||
}
|
||||
if node == nil && parent == nil || node != nil && node.parent == parent {
|
||||
if e.objectOrderParent(nodes, editorObjectID(object)) == parent {
|
||||
indexes = append(indexes, i)
|
||||
}
|
||||
}
|
||||
return parent, indexes
|
||||
}
|
||||
|
||||
// objectOrderNodes 获取原图层中的对象节点
|
||||
// 入参: page 页面索引, layer 当前图层
|
||||
// 返回: map[string]*editorXML 原节点映射
|
||||
func (e *Editor) objectOrderNodes(page int, layer *Layer) map[string]*editorXML {
|
||||
if e.originalPage(page) {
|
||||
source := e.source.pages[e.pages[page].ID]
|
||||
if slices.ContainsFunc(source.original.Content.Layer, func(original Layer) bool { return original.ID == layer.ID }) {
|
||||
return source.nodes
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// objectOrderParent 获取对象的原始直接容器,新对象沿用所在图层
|
||||
// 入参: nodes 原节点映射, id 对象标识
|
||||
// 返回: *editorXML 原始容器
|
||||
func (e *Editor) objectOrderParent(nodes map[string]*editorXML, id string) *editorXML {
|
||||
if nodes != nil {
|
||||
if node := nodes[id]; node != nil {
|
||||
return node.parent
|
||||
} else if origin := e.objectOrigin(id); origin != nil {
|
||||
return origin.node.parent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderObjects 调整同一图层或页块内选区的绘制顺序,保留各组选中及未选中对象的相对顺序
|
||||
// 入参: page 页面索引, ids 对象标识, order 为up、down、top或bottom
|
||||
// 返回: error 错误信息
|
||||
|
||||
+19
-8
@@ -367,7 +367,14 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er
|
||||
if err != nil {
|
||||
return ObjectCapabilities{}, err
|
||||
}
|
||||
object := layer.Objects[index]
|
||||
return e.objectCapabilities(layer.Objects[index], make(map[*editorXML]bool)), nil
|
||||
}
|
||||
|
||||
// objectCapabilities 检查对象能力,在同次遍历中复用原容器排序检查
|
||||
// 入参: object 当前对象, orderable 原容器排序能力
|
||||
// 返回: ObjectCapabilities 操作能力
|
||||
func (e *Editor) objectCapabilities(object GraphicObject, orderable map[*editorXML]bool) ObjectCapabilities {
|
||||
id := editorObjectID(object)
|
||||
all := ObjectCapabilities{Update: true, Paint: object.Type == "TextObject" || object.Type == "PathObject", ReplaceFont: object.Type == "TextObject", Reflow: object.Type == "TextObject" && object.TextObject.ReadDirection == 0 && object.TextObject.CharDirection == 0, LayoutKnown: object.Type == "TextObject" && (e.objectOrigin(id) == nil || object.TextObject.layout != nil), Transform: true, Arrange: true, Copy: true, Delete: true, Order: true}
|
||||
all.ResetCrop = object.Type == "ImageObject" && object.state.crop != nil
|
||||
origin := e.objectOrigin(id)
|
||||
@@ -377,22 +384,26 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er
|
||||
all.ReplaceImage = object.Type == "ImageObject"
|
||||
all.CropImage = all.ReplaceImage
|
||||
all.FitImage = all.ReplaceImage && axisAlignedMatrix(NewMatrix(object.ImageObject.CTM))
|
||||
return all, nil
|
||||
return all
|
||||
}
|
||||
node := origin.node
|
||||
if !editorXMLTransformable(node) {
|
||||
return ObjectCapabilities{Reason: "object uses unsupported editing features", ReasonCode: EditUnsupportedObject}, nil
|
||||
return ObjectCapabilities{Reason: "object uses unsupported editing features", ReasonCode: EditUnsupportedObject}
|
||||
}
|
||||
if origin.reason != nil && editReason(origin.reason) == EditUnsupportedStyle {
|
||||
return ObjectCapabilities{Reason: origin.reason.Error(), ReasonCode: EditUnsupportedStyle}, nil
|
||||
return ObjectCapabilities{Reason: origin.reason.Error(), ReasonCode: EditUnsupportedStyle}
|
||||
}
|
||||
if !editorXMLContainersSupported(node) {
|
||||
return ObjectCapabilities{Reason: "object container uses unsupported editing features", ReasonCode: EditUnsupportedContainer}, nil
|
||||
return ObjectCapabilities{Reason: "object container uses unsupported editing features", ReasonCode: EditUnsupportedContainer}
|
||||
}
|
||||
if err := validateEditorGeometry(object); err != nil {
|
||||
return ObjectCapabilities{Reason: err.Error(), ReasonCode: EditInvalidObject}, nil
|
||||
return ObjectCapabilities{Reason: err.Error(), ReasonCode: EditInvalidObject}
|
||||
}
|
||||
var checked bool
|
||||
if all.Order, checked = orderable[node.parent]; !checked {
|
||||
all.Order = editorContainerOrderable(node.parent)
|
||||
orderable[node.parent] = all.Order
|
||||
}
|
||||
all.Order = editorContainerOrderable(node.parent)
|
||||
all.Ungroup = all.Order && e.compositeUngroupable(node) && (object.CompositeGraphicUnit.ResourceID != "" || len(object.CompositeGraphicUnit.Objects) != 0)
|
||||
all.Copy = editorXMLCopyable(node) && (origin.reason == nil || object.Type == "PathObject" && editReason(origin.reason) == EditUnsupportedColor)
|
||||
if origin.reason != nil {
|
||||
@@ -422,7 +433,7 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er
|
||||
}
|
||||
}
|
||||
all.Stretch = all.Transform && e.objectStretchable(object, make(map[string]bool))
|
||||
return all, nil
|
||||
return all
|
||||
}
|
||||
|
||||
// objectOrigin 获取原对象或其副本的保真来源
|
||||
|
||||
@@ -353,6 +353,13 @@ func (p *fontUsagePage) decodeObjects(d *xml.Decoder, start xml.StartElement, de
|
||||
switch node.Name.Local {
|
||||
case "PageBlock":
|
||||
return p.decodeObjects(d, node, defaults)
|
||||
case "TextObject":
|
||||
var obj TextObject
|
||||
if err := d.DecodeElement(&obj, &node); err != nil {
|
||||
return err
|
||||
}
|
||||
p.renderer.countObjectFonts(GraphicObject{Type: "TextObject", TextObject: obj}, p.usage, defaults, nil)
|
||||
return nil
|
||||
case "PathObject":
|
||||
var obj struct {
|
||||
PathObject
|
||||
|
||||
Reference in new issue
Block a user