mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 13:56:32 +08:00
1 parent
d384527371
commit
7e3f2c468a
6 files changed
+59
-9
No files matched your search
@@ -4314,7 +4314,7 @@ async function beginCanvasText(index, box, surface, page) {
|
||||
const node = document.createElement("div");
|
||||
const item = { ...state.textDefaults, ...box, wrap: state.textDefaults.wrapOverride ?? box.wrap ?? state.textDefaults.wrap,
|
||||
index, page, surface, node, text: "", draft: true, fontData: data };
|
||||
const model = richTextModel(item);
|
||||
const model = { ...richTextModel(item), autoWidth: !box.wrap };
|
||||
const key = `temp-font-${crypto.randomUUID()}`;
|
||||
model.runs[0].format.font = key;
|
||||
canvasEditor.editText(item, face, null, { model, faces: new Map([[key, face]]), fonts: [{ key, data }] });
|
||||
|
||||
@@ -390,7 +390,8 @@ export class RichTextSession {
|
||||
}
|
||||
render() {
|
||||
this.rendering = true;
|
||||
const input = this.editing.input, layout = this.model.layout, lines = [[]], positions = [];
|
||||
const input = this.editing.input, layout = this.model.layout, lines = [[]], positions = [], distributed = [];
|
||||
const autoWidth = this.model.autoWidth && !layout.wrap;
|
||||
Object.assign(input.style, { whiteSpace: layout.wrap ? "pre-wrap" : "pre",
|
||||
lineHeight: layout.lineHeight ? `${layout.lineHeight * PX_PER_MM}px` : "normal",
|
||||
textIndent: `${(layout.firstLineIndent || 0) * PX_PER_MM}px`, letterSpacing: `${(layout.letterSpacing || 0) * PX_PER_MM}px` });
|
||||
@@ -409,7 +410,7 @@ export class RichTextSession {
|
||||
const align = this.model.paragraphs?.[index]?.align || this.model.layout.align || "left";
|
||||
const distribute = align === "distributed" && !CSS.supports("text-justify", "inter-character");
|
||||
const key = JSON.stringify([parts.map(part => [part.text, part.format, this.faces.get(part.format.font)?.family]), align, layout, distribute ? input.offsetWidth : null]);
|
||||
let paragraph = previous[index]?.key === key && previous[index].node.parentNode === input ? previous[index].node : null;
|
||||
let paragraph = (!autoWidth || !distribute) && previous[index]?.key === key && previous[index].node.parentNode === input ? previous[index].node : null;
|
||||
if (!paragraph) {
|
||||
paragraph = document.createElement("div");
|
||||
for (const {text, format} of parts) {
|
||||
@@ -427,9 +428,15 @@ export class RichTextSession {
|
||||
}
|
||||
Object.assign(paragraph.style, { minWidth: layout.wrap ? "" : "max-content", textAlign: distribute ? "left" : align === "distributed" ? "justify" : align,
|
||||
textAlignLast: !distribute && align === "distributed" ? "justify" : "auto", textJustify: align === "distributed" ? "inter-character" : "auto" });
|
||||
if (distribute) distributeParagraph(input, paragraph);
|
||||
}
|
||||
if (distribute && (autoWidth || previous[index]?.node !== paragraph)) distributed.push(paragraph);
|
||||
if (input.childNodes[index] !== paragraph) input.insertBefore(paragraph, input.childNodes[index] || null);
|
||||
return { key, node: paragraph };
|
||||
});
|
||||
while (input.childNodes.length > lines.length) input.lastChild.remove();
|
||||
if (autoWidth) input.style.width = getComputedStyle(input).width;
|
||||
for (const paragraph of distributed) distributeParagraph(input, paragraph);
|
||||
for (const [index, paragraph] of [...input.children].entries()) {
|
||||
if (index) offset++;
|
||||
const walker = document.createTreeWalker(paragraph, NodeFilter.SHOW_TEXT);
|
||||
let node;
|
||||
@@ -437,9 +444,7 @@ export class RichTextSession {
|
||||
positions.push({ node, start: offset, end: offset + node.textContent.length });
|
||||
offset += node.textContent.length;
|
||||
}
|
||||
return { key, node: paragraph };
|
||||
});
|
||||
while (input.childNodes.length > lines.length) input.lastChild.remove();
|
||||
}
|
||||
input.style.fontSize = this.model.runs.length ? "0px" : `${this.typing.size * PX_PER_MM}px`;
|
||||
input.style.caretColor = this.typing.color || "#000000";
|
||||
this.editing.range = null;
|
||||
@@ -1929,7 +1934,7 @@ export class CanvasEditor {
|
||||
const { input, frame, item, rich } = editing;
|
||||
const layout = rich?.model.layout || item;
|
||||
const width = Math.max(0, frame.width - (layout.leftIndent || 0) - (layout.rightIndent || 0)) * PX_PER_MM + 4;
|
||||
Object.assign(input.style, { width: `${width}px`, minHeight: "0px", height: "auto" });
|
||||
Object.assign(input.style, { width: rich?.model.autoWidth && !layout.wrap ? "max-content" : `${width}px`, minWidth: "4px", minHeight: "0px", height: "auto" });
|
||||
}
|
||||
|
||||
setTextError(error) {
|
||||
|
||||
@@ -5071,7 +5071,7 @@ func (e *businessEngine) browserRichText(value ofdgo.RichText) map[string]any {
|
||||
paragraphs[i] = map[string]any{"align": paragraph.Align}
|
||||
}
|
||||
layout := value.Layout
|
||||
return map[string]any{"runs": runs, "paragraphs": paragraphs, "layout": map[string]any{
|
||||
return map[string]any{"autoWidth": value.AutoWidth, "runs": runs, "paragraphs": paragraphs, "layout": map[string]any{
|
||||
"autoBidi": layout.AutoBidi, "shape": layout.Shape,
|
||||
"shaping": map[string]any{
|
||||
"Bidi": layout.Shaping.Bidi, "Direction": layout.Shaping.Direction,
|
||||
|
||||
@@ -134,6 +134,9 @@ func (e *Editor) updateRichTextFrame(page int, id string, value RichText, offset
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if width > 0 {
|
||||
value.AutoWidth = false
|
||||
}
|
||||
if old.Type == "CompositeObject" && offset == 0 && (width == 0 || width == frame.W) && reflect.DeepEqual(normalizeRichText(current), normalizeRichText(value)) {
|
||||
return nil
|
||||
}
|
||||
@@ -356,6 +359,9 @@ func (e *Editor) updateCompositeRichText(page int, path ObjectPath, index int, v
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if width > 0 {
|
||||
value.AutoWidth = false
|
||||
}
|
||||
if offset == 0 && (width == 0 || width == frame.W) && reflect.DeepEqual(normalizeRichText(current), normalizeRichText(value)) {
|
||||
return nil
|
||||
}
|
||||
@@ -460,6 +466,10 @@ func (e *Editor) prepareRichText(box Box, value RichText) (GraphicObject, richTe
|
||||
if err != nil {
|
||||
return GraphicObject{}, richTextMetadata{}, err
|
||||
}
|
||||
box.W = result.width
|
||||
if value.AutoWidth && !value.Layout.Wrap {
|
||||
box.H = result.bounds.Y + result.bounds.H
|
||||
}
|
||||
padX, padY := -min(0, result.bounds.X), -min(0, result.bounds.Y)
|
||||
extent := Box{W: max(box.W, result.bounds.X+result.bounds.W) + padX, H: max(box.H, result.bounds.Y+result.bounds.H) + padY}
|
||||
if !finite(extent.W) || !finite(extent.H) || extent.W <= 0 || extent.H <= 0 {
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
|
||||
// richLayout 保存局部标准对象、含墨迹的边界及各片段的字体锚点序号
|
||||
type richLayout struct {
|
||||
width float64
|
||||
objects []GraphicObject
|
||||
bounds Box
|
||||
anchors []int
|
||||
@@ -124,6 +125,38 @@ func (e *Editor) layoutRichText(value RichText, width float64) (richLayout, erro
|
||||
}
|
||||
chars := []rune(value.Text())
|
||||
var face *typefont.Face
|
||||
if value.AutoWidth && !options.Wrap {
|
||||
width = max(1, options.LeftIndent+options.RightIndent+max(0, options.FirstLineIndent)+1)
|
||||
for start := 0; start <= len(chars); {
|
||||
end := start
|
||||
for end < len(chars) && chars[end] != '\n' {
|
||||
end++
|
||||
}
|
||||
paragraph := chars[start:end]
|
||||
local := richParagraphOptions(paragraph, options)
|
||||
if local.Shaping.Bidi && face == nil && len(paragraph) > 0 {
|
||||
face, err = typefont.ParseTTF(bytes.NewReader(metrics[owners[start]].font.Write()))
|
||||
if err != nil {
|
||||
return richLayout{}, err
|
||||
}
|
||||
}
|
||||
_, advances, err := richShapeLine(paragraph, owners[start:end], value, metrics, local, face)
|
||||
if err != nil {
|
||||
return richLayout{}, err
|
||||
}
|
||||
length := 0.0
|
||||
for _, advance := range advances {
|
||||
length += advance
|
||||
}
|
||||
width = max(width, local.LeftIndent+local.RightIndent+local.FirstLineIndent+length)
|
||||
if end == len(chars) {
|
||||
break
|
||||
}
|
||||
start = end + 1
|
||||
}
|
||||
available = width - options.LeftIndent - options.RightIndent
|
||||
}
|
||||
result.width = width
|
||||
var underlays []GraphicObject
|
||||
var shadows []GraphicObject
|
||||
var y float64
|
||||
|
||||
@@ -30,7 +30,9 @@ var ErrNotRichText = errors.New("object is not rich text")
|
||||
|
||||
// RichText 保存有序文字片段及段落选项,长度和范围使用Unicode码点而非字节
|
||||
// Paragraphs为空时所有段落沿用Layout,否则数量必须与LF分隔的段落一致
|
||||
// AutoWidth在不折行时按各段自然宽度确定文本框,指定宽度调整后关闭
|
||||
type RichText struct {
|
||||
AutoWidth bool `json:"autoWidth,omitempty"`
|
||||
Runs []RichTextRun `json:"runs"`
|
||||
Layout TextLayout `json:"layout"`
|
||||
Paragraphs []RichTextParagraph `json:"paragraphs,omitempty"`
|
||||
|
||||
Reference in new issue
Block a user