mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
89a7ef04c7
commit
057bcba868
12 files changed
+51
-47
No files matched your search
@@ -86,7 +86,7 @@ function distributeParagraph(input, paragraph) {
|
|||||||
document.body.append(probe);
|
document.body.append(probe);
|
||||||
try {
|
try {
|
||||||
const lines = [], range = document.createRange(), width = copy.clientWidth;
|
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;
|
const indent = parseFloat(getComputedStyle(copy).textIndent) || 0;
|
||||||
for (const span of copy.children) {
|
for (const span of copy.children) {
|
||||||
const node = span.firstChild;
|
const node = span.firstChild;
|
||||||
@@ -375,7 +375,7 @@ export class RichTextSession {
|
|||||||
const first = this.text.slice(0, start).split("\n").length - 1;
|
const first = this.text.slice(0, start).split("\n").length - 1;
|
||||||
const paragraphs = text.split("\n").map((_, i) => {
|
const paragraphs = text.split("\n").map((_, i) => {
|
||||||
const layout = { ...this.model.layout, ...this.model.paragraphs?.[first + 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?.(),
|
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))) };
|
fonts, faces: new Map([...this.faces].filter(([key]) => keys.has(key))), choices: new Map([...this.choices].filter(([key]) => keys.has(key))) };
|
||||||
|
|||||||
+1
-4
@@ -800,10 +800,7 @@ func (e *Editor) LayoutText(obj *TextObject, value string, options TextLayout) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := LayoutText(obj, value, options, metrics); err != nil {
|
return LayoutText(obj, value, options, metrics)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRenderer 使用编辑器字体来源和完整后端配置创建资源度量器
|
// newRenderer 使用编辑器字体来源和完整后端配置创建资源度量器
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (e *Editor) StyleCompositeText(page int, path ObjectPath, indexes []int, st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LayoutCompositeText 显式重排内部普通文字,保留原始基线、变换、裁剪和绘制属性
|
// LayoutCompositeText 显式重排内部普通文字,保留原始基线、变换、裁剪和绘制属性
|
||||||
// 段落选项仅保留在编辑会话中,撤销重做随对象恢复,保存为标准TextCode定位
|
// 段落选项随标准TextCode定位保存,撤销重做及重新打开时恢复
|
||||||
// 入参: page 页面索引, path 父路径, index 成员序号, value 原文, options 段落选项
|
// 入参: page 页面索引, path 父路径, index 成员序号, value 原文, options 段落选项
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (e *Editor) LayoutCompositeText(page int, path ObjectPath, index int, value string, options TextLayout) 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.Size, object.TextObject.TextCode = text.Size, text.TextCode
|
||||||
object.TextObject.CGTransform = text.CGTransform
|
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 style.Font != "" && style.Font != object.TextObject.Font {
|
||||||
if err := e.prepareText(&text); err != nil {
|
if err := e.prepareText(&text); err != nil {
|
||||||
|
|||||||
@@ -968,7 +968,7 @@ func pruneUnreferencedFiles(ctx context.Context, reader *Reader, candidates ...s
|
|||||||
return fmt.Errorf("unknown file dependencies in %s", name)
|
return fmt.Errorf("unknown file dependencies in %s", name)
|
||||||
}
|
}
|
||||||
for _, attr := range node.Attr {
|
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
|
uncertain = true
|
||||||
return fmt.Errorf("unknown file dependencies in %s", name)
|
return fmt.Errorf("unknown file dependencies in %s", name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func scanEditorFontFilesMode(ctx context.Context, reader *Reader, index int, ins
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, attr := range node.attrs {
|
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
|
scan.unsafe = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ func (s *editorReferenceScan) accept(token xml.Token) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, attr := range token.Attr {
|
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
|
continue
|
||||||
}
|
}
|
||||||
custom := len(s.stack) == 0 && token.Name.Local == "CustomTags" || len(s.stack) > 0 && s.stack[0] == "CustomTags"
|
custom := len(s.stack) == 0 && token.Name.Local == "CustomTags" || len(s.stack) > 0 && s.stack[0] == "CustomTags"
|
||||||
|
|||||||
+17
-17
@@ -73,27 +73,27 @@ type textLayout struct {
|
|||||||
autoWidth bool
|
autoWidth bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// textLayoutNamespace 标识普通文字排版元数据,不包含资源引用
|
// textNamespace 标识普通文字编辑语义的命名空间,版本仅随不兼容的语义变更升级
|
||||||
const textLayoutNamespace = "https://ofdgo.aite.me/text-layout"
|
const textNamespace = "https://ofdgo.aite.me/ns/text/1"
|
||||||
|
|
||||||
// textLayoutAttribute 判断无资源引用的文字排版扩展属性
|
// textMetadataAttribute 判断无资源引用的普通文字编辑元数据属性
|
||||||
// 入参: node 所属节点, attr 属性
|
// 入参: node 所属节点, attr 属性
|
||||||
// 返回: bool 是否为本库排版属性
|
// 返回: bool 是否为本库普通文字元数据
|
||||||
func textLayoutAttribute(node xml.Name, attr xml.Attr) bool {
|
func textMetadataAttribute(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"
|
return classifyOFDNamespace(node.Space) != ofdXMLUnknown && (node.Local == "TextObject" || node.Local == "Text") && attr.Name.Space == textNamespace && attr.Name.Local == "Text"
|
||||||
}
|
}
|
||||||
|
|
||||||
// textLayoutMetadata 保存原文、选项与标准定位摘要
|
// textMetadata 保存原文、选项与标准定位摘要
|
||||||
type textLayoutMetadata struct {
|
type textMetadata struct {
|
||||||
Text string
|
Text string `json:"text"`
|
||||||
Options TextLayout
|
Layout TextLayout `json:"layout"`
|
||||||
AutoWidth bool
|
AutoWidth bool `json:"autoWidth"`
|
||||||
Digest string
|
Digest string `json:"digest"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// layoutDigest 计算与字体编号无关的文字定位摘要
|
// textDigest 计算与字体编号无关的文字定位摘要
|
||||||
// 返回: string 摘要
|
// 返回: string 摘要
|
||||||
func (obj TextObject) layoutDigest() string {
|
func (obj TextObject) textDigest() string {
|
||||||
codes := append([]TextCode(nil), obj.TextCode...)
|
codes := append([]TextCode(nil), obj.TextCode...)
|
||||||
for i := range codes {
|
for i := range codes {
|
||||||
codes[i].Value = string(textCodeRunes(codes[i].Value))
|
codes[i].Value = string(textCodeRunes(codes[i].Value))
|
||||||
@@ -102,13 +102,13 @@ func (obj TextObject) layoutDigest() string {
|
|||||||
return fmt.Sprintf("%x", sha256.Sum256(data))
|
return fmt.Sprintf("%x", sha256.Sum256(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// layoutMetadata 编码普通文字的排版语义,与标准定位一起保存
|
// textMetadata 编码普通文字的排版语义,与标准定位一起保存
|
||||||
// 返回: string 元数据, error 编码错误
|
// 返回: string 元数据, error 编码错误
|
||||||
func (obj TextObject) layoutMetadata() (string, error) {
|
func (obj TextObject) textMetadata() (string, error) {
|
||||||
if obj.layout == nil {
|
if obj.layout == nil {
|
||||||
return "", 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
|
return string(data), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -397,7 +397,7 @@ func editorXMLAttributes(node *editorXML, allowed string) bool {
|
|||||||
if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" && attr.Name.Space == "" {
|
if attr.Name.Space == "xmlns" || attr.Name.Local == "xmlns" && attr.Name.Space == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if textLayoutAttribute(node.name, attr) {
|
if textMetadataAttribute(node.name, attr) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if attr.Name.Space != "" || !slices.Contains(strings.Fields(allowed), attr.Name.Local) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
changes := make(map[string]string)
|
changes := make(map[string]string)
|
||||||
layoutValue := func(n *editorXML) string {
|
textValue := func(n *editorXML) string {
|
||||||
for _, attr := range n.attrs {
|
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 attr.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
layoutChanged := layoutValue(oldNode) != layoutValue(newNode)
|
textChanged := textValue(oldNode) != textValue(newNode)
|
||||||
if legacyClip {
|
if legacyClip {
|
||||||
changes["TransFlag"] = ""
|
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])
|
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
|
return bytes.Clone(data[node.start:node.end]), nil
|
||||||
}
|
}
|
||||||
d := xml.NewDecoder(bytes.NewReader(data[node.start:node.open]))
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
start := token.(xml.StartElement)
|
start := token.(xml.StartElement)
|
||||||
if layoutChanged {
|
if textChanged {
|
||||||
value, found := layoutValue(newNode), false
|
value, found := textValue(newNode), false
|
||||||
attrs := start.Attr[:0]
|
attrs := start.Attr[:0]
|
||||||
for i, attr := range start.Attr {
|
for i, attr := range start.Attr {
|
||||||
resolved := node.attrs[i]
|
resolved := node.attrs[i]
|
||||||
if resolved.Name.Space == textLayoutNamespace && resolved.Name.Local == "Layout" {
|
if resolved.Name.Space == textNamespace && resolved.Name.Local == "Text" {
|
||||||
found = true
|
found = true
|
||||||
if value != "" {
|
if value != "" {
|
||||||
attr.Value = value
|
attr.Value = value
|
||||||
@@ -617,7 +617,7 @@ func editorXMLMergeNamespace(data []byte, node *editorXML, oldXML, newXML []byte
|
|||||||
start.Attr = attrs
|
start.Attr = attrs
|
||||||
if !found && value != "" {
|
if !found && value != "" {
|
||||||
prefix := editorXMLNamespacePrefix(node, "ofdgo")
|
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 {
|
if legacyClip || namespace != node.name.Space {
|
||||||
|
|||||||
+1
-1
@@ -403,7 +403,7 @@ func scanResourcePage(ctx context.Context, file *editorFontXML, pages map[string
|
|||||||
return false, false, nil, nil
|
return false, false, nil, nil
|
||||||
}
|
}
|
||||||
for _, attr := range token.Attr {
|
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
|
return false, false, nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -432,10 +432,10 @@ func (obj *TextObject) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement
|
|||||||
}
|
}
|
||||||
*obj = TextObject(value)
|
*obj = TextObject(value)
|
||||||
for _, attr := range start.Attr {
|
for _, attr := range start.Attr {
|
||||||
if attr.Name.Space == textLayoutNamespace && attr.Name.Local == "Layout" {
|
if attr.Name.Space == textNamespace && attr.Name.Local == "Text" {
|
||||||
var meta textLayoutMetadata
|
var meta textMetadata
|
||||||
if json.Unmarshal([]byte(attr.Value), &meta) == nil && validateRichLayout(meta.Options) == nil && structureXMLText(meta.Text) && meta.Digest == obj.layoutDigest() {
|
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.Options, autoWidth: meta.AutoWidth}
|
obj.layout = &textLayout{value: meta.Text, options: meta.Layout, autoWidth: meta.AutoWidth}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type configuredTextShaper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LayoutText 按自有字体度量重排横向文字,保留绘制属性,可显式启用字体塑形
|
// LayoutText 按自有字体度量重排横向文字,保留绘制属性,可显式启用字体塑形
|
||||||
// 不修改文档,通过AddObject或UpdateObject提交;选项仅供当前编辑过程使用,保存为标准文字定位
|
// 不修改文档,通过AddObject或UpdateObject提交;选项随标准文字定位保存,重新打开时恢复
|
||||||
// 入参: obj 文字对象, value 原文, options 段落排版选项, metrics 字体度量
|
// 入参: obj 文字对象, value 原文, options 段落排版选项, metrics 字体度量
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func LayoutText(obj *TextObject, value string, options TextLayout, metrics FontMetrics) 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.TextCode = codes
|
||||||
obj.CGTransform = nil
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1242,14 +1242,14 @@ func (x *ofdXML) object(object GraphicObject, root bool) {
|
|||||||
switch object.Type {
|
switch object.Type {
|
||||||
case "TextObject", "Text":
|
case "TextObject", "Text":
|
||||||
obj := object.TextObject
|
obj := object.TextObject
|
||||||
metadata, err := obj.layoutMetadata()
|
metadata, err := obj.textMetadata()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
x.err = err
|
x.err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if metadata != "" {
|
if metadata != "" {
|
||||||
attrs.add("xmlns:ofdgo", textLayoutNamespace)
|
attrs.add("xmlns:ofdgo", textNamespace)
|
||||||
attrs.add("ofdgo:Layout", metadata)
|
attrs.add("ofdgo:Text", metadata)
|
||||||
}
|
}
|
||||||
attrs.add("ID", obj.ID)
|
attrs.add("ID", obj.ID)
|
||||||
attrs.add("DrawParam", obj.DrawParam)
|
attrs.add("DrawParam", obj.DrawParam)
|
||||||
|
|||||||
Reference in new issue
Block a user