From f312350f73fbd5e6488bab6172a3bdef78097fd2 Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Wed, 16 Sep 2026 13:58:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B5=8C=E5=A5=97=E9=A1=B5=E5=9D=97=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E7=BC=96=E8=BE=91=EF=BC=8C=E4=BF=9D=E6=8C=81=E5=8E=9F?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo_edit.js | 1 + internal/webui/wasm.go | 3 --- ofdgo_editor_open.go | 30 +++++++---------------- ofdgo_editor_package.go | 2 +- ofdgo_editor_style.go | 2 +- ofdgo_editor_xml.go | 50 ++++++++++++++++++++++++++++++-------- 6 files changed, 52 insertions(+), 36 deletions(-) diff --git a/assets/webui/ofdgo_edit.js b/assets/webui/ofdgo_edit.js index 6931e05..c276cfe 100644 --- a/assets/webui/ofdgo_edit.js +++ b/assets/webui/ofdgo_edit.js @@ -132,6 +132,7 @@ export function objectEditReason(item) { else if (reason.includes("color") || reason.includes("RGB")) message = "颜色样式暂不支持"; else if (reason.includes("draw parameter")) message = "绘制参数暂不支持"; else if (reason.includes("layer")) message = "图层特性暂不支持"; + else if (reason.includes("container")) message = "容器特性暂不支持"; else if (reason.includes("invalid")) message = "对象数据异常"; return `${object.capabilities.transform ? "部分操作受限" : "暂不可编辑"}:${message}`; }); diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go index 5e52277..73d2ba6 100644 --- a/internal/webui/wasm.go +++ b/internal/webui/wasm.go @@ -651,9 +651,6 @@ func editorObjects(index int, text *ofdgo.PageText) ([]any, error) { if err != nil { return nil, err } - if capability.Reason == "nested objects are read-only" { - continue - } var box ofdgo.Box switch object.Type { case "TextObject": diff --git a/ofdgo_editor_open.go b/ofdgo_editor_open.go index 3ea9c1b..b706054 100644 --- a/ofdgo_editor_open.go +++ b/ofdgo_editor_open.go @@ -47,7 +47,7 @@ type editorObjectOrigin struct { reason string } -// editorSourcePage 保存页面原文、解析快照及可定位的直接对象。 +// editorSourcePage 保存页面原文、解析快照及图层和页块内的对象位置。 type editorSourcePage struct { ref Page data []byte @@ -281,14 +281,8 @@ func (e *Editor) loadSourcePage(index int) error { if layer.name.Local != "Layer" { continue } - for _, node := range layer.children { - id := node.attr("ID") - if id != "" { - if nodes[id] != nil { - return fmt.Errorf("duplicate object ID %q", id) - } - nodes[id] = node - } + if err := editorXMLObjects(layer, nodes); err != nil { + return err } } } @@ -334,13 +328,6 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er source := e.source.pages[e.pages[page].ID] node := source.nodes[id] 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 } if !editorXMLSupported(node) { @@ -349,12 +336,13 @@ func (e *Editor) ObjectCapabilities(page int, id string) (ObjectCapabilities, er if origin := e.objectOrigin(id); origin.reason != "" { return ObjectCapabilities{Reason: origin.reason}, nil } - for _, attr := range node.parent.attrs { - if attr.Name.Space == "xmlns" || attr.Name.Space == "" && attr.Name.Local == "xmlns" { - continue + for parent := node.parent; parent.name.Local != "Content"; parent = parent.parent { + allowed := "ID" + if parent.name.Local == "Layer" { + allowed += " Type DrawParam" } - if attr.Name.Space != "" || attr.Name.Local != "ID" && attr.Name.Local != "Type" && attr.Name.Local != "DrawParam" { - return ObjectCapabilities{Reason: "layer uses unsupported editing features"}, nil + if !editorXMLAttributes(parent, allowed) { + return ObjectCapabilities{Reason: "object container uses unsupported editing features"}, nil } } if err := validateEditorGeometry(object); err != nil { diff --git a/ofdgo_editor_package.go b/ofdgo_editor_package.go index c71b0af..19b5a22 100644 --- a/ofdgo_editor_package.go +++ b/ofdgo_editor_package.go @@ -171,7 +171,7 @@ func (e *Editor) sourceParts() (map[string][]byte, error) { return parts, nil } -// sourcePageXML 仅改写已修改的直接对象、页面尺寸和新增图层。 +// sourcePageXML 仅改写已修改的对象、页面尺寸和新增图层,保留原页块层级。 // 入参: index 页面索引, source 原页面 // 返回: []byte 页面XML, error 错误信息 func (e *Editor) sourcePageXML(index int, source *editorSourcePage) ([]byte, error) { diff --git a/ofdgo_editor_style.go b/ofdgo_editor_style.go index 771e390..e1fdc12 100644 --- a/ofdgo_editor_style.go +++ b/ofdgo_editor_style.go @@ -35,7 +35,7 @@ func (e *Editor) editorDrawParam(id string, visited map[string]bool) (*DrawParam return mergeDrawParam(*base, dp), nil } -// resolveEditorStyle 为直接对象生成独立的有效样式,不改写共享资源或原XML。 +// resolveEditorStyle 为对象生成独立的有效样式,不改写共享资源或原XML。 // 入参: object 原对象, layer 图层绘制参数标识 // 返回: GraphicObject 编辑快照, error 错误信息 func (e *Editor) resolveEditorStyle(object GraphicObject, layer string) (GraphicObject, error) { diff --git a/ofdgo_editor_xml.go b/ofdgo_editor_xml.go index 8fa0245..8f74438 100644 --- a/ofdgo_editor_xml.go +++ b/ofdgo_editor_xml.go @@ -187,13 +187,48 @@ func editorXMLSetText(data []byte, node *editorXML, values [][2]string) []byte { 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 判断原文是否仅包含创建器能够保留的对象字段。 // 入参: node 对象或子节点 // 返回: 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 switch node.name.Local { case "TextObject": @@ -219,13 +254,8 @@ func editorXMLSupported(node *editorXML) bool { default: 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 - } + if !editorXMLAttributes(node, allowed) { + return false } for _, child := range node.children { if !slices.Contains(strings.Fields(children), child.name.Local) || !editorXMLSupported(child) {