From 057bcba868d378c4d3069579f795fe0db7f7f7ed Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Wed, 28 Oct 2026 13:05:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E7=BC=96=E8=BE=91=E8=83=BD=E5=8A=9B):=20?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=96=87=E5=AD=97=E7=BC=96=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=85=83=E6=95=B0=E6=8D=AE=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo_edit.js | 4 ++-- ofdgo_editor.go | 5 +---- ofdgo_editor_composite_text.go | 7 +++++-- ofdgo_editor_font_binding.go | 2 +- ofdgo_editor_font_scan.go | 2 +- ofdgo_editor_reference.go | 2 +- ofdgo_editor_text.go | 34 +++++++++++++++++----------------- ofdgo_editor_xml.go | 18 +++++++++--------- ofdgo_resource.go | 2 +- ofdgo_text.go | 8 ++++---- ofdgo_text_layout.go | 8 ++++++-- ofdgo_writer.go | 6 +++--- 12 files changed, 51 insertions(+), 47 deletions(-) diff --git a/assets/webui/ofdgo_edit.js b/assets/webui/ofdgo_edit.js index ce54119..650ed2a 100644 --- a/assets/webui/ofdgo_edit.js +++ b/assets/webui/ofdgo_edit.js @@ -86,7 +86,7 @@ function distributeParagraph(input, paragraph) { document.body.append(probe); try { const lines = [], range = document.createRange(), width = copy.clientWidth; - const spacing = parseFloat(getComputedStyle(probe).letterSpacing) || 0; + const spacing = parseFloat(getComputedStyle(copy).letterSpacing) || 0; const indent = parseFloat(getComputedStyle(copy).textIndent) || 0; for (const span of copy.children) { const node = span.firstChild; @@ -375,7 +375,7 @@ export class RichTextSession { const first = this.text.slice(0, start).split("\n").length - 1; const paragraphs = text.split("\n").map((_, i) => { const layout = { ...this.model.layout, ...this.model.paragraphs?.[first + i] }; - return Object.fromEntries(["align", "lineHeight", "letterSpacing", "leftIndent", "rightIndent", "firstLineIndent"].map(key => [key, key === "align" ? layout[key] || "left" : layout[key] || 0])); + return Object.fromEntries(["align", "lineHeight", "letterSpacing", "leftIndent", "rightIndent", "firstLineIndent"].map(key => [key, key === "align" ? layout[key] || this.model.layout.align || "left" : layout[key] || 0])); }); const clipboard = { token: crypto.randomUUID(), runs, text, paragraphs, scope: this.editor.options.textClipboardScope?.(), fonts, faces: new Map([...this.faces].filter(([key]) => keys.has(key))), choices: new Map([...this.choices].filter(([key]) => keys.has(key))) }; diff --git a/ofdgo_editor.go b/ofdgo_editor.go index 40aa772..2f0978d 100644 --- a/ofdgo_editor.go +++ b/ofdgo_editor.go @@ -800,10 +800,7 @@ func (e *Editor) LayoutText(obj *TextObject, value string, options TextLayout) e if err != nil { return err } - if err := LayoutText(obj, value, options, metrics); err != nil { - return err - } - return nil + return LayoutText(obj, value, options, metrics) } // newRenderer 使用编辑器字体来源和完整后端配置创建资源度量器 diff --git a/ofdgo_editor_composite_text.go b/ofdgo_editor_composite_text.go index ef207c6..442e741 100644 --- a/ofdgo_editor_composite_text.go +++ b/ofdgo_editor_composite_text.go @@ -38,7 +38,7 @@ func (e *Editor) StyleCompositeText(page int, path ObjectPath, indexes []int, st } // LayoutCompositeText 显式重排内部普通文字,保留原始基线、变换、裁剪和绘制属性 -// 段落选项仅保留在编辑会话中,撤销重做随对象恢复,保存为标准TextCode定位 +// 段落选项随标准TextCode定位保存,撤销重做及重新打开时恢复 // 入参: page 页面索引, path 父路径, index 成员序号, value 原文, options 段落选项 // 返回: error 错误信息 func (e *Editor) LayoutCompositeText(page int, path ObjectPath, index int, value string, options TextLayout) error { @@ -148,7 +148,10 @@ func (e *Editor) updateCompositeText(node *editorCompositeNode, member Composite } object.TextObject.Size, object.TextObject.TextCode = text.Size, text.TextCode object.TextObject.CGTransform = text.CGTransform - state.layout = &textLayout{value: content, options: layout} + if frame != nil { + text.layout.autoWidth = false + } + state.layout = text.layout } if style.Font != "" && style.Font != object.TextObject.Font { if err := e.prepareText(&text); err != nil { diff --git a/ofdgo_editor_font_binding.go b/ofdgo_editor_font_binding.go index 0e7934c..1eee676 100644 --- a/ofdgo_editor_font_binding.go +++ b/ofdgo_editor_font_binding.go @@ -968,7 +968,7 @@ func pruneUnreferencedFiles(ctx context.Context, reader *Reader, candidates ...s return fmt.Errorf("unknown file dependencies in %s", name) } for _, attr := range node.Attr { - if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace && !textLayoutAttribute(node.Name, attr) { + if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace && !textMetadataAttribute(node.Name, attr) { uncertain = true return fmt.Errorf("unknown file dependencies in %s", name) } diff --git a/ofdgo_editor_font_scan.go b/ofdgo_editor_font_scan.go index cefc05e..8fa6899 100644 --- a/ofdgo_editor_font_scan.go +++ b/ofdgo_editor_font_scan.go @@ -143,7 +143,7 @@ func scanEditorFontFilesMode(ctx context.Context, reader *Reader, index int, ins return nil } for _, attr := range node.attrs { - if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace && !textLayoutAttribute(node.name, attr) { + if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace && !textMetadataAttribute(node.name, attr) { scan.unsafe = true } } diff --git a/ofdgo_editor_reference.go b/ofdgo_editor_reference.go index 2b63822..06cfd03 100644 --- a/ofdgo_editor_reference.go +++ b/ofdgo_editor_reference.go @@ -207,7 +207,7 @@ func (s *editorReferenceScan) accept(token xml.Token) bool { } } for _, attr := range token.Attr { - if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" || attr.Name.Local == "xmlns:ofd" || attr.Name.Space == XMLNamespace || textLayoutAttribute(token.Name, attr) { + if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" || attr.Name.Local == "xmlns:ofd" || attr.Name.Space == XMLNamespace || textMetadataAttribute(token.Name, attr) { continue } custom := len(s.stack) == 0 && token.Name.Local == "CustomTags" || len(s.stack) > 0 && s.stack[0] == "CustomTags" diff --git a/ofdgo_editor_text.go b/ofdgo_editor_text.go index 0ea373f..8b693b4 100644 --- a/ofdgo_editor_text.go +++ b/ofdgo_editor_text.go @@ -73,27 +73,27 @@ type textLayout struct { autoWidth bool } -// textLayoutNamespace 标识普通文字排版元数据,不包含资源引用 -const textLayoutNamespace = "https://ofdgo.aite.me/text-layout" +// textNamespace 标识普通文字编辑语义的命名空间,版本仅随不兼容的语义变更升级 +const textNamespace = "https://ofdgo.aite.me/ns/text/1" -// textLayoutAttribute 判断无资源引用的文字排版扩展属性 +// textMetadataAttribute 判断无资源引用的普通文字编辑元数据属性 // 入参: node 所属节点, attr 属性 -// 返回: bool 是否为本库排版属性 -func textLayoutAttribute(node xml.Name, attr xml.Attr) bool { - return classifyOFDNamespace(node.Space) != ofdXMLUnknown && (node.Local == "TextObject" || node.Local == "Text") && attr.Name.Space == textLayoutNamespace && attr.Name.Local == "Layout" +// 返回: bool 是否为本库普通文字元数据 +func textMetadataAttribute(node xml.Name, attr xml.Attr) bool { + return classifyOFDNamespace(node.Space) != ofdXMLUnknown && (node.Local == "TextObject" || node.Local == "Text") && attr.Name.Space == textNamespace && attr.Name.Local == "Text" } -// textLayoutMetadata 保存原文、选项与标准定位摘要 -type textLayoutMetadata struct { - Text string - Options TextLayout - AutoWidth bool - Digest string +// textMetadata 保存原文、选项与标准定位摘要 +type textMetadata struct { + Text string `json:"text"` + Layout TextLayout `json:"layout"` + AutoWidth bool `json:"autoWidth"` + Digest string `json:"digest"` } -// layoutDigest 计算与字体编号无关的文字定位摘要 +// textDigest 计算与字体编号无关的文字定位摘要 // 返回: string 摘要 -func (obj TextObject) layoutDigest() string { +func (obj TextObject) textDigest() string { codes := append([]TextCode(nil), obj.TextCode...) for i := range codes { codes[i].Value = string(textCodeRunes(codes[i].Value)) @@ -102,13 +102,13 @@ func (obj TextObject) layoutDigest() string { return fmt.Sprintf("%x", sha256.Sum256(data)) } -// layoutMetadata 编码普通文字的排版语义,与标准定位一起保存 +// textMetadata 编码普通文字的排版语义,与标准定位一起保存 // 返回: string 元数据, error 编码错误 -func (obj TextObject) layoutMetadata() (string, error) { +func (obj TextObject) textMetadata() (string, error) { if obj.layout == nil { return "", nil } - data, err := json.Marshal(textLayoutMetadata{Text: obj.layout.value, Options: obj.layout.options, AutoWidth: obj.layout.autoWidth, Digest: obj.layoutDigest()}) + data, err := json.Marshal(textMetadata{Text: obj.layout.value, Layout: obj.layout.options, AutoWidth: obj.layout.autoWidth, Digest: obj.textDigest()}) return string(data), err } diff --git a/ofdgo_editor_xml.go b/ofdgo_editor_xml.go index aea181f..71c3d48 100644 --- a/ofdgo_editor_xml.go +++ b/ofdgo_editor_xml.go @@ -397,7 +397,7 @@ func editorXMLAttributes(node *editorXML, allowed string) bool { if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" && attr.Name.Space == "" { continue } - if textLayoutAttribute(node.name, attr) { + if textMetadataAttribute(node.name, attr) { continue } if attr.Name.Space != "" || !slices.Contains(strings.Fields(allowed), attr.Name.Local) { @@ -572,15 +572,15 @@ func editorXMLMergeNamespace(data []byte, node *editorXML, oldXML, newXML []byte return nil, err } changes := make(map[string]string) - layoutValue := func(n *editorXML) string { + textValue := func(n *editorXML) string { for _, attr := range n.attrs { - if attr.Name.Space == textLayoutNamespace && attr.Name.Local == "Layout" { + if attr.Name.Space == textNamespace && attr.Name.Local == "Text" { return attr.Value } } return "" } - layoutChanged := layoutValue(oldNode) != layoutValue(newNode) + textChanged := textValue(oldNode) != textValue(newNode) if legacyClip { changes["TransFlag"] = "" } @@ -590,7 +590,7 @@ func editorXMLMergeNamespace(data []byte, node *editorXML, oldXML, newXML []byte } } contentChanged := !bytes.Equal(oldXML[oldNode.open:oldNode.close], newXML[newNode.open:newNode.close]) - if len(changes) == 0 && !layoutChanged && !contentChanged && namespace == node.name.Space { + if len(changes) == 0 && !textChanged && !contentChanged && namespace == node.name.Space { return bytes.Clone(data[node.start:node.end]), nil } d := xml.NewDecoder(bytes.NewReader(data[node.start:node.open])) @@ -599,12 +599,12 @@ func editorXMLMergeNamespace(data []byte, node *editorXML, oldXML, newXML []byte return nil, err } start := token.(xml.StartElement) - if layoutChanged { - value, found := layoutValue(newNode), false + if textChanged { + value, found := textValue(newNode), false attrs := start.Attr[:0] for i, attr := range start.Attr { resolved := node.attrs[i] - if resolved.Name.Space == textLayoutNamespace && resolved.Name.Local == "Layout" { + if resolved.Name.Space == textNamespace && resolved.Name.Local == "Text" { found = true if value != "" { attr.Value = value @@ -617,7 +617,7 @@ func editorXMLMergeNamespace(data []byte, node *editorXML, oldXML, newXML []byte start.Attr = attrs if !found && value != "" { prefix := editorXMLNamespacePrefix(node, "ofdgo") - start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "xmlns", Local: prefix}, Value: textLayoutNamespace}, xml.Attr{Name: xml.Name{Space: prefix, Local: "Layout"}, Value: value}) + start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Space: "xmlns", Local: prefix}, Value: textNamespace}, xml.Attr{Name: xml.Name{Space: prefix, Local: "Text"}, Value: value}) } } if legacyClip || namespace != node.name.Space { diff --git a/ofdgo_resource.go b/ofdgo_resource.go index bd6be8b..65ef22c 100644 --- a/ofdgo_resource.go +++ b/ofdgo_resource.go @@ -403,7 +403,7 @@ func scanResourcePage(ctx context.Context, file *editorFontXML, pages map[string return false, false, nil, nil } for _, attr := range token.Attr { - if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace && !textLayoutAttribute(token.Name, attr) { + if attr.Name.Space != "" && attr.Name.Space != "xmlns" && attr.Name.Space != XMLNamespace && !textMetadataAttribute(token.Name, attr) { return false, false, nil, nil } } diff --git a/ofdgo_text.go b/ofdgo_text.go index 8cfda02..569f182 100644 --- a/ofdgo_text.go +++ b/ofdgo_text.go @@ -432,10 +432,10 @@ func (obj *TextObject) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement } *obj = TextObject(value) for _, attr := range start.Attr { - if attr.Name.Space == textLayoutNamespace && attr.Name.Local == "Layout" { - var meta textLayoutMetadata - if json.Unmarshal([]byte(attr.Value), &meta) == nil && validateRichLayout(meta.Options) == nil && structureXMLText(meta.Text) && meta.Digest == obj.layoutDigest() { - obj.layout = &textLayout{value: meta.Text, options: meta.Options, autoWidth: meta.AutoWidth} + if attr.Name.Space == textNamespace && attr.Name.Local == "Text" { + var meta textMetadata + if json.Unmarshal([]byte(attr.Value), &meta) == nil && validateRichLayout(meta.Layout) == nil && structureXMLText(meta.Text) && meta.Digest == obj.textDigest() { + obj.layout = &textLayout{value: meta.Text, options: meta.Layout, autoWidth: meta.AutoWidth} } } } diff --git a/ofdgo_text_layout.go b/ofdgo_text_layout.go index e59d0fd..ea6a45c 100644 --- a/ofdgo_text_layout.go +++ b/ofdgo_text_layout.go @@ -33,7 +33,7 @@ type configuredTextShaper struct { } // LayoutText 按自有字体度量重排横向文字,保留绘制属性,可显式启用字体塑形 -// 不修改文档,通过AddObject或UpdateObject提交;选项仅供当前编辑过程使用,保存为标准文字定位 +// 不修改文档,通过AddObject或UpdateObject提交;选项随标准文字定位保存,重新打开时恢复 // 入参: obj 文字对象, value 原文, options 段落排版选项, metrics 字体度量 // 返回: error 错误信息 func LayoutText(obj *TextObject, value string, options TextLayout, metrics FontMetrics) error { @@ -143,7 +143,11 @@ func LayoutText(obj *TextObject, value string, options TextLayout, metrics FontM } obj.TextCode = codes obj.CGTransform = nil - obj.layout = &textLayout{value: value, options: options, autoWidth: options == (TextLayout{})} + autoWidth := options == (TextLayout{}) + if obj.layout != nil && obj.layout.options == options { + autoWidth = obj.layout.autoWidth + } + obj.layout = &textLayout{value: value, options: options, autoWidth: autoWidth} return nil } diff --git a/ofdgo_writer.go b/ofdgo_writer.go index 7b84469..544dd23 100644 --- a/ofdgo_writer.go +++ b/ofdgo_writer.go @@ -1242,14 +1242,14 @@ func (x *ofdXML) object(object GraphicObject, root bool) { switch object.Type { case "TextObject", "Text": obj := object.TextObject - metadata, err := obj.layoutMetadata() + metadata, err := obj.textMetadata() if err != nil { x.err = err return } if metadata != "" { - attrs.add("xmlns:ofdgo", textLayoutNamespace) - attrs.add("ofdgo:Layout", metadata) + attrs.add("xmlns:ofdgo", textNamespace) + attrs.add("ofdgo:Text", metadata) } attrs.add("ID", obj.ID) attrs.add("DrawParam", obj.DrawParam)