feat(编辑能力): 支持嵌套页块对象编辑,保持原文档结构
build.yaml / build (push) Successful in 37s

This commit is contained in:
xiaoqidun committed 2026-09-16 13:58:09 +08:00
1 parent 0034e0edbd
commit f312350f73
6 files changed
+52 -36

No files matched your search

+1
View File
@@ -132,6 +132,7 @@ export function objectEditReason(item) {
else if (reason.includes("color") || reason.includes("RGB")) message = "颜色样式暂不支持"; else if (reason.includes("color") || reason.includes("RGB")) message = "颜色样式暂不支持";
else if (reason.includes("draw parameter")) message = "绘制参数暂不支持"; else if (reason.includes("draw parameter")) message = "绘制参数暂不支持";
else if (reason.includes("layer")) message = "图层特性暂不支持"; else if (reason.includes("layer")) message = "图层特性暂不支持";
else if (reason.includes("container")) message = "容器特性暂不支持";
else if (reason.includes("invalid")) message = "对象数据异常"; else if (reason.includes("invalid")) message = "对象数据异常";
return `${object.capabilities.transform ? "部分操作受限" : "暂不可编辑"}:${message}`; return `${object.capabilities.transform ? "部分操作受限" : "暂不可编辑"}:${message}`;
}); });
-3
View File
@@ -651,9 +651,6 @@ func editorObjects(index int, text *ofdgo.PageText) ([]any, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if capability.Reason == "nested objects are read-only" {
continue
}
var box ofdgo.Box var box ofdgo.Box
switch object.Type { switch object.Type {
case "TextObject": case "TextObject":
+9 -21
View File
@@ -47,7 +47,7 @@ type editorObjectOrigin struct {
reason string reason string
} }
// editorSourcePage 保存页面原文、解析快照及可定位的直接对象。 // editorSourcePage 保存页面原文、解析快照及图层和页块内的对象位置。
type editorSourcePage struct { type editorSourcePage struct {
ref Page ref Page
data []byte data []byte
@@ -281,14 +281,8 @@ func (e *Editor) loadSourcePage(index int) error {
if layer.name.Local != "Layer" { if layer.name.Local != "Layer" {
continue continue
} }
for _, node := range layer.children { if err := editorXMLObjects(layer, nodes); err != nil {
id := node.attr("ID") return err
if id != "" {
if nodes[id] != nil {
return fmt.Errorf("duplicate object ID %q", id)
}
nodes[id] = node
}
} }
} }
} }
@@ -334,13 +328,6 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er
source := e.source.pages[e.pages[page].ID] source := e.source.pages[e.pages[page].ID]
node := source.nodes[id] node := source.nodes[id]
if node == nil { if node == nil {
for _, original := range source.original.Content.Layer {
for _, object := range original.Objects {
if editorObjectID(object) == id {
return ObjectCapabilities{Reason: "nested objects are read-only"}, nil
}
}
}
return all, nil return all, nil
} }
if !editorXMLSupported(node) { if !editorXMLSupported(node) {
@@ -349,12 +336,13 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er
if origin := e.objectOrigin(id); origin.reason != "" { if origin := e.objectOrigin(id); origin.reason != "" {
return ObjectCapabilities{Reason: origin.reason}, nil return ObjectCapabilities{Reason: origin.reason}, nil
} }
for _, attr := range node.parent.attrs { for parent := node.parent; parent.name.Local != "Content"; parent = parent.parent {
if attr.Name.Space == "xmlns" || attr.Name.Space == "" && attr.Name.Local == "xmlns" { allowed := "ID"
continue if parent.name.Local == "Layer" {
allowed += " Type DrawParam"
} }
if attr.Name.Space != "" || attr.Name.Local != "ID" && attr.Name.Local != "Type" && attr.Name.Local != "DrawParam" { if !editorXMLAttributes(parent, allowed) {
return ObjectCapabilities{Reason: "layer uses unsupported editing features"}, nil return ObjectCapabilities{Reason: "object container uses unsupported editing features"}, nil
} }
} }
if err := validateEditorGeometry(object); err != nil { if err := validateEditorGeometry(object); err != nil {
+1 -1
View File
@@ -171,7 +171,7 @@ func (e *Editor) sourceParts() (map[string][]byte, error) {
return parts, nil return parts, nil
} }
// sourcePageXML 仅改写已修改的直接对象、页面尺寸和新增图层。 // sourcePageXML 仅改写已修改的对象、页面尺寸和新增图层,保留原页块层级。
// 入参: index 页面索引, source 原页面 // 入参: index 页面索引, source 原页面
// 返回: []byte 页面XML, error 错误信息 // 返回: []byte 页面XML, error 错误信息
func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, error) { func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, error) {
+1 -1
View File
@@ -35,7 +35,7 @@ func (e *Editor) editorDrawParam(id string, visited map[string]bool) (*DrawParam
return mergeDrawParam(*base, dp), nil return mergeDrawParam(*base, dp), nil
} }
// resolveEditorStyle 为直接对象生成独立的有效样式,不改写共享资源或原XML。 // resolveEditorStyle 为对象生成独立的有效样式,不改写共享资源或原XML。
// 入参: object 原对象, layer 图层绘制参数标识 // 入参: object 原对象, layer 图层绘制参数标识
// 返回: GraphicObject 编辑快照, error 错误信息 // 返回: GraphicObject 编辑快照, error 错误信息
func (e *Editor) resolveEditorStyle(object GraphicObject, layer string) (GraphicObject, error) { func (e *Editor) resolveEditorStyle(object GraphicObject, layer string) (GraphicObject, error) {
+40 -10
View File
@@ -187,13 +187,48 @@ func editorXMLSetText(data []byte, node *editorXML, values [][2]string) []byte {
return editorPatchXML(data, patches) return editorPatchXML(data, patches)
} }
// editorXMLObjects 递归登记图层和页块内的对象位置,保留原容器层级。
// 入参: container 图层或页块, nodes 标识与节点映射
// 返回: error 错误信息
func editorXMLObjects(container *editorXML, nodes map[string]*editorXML) error {
for _, node := range container.children {
if id := node.attr("ID"); id != "" {
if nodes[id] != nil {
return fmt.Errorf("duplicate object ID %q", id)
}
nodes[id] = node
}
if node.name.Local == "PageBlock" {
if err := editorXMLObjects(node, nodes); err != nil {
return err
}
}
}
return nil
}
// editorXMLAttributes 判断节点的命名空间与属性是否在编辑器支持范围内。
// 入参: node 节点, allowed 允许的属性名称
// 返回: bool 是否支持
func editorXMLAttributes(node *editorXML, allowed string) bool {
if node.name.Space != "" && node.name.Space != ofdNamespace && node.name.Space != "http://www.ofdspec.org" {
return false
}
for _, attr := range node.attrs {
if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" && attr.Name.Space == "" {
continue
}
if attr.Name.Space != "" || !slices.Contains(strings.Fields(allowed), attr.Name.Local) {
return false
}
}
return true
}
// editorXMLSupported 判断原文是否仅包含创建器能够保留的对象字段。 // editorXMLSupported 判断原文是否仅包含创建器能够保留的对象字段。
// 入参: node 对象或子节点 // 入参: node 对象或子节点
// 返回: bool 是否支持完整编辑 // 返回: bool 是否支持完整编辑
func editorXMLSupported(node *editorXML) bool { func editorXMLSupported(node *editorXML) bool {
if node.name.Space != "" && node.name.Space != ofdNamespace && node.name.Space != "http://www.ofdspec.org" {
return false
}
var allowed, children string var allowed, children string
switch node.name.Local { switch node.name.Local {
case "TextObject": case "TextObject":
@@ -219,13 +254,8 @@ func editorXMLSupported(node *editorXML) bool {
default: default:
return false return false
} }
for _, attr := range node.attrs { if !editorXMLAttributes(node, allowed) {
if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" && attr.Name.Space == "" { return false
continue
}
if attr.Name.Space != "" || !slices.Contains(strings.Fields(allowed), attr.Name.Local) {
return false
}
} }
for _, child := range node.children { for _, child := range node.children {
if !slices.Contains(strings.Fields(children), child.name.Local) || !editorXMLSupported(child) { if !slices.Contains(strings.Fields(children), child.name.Local) || !editorXMLSupported(child) {